feat: implement scratchpad window toggling
This commit is contained in:
parent
5c2f631fdb
commit
d756c8a6a2
17 changed files with 515 additions and 3 deletions
|
|
@ -640,6 +640,18 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SetWindowWorkspace { window, workspace });
|
self.send(&ClientMessage::SetWindowWorkspace { window, workspace });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn seat_send_to_scratchpad(&self, seat: Seat, name: &str) {
|
||||||
|
self.send(&ClientMessage::SeatSendToScratchpad { seat, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seat_toggle_scratchpad(&self, seat: Seat, name: &str) {
|
||||||
|
self.send(&ClientMessage::SeatToggleScratchpad { seat, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn window_send_to_scratchpad(&self, window: Window, name: &str) {
|
||||||
|
self.send(&ClientMessage::WindowSendToScratchpad { window, name });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn seat_split(&self, seat: Seat) -> Axis {
|
pub fn seat_split(&self, seat: Seat) -> Axis {
|
||||||
let res = self.send_with_response(&ClientMessage::GetSeatSplit { seat });
|
let res = self.send_with_response(&ClientMessage::GetSeatSplit { seat });
|
||||||
get_response!(res, Axis::Horizontal, GetSplit { axis });
|
get_response!(res, Axis::Horizontal, GetSplit { axis });
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,14 @@ pub enum ClientMessage<'a> {
|
||||||
seat: Seat,
|
seat: Seat,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
},
|
},
|
||||||
|
SeatSendToScratchpad {
|
||||||
|
seat: Seat,
|
||||||
|
name: &'a str,
|
||||||
|
},
|
||||||
|
SeatToggleScratchpad {
|
||||||
|
seat: Seat,
|
||||||
|
name: &'a str,
|
||||||
|
},
|
||||||
GetTimer {
|
GetTimer {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
},
|
},
|
||||||
|
|
@ -687,6 +695,10 @@ pub enum ClientMessage<'a> {
|
||||||
window: Window,
|
window: Window,
|
||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
},
|
},
|
||||||
|
WindowSendToScratchpad {
|
||||||
|
window: Window,
|
||||||
|
name: &'a str,
|
||||||
|
},
|
||||||
SetWindowFullscreen {
|
SetWindowFullscreen {
|
||||||
window: Window,
|
window: Window,
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
|
|
|
||||||
|
|
@ -466,6 +466,22 @@ impl Seat {
|
||||||
get!().set_seat_workspace(self, workspace)
|
get!().set_seat_workspace(self, workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends the currently focused window to a scratchpad.
|
||||||
|
///
|
||||||
|
/// Use an empty string for the default scratchpad.
|
||||||
|
pub fn send_to_scratchpad(self, name: &str) {
|
||||||
|
get!().seat_send_to_scratchpad(self, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles a scratchpad.
|
||||||
|
///
|
||||||
|
/// If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
||||||
|
/// most recently hidden window in the scratchpad is shown on the current workspace.
|
||||||
|
/// Use an empty string for the default scratchpad.
|
||||||
|
pub fn toggle_scratchpad(self, name: &str) {
|
||||||
|
get!().seat_toggle_scratchpad(self, name)
|
||||||
|
}
|
||||||
|
|
||||||
/// Toggles whether the currently focused window is fullscreen.
|
/// Toggles whether the currently focused window is fullscreen.
|
||||||
pub fn toggle_fullscreen(self) {
|
pub fn toggle_fullscreen(self) {
|
||||||
let c = get!();
|
let c = get!();
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,13 @@ impl Window {
|
||||||
get!().set_window_workspace(self, workspace)
|
get!().set_window_workspace(self, workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends the window to a scratchpad.
|
||||||
|
///
|
||||||
|
/// Use an empty string for the default scratchpad.
|
||||||
|
pub fn send_to_scratchpad(self, name: &str) {
|
||||||
|
get!().window_send_to_scratchpad(self, name)
|
||||||
|
}
|
||||||
|
|
||||||
/// Toggles whether the currently focused window is fullscreen.
|
/// Toggles whether the currently focused window is fullscreen.
|
||||||
pub fn toggle_fullscreen(self) {
|
pub fn toggle_fullscreen(self) {
|
||||||
self.set_fullscreen(!self.fullscreen())
|
self.set_fullscreen(!self.fullscreen())
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@ fn start_compositor2(
|
||||||
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
|
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
|
||||||
virtual_outputs: Default::default(),
|
virtual_outputs: Default::default(),
|
||||||
clean_logs_older_than: Default::default(),
|
clean_logs_older_than: Default::default(),
|
||||||
|
scratchpads: Default::default(),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
|
||||||
|
|
@ -1100,6 +1100,24 @@ impl ConfigProxyHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_seat_send_to_scratchpad(&self, seat: Seat, name: &str) -> Result<(), CphError> {
|
||||||
|
self.state.with_linear_layout_animations(|| {
|
||||||
|
let seat = self.get_seat(seat)?;
|
||||||
|
if let Some(toplevel) = seat.get_keyboard_node().node_toplevel() {
|
||||||
|
self.state.send_to_scratchpad(name, toplevel);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_seat_toggle_scratchpad(&self, seat: Seat, name: &str) -> Result<(), CphError> {
|
||||||
|
self.state.with_linear_layout_animations(|| {
|
||||||
|
let seat = self.get_seat(seat)?;
|
||||||
|
self.state.toggle_scratchpad(&seat, name);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_set_window_workspace(&self, window: Window, ws: Workspace) -> Result<(), CphError> {
|
fn handle_set_window_workspace(&self, window: Window, ws: Workspace) -> Result<(), CphError> {
|
||||||
let window = self.get_window(window)?;
|
let window = self.get_window(window)?;
|
||||||
let name = self.get_workspace(ws)?;
|
let name = self.get_workspace(ws)?;
|
||||||
|
|
@ -1114,6 +1132,14 @@ impl ConfigProxyHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_window_send_to_scratchpad(&self, window: Window, name: &str) -> Result<(), CphError> {
|
||||||
|
self.state.with_linear_layout_animations(|| {
|
||||||
|
let window = self.get_window(window)?;
|
||||||
|
self.state.send_to_scratchpad(name, window);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_get_device_name(&self, device: InputDevice) -> Result<(), CphError> {
|
fn handle_get_device_name(&self, device: InputDevice) -> Result<(), CphError> {
|
||||||
let dev = self.get_device_handler_data(device)?;
|
let dev = self.get_device_handler_data(device)?;
|
||||||
let name = dev.device.name();
|
let name = dev.device.name();
|
||||||
|
|
@ -2989,6 +3015,12 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SetSeatWorkspace { seat, workspace } => self
|
ClientMessage::SetSeatWorkspace { seat, workspace } => self
|
||||||
.handle_set_seat_workspace(seat, workspace)
|
.handle_set_seat_workspace(seat, workspace)
|
||||||
.wrn("set_seat_workspace")?,
|
.wrn("set_seat_workspace")?,
|
||||||
|
ClientMessage::SeatSendToScratchpad { seat, name } => self
|
||||||
|
.handle_seat_send_to_scratchpad(seat, name)
|
||||||
|
.wrn("seat_send_to_scratchpad")?,
|
||||||
|
ClientMessage::SeatToggleScratchpad { seat, name } => self
|
||||||
|
.handle_seat_toggle_scratchpad(seat, name)
|
||||||
|
.wrn("seat_toggle_scratchpad")?,
|
||||||
ClientMessage::GetConnector { ty, idx } => {
|
ClientMessage::GetConnector { ty, idx } => {
|
||||||
self.handle_get_connector(ty, idx).wrn("get_connector")?
|
self.handle_get_connector(ty, idx).wrn("get_connector")?
|
||||||
}
|
}
|
||||||
|
|
@ -3373,6 +3405,9 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SetWindowWorkspace { window, workspace } => self
|
ClientMessage::SetWindowWorkspace { window, workspace } => self
|
||||||
.handle_set_window_workspace(window, workspace)
|
.handle_set_window_workspace(window, workspace)
|
||||||
.wrn("set_window_workspace")?,
|
.wrn("set_window_workspace")?,
|
||||||
|
ClientMessage::WindowSendToScratchpad { window, name } => self
|
||||||
|
.handle_window_send_to_scratchpad(window, name)
|
||||||
|
.wrn("window_send_to_scratchpad")?,
|
||||||
ClientMessage::SetWindowFullscreen { window, fullscreen } => self
|
ClientMessage::SetWindowFullscreen { window, fullscreen } => self
|
||||||
.handle_set_window_fullscreen(window, fullscreen)
|
.handle_set_window_fullscreen(window, fullscreen)
|
||||||
.wrn("set_window_fullscreen")?,
|
.wrn("set_window_fullscreen")?,
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,20 @@ impl TestConfig {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_to_scratchpad(&self, seat: SeatId, name: &str) -> TestResult {
|
||||||
|
self.send(ClientMessage::SeatSendToScratchpad {
|
||||||
|
seat: Seat(seat.raw() as _),
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_scratchpad(&self, seat: SeatId, name: &str) -> TestResult {
|
||||||
|
self.send(ClientMessage::SeatToggleScratchpad {
|
||||||
|
seat: Seat(seat.raw() as _),
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn clear(&self) {
|
fn clear(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some(srv) = self.srv.take() {
|
if let Some(srv) = self.srv.take() {
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ mod t0052_bar;
|
||||||
mod t0053_theme;
|
mod t0053_theme;
|
||||||
mod t0054_subsurface_already_attached;
|
mod t0054_subsurface_already_attached;
|
||||||
mod t0055_autotiling;
|
mod t0055_autotiling;
|
||||||
|
mod t0055_scratchpad;
|
||||||
|
|
||||||
pub trait TestCase: Sync {
|
pub trait TestCase: Sync {
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
@ -160,5 +161,6 @@ pub fn tests() -> Vec<&'static dyn TestCase> {
|
||||||
t0053_theme,
|
t0053_theme,
|
||||||
t0054_subsurface_already_attached,
|
t0054_subsurface_already_attached,
|
||||||
t0055_autotiling,
|
t0055_autotiling,
|
||||||
|
t0055_scratchpad,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
50
src/it/tests/t0055_scratchpad.rs
Normal file
50
src/it/tests/t0055_scratchpad.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
it::{test_error::TestResult, testrun::TestRun},
|
||||||
|
tree::Node,
|
||||||
|
},
|
||||||
|
std::rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
|
testcase!();
|
||||||
|
|
||||||
|
async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
|
let ds = run.create_default_setup().await?;
|
||||||
|
|
||||||
|
let client = run.create_client().await?;
|
||||||
|
let win1 = client.create_window().await?;
|
||||||
|
win1.map2().await?;
|
||||||
|
let win2 = client.create_window().await?;
|
||||||
|
win2.map2().await?;
|
||||||
|
|
||||||
|
run.cfg.send_to_scratchpad(ds.seat.id(), "term")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(win1.tl.server.node_visible());
|
||||||
|
tassert!(!win2.tl.server.node_visible());
|
||||||
|
|
||||||
|
run.cfg.show_workspace(ds.seat.id(), "2")?;
|
||||||
|
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(win2.tl.server.node_visible());
|
||||||
|
tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "2");
|
||||||
|
|
||||||
|
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(!win2.tl.server.node_visible());
|
||||||
|
|
||||||
|
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(win2.tl.server.node_visible());
|
||||||
|
tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "2");
|
||||||
|
|
||||||
|
run.cfg.show_workspace(ds.seat.id(), "3")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(!win2.tl.server.node_visible());
|
||||||
|
|
||||||
|
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(win2.tl.server.node_visible());
|
||||||
|
tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "3");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
147
src/state.rs
147
src/state.rs
|
|
@ -114,9 +114,11 @@ use {
|
||||||
tree::{
|
tree::{
|
||||||
ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode,
|
ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode,
|
||||||
FoundNode, LatchListener, Node, NodeId, NodeIds, NodeVisitorBase, OutputNode,
|
FoundNode, LatchListener, Node, NodeId, NodeIds, NodeVisitorBase, OutputNode,
|
||||||
PlaceholderNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier,
|
PlaceholderNode, ScratchpadToplevelState, TearingMode, TileState, ToplevelData,
|
||||||
ToplevelNode, ToplevelNodeBase, Transform, VrrMode, WorkspaceDisplayOrder,
|
ToplevelIdentifier, ToplevelNode, ToplevelNodeBase, Transform, VrrMode,
|
||||||
WorkspaceNode, WorkspaceNodeId, WsMoveConfig, generic_node_visitor, move_ws_to_output,
|
WorkspaceDisplayOrder, WorkspaceNode, WorkspaceNodeId, WsMoveConfig,
|
||||||
|
generic_node_visitor, move_ws_to_output, toplevel_hide_for_scratchpad,
|
||||||
|
toplevel_restore_from_scratchpad, toplevel_set_workspace,
|
||||||
},
|
},
|
||||||
udmabuf::UdmabufHolder,
|
udmabuf::UdmabufHolder,
|
||||||
utils::{
|
utils::{
|
||||||
|
|
@ -412,6 +414,7 @@ pub struct State {
|
||||||
pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
|
pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
|
||||||
pub virtual_outputs: VirtualOutputs,
|
pub virtual_outputs: VirtualOutputs,
|
||||||
pub clean_logs_older_than: Cell<Option<SystemTime>>,
|
pub clean_logs_older_than: Cell<Option<SystemTime>>,
|
||||||
|
pub scratchpads: RefCell<AHashMap<String, Vec<Rc<ScratchpadEntry>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
@ -459,6 +462,28 @@ pub struct IdleState {
|
||||||
pub in_grace_period: Cell<bool>,
|
pub in_grace_period: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ScratchpadEntry {
|
||||||
|
node: Weak<dyn ToplevelNode>,
|
||||||
|
identifier: ToplevelIdentifier,
|
||||||
|
hidden: Cell<bool>,
|
||||||
|
restore: RefCell<Option<ScratchpadToplevelState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScratchpadEntry {
|
||||||
|
fn alive(&self) -> bool {
|
||||||
|
self.node().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node(&self) -> Option<Rc<dyn ToplevelNode>> {
|
||||||
|
let node = self.node.upgrade()?;
|
||||||
|
if node.tl_data().identifier.get() == self.identifier {
|
||||||
|
Some(node)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IdleState {
|
impl IdleState {
|
||||||
pub fn set_timeout(&self, state: &State, timeout: Duration) {
|
pub fn set_timeout(&self, state: &State, timeout: Duration) {
|
||||||
self.timeout.set(timeout);
|
self.timeout.set(timeout);
|
||||||
|
|
@ -1023,6 +1048,121 @@ impl State {
|
||||||
float
|
float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_to_scratchpad(self: &Rc<Self>, name: &str, node: Rc<dyn ToplevelNode>) {
|
||||||
|
if node.node_is_placeholder() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let identifier = node.tl_data().identifier.get();
|
||||||
|
let entry = Rc::new(ScratchpadEntry {
|
||||||
|
node: Rc::downgrade(&node),
|
||||||
|
identifier,
|
||||||
|
hidden: Cell::new(false),
|
||||||
|
restore: Default::default(),
|
||||||
|
});
|
||||||
|
let Some(restore) = toplevel_hide_for_scratchpad(node) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
*entry.restore.borrow_mut() = Some(restore);
|
||||||
|
entry.hidden.set(true);
|
||||||
|
{
|
||||||
|
let mut scratchpads = self.scratchpads.borrow_mut();
|
||||||
|
for entries in scratchpads.values_mut() {
|
||||||
|
entries.retain(|entry| entry.alive() && entry.identifier != identifier);
|
||||||
|
}
|
||||||
|
scratchpads
|
||||||
|
.entry(name.to_string())
|
||||||
|
.or_default()
|
||||||
|
.push(entry.clone());
|
||||||
|
}
|
||||||
|
self.tree_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_scratchpad(self: &Rc<Self>, seat: &Rc<WlSeatGlobal>, name: &str) {
|
||||||
|
let entry = {
|
||||||
|
let mut scratchpads = self.scratchpads.borrow_mut();
|
||||||
|
let Some(entries) = scratchpads.get_mut(name) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
entries.retain(|entry| entry.alive());
|
||||||
|
entries
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|entry| {
|
||||||
|
!entry.hidden.get() && entry.node().is_some_and(|node| node.node_visible())
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.or_else(|| {
|
||||||
|
entries
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|entry| {
|
||||||
|
entry.hidden.get()
|
||||||
|
|| entry.node().is_some_and(|node| !node.node_visible())
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let Some(entry) = entry else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if entry.hidden.get() {
|
||||||
|
self.show_scratchpad_entry(seat, &entry);
|
||||||
|
} else if entry.node().is_some_and(|node| !node.node_visible()) {
|
||||||
|
self.move_scratchpad_entry_to_current_workspace(seat, &entry);
|
||||||
|
} else {
|
||||||
|
self.hide_scratchpad_entry(&entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_scratchpad_entry(self: &Rc<Self>, entry: &Rc<ScratchpadEntry>) {
|
||||||
|
let Some(node) = entry.node() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(restore) = toplevel_hide_for_scratchpad(node) {
|
||||||
|
*entry.restore.borrow_mut() = Some(restore);
|
||||||
|
entry.hidden.set(true);
|
||||||
|
self.tree_changed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_scratchpad_entry(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
entry: &Rc<ScratchpadEntry>,
|
||||||
|
) {
|
||||||
|
if !entry.hidden.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(node) = entry.node() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let restore = entry.restore.borrow();
|
||||||
|
let Some(restore) = restore.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ws = seat.get_fallback_output().ensure_workspace();
|
||||||
|
toplevel_restore_from_scratchpad(self, node.clone(), &ws, restore);
|
||||||
|
entry.hidden.set(false);
|
||||||
|
node.node_do_focus(seat, Direction::Unspecified);
|
||||||
|
seat.maybe_schedule_warp_mouse_to_focus();
|
||||||
|
self.tree_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_scratchpad_entry_to_current_workspace(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
entry: &Rc<ScratchpadEntry>,
|
||||||
|
) {
|
||||||
|
let Some(node) = entry.node() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let ws = seat.get_fallback_output().ensure_workspace();
|
||||||
|
toplevel_set_workspace(self, node.clone(), &ws);
|
||||||
|
node.node_do_focus(seat, Direction::Unspecified);
|
||||||
|
seat.maybe_schedule_warp_mouse_to_focus();
|
||||||
|
self.tree_changed();
|
||||||
|
}
|
||||||
|
|
||||||
fn focus_after_map(&self, node: Rc<dyn ToplevelNode>, seat: Option<&Rc<WlSeatGlobal>>) {
|
fn focus_after_map(&self, node: Rc<dyn ToplevelNode>, seat: Option<&Rc<WlSeatGlobal>>) {
|
||||||
if !node.node_visible() {
|
if !node.node_visible() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1298,6 +1438,7 @@ impl State {
|
||||||
self.node_at_tree.borrow_mut().clear();
|
self.node_at_tree.borrow_mut().clear();
|
||||||
self.position_hint_requests.clear();
|
self.position_hint_requests.clear();
|
||||||
self.pending_warp_mouse_to_focus.clear();
|
self.pending_warp_mouse_to_focus.clear();
|
||||||
|
self.scratchpads.borrow_mut().clear();
|
||||||
self.head_managers.clear();
|
self.head_managers.clear();
|
||||||
self.head_managers_async.clear();
|
self.head_managers_async.clear();
|
||||||
self.const_40hz_latch.clear();
|
self.const_40hz_latch.clear();
|
||||||
|
|
|
||||||
|
|
@ -1323,3 +1323,64 @@ pub fn toplevel_set_workspace(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, ws: &
|
||||||
tl.tl_set_fullscreen(true, Some(ws.clone()));
|
tl.tl_set_fullscreen(true, Some(ws.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ScratchpadToplevelState {
|
||||||
|
pub floating: bool,
|
||||||
|
pub fullscreen: bool,
|
||||||
|
pub workspace: Option<Rc<WorkspaceNode>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toplevel_hide_for_scratchpad(tl: Rc<dyn ToplevelNode>) -> Option<ScratchpadToplevelState> {
|
||||||
|
if tl.node_is_placeholder() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let data = tl.tl_data();
|
||||||
|
let scratchpad_state = ScratchpadToplevelState {
|
||||||
|
floating: data.parent_is_float.get(),
|
||||||
|
fullscreen: data.is_fullscreen.get(),
|
||||||
|
workspace: data.workspace.get(),
|
||||||
|
};
|
||||||
|
if data.is_fullscreen.get() {
|
||||||
|
tl.clone().tl_set_fullscreen(false, None);
|
||||||
|
if data.is_fullscreen.get() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let parent = data.parent.get()?;
|
||||||
|
let kb_foci = collect_kb_foci(tl.clone());
|
||||||
|
parent.cnode_remove_child2(&*tl, true);
|
||||||
|
data.parent.take();
|
||||||
|
data.float.take();
|
||||||
|
if data.parent_is_float.replace(false) {
|
||||||
|
data.property_changed(TL_CHANGED_FLOATING);
|
||||||
|
}
|
||||||
|
if data.workspace.take().is_some() {
|
||||||
|
data.property_changed(TL_CHANGED_WORKSPACE);
|
||||||
|
}
|
||||||
|
tl.tl_set_visible(false);
|
||||||
|
if let Some(workspace) = &scratchpad_state.workspace {
|
||||||
|
for seat in kb_foci {
|
||||||
|
workspace
|
||||||
|
.clone()
|
||||||
|
.node_do_focus(&seat, Direction::Unspecified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(scratchpad_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toplevel_restore_from_scratchpad(
|
||||||
|
state: &Rc<State>,
|
||||||
|
tl: Rc<dyn ToplevelNode>,
|
||||||
|
ws: &Rc<WorkspaceNode>,
|
||||||
|
scratchpad_state: &ScratchpadToplevelState,
|
||||||
|
) {
|
||||||
|
if scratchpad_state.floating {
|
||||||
|
let (width, height) = tl.tl_data().float_size(ws);
|
||||||
|
state.map_floating(tl.clone(), width, height, ws, None);
|
||||||
|
} else {
|
||||||
|
state.map_tiled_on(tl.clone(), ws);
|
||||||
|
}
|
||||||
|
if scratchpad_state.fullscreen && ws.fullscreen.is_none() {
|
||||||
|
tl.tl_set_fullscreen(true, Some(ws.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,8 @@ pub enum SimpleCommand {
|
||||||
SetFloating(bool),
|
SetFloating(bool),
|
||||||
ToggleFullscreen,
|
ToggleFullscreen,
|
||||||
SetFullscreen(bool),
|
SetFullscreen(bool),
|
||||||
|
SendToScratchpad,
|
||||||
|
ToggleScratchpad,
|
||||||
Forward(bool),
|
Forward(bool),
|
||||||
EnableWindowManagement(bool),
|
EnableWindowManagement(bool),
|
||||||
SetFloatAboveFullscreen(bool),
|
SetFloatAboveFullscreen(bool),
|
||||||
|
|
@ -130,6 +132,12 @@ pub enum Action {
|
||||||
MoveToWorkspace {
|
MoveToWorkspace {
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
SendToScratchpad {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
ToggleScratchpad {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
Multi {
|
Multi {
|
||||||
actions: Vec<Action>,
|
actions: Vec<Action>,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,8 @@ impl ActionParser<'_> {
|
||||||
"toggle-fullscreen" => ToggleFullscreen,
|
"toggle-fullscreen" => ToggleFullscreen,
|
||||||
"enter-fullscreen" => SetFullscreen(true),
|
"enter-fullscreen" => SetFullscreen(true),
|
||||||
"exit-fullscreen" => SetFullscreen(false),
|
"exit-fullscreen" => SetFullscreen(false),
|
||||||
|
"send-to-scratchpad" => SendToScratchpad,
|
||||||
|
"toggle-scratchpad" => ToggleScratchpad,
|
||||||
"focus-parent" => FocusParent,
|
"focus-parent" => FocusParent,
|
||||||
"close" => Close,
|
"close" => Close,
|
||||||
"disable-pointer-constraint" => DisablePointerConstraint,
|
"disable-pointer-constraint" => DisablePointerConstraint,
|
||||||
|
|
@ -222,6 +224,24 @@ impl ActionParser<'_> {
|
||||||
Ok(Action::MoveToWorkspace { name })
|
Ok(Action::MoveToWorkspace { name })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_send_to_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let name = ext
|
||||||
|
.extract(opt(str("name")))?
|
||||||
|
.map(|name| name.value)
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
Ok(Action::SendToScratchpad { name })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_toggle_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let name = ext
|
||||||
|
.extract(opt(str("name")))?
|
||||||
|
.map(|name| name.value)
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
Ok(Action::ToggleScratchpad { name })
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
let con = ext
|
let con = ext
|
||||||
.extract(val("connector"))?
|
.extract(val("connector"))?
|
||||||
|
|
@ -551,6 +571,8 @@ impl Parser for ActionParser<'_> {
|
||||||
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
|
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
|
||||||
"show-workspace" => self.parse_show_workspace(&mut ext),
|
"show-workspace" => self.parse_show_workspace(&mut ext),
|
||||||
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
|
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
|
||||||
|
"send-to-scratchpad" => self.parse_send_to_scratchpad(&mut ext),
|
||||||
|
"toggle-scratchpad" => self.parse_toggle_scratchpad(&mut ext),
|
||||||
"configure-connector" => self.parse_configure_connector(&mut ext),
|
"configure-connector" => self.parse_configure_connector(&mut ext),
|
||||||
"configure-input" => self.parse_configure_input(&mut ext),
|
"configure-input" => self.parse_configure_input(&mut ext),
|
||||||
"configure-output" => self.parse_configure_output(&mut ext),
|
"configure-output" => self.parse_configure_output(&mut ext),
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,8 @@ impl Action {
|
||||||
SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)),
|
SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)),
|
||||||
SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()),
|
SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()),
|
||||||
SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)),
|
SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)),
|
||||||
|
SimpleCommand::SendToScratchpad => window_or_seat!(s, s.send_to_scratchpad("")),
|
||||||
|
SimpleCommand::ToggleScratchpad => b.new(move || s.toggle_scratchpad("")),
|
||||||
SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
|
SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
|
||||||
SimpleCommand::Close => window_or_seat!(s, s.close()),
|
SimpleCommand::Close => window_or_seat!(s, s.close()),
|
||||||
SimpleCommand::DisablePointerConstraint => {
|
SimpleCommand::DisablePointerConstraint => {
|
||||||
|
|
@ -306,6 +308,8 @@ impl Action {
|
||||||
let workspace = get_workspace(&name);
|
let workspace = get_workspace(&name);
|
||||||
window_or_seat!(s, s.set_workspace(workspace))
|
window_or_seat!(s, s.set_workspace(workspace))
|
||||||
}
|
}
|
||||||
|
Action::SendToScratchpad { name } => window_or_seat!(s, s.send_to_scratchpad(&name)),
|
||||||
|
Action::ToggleScratchpad { name } => b.new(move || s.toggle_scratchpad(&name)),
|
||||||
Action::ConfigureConnector { con } => b.new(move || {
|
Action::ConfigureConnector { con } => b.new(move || {
|
||||||
for c in connectors() {
|
for c in connectors() {
|
||||||
if con.match_.matches(c) {
|
if con.match_.matches(c) {
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,38 @@
|
||||||
"name"
|
"name"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Sends the currently focused window to a scratchpad and hides it.\n\nIf `name` is omitted, the default scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-shift-minus = { type = \"send-to-scratchpad\", name = \"terminal\" }\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "send-to-scratchpad"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the scratchpad."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Toggles a scratchpad.\n\nIf the scratchpad has a visible window, that window is hidden. Otherwise, the\nmost recently hidden window in the scratchpad is shown on the current workspace.\nIf `name` is omitted, the default scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-minus = { type = \"toggle-scratchpad\", name = \"terminal\" }\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "toggle-scratchpad"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the scratchpad."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Moves a workspace to a different output.\n\n- Example 1: Move a specific workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", workspace = \"1\", output.name = \"right\" }\n ```\n\n- Example 2: Move the current workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", output.name = \"right\" }\n ```\n\n- Example 3: Move the current workspace to the output on the right (directional)\n\n ```toml\n [shortcuts]\n \"logo+ctrl+shift+Right\" = { type = \"move-to-output\", direction = \"right\" }\n \"logo+ctrl+shift+Left\" = { type = \"move-to-output\", direction = \"left\" }\n \"logo+ctrl+shift+Up\" = { type = \"move-to-output\", direction = \"up\" }\n \"logo+ctrl+shift+Down\" = { type = \"move-to-output\", direction = \"down\" }\n ```\n",
|
"description": "Moves a workspace to a different output.\n\n- Example 1: Move a specific workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", workspace = \"1\", output.name = \"right\" }\n ```\n\n- Example 2: Move the current workspace to a named output\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", output.name = \"right\" }\n ```\n\n- Example 3: Move the current workspace to the output on the right (directional)\n\n ```toml\n [shortcuts]\n \"logo+ctrl+shift+Right\" = { type = \"move-to-output\", direction = \"right\" }\n \"logo+ctrl+shift+Left\" = { type = \"move-to-output\", direction = \"left\" }\n \"logo+ctrl+shift+Up\" = { type = \"move-to-output\", direction = \"up\" }\n \"logo+ctrl+shift+Down\" = { type = \"move-to-output\", direction = \"down\" }\n ```\n",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -2078,6 +2110,8 @@
|
||||||
"toggle-fullscreen",
|
"toggle-fullscreen",
|
||||||
"enter-fullscreen",
|
"enter-fullscreen",
|
||||||
"exit-fullscreen",
|
"exit-fullscreen",
|
||||||
|
"send-to-scratchpad",
|
||||||
|
"toggle-scratchpad",
|
||||||
"focus-parent",
|
"focus-parent",
|
||||||
"close",
|
"close",
|
||||||
"disable-pointer-constraint",
|
"disable-pointer-constraint",
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,50 @@ This table is a tagged union. The variant is determined by the `type` field. It
|
||||||
|
|
||||||
The value of this field should be a string.
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
- `send-to-scratchpad`:
|
||||||
|
|
||||||
|
Sends the currently focused window to a scratchpad and hides it.
|
||||||
|
|
||||||
|
If `name` is omitted, the default scratchpad is used.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-shift-minus = { type = "send-to-scratchpad", name = "terminal" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `name` (optional):
|
||||||
|
|
||||||
|
The name of the scratchpad.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
- `toggle-scratchpad`:
|
||||||
|
|
||||||
|
Toggles a scratchpad.
|
||||||
|
|
||||||
|
If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
||||||
|
most recently hidden window in the scratchpad is shown on the current workspace.
|
||||||
|
If `name` is omitted, the default scratchpad is used.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-minus = { type = "toggle-scratchpad", name = "terminal" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `name` (optional):
|
||||||
|
|
||||||
|
The name of the scratchpad.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
- `move-to-output`:
|
- `move-to-output`:
|
||||||
|
|
||||||
Moves a workspace to a different output.
|
Moves a workspace to a different output.
|
||||||
|
|
@ -1007,6 +1051,7 @@ The string should have one of the following values:
|
||||||
supported plan exists.
|
supported plan exists.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Animations"></a>
|
<a name="types-Animations"></a>
|
||||||
### `Animations`
|
### `Animations`
|
||||||
|
|
||||||
|
|
@ -4649,6 +4694,14 @@ The string should have one of the following values:
|
||||||
|
|
||||||
Makes the currently focused window windowed.
|
Makes the currently focused window windowed.
|
||||||
|
|
||||||
|
- `send-to-scratchpad`:
|
||||||
|
|
||||||
|
Sends the currently focused window to the default scratchpad.
|
||||||
|
|
||||||
|
- `toggle-scratchpad`:
|
||||||
|
|
||||||
|
Toggles the default scratchpad.
|
||||||
|
|
||||||
- `focus-parent`:
|
- `focus-parent`:
|
||||||
|
|
||||||
Focus the parent of the currently focused window.
|
Focus the parent of the currently focused window.
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,42 @@ Action:
|
||||||
description: The name of the workspace.
|
description: The name of the workspace.
|
||||||
required: true
|
required: true
|
||||||
kind: string
|
kind: string
|
||||||
|
send-to-scratchpad:
|
||||||
|
description: |
|
||||||
|
Sends the currently focused window to a scratchpad and hides it.
|
||||||
|
|
||||||
|
If `name` is omitted, the default scratchpad is used.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-shift-minus = { type = "send-to-scratchpad", name = "terminal" }
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
description: The name of the scratchpad.
|
||||||
|
required: false
|
||||||
|
kind: string
|
||||||
|
toggle-scratchpad:
|
||||||
|
description: |
|
||||||
|
Toggles a scratchpad.
|
||||||
|
|
||||||
|
If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
||||||
|
most recently hidden window in the scratchpad is shown on the current workspace.
|
||||||
|
If `name` is omitted, the default scratchpad is used.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-minus = { type = "toggle-scratchpad", name = "terminal" }
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
description: The name of the scratchpad.
|
||||||
|
required: false
|
||||||
|
kind: string
|
||||||
move-to-output:
|
move-to-output:
|
||||||
description: |
|
description: |
|
||||||
Moves a workspace to a different output.
|
Moves a workspace to a different output.
|
||||||
|
|
@ -1076,6 +1112,10 @@ SimpleActionName:
|
||||||
description: Makes the currently focused window fullscreen.
|
description: Makes the currently focused window fullscreen.
|
||||||
- value: exit-fullscreen
|
- value: exit-fullscreen
|
||||||
description: Makes the currently focused window windowed.
|
description: Makes the currently focused window windowed.
|
||||||
|
- value: send-to-scratchpad
|
||||||
|
description: Sends the currently focused window to the default scratchpad.
|
||||||
|
- value: toggle-scratchpad
|
||||||
|
description: Toggles the default scratchpad.
|
||||||
- value: focus-parent
|
- value: focus-parent
|
||||||
description: Focus the parent of the currently focused window.
|
description: Focus the parent of the currently focused window.
|
||||||
- value: close
|
- value: close
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue