Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0c9832d
feat: add minimization feature and raw protobuf API
sapient-cogbag Jan 29, 2026
65062c4
fix: make activation unminimize windows ^.^
sapient-cogbag Jan 29, 2026
3d7bad0
chore: clean up commented out junk/experimental code
sapient-cogbag Jan 29, 2026
616767d
Merge branch 'main' into feat-window-minimisation
sapient-cogbag Jan 29, 2026
5786a8a
feat: warn when window missing output on set_minimize
sapient-cogbag Jan 29, 2026
641ef7a
fix: only unset focus if the to-be-minimized window is actually focused
sapient-cogbag Jan 29, 2026
4d79958
chore: cargo fmt
sapient-cogbag Jan 29, 2026
c2fdf27
fix: make config functionality clearer
sapient-cogbag Jan 30, 2026
e768db2
fix: unminimize windows if being focused
sapient-cogbag Jan 31, 2026
2a7d4cf
feat: add example of unminimisation in config
sapient-cogbag Jan 31, 2026
e0b1e9a
Merge branch 'main' into feat-window-minimisation
sapient-cogbag Jan 31, 2026
a842d94
Merge branch 'main' into feat-window-minimisation
sapient-cogbag Feb 4, 2026
749d1c7
Merge branch 'main' into feat-window-minimisation
sapient-cogbag Feb 8, 2026
d863624
fix: hide minimized x11 surfaces ^.^ nya
sapient-cogbag Feb 9, 2026
de4b69b
compat: add specific RPC return message for SetMinimized for future c…
sapient-cogbag Feb 9, 2026
c19693e
feat: initial impl of WindowRules that set minimization state
sapient-cogbag Feb 9, 2026
0dd548a
fix: correctly set x11 hidden property on surfaces ^.^
sapient-cogbag Feb 9, 2026
6b2e760
feat: replace the focusing api into a version that is fallible and ca…
sapient-cogbag Feb 10, 2026
7fc65dc
chore: update focus tests
sapient-cogbag Feb 10, 2026
de32422
feat: implement lua api for the try_focused system
sapient-cogbag Feb 11, 2026
33acbf1
Merge branch 'main' into feat-window-minimisation
sapient-cogbag Feb 11, 2026
1b89758
feat: implement lua side of the client API for minimization
sapient-cogbag Feb 11, 2026
7a6cc1d
doc: add comment explaining the need for the weird construction. We m…
sapient-cogbag Feb 11, 2026
8fc71b1
style: replace the janky Result-Ok-Err swapping mechanism with some p…
sapient-cogbag Feb 15, 2026
5a67b22
Merge branch 'main' into feat-window-minimisation
sapient-cogbag Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions api/lua/pinnacle/grpc/defs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,12 @@ local pinnacle_window_v1_DecorationMode = {
DECORATION_MODE_SERVER_SIDE = 2,
}

---@enum pinnacle.window.v1.TrySetFocusedResponse.TrySetFocusedStatus
local pinnacle_window_v1_TrySetFocusedResponse_TrySetFocusedStatus = {
TRY_SET_FOCUSED_STATUS_SUCCESS = 0,
TRY_SET_FOCUSED_STATUS_WINDOW_MINIMIZED = 1,
}

