diff --git a/api/lua/msg.lua b/api/lua/msg.lua index 579279d..17b2b11 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -31,7 +31,7 @@ -------------------------------------------------------------------------------------------- ----@class _Request +---@class __Request --Windows ---@field GetWindowProps { window_id: WindowId } --Outputs @@ -39,11 +39,12 @@ --Tags ---@field GetTagProps { tag_id: TagId } ----@alias Request _Request | "GetWindows" | "GetOutputs" | "GetTags" +---@alias _Request __Request | "GetWindows" | "GetOutputs" | "GetTags" +---@alias Request { request_id: integer, request: _Request } ---@class IncomingMsg ----@field CallCallback { callback_id: integer, args: Args } ----@field RequestResponse { response: RequestResponse } +---@field CallCallback { callback_id: integer, args: Args? } +---@field RequestResponse { request_id: integer, response: RequestResponse } ---@class Args ---@field Spawn { stdout: string?, stderr: string?, exit_code: integer?, exit_msg: string? } @@ -51,6 +52,7 @@ ---@alias WindowId integer ---@alias TagId integer +---@alias RequestId integer ---@alias OutputName string ---@class RequestResponse diff --git a/api/lua/output.lua b/api/lua/output.lua index c7492bc..419bbfb 100644 --- a/api/lua/output.lua +++ b/api/lua/output.lua @@ -4,67 +4,64 @@ -- -- SPDX-License-Identifier: MPL-2.0 +---@class OutputGlobal +local output_global = {} + ---@class Output A display. ---@field private _name string The name of this output (or rather, of its connector). -local op = {} +local output = {} ---Get this output's name. This is something like "eDP-1" or "HDMI-A-0". ---@return string -function op:name() +function output:name() return self._name end ---Get all tags on this output. See `tag.get_on_output`. ---@return Tag[] -function op:tags() +function output:tags() return require("tag").get_on_output(self) end ---Add tags to this output. See `tag.add`. ---@param ... string The names of the tags you want to add. ---@overload fun(self: self, tag_names: string[]) -function op:add_tags(...) +function output:add_tags(...) require("tag").add(self, ...) end ---Get this output's make. ---@return string|nil -function op:make() - SendRequest({ +function output:make() + local response = ReadMsg(SendRequest({ GetOutputProps = { output_name = self._name, }, - }) - - local response = ReadMsg() + })) local props = response.RequestResponse.response.OutputProps return props.make end ---Get this output's model. ---@return string|nil -function op:model() - SendRequest({ +function output:model() + local response = ReadMsg(SendRequest({ GetOutputProps = { output_name = self._name, }, - }) - - local response = ReadMsg() + })) local props = response.RequestResponse.response.OutputProps return props.model end ---Get this output's location in the global space. ---@return { x: integer, y: integer }|nil -function op:loc() - SendRequest({ +function output:loc() + local response = ReadMsg(SendRequest({ GetOutputProps = { output_name = self._name, }, - }) - - local response = ReadMsg() + })) local props = response.RequestResponse.response.OutputProps if props.loc == nil then return nil @@ -75,14 +72,12 @@ end ---Get this output's resolution in pixels. ---@return { w: integer, h: integer }|nil -function op:res() - SendRequest({ +function output:res() + local response = ReadMsg(SendRequest({ GetOutputProps = { output_name = self._name, }, - }) - - local response = ReadMsg() + })) local props = response.RequestResponse.response.OutputProps if props.res == nil then return nil @@ -94,28 +89,24 @@ end ---Get this output's refresh rate in millihertz. ---For example, 60Hz will be returned as 60000. ---@return integer|nil -function op:refresh_rate() - SendRequest({ +function output:refresh_rate() + local response = ReadMsg(SendRequest({ GetOutputProps = { output_name = self._name, }, - }) - - local response = ReadMsg() + })) local props = response.RequestResponse.response.OutputProps return props.refresh_rate end ---Get this output's physical size in millimeters. ---@return { w: integer, h: integer }|nil -function op:physical_size() - SendRequest({ +function output:physical_size() + local response = ReadMsg(SendRequest({ GetOutputProps = { output_name = self._name, }, - }) - - local response = ReadMsg() + })) local props = response.RequestResponse.response.OutputProps if props.physical_size == nil then return nil @@ -126,14 +117,12 @@ end ---Get whether or not this output is focused. This is currently defined as having the cursor on it. ---@return boolean|nil -function op:focused() - SendRequest({ +function output:focused() + local response = ReadMsg(SendRequest({ GetOutputProps = { output_name = self._name, }, - }) - - local response = ReadMsg() + })) local props = response.RequestResponse.response.OutputProps return props.focused end @@ -145,7 +134,7 @@ local function new_output(output_name) ---@type Output local o = { _name = output_name } -- Copy functions over - for k, v in pairs(op) do + for k, v in pairs(output) do o[k] = v end @@ -154,9 +143,6 @@ end ------------------------------------------------------ ----@class OutputGlobal -local output = {} - ---Get an output by its name. --- ---"Name" in this sense does not mean its model or manufacturer; @@ -170,9 +156,8 @@ local output = {} ---``` ---@param name string The name of the output. ---@return Output|nil output The output, or nil if none have the provided name. -function output.get_by_name(name) - SendRequest("GetOutputs") - local response = ReadMsg() +function output_global.get_by_name(name) + local response = ReadMsg(SendRequest("GetOutputs")) local output_names = response.RequestResponse.response.Outputs.output_names for _, output_name in pairs(output_names) do @@ -190,9 +175,8 @@ end ---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays. ---@param model string The model of the output(s). ---@return Output[] outputs All outputs with this model. -function output.get_by_model(model) - SendRequest("GetOutputs") - local response = ReadMsg() +function output_global.get_by_model(model) + local response = ReadMsg(SendRequest("GetOutputs")) local output_names = response.RequestResponse.response.Outputs.output_names ---@type Output[] @@ -212,10 +196,8 @@ end ---@param width integer The width of the outputs, in pixels. ---@param height integer The height of the outputs, in pixels. ---@return Output[] outputs All outputs with this resolution. -function output.get_by_res(width, height) - SendRequest("GetOutputs") - - local response = ReadMsg() +function output_global.get_by_res(width, height) + local response = ReadMsg(SendRequest("GetOutputs")) local output_names = response.RequestResponse.response.Outputs.output_names @@ -251,9 +233,8 @@ end ---local tags = output.get_focused():tags() -- will NOT warn for nil ---``` ---@return Output|nil output The output, or nil if none are focused. -function output.get_focused() - SendRequest("GetOutputs") - local response = ReadMsg() +function output_global.get_focused() + local response = ReadMsg(SendRequest("GetOutputs")) local output_names = response.RequestResponse.response.Outputs.output_names for _, output_name in pairs(output_names) do @@ -274,7 +255,7 @@ end ---Please note: this function will be run *after* Pinnacle processes your entire config. ---For example, if you define tags in `func` but toggle them directly after `connect_for_all`, nothing will happen as the tags haven't been added yet. ---@param func fun(output: Output) The function that will be run. -function output.connect_for_all(func) +function output_global.connect_for_all(func) ---@param args Args table.insert(CallbackTable, function(args) local args = args.ConnectForAllOutputs @@ -290,14 +271,12 @@ end ---Get the output the specified tag is on. ---@param tag Tag ---@return Output|nil -function output.get_for_tag(tag) - SendRequest({ +function output_global.get_for_tag(tag) + local response = ReadMsg(SendRequest({ GetTagProps = { tag_id = tag:id(), }, - }) - - local response = ReadMsg() + })) local output_name = response.RequestResponse.response.TagProps.output_name if output_name == nil then @@ -307,4 +286,4 @@ function output.get_for_tag(tag) end end -return output +return output_global diff --git a/api/lua/pinnacle.lua b/api/lua/pinnacle.lua index 1a04125..5017dad 100644 --- a/api/lua/pinnacle.lua +++ b/api/lua/pinnacle.lua @@ -9,6 +9,33 @@ local msgpack = require("msgpack") local SOCKET_PATH = "/tmp/pinnacle_socket" +--[[ rPrint(struct, [limit], [indent]) Recursively print arbitrary data. + Set limit (default 100) to stanch infinite loops. + Indents tables as [KEY] VALUE, nested tables as [KEY] [KEY]...[KEY] VALUE + Set indent ("") to prefix each line: Mytable [KEY] [KEY]...[KEY] VALUE +--]] +function RPrint(s, l, i) -- recursive Print (structure, limit, indent) + l = l or 100 + i = i or "" -- default item limit, indent string + if l < 1 then + print("ERROR: Item limit reached.") + return l - 1 + end + local ts = type(s) + if ts ~= "table" then + print(i, ts, s) + return l - 1 + end + print(i, ts) -- print "table" + for k, v in pairs(s) do -- print "[KEY] VALUE" + l = RPrint(v, l, i .. "\t[" .. tostring(k) .. "]") + if l < 0 then + break + end + end + return l +end + ---Read the specified number of bytes. ---@param socket_fd integer The socket file descriptor ---@param count integer The amount of bytes to read @@ -85,6 +112,7 @@ function pinnacle.setup(config_func) ---This is an internal global function used to send serialized messages to the Pinnacle server. ---@param data Msg function SendMsg(data) + -- RPrint(data) local encoded = msgpack.encode(data) assert(encoded) -- print(encoded) @@ -93,54 +121,98 @@ function pinnacle.setup(config_func) socket.send(socket_fd, encoded) end + local request_id = 0 + ---Get the next request id. + ---@return integer + local function next_request_id() + local ret = request_id + request_id = request_id + 1 + return ret + end + + ---@type table + local unread_req_msgs = {} + ---@type table + local unread_cb_msgs = {} + ---This is an internal global function used to send requests to the Pinnacle server for information. - ---@param data Request + ---@param data _Request + ---@return RequestId function SendRequest(data) + local req_id = next_request_id() SendMsg({ - Request = data, + Request = { + request_id = req_id, + request = data, + }, }) + return req_id end ---This is an internal global function used to read messages sent from the server. ---These are used to call user-defined functions and provide requested information. - function ReadMsg() - local msg_len_bytes, err_msg, err_num = read_exact(socket_fd, 4) - assert(msg_len_bytes) + ---@return IncomingMsg + ---@param req_id integer? A request id if you're looking for that specific message. + function ReadMsg(req_id) + while true do + if req_id then + if unread_req_msgs[req_id] then + local msg = unread_req_msgs[req_id] + unread_req_msgs[req_id] = nil -- INFO: is this a reference? + return msg + end + end - -- TODO: break here if error in read_exact + local msg_len_bytes, err_msg, err_num = read_exact(socket_fd, 4) + assert(msg_len_bytes) - ---@type integer - local msg_len = string.unpack("=I4", msg_len_bytes) - -- print(msg_len) + -- TODO: break here if error in read_exact - local msg_bytes, err_msg2, err_num2 = read_exact(socket_fd, msg_len) - assert(msg_bytes) - -- print(msg_bytes) + ---@type integer + local msg_len = string.unpack("=I4", msg_len_bytes) + -- print(msg_len) - ---@type IncomingMsg - local tb = msgpack.decode(msg_bytes) - -- print(msg_bytes) + local msg_bytes, err_msg2, err_num2 = read_exact(socket_fd, msg_len) + assert(msg_bytes) + -- print(msg_bytes) - return tb + ---@type IncomingMsg + local inc_msg = msgpack.decode(msg_bytes) + -- print(msg_bytes) + + if req_id then + if inc_msg.CallCallback then + unread_cb_msgs[inc_msg.CallCallback.callback_id] = inc_msg + elseif inc_msg.RequestResponse.request_id ~= req_id then + unread_req_msgs[inc_msg.RequestResponse.request_id] = inc_msg + else + return inc_msg + end + else + return inc_msg + end + end end config_func(pinnacle) while true do - local tb = ReadMsg() - - if tb.CallCallback and tb.CallCallback.callback_id then - if tb.CallCallback.args then -- TODO: can just inline - CallbackTable[tb.CallCallback.callback_id](tb.CallCallback.args) - else - CallbackTable[tb.CallCallback.callback_id](nil) - end + for cb_id, inc_msg in pairs(unread_cb_msgs) do + CallbackTable[inc_msg.CallCallback.callback_id](inc_msg.CallCallback.args) + unread_cb_msgs[cb_id] = nil -- INFO: does this shift the table and frick everything up? end - -- if tb.RequestResponse then - -- local req_id = tb.RequestResponse.request_id - -- Requests[req_id] = tb.RequestResponse.response - -- end + local inc_msg = ReadMsg() + + assert(inc_msg.CallCallback) -- INFO: is this gucci or no + + if inc_msg.CallCallback and inc_msg.CallCallback.callback_id then + if inc_msg.CallCallback.args then -- TODO: can just inline + CallbackTable[inc_msg.CallCallback.callback_id](inc_msg.CallCallback.args) + else + CallbackTable[inc_msg.CallCallback.callback_id](nil) + end + end end end diff --git a/api/lua/tag.lua b/api/lua/tag.lua index 680260a..90fd968 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -4,7 +4,8 @@ -- -- SPDX-License-Identifier: MPL-2.0 -local tag = {} +---@class TagGlobal +local tag_global = {} ---@alias Layout ---| "MasterStack" # One master window on the left with all other windows stacked to the right. @@ -17,7 +18,7 @@ local tag = {} ---@class Tag ---@field private _id integer The internal id of this tag. -local tg = {} +local tag = {} ---@param tag_id integer ---@return Tag @@ -25,67 +26,66 @@ local function new_tag(tag_id) ---@type Tag local t = { _id = tag_id } -- Copy functions over - for k, v in pairs(tg) do + for k, v in pairs(tag) do t[k] = v end return t end ----Switch to this tag. -function tg:switch_to() - tag.switch_to(self) -end - ----Toggle this tag. -function tg:toggle() - tag.toggle(self) -end - ---Get this tag's internal id. ---@return integer -function tg:id() +function tag:id() return self._id end ---Get this tag's active status. ---@return boolean|nil active `true` if the tag is active, `false` if not, and `nil` if the tag doesn't exist. -function tg:active() - SendRequest({ +function tag:active() + local response = ReadMsg(SendRequest({ GetTagProps = { tag_id = self._id, }, - }) - - local response = ReadMsg() + })) local active = response.RequestResponse.response.TagProps.active return active end ---Get this tag's name. ---@return string|nil name The name of this tag, or nil if it doesn't exist. -function tg:name() - SendRequest({ +function tag:name() + local response = ReadMsg(SendRequest({ GetTagProps = { tag_id = self._id, }, - }) - - local response = ReadMsg() + })) local name = response.RequestResponse.response.TagProps.name return name end ---Get this tag's output. ---@return Output|nil output The output this tag is on, or nil if the tag doesn't exist. -function tg:output() +function tag:output() return require("output").get_for_tag(self) end +---Switch to this tag. +function tag:switch_to() + tag_global.switch_to(self) +end + +---Toggle this tag. +function tag:toggle() + tag_global.toggle(self) +end + ---Set this tag's layout. ---@param layout Layout -function tg:set_layout(layout) - tag.set_layout(self, layout) +function tag:set_layout(layout) + local name = self:name() + if name ~= nil then + tag_global.set_layout(name, layout) + end end ----------------------------------------------------------- @@ -110,7 +110,7 @@ end ---@param output Output The output you want these tags to be added to. ---@param ... string The names of the new tags you want to add. ---@overload fun(output: Output, tag_names: string[]) -function tag.add(output, ...) +function tag_global.add(output, ...) local varargs = { ... } if type(varargs[1]) == "string" then local tag_names = varargs @@ -145,17 +145,43 @@ end ---tag.toggle("2", op) ----- will cause windows on both tags 1 and 2 to be displayed at the same time. ---``` ----@param t Tag -function tag.toggle(t) - SendMsg({ - ToggleTag = { - tag_id = t:id(), - }, - }) +---@param name string The name of the tag. +---@param output Output? The output. +---@overload fun(t: Tag) +function tag_global.toggle(name, output) + if type(name) == "table" then + SendMsg({ + ToggleTag = { + tag_id = name--[[@as Tag]]:id(), + }, + }) + return + end + + local output = output or require("output").get_focused() + + if output == nil then + return + end + + print("before tag_global.get_by_name") + local tags = tag_global.get_by_name(name) + print("after tag_global.get_by_name") + for _, t in pairs(tags) do + if t:output() and t:output():name() == output:name() then + SendMsg({ + ToggleTag = { + tag_id = t:id(), + }, + }) + return + end + end end ---Switch to a tag on the specified output, deactivating any other active tags on it. ---If `output` is not specified, this uses the currently focused output instead. +---Alternatively, provide a tag object instead of a name and output. --- ---This is used to replicate what a traditional workspace is on some other Wayland compositors. --- @@ -164,25 +190,73 @@ end ---```lua ---tag.switch_to("3") -- Switches to and displays *only* windows on tag 3 ---``` ----@param t Tag -function tag.switch_to(t) - SendMsg({ - SwitchToTag = { - tag_id = t:id(), - }, - }) +---@param name string The name of the tag. +---@param output Output? The output. +---@overload fun(t: Tag) +function tag_global.switch_to(name, output) + if type(name) == "table" then + SendMsg({ + SwitchToTag = { + tag_id = name--[[@as Tag]]:id(), + }, + }) + return + end + + local output = output or require("output").get_focused() + + if output == nil then + return + end + + local tags = tag_global.get_by_name(name) + for _, t in pairs(tags) do + if t:output() and t:output():name() == output:name() then + SendMsg({ + SwitchToTag = { + tag_id = t:id(), + }, + }) + return + end + end end ----Set a layout for the specified tag. ----@param t Tag ----@param layout Layout -function tag.set_layout(t, layout) - SendMsg({ - SetLayout = { - tag_id = t:id(), - layout = layout, - }, - }) +---Set a layout for the tag on the specified output. If no output is provided, set it for the tag on the currently focused one. +---Alternatively, provide a tag object instead of a name and output. +---@param name string The name of the tag. +---@param layout Layout The layout. +---@param output Output? The output. +---@overload fun(t: Tag, layout: Layout) +function tag_global.set_layout(name, layout, output) + if type(name) == "table" then + SendMsg({ + SetLayout = { + tag_id = name--[[@as Tag]]:id(), + layout = layout, + }, + }) + return + end + + local output = output or require("output").get_focused() + + if output == nil then + return + end + + local tags = tag_global.get_by_name(name) + for _, t in pairs(tags) do + if t:output() and t:output():name() == output:name() then + SendMsg({ + SetLayout = { + tag_id = t:id(), + layout = layout, + }, + }) + return + end + end end ---Get all tags on the specified output. @@ -195,14 +269,12 @@ end ---``` ---@param output Output ---@return Tag[] -function tag.get_on_output(output) - SendRequest({ +function tag_global.get_on_output(output) + local response = ReadMsg(SendRequest({ GetOutputProps = { output_name = output:name(), }, - }) - - local response = ReadMsg() + })) local tag_ids = response.RequestResponse.response.OutputProps.tag_ids @@ -223,18 +295,13 @@ end ---Get all tags with this name across all outputs. ---@param name string The name of the tags you want. ---@return Tag[] -function tag.get_by_name(name) - SendRequest("GetTags") - - local response = ReadMsg() - - local tag_ids = response.RequestResponse.response.Tags.tag_ids +function tag_global.get_by_name(name) + local t_s = tag_global.get_all() ---@type Tag[] local tags = {} - for _, tag_id in pairs(tag_ids) do - local t = new_tag(tag_id) + for _, t in pairs(t_s) do if t:name() == name then table.insert(tags, t) end @@ -245,10 +312,9 @@ end ---Get all tags across all ouptuts. ---@return Tag[] -function tag.get_all() - SendRequest("GetTags") - - local response = ReadMsg() +function tag_global.get_all() + local response = ReadMsg(SendRequest("GetTags")) + RPrint(response) local tag_ids = response.RequestResponse.response.Tags.tag_ids @@ -262,4 +328,4 @@ function tag.get_all() return tags end -return tag +return tag_global diff --git a/api/lua/window.lua b/api/lua/window.lua index cb793d9..ee6118b 100644 --- a/api/lua/window.lua +++ b/api/lua/window.lua @@ -4,19 +4,24 @@ -- -- SPDX-License-Identifier: MPL-2.0 ----@class Window ----@field private id integer The internal id of this window -local win = {} +---@class WindowGlobal +local window_global = {} ----@param props Window +---@class Window +---@field private _id integer The internal id of this window +local window = {} + +---@param window_id WindowId ---@return Window -local function new_window(props) +local function new_window(window_id) + ---@type Window + local w = { _id = window_id } -- Copy functions over - for k, v in pairs(win) do - props[k] = v + for k, v in pairs(window) do + w[k] = v end - return props + return w end ---Set a window's size. @@ -28,10 +33,10 @@ end ---window.get_focused():set_size({}) -- do absolutely nothing useful ---``` ---@param size { w: integer?, h: integer? } -function win:set_size(size) +function window:set_size(size) SendMsg({ SetWindowSize = { - window_id = self.id, + window_id = self._id, width = size.w, height = size.h, }, @@ -46,14 +51,11 @@ end ---window.get_focused():move_to_tag("5") ----- ...will make the window only appear on tag 5. ---``` ----@param t Tag -function win:move_to_tag(t) - SendMsg({ - MoveWindowToTag = { - window_id = self.id, - tag_id = t:id(), - }, - }) +---@param name string +---@param output Output? +---@overload fun(self: self, t: Tag) +function window:move_to_tag(name, output) + window_global.move_to_tag(self, name, output) end ---Toggle the specified tag for this window. @@ -66,14 +68,11 @@ end ---window.get_focused():toggle_tag("2") ----- ...will also make the window appear on tag 2. ---``` ----@param t Tag -function win:toggle_tag(t) - SendMsg({ - ToggleTagOnWindow = { - window_id = self.id, - tag_id = t:id(), - }, - }) +---@param name string +---@param output Output? +---@overload fun(self: self, t: Tag) +function window:toggle_tag(name, output) + window_global.toggle_tag(self, name, output) end ---Close this window. @@ -85,10 +84,10 @@ end ---```lua ---window.get_focused():close() -- close the currently focused window ---``` -function win:close() +function window:close() SendMsg({ CloseWindow = { - window_id = self.id, + window_id = self._id, }, }) end @@ -99,10 +98,10 @@ end ---```lua ---window.get_focused():toggle_floating() -- toggles the focused window between tiled and floating ---``` -function win:toggle_floating() +function window:toggle_floating() SendMsg({ ToggleFloating = { - window_id = self.id, + window_id = self._id, }, }) end @@ -116,14 +115,12 @@ end ----- ...should have size equal to `{ w = 3840, h = 2160 }`. ---``` ---@return { w: integer, h: integer }|nil size The size of the window, or nil if it doesn't exist. -function win:size() - SendRequest({ +function window:size() + local response = ReadMsg(SendRequest({ GetWindowProps = { - window_id = self.id, + window_id = self._id, }, - }) - - local response = ReadMsg() + })) local size = response.RequestResponse.response.WindowProps.size if size == nil then return nil @@ -149,14 +146,12 @@ end ----- ...should have loc equal to `{ x = 1920, y = 0 }`. ---``` ---@return { x: integer, y: integer }|nil loc The location of the window, or nil if it's not on-screen or alive. -function win:loc() - SendRequest({ +function window:loc() + local response = ReadMsg(SendRequest({ GetWindowProps = { - window_id = self.id, + window_id = self._id, }, - }) - - local response = ReadMsg() + })) local loc = response.RequestResponse.response.WindowProps.loc if loc == nil then return nil @@ -177,14 +172,12 @@ end ----- ...should print "Alacritty". ---``` ---@return string|nil class This window's class, or nil if it doesn't exist. -function win:class() - SendRequest({ +function window:class() + local response = ReadMsg(SendRequest({ GetWindowProps = { - window_id = self.id, + window_id = self._id, }, - }) - - local response = ReadMsg() + })) local class = response.RequestResponse.response.WindowProps.class return class end @@ -198,14 +191,12 @@ end ----- ...should print the directory Alacritty is in or what it's running (what's in its title bar). ---``` ---@return string|nil title This window's title, or nil if it doesn't exist. -function win:title() - SendRequest({ +function window:title() + local response = ReadMsg(SendRequest({ GetWindowProps = { - window_id = self.id, + window_id = self._id, }, - }) - - local response = ReadMsg() + })) local title = response.RequestResponse.response.WindowProps.title return title end @@ -219,14 +210,12 @@ end ----- ...should print `true`. ---``` ---@return boolean|nil floating `true` if it's floating, `false` if it's tiled, or nil if it doesn't exist. -function win:floating() - SendRequest({ +function window:floating() + local response = ReadMsg(SendRequest({ GetWindowProps = { - window_id = self.id, + window_id = self._id, }, - }) - - local response = ReadMsg() + })) local floating = response.RequestResponse.response.WindowProps.floating return floating end @@ -238,28 +227,28 @@ end ---print(window.get_focused():focused()) -- should print `true`. ---``` ---@return boolean|nil floating `true` if it's floating, `false` if it's tiled, or nil if it doesn't exist. -function win:focused() - SendRequest({ +function window:focused() + local response = ReadMsg(SendRequest({ GetWindowProps = { - window_id = self.id, + window_id = self._id, }, - }) - - local response = ReadMsg() + })) local focused = response.RequestResponse.response.WindowProps.focused return focused end -------------------------------------------------------------------- +---@return WindowId +function window:id() + return self._id +end ----@class WindowGlobal -local window = {} +------------------------------------------------------------------- ---Get all windows with the specified class (usually the name of the application). ---@param class string The class. For example, Alacritty's class is "Alacritty". ---@return Window[] -function window.get_by_class(class) - local windows = window.get_all() +function window_global.get_by_class(class) + local windows = window_global.get_all() ---@type Window[] local windows_ret = {} @@ -275,8 +264,8 @@ end ---Get all windows with the specified title. ---@param title string The title. ---@return Window[] -function window.get_by_title(title) - local windows = window.get_all() +function window_global.get_by_title(title) + local windows = window_global.get_all() ---@type Window[] local windows_ret = {} @@ -291,8 +280,8 @@ end ---Get the currently focused window. ---@return Window|nil -function window.get_focused() - local windows = window.get_all() +function window_global.get_focused() + local windows = window_global.get_all() for _, w in pairs(windows) do if w:focused() then @@ -305,16 +294,85 @@ end ---Get all windows. ---@return Window[] -function window.get_all() - SendRequest("GetWindows") - - local window_ids = ReadMsg().RequestResponse.response.Windows.window_ids +function window_global.get_all() + local window_ids = ReadMsg(SendRequest("GetWindows")).RequestResponse.response.Windows.window_ids ---@type Window[] local windows = {} for _, window_id in pairs(window_ids) do - table.insert(windows, new_window({ id = window_id })) + table.insert(windows, new_window(window_id)) end return windows end -return window +---comment +---@param w Window +---@param name string +---@param output Output? +function window_global.toggle_tag(w, name, output) + if type(name) == "table" then + SendMsg({ + ToggleTagOnWindow = { + window_id = w:id(), + tag_id = name--[[@as Tag]]:id(), + }, + }) + return + end + + local output = output or require("output").get_focused() + + if output == nil then + return + end + + local tags = require("tag").get_by_name(name) + for _, t in pairs(tags) do + if t:output() and t:output():name() == output:name() then + SendMsg({ + ToggleTagOnWindow = { + window_id = w:id(), + tag_id = t:id(), + }, + }) + return + end + end +end + +---comment +---@param w Window +---@param name string +---@param output Output? +---@overload fun(w: Window, t: Tag) +function window_global.move_to_tag(w, name, output) + if type(name) == "table" then + SendMsg({ + MoveWindowToTag = { + window_id = w:id(), + tag_id = name--[[@as Tag]]:id(), + }, + }) + return + end + + local output = output or require("output").get_focused() + + if output == nil then + return + end + + local tags = require("tag").get_by_name(name) + for _, t in pairs(tags) do + if t:output() and t:output():name() == output:name() then + SendMsg({ + MoveWindowToTag = { + window_id = w:id(), + tag_id = t:id(), + }, + }) + return + end + end +end + +return window_global diff --git a/src/api.rs b/src/api.rs index e28b8ff..3507157 100644 --- a/src/api.rs +++ b/src/api.rs @@ -112,7 +112,7 @@ pub fn send_to_client( stream: &mut UnixStream, msg: &OutgoingMsg, ) -> Result<(), rmp_serde::encode::Error> { - // tracing::debug!("Sending {msg:?}"); + tracing::debug!("Sending {msg:?}"); let msg = rmp_serde::to_vec_named(msg)?; let msg_len = msg.len() as u32; let bytes = msg_len.to_ne_bytes(); diff --git a/src/api/msg.rs b/src/api/msg.rs index 94068f5..41e287e 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -85,11 +85,14 @@ pub enum Msg { /// Quit the compositor. Quit, - Request(Request), + Request { + request_id: RequestId, + request: Request, + }, } #[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct RequestId(pub u32); +pub struct RequestId(u32); #[allow(clippy::enum_variant_names)] #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -158,6 +161,7 @@ pub enum OutgoingMsg { args: Option, }, RequestResponse { + request_id: RequestId, response: RequestResponse, }, } diff --git a/src/state.rs b/src/state.rs index ee87cc4..9798cf6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -16,7 +16,7 @@ use std::{ use crate::{ api::{ - msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestResponse}, + msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestId, RequestResponse}, PinnacleSocketSource, }, focus::FocusState, @@ -259,13 +259,16 @@ impl State { self.loop_signal.stop(); } - Msg::Request(request) => { - self.handle_request(request); + Msg::Request { + request_id, + request, + } => { + self.handle_request(request_id, request); } } } - fn handle_request(&mut self, request: Request) { + fn handle_request(&mut self, request_id: RequestId, request: Request) { let stream = self .api_state .stream @@ -284,6 +287,7 @@ impl State { crate::api::send_to_client( &mut stream, &OutgoingMsg::RequestResponse { + request_id, response: RequestResponse::Windows { window_ids }, }, ) @@ -320,6 +324,7 @@ impl State { crate::api::send_to_client( &mut stream, &OutgoingMsg::RequestResponse { + request_id, response: RequestResponse::WindowProps { size, loc, @@ -341,6 +346,7 @@ impl State { crate::api::send_to_client( &mut stream, &OutgoingMsg::RequestResponse { + request_id, response: RequestResponse::Outputs { output_names }, }, ) @@ -385,6 +391,7 @@ impl State { crate::api::send_to_client( &mut stream, &OutgoingMsg::RequestResponse { + request_id, response: RequestResponse::OutputProps { make, model, @@ -406,9 +413,11 @@ impl State { .flat_map(|op| op.with_state(|state| state.tags.clone())) .map(|tag| tag.id()) .collect::>(); + tracing::debug!("GetTags: {:?}", tag_ids); crate::api::send_to_client( &mut stream, &OutgoingMsg::RequestResponse { + request_id, response: RequestResponse::Tags { tag_ids }, }, ) @@ -425,6 +434,7 @@ impl State { crate::api::send_to_client( &mut stream, &OutgoingMsg::RequestResponse { + request_id, response: RequestResponse::TagProps { active, name,