mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-25 09:59:21 +01:00
Merge pull request #166 from pinnacle-comp/focus_rework
Rework focus and add `Window.set_focused` Also omg I need tests, where are the tests
This commit is contained in:
commit
ba90e44711
22 changed files with 609 additions and 306 deletions
|
@ -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)
|
||||
|
|
|
@ -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 .. " "
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
})));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
252
src/api.rs
252
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<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(
|
||||
&self,
|
||||
request: Request<MoveToTagRequest>,
|
||||
|
@ -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 };
|
||||
|
@ -1575,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
|
||||
|
|
|
@ -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::{
|
||||
|
@ -516,14 +516,13 @@ 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
|
||||
.display_handle
|
||||
.flush_clients()
|
||||
.expect("failed to flush_clients");
|
||||
|
||||
state.focus_state.fix_up_focus(&mut state.space);
|
||||
},
|
||||
)?;
|
||||
|
||||
|
@ -874,7 +873,7 @@ impl State {
|
|||
);
|
||||
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 Some(geo) = self.space.output_geometry(o) else {
|
||||
|
@ -1254,13 +1253,7 @@ impl State {
|
|||
texture
|
||||
});
|
||||
|
||||
let windows = self
|
||||
.focus_state
|
||||
.focus_stack
|
||||
.iter()
|
||||
.filter(|win| win.alive())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let windows = self.space.elements().cloned().collect::<Vec<_>>();
|
||||
|
||||
let result = render_surface(
|
||||
surface,
|
||||
|
|
|
@ -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::<GlesTexture>::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::<Vec<_>>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
100
src/focus.rs
100
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,43 @@ use crate::{
|
|||
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 {
|
||||
/// 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<WindowElement> {
|
||||
self.focus_state.focus_stack.retain(|win| win.alive());
|
||||
pub fn focused_window(&self, output: &Output) -> Option<WindowElement> {
|
||||
// TODO: see if the below is necessary
|
||||
// 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::<Vec<_>>());
|
||||
|
||||
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::<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,
|
||||
/// 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 +65,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<T> {
|
||||
pub stack: Vec<T>,
|
||||
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<WindowElement>) {
|
||||
for win in self.focus_stack.iter() {
|
||||
space.raise_element(win, false);
|
||||
impl<T> Default for FocusStack<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stack: Default::default(),
|
||||
focused: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FocusTarget {
|
||||
|
|
|
@ -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<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| {
|
||||
self.display_handle
|
||||
.get_client(foc_target.wl_surface()?.id())
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
109
src/input.rs
109
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>()
|
||||
});
|
||||
|
||||
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::<XdgToplevelSurfaceData>()
|
||||
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
||||
.lock()
|
||||
.expect("Failed to lock Mutex<XdgToplevelSurfaceData>");
|
||||
|
||||
lock.has_pending_changes()
|
||||
.expect("Failed to lock Mutex<XdgToplevelSurfaceData>")
|
||||
.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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Tag>,
|
||||
pub focus_stack: FocusStack<WindowElement>,
|
||||
}
|
||||
|
||||
impl WithState for Output {
|
||||
|
|
18
src/state.rs
18
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<Output>,
|
||||
pub z_index_stack: FocusStack<WindowElement>,
|
||||
|
||||
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(),
|
||||
|
||||
|
|
Loading…
Reference in a new issue