u/calebc42-official

▲ 8 r/emacs

Secondary Glasspane Demo: Composing Android Companion app using Elisp.

Only one video allowed per post. See discussion post here

u/calebc42-official — 6 days ago
▲ 34 r/emacs

Glasspane: A proof of concept for a transparent bridge between mobile interaction and executing Elisp

Orgzly invented the mobile capture dialog. Orgro figured out syntax-highlighted org in Swift. Beorg brought agenda views to iOS. Plain figured out frictionless entry. These are genuinely hard problems, solved well.

They all share the same underlying architecture: parse the org file, reconstruct what Emacs would have computed, render the result. The org file format is the API.

This was the right call. The file is portable, versionable, and available whether or not Emacs is running. Given what was available, parsing the file was the only viable approach.

But the org file is a serialization format, not a specification. The canonical computation lives in Emacs. Custom TODO state sequences, user-defined agenda skip functions, org-ql queries, org-roam link resolution, clock persistence behavior, capture template hooks — none of this can be correctly replicated by a file parser. It can only be approximated. Every mobile org app is, at some level, an approximation of a running Emacs.

Glasspane is an LLM-Generated proof of concept built on a different premise: don't approximate Emacs — connect to it.

The architecture: a pane of glass

A glass pane is transparent. You're not supposed to look at it; you're supposed to look through it. Glasspane tries to be that: a thin layer between the phone's screen and a running Emacs, as invisible as possible.

  ┌─────────────────────────────────┐
  │  Android companion (the pane)   │
  │  • Foreground service           │
  │  • Renders whatever Emacs sends │
  │  • Caches last-known UI state   │
  │  • Queues offline actions       │
  └──────────────┬──────────────────┘
                 │  NDJSON bridge
                 │  (loopback socket → signed Unix socket)
  ┌──────────────┴──────────────────┐
  │  Emacs (the source of truth)    │
  │  • Pushes native UI specs       │
  │  • Handles user action events   │
  │  • Runs all the actual logic    │
  └─────────────────────────────────┘

The companion is the durable server. It listens, renders cached surfaces when Emacs is backgrounded, and queues user actions for replay when Emacs resumes. Emacs dials in as the client — the same inversion emacsclient uses on the desktop.

The companion contains no application logic. It is a generic renderer. Everything visible on screen — which tabs exist, what's in them, what tapping an item does — is specified by Emacs as a declarative UI tree and pushed over the bridge. The companion renders whatever it receives.

Two tiers: universal and polished

Tier 0 — generic buffer rendering, free for every package.

Every Emacs package, regardless of how exotic, renders through the same universal substrate: a buffer of text with face, display, invisible, keymap, and button text properties. Glasspane walks these properties with next-single-property-change, maps face attributes to styled spans (bold, italic, color), maps invisible regions to collapsed content, and maps keymap/button regions to tappable actions. The result is that any Emacs buffer — magit-status, notmuch-search, dired, *Org Agenda* — renders on the phone without a single line of package-specific code.

The minibuffer is already handled. Glasspane intercepts completing-read, read-string, y-or-n-p, and read-char-choice during action handlers and forwards them to the phone as native dialogs. This works for any package that uses standard prompting functions, which is essentially every package, without modification.

Tier 1 — per-mode polished skins, for when the generic render isn't enough.

A registry maps major modes to custom surface builders. When a skin is registered for a mode, it takes over from the generic renderer and can build whatever synthesized, card-based, native-feeling UI makes sense. An org-agenda skin doesn't render the *Org Agenda* buffer as text — it queries across org files and builds Material3 cards with scheduling information, TODO chips, and clock actions.

This is the tier where the existing mobile org apps would live. Existing org parsing work doesn't become obsolete — it becomes a Tier 1 skin, now running on top of a live Emacs rather than an approximated one.

The security model: why not Intents, and the socket tradeoff

The bridge runs over a socket. The obvious question is why not use Android's built-in inter-process communication mechanisms — Intents, the Activity Manager (am), AIDL Binder.

Intents are discrete fire-and-forget messages routed through the OS. They work well for "launch this activity" or "notify this receiver of an event." They are not a bidirectional streaming protocol. Each Intent invocation carries OS overhead, there is no flow control, and there is no concept of a stateful session. Glasspane needs to push multi-kilobyte surface specs and receive action events in a tight feedback loop; Intents are the wrong tool.