---@enum pinnacle.signal.v1.StreamControl
local pinnacle_signal_v1_StreamControl = {
STREAM_CONTROL_UNSPECIFIED = 0,
Expand Down Expand Up @@ -1087,6 +1093,12 @@ local pinnacle_v1_Backend = {
---@class pinnacle.window.v1.GetLayoutModeResponse
---@field layout_mode pinnacle.window.v1.LayoutMode?

---@class pinnacle.window.v1.GetMinimizedRequest
---@field window_id integer?

---@class pinnacle.window.v1.GetMinimizedResponse
---@field minimized boolean?

---@class pinnacle.window.v1.GetTagIdsRequest
---@field window_id integer?

Expand Down Expand Up @@ -1131,6 +1143,12 @@ local pinnacle_v1_Backend = {
---@field window_id integer?
---@field set_or_toggle pinnacle.util.v1.SetOrToggle?

---@class pinnacle.window.v1.SetMinimizedRequest
---@field window_id integer?
---@field set_or_toggle pinnacle.util.v1.SetOrToggle?

---@class pinnacle.window.v1.SetMinimizedResponse

---@class pinnacle.window.v1.SetFloatingRequest
---@field window_id integer?
---@field set_or_toggle pinnacle.util.v1.SetOrToggle?
Expand All @@ -1139,6 +1157,13 @@ local pinnacle_v1_Backend = {
---@field window_id integer?
---@field set_or_toggle pinnacle.util.v1.SetOrToggle?

---@class pinnacle.window.v1.TrySetFocusedRequest
---@field window_id integer?
---@field set_or_toggle pinnacle.util.v1.SetOrToggle?

---@class pinnacle.window.v1.TrySetFocusedResponse
---@field status pinnacle.window.v1.TrySetFocusedResponse.TrySetFocusedStatus?

---@class pinnacle.window.v1.SetDecorationModeRequest
---@field window_id integer?
---@field decoration_mode pinnacle.window.v1.DecorationMode?
Expand Down Expand Up @@ -1535,6 +1560,8 @@ pinnacle.window.v1.GetFocusedRequest = {}
pinnacle.window.v1.GetFocusedResponse = {}
pinnacle.window.v1.GetLayoutModeRequest = {}
pinnacle.window.v1.GetLayoutModeResponse = {}
pinnacle.window.v1.GetMinimizedRequest = {}
pinnacle.window.v1.GetMinimizedResponse = {}
pinnacle.window.v1.GetTagIdsRequest = {}
pinnacle.window.v1.GetTagIdsResponse = {}
pinnacle.window.v1.GetWindowsInDirRequest = {}
Expand All @@ -1546,8 +1573,12 @@ pinnacle.window.v1.SetGeometryRequest = {}
pinnacle.window.v1.ResizeTileRequest = {}
pinnacle.window.v1.SetFullscreenRequest = {}
pinnacle.window.v1.SetMaximizedRequest = {}
pinnacle.window.v1.SetMinimizedRequest = {}
pinnacle.window.v1.SetMinimizedResponse = {}
pinnacle.window.v1.SetFloatingRequest = {}
pinnacle.window.v1.SetFocusedRequest = {}
pinnacle.window.v1.TrySetFocusedRequest = {}
pinnacle.window.v1.TrySetFocusedResponse = {}
pinnacle.window.v1.SetDecorationModeRequest = {}
pinnacle.window.v1.MoveToTagRequest = {}
pinnacle.window.v1.SetTagRequest = {}
Expand Down Expand Up @@ -1654,6 +1685,7 @@ pinnacle.output.v1.Vrr = pinnacle_output_v1_Vrr
pinnacle.render.v1.Filter = pinnacle_render_v1_Filter
pinnacle.window.v1.LayoutMode = pinnacle_window_v1_LayoutMode
pinnacle.window.v1.DecorationMode = pinnacle_window_v1_DecorationMode
pinnacle.window.v1.TrySetFocusedResponse.TrySetFocusedStatus = pinnacle_window_v1_TrySetFocusedResponse_TrySetFocusedStatus
pinnacle.signal.v1.StreamControl = pinnacle_signal_v1_StreamControl
pinnacle.v1.Backend = pinnacle_v1_Backend

Expand Down Expand Up @@ -2677,6 +2709,23 @@ pinnacle.window.v1.WindowService.GetLayoutMode.response = ".pinnacle.window.v1.G
function Client:pinnacle_window_v1_WindowService_GetLayoutMode(data)
return self:unary_request(pinnacle.window.v1.WindowService.GetLayoutMode, data)
end
pinnacle.window.v1.WindowService.GetMinimized = {}
pinnacle.window.v1.WindowService.GetMinimized.service = "pinnacle.window.v1.WindowService"
pinnacle.window.v1.WindowService.GetMinimized.method = "GetMinimized"
pinnacle.window.v1.WindowService.GetMinimized.request = ".pinnacle.window.v1.GetMinimizedRequest"
pinnacle.window.v1.WindowService.GetMinimized.response = ".pinnacle.window.v1.GetMinimizedResponse"

---Performs a unary request.
---
---@nodiscard
---
---@param data pinnacle.window.v1.GetMinimizedRequest
---
---@return pinnacle.window.v1.GetMinimizedResponse | nil response
---@return string | nil error An error string, if any
function Client:pinnacle_window_v1_WindowService_GetMinimized(data)
return self:unary_request(pinnacle.window.v1.WindowService.GetMinimized, data)
end
pinnacle.window.v1.WindowService.GetTagIds = {}
pinnacle.window.v1.WindowService.GetTagIds.service = "pinnacle.window.v1.WindowService"
pinnacle.window.v1.WindowService.GetTagIds.method = "GetTagIds"
Expand Down Expand Up @@ -2813,6 +2862,23 @@ pinnacle.window.v1.WindowService.SetMaximized.response = ".google.protobuf.Empty
function Client:pinnacle_window_v1_WindowService_SetMaximized(data)
return self:unary_request(pinnacle.window.v1.WindowService.SetMaximized, data)
end
pinnacle.window.v1.WindowService.SetMinimized = {}
pinnacle.window.v1.WindowService.SetMinimized.service = "pinnacle.window.v1.WindowService"
pinnacle.window.v1.WindowService.SetMinimized.method = "SetMinimized"
pinnacle.window.v1.WindowService.SetMinimized.request = ".pinnacle.window.v1.SetMinimizedRequest"
pinnacle.window.v1.WindowService.SetMinimized.response = ".pinnacle.window.v1.SetMinimizedResponse"

---Performs a unary request.
---
---@nodiscard
---
---@param data pinnacle.window.v1.SetMinimizedRequest
---
---@return pinnacle.window.v1.SetMinimizedResponse | nil response
---@return string | nil error An error string, if any
function Client:pinnacle_window_v1_WindowService_SetMinimized(data)
return self:unary_request(pinnacle.window.v1.WindowService.SetMinimized, data)
end
pinnacle.window.v1.WindowService.SetFloating = {}
pinnacle.window.v1.WindowService.SetFloating.service = "pinnacle.window.v1.WindowService"
pinnacle.window.v1.WindowService.SetFloating.method = "SetFloating"
Expand Down Expand Up @@ -2847,6 +2913,23 @@ pinnacle.window.v1.WindowService.SetFocused.response = ".google.protobuf.Empty"
function Client:pinnacle_window_v1_WindowService_SetFocused(data)
return self:unary_request(pinnacle.window.v1.WindowService.SetFocused, data)
end
pinnacle.window.v1.WindowService.TrySetFocused = {}
pinnacle.window.v1.WindowService.TrySetFocused.service = "pinnacle.window.v1.WindowService"
pinnacle.window.v1.WindowService.TrySetFocused.method = "TrySetFocused"
pinnacle.window.v1.WindowService.TrySetFocused.request = ".pinnacle.window.v1.TrySetFocusedRequest"
pinnacle.window.v1.WindowService.TrySetFocused.response = ".pinnacle.window.v1.TrySetFocusedResponse"

---Performs a unary request.
---
---@nodiscard
---
---@param data pinnacle.window.v1.TrySetFocusedRequest
---
---@return pinnacle.window.v1.TrySetFocusedResponse | nil response
---@return string | nil error An error string, if any
function Client:pinnacle_window_v1_WindowService_TrySetFocused(data)
return self:unary_request(pinnacle.window.v1.WindowService.TrySetFocused, data)
end
pinnacle.window.v1.WindowService.SetDecorationMode = {}
pinnacle.window.v1.WindowService.SetDecorationMode.service = "pinnacle.window.v1.WindowService"
pinnacle.window.v1.WindowService.SetDecorationMode.method = "SetDecorationMode"
Expand Down
95 changes: 93 additions & 2 deletions api/lua/pinnacle/window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,32 @@ function WindowHandle:toggle_maximized()
end
end

--- Set this window to be minimized or not minimized.
--- @param minimized boolean
function WindowHandle:set_minimized(minimized)
local _, err = client:pinnacle_window_v1_WindowService_SetMinimized({
window_id = self.id,
set_or_toggle = set_or_toggle[minimized]
})

if err then
log.error(err)
end
end


--- Toggles this window between minimized and not.
function WindowHandle:toggle_minimized()
local _, err = client:pinnacle_window_v1_WindowService_SetMinimized({
window_id = self.id,
set_or_toggle = set_or_toggle.TOGGLE
})

if err then
log.error(err)
end
end

---Sets this window to floating or not.
---
---@param floating boolean
Expand Down Expand Up @@ -428,11 +454,23 @@ function WindowHandle:toggle_floating()
end
end


--- @alias pinnacle.window.TrySetFocusedError
--- | "WindowMinimized"
local try_set_focused_error = {
WindowMinimized = window_v1.TrySetFocusedResponse.TrySetFocusedStatus.TRY_SET_FOCUSED_STATUS_WINDOW_MINIMIZED
}

require("pinnacle.util").make_bijective(try_set_focused_error)

---Focuses or unfocuses this window.
---
---@deprecated silently fails if trying to focus a minimized window, use `WindowHandle:try_set_focused`
---@param focused boolean
---@see pinnacle.window.WindowHandle.try_set_focused replacement function.
function WindowHandle:set_focused(focused)
local _, err = client:pinnacle_window_v1_WindowService_SetFocused({
log.warn("Using deprecated function WindowHandle:set_focused(), which silently fails if focusing a minimized window.")
local _, err = client:pinnacle_window_v1_WindowService_TrySetFocused({
window_id = self.id,
set_or_toggle = set_or_toggle[focused],
})
Expand All @@ -442,10 +480,34 @@ function WindowHandle:set_focused(focused)
end
end


--- @class pinnacle.window.TrySetFocusedResult
--- @field err? pinnacle.window.TrySetFocusedError
---
--- Tries to focus or unfocus this window
--- @param focused boolean
--- @return pinnacle.window.TrySetFocusedResult
function WindowHandle:try_set_focused(focused)
local response, err = client:pinnacle_window_v1_WindowService_TrySetFocused({
window_id = self.id,
set_or_toggle = set_or_toggle[focused]
})

if err then
log.error(err)
end
assert(response)

return { err = try_set_focused_error[response.status] }
end

---Toggles this window to and from focused.
---
--- @deprecated silently fails if trying to focus a minimized window, use `WindowHandle:try_toggle_focused`.
--- @see pinnacle.window.WindowHandle.try_toggle_focused replacement function.
function WindowHandle:toggle_focused()
local _, err = client:pinnacle_window_v1_WindowService_SetFocused({
log.warn("Using deprecated function WindowHandle:toggle_focused(), which silently fails if focusing a minimized window.")
local _, err = client:pinnacle_window_v1_WindowService_TrySetFocused({
window_id = self.id,
set_or_toggle = set_or_toggle.TOGGLE,
})
Expand All @@ -455,6 +517,25 @@ function WindowHandle:toggle_focused()
end
end

--- @class pinnacle.window.TryToggleFocusedResult
--- @field err? pinnacle.window.TrySetFocusedError
---
--- Tries to toggle this window to and from focused, failing if the window is minimized and it is trying to be focused.
--- @return pinnacle.window.TrySetFocusedResult
function WindowHandle:try_toggle_focused()
local response, err = client:pinnacle_window_v1_WindowService_TrySetFocused({
window_id = self.id,
set_or_toggle = set_or_toggle.TOGGLE,
})

if err then
log.error(err)
end
assert(response)

return { err = try_set_focused_error[response.status] }
end

---Sets this window's decoration mode.
---
---If not set, the client is allowed to choose its decoration mode, defaulting to client-side if it doesn't.
Expand Down Expand Up @@ -758,6 +839,16 @@ function WindowHandle:maximized()
return response and response.layout_mode == layout_mode_def.LAYOUT_MODE_MAXIMIZED or false
end

--- Gets whether this window is minimized.
---
--- @return boolean
function WindowHandle:minimized()
local response, err =
client:pinnacle_window_v1_WindowService_GetMinimized({ window_id = self.id })

return response and response.minimized or false
end

---Gets all tags on this window.
---
---@return pinnacle.tag.TagHandle[]
Expand Down
40 changes: 38 additions & 2 deletions api/protobuf/pinnacle/window/v1/window.proto
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ message GetLayoutModeResponse {
LayoutMode layout_mode = 1;
}

message GetMinimizedRequest {
uint32 window_id = 1;
}
message GetMinimizedResponse {
bool minimized = 1;
}

message GetTagIdsRequest {
uint32 window_id = 1;
}
Expand Down Expand Up @@ -116,16 +123,39 @@ message SetMaximizedRequest {
pinnacle.util.v1.SetOrToggle set_or_toggle = 2;
}

message SetMinimizedRequest {
uint32 window_id = 1;
pinnacle.util.v1.SetOrToggle set_or_toggle = 2;
}

message SetMinimizedResponse {}

message SetFloatingRequest {
uint32 window_id = 1;
pinnacle.util.v1.SetOrToggle set_or_toggle = 2;
}

message SetFocusedRequest {
// TrySetFocusedRequest is for the new RPC.
option deprecated = true;
uint32 window_id = 1;
pinnacle.util.v1.SetOrToggle set_or_toggle = 2;
}

message TrySetFocusedRequest {
uint32 window_id = 1;
pinnacle.util.v1.SetOrToggle set_or_toggle = 2;
}

message TrySetFocusedResponse {
enum TrySetFocusedStatus {
TRY_SET_FOCUSED_STATUS_SUCCESS = 0;
TRY_SET_FOCUSED_STATUS_WINDOW_MINIMIZED = 1;
}

TrySetFocusedStatus status = 1;
}

enum DecorationMode {
DECORATION_MODE_UNSPECIFIED = 0;
DECORATION_MODE_CLIENT_SIDE = 1;
Expand Down Expand Up @@ -222,6 +252,7 @@ service WindowService {
rpc GetSize(GetSizeRequest) returns (GetSizeResponse);
rpc GetFocused(GetFocusedRequest) returns (GetFocusedResponse);
rpc GetLayoutMode(GetLayoutModeRequest) returns (GetLayoutModeResponse);
rpc GetMinimized(GetMinimizedRequest) returns (GetMinimizedResponse);
rpc GetTagIds(GetTagIdsRequest) returns (GetTagIdsResponse);
rpc GetWindowsInDir(GetWindowsInDirRequest) returns (GetWindowsInDirResponse);
rpc GetForeignToplevelListIdentifier(GetForeignToplevelListIdentifierRequest) returns (GetForeignToplevelListIdentifierResponse);
Expand All @@ -231,8 +262,13 @@ service WindowService {
rpc ResizeTile(ResizeTileRequest) returns (google.protobuf.Empty);
rpc SetFullscreen(SetFullscreenRequest) returns (google.protobuf.Empty);
rpc SetMaximized(SetMaximizedRequest) returns (google.protobuf.Empty);
rpc SetFloating(SetFloatingRequest) returns (google.protobuf.Empty);
rpc SetFocused(SetFocusedRequest) returns (google.protobuf.Empty);
rpc SetMinimized(SetMinimizedRequest) returns (SetMinimizedResponse);
rpc SetFloating(SetFloatingRequest) returns (google.protobuf.Empty);
rpc SetFocused(SetFocusedRequest) returns (google.protobuf.Empty) {
// use `TrySetFocused` instead.
option deprecated = true;
};
rpc TrySetFocused(TrySetFocusedRequest) returns (TrySetFocusedResponse);
rpc SetDecorationMode(SetDecorationModeRequest) returns (google.protobuf.Empty);
rpc MoveToTag(MoveToTagRequest) returns (google.protobuf.Empty);
rpc SetTag(SetTagRequest) returns (google.protobuf.Empty);
Expand Down
Loading
Loading