Home

Awesome

Carta

Python library for making GUI applications on the reMarkable tablet wrapped around the "simple" application

Simulating the ReMarkable device

It can be a pain having to transfer the script & run the script from the RM each time. Especially if you want to use the device for something else at the time. To help with this, I've made the option to manually set the command to a simple x64 binary that can be run on linux which will write to fb.pnm - This is the framebuffer file that you'll need to read from.

In the misc folder I've provided the binary and a basic script as an example. To use this, just initialize the ReMarkable object like so:

rm = ReMarkable(simple="path/to/simple/binay") 

Finally, checkout remarkable-sim for events (like button clicking) support

Using Carta

Carta's purpose is to function as a Python wrapper around sas - the simple app script for making apps on the reMarkable without need to compiling code. It's written in python and is designed to be as user-friendly and easy to use as possible.

To use it, you'll need to install a few things onto your reMarkable, in an SSH session:

After having installed all the prerequisites, you are ready to start developing. Make a python script on the rM and run chmod +x on the script to make it executable. You'll also need to add a shebang to the script (#!/opt/bin/python at the top) so that it knows what to run it with.

Here's an example script that you can copy:

#!/opt/bin/python 
from carta import ReMarkable, Widget 
rm = ReMarkable() 

my_button = Widget(id="but1", typ="button", value="Hello!", x="50%", y="50%")

rm.add(my_button)
rm.display()

Let's walk through it line by line,

from carta import ReMarkable, Widget 
rm = ReMarkable() 

This imports the necessary "parts" for the library to function. The ReMarkable class is the main "driver" code and handles everything from interacting with simple to display your app to managing the widgets themselves. The widget dataclass are the actual contents themselves and there are 6 core types;

Please refer to the simple spec for more detailed information.

my_button = Widget(id="but1", typ="button", value="Hello!", x="50%", y="50%")

Here we are making a new Widget which we have assigned to my_button. The following is a table outlining information about supported arguments which can be given.

ArgumentRequiredUseSupported inputsNotes
idYesUsed to uniquely identify a widget when being interacted withAny string that hasn't been used beforeSpaces will be converted to _
typYesGives a widget specific properties so it knows what to be used asAny from the following: label, paragraph, burtton, textinput, textarea, imageRange is a WIP
valueYesWhat will be displayed on the widgetAny string (Image path for the image widget type)Be careful about using new lines
xYesUsed to calculate the rendering position of the widgetA percentage, keyword or an integerSupported keywords are same (same value as previous item) and step (previous item's cord + previous items size)
yYesUsed to calculate the rendering position of the widgetA percentage, keyword or an integerSupported keywords are same (same value as previous item) and step (previous item's cord + previous items size)
widthNo (Automatically calculated)The width of the widgetAny integerYou might have some trouble with getting the right value for this
heightNo (Automatically calculated)The height of the widgetAny integerYou might have some trouble with getting the right value for this
justifyNo (Optional for buttons)Where the text sits in a buttonAny string from the following: center, right, left

Finally, we want to add the widget to the actual screen.

rm.add(my_button)
rm.display()

I've made the decision to have widgets be manually added like this to make it easier to manage when making apps). We can do this easily with add(args*), where args* are any Widget types - For example,

rm.add(my_button, label1, logo)

And then display it to the rM! The display() function will take all the currently active Widgets and convert them into something that simple can understand and render. The active widgets are all stored in the screen list (in this case rm.screen) and you can easily access and modify any of the widgets in there at any time via the lookup function which will return the Widget object that has the corresponding ID

print(rm.lookup("<id>"))

To remove a Widget you can use the remove() function and either supply it an ID or Widget object like so

rm.remove("<id>")

rm.remove(my_button)

In case you want to fully clear the screen, you can use the eclear() function - however this does require the modified updated simple binary which okeh can provide for you!

rm.eclear()

Finally, there are two other options that the ReMarkable class has that we can modify.

For example, if we wanted to make the fontsize 24 and timeout the program after 30 seconds we would do

rm.timeout = 32
rm.fontsize = 24

When you run the display function, carta will wait on any input being given OR the timeout being reached. When it's done this, it will return a dict object with the input given.

clicked = rm.display()

print(clicked) # {"<id>": True}

If it's a button that has been pressed it will return True as the value, if anything else has been given it will use that specific input (for example, from the keyboard) as the value.

Hope this guide was helpful, feel free to ask if you need some help (jake.sh on discord)