Rework focus and add Window.set_focused

This has to be one of the most unreviewable commits I think I've created yet
This commit is contained in:
Ottatop 2024-02-24 14:41:52 -06:00
parent 67ee5021bd
commit 14a36b9738
21 changed files with 605 additions and 304 deletions

View file

@ -142,4 +142,11 @@ require("pinnacle").setup(function(Pinnacle)
end end
end) end)
end end
-- Enable sloppy focus
Window.connect_signal({
pointer_enter = function(window)
window:set_focused(true)
end,
})
end) end)

View file

@ -18,6 +18,7 @@ function protobuf.build_protos()
PINNACLE_PROTO_DIR .. "/pinnacle/process/" .. version .. "/process.proto", PINNACLE_PROTO_DIR .. "/pinnacle/process/" .. version .. "/process.proto",
PINNACLE_PROTO_DIR .. "/pinnacle/window/" .. version .. "/window.proto", PINNACLE_PROTO_DIR .. "/pinnacle/window/" .. version .. "/window.proto",
PINNACLE_PROTO_DIR .. "/pinnacle/signal/" .. version .. "/signal.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 .. " " local cmd = "protoc --descriptor_set_out=/tmp/pinnacle.pb --proto_path=" .. PINNACLE_PROTO_DIR .. " "

View file

@ -44,6 +44,14 @@ local function build_grpc_request_params(method, data)
} }
end end
local set_or_toggle = {
SET = 1,
[true] = 1,
UNSET = 2,
[false] = 2,
TOGGLE = 3,
}
---@nodoc ---@nodoc
---@class TagHandleModule ---@class TagHandleModule
local tag_handle = {} local tag_handle = {}
@ -445,7 +453,9 @@ end
--- ---
---@param active boolean ---@param active boolean
function TagHandle:set_active(active) 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 end
---Toggle this tag's active state. ---Toggle this tag's active state.
@ -460,7 +470,9 @@ end
---Tag.get("2"):toggle_active() -- Displays nothing ---Tag.get("2"):toggle_active() -- Displays nothing
---``` ---```
function TagHandle:toggle_active() 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 end
---@class TagProperties ---@class TagProperties

View file

@ -16,6 +16,7 @@ local rpc_types = {
SetFullscreen = {}, SetFullscreen = {},
SetMaximized = {}, SetMaximized = {},
SetFloating = {}, SetFloating = {},
SetFocused = {},
MoveToTag = {}, MoveToTag = {},
SetTag = {}, SetTag = {},
MoveGrab = {}, MoveGrab = {},
@ -47,6 +48,14 @@ local function build_grpc_request_params(method, data)
} }
end end
local set_or_toggle = {
SET = 1,
[true] = 1,
UNSET = 2,
[false] = 2,
TOGGLE = 3,
}
---@nodoc ---@nodoc
---@class WindowHandleModule ---@class WindowHandleModule
local window_handle = {} local window_handle = {}
@ -412,7 +421,9 @@ end
--- ---
---@param fullscreen boolean ---@param fullscreen boolean
function WindowHandle:set_fullscreen(fullscreen) 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 end
---Toggle this window to and from fullscreen. ---Toggle this window to and from fullscreen.
@ -425,7 +436,9 @@ end
---end ---end
---``` ---```
function WindowHandle:toggle_fullscreen() 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 end
---Set this window to maximized or not. ---Set this window to maximized or not.
@ -441,7 +454,9 @@ end
--- ---
---@param maximized boolean ---@param maximized boolean
function WindowHandle:set_maximized(maximized) 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 end
---Toggle this window to and from maximized. ---Toggle this window to and from maximized.
@ -454,7 +469,9 @@ end
---end ---end
---``` ---```
function WindowHandle:toggle_maximized() 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 end
---Set this window to floating or not. ---Set this window to floating or not.
@ -470,7 +487,9 @@ end
--- ---
---@param floating boolean ---@param floating boolean
function WindowHandle:set_floating(floating) 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 end
---Toggle this window to and from floating. ---Toggle this window to and from floating.
@ -483,7 +502,41 @@ end
---end ---end
---``` ---```
function WindowHandle:toggle_floating() 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 end
---Move this window to the specified tag. ---Move this window to the specified tag.
@ -523,7 +576,12 @@ end
---@param tag TagHandle The tag to set or unset ---@param tag TagHandle The tag to set or unset
---@param set boolean ---@param set boolean
function WindowHandle:set_tag(tag, set) 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 end
---Toggle the given tag on this window. ---Toggle the given tag on this window.
@ -545,7 +603,12 @@ end
--- ---
---@param tag TagHandle The tag to toggle ---@param tag TagHandle The tag to toggle
function WindowHandle:toggle_tag(tag) 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 end
---@class WindowProperties ---@class WindowProperties

View file

