资讯详情

资讯详情

如何基于 AeroSpace 的 socket 协议实现自定义客户端调用窗口管理命令?

如何基于 AeroSpace 的 socket 协议实现自定义客户端调用窗口管理命令【免费下载链接】AeroSpaceAeroSpace is an i3-like tiling window manager for macOS项目地址: https://gitcode.com/GitHub_Trending/ae/AeroSpace如果你需要在没有aerospaceCLI 封装的语言里比如给状态栏程序、编辑器插件或脚本语言写集成层直接操作正在运行的 AeroSpace可以绕过命令行通过 Unix socket 直连AeroSpace.app服务进程。AeroSpace 的文档在 docs/guide.adoc 的 Socket protocol 一节专门描述了aerospaceCLI 与服务端之间的这套协议明确说明它面向的就是想从自定义客户端集成 AeroSpace的场景。本文按该节内容给出连接方式、握手、帧格式、请求/应答结构和一个可直接运行的 Python 客户端。前提macOS 上AeroSpace.apprelease 构建或AeroSpace-Debug.appdebug 构建已在运行客户端与它运行在同一用户下。连接地址与协议版本握手AeroSpace 监听一个 Unix-domain stream socket路径取决于构建类型/tmp/bobko.aerospace-${USER}.sock # AeroSpace.app (release builds) /tmp/bobko.aerospace.debug-${USER}.sock # AeroSpace-Debug.app (debug builds only)${USER}是 Swift 中NSUserName()的结果通常与 shell 里的$USER/id -un/whoami等价。连上之后双方立即执行一次性握手客户端把自己的SOCKET_PROTOCOL_VERSION作为单个 4 字节无符号整数写出服务端读取客户端版本决定以何种兼容模式与该客户端通信服务端写出自己的SOCKET_PROTOCOL_VERSION同样 4 字节无符号整数双方各自比较版本决定是否继续处理。当前唯一有效的SOCKET_PROTOCOL_VERSION值是1。文档同时警告服务端在发出自己的版本后如果不认识客户端的版本会直接断开连接。也就是说握手版本不匹配时不会收到错误码而是表现为连接被对端关闭。所有线上数值都是 4 字节无符号整数、host byte order由于 AeroSpace 只运行在 Apple Silicon 和 Intel macOS两者均为小端实际上帧定界恒为 4 字节、小端。消息帧格式握手完成后每个方向上的每条消息都是一个长度前缀 JSON 帧--------------------------------------------------- | length (UInt32, LE) | JSON payload (UTF-8 bytes) | ---------------------------------------------------length是 JSON payload 的字节长度不包含4 字节长度前缀本身。payload 是 UTF-8 编码的 JSON 对象。注意 recv 时必须循环读取不能假设一次recv恰好收齐一帧。流程分两种一次性命令几乎所有命令客户端发一个ClientRequest帧服务端回一个ServerAnswer帧这条一发一收可以无限循环直到客户端断开连接。subscribe 命令客户端发一个args以subscribe开头的ClientRequest帧之后服务端持续发送无界的ServerEvent帧流直到客户端断开。客户端在此连接上不得再发送任何其他内容。ClientRequest 与 ServerAnswer 结构请求 payload 是一个 JSON 对象{ args: [workspace, 1], stdin: , windowId: null, workspace: null }各字段含义引自 docs/guide.adoc字段类型说明args字符串数组传给aerospace的 CLI 参数不含程序名本身。例如[workspace, 1]就是aerospace workspace 1的线上形式发送 subscribe 命令时传[subscribe, ...]stdin字符串喂给命令的标准输入内容命令不读 stdin 时用。部分命令如workspace --stdin会消费该字段windowId无符号 32 位整数或null客户端侧看到的AEROSPACE_WINDOW_ID环境变量的值作用域内没有该变量时传nullworkspace字符串或null客户端侧看到的AEROSPACE_WORKSPACE环境变量的值未设置时传nullwindowId/workspace用于把回调上下文转发给需要目标窗口的命令layout、move、close、move-node-to-workspace等。文档给出的目标解析优先级从高到低是--window-id命令行选项 →--workspace命令行选项 →AEROSPACE_WINDOW_ID环境变量 →AEROSPACE_WORKSPACE环境变量 → 当前焦点窗口或焦点空工作区。你的自定义客户端若在回调脚本里被调用可以照搬这两个环境变量的取值填进请求里让命令继续作用于触发回调的那个窗口。一次性命令的应答是一个 JSON 对象{ exitCode: 0, stdout: 1\n2\n3, stderr: , serverVersionAndHash: 0.20.0-Beta 33fa0643 }字段类型说明exitCode有符号 32 位整数成功为0失败非零与aerospaceCLI 会退出的码一致stdout字符串CLI 本应打印到标准输出的内容可能含换行stderr字符串CLI 本应打印到标准错误的内容serverVersionAndHash字符串形如version git-hash标识运行中的服务端可用于兼容性检查文档建议与你客户端的对应字符串比较不匹配时向用户告警最小 Python 客户端文档给出的端到端示例是一个用 Python 标准库执行aerospace list-workspaces --focused的客户端可直接复制运行前提是 AeroSpace 正在运行import getpass, json, socket, struct SOCKET_PROTOCOL_VERSION 1 sock_path f/tmp/bobko.aerospace-{getpass.getuser()}.sock def send_frame(s, payload): s.sendall(struct.pack(I, len(payload)) payload) def recv_exact(s, n): buf b while len(buf) n: chunk s.recv(n - len(buf)) if not chunk: raise ConnectionError(server closed connection) buf chunk return buf def recv_frame(s): (length,) struct.unpack(I, recv_exact(s, 4)) return recv_exact(s, length) with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: s.connect(sock_path) # Version handshake s.sendall(struct.pack(I, SOCKET_PROTOCOL_VERSION)) (server_version,) struct.unpack(I, recv_exact(s, 4)) if server_version ! SOCKET_PROTOCOL_VERSION: raise RuntimeError(funsupported server version {server_version}) # One-shot command request { args: [list-workspaces, --focused], stdin: , windowId: None, workspace: None, } send_frame(s, json.dumps(request).encode(utf-8)) answer json.loads(recv_frame(s)) if answer[exitCode] ! 0: raise RuntimeError(answer[stderr]) print(answer[stdout])把args换成任意 CLI 参数即可调用其他命令例如[focus, --workspace, 2]对应aerospace focus --workspace 2。debug 构建把sock_path换成/tmp/bobko.aerospace.debug-{getpass.getuser()}.sock。实现 subscribe 事件流当args以subscribe开头时服务端进入事件流模式其余参数遵循 aerospace-subscribe 命令的同一套语法--all、--no-send-initial或显式的事件类型列表。订阅建立后服务端持续按同一长度前缀格式写出ServerEvent帧直到连接关闭客户端在连接上不能再发送任何内容。可订阅的事件类型引自 docs/aerospace-subscribe.adocfocus-changed窗口焦点变化含windowId、workspacefocused-monitor-changed焦点显示器变化含workspace、monitorIdfocused-workspace-changed焦点工作区变化含workspace、prevWorkspacemode-changed绑定模式变化含modewindow-detected检测到新窗口含windowId、workspace、appBundleId、appNamebinding-triggered键盘绑定被触发含binding、mode连接建立时服务端会立即发送当前状态当前窗口的focus-changed、当前模式的mode-changed等加上--no-send-initial可跳过初始状态。文档给出的事件输出示例JSON lines一个 JSON 对象一行{_event:focused-monitor-changed,monitorId:1,workspace:M} {_event:focused-workspace-changed,prevWorkspace:M,workspace:M} {_event:mode-changed,mode:main} {_event:focus-changed,windowId:28218,workspace:M}上面是文档示例字段结构可以参考具体数值会随你的环境变化。结果验证与失败判定一次调用是否成功按文档定义有两个判定点握手阶段服务端返回的版本号必须等于客户端发送的1否则报错更隐蔽的情况是服务端不认识你的版本时会在发完自己的版本后直接断开连接你的客户端会表现为recv读到空字节连接被对端关闭。命令阶段检查ServerAnswer的exitCode0为成功非零时stderr字段携带与 CLI 相同的错误输出命令的输出结果在stdout字段里与直接在终端跑aerospace args...的打印一致。list-workspaces --focused的参数语义如--focused等价于--monitor focused --visible、输出格式变量等可参考 aerospace-list-workspaces完整命令清单见 docs/commands.adoc。限制协议版本目前只有一个有效值1服务端对不认识版本的客户端不返回错误信息直接断开连接客户端侧只能靠连接关闭来感知。subscribe 连接是单向流发出订阅请求后客户端不能复用该连接再发其他命令。socket 路径带用户名客户端必须以运行 AeroSpace 的同一用户身份访问release 与 debug 构建使用不同的 socket 文件构建类型选错会直接连不上。【免费下载链接】AeroSpaceAeroSpace is an i3-like tiling window manager for macOS项目地址: https://gitcode.com/GitHub_Trending/ae/AeroSpace创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →