dex

command module
v0.0.0-...-7b48451 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2024 License: BSD-3-Clause Imports: 23 Imported by: 0

README

dex logo dex

dex is an El Gato Stream Deck controller system.

Architecture

dex is built on a JSON-RPC 2.0 message passing system with a message passing kernel managing a set of modules that can communicate with it and each other through it via the API described in the rpc package. Modules may provide services that perform actions in response to Stream Deck button presses or change button images in response to external state.

In addition to handling message passing, the kernel also provides a set of low level operations such as retaining persistent state in a key store and interacting with the Stream Deck device.

Modules running in the system are started by the kernel which passes a set of well-known command line flags to establish communication. These string flags MUST be handled by the module's executable at start.

  • -uid — opaque session-unique ID of the module
  • -network — network for communication ("unix" or "tcp")
  • -addr — address for communication

In addition to these required flags, if the log module.*.log_mode configuration is used, the module must understand the boolean log_stdout flag which indicates that it should log to stdout instead of stderr.

When a module is spawned it is given the read end of a pipe in stdin that may be used to detect unexpected termination of the kernel process.

Configuration

The complete specification of the required by the system is defined in the configuration schema using the CUE language and in the Go types used to hold the configuration values.

Configurations are represented on disk as TOML files and may be split across multiple files.

Configuration files MUST be in ${XDG_CONFIG_HOME}/dex/ (~/.config/dex/ on Linux and ~/Library/Application Support/dex/ on Mac) and this directory will be created at start up if it does not exist.

The minimal system configuration is

[kernel]
device = []
network = "unix"

which is a no-op system that brings up a kernel using Unix sockets, with no modules running, and does not attempt to find a Stream Deck device.

Each module and service it provides MUST use configurations consistent with the global schema, but MAY also have specific configuration options under module.*.options. Modules SHOULD use an api package so that configuration and RPC types are visible publicly. If module.*.schema exists it is interpreted as a CUE schema and the complete configuration will be validated against this schema in addition to the global schema.

Kernel RPC methods

All RPC methods expect call and notify parameters to be a JSON object that can be deserialised into an rpc.Message envelope which contains the time of the call, the module/service UID of the caller, and the call parameters in the rpc.Message.Body field.

Core methods provided by the kernel:

  • call — JSON-RPC2.0 call with a body corresponding to rcp.Message[rpc.Forward[any]] forwarding the rpc.Forward.Method call to the module/service identified by rpc.Forward.UID with the parameters in rpc.Forward.Params.
  • notify — JSON-RPC2.0 notify with a body corresponding to rcp.Message[rpc.Forward[any]] forwarding the rpc.Forward.Method notification to the module/service identified by rpc.Forward.UID with the parameters in rpc.Forward.Params.
  • unregisterrpc.Message[rpc.None] to unregister the sending module and its services from the kernel's registry.
  • heartbeatrpc.Message[rpc.Deadline] record a heartbeat from the sending module with a deadline for the next expected heartbeat.
  • staterpc.Message[rpc.None] returns or logs the current rpc.SysState.

Extended methods provide by the kernel:

Module executables are expected to implement the following methods:

  • who — call rpc.Message[rpc.None] returns rpc.Message[string] containing the version of the module executable.
  • configure — call rpc.Message[any] returns rpc.Message[string] with informational text.
  • stop — notify any signals the executable to terminate.

Provided modules

rest

rest is a module that can be used to run forward REST API calls to other modules and the kernel.

runner

runner is a module that can be used to run arbitrary executables. runner services allow buttons to start programs to perform actions.

watcher

watcher is a module that polls the host's user activity, and active application and window. This information can then be sent to other modules to use.

worklog

worklog is a module that can receive polling data from other modules such as watcher to keep a log of active application, AFK status and so on.

The kernel module

The kernel is a special module that has direct access to kernel RPC methods. It is specified in the configuration by omitting the module key for the service. For example, the following two services that are useful in debugging kernel configuration.

Log the current kernel state

Pressing the button at row 0/column 1 will log the kernel state at INFO level.

[service.kernel_state]
serial = ""
listen = [
    {row = 0, col = 1, image = "data:text/plain,state"},
    {row = 0, col = 1, change = "press", do = "state"}
]
Log the current system configuration

Pressing the button at row 0/column 2 will log the system configuration at INFO level.

[service.kernel_system]
serial = ""
listen = [
    {row = 0, col = 2, image = "data:text/plain,system"},
    {row = 0, col = 2, change = "press", do = "system"}
]

Pages

dex has a notion of pages. A page is a set of buttons and actions that are presented as a unit. The current page can be changed using the page RPC call.

For example, the state logging actions above could be put into a "debug" page which is togglable with the button at row 1/column 0.