The current loopback TCP socket (127.0.0.1:8765) has a real security vulnerability. Any process on the device can connect to a loopback TCP port. A malicious app could inject action events into Emacs or receive surface specs (which may contain sensitive org content) from the companion. The current implementation accepts this as a known v0 limitation.

The correct production transport is a Unix domain socket with Android's signature-level permission protection. A permission declared as android:protectionLevel="signature" is granted only to apps signed with the same certificate as the declaring app. If Glasspane and the Emacs APK share a signing key, the socket becomes accessible only to those two processes. The code change on both sides is minimal; the infrastructure decision is not trivial.

This choice has a significant consequence: a remote Emacs instance — running on a laptop or server and connecting over a network — cannot carry the Android signing key. Remote sessions are architecturally excluded by the signed-socket model. For users who want to drive a desktop Emacs from their phone over SSH or a local network, a different transport (TLS with token authentication, for example) would be needed, and that is a substantially different design with its own security surface to reason about. Glasspane currently makes the local-only bet.

The iOS question

iOS has no Emacs port. This is the biggest constraint, and everything else is secondary to it.

A Glasspane companion app for iOS would be straightforward to build — iOS supports Unix domain sockets within App Groups, and Kotlin Multiplatform could in principle share the bridge and protocol parsing code between Android and iOS companions. But a companion with no local Emacs to connect to is a cached renderer that never receives live data. The app would show stale surfaces indefinitely.

The remote Emacs workaround — connecting the iOS companion over the network to a Linux Emacs — is technically possible but requires abandoning the signature-based security model. A network socket needs its own auth story (TLS, certificates, tokens), and that is a materially different project. It would also mean that the security properties of the local model don't apply.

Until Emacs is ported to iOS, Glasspane is an Android-only architecture. The protocol itself is platform-agnostic — the Elisp layer would be identical on an iOS-connected Emacs.

What becomes possible that wasn't possible before

These are things a file parser cannot do, regardless of how good the parser is:

  • Execute the user's actual capture templates. A parser can read org-capture-templates from the file, but it cannot run :function entries, :prepare-finalize-hook, or %(your-elisp-function) escapes in template strings. Glasspane runs them in Emacs and presents the result as a native input dialog.
  • Run org-ql-select queries. The user builds queries visually on their phone; Emacs runs it over their entire org-roam graph and returns matching entries. No parser can do cross-file, index-aware queries with arbitrary Elisp predicates.
  • Reflect the user's actual TODO states. If someone has (sequence "WAITING" "IN-PROGRESS" "DELEGATED" | "DONE" "CANCELLED"), a parser has to infer this from occurrences in the file. Glasspane reads it from org-todo-keywords.
  • Clock in with the correct behavior. org-clock-in runs hooks, updates the modeline, handles "clock out previous task" logic. Tapping "Clock in" in a Glasspane surface runs the actual org-clock-in. The notification chronometer is driven by org-clock-start-time, not an approximation of it.
  • Trigger any existing interactive command. M-x on the phone works. Any command the user has configured in their init file is one command palette entry away.

The fallback story

The file-parsing approach these apps have built has real value independent of Glasspane. It works offline. It doesn't require a running Emacs. It is the right answer when Emacs isn't connected.

The interesting design space is the hybrid: parse the file when Emacs isn't available, upgrade to the live bridge when it is. Existing parsers become the offline fallback layer rather than the deprecated path. The user experience improves automatically as Emacs comes online — not as a mode switch the user manages, but transparently.

This composability is what makes collaboration more interesting than competition. Ultimately, I have a dream of the Emacs APK shipping with an Emacs(Client) companion app and different developers can ship their "distros" through ELPA/MELPA in the users `init.el`

Upstream feasibility

A reasonable question is whether Glasspane could eventually be bundled with the Emacs Android APK — the mobile equivalent of shipping emacsclient alongside emacs. The architecture supports it; the current implementation does not.

The Emacs Android port was built with deliberate FSF alignment in mind. It avoids Gradle plugins and the JetBrains toolchain because the GNU project does not consider Kotlin's compiler toolchain fully free in the GNU sense, and because the Gradle ecosystem introduces build-time dependencies on non-FSF-blessed infrastructure. The current Glasspane companion is written in Kotlin with Jetpack Compose as the UI layer. Compose is Kotlin-specific. This combination is not suitable for inclusion in the GNU Emacs build.

