text:
# Awesome reMarkable [![Discord](https://img.shields.io/discord/385916768696139794.svg?label=reMarkable&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/u3P9sDW)

# [
](https://github.com/sindresorhus/awesome)

The [reMarkable](https://www.remarkable.com) is a paper tablet for those who prefer writing on paper, rather than keyboards. Its remarkably fast paper-white display, Linux based operating system and awesome community make it highly attractive amongst hackers and developers.

*Contributions are welcome as long as they follow the [guidelines](CONTRIBUTING.md).*

## Disclaimer

No project here is affiliated or endorsed by [reMarkable AS](https://github.com/remarkable). If you modify your device official support might refuse to help you.

### Write down your SSH password

**:warning: WARNING, READ THIS FIRST :warning:**

**Make sure you have saved your SSH password somewhere secure, or you have setup a [SSH key](https://remarkablewiki.com/tech/ssh#passwordless_login_with_ssh_keys)**

You can find the SSH password in your settings: `Settings > Help > Copyrights and licenses > General information (scroll down)`.

Failure to do so could mean a **bricked device** and would require a hardware plug to recover from the bricked state. You have been warned!

### Factory reset may brick your device

This function may not do what you are expecting. While it resets all user data, it will not restore the device to the original factory condition. It will reset your SSH password and remove all SSH keys, which may make it impossible to connect to your device if it is malfunctioning.

### Take special care if you are using a reMarkable 2.

- System recovery requires some hardware. Check [ddvk/remarkable2-recovery](https://github.com/ddvk/remarkable2-recovery) for what to do in case you lose ssh access.
- The screen on rm2 and rm1 are different. Workarounds have been developed to interact with the rM2 framebuffer but some projects might not work on it. See [remarkable2-framebuffer repo](https://github.com/ddvk/remarkable2-framebuffer) and [#14](https://github.com/ddvk/remarkable2-framebuffer/issues/14).

## Contents

- [APIs](#apis)
  - [Cloud API](#cloud-api)
  - [Lines Format](#lines-format)
  - [Other APIs](#other-apis)
- [Applications](#applications)
  - [Games](#games)
  - [Launchers](#launchers)
- [Cloud Tools](#cloud-tools)
- [Device Tools](#device-tools)
- [GUI Clients](#gui-clients)
- [Other](#other)
- [Screen Sharing/Streaming](#screen-sharingstreaming)
- [Custom Templates](#custom-templates)

## APIs

### Cloud API

- [google-drive-remarkable-sync](https://github.com/bsdz/google-drive-remarkable-sync) - Apps Script API for reMarkable Cloud. Includes Synchronizer capability to automate mirroring of documents from Google Drive to reMarkable Cloud.
- [jrmapi](https://github.com/jlarriba/jrmapi) - A Java API for the reMarkable Cloud.
- [reMarkableAPI](https://github.com/splitbrain/ReMarkableAPI) - Docs and implementation of the reMarkable file sync API.
- [reMarkable-typescript](https://github.com/Ogdentrod/reMarkable-typescript) - TypeScript API for reMarkable Cloud.
- [Remarkable.jl](https://github.com/theogf/Remarkable.jl) - Julia API Interface to the reMarkable cloud.
- [remarkdav](https://github.com/hansegucker/remarkdav) - A tool to sync PDF files from a WebDAV directory to the reMarkable Cloud.
- [rMAPI](https://github.com/juruen/rmapi) - ReMarkable Cloud Go API.
- [rmapy](https://github.com/subutux/rmapy) - ReMarkable Cloud Python API.
- [rmcl](https://github.com/rschroll/rmcl)- Asynchronous Python library for the reMarkable Cloud.
- [rmfakecloud](https://github.com/ddvk/rmfakecloud) - Fake Cloud Sync, server implementation of the Cloud API.

### Lines Format

- [lines-are-beautiful](https://github.com/ax3l/lines-are-beautiful) - C++ File API for the reMarkable tablet.
- [lines-are-rusty](https://github.com/ax3l/lines-are-rusty) - Rust File API for the reMarkable tablet.
- [reMarkable-kaitai](https://github.com/matomatical/reMarkable-kaitai) - [Kaitai Struct](https://kaitai.io/) format specification for the binary lines format.
- [reMarkable-layers](https://github.com/bsdz/remarkable-layers) - Python API for reading & writing reMarkable Lines format. Supports very basic conversion of PDFs and SVGs to Lines format.
- [rmrl](https://github.com/rschroll/rmrl) - The reMarkable Rendering Library for Python converts annotated documents to PDF files.

### Other APIs

- [libreMarkable](https://github.com/canselcik/libremarkable) - A framework for developing applications with native refresh support for reMarkable Tablet.

## Applications

- [harmony](https://rmkit.dev/apps/harmony) - a low latency sketching app with procedural brushes.
- [KOReader](https://github.com/koreader/koreader) - An ebook reader application supporting PDF, DjVu, EPUB, FB2 and many more formats.
- [darvin/plato](https://github.com/darvin/plato) [LinusCDE/plato](https://github.com/LinusCDE/plato) - Plato reader port. Supports pdfs, epubs, many other formats.
- [Rebook](https://github.com/fsniper/ReBook) - The missing book store for reMarkable.
- [reMarkable keywriter](https://github.com/dps/remarkable-keywriter) - A distraction free keyboard notes app.
- [reMarkable wikipedia](https://github.com/dps/remarkable-wikipedia) - Offline wikipedia reader for reMarkable.
- [remarkaBot](https://github.com/Davide95/remarkaBot) - Fetch your documents from Telegram.
- [whiteboard-hypercard](https://github.com/fenollp/reMarkable-tools) - Live collaboration, drawing, chat, whiteboarding.

### Games

- [chessMarkable](https://github.com/LinusCDE/chessmarkable) - Play chess against a bot or a friend.
- [DOOMarkable](https://github.com/LinusCDE/doomarkable) - Play DOOM on the reMarkable 1.
- [minesweeper](https://rmkit.dev/apps/minesweeper) - A mine detection game.
- [recrossable](https://github.com/sandsmark/recrossable) - Crossword game with simplistic handwriting recognition and automatic generation of crosswords.
- [retris](https://github.com/LinusCDE/retris) - Play a clone of the popular block stacking game with either buttons or swipe guestures.

### Launchers

- [draft-reMarkable](https://github.com/dixonary/draft-reMarkable) - A launcher for the reMarkable tablet, which wraps around the standard interface.
- [oxide](https://github.com/Eeems/oxide/releases) - A launcher application for the reMarkable tablet.
- [remux](https://rmkit.dev/apps/remux) - A multi-tasking launcher for the reMarkable tablet.

## Cloud Tools

- [CUPS Printing](https://ofosos.org/2018/10/22/printing-to-remarkable-cloud-from-cups/) - Script to print directly to reMarkable Cloud from CUPS using rMAPI.
- [mendeley-rMsync](https://github.com/anilkyelam/mendeley-rMsync) - Script to sync PDFs (with annotations) from/to a [Mendeley](https://www.mendeley.com/) folder.
- [reCatchable](https://github.com/lapwat/reCatchable) - Turn websites into ebooks, upload them to reMarkable.
- [reGitable](https://github.com/after-eight/regitable) - Backup your reMarkable with git and sync changes to a remote repository automatically.
- [remarkable_simplenote](https://github.com/bgribble/remarkable_simplenote) - Sync simplenote notes to reMarkable (currently one-way)
- [reMarkable-Sink](http://github.com/hmenzagh/reMarkable-Sink) - Turn a folder into a wormhole to your reMarkable.
- [reMarkable_syncthing](http://github.com/evidlo/remarkable_syncthing) - Syncthing on reMarkable.
- [remarkable-zapier](https://github.com/artes-dev/remarkable-zapier) - Zapier Integration for reMarkable Cloud
- [remarking](https://github.com/sabidib/remarking) - CLI tool to extract highlights from any document in the reMarkable cloud.
- [rm-pdf-tools](https://github.com/skius/rm-pdf-tools) - Service that allows users to insert and delete pages from annotated PDFs on the device.
- [rM-sync](https://github.com/simonschllng/rm-sync) - Sync script for reMarkable paper tablet.
- [RMfuse](https://github.com/rschroll/rmfuse) - FUSE filesystem for the reMarkable Cloud.
- [sync_zotero_remarkable](https://github.com/danijoo/sync_zotero_remarkable) - Sync PDFs from Zotero to reMarkable.
- [url2epub](https://github.com/fishy/url2epub) - Telegram bot to generate ePub out of URL and send directly to reMarkable Cloud.
- [zotero-reMarkable](https://github.com/michaelmior/zotero-remarkable) - Script to sync PDFs from the [Zotero](https://www.zotero.org/) reference manager.

## Device Tools
- [ReCept](https://github.com/funkey/recept/) - Fix for the rM2 jagged line issue.
- [rM-signature-patch](https://github.com/Barabazs/rM-signature-patch) - Simple script to remove that pesky advert at the bottom of a mail originating from a reMarkable.
- [WebInterface-Wifi](https://github.com/rM-self-serve/webinterface-wifi) - View the web interface if running, over wifi.

## GUI Clients

- [asTounding](https://github.com/jlarriba/astounding) -  A multiplatform GUI application for the reMarkable cloud, including Linux.
- [RemaDroid](https://play.google.com/store/apps/details?id=org.remadroid) - An alternative Android app to upload documents, webpages or images to the reMarkable tablet.
- [RemaPy](https://github.com/peerdavid/remapy) - GUI to browse, download/upload files and backup the tablet (also on Linux) using the cloud.
- [reMarkable-assistant](https://github.com/richeymichael/remarkable-assistant) - Manage templates, splash screens, and settings on your reMarkable tablet.
- [reMarkable Connection Utility (RCU)](http://www.davisr.me/projects/rcu/) - A cross-platform client for offline management of backups, screenshots, notebooks, templates, wallpaper, and third-party software.
- [reMarkable-hyutilities](https://github.com/moovida/remarkable-hyutilities) - A GUI written in java to backup your device, upload templates and modify splash screens.
- [ReMy](https://github.com/bordaigorl/remy) - A GUI to browse, preview documents, export documents with custom settings, all via SSH (no cloud needed); works from local raw backups too.
- [rMExplorer](https://github.com/bruot/pyrmexplorer/wiki) - GUI to browse, download/upload files and backup the tablet without using the cloud.
- [rmUploader](https://github.com/lobre/rmuploader) - Simple web app to upload epub or pdf files to the reMarkable tablet via drag and drop.
- [Slithin](https://github.com/furesoft/Slithin) - Free Management Application for Windows/Linux/MacOS

## Other

- [Calibre-Remarkable-Device-Driver-Plugin](https://github.com/naclander/Calibre-Remarkable-Device-Driver-Plugin) - A Calibre Plugin to Manage your Remarkable Books.
- [Crazy Cow](https://github.com/machinelevel/sp425-crazy-cow) - Typewriter input from USB keyboard directly into reMarkable interface.
- [Drawj2d](https://drawj2d.sourceforge.io/) - Create technical line drawings on an editable reMarkable notebook page. ([Guidance how to upload](https://sourceforge.net/p/drawj2d/wiki/reMarkable/) the page to the device using rMAPI.)
- [Funcky reMarkable Exporter](https://github.com/simonbaudart/Funcky.Remarkable.Exporter) - Export notes from a reMarkable Tablet to File System and External Services.
- [Goosepaper](https://github.com/j6k4m8/goosepaper): Deliver prettily-formatted RSS feeds, news articles, Wikipedia articles-of-the-day, and more to your reMarkable tablet.
- [instapaper-as-pdf-to-reMarkable](https://github.com/fabianmu/instapaper-as-pdf-to-remarkable) - Export Instapaper-Articles to PDF and send them to a connected rM tablet.
- [landscape-pdf](https://github.com/nmoran/landscape-pdf) - Utility to convert pdf documents to read in landscape mode. Useful for papers and text books.
- [morningpaper2reMarkable](https://github.com/jessfraz/morningpaper2remarkable) - A bot to sync the morning paper to a reMarkable tablet.
- [nix-remarkable](https://github.com/siraben/nix-remarkable) - Nix expressions for the reMarkable tablet leveraging the company's toolchain.
- [paper2reMarkable](https://github.com/GjjvdBurg/paper2remarkable) - Download an academic paper or HTML article, crop it, and send it to the reMarkable with a single command.
- [mail2rm](https://github.com/glatzor/mail2rm) - Mail PDF documents to your reMarkable cloud using your mail transport agent e.g. postfix.
- [Parabola-rM](http://www.davisr.me/projects/parabola-rm/) - A Desktop GNU/Linux-libre replacement OS with fast partial refreshing and USB OTG.
- [pdf2rmnotebook](https://github.com/JCN-9000/pdf2rmnotebook) - Creates a reMarkable Notebook from multiple PDF files.
- [pocket2rm](https://github.com/glidergeek/pocket2rm) - Synchronize articles from read-later platform pocket in PDF and epub.
- [remailable](https://github.com/j6k4m8/remailable) - Email PDFs directly to your reMarkable. ([Free publicly-hosted version available](https://remailable.getneutrality.org/)).
- [reHackable/maxio](https://github.com/reHackable/maxio) - Companion daemon for the reMarkable paper tablet.
- [reHackable/scripts](https://github.com/reHackable/scripts) - A set of bash scripts that may enhance your reMarkable experience.
- [reMarkable CLI tooling](https://github.com/cherti/remarkable-cli-tooling) - CLI-tooling to sync documents to a reMarkable, to clean deleted files etc. without needing cloud access
- [reMarkable-crosswords](https://github.com/shapeshed/remarkable-crosswords) - Get crosswords freshly delivered to your Remarkable every morning.
- [reMarkable_entware](http://github.com/evidlo/remarkable_entware) - Package manager for reMarkable.  Install common Unix utilities through the command line.
- [reMarkable_keyboard](https://github.com/Evidlo/remarkable_keyboard) - Software to use rM as wireless keyboard/mouse.
- [reMarkable_mouse](https://github.com/evidlo/remarkable_mouse) - Use your reMarkable as a graphics tablet.
- [remarkable_news](https://github.com/evidlo/remarkable_news) - Use daily news/comics/images as the suspend screen.
- [reMarkable_pdflets](https://github.com/evidlo/remarkable_pdflets) - Dynamically updating PDFs.
- [remarkable_printer](https://github.com/Evidlo/remarkable_printer) - Print from any device to reMarkable without browser extensions or reMarkable cloud.
- [reMarkable-fs](https://github.com/nick8325/remarkable-fs) - A FUSE filesystem wrapper for the reMarkable tablet.
- [reMarkable-random-screens](https://github.com/Neurone/reMarkable) - Change your poweroff and suspend screens every 5 minutes with random images of your choice
- [reMarkable-touchgestures](https://github.com/ddvk/remarkable-touchgestures) - Touch gestures (swipe/touch) for easy page navigation.
- [reMarkable-tweak](https://github.com/morngrar/remarkable-tweak) - Tweak tool for the reMarkable paper tablet. Lets you organize your templates with no fuss.
- [reMarkablePocket](https://github.com/nov1n/RemarkablePocket) - Synchronize articles from read-later platform Pocket in epub format.
- [reMarkableSync](https://github.com/jamesf91/reMarkableSync) - A Microsoft OneNote addin for importing notebooks from reMarkable as text or images.
- [remarks](https://github.com/lucasrla/remarks) - Extract highlights, scribbles, and annotations from PDFs. Export to Markdown, PNG, and SVG.
- [reMouseable](https://github.com/kevinconway/remouseable) - Use your reMarkable tablet as a mouse.
- [remt](https://gitlab.com/wrobell/remt) - reMarkable tablet command-line tools.
- [reSnap](https://github.com/cloudsftp/reSnap) - Take snapshots of your reMarkable screen over SSH.
- [rM-dl-annotated](https://github.com/jmptable/rm-dl-annotated) - Export annotated PDFs from reMarkable tablets.
- [rMsync](https://github.com/lschwetlick/rMsync) - Synchronisation script with a local dedicated "library" folder.
- [rmTabletDriver](https://github.com/LinusCDE/rmTabletDriver) - Application that allows you to simulate/clone rM input on your computer.
- [rmWacomToMouse](https://github.com/LinusCDE/rmWacomToMouse) - Use the wacom pen as a mouse to draw on your pc.
- [rmWebUiTools](https://github.com/LinusCDE/rmWebUiTools) - View a file tree, see statistics and export/backup all files with some simple scripts utilizing the web ui.
- [send_by_rmapi](https://github.com/LisaGlaser/send_by_rmapi) - A Calibre plugin to send books to your reMarkable, making use of rmapi.

## Screen Sharing/Streaming

- [pipes and paper](https://gitlab.com/afandian/pipes-and-paper) - Stream pen strokes to browser canvas via websockets ([blog post](https://blog.afandian.com/2020/10/pipes-and-paper-remarkable/)). Uses Python and SSH, nothing to compile or install on the reMarkable device.
- [pipes and paper enhanced](https://github.com/Pyrrhu5/pipes-and-paper-enhanced/tree/stable) - Share the pen strokes to a browser without installling anything on the ReMarkable (a revived fork of the previous link with pen colors and eraser support, responsive interface).
- [pipes and rust](https://github.com/AnyTimeTraveler/pipes-and-rust) - (Made for rM2) Stream pen strokes to browser. A single executable on the reMarkable that hosts a tiny webserver in the local WLAN.
- [reStream](https://github.com/rien/reStream) - Stream your reMarkable screen over SSH.
- [rMview](https://github.com/bordaigorl/rmview) - A fast GUI to stream your reMarkable screen over vanilla-SSH or VNC.
- [rM-vnc-server](https://github.com/peter-sa/rM-vnc-server) - A fast & efficient damage-tracking (sending only updated regions) VNC server for streaming the reMarkable's screen.
- [srvfb](https://github.com/merovius/srvfb) - Alternative screen-streaming software. Written in Go.
- [VNSee](https://github.com/matteodelabre/vnsee) - VNC client for the reMarkable tablet allowing you to use the device as a second screen.
- [goMarkableStream](https://github.com/owulveryck/goMarkableStream) - Stream the screen of the reMarkable 2 (FW 2.5) easily (client/server in Go with no installation) along with the colors (with FW > 2.11.x).

## Custom Templates

- [latex-yearly-planner](https://github.com/kudrykv/latex-yearly-planner) - PDF planner designed for e-ink devices.
- [ReCalendar.me](https://recalendar.me/) - Highly customizable online calendar generator optimized for reMarkable.
- [ReCalendar](https://github.com/klimeryk/recalendar) - Highly customizable calendar generator in PHP optimized for reMarkable.
- [reMarkable-bujo](https://github.com/vonneudeck/remarkable-bujo) - "Bullet Journal" templates.
- [remarkable-engineering](https://gitlab.com/asciiphil/remarkable-engineering) - Engineering-style grid templates.
- [reMarkable-gtd-templates](https://github.com/BartKeulen/remarkable-gtd-templates) - "Getting Things Done" templates.
- [reMarkable Templates](https://rm.ezb.io/) - A website to host and view community-built templates.
- [reMarkable-Templates](https://github.com/newtonhonk/reMarkable-Templates) - To Do templates with lines, checkboxes or text blocks.
- [reMarkable_templates](https://github.com/steka/reMarkable_templates) - White lines/squares on gray background.
- [reMarkabletemplates](https://github.com/equivocates/remarkabletemplates/) - Planner per 1 or 3 weeks.
- [rM2Mods templates](https://github.com/DanielRunningen/rM2Mods/tree/main/assests/templates) - Collection of different templates. E.g., micro dots/grids, checklists, budgeting,  boxes.
- [reMarkable planning/journaling templates](https://github.com/msencer/remarkable_templates) - Collection of daily/weekly planning, journaling templates

### Template Builders

- [NoTeTo](https://noteto.needleinthehay.de/) - Design templates by drag and drop components
- [Remarkable Grid Template Generator](https://xosh.org/remarkable-grid-template/) - Generate pixel perfect line grid and dotted grid templates
- [reMarkable Template Builder](https://templarian.github.io/remarkable/) - Generate Isometric and Grid templates of all sizes


text:
import requests #pip install requests
from bs4 import BeautifulSoup #pip install bs4
import pygame #pip install  pygame
import os
import time
import json
from colored import fg, attr

#Opening The Settings.json file
with open('settings.json','r') as file:
    settings = json.load(file)

# To play a ding if the product is in our budget
pygame.mixer.init()
pygame.mixer.music.load(settings["remind-sound-path"])

# Set your budjet
my_price = settings['budget']

# initializing Currency Symbols to substract it from our string
currency_symbols = ['€', '	£', '$', "¥", "HK$", "₹", "¥", "," ]

# the URL we are going to use
URL = settings['url']

# Google "My User Agent" And Replace It
headers = {"User-Agent": 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36'}

#Checking the price
def checking_price():
    page = requests.get(URL, headers=headers)
    soup  = BeautifulSoup(page.text, 'html.parser')

    #Finding the elements
    product_title = soup.find('span', id='productTitle').getText()
    product_price = soup.find('span', class_ = "a-offscreen").getText()

    # using replace() to remove currency symbols
    for i in currency_symbols :
        product_price = product_price.replace(i, '')

    #Converting the string to integer
    product_price = int(float(product_price))

    ProductTitleStrip = product_title.strip()
    print(f"{fg('green_1')}The Product Name is:{attr('reset')}{fg('dark_slate_gray_2')} {ProductTitleStrip}{attr('reset')}")
    print(f"{fg('green_1')}The Price is:{attr('reset')}{fg('orange_red_1')} {product_price}{attr('reset')}")

    # checking the price
    if(product_price<my_price):
        pygame.mixer.music.play()
        print(f"{fg('medium_orchid_1b')}You Can Buy This Now!{attr('reset')}")
        time.sleep(3) # audio will be played first then exit the program. This time for audio playing.
        exit()
    else:
        print(f"{fg('red_1')}The Price Is Too High!{attr('reset')}")

while True:
    checking_price()
    time.sleep(settings['remind-time']) #It is set to run the program once in an hour! You can change by changing the value in seconds!

repo_language: Python

mime_type: text/plain


text:
bs4
requests
pygame
colored


text:
# Amazon Price Tracker[![forthebadge](https://forthebadge.com/images/badges/made-with-python.svg)](https://forthebadge.com)
### This is a simple python script which can be used to track a products prices on [**Amazon**]([https://www.amazon.com](https://www.amazon.com/)). It uses Bs4 and Requests library to do the task.
---
### Instructions
 1. Make sure you have [**Python** ](https://www.python.org/)(Version 3.6 or higher) installed in your system.
 2. Open the folder in CMD or terminal and type ``` pip install -r requirements.txt```
 3. Now edit the **settings.json** file using your favorite code editor(Example: Sublime text editor, VSCode, Vim).
 4. Change ```budget:1000000```  value to your budget. (Example: ```budget:991001```
 5. Replace the link in **URL** with your link. (Example: ```url:'https://www.amazon.in/G731GT-Graphics-i5-9300H-Windows-G731GT-AU022T/dp/B07S36XJ8Q/'```)
 6. Change the **headers** in **main.py** according to your [**user agent**](https://www.google.com/search?q=my+user+agent&oq=my+user&aqs=chrome.1.69i57j0l5.2294j1j7&sourceid=chrome&ie=UTF-8). (Example: ```headers = {"User-Agent": 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36'} ```)
 7. Now save the file, close the editor and run **main.py**.
---
## Run this program on Startup
### Linux
https://help.ubuntu.com/stable/ubuntu-help/startup-applications.html.en
### Windows
https://support.microsoft.com/en-in/help/4026268/windows-10-change-startup-apps

---
### Be sure to stargaze the repository and also checkout [My Website](https://rohandas28.github.io/) to contact me.
#### Thank You.


text:
platform=$(shell uname -s)

BIN = tau
BINEMC = tau.html
EMCC = emcc
CC = clang

# MAC OS
ifeq ($(platform),Darwin)

CFLAGS = -std=c99 -pedantic -DUSE_SIMD_128
OBJCFLAGS = $(CFLAGS) -ObjC -fobjc-arc

.PHONY: debug
debug: CFLAGS += -g -Weverything -Wno-missing-noreturn -Wno-covered-switch-default
debug: CFLAGS += -Wno-padded -Wno-comma -Wno-declaration-after-statement
debug: CFLAGS += -Wno-double-promotion -Wno-float-equal
debug: CFLAGS += -Wno-unused-macros -Wno-unused-local-typedef -Wno-format-nonliteral
debug: CFLAGS += -Wc++-compat -Wno-unused-function -Wsign-conversion
debug: CFLAGS += -Wimplicit-int-conversion -Wimplicit-fallthrough -Wno-vla
debug: CFLAGS += -Wno-declaration-after-statement -Wno-direct-ivar-access
debug: CFLAGS += -Wno-deprecated-declarations -DDEBUG_MODE
debug: CFLAGS += -Wno-unsafe-buffer-usage -Wno-reserved-identifier
debug: OBJCFLAGS = -g -Weverything -Wno-missing-noreturn -Wno-covered-switch-default
debug: OBJCFLAGS += -Wno-padded -Wno-comma -Wno-double-promotion -Wno-float-equal
debug: OBJCFLAGS += -Wno-unused-macros -Wno-unused-local-typedef -Wno-format-nonliteral
debug: OBJCFLAGS += -Wno-unused-function -Wimplicit-int-conversion -Wimplicit-fallthrough
debug: OBJCFLAGS += -Wno-atomic-implicit-seq-cst -Wno-deprecated-declarations -Wno-vla
debug: OBJCFLAGS += -DDEBUG_MODE -std=c99 -pedantic -Wno-deprecated-declarations
debug: OBJCFLAGS += -Wno-declaration-after-statement -Wno-direct-ivar-access
debug: OBJCFLAGS += -Wno-unsafe-buffer-usage -Wno-reserved-identifier
debug: CC = clang
debug: $(BIN)

.PHONY: release
release: CFLAGS += -Wall -Wextra -O2 -fwhole-program -flto -fwrapv -mfma
release: CFLAGS += -DRELEASE_MODE
release: OBJCFLAGS = -Wall -Wextra -O2 -fwhole-program -flto -fwrapv
release: OBJCFLAGS += -DRELEASE_MODE -std=c99 -pedantic
release: CC = clang
release: $(BIN)

.PHONY: clean
clean:
	rm bin/tau src/sys/ren.o src/app.o src/res.o src/sys/sys_mac.o
	rm src/gui.o src/pck.o src/dbs.o
	rm bin/dbg.so bin/ren.so bin/app.so bin/res.so bin/gui.so bin/pck.so bin/dbs.so
	rm src/sys/gfx_mtl.air bin/gfx.metallib

$(BIN): src/sys/sys_mac.o src/app.o
	rm -r -f bin
	@mkdir -p bin
	xcrun -sdk macosx metal -c src/sys/gfx.metal -o src/sys/gfx.air
	xcrun -sdk macosx metallib src/sys/gfx.air -o bin/gfx.metallib
	$(CC) $(OBJCFLAGS) -c src/sys/sys_mac.m -o src/sys/sys_mac.o
	$(CC) $(CFLAGS) -o bin/$(BIN) src/app.c src/sys/sys_mac.o -framework Cocoa -framework Metal -framework MetalKit
	rm src/sys/gfx.air

else # UNIX

CFLAGS = -std=c99 -pedantic -DUSE_SIMD_256 -D_DEFAULT_SOURCE -msse4.1 -mavx2
CFLAGS += -D_POSIX_C_SOURCE=200809L

SYSLIBS = -L /usr/X11/lib -L /usr/local/lib -lX11 -ldl -lXext
SYSINCL = -I /usr/X11/include -I /usr/local/include

.PHONY: debug
debug: CFLAGS += -g -Weverything -Wno-missing-noreturn -Wno-covered-switch-default
debug: CFLAGS += -Wno-padded -Wno-comma -Wno-missing-field-initializers
debug: CFLAGS += -Wno-double-promotion -Wno-float-equal -Wno-switch -Wno-switch-enum
debug: CFLAGS += -Wno-unused-macros -Wno-unused-local-typedef -Wno-format-nonliteral
debug: CFLAGS += -Wc++-compat -Wno-unused-function
debug: CFLAGS += -Wimplicit-int-conversion -Wimplicit-fallthrough
debug: CFLAGS += -Wno-atomic-implicit-seq-cst
debug: CC = clang
debug: $(BIN)

.PHONY: release
release: CFLAGS += -Wall -Wextra -O2 -fwhole-program -flto -DRELEASE_MODE
release: CC = clang
release: $(BIN)

$(BIN): src/sys/sys_x11.o src/app.o
	rm -r -f bin
	@mkdir -p bin
	$(CC) $(CFLAGS) -c src/sys/sys_x11.m -o src/sys/sys_x11.o $(SYSLIBS) $(SYSINCL)
	$(CC) $(CFLAGS) -o bin/$(BIN) src/app.c src/sys/sys_x11.o $(SYSLIBS) $(SYSINCL)

endif

repo_name: vurtun/tau

repo_language: C

mime_type: text/x-c


text:
# Tau
Small sqlite database viewer immediate mode graphical user interface playground


text:
/* std */
#include <assert.h>
#include <stddef.h>
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/* sys */
#include "sys/cpu.h"
#include "lib/fmt.h"
#include "lib/std.h"
#include "lib/rpq.h"
#include "lib/img.h"
#include "lib/fnt.h"
#include "lib/sql.h"
#include "sys/gfx.h"
#include "sys/sys.h"
#include "gui/res.h"
#include "gui/gui.h"
#include "app/pck.h"
#include "app/dbs.h"

struct gui_app_key {
  int code;
  unsigned mod;
};
struct app_ui_shortcut {
  struct gui_app_key key;
  struct gui_app_key alt;
};
enum app_view_state {
  APP_VIEW_STATE_FILE,
  APP_VIEW_STATE_DB,
};
struct app_view {
  enum app_view_state state;
  enum app_view_state last_state;
  struct lst_elm hook;
  dyn(char) file_path;
  struct db_ui_view *db;
};
struct app {
  struct sys sys;
  struct res res;
  struct gui_ctx gui;
  struct mem_blk *db_mem;

  /* views */
  struct file_view *fs;
  int show_tab_lst;
  double tab_lst_off[2];
  dyn(struct app_view*) views;
  struct lst_elm del_lst;
  int sel_tab;
};

static struct sys_api sys;
static struct res_api res;
static struct gui_api gui;
static struct pck_api pck;
static struct db_api dbs;

#include "app/cfg.h"
#include "lib/fmt.c"
#include "lib/std.c"
#include "lib/math.c"
#include "lib/rpq.c"
#include "lib/img.c"
#include "lib/fnt.c"
#include "lib/sql.c"
#include "gui/res.c"
#include "gui/gui.c"
#include "app/pck.c"
#include "app/dbs.c"

/* -----------------------------------------------------------------------------
 *                                  App
 * ---------------------------------------------------------------------------*/
static struct app_view*
app_view_new(struct app *app) {
  assert(app);
  struct app_view *s = 0;
  if (lst_any(&app->del_lst)) {
    s = lst_get(lst_first(&app->del_lst), struct app_view, hook);
    lst_del(app->del_lst.nxt);
  } else {
    s = arena_alloc(app->sys.mem.arena, &app->sys, szof(*s));
  }
  lst_init(&s->hook);
  return s;
}
static void
app_view_del(struct app *app, struct app_view *view) {
  assert(app);
  assert(view);

  dyn_clr(view->file_path);
  lst_init(&view->hook);
  lst_add(&app->del_lst, &view->hook);
}
static void
app_view_setup(struct app *app, struct app_view *view) {
  assert(view);
  assert(app);

  view->state = APP_VIEW_STATE_FILE;
  view->last_state = view->state;
  if (!view->file_path) {
    view->file_path = arena_dyn(app->sys.mem.arena, &app->sys, char, 256);
  }
}
static void
app_view_init(struct app_view *view, struct app *app, struct str path) {
  assert(view);
  assert(app);

  app_view_setup(app, view);
  dyn_asn_str(view->file_path, &app->sys, path);
  view->state = APP_VIEW_STATE_DB;
}
static void
app_open_files(struct app *app, const struct str *files, int cnt) {
  assert(app);
  assert(sys);
  assert(files);
  for_cnt(i,cnt) {
    /* open each database in new tab */
    struct app_view *view = app_view_new(app);
    view->db = dbs.new(&app->gui, app->sys.mem.arena, app->sys.mem.tmp, files[i]);
    if (!view->db) {
      app_view_del(app, view);
      continue;
    }
    app_view_init(view, app, files[i]);
    app->sel_tab = dyn_cnt(app->views);
    dyn_add(app->views, &app->sys, view);
  }
}
static void
app_swap_view(struct app *app, int dst_idx, int src_idx) {
  assert(app);
  assert(dst_idx < dyn_cnt(app->views));
  assert(src_idx < dyn_cnt(app->views));

  struct app_view *dst = app->views[dst_idx];
  struct app_view *src = app->views[src_idx];

  app->views[dst_idx] = src;
  app->views[src_idx] = dst;
}
static void
app_init(struct app *app) {
  assert(app);
  app->sys.win.title = "Tau";
  app->sys.win.x = -1;
  app->sys.win.y = -1;
  app->sys.win.w = 800;
  app->sys.win.h = 600;
  app->sys.gfx.clear_color = col_rgb(0,0,0);
  sys.init(&app->sys);
  {
    struct res_args args = {0};
    args.run_cnt = KB(2);
    args.hash_cnt = KB(4);
    args.sys = &app->sys;
    res.init(&app->res, &args);
  }
  {
    struct gui_args args = {0};
    args.scale = app->sys.dpi_scale;
    if (app->sys.has_style){
      args.scm = GUI_COL_SCHEME_SYS;
    } else {
      args.scm = GUI_COL_SCHEME_DARK;
    }
    args.txt_mem = KB(2);
    args.vtx_mem = KB(64);
    args.idx_mem = KB(64);

    app->gui.sys = &app->sys;
    app->gui.res = &app->res;
    gui.init(&app->gui, app->sys.mem.arena, &args);
  }
  app->fs = pck.init(&app->sys, &app->gui, app->sys.mem.arena, app->sys.mem.tmp);
  app->db_mem = app->sys.mem.alloc(&app->sys, 0, CFG_DB_MAX_MEMORY, 0, 0);
  app->views = arena_dyn(app->sys.mem.arena, &app->sys, struct app_view*, 16);
  lst_init(&app->del_lst);
  dbs.init(app->db_mem);

  struct app_view *view = app_view_new(app);
  app_view_setup(app, view);
  dyn_add(app->views, &app->sys, view);
}
static void
app_shutdown(struct app *app) {
  assert(app);
  fori_dyn(i, app->views) {
    struct app_view *view = app->views[i];
    if (view->db) {
      dbs.del(view->db, &app->sys);
    }
    dyn_free(view->file_path, &app->sys);
    app_view_del(app, view);
  }
  dyn_free(app->views, &app->sys);

  pck.shutdown(app->fs, &app->sys);
  gui.free(&app->gui);
  res.shutdown(&app->res);
  sys.shutdown(&app->sys);
}

/* -----------------------------------------------------------------------------
 *                                  GUI
 * -----------------------------------------------------------------------------
 */
static void
ui_app_view(struct app *app, struct app_view *view, struct gui_ctx *ctx,
            struct gui_panel *pan, struct gui_panel *parent) {
  assert(app);
  assert(ctx);
  assert(pan);
  assert(parent);
  gui.pan.begin(ctx, pan, parent);
  {
    struct gui_panel bdy = {.box = pan->box};
    switch (view->state) {
    case APP_VIEW_STATE_FILE: {
      if (pck.ui(&view->file_path, app->fs, ctx, &bdy, pan)) {
        struct str file_path = dyn_str(view->file_path);
        view->db = dbs.new(&app->gui, app->sys.mem.arena, app->sys.mem.tmp, file_path);
        view->state = APP_VIEW_STATE_DB;
        if (view->db) {
          struct app_view *new_view = app_view_new(app);
          app_view_setup(app, new_view);
          dyn_add(app->views, &app->sys, new_view);
          app->sys.repaint = 1;
        }
      }
    } break;
    case APP_VIEW_STATE_DB: {
      dbs.ui(view->db, ctx, &bdy, pan);
    } break;
    }
  }
  gui.pan.end(ctx, pan, parent);
}
static void
ui_app_dnd_files(struct app *app, struct gui_ctx *ctx, struct gui_panel *pan) {
  assert(app);
  assert(ctx);
  assert(pan);
  if (gui.dnd.dst.begin(ctx, pan)) {
    struct gui_dnd_paq *paq = gui.dnd.dst.get(ctx, STR_HASH16("[sys:files]"));
    if (paq) { /* file drag & drop */
      const struct str *file_urls = paq->data;
      switch (paq->state) {
      case GUI_DND_DELIVERY: {
        int file_cnt = paq->size;
        app_open_files(app, file_urls, file_cnt);
        paq->response = GUI_DND_ACCEPT;
      } break;
      case GUI_DND_LEFT: break;
      case GUI_DND_ENTER:
      case GUI_DND_PREVIEW: {
        paq->response = GUI_DND_ACCEPT;
      } break;}
    }
    gui.dnd.dst.end(ctx);
  }
}
static int
ui_app_tab_view_lst(struct app *app, struct gui_ctx *ctx, struct gui_panel *pan,
                     struct gui_panel *parent) {
  assert(app);
  assert(ctx);
  assert(pan);
  assert(parent);

  int ret = -1;
  gui.pan.begin(ctx, pan, parent);
  {
    struct gui_lst_cfg cfg = {0};
    gui.lst.cfg(&cfg, dyn_cnt(app->views), app->tab_lst_off[1]);
    cfg.ctl.focus = GUI_LST_FOCUS_ON_HOV;
    cfg.sel.on = GUI_LST_SEL_ON_HOV;

    struct gui_lst_reg reg = {.box = pan->box};
    gui.lst.reg.begin(ctx, &reg, pan, &cfg, app->tab_lst_off);
    for_gui_lst_reg(i,gui,&reg) {
      struct gui_panel elm = {0};
      struct app_view *view = app->views[i];
      unsigned long long tab_id = hash_ptr(view);
      gui.lst.reg.elm.txt(ctx, &reg, &elm, tab_id, 0, strv("Open"), 0);

      struct gui_input in = {0};
      gui.pan.input(&in, ctx, &elm, GUI_BTN_LEFT);
      ret = in.mouse.btn.left.clk ? i : ret;
    }
    gui.lst.reg.end(ctx, &reg, pan, app->tab_lst_off);
  }
  gui.pan.end(ctx, pan, parent);
  return ret;
}
static int
ui_app_view_tab_slot_close(struct gui_ctx *ctx, struct gui_panel *pan,
                           struct gui_panel *parent, struct str title,
                           enum res_ico_id ico) {
  assert(ctx);
  assert(pan);
  assert(parent);
  assert(ico);

  int ret = 0;
  gui.pan.begin(ctx, pan, parent);
  {
    struct gui_box lay = pan->box;
    struct gui_icon close = {.box = gui.cut.rhs(&lay, ctx->cfg.item, 0)};
    gui.ico.clk(ctx, &close, pan, RES_ICO_CLOSE);
    ret = close.clk;

    struct gui_panel lbl = {.box = lay};
    gui.ico.box(ctx, &lbl, pan, ico, title);
  }
  gui.pan.end(ctx, pan, parent);
  return ret;
}
static int
ui_app_view_tab(struct app *app, struct app_view *view,
                struct gui_ctx *ctx, struct gui_tab_ctl *tab,
                struct gui_tab_ctl_hdr *hdr, struct gui_panel *slot,
                struct str title, enum res_ico_id ico) {
  assert(app);
  assert(view);
  assert(ctx);
  assert(tab);
  assert(hdr);
  assert(slot);
  assert(ico);

  int ret = 0;
  gui.tab.hdr.slot.begin(ctx, tab, hdr, slot, hash_ptr(view));
  if (dyn_cnt(app->views) > 1 && tab->idx == tab->sel.idx) {
    ret = ui_app_view_tab_slot_close(ctx, slot, &hdr->pan, title, ico);
  } else {
    gui.ico.box(ctx, slot, &hdr->pan, ico, title);
  }
  gui.tab.hdr.slot.end(ctx, tab, hdr, slot, 0);
  return ret;
}
static void
ui_app_main(struct app *app, struct gui_ctx *ctx, struct gui_panel *pan,
            struct gui_panel *parent) {
  unused(app);
  assert(app);
  assert(ctx);
  assert(pan);
  assert(parent);

  gui.pan.begin(ctx, pan, parent);
  {
    /* tab control */
    struct gui_tab_ctl tab = {.box = pan->box, .show_btn = 1};
    gui.tab.begin(ctx, &tab, pan, dyn_cnt(app->views), app->sel_tab);
    {
      /* tab header */
      int del_tab = 0;
      struct gui_tab_ctl_hdr hdr = {.box = tab.hdr};
      gui.tab.hdr.begin(ctx, &tab, &hdr);
      for_cnt(i, tab.cnt) {
        /* tab header slots */
        struct app_view *view = app->views[i];
        enum res_ico_id ico = RES_ICO_FOLDER_OPEN;
        struct str title = strv("Open");
        if (view->state != APP_VIEW_STATE_FILE) {
          title = path_file(dyn_str(view->file_path));
          ico = RES_ICO_DATABASE;
        }
        struct gui_panel slot = {0};
        if (ui_app_view_tab(app, app->views[i], ctx, &tab, &hdr, &slot, title, ico)) {
          del_tab = 1;
        }
      }
      gui.tab.hdr.end(ctx, &tab, &hdr);
      if (tab.sort.mod) {
        app_swap_view(app, tab.sort.dst, tab.sort.src);
      }
      if (del_tab) {
        assert(dyn_any(app->views));
        assert(app->sel_tab < dyn_cnt(app->views));
        /* close database view tab */
        struct app_view *view = app->views[app->sel_tab];
        dyn_rm(app->views, app->sel_tab);
        app_view_del(app, view);
        app->sel_tab = clamp(0, tab.sel.idx, dyn_cnt(app->views)-1);
      }
      struct gui_btn add = {.box = hdr.pan.box};
      add.box.x = gui.bnd.min_ext(tab.off, ctx->cfg.item);
      if (gui.btn.ico(ctx, &add, &hdr.pan, RES_ICO_FOLDER_ADD)) {
        /* open new file view tab */
        struct app_view *view = app_view_new(app);
        app->sel_tab = dyn_cnt(app->views);
        dyn_add(app->views, ctx->sys, view);
      }
      /* tab body */
      struct gui_panel bdy = {.box = tab.bdy};
      app->show_tab_lst = tab.btn.clk ? !app->show_tab_lst : app->show_tab_lst;
      if (app->show_tab_lst) {
        /* tab selection */
        int ret = ui_app_tab_view_lst(app, ctx, &bdy, pan);
        if (ret >= 0) {
          app_swap_view(app, 0, ret);
          app->show_tab_lst = 0;
          app->sel_tab = 0;
        }
      } else {
        ui_app_view(app, app->views[app->sel_tab], ctx, &bdy, pan);
      }
    }
    gui.tab.end(ctx, &tab, pan);
    if (tab.sel.mod) {
      app->sel_tab = tab.sel.idx;
    }
  }
  gui.pan.end(ctx, pan, parent);
  ui_app_dnd_files(app, ctx, pan);
}

/* -----------------------------------------------------------------------------
 *                                  Main
 * -----------------------------------------------------------------------------
 */
static int
app_on_mod(unsigned mod, unsigned keymod) {
  if (mod == 0) {
    return 1;
  } else if (mod == (unsigned)-1) {
    return keymod == 0;
  } else {
    return keymod == mod;
  }
}
static void
usage(const char *app) {
  printf("\n"
      "usage: %s [options] file0 file1\n"
      "\n"
      "   arguments:\n"
      "\n"
      "   file,         Input sqlite database\n"
      "\n"
      "   options:\n"
      "   -h            help message\n"
      "\n",
      app
  );
  exit(1);
}
extern int
main(int argc, char **argv) {
  const char *exe = 0;
  /* api */
  sys_api(&sys, 0);
  res_api(&res, 0);
  gui_api(&gui, 0);
  pck_api(&pck, 0);
  db_api(&dbs, 0);

  /* init */
  struct app app = {0};
  cmd_arg_begin(exe, argc, argv){
  case 'h': default: usage(exe); break;
  } cmd_arg_end
  app_init(&app);

  /* run */
  while (app.sys.running) {
    sys.pull(&app.sys);
    fori_dyn(i, app.views) {
      struct app_view *view = app.views[i];
      switch (view->state) {
      case APP_VIEW_STATE_FILE: break;
      case APP_VIEW_STATE_DB:
        dbs.update(view->db, &app.sys);
        break;
      }
    }
    pck.update(app.fs, &app.sys);

    /* gui */
    if (app.sys.style_mod) {
      gui.color_scheme(&app.gui, CFG_COLOR_SCHEME);
    }
    fori_arrv(i, app_ui_key_tbl) {
      /* map system keys to ui shortcuts */
      const struct app_ui_shortcut *s = app_ui_key_tbl + i;
      struct gui_ctx *ctx = &app.gui;
      int keymod = app_on_mod(s->key.mod, app.sys.keymod);
      if (bit_tst(app.sys.keys, s->key.code) && keymod) {
        bit_set(ctx->keys, i);
      } else if (bit_tst(app.sys.keys, s->alt.code) && keymod) {
        bit_set(ctx->keys, i);
      }
    }
    while (gui.begin(&app.gui)) {
      struct gui_panel pan = {.box = app.gui.box};
      ui_app_main(&app, &app.gui, &pan, &app.gui.root);
      gui.end(&app.gui);
    }
    sys.push(&app.sys);
  }
  app_shutdown(&app);
  return 0;
}


text:
#define DBS_VERISON 1

struct arena;
struct gui_api;
struct gui_ctx;
struct gui_panel;
struct db_ui_view;

struct db_api {
  int version;
  int (*init)(struct mem_blk *blk);
  struct db_ui_view* (*new)(struct gui_ctx *ctx, struct arena *mem, struct arena *tmp_mem, struct str path);
  void (*update)(struct db_ui_view *db, struct sys *sys);
  void (*del)(struct db_ui_view *db, struct sys *sys);
  void (*ui)(struct db_ui_view *db, struct gui_ctx *ctx, struct gui_panel *pan, struct gui_panel *parent);
};


text:
// clang-format off

#if 1
#define CFG_COLOR_SCHEME        GUI_COL_SCHEME_SYS
#define CFG_COLOR_TEXT          0xFFE0E0E0
#elif 0
#define CFG_COLOR_SCHEME        GUI_COL_SCHEME_DARK
#define CFG_COLOR_TEXT          0xFFE0E0E0
#elif 0
#define CFG_COLOR_SCHEME        GUI_COL_SCHEME_STEAM
#define CFG_COLOR_TEXT          0xFFF0F0F0
#else
#define CFG_COLOR_SCHEME        GUI_COL_SCHEME_GRAY
#define CFG_COLOR_TEXT          0xFF101010
#endif

#define CFG_DB_MAX_MEMORY       MB(1)

/* shortcuts  */
static const struct app_ui_shortcut app_ui_key_tbl[GUI_KEY_CNT] = {
  [GUI_KEY_ACT]                = {.key = {SYS_KEY_RETURN, 0}},
  [GUI_KEY_DEACT]              = {.key = {SYS_KEY_ESCAPE, 0}},
  [GUI_KEY_NEXT_WIDGET]        = {.key = {SYS_KEY_TAB, (unsigned)-1}},
  [GUI_KEY_PREV_WIDGET]        = {.key = {SYS_KEY_TAB, SYS_KEYMOD_CTRL}},
  [GUI_KEY_TAB_NEXT]           = {.key = {SYS_KEY_LEFT, 0}},
  [GUI_KEY_TAB_PREV]           = {.key = {SYS_KEY_RIGHT, 0}},
  [GUI_KEY_SPIN_INC]           = {.key = {SYS_KEY_UP, 0}},
  [GUI_KEY_SPIN_DEC]           = {.key = {SYS_KEY_DOWN, 0}},
  [GUI_KEY_MENU_NXT]           = {.key = {SYS_KEY_RIGHT, 0}},
  /* list */
  [GUI_KEY_LST_LEFT]           = {.key = {SYS_KEY_LEFT, 0}},
  [GUI_KEY_LST_RIGHT]          = {.key = {SYS_KEY_RIGHT, 0}},
  [GUI_KEY_LST_UP]             = {.key = {SYS_KEY_UP,0}},
  [GUI_KEY_LST_DN]             = {.key = {SYS_KEY_DOWN,0}},
  [GUI_KEY_LST_SEL]            = {.key = {SYS_KEY_SPACE,0}},
  [GUI_KEY_LST_SEL_ALL]        = {.key = {'a',SYS_KEYMOD_CTRL}},
  [GUI_KEY_LST_CPY]            = {.key = {'c',SYS_KEYMOD_CTRL}},
  [GUI_KEY_LST_CUT]            = {.key = {'x',SYS_KEYMOD_CTRL}},
  [GUI_KEY_LST_PASTE]          = {.key = {'v',SYS_KEYMOD_CTRL}},
  [GUI_KEY_LST_DEL]            = {.key = {SYS_KEY_DEL,0}},
  [GUI_KEY_LST_RET]            = {.key = {SYS_KEY_RETURN,0}},
  /* tree */
  [GUI_KEY_TREE_EXPAND]        = {.key = {SYS_KEY_PLUS, 0}},
  [GUI_KEY_TREE_COLLAPSE]      = {.key = {SYS_KEY_MINUS, 0}},
  [GUI_KEY_TREE_LEFT]          = {.key = {SYS_KEY_LEFT, 0}},
  [GUI_KEY_TREE_RIGHT]         = {.key = {SYS_KEY_RIGHT, 0}},
  /* scroll */
  [GUI_KEY_SCRL_PGDN]          = {.key = {SYS_KEY_PGDN,0}},
  [GUI_KEY_SCRL_PGUP]          = {.key = {SYS_KEY_PGUP,0}},
  [GUI_KEY_SCRL_BEGIN]         = {.key = {SYS_KEY_HOME,0}},
  [GUI_KEY_SCRL_END]           = {.key = {SYS_KEY_END,0}},
  /* edit */
  [GUI_KEY_EDIT_SEL_ALL]       = {.key = {'a',SYS_KEYMOD_CTRL}},
  [GUI_KEY_EDIT_CUR_LEFT]      = {.key = {SYS_KEY_LEFT,(unsigned)-1}},
  [GUI_KEY_EDIT_CUR_RIGHT]     = {.key = {SYS_KEY_RIGHT,(unsigned)-1}},
  [GUI_KEY_EDIT_CUR_UP]        = {.key = {SYS_KEY_UP, (unsigned)-1}},
  [GUI_KEY_EDIT_CUR_DOWN]      = {.key = {SYS_KEY_DOWN, (unsigned)-1}},
  [GUI_KEY_EDIT_SEL_CUR_LEFT]  = {.key = {SYS_KEY_LEFT,SYS_KEYMOD_SHIFT}},
  [GUI_KEY_EDIT_SEL_CUR_RIGHT] = {.key = {SYS_KEY_RIGHT,SYS_KEYMOD_SHIFT}},
  [GUI_KEY_EDIT_SEL_CUR_UP]    = {.key = {SYS_KEY_UP,SYS_KEYMOD_SHIFT}},
  [GUI_KEY_EDIT_SEL_CUR_DOWN]  = {.key = {SYS_KEY_DOWN,SYS_KEYMOD_SHIFT}},
  [GUI_KEY_EDIT_DELETE]        = {.key = {SYS_KEY_DEL,0}},
  [GUI_KEY_EDIT_REMOVE]        = {.key = {SYS_KEY_BACKSPACE,0}},
  [GUI_KEY_EDIT_START]         = {.key = {SYS_KEY_HOME,(unsigned)-1}},
  [GUI_KEY_EDIT_END]           = {.key = {SYS_KEY_END,(unsigned)-1}},
  [GUI_KEY_EDIT_SEL_START]     = {.key = {SYS_KEY_HOME,SYS_KEYMOD_SHIFT}},
  [GUI_KEY_EDIT_SEL_END]       = {.key = {SYS_KEY_END,SYS_KEYMOD_SHIFT}},
  [GUI_KEY_EDIT_COMMIT]        = {.key = {SYS_KEY_RETURN,0}},
  [GUI_KEY_EDIT_ABORT]         = {.key = {SYS_KEY_ESCAPE,0}},
  [GUI_KEY_EDIT_COPY]          = {.key = {'c', SYS_KEYMOD_CTRL}},
  [GUI_KEY_EDIT_CUT]           = {.key = {'x', SYS_KEYMOD_CTRL}},
  [GUI_KEY_EDIT_PASTE]         = {.key = {'v', SYS_KEYMOD_CTRL}},
  [GUI_KEY_EDIT_UNDO]          = {.key = {'z', SYS_KEYMOD_CTRL}},
  [GUI_KEY_EDIT_REDO]          = {.key = {'z', SYS_KEYMOD_CTRL|SYS_KEYMOD_SHIFT}},
  [GUI_KEY_EDIT_CLR_MODE]      = {.key = {SYS_KEY_ESCAPE, 0}},
  [GUI_KEY_EDIT_WORD_LEFT]     = {.key = {SYS_KEY_LEFT, SYS_KEYMOD_CTRL}},
  [GUI_KEY_EDIT_WORD_RIGHT]    = {.key = {SYS_KEY_RIGHT, SYS_KEYMOD_CTRL}},
  [GUI_KEY_EDIT_SEL_WORD_LEFT] = {.key = {SYS_KEY_LEFT, SYS_KEYMOD_CTRL|SYS_KEYMOD_SHIFT}},
  [GUI_KEY_EDIT_SEL_WORD_RIGHT]= {.key = {SYS_KEY_RIGHT, SYS_KEYMOD_CTRL|SYS_KEYMOD_SHIFT}},
};
// clang-format on


text:
#define PCK_VERISON 1

struct arena;
struct gui_api;
struct gui_ctx;
struct gui_panel;
struct file_view;

struct pck_api {
  int version;
  struct file_view* (*init)(struct sys *sys, struct gui_ctx *ctx, struct arena *mem, struct arena *tmp);
  void (*update)(struct file_view*, struct sys *sys);
  void (*shutdown)(struct file_view*, struct sys *sys);
  int (*ui)(char **filepath, struct file_view*, struct gui_ctx *ctx,
            struct gui_panel *pan, struct gui_panel *parent);
};
static void pck_api(void *export, void *import);