Docs Desktop
Desktop#
The Qt desktop is Tomacco's visual workspace. It calls the same typed operations as
tomaccoctl and the MCP server, so everything shown here is also available to agents
(see the agent guide).
On this page (34)
- Starting the app
- First start
- Languages
- Appearance
- Updates
- Searching the settings
- Moving settings to another computer
- Window layouts
- Navigation and shortcuts
- When something fails
- Activity
- Builds and jobs
- Notifications
- Agents and tools
- Installing an app by dropping it
- Devices page
- Nicknames and pins
- All screens
- Live log
- Pairing over Wi-Fi
- Touches and coordinates
- Log beside the device
- Screen recording
- Device window
- Omarchy panel
- Omarchy keys, menu and commands
- Toolchains and platform setup
- Visual layout lab
- Lab controls
- Color scheme, motion and network
- App layout metadata
- Scope and device data
- Custom devices
- Visual checks
Where this guide names a button, the German label follows in italics.
Starting the app#
tomacco
tomacco --device-only
tomacco --project /path/to/app --workspace runtime
tomacco --url http://localhost:5173
tomacco --folder /path/to/project/build/web
tomacco --device duo --fold open --orientation landscape
Open Tomacco in the application menu to enter the native project workspace. Explicit URL, folder or device arguments open the visual layout lab instead (see Visual layout lab).
TOMACCO_STARTUP_TIMING=1 prints on standard error how long the start took: until the window's
first frame and until it is ready, which is when tomaccoctl has also answered the operation list and
the first job list. The debugger, UI tests, performance, release and the iOS test system are built when
they are first shown, and the web preview checks for xtool and swift when it is first shown.
Tomacco remembers the last eight project folders you opened. The start screen
shows the three newest besides the open one under Recent (Zuletzt), and
the command palette (Ctrl+K) finds all of them as Open recent project
(Zuletzt geöffnetes Projekt … öffnen). Folders that no longer exist are left out.
A project folder dragged from the file manager onto the window opens as the
project and shows its overview; a single file or an .app bundle does not.
Each project keeps its own choices on this computer: the device it ran on last with its platform and Apple target, and the platform, configuration and format of its build under Project. When the project opens again they are chosen again; a device that is not connected yet is chosen as soon as it is, unless another device is live or you pick one first. Choosing a device, running the app or changing a build choice stores it. The 20 most recently used projects are kept; the settings export leaves these choices out, as they name folders.
First start#
On the very first start the desktop shows Welcome to Tomacco (Willkommen bei Tomacco). It runs doctor
and lists what is left to set up in plain words, such as the Android SDK or iOS builds, or says that everything
is in place. Connect your coding agent (Deinen Coding-Agenten verbinden) copies the MCP configuration to
paste into your agent's MCP settings; Skip (Überspringen) closes the sheet. It shows by itself only once;
Get started (Erste Schritte) in the command palette (Ctrl+K) opens it again.
Languages#
The desktop speaks English, German, French, Spanish and Italian. It follows the desktop language: the
first of the system's UI languages that it has, English otherwise. Choose System,
English, Deutsch, Français, Español or Italiano under Language in Settings (Ctrl+,); the
choice is stored and applies at once. TOMACCO_LANGUAGE=en|de|fr|es|it overrides it for one
run. tomaccoctl and MCP output always stay English.
Appearance#
The desktop has a dark and a light theme. It follows the desktop's dark or light
preference, and is dark when the desktop states none. Choose System, Dark or
Light under Appearance in Settings (Ctrl+,); the choice is stored and
applies at once. TOMACCO_THEME=dark|light overrides it for one run. Animations
stop when the desktop asks for reduced motion (Qt 6.12 or later). The fonts,
Archivo and JetBrains Mono, ship with the app.
On Omarchy, Use the Omarchy theme's accent (Akzentfarbe des Omarchy-Themes verwenden) under
Appearance takes the accent colour of the active Omarchy theme instead of Tomacco's red. Tomacco reads
the theme's colors.toml from ~/.local/state/omarchy/current/theme (or $XDG_STATE_HOME; older
releases keep it in ~/.config/omarchy/current/theme) and never writes to it. The accent is used only
where it reads clearly: at least 3:1 against Tomacco's background and against the theme's own
background. Otherwise Tomacco keeps its red, and the setting says so. Another theme set in Omarchy is
picked up while Tomacco runs. The choice is off by default and moves with the exported settings.
Updates#
The Updates part of Settings (Ctrl+,) chooses how Tomacco treats new releases on GitHub:
| Choice | What it does |
|---|---|
| Off | No request at all. Check now still asks GitHub, once, when you press it. |
| Notify (default) | Checks once a day while the desktop runs, and shows the new version in a band at the top of the window and in the status line. |
| Automatic | Also installs it by itself while no job, device session or virtual iOS device runs, then the band says Updated to X with Restart now. A version that failed or that you rolled back is not installed by itself again. |
The section explains each choice in one line, the chosen one stronger. The band (Tomacco X ist verfügbar) has What's new, Install and Later (Später). Later hides the band for that version; the status line keeps its word, and a newer version brings the band back. When a new version is found while no Tomacco window is active, the desktop also sends one notification for that version (over D-Bus, as for jobs); clicking it brings the window back.
Channel (Kanal) chooses which releases count: Stable (Stabil, the default) only
finished releases, Pre-release (Vorabversion) also those GitHub marks as pre-releases. Changing
it starts a new check unless updates are off. updates.channel in settings.configure is the same choice.
A check never delays the start and fails silently when you are offline; Settings shows how it ended. What's new opens the release's page, Install downloads, verifies and
installs it, and Restart (or Restart now in the band) starts the new version once it is in place: the running app keeps working until then. The restart goes
through the installed tomacco launcher, opens the same project and view at the same window size, and waits until the old window has
closed, so agents reach the new one. With unsaved files nothing restarts; the window shows them first. On the first start of a new
version a short What's new in X sheet (Neu in X) shows the highlights from the installed release notes, once. Roll back returns to the version before. If the new version does not
start after installing, the previous one is restored by itself.
An installation made by install.py can be updated. A source checkout, a system package or a directory you cannot write is left alone, and the section says so. Installing needs Python 3.9
or newer, for the installer. Privacy says what the checks send.
Searching the settings#
Settings (Ctrl+,) opens with the keyboard in Search settings (Einstellungen durchsuchen). Typing
keeps the sections whose title, options or explanations contain every word typed, in any order; for
example channel leaves Updates, job background leaves Notifications. Down moves on into the
settings; Esc empties the search, and Esc again closes the dialog.
Moving settings to another computer#
Export settings… and Import settings… under Another computer in
Settings (Einstellungen exportieren…, Einstellungen importieren…) save
and read a JSON file with the desktop's preferences: appearance, language,
job notifications, the update choice, the device view's frame, activity panel,
touches and sound forwarding, the WebDriverAgent bundle ID and the web
preview's last device. Paths, window sizes, recent projects, job history and the
iOS research settings belong to this computer and are left out; Tomacco keeps
no passwords or tokens in its settings. An import checks every value, ignores
what it does not know and says how many settings it took. The update choice is
not exported or imported where a build or TOMACCO_UPDATES fixes it.
{
"format": "tomacco-desktop-settings",
"version": 1,
"application": "Tomacco",
"settings": {"appearance": "dark", "language": "de", "Notifications/jobs": true}
}
Window layouts#
A window layout keeps the open view, whether the activity panel is shown, the log beside the device with
its mode and width, whether the device and the virtual iOS device are in their own windows, and the
window's size or that it was maximised. Save one under Window layouts in Settings
(Fensteranordnungen) with a name such as testing or debugging; Save the current layout in the
command palette opens that section. A name used again replaces that layout. The palette lists each one as Use window
layout name, and Settings has Use and a delete button beside each. A device window in a layout
opens as soon as a device is live. A tiling window manager still decides the window's size itself. At most
12 layouts are kept, in the desktop's settings on this computer; the settings export leaves them out.
Navigation and shortcuts#
A rail on the left holds every view, with names and keys in a wide window and
icons in a narrow one. Pages open under a block of colour with their name;
Files, Builds & jobs and Agents run edge to edge. Apple and Android devices have
separate categories. Narrow, tall windows are normal in a tiling window manager:
the rail stays icons, the device bar's actions become icons, and the activity
panel takes the whole stage while it is open. The smallest window is 560×480.
The window opens at the size it had when it was last closed, at most as large
as the screen, and maximised if it was; a tiling window manager still decides
the size itself. --capture and --smoke-test keep the default size.
| Action | Shortcut |
|---|---|
| Search views, commands and all API operations (a tool opens its form before it runs) | Ctrl+K |
| Keyboard shortcuts | F1 |
| Open a project | Ctrl+O |
| Show or hide activity | Ctrl+J |
| Settings | Ctrl+, |
| Live device / files / jobs / devices / overview | Ctrl+1 … Ctrl+5 |
| Debugger / UI tests / performance / release / agents | Ctrl+6 … Ctrl+9, Ctrl+0 |
| Save the current source file | Ctrl+S |
| Save a screenshot of the live device and copy it to the clipboard | Ctrl+Shift+S |
The command palette (Ctrl+K) matches loosely: the letters typed have to appear in
order in a name, so opproj finds Open project and dev log finds the device
logs operation. Matches at the start of words, letters in a row and whole words rank
higher, the matched letters are marked, and letters scattered across a long name do
not count. The eight entries chosen last come first in an empty palette, marked
Recent (Zuletzt), and rank higher when they match. Views and commands show
their shortcut on the right; ↑ ↓ select, PgUp and PgDn move eight rows, Enter or a
click opens and Esc closes.
Tab moves through the title bar, the rail, the device bar and the page, in that order; the control with keyboard focus has a two-pixel ring. The lists of jobs, agent tools and project files take the keyboard too: Tab into the list, the arrow keys move, Enter or Space opens the entry. Every control the keyboard reaches on the main screens has an accessible name, icon buttons included, whose tip (with its shortcut) becomes the description. The desktop UI test checks these names with Qt's accessibility switched on; the desktop has not yet been tried with a screen reader such as Orca.
Screenshots of the live device, from the camera button, the palette, Ctrl+Shift+S or
the device window's rail (Ctrl+Shift+S there too), are saved in ~/Pictures/Tomacco
(or the configured Pictures directory) and put on the clipboard as an image.
The virtual iOS device's Screenshot button copies its capture the same way.
When something fails#
A request that fails on a workspace view (Project, Devices, Builds & jobs, Agents & tools, Debugger, UI tests, Performance, Files or Release) shows a red band above the page with what went wrong and, when the control plane names one, What to do: (Was zu tun ist:) with its remedy. Try again (Erneut versuchen) sends the same request once more; the close button hides the band. The notice line under the live device does the same for the device view's requests, and the live log under Devices and the log beside the device offer Try again when their log cannot be read.
Views without anything to show say what is missing and offer the next step: Files without a project offers Choose folder; Agents & tools says when the control program has listed no tools, with its reason and Try again, and when the search finds no tool; Builds & jobs says when no job runs or failed under its filters.
Activity#
The panel beside the device (Ctrl+J) shows the device's state in large type, the jobs that are running with the newest one's log, what agents called under Agent activity (Agenten-Aktivität), and recent jobs. Jobs come from every client. The panel closes by itself below 1120 px wide and opens on request. The status line at the bottom always names the device's state and how many jobs run.
Agent activity lists, newest first, the calls agents make to this window through its preview socket and, when you name an agent's session log, every operation the agent called: its name, the time, how long it took and whether it worked, with the error's message when it did not. Repeats of the same call to the window fold into one line with the latest result. All and Failed (Alle, Fehler) and a search field narrow the list; a call that started a job opens it under Builds & jobs. Notifications (Benachrichtigungen) in the same panel lists, newest first, what happened while the window was open: a job that ended (completed, failed, timed out or cancelled, with its duration), a new version of Tomacco or one that waits for a restart, the end or failure of the live device's session, and an agent's call that failed or started a job. A click leads back: a job to its log under Builds & jobs, a device to the live device view with that device chosen, an update to Updates in Settings. The panel shows the six newest, All shows up to 50, and Clear empties the list. The last 50 are kept across starts.
To name the session log, enter its absolute path under Agent activity in
Settings (Ctrl+,) and set TOMACCO_SESSION_LOG to the same file in the env of
your agent's MCP server entry (see the session log); Copy MCP
configuration in the panel then includes it. A desktop started with
TOMACCO_SESSION_LOG set uses that file. Without a session log, calls an agent
makes directly to tomaccoctl leave no record unless they start a job.
Repeated app/build requests reuse the active job, and each device uses one native display session. A running Flutter app exposes Hot Reload and a Stop button (Beenden) instead of remaining labelled as a build. Other work in the occupied project returns its existing job context; it does not silently queue another app launch. The display uses immediate Android H.264 access units and reuses image buffers.
The default workspace displays an actual native runtime and its project files. Apple defaults to a physical iPhone over USB: Linux compiles supported Swift apps and the iPhone executes them. Apple's Simulator remains an optional Mac connection. The Web preview button (Web-Vorschau) opens the separate Chromium layout lab.
Builds and jobs#
Builds & jobs (Ctrl+3, Builds & Aufträge) lists the jobs of every client,
newest first, with their state, how long they ran or have been running, and the
project they work in. All, Running and Failed (Alle, Laufend,
Fehler) above the list narrow it; Failed includes jobs that timed out. The
chosen job's log fills the rest. Beside its name, Open log file
(Protokolldatei öffnen) opens the log in the desktop's text viewer,
Run again (Erneut ausführen) starts the same request once more and
Cancel (Abbrechen) stops a job that runs. Run again works for jobs this
window started (builds, tests, lint, Run app, installs and tool forms); it
remembers the last 40 such requests across restarts. Jobs that agents or
tomaccoctl started carry no request the desktop could repeat.
Above the log, Filter the log
(Protokoll filtern) keeps the lines that contain a text, ignoring case, and
Warnings or Errors (Warnungen, Fehler) keep the lines at that level:
Warnings shows warnings and errors. The level is guessed from words such as
error, failed, exception and warning, and from Android's logcat letters
(E/Tag, W Tag:). Copy these lines (Diese Zeilen kopieren) copies
exactly the lines shown; without a filter the button copies the whole log.
Notifications#
A job that ran for at least 10 seconds and ends while no Tomacco window is
active, such as a build, a test run or an app start, shows a desktop
notification: Completed after, Failed after or Timed out after and
the duration (Fertig nach, Fehlgeschlagen nach, Zeitüberschreitung nach).
Its Open action (Öffnen) brings the window back with that job's log.
Cancelled jobs, and jobs that end while you are in the window, stay quiet.
Turn it off under Notifications in Settings (Ctrl+,): When a job ends
in the background (Wenn ein Auftrag im Hintergrund endet). The desktop sends
the notification to the desktop's notification service over D-Bus
(org.freedesktop.Notifications); a build without Qt D-Bus sends none. On
Omarchy the panel can notify about builds too; turn one of them off if you get
both. For jobs started from a terminal or by an agent with no desktop open,
tomaccoctl omarchy notify on sends the notice (see
Omarchy keys, menu and commands).
Agents and tools#
Agents & tools (Ctrl+0, Agenten & Werkzeuge) lists every operation as a form, and the command palette
(Ctrl+K) opens any of them by name. The form is built from the operation's schema: a check box for a
yes/no value, a choice for a fixed list, a text field otherwise, with defaults filled in and required
fields marked *. Each field shows its description and, for a number, its limits, such as 1 – 3600;
a value that is no number or lies outside the limits is refused before anything runs. Run
(Ausführen) shows the JSON result, or follows the job it starts under Builds & jobs. An operation
whose schema marks it as destructive, such as job.cancel, files.write or storage.prune, asks once
more before it runs (Run anyway, Trotzdem ausführen). Copy as command (Als Befehl kopieren)
copies the same call for a terminal or a script, for example
tomaccoctl call device.logs '{"platform":"android","device":"emulator-5554","duration":60}'.
Installing an app by dropping it#
Drag an app from the file manager onto the device view to install it on the
device shown there, or on the chosen one when none is live. Android devices
take one or more .apk files (split APKs install together), an iPhone over USB
one signed .ipa; both go through device.install and show up as a job under
Builds & jobs. On the virtual iOS device's picture, docked or in its own
window, an .app bundle is added to the profile through
ios.virtual.configure and, while the phone runs, installed at once
(deploy). A file the device cannot take is refused with the reason; files that
are no app are ignored. The detached device window accepts drops too.
Devices page#
Nicknames and pins#
Each Android device and emulator under Devices has a pin button and a rename
button. A pinned device is listed first; a nickname is shown in place of the model
name in the device lists, the device picker and the Omarchy panel, where a star marks
a pinned device. Both are stored through device.label, so agents see them in
device.list too. An emulator keeps its nickname by its AVD name, also while it is
off and when its serial changes.
All screens#
All screens under Devices shows every running Android device and emulator side by
side, each with a picture of its screen that refreshes every few seconds through
android.screen. A click on one opens it as the live device. The pictures are still
screenshots, not video; the live device view streams one device at a time.
Live log#
Live log under Devices opens the log of the selected Android device below the
device list, as it grows. An app ID keeps only that app's lines, by its user ID on
Android 12 and newer, so they continue across restarts; the level buttons show
everything, or from info, warnings or errors up, filtered by logcat. The search field
keeps lines with a text, and errors and warnings are coloured. The pane reads a
device.logs job and starts a new one when a capture ends or the app or level
changes; closing the pane or pausing it ends the capture.
Pairing over Wi-Fi#
Connect via Wi-Fi under Devices shows a QR code for the phone's Developer options ›
Wireless debugging › Pair device with QR code and pairs and connects as soon as the
phone has scanned it. Pairing code is the other way: enter the address and six-digit
code the phone shows; when adb finds a single pairing address on the network it is
filled in. Both go through android.pair and android.connect.
Touches and coordinates#
Touches and coordinates in the tool strip beside the live device
(Berührungen und Koordinaten, also in the command palette) helps when writing
UI tests. While it is on, a ring marks where the pointer presses the device and
follows the finger until it lifts, and a label beside the pointer names the
device pixel under it and the same point as fractions of the screen, for
example 250, 1500 · 0.250, 0.750. runtime.input takes the fractions as its
coordinates. Presses still reach the device as before. The choice is
stored. It applies to the Android and iPhone picture, docked or in its own
window; the virtual iOS device has its own touch mark and no coordinates.
Log beside the device#
Log beside the device in the tool strip beside a live device, or Show the log beside the device in the command palette (Log neben dem Gerät zeigen, found as "split view" too), opens a log to the right of the device view. Device log (Geräte-Log) is the live device's own log, with the same app, level and search controls as the live log under Devices; Job log (Auftrags-Log) follows the app's launch while it runs, otherwise the newest running job, otherwise the newest job, and opens it under Builds & jobs on request. Drag the handle between the device and the log to resize it. Whether the log is shown, which log and its width are kept across starts.
Screen recording#
The record button in the tool strip beside a live Android device records its screen
with android.record, for up to three minutes; pressing it again stops the recording
with job.send stop. The video is saved under Videos/Tomacco and shown in the file
manager. The virtual iOS device has a Record button beside its picture that does
the same through ios.virtual.record, for up to ten minutes.
Device window#
The Own window button in the tool strip beside the device (Eigenes Fenster) moves the
running device into a frameless, device-shaped window with its own
Home/Back/Rotate/Screenshot rail. It is the same display item moved to another
window, so no second stream is opened and pointer coordinates stay exact. Drag
it by the bezel or rail; Ctrl+wheel or the rail buttons change its size; the
close button or Esc docks it again. Agents use preview.configure with
native.detached.
Each device view, the native device and the virtual iOS device, can be detached into its own window. The virtual iOS device's window opens pixel for pixel where the monitor fits the panel, zooms with Ctrl+wheel up to one panel pixel per screen pixel and keeps only a zoom the user chose; docked, the picture takes the dialog's height and carries the model's island and rounded corners. Its rail also has App switcher, Notification Center and Control Center for a phone without a Home button, the keyboard switch, the speed overlay and a pin that keeps the window above the others. The pin and the window's place are kept for the next time; on Wayland the compositor places windows, so only the pin applies there. Offscreen tests only; not tried on a live phone.
In device-only mode that window is the whole app: tomacco --device-only
or desktop.open with device_only starts without showing the main window.
Closing the device window then ends the session and the app; the device itself
keeps running. Losing the device ends the app after a short grace period, and
unsaved editor changes bring the main window back instead of being dropped.
Closing the main window while a device is detached switches to this mode, and
the rail's workspace button returns to the full app.
The virtual iOS device takes live touch: a press, its moves and its release reach the guest as they happen, so a finger can hold, drag and pause, and the picture keeps coming meanwhile. A press near the screen's edge, or on the frame around it, starts at the edge, which is what opens iOS's edge gestures by mouse. The same gestures have buttons and Simulator-style shortcuts, with Ctrl for Command:
| Shortcut | Action |
|---|---|
| Ctrl+Shift+H | Home |
| Ctrl+Shift+L | Side button (wake or lock) |
| Ctrl+Shift+A | App switcher |
| Ctrl+Shift+N | Notification Center |
| Ctrl+Shift+C | Control Center |
| Ctrl+Shift+B | Back (swipe right from the left edge) |
| Ctrl+V | Type the host clipboard's text into the focused field (Paste) |
| Ctrl+Shift+K | Give the keyboard back to the desktop, or to the phone again |
While the phone has the keyboard (Keyboard to phone under the picture), typed characters, Enter, Backspace, the arrows and the other keys go to it; see using the virtual iOS device for typing, scrolling, the Speed overlay and Screenshot with frame.
The frames are drawn to scale per model from Apple's dimensional drawings and the iOS 27 simulator: body, screen offset, island, notch or punch, continuous corners and buttons, the Duo's folded and open states, and a back view.
native.pending_avd shows the frame of an Android emulator that is still
booting, with a startup screen, and attaches the real stream as soon as Android
is up. The Omarchy panel uses this: its start button boots the emulator without
the emulator's own window and shows only the phone.
Screenshots go to OMARCHY_SCREENSHOT_DIR on Omarchy when it is set, else to the Pictures folder, as
Omarchy's own screenshots do; elsewhere they go to Pictures/Tomacco.
On Omarchy the installer adds a Hyprland rule that keeps the device window floating without border, shadow or blur. With GPU rendering the picture is cut to the frame's corner radius; the software renderer keeps the square picture.
Omarchy panel#
The Omarchy panel is a user plugin, so system updates do not replace it. Left click opens the panel, middle click opens the live device as its own window and right click opens the native runtime workspace. The bar icon lights its screen while a device is live and shows an orbiting arc while a build or task runs; emulators and audio bridges are listed as services and do not count as activity.
A running job shows its phase as job.list names it, such as building, installing or session running,
and why a queued job waits; no job reports a percentage. While a device is live, the open panel shows it
above the device list with a small picture of its screen, taken with runtime.capture every 4 seconds
into the data directory (panel-thumbnail.png); a click opens it as a window. When a new version of
Tomacco is available, or one is installed while the open workspace still runs the older one, a line
above the tiles says so (from update.status, which uses no network; checked every 10 minutes and when
the panel opens); U or a click opens the workspace, whose band installs or restarts. These three are
checked with qmllint and the panel's source tests, not yet in a running Omarchy shell.
The panel lists connected devices (connect, disconnect, open as window, stop an
emulator), stopped Android emulators with a start button, each under its nickname
and with pinned devices first, running and recent
jobs with their duration, and saved projects with build-and-run. Each job row
has a button that opens the job's log file in the desktop's text viewer
(xdg-open). Keys in the open panel: O workspace, F device window,
S screenshot, G devices, A jobs, V log of the newest job, R refresh, U update. The same
actions are available over IPC for Hyprland bindings, for example
omarchy-shell io.github.marselnenaj.tomacco window, screenshot, log,
show jobs, emulator <name>, iphone start|window|stop, status, version,
update and refresh. Its native popup supports Escape and switching between panels.
Completion notifications can be turned off in the widget's settings, or with
"notifyBuilds": false in its bar entry. Filesystem state and jobs are local; the control plane
opens no network API server.
The shell compiles a plugin file once per URL and keeps it until it restarts, so the installer writes the panel into a directory named after its content and points the manifest there. The running shell then loads an upgrade within about a second; no restart is needed. Earlier copies of the panel are removed.
The panel takes the shell theme's accent colour for its live and busy marks where that colour stands out from the
bar's text (at least 1.6:1) and reads on the bar's background (at least 3:1), and keeps Tomacco's red otherwise.
"themeAccent": false in the panel's bar entry, or its widget setting, keeps the red. Its other colours already
come from the shell. The detached device window keeps the device's own frame: the app draws it, and the Hyprland
rule above adds nothing around it.
Omarchy keys, menu and commands#
install.py --omarchy and tomaccoctl _install --omarchy also add Tomacco to Omarchy's menu, under
Trigger, Tomacco: open the workspace, devices and emulators, builds and jobs, start the virtual iPhone, the
device window and a device screenshot. The entries sit between two comment lines in
~/.config/omarchy/extensions/omarchy-menu.jsonc, the file Omarchy reads beside its own menu, and the rest of
the file is left as it is. A file that is not a JSON object, or that wraps its entries in "items", is not
touched. --no-omarchy-menu leaves the menu alone. The installer journals the change; --uninstall and
--rollback take the entries out again, and a file that held only them goes too. The virtual iPhone and device
window entries show only while the panel is installed, because they use its IPC.
--omarchy-bindings adds three keys as ~/.config/hypr/tomacco-bindings.lua, required from hyprland.lua
(like the window rules):
| Keys | Does |
|---|---|
| Super+Ctrl+Shift+T | Opens the workspace (desktop.open) |
| Super+Ctrl+Shift+P | Opens the live device as a window (the panel's window command) |
| Super+Ctrl+Shift+S | Saves and copies a device screenshot (tomaccoctl omarchy screenshot) |
Omarchy's own bindings use no key with Super, Ctrl and Shift together and a letter, which is why these sit there;
a test reads the bindings Omarchy ships and fails if one of these appears in them. Change the keys in that file.
--update writes the file again only where it exists, so keep your own keys in bindings.lua.
tomaccoctl omarchy is for this desktop:
tomaccoctl omarchy status(the default) lists the panel, window rules, keys, menu entries and agent skill asok,outdated,missing,disabled,unlinkedornot_installed, and the notification setting. It exits 1 when something installed is outdated, missing or disabled, and--jsonprints the same as JSON.tomaccoctl omarchy repairinstalls those again from the copies the installation keeps in~/.local/lib/tomacco, in one journalled run that_install --rollbackundoes. It needs no package.tomaccoctl omarchy screenshotsaves the connected device inOMARCHY_SCREENSHOT_DIRwhen Omarchy has that set, else in the Pictures folder of~/.config/user-dirs.dirs, as<device>-<date>-<time>.png, copies it withwl-copyand sends a notification withnotify-send. The desktop's own screenshots go to the same folder on Omarchy, instead ofPictures/Tomacco.tomaccoctl omarchy notify onsends a notification throughnotify-send(Omarchy's notification service) when a job of 15 seconds or more succeeds, fails or times out while no Tomacco desktop is open: a build an agent or a terminal started, say. It is off by default and stored insettings.jsonasnotifications.jobs. With the desktop open, the desktop notifies, and the panel's build notices are separate, so use one of them.
All of this ran in a scratch home directory and in unit tests, and the key, menu and panel files were read against Omarchy's shipped sources. It has not been tried in a live Omarchy session.
Toolchains and platform setup#
Use the Toolchain view in the project overview or tomaccoctl doctor. Configure
existing SDK/JDK/Flutter/Gradle paths through SDK & tool paths (SDK & Tool-Pfade) or
settings.configure. Tomacco invokes the real upstream tools and returns
their status and logs. Android project wrappers take precedence over a
configured Gradle executable.
Swift on Android. Swift Android apps use project.create with
kind:"swift-android". Install a matching Swift compiler/Android SDK and NDK,
run swift.android_configure, then build and deploy through the same Android
operations. The starter packages actual Swift machine code for ARM64 phones and
x86_64 emulators. Android widgets supply the native UI; SwiftUI/UIKit remain
Apple frameworks. See Swift on Android.
Physical iPhone without a Mac. Choose Apple → Set up iPhone (iPhone einrichten) for the
Linux route, or start with ios.doctor. Install matching Swift/xtool, import an
Apple-provided Xcode archive using ios.sdk_install, and authenticate in the
local terminal using ios.prepare action=login. Credentials stay with xtool.
Connect an unlocked iPhone, trust the computer and enable Developer Mode. The
setup dialog handles USB pairing, go-ios services and the signed automation
runner. The Xcode archive supplies the SDK; Xcode itself does not run on Linux.
General Xcode and Flutter iOS builds are not supported by this Linux build
route; they use the optional SSH Mac route. Protocol and setup paths are
contract-tested; execution on a physical iPhone is not yet qualified. See the
iPhone guide.
Virtual iOS device. The devices tab (Apple) and the Omarchy panel show the
virtual iOS device's state and offer start, stop and a classic iPhone-shaped window
with a working Home button, wheel scrolling and an immediate touch mark. The
iOS test system dialog (Open test system, Testsystem öffnen) lists the profile's own apps with
their state, adds an .app bundle (Add app…, App hinzufügen…), removes one and chooses
the app the start opens, each through ios.virtual.configure. See
using the virtual iOS device.
iOS research engines. Apple → Open test system also controls bounded
full-system iOS research on Linux. The same ios.emulator.prepare_research,
ios.emulator.research and ios.emulator.report operations are available to
CLI/MCP agents. Jobs use checksummed inputs and disposable APFS copies, reject
conflicting starts, retain real diagnostic logs and show the guest boot
framebuffer. This restore-shell engine boots iOS 27.0.1 without macOS and runs
own C and Embedded Swift CLI programs; graphical iOS apps and a working main
display remain unavailable there. See setup and limits.
For controller development, ios.emulator.prepare_pmp, inspect_pmp and pmp
verify and execute the original PMP firmware in an isolated ARM64 machine.
Reports preserve actual instruction/bus faults and clean up the VM. CPU identity
is configurable through CLI/MCP and the desktop, and verified before execution.
These are explicit laboratory settings, not measured silicon identities. Strict
handling still exposes unsupported Apple registers; controller services and
full iOS remain unavailable. See the reproducible PMP workflow.
The separate ASC transport model
passes queue, reset and real ARM interrupt tests; it is not yet a bootable PMP
controller or integrated into the iOS machine. The opt-in
ASC/KIC mode connects
configuration, interrupt masks, queues and ARM timers to the PMP CPU. CLI/MCP
and the desktop panel expose the same exclusive jobs and read-only controller
reports. Reset/routing policies remain experimental; power services and
graphical iOS are not qualified. For exact fault context, the same workflow can
stop at a verified instruction, capture registers and RAM, and inspect memory
through CLI/MCP or the desktop hex view. See
targeted firmware debugging.
Visual layout lab#
The lab is a separate Chromium (Qt WebEngine) surface for responsive web
layouts, including Flutter web. Enter your dev-server URL or choose a built web
folder containing index.html. The folder picker also detects build/web,
dist and public. It serves that folder on an ephemeral 127.0.0.1 port
while the app is open. It does not run build scripts or modify the selected
project.
The included Fokus demo has a working timer, editable task, light/dark theme,
session history and navigation. It contains no external image, font or script
dependencies. Its content consumes the current profile's safe-area metadata,
including Duo's side reservations, while its background fills the full display.
Automatic system-indicator contrast follows the page's theme-color metadata
or body background; manual light/dark overrides are available.
Lab controls#
| Action | Shortcut |
|---|---|
| Address | Ctrl+L |
| Reload | Ctrl+R |
| Rotate | Ctrl+Shift+R |
| Save device PNG | Ctrl+Shift+S |
| Fullscreen / exit | F11 / Esc |
| Chromium developer tools | F12 |
Device changes and rotation preserve the current page. The Duo has four direct presets: folded portrait, folded landscape, open portrait and open landscape. Outside it has a round camera in the upper-right corner (rotated with the hardware); inside it has no camera cutout. The folded shell has a straight hinge edge and different corner radii. These details follow Apple's product-viewer images on the Duo design page, rather than the generic Dynamic Island text in the combined specifications table.
Fit scales the whole device without changing the page's CSS viewport. Chromium
and the rounded display render using each profile's declared density,
accounting for the desktop display scale. This keeps text and canvas content
sharp when the device is enlarged. Zoomed devices can be scrolled into view.
Screenshots include the device frame and are saved at 2× the preview's logical
size in ~/Pictures/Tomacco (or the configured Pictures directory) and copied
to the clipboard.
Pages on every profile always fill the complete display: there are no blank strips around the browser. The app can read the layout metadata below to position its controls beside the camera and system indicators. In folded portrait and open landscape, bars sit on the right; rotating the outer display moves them with the hardware to the left. Open portrait uses horizontal bars. This follows Apple's bar layout guidance. Exact inset dimensions remain approximations.
The Safe area metadata switch (Safe-Area-Metadaten) supplies approximate safe-area hints without shrinking the browser. The footer reports the complete CSS viewport. The Safe Area overlay is a visual aid. Status indicators can be hidden, use automatic contrast or be switched between light/dark manually. Duo's side views use the compact ring indicator; open portrait uses a horizontal status bar. Android profiles use their own indicator shapes.
Color scheme, motion and network#
Under View, Page color scheme (Farbschema der Seite) makes the page see
prefers-color-scheme: light or dark, Reduce motion (Bewegung reduzieren)
prefers-reduced-motion: reduce, and Network (Netzwerk) slows its requests to
Chromium DevTools' Fast 3G or Slow 3G or takes it offline. System and No
throttling leave the page as the desktop has it. Agents set the same with
preview.configure and can give their own latency and bandwidth.
Qt WebEngine has no setting for these, so Tomacco sends them through the Chrome
DevTools protocol of the web inspector's view. From the first change on, that
view stays attached to the page while its window is hidden, and the page's
debugger statements are skipped until you open the inspector. The settings last
across reloads and navigation. Requests of service workers and WebSocket traffic
are not throttled.
App layout metadata#
Tomacco injects window.__tomaccoViewport before page scripts run,
and dispatches tomacco:viewport after a device, fold or orientation
change. Reading this is optional: unmodified web apps still get the full display
viewport. The object contains only layout data, with no native commands or host
paths. It does not replace browser CSS env() values or native iOS
reserved-region APIs.
function applyPreviewLayout(viewport) {
// Keep backgrounds full bleed; inset interactive content as appropriate.
console.log(viewport.barEdge, viewport.safeArea);
}
if (window.__tomaccoViewport) {
applyPreviewLayout(window.__tomaccoViewport);
}
window.addEventListener('tomacco:viewport', event => {
applyPreviewLayout(event.detail);
});
Example for folded portrait (all dimensions in CSS pixels):
{
"version": 1, "model": "duo", "platform": "ios", "display": "outer",
"orientation": "portrait", "width": 466, "height": 678,
"barEdge": "right", "controlsTop": 126,
"safeArea": {"top": 0, "right": 72, "bottom": 0, "left": 0}
}
platform is ios, android or generic. barEdge is left, right, bottom, or automatic for ordinary profiles.
controlsTop is the suggested starting Y of vertical controls, below system UI.
A page reload is unnecessary when the metadata changes.
Scope and device data#
The Chromium layout lab does not execute SwiftUI/UIKit apps, IPA files, native
plugins or Apple services; use the native workspace and device operations for
those apps. The browser's user agent, touch environment and CSS
env(safe-area-inset-*) values are not emulated. Use the viewport to evaluate
responsive layouts; verify platform-specific behavior separately. The device
pixel ratio follows the profile's declared render density where the desktop
scale permits it; the UI and API report the actual viewport separately.
Model names and physical display resolutions were checked against manufacturer specifications on 2026-10-03. iPhone logical sizes use the profiles' 3× density. Android profiles describe reference layouts, not measured Android configurations. Each profile records its source and confidence; real devices can differ because of display zoom, font scale and system settings. Frames, colors, cutouts, radii and safe-area guides are visual approximations. The iPhone 17e uses an edge-connected TrueDepth notch with concave shoulders, rounded lower corners and its camera on the left, following Apple's front-view imagery. Its frame width follows the published body/display dimensions; it does not reuse a floating Dynamic Island shape. Apple and Android models have separate selectors, remembered selection and search text. Custom profiles appear in their declared platform; platform-neutral imports get an additional Custom category (Eigene).
- iPhone 18 Pro / Pro Max
- iPhone Duo
- iPhone Air
- iPhone 17
- iPhone 17e
- iPhone 16
- Pixel 11
- Pixel 11 Pro
- Pixel 11 Pro Fold
- Galaxy S26 / S26 Ultra: source URLs are stored in their catalogue records.
- Galaxy Z Fold8 Ultra, Galaxy Z Fold8 and Galaxy Z Flip8, checked on 2026-10-31. Their logical sizes assume a density of 3, which Samsung does not publish; the Flip8 profile is its unfolded main display.
- Galaxy Tab S12+ and Tab S12 Ultra, checked on 2026-10-31, at an assumed density of 2. The frame draws no camera notch.
assets/models.json holds all profiles. Qt renders the app and device frames;
Qt WebEngine renders the page. Page content has no bridge to native commands.
Requests for device permissions are denied. TLS errors are not bypassed. The
four Duo presets switch the page size, camera placement, hinge edge, corner
radii and approximate insets together. They are 2D layout states; intermediate
3D folding angles and native iOS Flex/StandBy behavior are not implemented.
Custom devices#
Export a built-in profile, give it a new id, edit its dimensions/density/cutout,
colors and portrait/landscape insets, then import the JSON. Built-ins cannot be
overwritten. Foldables can declare openWidth, openHeight, openCutout and
separate open insets. See the profile schema.
Profiles contain data only. android.calibrate reads real wm size/density;
frame details and reserved insets still need visual calibration.
Visual checks#
tomacco --capture /absolute/path.png captures the window after 4.5
seconds and exits. Native Wayland rendering is used for visual checks: the
offscreen Qt software renderer does not render the WebEngine layer mask
correctly on the development machine.
The GUI integration check (tests/gui_integration.py) verifies real JavaScript
viewport dimensions, rotation, all four Duo states and their cutouts, demo
input, PNG capture and fullscreen. It needs a functioning graphical session.
Backend tests cover URL handling and the local server, including SPA fallback,
HEAD, missing assets, traversal, symlink escape, hidden files, invalid hosts and
unsupported methods; they need loopback networking.