A companion written in Java, using Android's traditional View system rather than Compose, built with a minimal Gradle configuration or the Android SDK tools directly, would be a different matter. The protocol and all of the Elisp are unchanged; only the companion implementation would need to be rewritten. This is a significant but bounded amount of work — the logic is not complex, the Kotlin is idiomatic and well-structured, and the translation to Java is mechanical in most places.

The two paths are therefore distinct: Glasspane as an independent project (Kotlin, Compose, modern Android stack, no FSF constraints) and a potential upstream companion (Java, View system, FSF-compatible build). The first is what exists now. The second is what upstreaming would require.

The shape of a collaboration

Glasspane is a LLM Generated proof of concept. It's a buggy mess that barely works — the org dashboard, the clock notification, the minibuffer bridge, the offline action queue. It is ultimately one inexperienced, vibe-coder's, implementation of an architecture that could be much larger.

The things that would benefit most from collaboration with developers who have been working in this space:

  • The generic buffer renderer is the piece that makes this universally applicable rather than org-specific. Everything else rests on the claim that any package works without a bespoke implementation; this is what makes that claim true.
  • The fallback/offline hybrid — existing parsing work as the graceful degradation layer — is an architectural pattern worth designing carefully. The interface between "Emacs is connected" and "Emacs is not" has real design decisions in it.
  • The signed-socket transport requires someone who knows Android's signing infrastructure and sharedUserId / signature-level permission model well. The code change is small; getting the packaging right is not.
  • The skin registration API needs to be clean enough that a magit-glasspane.el or notmuch-glasspane.el could be written by someone unfamiliar with the internals. The current API is not yet at that level.
  • Latency and battery impact are untested. The bridge adds a round-trip between user input and Emacs response, and the foreground service with its persistent socket holds a partial wakelock. Neither has been profiled. On a device where Emacs itself is running, the combined impact on battery is genuinely unknown. These are not theoretical concerns — they are measurements that have not yet been taken.
u/calebc42-official — 6 days ago

[InkHub] Sleep Screen or Covers top-level category/subcategory.

Currently, there are covers being uploaded to Templates, Drawing and Notes. This is because there are covers that are not drawings, for example, the "Composition Notebook Covers" in Templates/Journal or Covers that are `.note` files, for example, "Recipe Note Covers". It can feel disingenuous for a User to upload a photo/image that was downloaded from the Internet as a "Drawing" for the "Screensavers" subcategory. This ambiguousness would be resolved if there were a top level category for it.

If the top level category would be created, the ambiguousness could be further resolved by restricting the "Drawing" category to `.spd` files only, similar to the Stickers category being restricted to `.snstk` files.

Since a Sleep Screen can only be a `.png` it does not make sense to have it located under "Drawings/Screensaver". Additionally, a "Cover" cannot be a `.spd` file. So while a Screensaver sub-category exists, all images intended to be used as a notebook cover are orphaned and subsequently being placed in the Drawings category or the Templates/Creative subcategory even though, I imagine that category was intended for things like perspective grid templates.

There are even some covers found in the Note category that cannot be uploaded to the Drawings/Screensaver OR Templates category.

I won't pretend to know a solution, but I think the best way to prevent further splintering of the location of covers and Sleep Screens is to restrict the Drawings category to `.spd`, and add a new category for images/pictures/drawings that are not `.spd` files but could be `.pdf`, `.png`, or `.note` files. At first, I thought a Templates/Covers subcategory would be best, but this would still restrict the `.note` files that are uploaded and intended to be used as covers.

This would make the Templates category more clear that it is intended for stationary-like backgrounds and templates.

Lastly, regardless of the decision, the Drawings/Screensavers sub-category should be renamed to "Sleep Screen" since the term "Screensaver" is colloquially outdated and internally referenced as either "Sleep Screen" in the device settings, or "Set cover" for a note.

reddit.com
u/calebc42-official — 7 days ago

[InkHub] No ability to report or send feedback to Ratta/uploader

I wanted to let the uploader know that their sub-categorization is wrong and should be updated.

For the difference between sending Feedback and sending a Report, I would like to be able to let them know via feedback to update it and if ignored, escalate to Ratta.

Oh additionally, being able to report content that violates the Terms and Conditions.

u/calebc42-official — 7 days ago