[service.kernel_debug]
serial = ""
listen = [
    {row = 1, col = 0, image = "data:text/plain,debug"},
    {row = 1, col = 0, change = "press", do = "page", args = {page = "debug"}},
    {page = "debug", row = 1, col = 0, image = "data:text/plain,home"},
    {page = "debug", row = 1, col = 0, change = "press", do = "page", args = {page = "default"}}
]

[service.kernel_state]
serial = ""
listen = [
    {page = "debug", row = 0, col = 1, image = "data:text/plain,state"},
    {page = "debug", row = 0, col = 1, change = "press", do = "state"}
]

[service.kernel_system]
serial = ""
listen = [
    {page = "debug", row = 0, col = 2, image = "data:text/plain,system"},
    {page = "debug", row = 0, col = 2, change = "press", do = "system"}
]

The set of available pages can be obtained using the page_names RPC call. The following button action in the "debug" page logs the list of pages at INFO level. A complete description of the page layout and actions can be obtained using the page_details method.

[service.kernel_page_names]
serial = ""
listen = [
    {page = "debug", row = 0, col = 3, image = "data:text/plain,pages"},
    {page = "debug", row = 0, col = 3, change = "press", do = "page_names"}
]

[service.kernel_page_details]
serial = ""
listen = [
    {page = "debug", row = 0, col = 4, image = "data:text/plain,page details"},
    {page = "debug", row = 0, col = 4, change = "press", do = "page_details"}
]

minidex

minidex is a greatly simplified Stream Deck controller that does not use RPC and is intended primarily for debugging the device management components of the kernel.

Logging

The dex system and provided modules all provide configurable structured logging using log/slog.

Log level for the kernel and modules are specified in their config stanzas using the log_level option. Source location of the log calls can be added by setting log_add_source to true. Both of these options are dynamically reloaded during run time.

[kernel]
device = ...
network = ...
log_level = "info"
log_add_source = false
[module. ...]
path = ...
log_mode = "log"
log_level = "error"
log_add_source = true

Modules have an additional setting, log_mode, that specifies how module logging is handled by the system; options are "log", "passthrough" and "none". The default behaviour is "passthrough". Modules must support the boolean -log_stdout flag to use the "log" option. When a module is passed a true -log_stdout it must log to stdout, but can emit arbitrary text to stderr.

  • log: stdout → stderr stderr → capture and log via system logger

  • passthrough: stdout → stdout stderr → stderr

  • none: stdout → /dev/null stderr → /dev/null

The log_mode option is static and set when the module is spawned.

Installation

dex can be installed from source using the Go tool chain with

go install github.com/kortschak/dex@latest github.com/kortschak/dex/cmd/...@latest

Some dependencies will need to be installed before doing this.

Setting Up a Service (Linux)

On Linux you can start dex as a service using systemd. Since dex handles a single user's interaction with the Stream Deck it should be a user service.

You can place the unit file below either in ~/.config/systemd/user or in /etc/systemd/user, run systemctl --user daemon-reload, and then start the service; systemctl --user start dex.service.

[Unit]
Description=Dex Service

[Service]
Type=simple
ExecStart=%h/bin/dex
Restart=on-failure
StandardError=journal

[Install]
WantedBy=default.target

This will log to the system journal, viewable with journalctl --user -u dex. It assumes that dex is located in your ~/bin.

If debugging dex running as a service, it may be helpful instead to log to a file, in which case replace the StandardError setting with

StandardError=file:%h/.local/state/dex/log/dex.log

or another more convenient path.

Since the service will most likely need access to your user environment (depending on the configuration), if you want the service to start on login, it is best to start the service with an autostart desktop file. For example

[Desktop Entry]
Type=Application
Name=dex
Exec=systemctl --user start dex.service
Comment=Start dex Stream Deck controller

The service can be stopped with systemctl --user stop dex.service.

Setting Up an Agent (Mac)

On Mac you can start dex as an agent using launchd.

Place the job definition property list file below in ~/Library/LaunchAgents as io.kortschak.dex.plist (replacing $PATH_TO_DEX_COMMANDS with the directory in which the commands were installed — go env GOBIN will give this value), run launchctl load -w ~/Library/LaunchAgents/io.kortschak.dex.plist.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key><string>io.kortschak.dex</string>
        <key>ProgramArguments</key>
            <array>
                <string>$PATH_TO_DEX_COMMANDS/dex</string>
                <!-- Any additional flags... e.g. start-up debug logging.
                <string>-log=debug</string>
                -->
            </array>
        <key>KeepAlive</key>
            <dict>
               <key>SuccessfulExit</key><false/>
            </dict>
        <key>ThrottleInterval</key><integer>1</integer>
        <key>EnvironmentVariables</key>
            <dict>
                <key>PATH</key><string>$PATH_TO_DEX_COMMANDS:/bin:/usr/bin:/usr/local/bin</string>
            </dict>
        <key>StandardOutPath</key><string>/tmp/dex.stdout</string>
        <key>StandardErrorPath</key><string>/tmp/dex.stderr</string>
    </dict>
