From 4a2502cb63188fe42a9ab84495c6a481c5462a60 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 2 Jul 2026 22:08:56 -0700 Subject: [PATCH] Replace hand-rolled shell IPC with qs ipc call The embedded Perl program parsed instance.lock QString framing and spoke the Quickshell IPC socket protocol by hand. Quickshell ships this natively: qs -p ipc call does instance discovery, the call, and response printing, byte-identical for every in-repo call pattern. The bash wrapper keeps what qs doesn't do: the -q best-effort mode, the {} default argument for summon/toggle, and the familiar error wordings. Two qs quirks are handled: function names like "show" shadow qs's own CLI subcommands, so arguments are always passed after --, and IPC-level failures (unknown target/function, bad arguments) are reported on stdout with exit 0, so those are matched and mapped to stderr with exit 1. 165 lines to 62. Co-Authored-By: Claude Fable 5 --- bin/omarchy-shell | 146 +++++++--------------------------------------- 1 file changed, 22 insertions(+), 124 deletions(-) diff --git a/bin/omarchy-shell b/bin/omarchy-shell index 79edb244..583beee1 100755 --- a/bin/omarchy-shell +++ b/bin/omarchy-shell @@ -10,6 +10,12 @@ if [[ ${1:-} == "-q" ]]; then shift fi +fail() { + (( QUIET )) && exit 0 + echo "$1" >&2 + exit 1 +} + if (( $# == 0 )) || [[ $1 == "-h" || $1 == "--help" ]]; then cat < [args...] @@ -31,134 +37,26 @@ USAGE exit 0 fi -if (( $# < 2 )); then - (( QUIET )) && exit 0 - echo "Usage: omarchy-shell [args...]" >&2 - exit 1 -fi - -if [[ -z ${OMARCHY_PATH:-} ]]; then - (( QUIET )) && exit 0 - echo "OMARCHY_PATH is not set" >&2 - exit 1 -fi - -SHELL_QML="$OMARCHY_PATH/shell/shell.qml" +(( $# >= 2 )) || fail "Usage: omarchy-shell [args...]" +[[ -n ${OMARCHY_PATH:-} ]] || fail "OMARCHY_PATH is not set" +[[ -f $OMARCHY_PATH/shell/shell.qml ]] || fail "omarchy-shell config not found: $OMARCHY_PATH/shell/shell.qml" if [[ $1 == "shell" && ( $2 == "summon" || $2 == "toggle" ) ]] && (( $# == 3 )); then set -- "$1" "$2" "$3" "{}" fi -export OMARCHY_SHELL_QUIET="$QUIET" +# The -- keeps function names that shadow qs subcommands (e.g. show) as +# positionals. qs reports connection failures with a nonzero exit, but IPC-level +# failures (unknown target/function, bad arguments) go to stdout with exit 0. +output=$(qs -p "$OMARCHY_PATH/shell" ipc call -- "$@" 2>/dev/null) || fail "omarchy-shell is not running" -perl -MCwd=abs_path -MEncode=encode,decode -MSocket -MIO::Handle \ - -e ' - binmode STDOUT, ":encoding(UTF-8)"; - binmode STDERR, ":encoding(UTF-8)"; - $SIG{PIPE} = "IGNORE"; +case $output in + "Target not found." | "Function not found." | "Too few arguments provided"* | "Too many arguments provided"*) + fail "$output" + ;; +esac - sub read_qstring { - my ($data, $offset) = @_; - return (undef, $offset) if $offset + 4 > length($data); - my $length = unpack("N", substr($data, $offset, 4)); - $offset += 4; - return (undef, $offset) if $length == 0xffffffff || $offset + $length > length($data); - return (decode("UTF-16BE", substr($data, $offset, $length)), $offset + $length); - } - - sub qstring { - my $encoded = encode("UTF-16BE", $_[0] // ""); - return pack("N", length($encoded)) . $encoded; - } - - sub write_all { - my ($client, $payload) = @_; - my $written = 0; - while ($written < length($payload)) { - my $n = syswrite($client, $payload, length($payload) - $written, $written); - return 0 unless defined $n; - $written += $n; - } - return 1; - } - - sub print_success { - my ($response) = @_; - my ($message) = read_qstring($response, 2); - return 0 unless defined $message && length($message); - print $message; - print "\n" unless substr($message, -1) eq "\n"; - return 0; - } - - my $shell_qml_arg = shift @ARGV; - my $shell_qml = abs_path($shell_qml_arg); - my $target = decode("UTF-8", shift @ARGV); - my $function = decode("UTF-8", shift @ARGV); - my @args = map { decode("UTF-8", $_) } @ARGV; - my $runtime_dir = $ENV{"XDG_RUNTIME_DIR"} || "/run/user/$<"; - my $quiet = ($ENV{"OMARCHY_SHELL_QUIET"} || "") eq "1"; - my $payload = chr(3) . qstring($target) . qstring($function) . pack("N", scalar @args) . join("", map { qstring($_) } @args); - my @candidates; - - if (!defined $shell_qml) { - exit 0 if $quiet; - print STDERR "omarchy-shell config not found: $shell_qml_arg\n"; - exit 1; - } - - for my $lock_path (glob("$runtime_dir/quickshell/by-id/*/instance.lock")) { - my $data; - next unless open(my $lock, "<:raw", $lock_path); - { local $/; $data = <$lock>; } - close($lock); - my (undef, $offset) = read_qstring($data, 0); - my ($path) = read_qstring($data, $offset); - next unless $path; - my $abs_path = abs_path($path); - next unless defined $abs_path && $abs_path eq $shell_qml; - (my $socket_path = $lock_path) =~ s{/instance\.lock$}{/ipc.sock}; - push @candidates, [(stat($lock_path))[9] || 0, $socket_path]; - } - - for my $candidate (sort { $b->[0] <=> $a->[0] } @candidates) { - socket(my $client, AF_UNIX, SOCK_STREAM, 0) || next; - if (connect($client, sockaddr_un($candidate->[1]))) { - unless (write_all($client, $payload)) { - close($client); - next; - } - - my $response = ""; - while (1) { - my $buffer = ""; - my $n = sysread($client, $buffer, 65536); - last unless defined $n && $n > 0; - $response .= $buffer; - } - close($client); - - next unless length($response); - my $code = ord(substr($response, 0, 1)); - if ($code == 5) { - exit 0 if $quiet; - print_success($response); - exit 0; - } elsif ($code == 2) { - print STDERR "Target not found.\n" unless $quiet; - } elsif ($code == 3) { - print STDERR "Function not found.\n" unless $quiet; - } elsif ($code == 4) { - print STDERR "Invalid arguments.\n" unless $quiet; - } else { - print STDERR "Unexpected IPC response: $code\n" unless $quiet; - } - exit($quiet ? 0 : 1); - } - close($client); - } - - exit 0 if $quiet; - print STDERR "omarchy-shell is not running\n"; - exit 1; - ' "$SHELL_QML" "$@" +if (( !QUIET )) && [[ -n $output ]]; then + echo "$output" +fi +exit 0