← Otto Developer Guideotto-msg

User guide: Scripting Otto, Reporting a bug
Specs: Window Tiling

components/otto-msg/ is a command-line tool that runs i3-syntax commands against Otto, prints the window tree, workspaces, outputs, inputs and the compositor’s debug info as JSON, and follows events. Its doctor subcommand writes a bug report. It is a client of the compositor’s org.otto.Shell1 and org.otto.Compositor D-Bus services and has no Wayland connection of its own. Each invocation is a short-lived process that makes one call (or one subscription) on the session bus and exits. It sits in the app layer of architecture.toml; nothing depends on it, and the Otto plugin’s skills call it.

otto-msg calls org.otto.Shell1 and org.otto.Compositor on the session bus; doctor also reads the logs, the configuration and the system

The command grammar and the JSON shapes are i3’s, so a script written for i3-msg or swaymsg ports with a rename.

Where the code is

ModuleWhat it does
src/main.rsargument parsing, one call per -t type, JSON printing; hands doctor and everything after it to doctor::main
src/doctor/mod.rsdoctor’s options; chooses between the interface, --print and a non-interactive write; writes the file, copies it, prints the issue link
src/doctor/sections.rseach section’s reader, all of them reading through Sources (a root folder, the log folder, the configuration files and the compositor’s answer)
src/doctor/compositor.rsthe DebugInfo call and its JSON
src/doctor/logs.rswhere Otto’s logs are
src/doctor/summary.rsthe last otto::summary line in a log, read when the compositor cannot answer
src/doctor/redact.rstakes personal details out of the text
src/doctor/report.rsthe Markdown report, its file name and the pre-filled issue link
src/doctor/term.rs, tui.rsraw mode, the alternate screen and key parsing; the chooser on top of them

Message types

-t picks the message type. Both the underscored and the hyphenated spellings are accepted (get_tree, get-tree).

-tCallOutput
run_command (default)org.otto.Shell1.RunCommand(s) → a(bs)one {"success": …} object per ;-separated command, with error on the ones that failed
get_treeorg.otto.Shell1.GetTree() → sthe tree, i3’s GET_TREE shape
get_workspacesorg.otto.Shell1.GetWorkspaces() → si3’s GET_WORKSPACES shape
get_outputsorg.otto.Shell1.GetOutputs() → si3’s GET_OUTPUTS shape
get_inputsorg.otto.Shell1.GetInputs() → ssway’s GET_INPUTS shape
get_debug_infoorg.otto.Compositor.DebugInfo() → sversion, backend, renderer, configuration files, log path and outputs
subscribeorg.otto.Shell1’s signalseach matching event’s JSON payload

The getters answer with a JSON string. otto-msg parses it and pretty-prints it (or prints it on one line with -r); a string that is not JSON is passed through as it came.

How it runs

otto-msg doctor

When the first argument is doctor, the rest of the command line is doctor’s own: --no-redact, --print, --output <path>, --lines <n> and --no-clipboard. It gathers what a bug report needs, lets the user choose what to share, and writes one Markdown file. It sends nothing anywhere.

Sections

Every section is selected by default.

SectionRead from
Operating systemPRETTY_NAME (or NAME and VERSION_ID) from /etc/os-release or /usr/lib/os-release; the kernel from /proc/sys/kernel/osrelease
Graphicseach card<N> under /sys/class/drm: PCI vendor and device, kernel driver, boot VGA; the renderer and any Vulkan fallback from DebugInfo
Screensthe outputs in DebugInfo: mode, refresh, scale, position, virtual or not
Ottoversion, commit, backend and the configuration files read, from DebugInfo; otto-msg’s own version
Configurationeach configuration file, whole, in a fenced toml block
Session logsthe last lines of each log (2000 by default, --lines changes it), colours stripped, at most 8 MiB read from the end of a file

The configuration files are the ones DebugInfo lists. When the compositor does not answer, it reads the usual locations instead (the system and user config.toml, and otto_config.toml, otto_config.tty-udev.toml and otto_config.udev.toml in the home folder) and says so in the section.

The compositor’s details

src/doctor/compositor.rs calls org.otto.Compositor.DebugInfo with a three-second timeout: the call goes through the compositor’s main loop, and a report about a frozen session is when this tool runs. The answer is the JSON object described in Compositor D-Bus API.

When the call fails (Otto crashed, is frozen, or is too old to have the method), summary.rs looks for the last otto::summary line in the logs. The compositor logs that line, holding the same JSON, once its renderer and an output are up and again whenever the outputs change. Only log files are searched, not the journal or the panic log. The Graphics, Screens and Otto sections then carry a “Not live” note naming the log and the reason. With no such line either, they say the compositor could not be reached.

Where the logs are

logs.rs lists them in this order, skipping a file already listed under another path:

  1. The file DebugInfo reports as log_path, and the same name with .1 (the previous session).
  2. otto.log and otto.log.1 in $XDG_STATE_HOME/otto.
  3. For each running otto process of this user (by comm, or by the executable’s name), its stderr: read through /proc/<pid>/fd/2 when that is a regular file, or with journalctl _PID=<pid> (the user journal, then the system one) when it is a socket and the process has JOURNAL_STREAM set.
  4. session.log and session.log.1, kept by the otto-session login script.
  5. panic.log, every recorded crash.

Writing the report

Who it talks to

PeerHow
Compositor, shellorg.otto.Shell1 at /org/otto/Shell1, served by src/shell_service.rs. Each call is handed to the compositor thread over a calloop channel and answered on a oneshot
Compositor, debug infoorg.otto.Compositor.DebugInfo at /org/otto/Compositor, served by src/screenshare/dbus_service.rs and answered on the main loop; the JSON is built in src/debug_info.rs
Command parsersrc/shell/commands.rs and src/workspaces/tiling/command.rs in the compositor, behind RunCommand
otto-dbusthe ShellProxyBlocking and CompositorProxyBlocking proxies and the interface definitions
The system (doctor)/etc, /proc and /sys/class/drm, Otto’s log files, journalctl, wl-copy

Its dependencies are zbus, otto-dbus, otto-foundations (XDG folders), serde_json and libc. The terminal interface uses no UI crate.

Going deeper