</plist>

Allow dex to have accessibility permissions when requested by the operating system.

The logging locations and environment variables can be altered to suit.

The service can be stopped with launchctl stop io.kortschak.dex and restarted with launchctl stop io.kortschak.dex, It can disabled permanently with launchctl unload -w ~/Library/LaunchAgents/io.kortschak.dex.plist.

See https://launchd.info/ for details of launchd configuration.

Non-Go Dependencies

Interaction with Stream Deck devices depends on github.com/sstallion/go-hid. This package makes use of non-Go dependencies.

The watcher module depends on Xlib, libXss and libXRes on Linux (libx11-dev, libxss1/libxss-dev and libxres1/libxres-dev in deb-based distributions). Xlib headers are required during compilation, but the libraries may be absent with reduced or absent functionality. watcher may be built with a no_xorg build tag to avoid the need for any X11 dependencies; this build will not be able to make use of X11. Testing the watcher module uses gioui.org, and so its dependencies must be provided if testing the module.

Linux udev rules

On Linux, udev rules for HID will need to be added.

In a new file, "/etc/udev/rules.d/99-streamdeck.rules" add

ACTION=="add", ATTRS{idVendor}=="0fd9", MODE="0666"

and then run sudo udevadm trigger.

State debugging

A debug configuration snippet is available in the debug.toml file. This can be used to get system state for reporting bugs. See details within the file.


Written in honour of Alex "Mr Harry Joy" Duval. Just another alternative conference report.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
cmd
rest
The rest executable is a dex module for forwarding REST API calls to dex modules over JSON RPC-2.0.
The rest executable is a dex module for forwarding REST API calls to dex modules over JSON RPC-2.0.
rest/api
Package api defines RPC messages used to communicate with the runner module.
Package api defines RPC messages used to communicate with the runner module.
runner
The runner executable is a dex module for running arbitrary programs using dex.
The runner executable is a dex module for running arbitrary programs using dex.
runner/api
Package api defines RPC messages used to communicate with the runner module.
Package api defines RPC messages used to communicate with the runner module.
watcher
The watcher executable is a dex module for notifying dex of details of the active running application and user input activity.
The watcher executable is a dex module for notifying dex of details of the active running application and user input activity.
watcher/api
Package api defines RPC messages used to communicate with the runner module.
Package api defines RPC messages used to communicate with the runner module.
watcher/dl
Package dl implements dlopen and related functionality.
Package dl implements dlopen and related functionality.
worklog
The worklog executable is a dex module for logging user activity.
The worklog executable is a dex module for logging user activity.
worklog/api
Package api defines RPC messages used to communicate with the runner module.
Package api defines RPC messages used to communicate with the runner module.
worklog/pgstore
Package pgstore provides a worklog data storage layer using PostgreSQL.
Package pgstore provides a worklog data storage layer using PostgreSQL.
worklog/store
Package store provides the worklog data storage layer.
Package store provides the worklog data storage layer.
Package config provides dex system configuration types and schemas.
Package config provides dex system configuration types and schemas.
internal
animation
Package animation provides animated image support.
Package animation provides animated image support.
celext
Package celext provides extensions to ease use of maps in CEL programs.
Package celext provides extensions to ease use of maps in CEL programs.
config
Package config provides live configuration reloading and unification functions.
Package config provides live configuration reloading and unification functions.
device
Package device provides types for managing a dex device.
Package device provides types for managing a dex device.
localtime
Package localtime provides a dynamically updated time.Local value.
Package localtime provides a dynamically updated time.Local value.
locked
Package locked provides concurrency-safe helpers.
Package locked provides concurrency-safe helpers.
mtls
Package mtls provides mTLS and TLS config support.
Package mtls provides mTLS and TLS config support.
private
Package private implements field redaction in maps and structs.
Package private implements field redaction in maps and structs.
slogext
Package slogext provides slog helpers.
Package slogext provides slog helpers.
state
Package state provides state persistence.
Package state provides state persistence.
sys
Package sys manages the state of a running dex system.
Package sys manages the state of a running dex system.
text
Package text provides functions for rendering basicfont.Face fonts to a an image.
Package text provides functions for rendering basicfont.Face fonts to a an image.
version
Package version prints the build version.
Package version prints the build version.
xdg
Package xdg provides functions for handling cross-platform configuration, data and state directories.
Package xdg provides functions for handling cross-platform configuration, data and state directories.
The minidex command builds and runs a minidex/dex controller.
The minidex command builds and runs a minidex/dex controller.
dex
The dex command is the minidex integrator.
The dex command is the minidex integrator.
plugins
Package plugins abuses the go build chain to use code as configuration.
Package plugins abuses the go build chain to use code as configuration.
Package rpc provides a kernel message passing RPC system.
Package rpc provides a kernel message passing RPC system.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL