feat: implement declarative scratchpads
This commit is contained in:
parent
bd715e8af5
commit
e3c323c296
17 changed files with 549 additions and 78 deletions
|
|
@ -648,6 +648,10 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SeatToggleScratchpad { seat, name });
|
self.send(&ClientMessage::SeatToggleScratchpad { seat, name });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn seat_cycle_scratchpad(&self, seat: Seat, name: &str) {
|
||||||
|
self.send(&ClientMessage::SeatCycleScratchpad { seat, name });
|
||||||
|
}
|
||||||
|
|
||||||
pub fn window_send_to_scratchpad(&self, window: Window, name: &str) {
|
pub fn window_send_to_scratchpad(&self, window: Window, name: &str) {
|
||||||
self.send(&ClientMessage::WindowSendToScratchpad { window, name });
|
self.send(&ClientMessage::WindowSendToScratchpad { window, name });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,10 @@ pub enum ClientMessage<'a> {
|
||||||
seat: Seat,
|
seat: Seat,
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
},
|
},
|
||||||
|
SeatCycleScratchpad {
|
||||||
|
seat: Seat,
|
||||||
|
name: &'a str,
|
||||||
|
},
|
||||||
GetTimer {
|
GetTimer {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -477,11 +477,22 @@ impl Seat {
|
||||||
///
|
///
|
||||||
/// If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
/// 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.
|
/// most recently hidden window in the scratchpad is shown on the current workspace.
|
||||||
|
/// Scratchpad windows are always shown floating.
|
||||||
/// Use an empty string for the default scratchpad.
|
/// Use an empty string for the default scratchpad.
|
||||||
pub fn toggle_scratchpad(self, name: &str) {
|
pub fn toggle_scratchpad(self, name: &str) {
|
||||||
get!().seat_toggle_scratchpad(self, name)
|
get!().seat_toggle_scratchpad(self, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cycles through the windows of a scratchpad, one at a time.
|
||||||
|
///
|
||||||
|
/// With nothing shown, the first window is brought up; each further invocation
|
||||||
|
/// hides the current window and shows the next; after the last window the
|
||||||
|
/// scratchpad is hidden again. Scratchpad windows are always shown floating.
|
||||||
|
/// Use an empty string for the default scratchpad.
|
||||||
|
pub fn cycle_scratchpad(self, name: &str) {
|
||||||
|
get!().seat_cycle_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!();
|
||||||
|
|
|
||||||
|
|
@ -1118,6 +1118,14 @@ impl ConfigProxyHandler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_seat_cycle_scratchpad(&self, seat: Seat, name: &str) -> Result<(), CphError> {
|
||||||
|
self.state.with_linear_layout_animations(|| {
|
||||||
|
let seat = self.get_seat(seat)?;
|
||||||
|
self.state.cycle_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)?;
|
||||||
|
|
@ -3021,6 +3029,9 @@ impl ConfigProxyHandler {
|
||||||
ClientMessage::SeatToggleScratchpad { seat, name } => self
|
ClientMessage::SeatToggleScratchpad { seat, name } => self
|
||||||
.handle_seat_toggle_scratchpad(seat, name)
|
.handle_seat_toggle_scratchpad(seat, name)
|
||||||
.wrn("seat_toggle_scratchpad")?,
|
.wrn("seat_toggle_scratchpad")?,
|
||||||
|
ClientMessage::SeatCycleScratchpad { seat, name } => self
|
||||||
|
.handle_seat_cycle_scratchpad(seat, name)
|
||||||
|
.wrn("seat_cycle_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")?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,13 @@ impl TestConfig {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cycle_scratchpad(&self, seat: SeatId, name: &str) -> TestResult {
|
||||||
|
self.send(ClientMessage::SeatCycleScratchpad {
|
||||||
|
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() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
it::{test_error::TestResult, testrun::TestRun},
|
it::{test_error::TestResult, testrun::TestRun},
|
||||||
tree::Node,
|
tree::{Node, ToplevelNodeBase},
|
||||||
},
|
},
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
@ -45,6 +45,63 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
||||||
client.sync().await;
|
client.sync().await;
|
||||||
tassert!(win2.tl.server.node_visible());
|
tassert!(win2.tl.server.node_visible());
|
||||||
tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "3");
|
tassert_eq!(ds.output.workspace.get().unwrap().name.as_str(), "3");
|
||||||
|
// Scratchpad windows are always shown floating.
|
||||||
|
tassert!(win2.tl.server.tl_data().parent_is_float.get());
|
||||||
|
|
||||||
|
// Park win2 again, then build a multi-window scratchpad and cycle it.
|
||||||
|
run.cfg.toggle_scratchpad(ds.seat.id(), "term")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(!win2.tl.server.node_visible());
|
||||||
|
|
||||||
|
// Build a three-window scratchpad. Each window is focused right after it is
|
||||||
|
// mapped, so sending the focused window parks them in a known order.
|
||||||
|
let cyc1 = client.create_window().await?;
|
||||||
|
cyc1.map2().await?;
|
||||||
|
run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?;
|
||||||
|
let cyc2 = client.create_window().await?;
|
||||||
|
cyc2.map2().await?;
|
||||||
|
run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?;
|
||||||
|
let cyc3 = client.create_window().await?;
|
||||||
|
cyc3.map2().await?;
|
||||||
|
run.cfg.send_to_scratchpad(ds.seat.id(), "cyc")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(!cyc1.tl.server.node_visible());
|
||||||
|
tassert!(!cyc2.tl.server.node_visible());
|
||||||
|
tassert!(!cyc3.tl.server.node_visible());
|
||||||
|
|
||||||
|
// Nothing shown: cycle brings up the first window (insertion order: cyc1).
|
||||||
|
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(cyc1.tl.server.node_visible());
|
||||||
|
tassert!(!cyc2.tl.server.node_visible());
|
||||||
|
tassert!(!cyc3.tl.server.node_visible());
|
||||||
|
// Scratchpad windows are always shown floating.
|
||||||
|
tassert!(cyc1.tl.server.tl_data().parent_is_float.get());
|
||||||
|
|
||||||
|
// Cycle advances one at a time.
|
||||||
|
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(!cyc1.tl.server.node_visible());
|
||||||
|
tassert!(cyc2.tl.server.node_visible());
|
||||||
|
tassert!(!cyc3.tl.server.node_visible());
|
||||||
|
|
||||||
|
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(!cyc1.tl.server.node_visible());
|
||||||
|
tassert!(!cyc2.tl.server.node_visible());
|
||||||
|
tassert!(cyc3.tl.server.node_visible());
|
||||||
|
|
||||||
|
// On the final window, the next cycle hides everything.
|
||||||
|
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(!cyc1.tl.server.node_visible());
|
||||||
|
tassert!(!cyc2.tl.server.node_visible());
|
||||||
|
tassert!(!cyc3.tl.server.node_visible());
|
||||||
|
|
||||||
|
// And it wraps back to the first window.
|
||||||
|
run.cfg.cycle_scratchpad(ds.seat.id(), "cyc")?;
|
||||||
|
client.sync().await;
|
||||||
|
tassert!(cyc1.tl.server.node_visible());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
84
src/state.rs
84
src/state.rs
|
|
@ -113,7 +113,7 @@ 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, ScratchpadToplevelState, TearingMode, TileState, ToplevelData,
|
PlaceholderNode, TearingMode, TileState, ToplevelData,
|
||||||
ToplevelIdentifier, ToplevelNode, ToplevelNodeBase, Transform, VrrMode,
|
ToplevelIdentifier, ToplevelNode, ToplevelNodeBase, Transform, VrrMode,
|
||||||
WorkspaceDisplayOrder, WorkspaceNode, WorkspaceNodeId, WsMoveConfig,
|
WorkspaceDisplayOrder, WorkspaceNode, WorkspaceNodeId, WsMoveConfig,
|
||||||
generic_node_visitor, move_ws_to_output, toplevel_hide_for_scratchpad,
|
generic_node_visitor, move_ws_to_output, toplevel_hide_for_scratchpad,
|
||||||
|
|
@ -462,7 +462,6 @@ pub struct ScratchpadEntry {
|
||||||
node: Weak<dyn ToplevelNode>,
|
node: Weak<dyn ToplevelNode>,
|
||||||
identifier: ToplevelIdentifier,
|
identifier: ToplevelIdentifier,
|
||||||
hidden: Cell<bool>,
|
hidden: Cell<bool>,
|
||||||
restore: RefCell<Option<ScratchpadToplevelState>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScratchpadEntry {
|
impl ScratchpadEntry {
|
||||||
|
|
@ -1055,17 +1054,14 @@ impl State {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let identifier = node.tl_data().identifier.get();
|
let identifier = node.tl_data().identifier.get();
|
||||||
|
if !toplevel_hide_for_scratchpad(node.clone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let entry = Rc::new(ScratchpadEntry {
|
let entry = Rc::new(ScratchpadEntry {
|
||||||
node: Rc::downgrade(&node),
|
node: Rc::downgrade(&node),
|
||||||
identifier,
|
identifier,
|
||||||
hidden: Cell::new(false),
|
hidden: Cell::new(true),
|
||||||
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();
|
let mut scratchpads = self.scratchpads.borrow_mut();
|
||||||
for entries in scratchpads.values_mut() {
|
for entries in scratchpads.values_mut() {
|
||||||
|
|
@ -1074,7 +1070,7 @@ impl State {
|
||||||
scratchpads
|
scratchpads
|
||||||
.entry(name.to_string())
|
.entry(name.to_string())
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(entry.clone());
|
.push(entry);
|
||||||
}
|
}
|
||||||
self.tree_changed();
|
self.tree_changed();
|
||||||
}
|
}
|
||||||
|
|
@ -1086,29 +1082,19 @@ impl State {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
entries.retain(|entry| entry.alive());
|
entries.retain(|entry| entry.alive());
|
||||||
|
// Prefer the currently-shown window; otherwise act on the most recent.
|
||||||
entries
|
entries
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find(|entry| {
|
.find(|entry| !entry.hidden.get())
|
||||||
!entry.hidden.get() && entry.node().is_some_and(|node| node.node_visible())
|
.or_else(|| entries.last())
|
||||||
})
|
|
||||||
.cloned()
|
.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 {
|
let Some(entry) = entry else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if entry.hidden.get() {
|
if entry.hidden.get() {
|
||||||
self.show_scratchpad_entry(seat, &entry);
|
self.show_scratchpad_entry(seat, name, &entry);
|
||||||
} else if entry.node().is_some_and(|node| !node.node_visible()) {
|
} else if entry.node().is_some_and(|node| !node.node_visible()) {
|
||||||
self.move_scratchpad_entry_to_current_workspace(seat, &entry);
|
self.move_scratchpad_entry_to_current_workspace(seat, &entry);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1116,12 +1102,39 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cycles through the windows of a scratchpad, one at a time:
|
||||||
|
/// nothing shown -> first window -> ... -> last window -> nothing shown.
|
||||||
|
pub fn cycle_scratchpad(self: &Rc<Self>, seat: &Rc<WlSeatGlobal>, name: &str) {
|
||||||
|
let (current, next) = {
|
||||||
|
let mut scratchpads = self.scratchpads.borrow_mut();
|
||||||
|
let Some(entries) = scratchpads.get_mut(name) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
entries.retain(|entry| entry.alive());
|
||||||
|
match entries.iter().position(|entry| !entry.hidden.get()) {
|
||||||
|
// Nothing shown yet: bring up the first window.
|
||||||
|
None => (None, entries.first().cloned()),
|
||||||
|
// Hide the shown window and advance; on the last window, `next`
|
||||||
|
// is `None`, so the scratchpad toggles off.
|
||||||
|
Some(i) => (entries.get(i).cloned(), entries.get(i + 1).cloned()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(current) = ¤t {
|
||||||
|
self.hide_scratchpad_entry(current);
|
||||||
|
}
|
||||||
|
if let Some(next) = &next {
|
||||||
|
self.show_scratchpad_entry(seat, name, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn hide_scratchpad_entry(self: &Rc<Self>, entry: &Rc<ScratchpadEntry>) {
|
fn hide_scratchpad_entry(self: &Rc<Self>, entry: &Rc<ScratchpadEntry>) {
|
||||||
|
if entry.hidden.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let Some(node) = entry.node() else {
|
let Some(node) = entry.node() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Some(restore) = toplevel_hide_for_scratchpad(node) {
|
if toplevel_hide_for_scratchpad(node) {
|
||||||
*entry.restore.borrow_mut() = Some(restore);
|
|
||||||
entry.hidden.set(true);
|
entry.hidden.set(true);
|
||||||
self.tree_changed();
|
self.tree_changed();
|
||||||
}
|
}
|
||||||
|
|
@ -1130,6 +1143,7 @@ impl State {
|
||||||
fn show_scratchpad_entry(
|
fn show_scratchpad_entry(
|
||||||
self: &Rc<Self>,
|
self: &Rc<Self>,
|
||||||
seat: &Rc<WlSeatGlobal>,
|
seat: &Rc<WlSeatGlobal>,
|
||||||
|
name: &str,
|
||||||
entry: &Rc<ScratchpadEntry>,
|
entry: &Rc<ScratchpadEntry>,
|
||||||
) {
|
) {
|
||||||
if !entry.hidden.get() {
|
if !entry.hidden.get() {
|
||||||
|
|
@ -1138,12 +1152,22 @@ impl State {
|
||||||
let Some(node) = entry.node() else {
|
let Some(node) = entry.node() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let restore = entry.restore.borrow();
|
// Only one window of a scratchpad is visible at a time.
|
||||||
let Some(restore) = restore.as_ref() else {
|
let siblings: Vec<_> = {
|
||||||
return;
|
let scratchpads = self.scratchpads.borrow();
|
||||||
|
scratchpads
|
||||||
|
.get(name)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.filter(|sibling| !Rc::ptr_eq(sibling, entry) && !sibling.hidden.get())
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
};
|
};
|
||||||
|
for sibling in siblings {
|
||||||
|
self.hide_scratchpad_entry(&sibling);
|
||||||
|
}
|
||||||
let ws = seat.get_fallback_output().ensure_workspace();
|
let ws = seat.get_fallback_output().ensure_workspace();
|
||||||
toplevel_restore_from_scratchpad(self, node.clone(), &ws, restore);
|
toplevel_restore_from_scratchpad(self, node.clone(), &ws);
|
||||||
entry.hidden.set(false);
|
entry.hidden.set(false);
|
||||||
node.node_do_focus(seat, Direction::Unspecified);
|
node.node_do_focus(seat, Direction::Unspecified);
|
||||||
seat.maybe_schedule_warp_mouse_to_focus();
|
seat.maybe_schedule_warp_mouse_to_focus();
|
||||||
|
|
|
||||||
|
|
@ -1324,29 +1324,25 @@ pub fn toplevel_set_workspace(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, ws: &
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ScratchpadToplevelState {
|
/// Removes a toplevel from the tree so it can be parked in a scratchpad.
|
||||||
pub floating: bool,
|
///
|
||||||
pub fullscreen: bool,
|
/// Returns `true` if the window was hidden. A placeholder, a window without a
|
||||||
pub workspace: Option<Rc<WorkspaceNode>>,
|
/// parent, or a window that refuses to leave fullscreen cannot be parked.
|
||||||
}
|
pub fn toplevel_hide_for_scratchpad(tl: Rc<dyn ToplevelNode>) -> bool {
|
||||||
|
|
||||||
pub fn toplevel_hide_for_scratchpad(tl: Rc<dyn ToplevelNode>) -> Option<ScratchpadToplevelState> {
|
|
||||||
if tl.node_is_placeholder() {
|
if tl.node_is_placeholder() {
|
||||||
return None;
|
return false;
|
||||||
}
|
}
|
||||||
let data = tl.tl_data();
|
let data = tl.tl_data();
|
||||||
let scratchpad_state = ScratchpadToplevelState {
|
let workspace = data.workspace.get();
|
||||||
floating: data.parent_is_float.get(),
|
|
||||||
fullscreen: data.is_fullscreen.get(),
|
|
||||||
workspace: data.workspace.get(),
|
|
||||||
};
|
|
||||||
if data.is_fullscreen.get() {
|
if data.is_fullscreen.get() {
|
||||||
tl.clone().tl_set_fullscreen(false, None);
|
tl.clone().tl_set_fullscreen(false, None);
|
||||||
if data.is_fullscreen.get() {
|
if data.is_fullscreen.get() {
|
||||||
return None;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let parent = data.parent.get()?;
|
let Some(parent) = data.parent.get() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
let kb_foci = collect_kb_foci(tl.clone());
|
let kb_foci = collect_kb_foci(tl.clone());
|
||||||
parent.cnode_remove_child2(&*tl, true);
|
parent.cnode_remove_child2(&*tl, true);
|
||||||
data.parent.take();
|
data.parent.take();
|
||||||
|
|
@ -1358,29 +1354,23 @@ pub fn toplevel_hide_for_scratchpad(tl: Rc<dyn ToplevelNode>) -> Option<Scratchp
|
||||||
data.property_changed(TL_CHANGED_WORKSPACE);
|
data.property_changed(TL_CHANGED_WORKSPACE);
|
||||||
}
|
}
|
||||||
tl.tl_set_visible(false);
|
tl.tl_set_visible(false);
|
||||||
if let Some(workspace) = &scratchpad_state.workspace {
|
if let Some(workspace) = &workspace {
|
||||||
for seat in kb_foci {
|
for seat in kb_foci {
|
||||||
workspace
|
workspace
|
||||||
.clone()
|
.clone()
|
||||||
.node_do_focus(&seat, Direction::Unspecified);
|
.node_do_focus(&seat, Direction::Unspecified);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(scratchpad_state)
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maps a parked scratchpad window back onto `ws`. Scratchpad windows always
|
||||||
|
/// return floating, regardless of how they were laid out before parking.
|
||||||
pub fn toplevel_restore_from_scratchpad(
|
pub fn toplevel_restore_from_scratchpad(
|
||||||
state: &Rc<State>,
|
state: &Rc<State>,
|
||||||
tl: Rc<dyn ToplevelNode>,
|
tl: Rc<dyn ToplevelNode>,
|
||||||
ws: &Rc<WorkspaceNode>,
|
ws: &Rc<WorkspaceNode>,
|
||||||
scratchpad_state: &ScratchpadToplevelState,
|
|
||||||
) {
|
) {
|
||||||
if scratchpad_state.floating {
|
let (width, height) = tl.tl_data().float_size(ws);
|
||||||
let (width, height) = tl.tl_data().float_size(ws);
|
state.map_floating(tl.clone(), width, height, ws, None);
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ pub enum SimpleCommand {
|
||||||
SetFullscreen(bool),
|
SetFullscreen(bool),
|
||||||
SendToScratchpad,
|
SendToScratchpad,
|
||||||
ToggleScratchpad,
|
ToggleScratchpad,
|
||||||
|
CycleScratchpad,
|
||||||
Forward(bool),
|
Forward(bool),
|
||||||
EnableWindowManagement(bool),
|
EnableWindowManagement(bool),
|
||||||
SetFloatAboveFullscreen(bool),
|
SetFloatAboveFullscreen(bool),
|
||||||
|
|
@ -138,6 +139,9 @@ pub enum Action {
|
||||||
ToggleScratchpad {
|
ToggleScratchpad {
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
CycleScratchpad {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
Multi {
|
Multi {
|
||||||
actions: Vec<Action>,
|
actions: Vec<Action>,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ pub mod modified_keysym;
|
||||||
mod output;
|
mod output;
|
||||||
mod output_match;
|
mod output_match;
|
||||||
mod repeat_rate;
|
mod repeat_rate;
|
||||||
|
mod scratchpad;
|
||||||
pub mod shortcuts;
|
pub mod shortcuts;
|
||||||
mod simple_im;
|
mod simple_im;
|
||||||
mod status;
|
mod status;
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,7 @@ impl ActionParser<'_> {
|
||||||
"exit-fullscreen" => SetFullscreen(false),
|
"exit-fullscreen" => SetFullscreen(false),
|
||||||
"send-to-scratchpad" => SendToScratchpad,
|
"send-to-scratchpad" => SendToScratchpad,
|
||||||
"toggle-scratchpad" => ToggleScratchpad,
|
"toggle-scratchpad" => ToggleScratchpad,
|
||||||
|
"cycle-scratchpad" => CycleScratchpad,
|
||||||
"focus-parent" => FocusParent,
|
"focus-parent" => FocusParent,
|
||||||
"close" => Close,
|
"close" => Close,
|
||||||
"disable-pointer-constraint" => DisablePointerConstraint,
|
"disable-pointer-constraint" => DisablePointerConstraint,
|
||||||
|
|
@ -242,6 +243,15 @@ impl ActionParser<'_> {
|
||||||
Ok(Action::ToggleScratchpad { name })
|
Ok(Action::ToggleScratchpad { name })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_cycle_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::CycleScratchpad { 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"))?
|
||||||
|
|
@ -573,6 +583,7 @@ impl Parser for ActionParser<'_> {
|
||||||
"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),
|
"send-to-scratchpad" => self.parse_send_to_scratchpad(&mut ext),
|
||||||
"toggle-scratchpad" => self.parse_toggle_scratchpad(&mut ext),
|
"toggle-scratchpad" => self.parse_toggle_scratchpad(&mut ext),
|
||||||
|
"cycle-scratchpad" => self.parse_cycle_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),
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ use {
|
||||||
log_level::LogLevelParser,
|
log_level::LogLevelParser,
|
||||||
output::OutputsParser,
|
output::OutputsParser,
|
||||||
repeat_rate::RepeatRateParser,
|
repeat_rate::RepeatRateParser,
|
||||||
|
scratchpad::ScratchpadsParser,
|
||||||
shortcuts::{
|
shortcuts::{
|
||||||
ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError,
|
ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError,
|
||||||
parse_modified_keysym_str,
|
parse_modified_keysym_str,
|
||||||
|
|
@ -570,6 +571,13 @@ impl Parser for ConfigParser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut scratchpads = vec![];
|
||||||
|
if let Some(value) = scratchpads_val {
|
||||||
|
match value.parse(&mut ScratchpadsParser(self.0)) {
|
||||||
|
Ok(v) => scratchpads = v,
|
||||||
|
Err(e) => log::warn!("Could not parse the scratchpads: {}", self.0.error(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
keymap,
|
keymap,
|
||||||
repeat_rate,
|
repeat_rate,
|
||||||
|
|
|
||||||
87
toml-config/src/config/parsers/scratchpad.rs
Normal file
87
toml-config/src/config/parsers/scratchpad.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
Scratchpad,
|
||||||
|
context::Context,
|
||||||
|
extractor::{Extractor, ExtractorError, opt, str, val},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::exec::{ExecParser, ExecParserError},
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ScratchpadParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Exec(#[from] ExecParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScratchpadParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for ScratchpadParser<'_> {
|
||||||
|
type Value = Scratchpad;
|
||||||
|
type Error = ScratchpadParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (name, exec_val) = ext.extract((str("name"), opt(val("exec"))))?;
|
||||||
|
let exec = match exec_val {
|
||||||
|
None => None,
|
||||||
|
Some(e) => Some(e.parse_map(&mut ExecParser(self.0))?),
|
||||||
|
};
|
||||||
|
Ok(Scratchpad {
|
||||||
|
name: name.value.to_string(),
|
||||||
|
exec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScratchpadsParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for ScratchpadsParser<'_> {
|
||||||
|
type Value = Vec<Scratchpad>;
|
||||||
|
type Error = ScratchpadParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(&mut ScratchpadParser(self.0)) {
|
||||||
|
Ok(o) => res.push(o),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse scratchpad: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
log::warn!(
|
||||||
|
"`scratchpads` value should be an array: {}",
|
||||||
|
self.0.error3(span)
|
||||||
|
);
|
||||||
|
ScratchpadParser(self.0)
|
||||||
|
.parse_table(span, table)
|
||||||
|
.map(|v| vec![v])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ use {
|
||||||
config::{
|
config::{
|
||||||
Action, AnimationCurveConfig, ClientRule, Config, ConfigConnector, ConfigDrmDevice,
|
Action, AnimationCurveConfig, ClientRule, Config, ConfigConnector, ConfigDrmDevice,
|
||||||
ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output,
|
ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output,
|
||||||
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
|
OutputMatch, SimpleCommand, Status, Theme, WindowMatch, WindowRule, parse_config,
|
||||||
},
|
},
|
||||||
rules::{MatcherTemp, RuleMapper},
|
rules::{MatcherTemp, RuleMapper},
|
||||||
shortcuts::ModeState,
|
shortcuts::ModeState,
|
||||||
|
|
@ -175,6 +175,7 @@ impl Action {
|
||||||
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::SendToScratchpad => window_or_seat!(s, s.send_to_scratchpad("")),
|
||||||
SimpleCommand::ToggleScratchpad => b.new(move || s.toggle_scratchpad("")),
|
SimpleCommand::ToggleScratchpad => b.new(move || s.toggle_scratchpad("")),
|
||||||
|
SimpleCommand::CycleScratchpad => b.new(move || s.cycle_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 => {
|
||||||
|
|
@ -310,6 +311,7 @@ impl Action {
|
||||||
}
|
}
|
||||||
Action::SendToScratchpad { name } => window_or_seat!(s, s.send_to_scratchpad(&name)),
|
Action::SendToScratchpad { name } => window_or_seat!(s, s.send_to_scratchpad(&name)),
|
||||||
Action::ToggleScratchpad { name } => b.new(move || s.toggle_scratchpad(&name)),
|
Action::ToggleScratchpad { name } => b.new(move || s.toggle_scratchpad(&name)),
|
||||||
|
Action::CycleScratchpad { name } => b.new(move || s.cycle_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) {
|
||||||
|
|
@ -1461,6 +1463,43 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
||||||
window: Default::default(),
|
window: Default::default(),
|
||||||
});
|
});
|
||||||
state.clear_modes_after_reload();
|
state.clear_modes_after_reload();
|
||||||
|
// Desugar `[[scratchpads]]` into spawn-on-graphics-init plus an internal
|
||||||
|
// window rule that parks the spawned window. Each spawned process gets a
|
||||||
|
// unique tag so only its own windows are captured, never other windows of
|
||||||
|
// the same application.
|
||||||
|
if !config.scratchpads.is_empty() {
|
||||||
|
let mut spawn_actions = vec![];
|
||||||
|
for (i, sp) in config.scratchpads.drain(..).enumerate() {
|
||||||
|
let Some(mut exec) = sp.exec else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let tag = exec
|
||||||
|
.tag
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| format!("__scratchpad.{i}.{}", sp.name));
|
||||||
|
exec.tag = Some(tag.clone());
|
||||||
|
spawn_actions.push(Action::Exec { exec });
|
||||||
|
config.window_rules.push(WindowRule {
|
||||||
|
name: None,
|
||||||
|
match_: WindowMatch {
|
||||||
|
tag: Some(tag),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
action: Some(Action::SendToScratchpad { name: sp.name }),
|
||||||
|
latch: None,
|
||||||
|
auto_focus: None,
|
||||||
|
initial_tile_state: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if !spawn_actions.is_empty() {
|
||||||
|
let mut actions = Vec::with_capacity(spawn_actions.len() + 1);
|
||||||
|
if let Some(existing) = config.on_graphics_initialized.take() {
|
||||||
|
actions.push(existing);
|
||||||
|
}
|
||||||
|
actions.extend(spawn_actions);
|
||||||
|
config.on_graphics_initialized = Some(Action::Multi { actions });
|
||||||
|
}
|
||||||
|
}
|
||||||
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
||||||
persistent.client_rules.set(client_rules);
|
persistent.client_rules.set(client_rules);
|
||||||
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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",
|
"description": "Sends the currently focused window to a scratchpad and hides it.\n\nA scratchpad can hold any number of windows. If `name` is omitted, the\ndefault 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",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
|
|
@ -179,7 +179,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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",
|
"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.\nOnly one window of a scratchpad is shown at a time, and scratchpad windows are\nalways shown floating. If `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",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
|
|
@ -194,6 +194,22 @@
|
||||||
"type"
|
"type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Cycles through the windows of a scratchpad, one at a time.\n\nWith no window shown, the first window is brought up. Each further invocation\nhides the current window and shows the next; after the last window the\nscratchpad is hidden again. Scratchpad windows are always shown floating.\nIf `name` is omitted, the default scratchpad is used.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-minus = { type = \"cycle-scratchpad\", name = \"terminal\" }\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "cycle-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",
|
||||||
|
|
@ -1272,6 +1288,14 @@
|
||||||
"egui": {
|
"egui": {
|
||||||
"description": "Sets the egui settings of the compositor.\n",
|
"description": "Sets the egui settings of the compositor.\n",
|
||||||
"$ref": "#/$defs/Egui"
|
"$ref": "#/$defs/Egui"
|
||||||
|
},
|
||||||
|
"scratchpads": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "An array of pre-configured scratchpads.\n\nEach entry launches a program when the graphics are first initialized and\nimmediately parks its window in the named scratchpad. The window is captured\nvia a unique tag attached to the spawned process, so other windows of the\nsame application are never affected.\n\nUse a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows\nup; they are always shown floating.\n\n- Example:\n\n ```toml\n [[scratchpads]]\n name = \"term\"\n exec = \"foot\"\n\n [[scratchpads]]\n name = \"notes\"\n exec = [\"obsidian\"]\n ```\n",
|
||||||
|
"items": {
|
||||||
|
"description": "",
|
||||||
|
"$ref": "#/$defs/Scratchpad"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|
@ -2086,6 +2110,23 @@
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
},
|
},
|
||||||
|
"Scratchpad": {
|
||||||
|
"description": "A pre-configured scratchpad whose program is launched at startup and parked\nin the scratchpad.\n\n- Example:\n\n ```toml\n [[scratchpads]]\n name = \"term\"\n exec = \"foot\"\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the scratchpad that the spawned window is parked in."
|
||||||
|
},
|
||||||
|
"exec": {
|
||||||
|
"description": "The program to launch when the graphics are first initialized.\n\nIf omitted, no program is launched and the scratchpad is only created on\ndemand by `send-to-scratchpad`.\n",
|
||||||
|
"$ref": "#/$defs/Exec"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
"SimpleActionName": {
|
"SimpleActionName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of a `simple` Action.\n\nWhen used inside a window rule, the following actions apply to the matched window\ninstead fo the focused window:\n\n- `move-left`\n- `move-down`\n- `move-up`\n- `move-right`\n- `toggle-fullscreen`\n- `enter-fullscreen`\n- `exit-fullscreen`\n- `close`\n- `toggle-floating`\n- `float`\n- `tile`\n- `toggle-float-pinned`\n- `pin-float`\n- `unpin-float`\n\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n",
|
"description": "The name of a `simple` Action.\n\nWhen used inside a window rule, the following actions apply to the matched window\ninstead fo the focused window:\n\n- `move-left`\n- `move-down`\n- `move-up`\n- `move-right`\n- `toggle-fullscreen`\n- `enter-fullscreen`\n- `exit-fullscreen`\n- `close`\n- `toggle-floating`\n- `float`\n- `tile`\n- `toggle-float-pinned`\n- `pin-float`\n- `unpin-float`\n\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n",
|
||||||
|
|
@ -2112,6 +2153,7 @@
|
||||||
"exit-fullscreen",
|
"exit-fullscreen",
|
||||||
"send-to-scratchpad",
|
"send-to-scratchpad",
|
||||||
"toggle-scratchpad",
|
"toggle-scratchpad",
|
||||||
|
"cycle-scratchpad",
|
||||||
"focus-parent",
|
"focus-parent",
|
||||||
"close",
|
"close",
|
||||||
"disable-pointer-constraint",
|
"disable-pointer-constraint",
|
||||||
|
|
|
||||||
|
|
@ -289,11 +289,12 @@ This table is a tagged union. The variant is determined by the `type` field. It
|
||||||
- `send-to-scratchpad`:
|
- `send-to-scratchpad`:
|
||||||
|
|
||||||
Sends the currently focused window to a scratchpad and hides it.
|
Sends the currently focused window to a scratchpad and hides it.
|
||||||
|
|
||||||
If `name` is omitted, the default scratchpad is used.
|
A scratchpad can hold any number of windows. If `name` is omitted, the
|
||||||
|
default scratchpad is used.
|
||||||
|
|
||||||
- Example:
|
- Example:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[shortcuts]
|
[shortcuts]
|
||||||
alt-shift-minus = { type = "send-to-scratchpad", name = "terminal" }
|
alt-shift-minus = { type = "send-to-scratchpad", name = "terminal" }
|
||||||
|
|
@ -310,13 +311,14 @@ This table is a tagged union. The variant is determined by the `type` field. It
|
||||||
- `toggle-scratchpad`:
|
- `toggle-scratchpad`:
|
||||||
|
|
||||||
Toggles a scratchpad.
|
Toggles a scratchpad.
|
||||||
|
|
||||||
If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
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.
|
most recently hidden window in the scratchpad is shown on the current workspace.
|
||||||
If `name` is omitted, the default scratchpad is used.
|
Only one window of a scratchpad is shown at a time, and scratchpad windows are
|
||||||
|
always shown floating. If `name` is omitted, the default scratchpad is used.
|
||||||
|
|
||||||
- Example:
|
- Example:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[shortcuts]
|
[shortcuts]
|
||||||
alt-minus = { type = "toggle-scratchpad", name = "terminal" }
|
alt-minus = { type = "toggle-scratchpad", name = "terminal" }
|
||||||
|
|
@ -330,6 +332,30 @@ 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.
|
||||||
|
|
||||||
|
- `cycle-scratchpad`:
|
||||||
|
|
||||||
|
Cycles through the windows of a scratchpad, one at a time.
|
||||||
|
|
||||||
|
With no window shown, the first window is brought up. Each further invocation
|
||||||
|
hides the current window and shows the next; after the last window the
|
||||||
|
scratchpad is hidden again. Scratchpad windows are always shown floating.
|
||||||
|
If `name` is omitted, the default scratchpad is used.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-minus = { type = "cycle-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.
|
||||||
|
|
@ -1074,7 +1100,7 @@ The table has the following fields:
|
||||||
- `enabled` (optional):
|
- `enabled` (optional):
|
||||||
|
|
||||||
Enables or disables window animations.
|
Enables or disables window animations.
|
||||||
|
|
||||||
The default is `false`.
|
The default is `false`.
|
||||||
|
|
||||||
The value of this field should be a boolean.
|
The value of this field should be a boolean.
|
||||||
|
|
@ -1082,7 +1108,7 @@ The table has the following fields:
|
||||||
- `duration-ms` (optional):
|
- `duration-ms` (optional):
|
||||||
|
|
||||||
Sets the animation duration in milliseconds.
|
Sets the animation duration in milliseconds.
|
||||||
|
|
||||||
The default is `160`.
|
The default is `160`.
|
||||||
|
|
||||||
The value of this field should be a number.
|
The value of this field should be a number.
|
||||||
|
|
@ -1092,7 +1118,7 @@ The table has the following fields:
|
||||||
- `style` (optional):
|
- `style` (optional):
|
||||||
|
|
||||||
Sets the animation style used for tiled window movement animations.
|
Sets the animation style used for tiled window movement animations.
|
||||||
|
|
||||||
The default is `multiphase`.
|
The default is `multiphase`.
|
||||||
|
|
||||||
The value of this field should be a [AnimationStyle](#types-AnimationStyle).
|
The value of this field should be a [AnimationStyle](#types-AnimationStyle).
|
||||||
|
|
@ -1100,7 +1126,7 @@ The table has the following fields:
|
||||||
- `curve` (optional):
|
- `curve` (optional):
|
||||||
|
|
||||||
Sets the animation curve.
|
Sets the animation curve.
|
||||||
|
|
||||||
The default is `ease-out`.
|
The default is `ease-out`.
|
||||||
|
|
||||||
The value of this field should be a [AnimationCurve](#types-AnimationCurve).
|
The value of this field should be a [AnimationCurve](#types-AnimationCurve).
|
||||||
|
|
@ -2336,11 +2362,11 @@ The table has the following fields:
|
||||||
- `animations` (optional):
|
- `animations` (optional):
|
||||||
|
|
||||||
Configures window animations.
|
Configures window animations.
|
||||||
|
|
||||||
Animations are disabled by default.
|
Animations are disabled by default.
|
||||||
|
|
||||||
- Example:
|
- Example:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[animations]
|
[animations]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
@ -2646,6 +2672,32 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a [Egui](#types-Egui).
|
The value of this field should be a [Egui](#types-Egui).
|
||||||
|
|
||||||
|
- `scratchpads` (optional):
|
||||||
|
|
||||||
|
An array of pre-configured scratchpads.
|
||||||
|
|
||||||
|
Each entry launches a program when the graphics are first initialized and
|
||||||
|
immediately parks its window in the named scratchpad. The window is captured
|
||||||
|
via a unique tag attached to the spawned process, so other windows of the
|
||||||
|
same application are never affected.
|
||||||
|
|
||||||
|
Use a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows
|
||||||
|
up; they are always shown floating.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[scratchpads]]
|
||||||
|
name = "term"
|
||||||
|
exec = "foot"
|
||||||
|
|
||||||
|
[[scratchpads]]
|
||||||
|
name = "notes"
|
||||||
|
exec = ["obsidian"]
|
||||||
|
```
|
||||||
|
|
||||||
|
The value of this field should be an array of [Scratchpads](#types-Scratchpad).
|
||||||
|
|
||||||
|
|
||||||
<a name="types-Connector"></a>
|
<a name="types-Connector"></a>
|
||||||
### `Connector`
|
### `Connector`
|
||||||
|
|
@ -4579,6 +4631,40 @@ The table has the following fields:
|
||||||
The value of this field should be a string.
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
|
||||||
|
<a name="types-Scratchpad"></a>
|
||||||
|
### `Scratchpad`
|
||||||
|
|
||||||
|
A pre-configured scratchpad whose program is launched at startup and parked
|
||||||
|
in the scratchpad.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[scratchpads]]
|
||||||
|
name = "term"
|
||||||
|
exec = "foot"
|
||||||
|
```
|
||||||
|
|
||||||
|
Values of this type should be tables.
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `name` (required):
|
||||||
|
|
||||||
|
The name of the scratchpad that the spawned window is parked in.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
- `exec` (optional):
|
||||||
|
|
||||||
|
The program to launch when the graphics are first initialized.
|
||||||
|
|
||||||
|
If omitted, no program is launched and the scratchpad is only created on
|
||||||
|
demand by `send-to-scratchpad`.
|
||||||
|
|
||||||
|
The value of this field should be a [Exec](#types-Exec).
|
||||||
|
|
||||||
|
|
||||||
<a name="types-SimpleActionName"></a>
|
<a name="types-SimpleActionName"></a>
|
||||||
### `SimpleActionName`
|
### `SimpleActionName`
|
||||||
|
|
||||||
|
|
@ -4702,6 +4788,10 @@ The string should have one of the following values:
|
||||||
|
|
||||||
Toggles the default scratchpad.
|
Toggles the default scratchpad.
|
||||||
|
|
||||||
|
- `cycle-scratchpad`:
|
||||||
|
|
||||||
|
Cycles through the windows of the default scratchpad.
|
||||||
|
|
||||||
- `focus-parent`:
|
- `focus-parent`:
|
||||||
|
|
||||||
Focus the parent of the currently focused window.
|
Focus the parent of the currently focused window.
|
||||||
|
|
@ -5883,3 +5973,5 @@ The table has the following fields:
|
||||||
The scaling mode of X windows.
|
The scaling mode of X windows.
|
||||||
|
|
||||||
The value of this field should be a [XScalingMode](#types-XScalingMode).
|
The value of this field should be a [XScalingMode](#types-XScalingMode).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,8 @@ Action:
|
||||||
description: |
|
description: |
|
||||||
Sends the currently focused window to a scratchpad and hides it.
|
Sends the currently focused window to a scratchpad and hides it.
|
||||||
|
|
||||||
If `name` is omitted, the default scratchpad is used.
|
A scratchpad can hold any number of windows. If `name` is omitted, the
|
||||||
|
default scratchpad is used.
|
||||||
|
|
||||||
- Example:
|
- Example:
|
||||||
|
|
||||||
|
|
@ -368,7 +369,8 @@ Action:
|
||||||
|
|
||||||
If the scratchpad has a visible window, that window is hidden. Otherwise, the
|
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.
|
most recently hidden window in the scratchpad is shown on the current workspace.
|
||||||
If `name` is omitted, the default scratchpad is used.
|
Only one window of a scratchpad is shown at a time, and scratchpad windows are
|
||||||
|
always shown floating. If `name` is omitted, the default scratchpad is used.
|
||||||
|
|
||||||
- Example:
|
- Example:
|
||||||
|
|
||||||
|
|
@ -381,6 +383,26 @@ Action:
|
||||||
description: The name of the scratchpad.
|
description: The name of the scratchpad.
|
||||||
required: false
|
required: false
|
||||||
kind: string
|
kind: string
|
||||||
|
cycle-scratchpad:
|
||||||
|
description: |
|
||||||
|
Cycles through the windows of a scratchpad, one at a time.
|
||||||
|
|
||||||
|
With no window shown, the first window is brought up. Each further invocation
|
||||||
|
hides the current window and shows the next; after the last window the
|
||||||
|
scratchpad is hidden again. Scratchpad windows are always shown floating.
|
||||||
|
If `name` is omitted, the default scratchpad is used.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-minus = { type = "cycle-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.
|
||||||
|
|
@ -1116,6 +1138,8 @@ SimpleActionName:
|
||||||
description: Sends the currently focused window to the default scratchpad.
|
description: Sends the currently focused window to the default scratchpad.
|
||||||
- value: toggle-scratchpad
|
- value: toggle-scratchpad
|
||||||
description: Toggles the default scratchpad.
|
description: Toggles the default scratchpad.
|
||||||
|
- value: cycle-scratchpad
|
||||||
|
description: Cycles through the windows of 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
|
||||||
|
|
@ -3286,6 +3310,61 @@ Config:
|
||||||
required: false
|
required: false
|
||||||
description: |
|
description: |
|
||||||
Sets the egui settings of the compositor.
|
Sets the egui settings of the compositor.
|
||||||
|
scratchpads:
|
||||||
|
kind: array
|
||||||
|
items:
|
||||||
|
ref: Scratchpad
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
An array of pre-configured scratchpads.
|
||||||
|
|
||||||
|
Each entry launches a program when the graphics are first initialized and
|
||||||
|
immediately parks its window in the named scratchpad. The window is captured
|
||||||
|
via a unique tag attached to the spawned process, so other windows of the
|
||||||
|
same application are never affected.
|
||||||
|
|
||||||
|
Use a `toggle-scratchpad` or `cycle-scratchpad` action to bring the windows
|
||||||
|
up; they are always shown floating.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[scratchpads]]
|
||||||
|
name = "term"
|
||||||
|
exec = "foot"
|
||||||
|
|
||||||
|
[[scratchpads]]
|
||||||
|
name = "notes"
|
||||||
|
exec = ["obsidian"]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Scratchpad:
|
||||||
|
kind: table
|
||||||
|
description: |
|
||||||
|
A pre-configured scratchpad whose program is launched at startup and parked
|
||||||
|
in the scratchpad.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[scratchpads]]
|
||||||
|
name = "term"
|
||||||
|
exec = "foot"
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
kind: string
|
||||||
|
required: true
|
||||||
|
description: The name of the scratchpad that the spawned window is parked in.
|
||||||
|
exec:
|
||||||
|
ref: Exec
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
The program to launch when the graphics are first initialized.
|
||||||
|
|
||||||
|
If omitted, no program is launched and the scratchpad is only created on
|
||||||
|
demand by `send-to-scratchpad`.
|
||||||
|
|
||||||
|
|
||||||
Idle:
|
Idle:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue