From 14a36b973839087aa2647f6bfdeba154ecbcc04b Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 24 Feb 2024 14:41:52 -0600 Subject: [PATCH 1/4] Rework focus and add `Window.set_focused` This has to be one of the most unreviewable commits I think I've created yet --- api/lua/examples/default/default_config.lua | 7 + api/lua/pinnacle/grpc/protobuf.lua | 1 + api/lua/pinnacle/tag.lua | 16 +- api/lua/pinnacle/window.lua | 79 +++++- api/protocol/pinnacle/tag/v0alpha1/tag.proto | 6 +- api/protocol/pinnacle/v0alpha1/pinnacle.proto | 8 + .../pinnacle/window/v0alpha1/window.proto | 26 +- api/rust/examples/default_config/main.rs | 6 + api/rust/src/tag.rs | 20 +- api/rust/src/window.rs | 85 ++++-- src/api.rs | 245 +++++++++++++----- src/backend/udev.rs | 21 +- src/backend/winit.rs | 15 +- src/focus.rs | 97 ++++--- src/handlers.rs | 14 +- src/handlers/xdg_shell.rs | 20 +- src/handlers/xwayland.rs | 77 ++++-- src/input.rs | 109 ++++---- src/layout.rs | 36 ++- src/output.rs | 3 + src/state.rs | 18 +- 21 files changed, 605 insertions(+), 304 deletions(-) diff --git a/api/lua/examples/default/default_config.lua b/api/lua/examples/default/default_config.lua index 28427d8..252ed5c 100644 --- a/api/lua/examples/default/default_config.lua +++ b/api/lua/examples/default/default_config.lua @@ -142,4 +142,11 @@ require("pinnacle").setup(function(Pinnacle) end end) end + + -- Enable sloppy focus + Window.connect_signal({ + pointer_enter = function(window) + window:set_focused(true) + end, + }) end) diff --git a/api/lua/pinnacle/grpc/protobuf.lua b/api/lua/pinnacle/grpc/protobuf.lua index 456dbf5..0fdaa38 100644 --- a/api/lua/pinnacle/grpc/protobuf.lua +++ b/api/lua/pinnacle/grpc/protobuf.lua @@ -18,6 +18,7 @@ function protobuf.build_protos() PINNACLE_PROTO_DIR .. "/pinnacle/process/" .. version .. "/process.proto", PINNACLE_PROTO_DIR .. "/pinnacle/window/" .. version .. "/window.proto", PINNACLE_PROTO_DIR .. "/pinnacle/signal/" .. version .. "/signal.proto", + PINNACLE_PROTO_DIR .. "/google/protobuf/empty.proto", } local cmd = "protoc --descriptor_set_out=/tmp/pinnacle.pb --proto_path=" .. PINNACLE_PROTO_DIR .. " " diff --git a/api/lua/pinnacle/tag.lua b/api/lua/pinnacle/tag.lua index e707eed..a29b626 100644 --- a/api/lua/pinnacle/tag.lua +++ b/api/lua/pinnacle/tag.lua @@ -44,6 +44,14 @@ local function build_grpc_request_params(method, data) } end +local set_or_toggle = { + SET = 1, + [true] = 1, + UNSET = 2, + [false] = 2, + TOGGLE = 3, +} + ---@nodoc ---@class TagHandleModule local tag_handle = {} @@ -445,7 +453,9 @@ end --- ---@param active boolean function TagHandle:set_active(active) - client.unary_request(build_grpc_request_params("SetActive", { tag_id = self.id, set = active })) + client.unary_request( + build_grpc_request_params("SetActive", { tag_id = self.id, set_or_toggle = set_or_toggle[active] }) + ) end ---Toggle this tag's active state. @@ -460,7 +470,9 @@ end ---Tag.get("2"):toggle_active() -- Displays nothing ---``` function TagHandle:toggle_active() - client.unary_request(build_grpc_request_params("SetActive", { tag_id = self.id, toggle = {} })) + client.unary_request( + build_grpc_request_params("SetActive", { tag_id = self.id, set_or_toggle = set_or_toggle.TOGGLE }) + ) end ---@class TagProperties diff --git a/api/lua/pinnacle/window.lua b/api/lua/pinnacle/window.lua index 0cf5fff..bc594ac 100644 --- a/api/lua/pinnacle/window.lua +++ b/api/lua/pinnacle/window.lua @@ -16,6 +16,7 @@ local rpc_types = { SetFullscreen = {}, SetMaximized = {}, SetFloating = {}, + SetFocused = {}, MoveToTag = {}, SetTag = {}, MoveGrab = {}, @@ -47,6 +48,14 @@ local function build_grpc_request_params(method, data) } end +local set_or_toggle = { + SET = 1, + [true] = 1, + UNSET = 2, + [false] = 2, + TOGGLE = 3, +} + ---@nodoc ---@class WindowHandleModule local window_handle = {} @@ -412,7 +421,9 @@ end --- ---@param fullscreen boolean function WindowHandle:set_fullscreen(fullscreen) - client.unary_request(build_grpc_request_params("SetFullscreen", { window_id = self.id, set = fullscreen })) + client.unary_request( + build_grpc_request_params("SetFullscreen", { window_id = self.id, set_or_toggle = set_or_toggle[fullscreen] }) + ) end ---Toggle this window to and from fullscreen. @@ -425,7 +436,9 @@ end ---end ---``` function WindowHandle:toggle_fullscreen() - client.unary_request(build_grpc_request_params("SetFullscreen", { window_id = self.id, toggle = {} })) + client.unary_request( + build_grpc_request_params("SetFullscreen", { window_id = self.id, set_or_toggle = set_or_toggle.TOGGLE }) + ) end ---Set this window to maximized or not. @@ -441,7 +454,9 @@ end --- ---@param maximized boolean function WindowHandle:set_maximized(maximized) - client.unary_request(build_grpc_request_params("SetMaximized", { window_id = self.id, set = maximized })) + client.unary_request( + build_grpc_request_params("SetMaximized", { window_id = self.id, set_or_toggle = set_or_toggle[maximized] }) + ) end ---Toggle this window to and from maximized. @@ -454,7 +469,9 @@ end ---end ---``` function WindowHandle:toggle_maximized() - client.unary_request(build_grpc_request_params("SetMaximized", { window_id = self.id, toggle = {} })) + client.unary_request( + build_grpc_request_params("SetMaximized", { window_id = self.id, set_or_toggle = set_or_toggle.TOGGLE }) + ) end ---Set this window to floating or not. @@ -470,7 +487,9 @@ end --- ---@param floating boolean function WindowHandle:set_floating(floating) - client.unary_request(build_grpc_request_params("SetFloating", { window_id = self.id, set = floating })) + client.unary_request( + build_grpc_request_params("SetFloating", { window_id = self.id, set_or_toggle = set_or_toggle[floating] }) + ) end ---Toggle this window to and from floating. @@ -483,7 +502,41 @@ end ---end ---``` function WindowHandle:toggle_floating() - client.unary_request(build_grpc_request_params("SetFloating", { window_id = self.id, toggle = {} })) + client.unary_request( + build_grpc_request_params("SetFloating", { window_id = self.id, set_or_toggle = set_or_toggle.TOGGLE }) + ) +end + +---Focus or unfocus this window. +--- +---### Example +---```lua +---local focused = Window.get_focused() +---if focused then +--- focused:set_focused(false) +---end +---``` +--- +---@param focused boolean +function WindowHandle:set_focused(focused) + client.unary_request( + build_grpc_request_params("SetFocused", { window_id = self.id, set_or_toggle = set_or_toggle[focused] }) + ) +end + +---Toggle this window to and from focused. +--- +---### Example +---```lua +---local focused = Window.get_focused() +---if focused then +--- focused:toggle_focused() +---end +---``` +function WindowHandle:toggle_focused() + client.unary_request( + build_grpc_request_params("SetFocused", { window_id = self.id, set_or_toggle = set_or_toggle.TOGGLE }) + ) end ---Move this window to the specified tag. @@ -523,7 +576,12 @@ end ---@param tag TagHandle The tag to set or unset ---@param set boolean function WindowHandle:set_tag(tag, set) - client.unary_request(build_grpc_request_params("SetTag", { window_id = self.id, tag_id = tag.id, set = set })) + client.unary_request( + build_grpc_request_params( + "SetTag", + { window_id = self.id, tag_id = tag.id, set_or_toggle = set_or_toggle[set] } + ) + ) end ---Toggle the given tag on this window. @@ -545,7 +603,12 @@ end --- ---@param tag TagHandle The tag to toggle function WindowHandle:toggle_tag(tag) - client.unary_request(build_grpc_request_params("SetTag", { window_id = self.id, tag_id = tag.id, toggle = {} })) + client.unary_request( + build_grpc_request_params( + "SetTag", + { window_id = self.id, tag_id = tag.id, set_or_toggle = set_or_toggle.TOGGLE } + ) + ) end ---@class WindowProperties diff --git a/api/protocol/pinnacle/tag/v0alpha1/tag.proto b/api/protocol/pinnacle/tag/v0alpha1/tag.proto index 07d593f..9ac0ef3 100644 --- a/api/protocol/pinnacle/tag/v0alpha1/tag.proto +++ b/api/protocol/pinnacle/tag/v0alpha1/tag.proto @@ -3,13 +3,11 @@ syntax = "proto2"; package pinnacle.tag.v0alpha1; import "google/protobuf/empty.proto"; +import "pinnacle/v0alpha1/pinnacle.proto"; message SetActiveRequest { optional uint32 tag_id = 1; - oneof set_or_toggle { - bool set = 2; - google.protobuf.Empty toggle = 3; - } + optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2; } message SwitchToRequest { diff --git a/api/protocol/pinnacle/v0alpha1/pinnacle.proto b/api/protocol/pinnacle/v0alpha1/pinnacle.proto index 7f436a0..e2177d7 100644 --- a/api/protocol/pinnacle/v0alpha1/pinnacle.proto +++ b/api/protocol/pinnacle/v0alpha1/pinnacle.proto @@ -11,6 +11,14 @@ message Geometry { optional int32 height = 4; } +// NOTE TO SELF: If you change this you MUST change the mappings in the Lua API +enum SetOrToggle { + SET_OR_TOGGLE_UNSPECIFIED = 0; + SET_OR_TOGGLE_SET = 1; + SET_OR_TOGGLE_UNSET = 2; + SET_OR_TOGGLE_TOGGLE = 3; +} + message QuitRequest {} service PinnacleService { diff --git a/api/protocol/pinnacle/window/v0alpha1/window.proto b/api/protocol/pinnacle/window/v0alpha1/window.proto index 9741b92..a591c8d 100644 --- a/api/protocol/pinnacle/window/v0alpha1/window.proto +++ b/api/protocol/pinnacle/window/v0alpha1/window.proto @@ -17,26 +17,22 @@ message SetGeometryRequest { message SetFullscreenRequest { optional uint32 window_id = 1; - oneof set_or_toggle { - bool set = 2; - google.protobuf.Empty toggle = 3; - } + optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2; } message SetMaximizedRequest { optional uint32 window_id = 1; - oneof set_or_toggle { - bool set = 2; - google.protobuf.Empty toggle = 3; - } + optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2; } message SetFloatingRequest { optional uint32 window_id = 1; - oneof set_or_toggle { - bool set = 2; - google.protobuf.Empty toggle = 3; - } + optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2; +} + +message SetFocusedRequest { + optional uint32 window_id = 1; + optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2; } message MoveToTagRequest { @@ -47,10 +43,7 @@ message MoveToTagRequest { message SetTagRequest { optional uint32 window_id = 1; optional uint32 tag_id = 2; - oneof set_or_toggle { - bool set = 3; - google.protobuf.Empty toggle = 4; - } + optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 3; } message MoveGrabRequest { @@ -119,6 +112,7 @@ service WindowService { 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 MoveToTag(MoveToTagRequest) returns (google.protobuf.Empty); rpc SetTag(SetTagRequest) returns (google.protobuf.Empty); rpc MoveGrab(MoveGrabRequest) returns (google.protobuf.Empty); diff --git a/api/rust/examples/default_config/main.rs b/api/rust/examples/default_config/main.rs index 1980f49..e9aa9b7 100644 --- a/api/rust/examples/default_config/main.rs +++ b/api/rust/examples/default_config/main.rs @@ -1,3 +1,4 @@ +use pinnacle_api::signal::WindowSignal; use pinnacle_api::xkbcommon::xkb::Keysym; use pinnacle_api::{ input::{Mod, MouseButton, MouseEdge}, @@ -148,4 +149,9 @@ async fn main() { } }); } + + // Enable sloppy focus + window.connect_signal(WindowSignal::PointerEnter(Box::new(|win| { + win.set_focused(true); + }))); } diff --git a/api/rust/src/tag.rs b/api/rust/src/tag.rs index 7aa9806..77b01c3 100644 --- a/api/rust/src/tag.rs +++ b/api/rust/src/tag.rs @@ -36,12 +36,15 @@ use std::{ use futures::FutureExt; use num_enum::TryFromPrimitive; -use pinnacle_api_defs::pinnacle::tag::{ - self, - v0alpha1::{ - tag_service_client::TagServiceClient, AddRequest, RemoveRequest, SetActiveRequest, - SetLayoutRequest, SwitchToRequest, +use pinnacle_api_defs::pinnacle::{ + tag::{ + self, + v0alpha1::{ + tag_service_client::TagServiceClient, AddRequest, RemoveRequest, SetActiveRequest, + SetLayoutRequest, SwitchToRequest, + }, }, + v0alpha1::SetOrToggle, }; use tonic::transport::Channel; @@ -451,7 +454,10 @@ impl TagHandle { let mut client = self.tag_client.clone(); block_on_tokio(client.set_active(SetActiveRequest { tag_id: Some(self.id), - set_or_toggle: Some(tag::v0alpha1::set_active_request::SetOrToggle::Set(set)), + set_or_toggle: Some(match set { + true => SetOrToggle::Set, + false => SetOrToggle::Unset, + } as i32), })) .unwrap(); } @@ -479,7 +485,7 @@ impl TagHandle { let mut client = self.tag_client.clone(); block_on_tokio(client.set_active(SetActiveRequest { tag_id: Some(self.id), - set_or_toggle: Some(tag::v0alpha1::set_active_request::SetOrToggle::Toggle(())), + set_or_toggle: Some(SetOrToggle::Toggle as i32), })) .unwrap(); } diff --git a/api/rust/src/window.rs b/api/rust/src/window.rs index 910f55d..56c62ef 100644 --- a/api/rust/src/window.rs +++ b/api/rust/src/window.rs @@ -15,15 +15,13 @@ use futures::FutureExt; use num_enum::TryFromPrimitive; use pinnacle_api_defs::pinnacle::{ - window::v0alpha1::{ - window_service_client::WindowServiceClient, AddWindowRuleRequest, CloseRequest, - MoveToTagRequest, SetTagRequest, - }, + v0alpha1::SetOrToggle, window::{ self, v0alpha1::{ - GetRequest, MoveGrabRequest, ResizeGrabRequest, SetFloatingRequest, - SetFullscreenRequest, SetMaximizedRequest, + window_service_client::WindowServiceClient, AddWindowRuleRequest, CloseRequest, + GetRequest, MoveGrabRequest, MoveToTagRequest, ResizeGrabRequest, SetFloatingRequest, + SetFocusedRequest, SetFullscreenRequest, SetMaximizedRequest, SetTagRequest, }, }, }; @@ -280,9 +278,10 @@ impl WindowHandle { let mut client = self.window_client.clone(); block_on_tokio(client.set_fullscreen(SetFullscreenRequest { window_id: Some(self.id), - set_or_toggle: Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Set( - set, - )), + set_or_toggle: Some(match set { + true => SetOrToggle::Set, + false => SetOrToggle::Unset, + } as i32), })) .unwrap(); } @@ -301,7 +300,7 @@ impl WindowHandle { let mut client = self.window_client.clone(); block_on_tokio(client.set_fullscreen(SetFullscreenRequest { window_id: Some(self.id), - set_or_toggle: Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Toggle(())), + set_or_toggle: Some(SetOrToggle::Toggle as i32), })) .unwrap(); } @@ -320,9 +319,10 @@ impl WindowHandle { let mut client = self.window_client.clone(); block_on_tokio(client.set_maximized(SetMaximizedRequest { window_id: Some(self.id), - set_or_toggle: Some(window::v0alpha1::set_maximized_request::SetOrToggle::Set( - set, - )), + set_or_toggle: Some(match set { + true => SetOrToggle::Set, + false => SetOrToggle::Unset, + } as i32), })) .unwrap(); } @@ -341,7 +341,7 @@ impl WindowHandle { let mut client = self.window_client.clone(); block_on_tokio(client.set_maximized(SetMaximizedRequest { window_id: Some(self.id), - set_or_toggle: Some(window::v0alpha1::set_maximized_request::SetOrToggle::Toggle(())), + set_or_toggle: Some(SetOrToggle::Toggle as i32), })) .unwrap(); } @@ -363,9 +363,10 @@ impl WindowHandle { let mut client = self.window_client.clone(); block_on_tokio(client.set_floating(SetFloatingRequest { window_id: Some(self.id), - set_or_toggle: Some(window::v0alpha1::set_floating_request::SetOrToggle::Set( - set, - )), + set_or_toggle: Some(match set { + true => SetOrToggle::Set, + false => SetOrToggle::Unset, + } as i32), })) .unwrap(); } @@ -387,9 +388,46 @@ impl WindowHandle { let mut client = self.window_client.clone(); block_on_tokio(client.set_floating(SetFloatingRequest { window_id: Some(self.id), - set_or_toggle: Some(window::v0alpha1::set_floating_request::SetOrToggle::Toggle( - (), - )), + set_or_toggle: Some(SetOrToggle::Toggle as i32), + })) + .unwrap(); + } + + /// Focus or unfocus this window. + /// + /// # Examples + /// + /// ``` + /// // Unfocus the focused window + /// window.get_focused()?.set_focused(false); + /// ``` + pub fn set_focused(&self, set: bool) { + let mut client = self.window_client.clone(); + block_on_tokio(client.set_focused(SetFocusedRequest { + window_id: Some(self.id), + set_or_toggle: Some(match set { + true => SetOrToggle::Set, + false => SetOrToggle::Unset, + } as i32), + })) + .unwrap(); + } + + /// Toggle this window to and from focused. + /// + /// # Examples + /// + /// ``` + /// // Toggle the focused window to and from floating. + /// // Calling this a second time will do nothing because there won't + /// // be a focused window. + /// window.get_focused()?.toggle_focused(); + /// ``` + pub fn toggle_focused(&self) { + let mut client = self.window_client.clone(); + block_on_tokio(client.set_focused(SetFocusedRequest { + window_id: Some(self.id), + set_or_toggle: Some(SetOrToggle::Toggle as i32), })) .unwrap(); } @@ -432,7 +470,10 @@ impl WindowHandle { block_on_tokio(client.set_tag(SetTagRequest { window_id: Some(self.id), tag_id: Some(tag.id), - set_or_toggle: Some(window::v0alpha1::set_tag_request::SetOrToggle::Set(set)), + set_or_toggle: Some(match set { + true => SetOrToggle::Set, + false => SetOrToggle::Unset, + } as i32), })) .unwrap(); } @@ -456,7 +497,7 @@ impl WindowHandle { block_on_tokio(client.set_tag(SetTagRequest { window_id: Some(self.id), tag_id: Some(tag.id), - set_or_toggle: Some(window::v0alpha1::set_tag_request::SetOrToggle::Toggle(())), + set_or_toggle: Some(SetOrToggle::Toggle as i32), })) .unwrap(); } diff --git a/src/api.rs b/src/api.rs index 7ed2142..1bb37e4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -22,14 +22,14 @@ use pinnacle_api_defs::pinnacle::{ SetLayoutRequest, SwitchToRequest, }, }, - v0alpha1::{pinnacle_service_server, Geometry, QuitRequest}, + v0alpha1::{pinnacle_service_server, Geometry, QuitRequest, SetOrToggle}, window::{ self, v0alpha1::{ window_service_server, AddWindowRuleRequest, CloseRequest, FullscreenOrMaximized, MoveGrabRequest, MoveToTagRequest, ResizeGrabRequest, SetFloatingRequest, - SetFullscreenRequest, SetGeometryRequest, SetMaximizedRequest, SetTagRequest, - WindowRule, WindowRuleCondition, + SetFocusedRequest, SetFullscreenRequest, SetGeometryRequest, SetMaximizedRequest, + SetTagRequest, WindowRule, WindowRuleCondition, }, }, }; @@ -682,19 +682,22 @@ impl tag_service_server::TagService for TagService { .ok_or_else(|| Status::invalid_argument("no tag specified"))?, ); - let set_or_toggle = match request.set_or_toggle { - Some(tag::v0alpha1::set_active_request::SetOrToggle::Set(set)) => Some(set), - Some(tag::v0alpha1::set_active_request::SetOrToggle::Toggle(_)) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; + let set_or_toggle = request.set_or_toggle(); + + if set_or_toggle == SetOrToggle::Unspecified { + return Err(Status::invalid_argument("unspecified set or toggle")); + } run_unary_no_response(&self.sender, move |state| { let Some(tag) = tag_id.tag(state) else { return; }; + match set_or_toggle { - Some(set) => tag.set_active(set), - None => tag.set_active(!tag.active()), + SetOrToggle::Set => tag.set_active(true), + SetOrToggle::Unset => tag.set_active(false), + SetOrToggle::Toggle => tag.set_active(!tag.active()), + SetOrToggle::Unspecified => unreachable!(), } let Some(output) = tag.output(state) else { @@ -1057,9 +1060,8 @@ impl output_service_server::OutputService for OutputService { let y = output.as_ref().map(|output| output.current_location().y); let focused = state - .focus_state - .focused_output - .as_ref() + .output_focus_stack + .current_focus() .and_then(|foc_op| output.as_ref().map(|op| op == foc_op)); let tag_ids = output @@ -1197,25 +1199,30 @@ impl window_service_server::WindowService for WindowService { .ok_or_else(|| Status::invalid_argument("no window specified"))?, ); - let set_or_toggle = match request.set_or_toggle { - Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Set(set)) => Some(set), - Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Toggle(_)) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; + let set_or_toggle = request.set_or_toggle(); + + if set_or_toggle == SetOrToggle::Unspecified { + return Err(Status::invalid_argument("unspecified set or toggle")); + } run_unary_no_response(&self.sender, move |state| { let Some(window) = window_id.window(state) else { return; }; + match set_or_toggle { - Some(set) => { - let is_fullscreen = - window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()); - if set != is_fullscreen { + SetOrToggle::Set => { + if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) { window.toggle_fullscreen(); } } - None => window.toggle_fullscreen(), + SetOrToggle::Unset => { + if window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) { + window.toggle_fullscreen(); + } + } + SetOrToggle::Toggle => window.toggle_fullscreen(), + SetOrToggle::Unspecified => unreachable!(), } let Some(output) = window.output(state) else { @@ -1240,25 +1247,30 @@ impl window_service_server::WindowService for WindowService { .ok_or_else(|| Status::invalid_argument("no window specified"))?, ); - let set_or_toggle = match request.set_or_toggle { - Some(window::v0alpha1::set_maximized_request::SetOrToggle::Set(set)) => Some(set), - Some(window::v0alpha1::set_maximized_request::SetOrToggle::Toggle(_)) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; + let set_or_toggle = request.set_or_toggle(); + + if set_or_toggle == SetOrToggle::Unspecified { + return Err(Status::invalid_argument("unspecified set or toggle")); + } run_unary_no_response(&self.sender, move |state| { let Some(window) = window_id.window(state) else { return; }; + match set_or_toggle { - Some(set) => { - let is_maximized = - window.with_state(|state| state.fullscreen_or_maximized.is_maximized()); - if set != is_maximized { + SetOrToggle::Set => { + if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) { window.toggle_maximized(); } } - None => window.toggle_maximized(), + SetOrToggle::Unset => { + if window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) { + window.toggle_maximized(); + } + } + SetOrToggle::Toggle => window.toggle_maximized(), + SetOrToggle::Unspecified => unreachable!(), } let Some(output) = window.output(state) else { @@ -1283,25 +1295,30 @@ impl window_service_server::WindowService for WindowService { .ok_or_else(|| Status::invalid_argument("no window specified"))?, ); - let set_or_toggle = match request.set_or_toggle { - Some(window::v0alpha1::set_floating_request::SetOrToggle::Set(set)) => Some(set), - Some(window::v0alpha1::set_floating_request::SetOrToggle::Toggle(_)) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; + let set_or_toggle = request.set_or_toggle(); + + if set_or_toggle == SetOrToggle::Unspecified { + return Err(Status::invalid_argument("unspecified set or toggle")); + } run_unary_no_response(&self.sender, move |state| { let Some(window) = window_id.window(state) else { return; }; + match set_or_toggle { - Some(set) => { - let is_floating = - window.with_state(|state| state.floating_or_tiled.is_floating()); - if set != is_floating { + SetOrToggle::Set => { + if !window.with_state(|state| state.floating_or_tiled.is_floating()) { window.toggle_floating(); } } - None => window.toggle_floating(), + SetOrToggle::Unset => { + if window.with_state(|state| state.floating_or_tiled.is_floating()) { + window.toggle_floating(); + } + } + SetOrToggle::Toggle => window.toggle_floating(), + SetOrToggle::Unspecified => unreachable!(), } let Some(output) = window.output(state) else { @@ -1314,6 +1331,116 @@ impl window_service_server::WindowService for WindowService { .await } + async fn set_focused( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let window_id = WindowId( + request + .window_id + .ok_or_else(|| Status::invalid_argument("no window specified"))?, + ); + + let set_or_toggle = request.set_or_toggle(); + + if set_or_toggle == SetOrToggle::Unspecified { + return Err(Status::invalid_argument("unspecified set or toggle")); + } + + run_unary_no_response(&self.sender, move |state| { + let Some(window) = window_id.window(state) else { + return; + }; + + let Some(output) = window.output(state) else { + return; + }; + + // if !matches!( + // &focus, + // FocusTarget::Window(WindowElement::X11OverrideRedirect(_)) + // ) { + // keyboard.set_focus(self, Some(focus.clone()), serial); + // } + // + // self.space.elements().for_each(|window| { + // if let WindowElement::Wayland(window) = window { + // window.toplevel().send_configure(); + // } + // }); + // } else { + // self.space.elements().for_each(|window| { + // window.set_activate(false); + // if let WindowElement::Wayland(window) = window { + // window.toplevel().send_configure(); + // } + // }); + // keyboard.set_focus(self, None, serial); + // } + + for win in state.space.elements() { + win.set_activate(false); + } + + match set_or_toggle { + SetOrToggle::Set => { + window.set_activate(true); + output.with_state(|state| state.focus_stack.set_focus(window.clone())); + state.output_focus_stack.set_focus(output.clone()); + if let Some(keyboard) = state.seat.get_keyboard() { + keyboard.set_focus( + state, + Some(FocusTarget::Window(window)), + SERIAL_COUNTER.next_serial(), + ); + } + } + SetOrToggle::Unset => { + if output.with_state(|state| state.focus_stack.current_focus() == Some(&window)) + { + output.with_state(|state| state.focus_stack.unset_focus()); + if let Some(keyboard) = state.seat.get_keyboard() { + keyboard.set_focus(state, None, SERIAL_COUNTER.next_serial()); + } + } + } + SetOrToggle::Toggle => { + if output.with_state(|state| state.focus_stack.current_focus() == Some(&window)) + { + output.with_state(|state| state.focus_stack.unset_focus()); + if let Some(keyboard) = state.seat.get_keyboard() { + keyboard.set_focus(state, None, SERIAL_COUNTER.next_serial()); + } + } else { + window.set_activate(true); + output.with_state(|state| state.focus_stack.set_focus(window.clone())); + state.output_focus_stack.set_focus(output.clone()); + if let Some(keyboard) = state.seat.get_keyboard() { + keyboard.set_focus( + state, + Some(FocusTarget::Window(window)), + SERIAL_COUNTER.next_serial(), + ); + } + } + } + SetOrToggle::Unspecified => unreachable!(), + } + + for window in state.space.elements() { + if let WindowElement::Wayland(window) = window { + window.toplevel().send_configure(); + } + } + + state.update_windows(&output); + state.schedule_render(&output); + }) + .await + } + async fn move_to_tag( &self, request: Request, @@ -1360,11 +1487,11 @@ impl window_service_server::WindowService for WindowService { .ok_or_else(|| Status::invalid_argument("no tag specified"))?, ); - let set_or_toggle = match request.set_or_toggle { - Some(window::v0alpha1::set_tag_request::SetOrToggle::Set(set)) => Some(set), - Some(window::v0alpha1::set_tag_request::SetOrToggle::Toggle(_)) => None, - None => return Err(Status::invalid_argument("unspecified set or toggle")), - }; + let set_or_toggle = request.set_or_toggle(); + + if set_or_toggle == SetOrToggle::Unspecified { + return Err(Status::invalid_argument("unspecified set or toggle")); + } run_unary_no_response(&self.sender, move |state| { let Some(window) = window_id.window(state) else { return }; @@ -1372,25 +1499,21 @@ impl window_service_server::WindowService for WindowService { // TODO: turn state.tags into a hashset match set_or_toggle { - Some(set) => { - if set { - window.with_state(|state| { - state.tags.retain(|tg| tg != &tag); - state.tags.push(tag.clone()); - }) - } else { - window.with_state(|state| { - state.tags.retain(|tg| tg != &tag); - }) - } - } - None => window.with_state(|state| { + SetOrToggle::Set => window.with_state(|state| { + state.tags.retain(|tg| tg != &tag); + state.tags.push(tag.clone()); + }), + SetOrToggle::Unset => window.with_state(|state| { + state.tags.retain(|tg| tg != &tag); + }), + SetOrToggle::Toggle => window.with_state(|state| { if !state.tags.contains(&tag) { state.tags.push(tag.clone()); } else { state.tags.retain(|tg| tg != &tag); } }), + SetOrToggle::Unspecified => unreachable!(), } let Some(output) = tag.output(state) else { return }; diff --git a/src/backend/udev.rs b/src/backend/udev.rs index d014ab4..c8841d3 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -63,7 +63,7 @@ use smithay::{ backend::GlobalId, protocol::wl_surface::WlSurface, Display, DisplayHandle, }, }, - utils::{Clock, DeviceFd, IsAlive, Logical, Monotonic, Point, Transform}, + utils::{Clock, DeviceFd, Logical, Monotonic, Point, Transform}, wayland::dmabuf::{DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState}, }; use smithay_drm_extras::{ @@ -522,8 +522,6 @@ pub fn run_udev() -> anyhow::Result<()> { .display_handle .flush_clients() .expect("failed to flush_clients"); - - state.focus_state.fix_up_focus(&mut state.space); }, )?; @@ -874,7 +872,7 @@ impl State { ); let global = output.create_global::(&udev.display_handle); - self.focus_state.focused_output = Some(output.clone()); + self.output_focus_stack.set_focus(output.clone()); let x = self.space.outputs().fold(0, |acc, o| { let Some(geo) = self.space.output_geometry(o) else { @@ -1254,13 +1252,14 @@ impl State { texture }); - let windows = self - .focus_state - .focus_stack - .iter() - .filter(|win| win.alive()) - .cloned() - .collect::>(); + // let windows = self + // .output_focus_stack + // .stack + // .iter() + // .flat_map(|op| op.with_state(|state| state.focus_stack.stack.clone())) + // .collect::>(); + + let windows = self.space.elements().cloned().collect::>(); let result = render_surface( surface, diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 728030f..51a3c65 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -30,7 +30,7 @@ use smithay::{ use crate::{ render::{pointer::PointerElement, take_presentation_feedback}, - state::State, + state::{State, WithState}, }; use super::{Backend, BackendData}; @@ -165,7 +165,7 @@ pub fn run_winit() -> anyhow::Result<()> { evt_loop_handle, )?; - state.focus_state.focused_output = Some(output.clone()); + state.output_focus_stack.set_focus(output.clone()); let winit = state.backend.winit_mut(); @@ -237,6 +237,7 @@ pub fn run_winit() -> anyhow::Result<()> { Some(Duration::from_micros(((1.0 / 144.0) * 1000000.0) as u64)), &mut state, |state| { + state.fixup_focus(); state.space.refresh(); state.popup_manager.cleanup(); state @@ -256,8 +257,6 @@ impl State { let full_redraw = &mut winit.full_redraw; *full_redraw = full_redraw.saturating_sub(1); - self.focus_state.fix_up_focus(&mut self.space); - if let CursorImageStatus::Surface(surface) = &self.cursor_status { if !surface.alive() { self.cursor_status = CursorImageStatus::default_named(); @@ -269,11 +268,15 @@ impl State { let mut pointer_element = PointerElement::::new(); pointer_element.set_status(self.cursor_status.clone()); + // The z-index of these is determined by `state.fixup_focus()`, which is called at the end + // of every event loop cycle + let windows = self.space.elements().cloned().collect::>(); + let output_render_elements = crate::render::generate_render_elements( output, winit.backend.renderer(), &self.space, - &self.focus_state.focus_stack, + &windows, self.pointer_location, &mut self.cursor_status, self.dnd_icon.as_ref(), @@ -316,7 +319,7 @@ impl State { // Send frames to the cursor surface so it updates correctly if let CursorImageStatus::Surface(surf) = &self.cursor_status { - if let Some(op) = self.focus_state.focused_output.as_ref() { + if let Some(op) = self.output_focus_stack.current_focus() { send_frames_surface_tree(surf, op, time, Some(Duration::ZERO), |_, _| None); } } diff --git a/src/focus.rs b/src/focus.rs index 34ade3e..cf3d81a 100644 --- a/src/focus.rs +++ b/src/focus.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later use smithay::{ - desktop::{LayerSurface, PopupKind, Space}, + desktop::{LayerSurface, PopupKind}, input::{ keyboard::KeyboardTarget, pointer::{MotionEvent, PointerTarget}, @@ -18,42 +18,42 @@ use crate::{ window::WindowElement, }; -#[derive(Default)] -pub struct FocusState { - /// The ordering of window focus - pub focus_stack: Vec, - /// The focused output, currently defined to be the one the pointer is on. - pub focused_output: Option, -} - impl State { /// Get the currently focused window on `output` /// that isn't an override redirect window, if any. pub fn focused_window(&mut self, output: &Output) -> Option { - self.focus_state.focus_stack.retain(|win| win.alive()); + output.with_state(|state| state.focus_stack.stack.retain(|win| win.alive())); - let mut windows = self.focus_state.focus_stack.iter().rev().filter(|win| { - let win_tags = win.with_state(|state| state.tags.clone()); - let output_tags = - output.with_state(|state| state.focused_tags().cloned().collect::>()); - - win_tags + let windows = output.with_state(|state| { + state + .focus_stack + .stack .iter() - .any(|win_tag| output_tags.iter().any(|op_tag| win_tag == op_tag)) + .rev() + .filter(|win| { + let win_tags = win.with_state(|state| state.tags.clone()); + let output_tags = state.focused_tags().cloned().collect::>(); + + win_tags + .iter() + .any(|win_tag| output_tags.iter().any(|op_tag| win_tag == op_tag)) + }) + .cloned() + .collect::>() }); - windows.find(|win| !win.is_x11_override_redirect()).cloned() + windows + .into_iter() + .find(|win| !win.is_x11_override_redirect()) } - /// Update the focus. This will raise the current focus and activate it, - /// as well as setting the keyboard focus to it. + /// Update the keyboard focus. pub fn update_focus(&mut self, output: &Output) { let current_focus = self.focused_window(output); if let Some(win) = ¤t_focus { assert!(!win.is_x11_override_redirect()); - self.space.raise_element(win, true); if let WindowElement::Wayland(w) = win { w.toplevel().send_configure(); } @@ -64,33 +64,52 @@ impl State { current_focus.map(|win| win.into()), SERIAL_COUNTER.next_serial(), ); + } - // TODO: if there already is a visible focused window, don't do anything + pub fn fixup_focus(&mut self) { + for win in self.z_index_stack.stack.iter() { + self.space.raise_element(win, false); + } } } -impl FocusState { - pub fn new() -> Self { - Default::default() - } +/// A vector of windows, with the last one being the one in focus and the first +/// being the one at the bottom of the focus stack. +#[derive(Debug)] +pub struct FocusStack { + pub stack: Vec, + focused: bool, +} - /// Set the currently focused window. - pub fn set_focus(&mut self, window: WindowElement) { - self.focus_stack.retain(|win| win != &window); - self.focus_stack.push(window); - } - - /// Fix focus layering for all windows in the `focus_stack`. - /// - /// This will call `space.raise_element` on all windows from back - /// to front to correct their z locations. - pub fn fix_up_focus(&self, space: &mut Space) { - for win in self.focus_stack.iter() { - space.raise_element(win, false); +impl Default for FocusStack { + fn default() -> Self { + Self { + stack: Default::default(), + focused: Default::default(), } } } +impl FocusStack { + /// Set `focus` to be focused. + /// + /// If it's already in the stack, it will be removed then pushed. + /// If it isn't, it will just be pushed. + pub fn set_focus(&mut self, focus: T) { + self.stack.retain(|foc| foc != &focus); + self.stack.push(focus); + self.focused = true; + } + + pub fn unset_focus(&mut self) { + self.focused = false; + } + + pub fn current_focus(&self) -> Option<&T> { + self.focused.then(|| self.stack.last())? + } +} + /// Different focusable objects. #[derive(Debug, Clone, PartialEq)] pub enum FocusTarget { diff --git a/src/handlers.rs b/src/handlers.rs index 6b05cdc..cc3b819 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -143,13 +143,15 @@ impl CompositorHandler for State { if is_mapped { self.new_windows.retain(|win| win != &new_window); self.windows.push(new_window.clone()); + self.z_index_stack.set_focus(new_window.clone()); if let (Some(output), _) | (None, Some(output)) = ( - &self.focus_state.focused_output, + self.output_focus_stack.current_focus(), self.space.outputs().next(), ) { tracing::debug!("PLACING TOPLEVEL"); new_window.place_on_output(output); + output.with_state(|state| state.focus_stack.set_focus(new_window.clone())); } self.space @@ -157,7 +159,7 @@ impl CompositorHandler for State { self.apply_window_rules(&new_window); - if let Some(focused_output) = self.focus_state.focused_output.clone() { + if let Some(focused_output) = self.output_focus_stack.current_focus().cloned() { self.update_windows(&focused_output); new_window.send_frame( &focused_output, @@ -414,14 +416,6 @@ impl SeatHandler for State { } fn focus_changed(&mut self, seat: &Seat, focused: Option<&Self::KeyboardFocus>) { - if let Some(win) = - focused.and_then(|focused| self.window_for_surface(&focused.wl_surface()?)) - { - if let WindowElement::Wayland(win) = &win { - win.set_activated(true); - } - self.focus_state.set_focus(win); - } let focus_client = focused.and_then(|foc_target| { self.display_handle .get_client(foc_target.wl_surface()?.id()) diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index c99003c..f2a02a5 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -109,12 +109,23 @@ impl XdgShellHandler for State { .wl_surface() .is_some_and(|surf| &surf != surface.wl_surface()) }); - self.focus_state.focus_stack.retain(|window| { + + self.z_index_stack.stack.retain(|window| { window .wl_surface() .is_some_and(|surf| &surf != surface.wl_surface()) }); + for output in self.space.outputs() { + output.with_state(|state| { + state.focus_stack.stack.retain(|window| { + window + .wl_surface() + .is_some_and(|surf| &surf != surface.wl_surface()) + }) + }); + } + let Some(window) = self.window_for_surface(surface.wl_surface()) else { return; }; @@ -124,7 +135,9 @@ impl XdgShellHandler for State { let focus = self.focused_window(&output).map(FocusTarget::Window); if let Some(FocusTarget::Window(win)) = &focus { tracing::debug!("Focusing on prev win"); + // TODO: self.space.raise_element(win, true); + self.z_index_stack.set_focus(win.clone()); if let WindowElement::Wayland(win) = &win { win.toplevel().send_configure(); } @@ -142,9 +155,8 @@ impl XdgShellHandler for State { fn new_popup(&mut self, surface: PopupSurface, mut positioner: PositionerState) { tracing::debug!(?positioner.constraint_adjustment, ?positioner.gravity); let output_rect = self - .focus_state - .focused_output - .as_ref() + .output_focus_stack + .current_focus() .or_else(|| self.space.outputs().next()) .and_then(|op| self.space.output_geometry(op)); diff --git a/src/handlers/xwayland.rs b/src/handlers/xwayland.rs index 71628a3..310cfc9 100644 --- a/src/handlers/xwayland.rs +++ b/src/handlers/xwayland.rs @@ -49,17 +49,15 @@ impl XwmHandler for State { .expect("called element_bbox on an unmapped window"); let output_size = self - .focus_state - .focused_output - .as_ref() + .output_focus_stack + .current_focus() .and_then(|op| self.space.output_geometry(op)) .map(|geo| geo.size) .unwrap_or((2, 2).into()); let output_loc = self - .focus_state - .focused_output - .as_ref() + .output_focus_stack + .current_focus() .map(|op| op.current_location()) .unwrap_or((0, 0).into()); @@ -88,7 +86,7 @@ impl XwmHandler for State { // TODO: ssd if let (Some(output), _) | (None, Some(output)) = ( - &self.focus_state.focused_output, + self.output_focus_stack.current_focus(), self.space.outputs().next(), ) { window.place_on_output(output); @@ -100,26 +98,27 @@ impl XwmHandler for State { }); } + // TODO: will an unmap -> map duplicate the window self.windows.push(window.clone()); - - self.focus_state.set_focus(window.clone()); + self.z_index_stack.set_focus(window.clone()); self.apply_window_rules(&window); if let Some(output) = window.output(self) { + output.with_state(|state| state.focus_stack.set_focus(window.clone())); self.update_windows(&output); } self.loop_handle.insert_idle(move |state| { state - .seat - .get_keyboard() - .expect("Seat had no keyboard") // FIXME: actually handle error - .set_focus( - state, - Some(FocusTarget::Window(window)), - SERIAL_COUNTER.next_serial(), - ); + .seat + .get_keyboard() + .expect("Seat had no keyboard") // FIXME: actually handle error + .set_focus( + state, + Some(FocusTarget::Window(window)), + SERIAL_COUNTER.next_serial(), + ); }); } @@ -131,24 +130,30 @@ impl XwmHandler for State { let loc = window.geometry().loc; let window = WindowElement::X11OverrideRedirect(window); + self.windows.push(window.clone()); + self.z_index_stack.set_focus(window.clone()); if let (Some(output), _) | (None, Some(output)) = ( - &self.focus_state.focused_output, + self.output_focus_stack.current_focus(), self.space.outputs().next(), ) { window.place_on_output(output); + output.with_state(|state| state.focus_stack.set_focus(window.clone())) } - self.space.map_element(window.clone(), loc, true); - self.focus_state.set_focus(window); + self.space.map_element(window, loc, true); } fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) { - self.focus_state.focus_stack.retain(|win| { - win.wl_surface() - .is_some_and(|surf| Some(surf) != window.wl_surface()) - }); + for output in self.space.outputs() { + output.with_state(|state| { + state.focus_stack.stack.retain(|win| { + win.wl_surface() + .is_some_and(|surf| Some(surf) != window.wl_surface()) + }) + }); + } let win = self .space @@ -157,6 +162,12 @@ impl XwmHandler for State { .cloned(); if let Some(win) = win { + self.windows + .retain(|elem| win.wl_surface() != elem.wl_surface()); + self.z_index_stack + .stack + .retain(|elem| win.wl_surface() != elem.wl_surface()); + self.space.unmap_elem(&win); if let Some(output) = win.output(self) { @@ -166,6 +177,7 @@ impl XwmHandler for State { if let Some(FocusTarget::Window(win)) = &focus { self.space.raise_element(win, true); + self.z_index_stack.set_focus(win.clone()); if let WindowElement::Wayland(win) = &win { win.toplevel().send_configure(); } @@ -187,10 +199,14 @@ impl XwmHandler for State { } fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) { - self.focus_state.focus_stack.retain(|win| { - win.wl_surface() - .is_some_and(|surf| Some(surf) != window.wl_surface()) - }); + for output in self.space.outputs() { + output.with_state(|state| { + state.focus_stack.stack.retain(|win| { + win.wl_surface() + .is_some_and(|surf| Some(surf) != window.wl_surface()) + }) + }); + } let win = self .windows @@ -213,6 +229,10 @@ impl XwmHandler for State { self.windows .retain(|elem| win.wl_surface() != elem.wl_surface()); + self.z_index_stack + .stack + .retain(|elem| win.wl_surface() != elem.wl_surface()); + if let Some(output) = win.output(self) { self.update_windows(&output); @@ -220,6 +240,7 @@ impl XwmHandler for State { if let Some(FocusTarget::Window(win)) = &focus { self.space.raise_element(win, true); + self.z_index_stack.set_focus(win.clone()); if let WindowElement::Wayland(win) = &win { win.toplevel().send_configure(); } diff --git a/src/input.rs b/src/input.rs index c63a3bc..c6a1d5c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -21,7 +21,7 @@ use smithay::{ }, reexports::input::{self, Led}, utils::{Logical, Point, SERIAL_COUNTER}, - wayland::{seat::WaylandFocus, shell::wlr_layer}, + wayland::shell::wlr_layer, }; use tokio::sync::mpsc::UnboundedSender; use xkbcommon::xkb::Keysym; @@ -169,16 +169,20 @@ impl State { let layers = layer_map_for_output(output); - let top_fullscreen_window = self.focus_state.focus_stack.iter().rev().find(|win| { - win.with_state(|state| { - state.fullscreen_or_maximized.is_fullscreen() - && output.with_state(|op_state| { - op_state - .focused_tags() - .any(|op_tag| state.tags.contains(op_tag)) - }) - }) - }); + let top_fullscreen_window = output + .with_state(|state| state.focus_stack.stack.clone()) + .into_iter() + .rev() + .find(|win| { + win.with_state(|state| { + state.fullscreen_or_maximized.is_fullscreen() + && output.with_state(|op_state| { + op_state + .focused_tags() + .any(|op_tag| state.tags.contains(op_tag)) + }) + }) + }); if let Some(window) = top_fullscreen_window { Some((FocusTarget::from(window.clone()), output_geo.loc)) @@ -331,29 +335,20 @@ impl State { // unfocus on windows. if button_state == ButtonState::Pressed { if let Some((focus, _)) = self.focus_target_under(pointer_loc) { - // Move window to top of stack. - if let FocusTarget::Window(window) = &focus { - self.space.raise_element(window, true); - if let WindowElement::X11(surface) = &window { - self.xwm - .as_mut() - .expect("no xwm") - .raise_window(surface) - .expect("failed to raise x11 win"); - surface - .set_activated(true) - .expect("failed to set x11 win to activated"); - } - } - - tracing::debug!("wl_surface focus is some? {}", focus.wl_surface().is_some()); - // NOTE: *Do not* set keyboard focus to an override redirect window. This leads // | to wonky things like right-click menus not correctly getting pointer // | clicks or showing up at all. // TODO: use update_keyboard_focus from anvil + if let FocusTarget::Window(window) = &focus { + self.space.raise_element(window, true); + self.z_index_stack.set_focus(window.clone()); + if let Some(output) = window.output(self) { + output.with_state(|state| state.focus_stack.set_focus(window.clone())); + } + } + if !matches!( &focus, FocusTarget::Window(WindowElement::X11OverrideRedirect(_)) @@ -361,30 +356,23 @@ impl State { keyboard.set_focus(self, Some(focus.clone()), serial); } - self.space.elements().for_each(|window| { + for window in self.space.elements() { if let WindowElement::Wayland(window) = window { window.toplevel().send_configure(); } - }); - - if let FocusTarget::Window(window) = &focus { - tracing::debug!("setting keyboard focus to {:?}", window.class()); } } else { - self.space.elements().for_each(|window| match window { - WindowElement::Wayland(window) => { - window.set_activated(false); - window.toplevel().send_configure(); - } - WindowElement::X11(surface) => { - surface - .set_activated(false) - .expect("failed to deactivate x11 win"); - // INFO: do i need to configure this? - } - WindowElement::X11OverrideRedirect(_) => (), - _ => unreachable!(), - }); + if let Some(focused_op) = self.output_focus_stack.current_focus() { + focused_op.with_state(|state| { + state.focus_stack.unset_focus(); + for window in state.focus_stack.stack.iter() { + window.set_activate(false); + if let WindowElement::Wayland(window) = window { + window.toplevel().send_configure(); + } + } + }); + } keyboard.set_focus(self, None, serial); } }; @@ -488,7 +476,7 @@ impl State { self.pointer_location = pointer_loc; - match self.focus_state.focused_output { + match self.output_focus_stack.current_focus() { Some(_) => { if let Some(output) = self .space @@ -496,11 +484,13 @@ impl State { .next() .cloned() { - self.focus_state.focused_output = Some(output); + self.output_focus_stack.set_focus(output); } } None => { - self.focus_state.focused_output = self.space.outputs().next().cloned(); + if let Some(output) = self.space.outputs().next().cloned() { + self.output_focus_stack.set_focus(output); + } } } @@ -524,7 +514,8 @@ impl State { // clamp to screen limits // this event is never generated by winit self.pointer_location = self.clamp_coords(self.pointer_location); - match self.focus_state.focused_output { + + match self.output_focus_stack.current_focus() { Some(_) => { if let Some(output) = self .space @@ -532,11 +523,13 @@ impl State { .next() .cloned() { - self.focus_state.focused_output = Some(output); + self.output_focus_stack.set_focus(output); } } None => { - self.focus_state.focused_output = self.space.outputs().next().cloned(); + if let Some(output) = self.space.outputs().next().cloned() { + self.output_focus_stack.set_focus(output); + } } } @@ -565,13 +558,9 @@ impl State { pointer.frame(self); - self.schedule_render( - &self - .focus_state - .focused_output - .clone() - .expect("no focused output"), - ); + if let Some(output) = self.output_focus_stack.current_focus().cloned() { + self.schedule_render(&output); + } } } } diff --git a/src/layout.rs b/src/layout.rs index 1d0dfe5..ab16be6 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -56,22 +56,17 @@ impl State { return; }; - let (windows_on_foc_tags, mut windows_not_on_foc_tags): (Vec<_>, _) = - output.with_state(|state| { - let focused_tags = state.focused_tags().collect::>(); - self.windows - .iter() - .filter(|win| !win.is_x11_override_redirect()) - .cloned() - .partition(|win| { - win.with_state(|state| { - state.tags.iter().any(|tg| focused_tags.contains(&tg)) - }) - }) - }); - - // Don't unmap windows that aren't on `output` (that would clear all other monitors) - windows_not_on_foc_tags.retain(|win| win.output(self) == Some(output.clone())); + let windows_on_foc_tags = output.with_state(|state| { + let focused_tags = state.focused_tags().collect::>(); + self.windows + .iter() + .filter(|win| !win.is_x11_override_redirect()) + .filter(|win| { + win.with_state(|state| state.tags.iter().any(|tg| focused_tags.contains(&tg))) + }) + .cloned() + .collect::>() + }); let tiled_windows = windows_on_foc_tags .iter() @@ -124,14 +119,13 @@ impl State { WindowElement::Wayland(wl_win) => { let pending = compositor::with_states(wl_win.toplevel().wl_surface(), |states| { - let lock = states + states .data_map .get::() .expect("XdgToplevelSurfaceData wasn't in surface's data map") .lock() - .expect("Failed to lock Mutex"); - - lock.has_pending_changes() + .expect("Failed to lock Mutex") + .has_pending_changes() }); if pending { @@ -158,6 +152,8 @@ impl State { for (loc, window) in non_pending_wins { self.space.map_element(window, loc, false); } + + self.fixup_focus(); } } diff --git a/src/output.rs b/src/output.rs index 986dae0..c3a0d8e 100644 --- a/src/output.rs +++ b/src/output.rs @@ -5,8 +5,10 @@ use std::cell::RefCell; use smithay::output::Output; use crate::{ + focus::FocusStack, state::{State, WithState}, tag::Tag, + window::WindowElement, }; /// A unique identifier for an output. @@ -31,6 +33,7 @@ impl OutputName { #[derive(Default, Debug)] pub struct OutputState { pub tags: Vec, + pub focus_stack: FocusStack, } impl WithState for Output { diff --git a/src/state.rs b/src/state.rs index 1726011..2d21a74 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,13 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later use crate::{ - api::signal::SignalState, backend::Backend, config::Config, cursor::Cursor, focus::FocusState, + api::signal::SignalState, backend::Backend, config::Config, cursor::Cursor, focus::FocusStack, grab::resize_grab::ResizeSurfaceState, window::WindowElement, }; use anyhow::Context; use smithay::{ desktop::{PopupManager, Space}, input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState}, + output::Output, reexports::{ calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction}, wayland_server::{ @@ -22,8 +23,10 @@ use smithay::{ dmabuf::DmabufFeedback, fractional_scale::FractionalScaleManagerState, output::OutputManagerState, - selection::data_device::DataDeviceState, - selection::{primary_selection::PrimarySelectionState, wlr_data_control::DataControlState}, + selection::{ + data_device::DataDeviceState, primary_selection::PrimarySelectionState, + wlr_data_control::DataControlState, + }, shell::{wlr_layer::WlrLayerShellState, xdg::XdgShellState}, shm::ShmState, socket::ListeningSocketSource, @@ -67,8 +70,9 @@ pub struct State { /// The state of key and mousebinds along with libinput settings pub input_state: InputState, - /// Keeps track of the focus stack and focused output - pub focus_state: FocusState, + + pub output_focus_stack: FocusStack, + pub z_index_stack: FocusStack, pub popup_manager: PopupManager, @@ -246,7 +250,9 @@ impl State { data_control_state, input_state: InputState::new(), - focus_state: FocusState::new(), + + output_focus_stack: FocusStack::default(), + z_index_stack: FocusStack::default(), config: Config::default(), From 1f909ac81b39877db8774fb7cd65eee6f2e2994e Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 24 Feb 2024 14:42:43 -0600 Subject: [PATCH 2/4] Remove unused import QUICK BEFORE THE CI FAILS --- src/backend/winit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 51a3c65..34a6fd3 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -30,7 +30,7 @@ use smithay::{ use crate::{ render::{pointer::PointerElement, take_presentation_feedback}, - state::{State, WithState}, + state::State, }; use super::{Backend, BackendData}; From e734a716c08c78c4cc1f57d020acbf1810174036 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 24 Feb 2024 14:58:58 -0600 Subject: [PATCH 3/4] Set window focused only if it's on the focused output --- src/api.rs | 7 +++++-- src/backend/udev.rs | 8 +------- src/focus.rs | 5 +++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/api.rs b/src/api.rs index 1bb37e4..13c06cc 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1698,8 +1698,11 @@ impl window_service_server::WindowService for WindowService { }); let focused = window.as_ref().and_then(|win| { - let output = win.output(state)?; - state.focused_window(&output).map(|foc_win| win == &foc_win) + state + .output_focus_stack + .current_focus() + .and_then(|output| state.focused_window(output)) + .map(|foc_win| win == &foc_win) }); let floating = window diff --git a/src/backend/udev.rs b/src/backend/udev.rs index c8841d3..8112493 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -516,6 +516,7 @@ pub fn run_udev() -> anyhow::Result<()> { Some(Duration::from_micros(((1.0 / 144.0) * 1000000.0) as u64)), &mut state, |state| { + state.fixup_focus(); state.space.refresh(); state.popup_manager.cleanup(); state @@ -1252,13 +1253,6 @@ impl State { texture }); - // let windows = self - // .output_focus_stack - // .stack - // .iter() - // .flat_map(|op| op.with_state(|state| state.focus_stack.stack.clone())) - // .collect::>(); - let windows = self.space.elements().cloned().collect::>(); let result = render_surface( diff --git a/src/focus.rs b/src/focus.rs index cf3d81a..fd950b7 100644 --- a/src/focus.rs +++ b/src/focus.rs @@ -21,8 +21,9 @@ use crate::{ impl State { /// Get the currently focused window on `output` /// that isn't an override redirect window, if any. - pub fn focused_window(&mut self, output: &Output) -> Option { - output.with_state(|state| state.focus_stack.stack.retain(|win| win.alive())); + pub fn focused_window(&self, output: &Output) -> Option { + // TODO: see if the below is necessary + // output.with_state(|state| state.focus_stack.stack.retain(|win| win.alive())); let windows = output.with_state(|state| { state From 2e5853eabe8d53d767dd32c543563ab661001cac Mon Sep 17 00:00:00 2001 From: Ottatop Date: Sat, 24 Feb 2024 15:23:42 -0600 Subject: [PATCH 4/4] Return from batch if `requests` is empty Fixes a hang if you tried to close a window with the keybind with no windows open --- api/lua/pinnacle/util.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/lua/pinnacle/util.lua b/api/lua/pinnacle/util.lua index e05b814..661f919 100644 --- a/api/lua/pinnacle/util.lua +++ b/api/lua/pinnacle/util.lua @@ -58,6 +58,10 @@ local util = {} --- ---@return T[] responses The results of each request in the same order that they were in `requests`. function util.batch(requests) + if #requests == 0 then + return {} + end + local loop = require("cqueues").new() local responses = {}