OMC version 5.3 #28
abra-code
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
OMC 5 requires macOS 14.6 or later. For older macOS versions, use OMC 4.x.
OMC 5.3 is built against ActionUI 0.8.1. It adds a two-way bridge between a command handler and the ActionUI window it was dispatched from: for the first time a handler can read what is on screen right now, not only write to it. Python handlers get this through a new
omcmodule that AppletBuilder installs for them. Also in this release: embedded-Python thinning built into AppletBuilder's Build & Run pane and theappletbuildercommand-line tool, off-screen ActionUI JSON preview rendering that works on a locked screen, and an omctest harness that can test bridge handlers against a real host.Highlights
Command handlers can read their ActionUI window
omc_dialog_controlhas always been able to set a value, fill a table or enable a control, but it has never been able to ask what a control currently holds. A handler's only view of its window was the snapshot the engine took at dispatch time -$OMC_ACTIONUI_VIEW_101_VALUEand its siblings - so anything the user changed afterward was invisible.An applet with an ActionUI window now also serves the ActionUI remote protocol (JSON-RPC 2.0 over a Unix domain socket, from ActionUI 0.8). The engine starts the server with the first ActionUI window and stops it when the applet terminates. Closing the last window deliberately does not stop it, because a script the applet spawned may still be holding the endpoint.
Handlers find the bridge through the environment, under both OMC-prefixed and plain names, so a client written against the ActionUI protocol does not need to know its host is OMC:
OMC_ACTIONUI_REMOTE_ENDPOINT- a new always-exported special word - andACTIONUI_REMOTE_ENDPOINT, merged into every child's environment in every execution mode.ACTIONUI_WINDOW_UUID, the plain-name alias ofOMC_ACTIONUI_WINDOW_UUID.ACTIONUI_REMOTE_TOKEN_FD, the descriptor the handler's access token arrives on (see Security below).The full reference is the new
omc_python_bridge_guide.md;omc_runtime_context_reference.mdlists the new variables.The
omcPython moduleA Python handler can now simply
import omc:omc.window()returns the dispatching window as anomc.OMCWindow, a subclass of ActionUI'sactionui_remote.Window, so every ActionUI verb - reading, writing, tables, adding and removing elements, modals and toasts, batching - travels over the bridge. It raisesomc.OMCErrorwith a specific message when the applet serves no bridge (a command file with noACTIONUI_WINDOW) or the command was not dispatched from a window.omc_dialog_controlandomc_next_commandtools with exactly the arguments a shell handler would use, so each verb still has a single implementation.omc.context()parses the runtime environment the engine exports, including the dispatch-time value snapshots, into named fields, so a handler no longer opens with a block ofos.environ.getcalls.Nothing to install. AppletBuilder copies
omc.pyandactionui_remote.pyintoContents/Library/Packages/of every applet that has Python handlers and an embedded Python runtime, both when building and when creating an applet from a template. That directory is already onPYTHONPATHand is never touched by a Python runtime update, so the modules survive one. An applet that uses the system Python gets nothing, because the engine does not put that directory onPYTHONPATHfor it.AppletBuilder also vendors ActionUI's shell clients -
actionui_remote.shandactionui_remote.zsh, together with the two awk programs they load,actionui_remote_escape.awkandactionui_remote_walk.awk- and copies them into built applets. The install is all-or-nothing: a partial copy is rolled back, since three of the four files make a client that refuses to load.Embedded Python thinning from the Build & Run pane
The Build & Run pane gains an Embedded Python Thinning group, and the
appletbuilderagent command-line tool gains a matchingappletbuilder thin-python plan|apply|plan-applysubcommand. Both drive one shared phase inlib.build.sh, the way Build and Test already do, and AppletBuilder now bundles the Python-Embedding thinning toolkit underContents/Library/python_thinning, so neither needs a checkout of that repository.The workflow is unchanged: write a reviewable JSON plan from a clone of the applet, then apply it, which removes what the plan names, verifies the result, and restores the interpreter if anything needed went with it. On a fresh Python applet that takes the interpreter from 61.3 MB to 43.1 MB.
Thinning deliberately stays out of Build, and every path that removes modules asks for confirmation first, naming the bundle. When no plan sits beside the bundle, apply falls back to the last plan written for that applet, matched on its bundle identifier, and says so in the log. That supports the intended release flow: plan against the development copy, apply to a distribution copy. An optional per-applet
<App>.thinning-keep.txtlists modules to keep regardless of the analysis.Off-screen preview that works on a locked screen
appletbuilder previewnow runs the bundled ActionUIViewer with--hide-window, which draws the window in-process at an off-screen position. No window appears over the desktop, no Screen Recording permission is involved, and the capture works while the screen is locked - the usual state in an agent session, where the old window-server capture returned an empty image. Pass--show-windowfor the previous behavior.Known gaps of the off-screen capture, from ActionUI 0.8.1: no window shadow, TabView tab segments draw as a solid block, and a locked screen gives controls the inactive gray tint.
The skill gains a hard rule telling an agent to screenshot an ActionUI change and look at the image, and the nib migration guide drops its "transparent or black on a locked screen" trap.
Security
The bridge token is handed to each handler on a descriptor, never in the environment
The ActionUI bridge refuses clients that do not present a token. A handler's environment is copied from the applet's, and a process's exec-time environment is visible to any process of the same user through
ps -E-python3andnodecannot be made to hide it. A token passed in the environment would therefore have been published machine-wide for as long as the handler ran, and clearing the variable inside the handler does not help, becausepsreads a snapshot taken at exec.So the engine mints one token per spawned handler and delivers it on an inherited pipe: the token is written before the spawn, the read end becomes descriptor 3 in the child, and the parent closes both ends immediately. The child is told the number through
ACTIONUI_REMOTE_TOKEN_FD=3and never seesACTIONUI_REMOTE_TOKEN- the host removes that from its own environment as soon as the bridge starts, and the spawn strips it from the child's environment as a second layer. The shipped Python and shell clients read the token from the descriptor.Known limits, documented and pinned by tests:
exe_terminalandexe_itermget no token: Terminal is not the applet's child and inherits no descriptor, and writing the token into the export script would put it in a file in the clear.exe_silent_systemand the AppleScript execution modes get no token either, since they take neither an environment nor a descriptor from the engine. These modes can still change the window throughomc_dialog_control, which needs no token.Nothing regresses: the bridge is new in this release.
Testing
omctest stands up a real bridge host
A Python handler that reads its window needs a host on the other end of the socket, and under test there is no engine. For an applet that ships the client, the harness now runs ActionUI's own fake host for the length of a test file, seeded with the window UUID the harness already exports and with the applet's elements. Without it, such a handler did not fail an assertion - it raised, and the whole file read as a crash.
It is a real host rather than a recording stub: the client and protocol are the shipped ones, so an applet is tested against a genuine implementation.
bridge_calledandbridge_valueare new assertions that read what the applet did over the bridge. They sit besideui_valueon purpose: ActionUI verbs travel over the bridge, while OMC's own window verbs still go throughomc_dialog_control- sobridge_valuereads back aset_valueandui_valuereads back aset_title.omc_window_switchnow carries the plain-name window UUID as well as the prefixed one. The client prefers the plain name, so leaving it behind pointed a handler at the window the test had just left.New and wider test coverage
OMCActionUIRemoteTests) and the token-carrying spawn (OMCPopenTests). This also fixes five engine tests that had been failing since ActionUI turned on the token requirement.omcmodule (Tools/omc_python_tests).50-thin-python.test.sh(164 checks) covers every decision AppletBuilder makes around thinning: refusing an applet with no embedded Python, plan placement, the three plan-resolution routes, both answers to the confirmation, plan-then-apply ordering, and the pane's checkbox combinations.Compatibility notes
omcmodule is installed at build time. Ifimport omcraisesModuleNotFoundError, the applet was built by an older AppletBuilder.omc_dialog_controland the dispatch-timeOMC_ACTIONUI_VIEW_*snapshots work exactly as before; the bridge guide has a section on when to keep using them.OMCTEST_API_VERSIONgoes from 6 to 7.appletbuilder previewrenders off screen by default. Scripts that relied on a visible window should pass--show-window.ActionUI 0.8.1
The bundled ActionUI moves to 0.8.1, which brings:
--method offscreenand--hide-window, with the older capture methods falling back to off-screen drawing when the screen is locked or the capture comes back empty.VideoPlayerelement crashed at load in a Swift Package Manager executable such as ActionUIViewer.Documentation and skill
Documentation/omc_python_bridge_guide.md, also bundled in AppletBuilder: the two entry points, reading, writing, tables, elements, modals, OMC's own window verbs, batching, errors, when to stay withomc_dialog_control, testing a bridge handler, and how it works underneath.omc_python_scripting_guide.mdintroduces the module and links to the guide;omc_runtime_context_reference.mddocuments the bridge variables;omctest_guide.mddocuments the bridge host and the two new assertions.appletbuilder_user_guide.mdcovers the thinning group and contrasts the GUI's live Preview window with the command-line tool's off-screen PNG, and the agent README documentsthin-python, off-screen preview and--show-window.building_omc_applet.mdlists WatchdogApp as an ActionUI applet with Python handlers.omc_applet_catalog.mdupdated for Watchdog.app.In this distribution
~/Library/Preferences/com.abracode.OnMyCommandCMPrefs.plist.codesign_applet.sh,install_contextual_menu_plugin.sh,thin_distribution.sh,OMCApplet.entitlements, and the examplecom.abracode.OnMyCommandCMPrefs.plist.See the main OMC README at https://github.com/abra-code/OMC/ for full documentation on commands, runtime context, dialogs, and services.
This discussion was created from the release OMC version 5.3.
All reactions