@ -3,13 +3,11 @@ syntax = "proto2";
package pinnacle.tag.v0alpha1; package pinnacle.tag.v0alpha1;
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
import "pinnacle/v0alpha1/pinnacle.proto";
message SetActiveRequest { message SetActiveRequest {
optional uint32 tag_id = 1; optional uint32 tag_id = 1;
oneof set_or_toggle { optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2;
bool set = 2;
google.protobuf.Empty toggle = 3;
}
} }
message SwitchToRequest { message SwitchToRequest {

View file

@ -11,6 +11,14 @@ message Geometry {
optional int32 height = 4; 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 {} message QuitRequest {}
service PinnacleService { service PinnacleService {

View file

@ -17,26 +17,22 @@ message SetGeometryRequest {
message SetFullscreenRequest { message SetFullscreenRequest {
optional uint32 window_id = 1; optional uint32 window_id = 1;
oneof set_or_toggle { optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2;
bool set = 2;
google.protobuf.Empty toggle = 3;
}
} }
message SetMaximizedRequest { message SetMaximizedRequest {
optional uint32 window_id = 1; optional uint32 window_id = 1;
oneof set_or_toggle { optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2;
bool set = 2;
google.protobuf.Empty toggle = 3;
}
} }
message SetFloatingRequest { message SetFloatingRequest {
optional uint32 window_id = 1; optional uint32 window_id = 1;
oneof set_or_toggle { optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2;
bool set = 2; }
google.protobuf.Empty toggle = 3;
} message SetFocusedRequest {
optional uint32 window_id = 1;
optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 2;
} }
message MoveToTagRequest { message MoveToTagRequest {
@ -47,10 +43,7 @@ message MoveToTagRequest {
message SetTagRequest { message SetTagRequest {
optional uint32 window_id = 1; optional uint32 window_id = 1;
optional uint32 tag_id = 2; optional uint32 tag_id = 2;
oneof set_or_toggle { optional .pinnacle.v0alpha1.SetOrToggle set_or_toggle = 3;
bool set = 3;
google.protobuf.Empty toggle = 4;
}
} }
message MoveGrabRequest { message MoveGrabRequest {
@ -119,6 +112,7 @@ service WindowService {
rpc SetFullscreen(SetFullscreenRequest) returns (google.protobuf.Empty); rpc SetFullscreen(SetFullscreenRequest) returns (google.protobuf.Empty);
rpc SetMaximized(SetMaximizedRequest) returns (google.protobuf.Empty); rpc SetMaximized(SetMaximizedRequest) returns (google.protobuf.Empty);
rpc SetFloating(SetFloatingRequest) 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 MoveToTag(MoveToTagRequest) returns (google.protobuf.Empty);
rpc SetTag(SetTagRequest) returns (google.protobuf.Empty); rpc SetTag(SetTagRequest) returns (google.protobuf.Empty);
rpc MoveGrab(MoveGrabRequest) returns (google.protobuf.Empty); rpc MoveGrab(MoveGrabRequest) returns (google.protobuf.Empty);

View file

@ -1,3 +1,4 @@
use pinnacle_api::signal::WindowSignal;
use pinnacle_api::xkbcommon::xkb::Keysym; use pinnacle_api::xkbcommon::xkb::Keysym;
use pinnacle_api::{ use pinnacle_api::{
input::{Mod, MouseButton, MouseEdge}, 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);
})));
} }

View file

@ -36,12 +36,15 @@ use std::{
use futures::FutureExt; use futures::FutureExt;
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use pinnacle_api_defs::pinnacle::tag::{ use pinnacle_api_defs::pinnacle::{
self, tag::{
v0alpha1::{ self,
tag_service_client::TagServiceClient, AddRequest, RemoveRequest, SetActiveRequest, v0alpha1::{
SetLayoutRequest, SwitchToRequest, tag_service_client::TagServiceClient, AddRequest, RemoveRequest, SetActiveRequest,
SetLayoutRequest, SwitchToRequest,
},
}, },
v0alpha1::SetOrToggle,
}; };
use tonic::transport::Channel; use tonic::transport::Channel;
@ -451,7 +454,10 @@ impl TagHandle {
let mut client = self.tag_client.clone(); let mut client = self.tag_client.clone();
block_on_tokio(client.set_active(SetActiveRequest { block_on_tokio(client.set_active(SetActiveRequest {
tag_id: Some(self.id), 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(); .unwrap();
} }
@ -479,7 +485,7 @@ impl TagHandle {
let mut client = self.tag_client.clone(); let mut client = self.tag_client.clone();
block_on_tokio(client.set_active(SetActiveRequest { block_on_tokio(client.set_active(SetActiveRequest {
tag_id: Some(self.id), 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(); .unwrap();
} }

View file

@ -15,15 +15,13 @@
use futures::FutureExt; use futures::FutureExt;
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
use pinnacle_api_defs::pinnacle::{ use pinnacle_api_defs::pinnacle::{
window::v0alpha1::{ v0alpha1::SetOrToggle,
window_service_client::WindowServiceClient, AddWindowRuleRequest, CloseRequest,
MoveToTagRequest, SetTagRequest,
},
window::{ window::{
self, self,
v0alpha1::{ v0alpha1::{
GetRequest, MoveGrabRequest, ResizeGrabRequest, SetFloatingRequest, window_service_client::WindowServiceClient, AddWindowRuleRequest, CloseRequest,
SetFullscreenRequest, SetMaximizedRequest, GetRequest, MoveGrabRequest, MoveToTagRequest, ResizeGrabRequest, SetFloatingRequest,
SetFocusedRequest, SetFullscreenRequest, SetMaximizedRequest, SetTagRequest,
}, },
}, },
}; };
@ -280,9 +278,10 @@ impl WindowHandle {
let mut client = self.window_client.clone(); let mut client = self.window_client.clone();
block_on_tokio(client.set_fullscreen(SetFullscreenRequest { block_on_tokio(client.set_fullscreen(SetFullscreenRequest {
window_id: Some(self.id), window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Set( set_or_toggle: Some(match set {
set, true => SetOrToggle::Set,
)), false => SetOrToggle::Unset,
} as i32),
})) }))
.unwrap(); .unwrap();
} }
@ -301,7 +300,7 @@ impl WindowHandle {
let mut client = self.window_client.clone(); let mut client = self.window_client.clone();
block_on_tokio(client.set_fullscreen(SetFullscreenRequest { block_on_tokio(client.set_fullscreen(SetFullscreenRequest {
window_id: Some(self.id), 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(); .unwrap();
} }
@ -320,9 +319,10 @@ impl WindowHandle {
let mut client = self.window_client.clone(); let mut client = self.window_client.clone();
block_on_tokio(client.set_maximized(SetMaximizedRequest { block_on_tokio(client.set_maximized(SetMaximizedRequest {
window_id: Some(self.id), window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_maximized_request::SetOrToggle::Set( set_or_toggle: Some(match set {
set, true => SetOrToggle::Set,
)), false => SetOrToggle::Unset,
} as i32),
})) }))
.unwrap(); .unwrap();
} }
@ -341,7 +341,7 @@ impl WindowHandle {
let mut client = self.window_client.clone(); let mut client = self.window_client.clone();
block_on_tokio(client.set_maximized(SetMaximizedRequest { block_on_tokio(client.set_maximized(SetMaximizedRequest {
window_id: Some(self.id), 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(); .unwrap();
} }
@ -363,9 +363,10 @@ impl WindowHandle {
let mut client = self.window_client.clone(); let mut client = self.window_client.clone();
block_on_tokio(client.set_floating(SetFloatingRequest { block_on_tokio(client.set_floating(SetFloatingRequest {
window_id: Some(self.id), window_id: Some(self.id),
set_or_toggle: Some(window::v0alpha1::set_floating_request::SetOrToggle::Set( set_or_toggle: Some(match set {
set, true => SetOrToggle::Set,
)), false => SetOrToggle::Unset,
} as i32),
})) }))
.unwrap(); .unwrap();
} }
@ -387,9 +388,46 @@ impl WindowHandle {
let mut client = self.window_client.clone(); let mut client = self.window_client.clone();
block_on_tokio(client.set_floating(SetFloatingRequest { block_on_tokio(client.set_floating(SetFloatingRequest {
window_id: Some(self.id), 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(); .unwrap();
} }
@ -432,7 +470,10 @@ impl WindowHandle {
block_on_tokio(client.set_tag(SetTagRequest { block_on_tokio(client.set_tag(SetTagRequest {
window_id: Some(self.id), window_id: Some(self.id),
tag_id: Some(tag.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(); .unwrap();
} }
@ -456,7 +497,7 @@ impl WindowHandle {
block_on_tokio(client.set_tag(SetTagRequest { block_on_tokio(client.set_tag(SetTagRequest {
window_id: Some(self.id), window_id: Some(self.id),
tag_id: Some(tag.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(); .unwrap();
} }

View file

@ -22,14 +22,14 @@ use pinnacle_api_defs::pinnacle::{
SetLayoutRequest, SwitchToRequest, SetLayoutRequest, SwitchToRequest,
}, },
}, },
v0alpha1::{pinnacle_service_server, Geometry, QuitRequest}, v0alpha1::{pinnacle_service_server, Geometry, QuitRequest, SetOrToggle},
window::{ window::{
self, self,
v0alpha1::{ v0alpha1::{
window_service_server, AddWindowRuleRequest, CloseRequest, FullscreenOrMaximized, window_service_server, AddWindowRuleRequest, CloseRequest, FullscreenOrMaximized,
MoveGrabRequest, MoveToTagRequest, ResizeGrabRequest, SetFloatingRequest, MoveGrabRequest, MoveToTagRequest, ResizeGrabRequest, SetFloatingRequest,
SetFullscreenRequest, SetGeometryRequest, SetMaximizedRequest, SetTagRequest, SetFocusedRequest, SetFullscreenRequest, SetGeometryRequest, SetMaximizedRequest,
WindowRule, WindowRuleCondition, SetTagRequest, WindowRule, WindowRuleCondition,
}, },
}, },
}; };
@ -682,19 +682,22 @@ impl tag_service_server::TagService for TagService {
.ok_or_else(|| Status::invalid_argument("no tag specified"))?, .ok_or_else(|| Status::invalid_argument("no tag specified"))?,
); );
let set_or_toggle = match request.set_or_toggle { let set_or_toggle = request.set_or_toggle();
Some(tag::v0alpha1::set_active_request::SetOrToggle::Set(set)) => Some(set),
Some(tag::v0alpha1::set_active_request::SetOrToggle::Toggle(_)) => None, if set_or_toggle == SetOrToggle::Unspecified {
None => return Err(Status::invalid_argument("unspecified set or toggle")), return Err(Status::invalid_argument("unspecified set or toggle"));
}; }
run_unary_no_response(&self.sender, move |state| { run_unary_no_response(&self.sender, move |state| {
let Some(tag) = tag_id.tag(state) else { let Some(tag) = tag_id.tag(state) else {
return; return;
}; };
match set_or_toggle { match set_or_toggle {
Some(set) => tag.set_active(set), SetOrToggle::Set => tag.set_active(true),
None => tag.set_active(!tag.active()), SetOrToggle::Unset => tag.set_active(false),
SetOrToggle::Toggle => tag.set_active(!tag.active()),
SetOrToggle::Unspecified => unreachable!(),
} }
let Some(output) = tag.output(state) else { 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 y = output.as_ref().map(|output| output.current_location().y);
let focused = state let focused = state
.focus_state .output_focus_stack
.focused_output .current_focus()
.as_ref()
.and_then(|foc_op| output.as_ref().map(|op| op == foc_op)); .and_then(|foc_op| output.as_ref().map(|op| op == foc_op));
let tag_ids = output let tag_ids = output
@ -1197,25 +1199,30 @@ impl window_service_server::WindowService for WindowService {
.ok_or_else(|| Status::invalid_argument("no window specified"))?, .ok_or_else(|| Status::invalid_argument("no window specified"))?,
); );
let set_or_toggle = match request.set_or_toggle { let set_or_toggle = request.set_or_toggle();
Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Set(set)) => Some(set),
Some(window::v0alpha1::set_fullscreen_request::SetOrToggle::Toggle(_)) => None, if set_or_toggle == SetOrToggle::Unspecified {
None => return Err(Status::invalid_argument("unspecified set or toggle")), return Err(Status::invalid_argument("unspecified set or toggle"));
}; }
run_unary_no_response(&self.sender, move |state| { run_unary_no_response(&self.sender, move |state| {
let Some(window) = window_id.window(state) else { let Some(window) = window_id.window(state) else {
return; return;
}; };
match set_or_toggle { match set_or_toggle {
Some(set) => { SetOrToggle::Set => {
let is_fullscreen = if !window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen()) {
window.with_state(|state| state.fullscreen_or_maximized.is_fullscreen());
if set != is_fullscreen {
window.toggle_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 { 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"))?, .ok_or_else(|| Status::invalid_argument("no window specified"))?,
); );
let set_or_toggle = match request.set_or_toggle { let set_or_toggle = request.set_or_toggle();
Some(window::v0alpha1::set_maximized_request::SetOrToggle::Set(set)) => Some(set),
Some(window::v0alpha1::set_maximized_request::SetOrToggle::Toggle(_)) => None, if set_or_toggle == SetOrToggle::Unspecified {
None => return Err(Status::invalid_argument("unspecified set or toggle")), return Err(Status::invalid_argument("unspecified set or toggle"));
}; }
run_unary_no_response(&self.sender, move |state| { run_unary_no_response(&self.sender, move |state| {
let Some(window) = window_id.window(state) else { let Some(window) = window_id.window(state) else {
return; return;
}; };
match set_or_toggle { match set_or_toggle {
Some(set) => { SetOrToggle::Set => {
let is_maximized = if !window.with_state(|state| state.fullscreen_or_maximized.is_maximized()) {
window.with_state(|state| state.fullscreen_or_maximized.is_maximized());
if set != is_maximized {
window.toggle_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 { 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"))?, .ok_or_else(|| Status::invalid_argument("no window specified"))?,
); );
let set_or_toggle = match request.set_or_toggle { let set_or_toggle = request.set_or_toggle();
Some(window::v0alpha1::set_floating_request::SetOrToggle::Set(set)) => Some(set),
Some(window::v0alpha1::set_floating_request::SetOrToggle::Toggle(_)) => None, if set_or_toggle == SetOrToggle::Unspecified {
None => return Err(Status::invalid_argument("unspecified set or toggle")), return Err(Status::invalid_argument("unspecified set or toggle"));
}; }
run_unary_no_response(&self.sender, move |state| { run_unary_no_response(&self.sender, move |state| {
let Some(window) = window_id.window(state) else { let Some(window) = window_id.window(state) else {
return; return;
}; };
match set_or_toggle { match set_or_toggle {
Some(set) => { SetOrToggle::Set => {
let is_floating = if !window.with_state(|state| state.floating_or_tiled.is_floating()) {
window.with_state(|state| state.floating_or_tiled.is_floating());
if set != is_floating {
window.toggle_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 { let Some(output) = window.output(state) else {
@ -1314,6 +1331,116 @@ impl window_service_server::WindowService for WindowService {
.await .await
} }
async fn set_focused(
&self,
request: Request<SetFocusedRequest>,
) -> Result<Response<()>, 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( async fn move_to_tag(
&self, &self,
request: Request<MoveToTagRequest>, request: Request<MoveToTagRequest>,
@ -1360,11 +1487,11 @@ impl window_service_server::WindowService for WindowService {
.ok_or_else(|| Status::invalid_argument("no tag specified"))?, .ok_or_else(|| Status::invalid_argument("no tag specified"))?,
); );
let set_or_toggle = match request.set_or_toggle { let set_or_toggle = request.set_or_toggle();
Some(window::v0alpha1::set_tag_request::SetOrToggle::Set(set)) => Some(set),
Some(window::v0alpha1::set_tag_request::SetOrToggle::Toggle(_)) => None, if set_or_toggle == SetOrToggle::Unspecified {
None => return Err(Status::invalid_argument("unspecified set or toggle")), return Err(Status::invalid_argument("unspecified set or toggle"));
}; }
run_unary_no_response(&self.sender, move |state| { run_unary_no_response(&self.sender, move |state| {
let Some(window) = window_id.window(state) else { return }; 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 // TODO: turn state.tags into a hashset
match set_or_toggle { match set_or_toggle {
Some(set) => { SetOrToggle::Set => window.with_state(|state| {
if set { state.tags.retain(|tg| tg != &tag);
window.with_state(|state| { state.tags.push(tag.clone());
state.tags.retain(|tg| tg != &tag); }),
state.tags.push(tag.clone()); SetOrToggle::Unset => window.with_state(|state| {
}) state.tags.retain(|tg| tg != &tag);
} else { }),
window.with_state(|state| { SetOrToggle::Toggle => window.with_state(|state| {
state.tags.retain(|tg| tg != &tag);
})
}
}
None => window.with_state(|state| {
if !state.tags.contains(&tag) { if !state.tags.contains(&tag) {
state.tags.push(tag.clone()); state.tags.push(tag.clone());
} else { } else {
state.tags.retain(|tg| tg != &tag); state.tags.retain(|tg| tg != &tag);
} }
}), }),
SetOrToggle::Unspecified => unreachable!(),
} }
let Some(output) = tag.output(state) else { return }; let Some(output) = tag.output(state) else { return };

View file

@ -63,7 +63,7 @@ use smithay::{
backend::GlobalId, protocol::wl_surface::WlSurface, Display, DisplayHandle, 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}, wayland::dmabuf::{DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
}; };
use smithay_drm_extras::{ use smithay_drm_extras::{
@ -522,8 +522,6 @@ pub fn run_udev() -> anyhow::Result<()> {
.display_handle .display_handle
.flush_clients() .flush_clients()
.expect("failed to 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::<State>(&udev.display_handle); let global = output.create_global::<State>(&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 x = self.space.outputs().fold(0, |acc, o| {
let Some(geo) = self.space.output_geometry(o) else { let Some(geo) = self.space.output_geometry(o) else {
@ -1254,13 +1252,14 @@ impl State {
texture texture
}); });
let windows = self // let windows = self
.focus_state // .output_focus_stack
.focus_stack // .stack
.iter() // .iter()
.filter(|win| win.alive()) // .flat_map(|op| op.with_state(|state| state.focus_stack.stack.clone()))
.cloned() // .collect::<Vec<_>>();
.collect::<Vec<_>>();
let windows = self.space.elements().cloned().collect::<Vec<_>>();
let result = render_surface( let result = render_surface(
surface, surface,

View file

@ -30,7 +30,7 @@ use smithay::{
use crate::{ use crate::{
render::{pointer::PointerElement, take_presentation_feedback}, render::{pointer::PointerElement, take_presentation_feedback},
state::State, state::{State, WithState},
}; };
use super::{Backend, BackendData}; use super::{Backend, BackendData};
@ -165,7 +165,7 @@ pub fn run_winit() -> anyhow::Result<()> {
evt_loop_handle, 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(); 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)), Some(Duration::from_micros(((1.0 / 144.0) * 1000000.0) as u64)),
&mut state, &mut state,
|state| { |state| {
state.fixup_focus();
state.space.refresh(); state.space.refresh();
state.popup_manager.cleanup(); state.popup_manager.cleanup();
state state
@ -256,8 +257,6 @@ impl State {
let full_redraw = &mut winit.full_redraw; let full_redraw = &mut winit.full_redraw;
*full_redraw = full_redraw.saturating_sub(1); *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 let CursorImageStatus::Surface(surface) = &self.cursor_status {
if !surface.alive() { if !surface.alive() {
self.cursor_status = CursorImageStatus::default_named(); self.cursor_status = CursorImageStatus::default_named();
@ -269,11 +268,15 @@ impl State {
let mut pointer_element = PointerElement::<GlesTexture>::new(); let mut pointer_element = PointerElement::<GlesTexture>::new();
pointer_element.set_status(self.cursor_status.clone()); 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::<Vec<_>>();
let output_render_elements = crate::render::generate_render_elements( let output_render_elements = crate::render::generate_render_elements(
output, output,
winit.backend.renderer(), winit.backend.renderer(),
&self.space, &self.space,
&self.focus_state.focus_stack, &windows,
self.pointer_location, self.pointer_location,
&mut self.cursor_status, &mut self.cursor_status,
self.dnd_icon.as_ref(), self.dnd_icon.as_ref(),
@ -316,7 +319,7 @@ impl State {
// Send frames to the cursor surface so it updates correctly // Send frames to the cursor surface so it updates correctly
if let CursorImageStatus::Surface(surf) = &self.cursor_status { 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); send_frames_surface_tree(surf, op, time, Some(Duration::ZERO), |_, _| None);
} }
} }

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use smithay::{ use smithay::{
desktop::{LayerSurface, PopupKind, Space}, desktop::{LayerSurface, PopupKind},
input::{ input::{
keyboard::KeyboardTarget, keyboard::KeyboardTarget,
pointer::{MotionEvent, PointerTarget}, pointer::{MotionEvent, PointerTarget},
@ -18,42 +18,42 @@ use crate::{
window::WindowElement, window::WindowElement,
}; };
#[derive(Default)]
pub struct FocusState {
/// The ordering of window focus
pub focus_stack: Vec<WindowElement>,
/// The focused output, currently defined to be the one the pointer is on.
pub focused_output: Option<Output>,
}
impl State { impl State {
/// Get the currently focused window on `output` /// Get the currently focused window on `output`
/// that isn't an override redirect window, if any. /// that isn't an override redirect window, if any.
pub fn focused_window(&mut self, output: &Output) -> Option<WindowElement> { pub fn focused_window(&mut self, output: &Output) -> Option<WindowElement> {
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 windows = output.with_state(|state| {
let win_tags = win.with_state(|state| state.tags.clone()); state
let output_tags = .focus_stack
output.with_state(|state| state.focused_tags().cloned().collect::<Vec<_>>()); .stack
win_tags
.iter() .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::<Vec<_>>();
win_tags
.iter()
.any(|win_tag| output_tags.iter().any(|op_tag| win_tag == op_tag))
})
.cloned()
.collect::<Vec<_>>()
}); });
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, /// Update the keyboard focus.
/// as well as setting the keyboard focus to it.
pub fn update_focus(&mut self, output: &Output) { pub fn update_focus(&mut self, output: &Output) {
let current_focus = self.focused_window(output); let current_focus = self.focused_window(output);
if let Some(win) = &current_focus { if let Some(win) = &current_focus {
assert!(!win.is_x11_override_redirect()); assert!(!win.is_x11_override_redirect());
self.space.raise_element(win, true);
if let WindowElement::Wayland(w) = win { if let WindowElement::Wayland(w) = win {
w.toplevel().send_configure(); w.toplevel().send_configure();
} }
@ -64,33 +64,52 @@ impl State {
current_focus.map(|win| win.into()), current_focus.map(|win| win.into()),
SERIAL_COUNTER.next_serial(), 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 { /// A vector of windows, with the last one being the one in focus and the first
pub fn new() -> Self { /// being the one at the bottom of the focus stack.
Default::default() #[derive(Debug)]
} pub struct FocusStack<T> {
pub stack: Vec<T>,
focused: bool,
}
/// Set the currently focused window. impl<T> Default for FocusStack<T> {
pub fn set_focus(&mut self, window: WindowElement) { fn default() -> Self {
self.focus_stack.retain(|win| win != &window); Self {
self.focus_stack.push(window); stack: Default::default(),
} focused: Default::default(),
/// 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<WindowElement>) {
for win in self.focus_stack.iter() {
space.raise_element(win, false);
} }
} }
} }
impl<T: PartialEq> FocusStack<T> {
/// 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. /// Different focusable objects.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum FocusTarget { pub enum FocusTarget {

View file

@ -143,13 +143,15 @@ impl CompositorHandler for State {
if is_mapped { if is_mapped {
self.new_windows.retain(|win| win != &new_window); self.new_windows.retain(|win| win != &new_window);
self.windows.push(new_window.clone()); self.windows.push(new_window.clone());
self.z_index_stack.set_focus(new_window.clone());
if let (Some(output), _) | (None, Some(output)) = ( if let (Some(output), _) | (None, Some(output)) = (
&self.focus_state.focused_output, self.output_focus_stack.current_focus(),
self.space.outputs().next(), self.space.outputs().next(),
) { ) {
tracing::debug!("PLACING TOPLEVEL"); tracing::debug!("PLACING TOPLEVEL");
new_window.place_on_output(output); new_window.place_on_output(output);
output.with_state(|state| state.focus_stack.set_focus(new_window.clone()));
} }
self.space self.space
@ -157,7 +159,7 @@ impl CompositorHandler for State {
self.apply_window_rules(&new_window); 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); self.update_windows(&focused_output);
new_window.send_frame( new_window.send_frame(
&focused_output, &focused_output,
@ -414,14 +416,6 @@ impl SeatHandler for State {
} }
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&Self::KeyboardFocus>) { fn focus_changed(&mut self, seat: &Seat<Self>, 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| { let focus_client = focused.and_then(|foc_target| {
self.display_handle self.display_handle
.get_client(foc_target.wl_surface()?.id()) .get_client(foc_target.wl_surface()?.id())

View file

@ -109,12 +109,23 @@ impl XdgShellHandler for State {
.wl_surface() .wl_surface()
.is_some_and(|surf| &surf != surface.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 window
.wl_surface() .wl_surface()
.is_some_and(|surf| &surf != surface.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 { let Some(window) = self.window_for_surface(surface.wl_surface()) else {
return; return;
}; };
@ -124,7 +135,9 @@ impl XdgShellHandler for State {
let focus = self.focused_window(&output).map(FocusTarget::Window); let focus = self.focused_window(&output).map(FocusTarget::Window);
if let Some(FocusTarget::Window(win)) = &focus { if let Some(FocusTarget::Window(win)) = &focus {
tracing::debug!("Focusing on prev win"); tracing::debug!("Focusing on prev win");
// TODO:
self.space.raise_element(win, true); self.space.raise_element(win, true);
self.z_index_stack.set_focus(win.clone());
if let WindowElement::Wayland(win) = &win { if let WindowElement::Wayland(win) = &win {
win.toplevel().send_configure(); win.toplevel().send_configure();
} }
@ -142,9 +155,8 @@ impl XdgShellHandler for State {
fn new_popup(&mut self, surface: PopupSurface, mut positioner: PositionerState) { fn new_popup(&mut self, surface: PopupSurface, mut positioner: PositionerState) {
tracing::debug!(?positioner.constraint_adjustment, ?positioner.gravity); tracing::debug!(?positioner.constraint_adjustment, ?positioner.gravity);
let output_rect = self let output_rect = self
.focus_state .output_focus_stack
.focused_output .current_focus()
.as_ref()
.or_else(|| self.space.outputs().next()) .or_else(|| self.space.outputs().next())
.and_then(|op| self.space.output_geometry(op)); .and_then(|op| self.space.output_geometry(op));

View file

@ -49,17 +49,15 @@ impl XwmHandler for State {
.expect("called element_bbox on an unmapped window"); .expect("called element_bbox on an unmapped window");
let output_size = self let output_size = self
.focus_state .output_focus_stack
.focused_output .current_focus()
.as_ref()
.and_then(|op| self.space.output_geometry(op)) .and_then(|op| self.space.output_geometry(op))
.map(|geo| geo.size) .map(|geo| geo.size)
.unwrap_or((2, 2).into()); .unwrap_or((2, 2).into());
let output_loc = self let output_loc = self
.focus_state .output_focus_stack
.focused_output .current_focus()
.as_ref()
.map(|op| op.current_location()) .map(|op| op.current_location())
.unwrap_or((0, 0).into()); .unwrap_or((0, 0).into());
@ -88,7 +86,7 @@ impl XwmHandler for State {
// TODO: ssd // TODO: ssd
if let (Some(output), _) | (None, Some(output)) = ( if let (Some(output), _) | (None, Some(output)) = (
&self.focus_state.focused_output, self.output_focus_stack.current_focus(),
self.space.outputs().next(), self.space.outputs().next(),
) { ) {
window.place_on_output(output); 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.windows.push(window.clone());
self.z_index_stack.set_focus(window.clone());
self.focus_state.set_focus(window.clone());
self.apply_window_rules(&window); self.apply_window_rules(&window);
if let Some(output) = window.output(self) { if let Some(output) = window.output(self) {
output.with_state(|state| state.focus_stack.set_focus(window.clone()));
self.update_windows(&output); self.update_windows(&output);
} }
self.loop_handle.insert_idle(move |state| { self.loop_handle.insert_idle(move |state| {
state state
.seat .seat
.get_keyboard() .get_keyboard()
.expect("Seat had no keyboard") // FIXME: actually handle error .expect("Seat had no keyboard") // FIXME: actually handle error
.set_focus( .set_focus(
state, state,
Some(FocusTarget::Window(window)), Some(FocusTarget::Window(window)),
SERIAL_COUNTER.next_serial(), SERIAL_COUNTER.next_serial(),
); );
}); });
} }
@ -131,24 +130,30 @@ impl XwmHandler for State {
let loc = window.geometry().loc; let loc = window.geometry().loc;
let window = WindowElement::X11OverrideRedirect(window); let window = WindowElement::X11OverrideRedirect(window);
self.windows.push(window.clone()); self.windows.push(window.clone());
self.z_index_stack.set_focus(window.clone());
if let (Some(output), _) | (None, Some(output)) = ( if let (Some(output), _) | (None, Some(output)) = (
&self.focus_state.focused_output, self.output_focus_stack.current_focus(),
self.space.outputs().next(), self.space.outputs().next(),
) { ) {
window.place_on_output(output); 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.space.map_element(window, loc, true);
self.focus_state.set_focus(window);
} }
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) { fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
self.focus_state.focus_stack.retain(|win| { for output in self.space.outputs() {
win.wl_surface() output.with_state(|state| {
.is_some_and(|surf| Some(surf) != window.wl_surface()) state.focus_stack.stack.retain(|win| {
}); win.wl_surface()
.is_some_and(|surf| Some(surf) != window.wl_surface())
})
});
}
let win = self let win = self
.space .space
@ -157,6 +162,12 @@ impl XwmHandler for State {
.cloned(); .cloned();
if let Some(win) = win { 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); self.space.unmap_elem(&win);
if let Some(output) = win.output(self) { if let Some(output) = win.output(self) {
@ -166,6 +177,7 @@ impl XwmHandler for State {
if let Some(FocusTarget::Window(win)) = &focus { if let Some(FocusTarget::Window(win)) = &focus {
self.space.raise_element(win, true); self.space.raise_element(win, true);
self.z_index_stack.set_focus(win.clone());
if let WindowElement::Wayland(win) = &win { if let WindowElement::Wayland(win) = &win {
win.toplevel().send_configure(); win.toplevel().send_configure();
} }
@ -187,10 +199,14 @@ impl XwmHandler for State {
} }
fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) { fn destroyed_window(&mut self, _xwm: XwmId, window: X11Surface) {
self.focus_state.focus_stack.retain(|win| { for output in self.space.outputs() {
win.wl_surface() output.with_state(|state| {
.is_some_and(|surf| Some(surf) != window.wl_surface()) state.focus_stack.stack.retain(|win| {
}); win.wl_surface()
.is_some_and(|surf| Some(surf) != window.wl_surface())
})
});
}
let win = self let win = self
.windows .windows
@ -213,6 +229,10 @@ impl XwmHandler for State {
self.windows self.windows
.retain(|elem| win.wl_surface() != elem.wl_surface()); .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) { if let Some(output) = win.output(self) {
self.update_windows(&output); self.update_windows(&output);
@ -220,6 +240,7 @@ impl XwmHandler for State {
if let Some(FocusTarget::Window(win)) = &focus { if let Some(FocusTarget::Window(win)) = &focus {
self.space.raise_element(win, true); self.space.raise_element(win, true);
self.z_index_stack.set_focus(win.clone());
if let WindowElement::Wayland(win) = &win { if let WindowElement::Wayland(win) = &win {
win.toplevel().send_configure(); win.toplevel().send_configure();
} }

View file

@ -21,7 +21,7 @@ use smithay::{
}, },
reexports::input::{self, Led}, reexports::input::{self, Led},
utils::{Logical, Point, SERIAL_COUNTER}, utils::{Logical, Point, SERIAL_COUNTER},
wayland::{seat::WaylandFocus, shell::wlr_layer}, wayland::shell::wlr_layer,
}; };
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use xkbcommon::xkb::Keysym; use xkbcommon::xkb::Keysym;
@ -169,16 +169,20 @@ impl State {
let layers = layer_map_for_output(output); let layers = layer_map_for_output(output);
let top_fullscreen_window = self.focus_state.focus_stack.iter().rev().find(|win| { let top_fullscreen_window = output
win.with_state(|state| { .with_state(|state| state.focus_stack.stack.clone())
state.fullscreen_or_maximized.is_fullscreen() .into_iter()
&& output.with_state(|op_state| { .rev()
op_state .find(|win| {
.focused_tags() win.with_state(|state| {
.any(|op_tag| state.tags.contains(op_tag)) 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 { if let Some(window) = top_fullscreen_window {
Some((FocusTarget::from(window.clone()), output_geo.loc)) Some((FocusTarget::from(window.clone()), output_geo.loc))
@ -331,29 +335,20 @@ impl State {
// unfocus on windows. // unfocus on windows.
if button_state == ButtonState::Pressed { if button_state == ButtonState::Pressed {
if let Some((focus, _)) = self.focus_target_under(pointer_loc) { 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 // NOTE: *Do not* set keyboard focus to an override redirect window. This leads
// | to wonky things like right-click menus not correctly getting pointer // | to wonky things like right-click menus not correctly getting pointer
// | clicks or showing up at all. // | clicks or showing up at all.
// TODO: use update_keyboard_focus from anvil // 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!( if !matches!(
&focus, &focus,
FocusTarget::Window(WindowElement::X11OverrideRedirect(_)) FocusTarget::Window(WindowElement::X11OverrideRedirect(_))
@ -361,30 +356,23 @@ impl State {
keyboard.set_focus(self, Some(focus.clone()), serial); 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 { if let WindowElement::Wayland(window) = window {
window.toplevel().send_configure(); window.toplevel().send_configure();
} }
});
if let FocusTarget::Window(window) = &focus {
tracing::debug!("setting keyboard focus to {:?}", window.class());
} }
} else { } else {
self.space.elements().for_each(|window| match window { if let Some(focused_op) = self.output_focus_stack.current_focus() {
WindowElement::Wayland(window) => { focused_op.with_state(|state| {
window.set_activated(false); state.focus_stack.unset_focus();
window.toplevel().send_configure(); for window in state.focus_stack.stack.iter() {
} window.set_activate(false);
WindowElement::X11(surface) => { if let WindowElement::Wayland(window) = window {
surface window.toplevel().send_configure();
.set_activated(false) }
.expect("failed to deactivate x11 win"); }
// INFO: do i need to configure this? });
} }
WindowElement::X11OverrideRedirect(_) => (),
_ => unreachable!(),
});
keyboard.set_focus(self, None, serial); keyboard.set_focus(self, None, serial);
} }
}; };
@ -488,7 +476,7 @@ impl State {
self.pointer_location = pointer_loc; self.pointer_location = pointer_loc;
match self.focus_state.focused_output { match self.output_focus_stack.current_focus() {
Some(_) => { Some(_) => {
if let Some(output) = self if let Some(output) = self
.space .space
@ -496,11 +484,13 @@ impl State {
.next() .next()
.cloned() .cloned()
{ {
self.focus_state.focused_output = Some(output); self.output_focus_stack.set_focus(output);
} }
} }
None => { 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 // clamp to screen limits
// this event is never generated by winit // this event is never generated by winit
self.pointer_location = self.clamp_coords(self.pointer_location); self.pointer_location = self.clamp_coords(self.pointer_location);
match self.focus_state.focused_output {
match self.output_focus_stack.current_focus() {
Some(_) => { Some(_) => {
if let Some(output) = self if let Some(output) = self
.space .space
@ -532,11 +523,13 @@ impl State {
.next() .next()
.cloned() .cloned()
{ {
self.focus_state.focused_output = Some(output); self.output_focus_stack.set_focus(output);
} }
} }
None => { 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); pointer.frame(self);
self.schedule_render( if let Some(output) = self.output_focus_stack.current_focus().cloned() {
&self self.schedule_render(&output);
.focus_state }
.focused_output
.clone()
.expect("no focused output"),
);
} }
} }
} }

View file

@ -56,22 +56,17 @@ impl State {
return; return;
}; };
let (windows_on_foc_tags, mut windows_not_on_foc_tags): (Vec<_>, _) = let windows_on_foc_tags = output.with_state(|state| {
output.with_state(|state| { let focused_tags = state.focused_tags().collect::<Vec<_>>();
let focused_tags = state.focused_tags().collect::<Vec<_>>(); self.windows
self.windows .iter()
.iter() .filter(|win| !win.is_x11_override_redirect())
.filter(|win| !win.is_x11_override_redirect()) .filter(|win| {
.cloned() win.with_state(|state| state.tags.iter().any(|tg| focused_tags.contains(&tg)))
.partition(|win| { })
win.with_state(|state| { .cloned()
state.tags.iter().any(|tg| focused_tags.contains(&tg)) .collect::<Vec<_>>()
}) });
})
});
// 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 tiled_windows = windows_on_foc_tags let tiled_windows = windows_on_foc_tags
.iter() .iter()
@ -124,14 +119,13 @@ impl State {
WindowElement::Wayland(wl_win) => { WindowElement::Wayland(wl_win) => {
let pending = let pending =
compositor::with_states(wl_win.toplevel().wl_surface(), |states| { compositor::with_states(wl_win.toplevel().wl_surface(), |states| {
let lock = states states
.data_map .data_map
.get::<XdgToplevelSurfaceData>() .get::<XdgToplevelSurfaceData>()
.expect("XdgToplevelSurfaceData wasn't in surface's data map") .expect("XdgToplevelSurfaceData wasn't in surface's data map")
.lock() .lock()
.expect("Failed to lock Mutex<XdgToplevelSurfaceData>"); .expect("Failed to lock Mutex<XdgToplevelSurfaceData>")
.has_pending_changes()
lock.has_pending_changes()
}); });
if pending { if pending {
@ -158,6 +152,8 @@ impl State {
for (loc, window) in non_pending_wins { for (loc, window) in non_pending_wins {
self.space.map_element(window, loc, false); self.space.map_element(window, loc, false);
} }
self.fixup_focus();
} }
} }

View file

@ -5,8 +5,10 @@ use std::cell::RefCell;
use smithay::output::Output; use smithay::output::Output;
use crate::{ use crate::{
focus::FocusStack,
state::{State, WithState}, state::{State, WithState},
tag::Tag, tag::Tag,
window::WindowElement,
}; };
/// A unique identifier for an output. /// A unique identifier for an output.
@ -31,6 +33,7 @@ impl OutputName {
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct OutputState { pub struct OutputState {
pub tags: Vec<Tag>, pub tags: Vec<Tag>,
pub focus_stack: FocusStack<WindowElement>,
} }
impl WithState for Output { impl WithState for Output {

View file

@ -1,13 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use crate::{ 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, grab::resize_grab::ResizeSurfaceState, window::WindowElement,
}; };
use anyhow::Context; use anyhow::Context;
use smithay::{ use smithay::{
desktop::{PopupManager, Space}, desktop::{PopupManager, Space},
input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState}, input::{keyboard::XkbConfig, pointer::CursorImageStatus, Seat, SeatState},
output::Output,
reexports::{ reexports::{
calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction}, calloop::{generic::Generic, Interest, LoopHandle, LoopSignal, Mode, PostAction},
wayland_server::{ wayland_server::{
@ -22,8 +23,10 @@ use smithay::{
dmabuf::DmabufFeedback, dmabuf::DmabufFeedback,
fractional_scale::FractionalScaleManagerState, fractional_scale::FractionalScaleManagerState,
output::OutputManagerState, output::OutputManagerState,
selection::data_device::DataDeviceState, selection::{
selection::{primary_selection::PrimarySelectionState, wlr_data_control::DataControlState}, data_device::DataDeviceState, primary_selection::PrimarySelectionState,
wlr_data_control::DataControlState,
},
shell::{wlr_layer::WlrLayerShellState, xdg::XdgShellState}, shell::{wlr_layer::WlrLayerShellState, xdg::XdgShellState},
shm::ShmState, shm::ShmState,
socket::ListeningSocketSource, socket::ListeningSocketSource,
@ -67,8 +70,9 @@ pub struct State {
/// The state of key and mousebinds along with libinput settings /// The state of key and mousebinds along with libinput settings
pub input_state: InputState, pub input_state: InputState,
/// Keeps track of the focus stack and focused output
pub focus_state: FocusState, pub output_focus_stack: FocusStack<Output>,
pub z_index_stack: FocusStack<WindowElement>,
pub popup_manager: PopupManager, pub popup_manager: PopupManager,
@ -246,7 +250,9 @@ impl State {
data_control_state, data_control_state,
input_state: InputState::new(), input_state: InputState::new(),
focus_state: FocusState::new(),
output_focus_stack: FocusStack::default(),
z_index_stack: FocusStack::default(),
config: Config::default(), config: Config::default(),