1
0
Fork 0
forked from wry/wry

Compare commits

..

124 commits

Author SHA1 Message Date
c5dd462a6e
wl-seat: split focus navigation 2026-05-29 21:58:14 -04:00
c4e9011714
wl-seat: split window management 2026-05-29 21:51:40 -04:00
8f7997e270
wl-seat: split selection handling 2026-05-29 21:50:09 -04:00
4074cee365
tree: split output captures 2026-05-29 21:48:05 -04:00
fa1ad20864
tree: split output workspaces 2026-05-29 21:46:18 -04:00
112b1a8e5f
copy-device: split copy execution 2026-05-29 21:44:06 -04:00
100c657050
tree: split container layout 2026-05-29 21:39:35 -04:00
9437a56807
vulkan: split renderer pipelines 2026-05-29 21:35:40 -04:00
ea7251d6e0
xwayland: split window property loading 2026-05-29 21:32:44 -04:00
7531c8f791
metal: split video discovery 2026-05-29 21:27:55 -04:00
556e4214c4
xwayland: remove transfer wildcard import 2026-05-29 21:21:56 -04:00
4e4de21c2e
metal: remove video property wildcard import 2026-05-29 21:21:52 -04:00
7a49da0a48
metal: split video object model 2026-05-29 21:19:57 -04:00
774177390e
tree: split container drag destinations 2026-05-29 21:13:54 -04:00
795cc7d117
vulkan: split renderer pipeline caches 2026-05-29 21:12:22 -04:00
ed0dc3fbad
vulkan: split renderer operations 2026-05-29 21:11:23 -04:00
959f23f61b
vulkan: split renderer paint regions 2026-05-29 21:09:25 -04:00
8c61a3150b
metal: split copy device holder 2026-05-29 21:06:28 -04:00
1e9ed0e568
metal: split drm property helpers 2026-05-29 21:05:27 -04:00
097eb2e8d3
metal: split video leases and cursors 2026-05-29 21:03:23 -04:00
3c7ba1a7b7
vulkan: split renderer color caches 2026-05-29 21:01:31 -04:00
1a43bd55e1
xwayland: split selection transfers 2026-05-29 20:47:04 -04:00
ff04218023
wl-seat: split device and object handlers 2026-05-29 20:44:28 -04:00
054a3a919f
wl-surface: split interface handlers 2026-05-29 20:42:15 -04:00
d36ec3b7d3
copy-device: split support modules 2026-05-29 20:39:13 -04:00
c482a2b99d
tree: split output helpers 2026-05-29 20:35:58 -04:00
7d9ee6e696
tree: split container tasks 2026-05-29 19:50:08 -04:00
16c402825f
state: split render context handling 2026-05-29 19:48:26 -04:00
c4655d4641
state: split tree operations 2026-05-29 19:46:42 -04:00
36eb811f25
state: split settings mutations 2026-05-29 19:45:30 -04:00
c2d86dcd7f
state: split xwayland handling 2026-05-29 19:44:20 -04:00
cd1f049a3e
state: split idle handling 2026-05-29 19:43:08 -04:00
27750a31c4
state: split rendering helpers 2026-05-29 19:41:55 -04:00
19c2265400
state: split animation orchestration 2026-05-29 19:39:44 -04:00
fb31d5115d
state: split connector records 2026-05-29 19:36:43 -04:00
b387c57d57
forker: split proxy boundary 2026-05-29 19:34:06 -04:00
b0603f0639
forker: split worker implementation 2026-05-29 19:31:48 -04:00
e78e9bcd4c
forker: split protocol messages 2026-05-29 19:23:28 -04:00
08b37552e2
config: split runtime handling 2026-05-29 19:22:09 -04:00
d74c56fc76
config: split keymap handling 2026-05-29 19:21:15 -04:00
32ad879a65
config: split option handling 2026-05-29 19:20:24 -04:00
ec9ce08c77
config: split seat handling 2026-05-29 19:19:03 -04:00
05c40cb46d
config: move window operations 2026-05-29 19:15:36 -04:00
274e7c0602
config: split resource handling 2026-05-29 19:10:36 -04:00
b08a8b63c9
config: split workspace handling 2026-05-29 19:08:40 -04:00
814ba579bf
config: split input device handling 2026-05-29 19:05:55 -04:00
988a1e6965
config: split theme handling 2026-05-29 19:03:07 -04:00
1d739ac4b6
config: split output handling 2026-05-29 19:00:12 -04:00
6393fdf3c0
workspace: move crates under crates 2026-05-29 18:55:59 -04:00
0016bc8cf0
config: split window handling 2026-05-29 18:48:33 -04:00
f4fdf750ec
config: split matcher handling 2026-05-29 18:47:25 -04:00
899d39a320
config: split request dispatch 2026-05-29 18:46:17 -04:00
b5b690d9cf
config: split toml parser crate 2026-05-29 18:42:50 -04:00
e3f122e903
config: expose runtime protocol 2026-05-29 18:36:27 -04:00
bcc85c8b1b
renderer: remove unused helper methods 2026-05-29 18:30:30 -04:00
864f506b1e
ifs: remove dead interface markers 2026-05-29 18:28:38 -04:00
9135834cc7
utils: inline buffer module reexports 2026-05-29 18:27:36 -04:00
869a2c2428
tree: rename capture notifications 2026-05-29 18:25:57 -04:00
3bc3d7ac2a
docs: remove security context global 2026-05-29 18:25:10 -04:00
5f02f22c8b
wire: remove unused user client layer 2026-05-29 18:24:36 -04:00
ce03990ea4
ifs: remove private screencast interface 2026-05-29 18:20:10 -04:00
698110c265
globals: remove client capability permissions 2026-05-29 18:16:06 -04:00
8a35bc3ca4
security: remove context protocol 2026-05-29 18:13:14 -04:00
6f913d5f69
ifs: rename data control protocol traits 2026-05-29 18:09:44 -04:00
5cb7b5973f
ifs: rename data transfer interfaces 2026-05-29 18:08:56 -04:00
f9e8d614ce
config: rename criterion payload types 2026-05-29 18:07:30 -04:00
a8176b96c3
config: rename private command messages 2026-05-29 18:06:35 -04:00
87de5fcca3
config: remove bincode command bridge 2026-05-29 18:05:41 -04:00
d8920ed7a0
ifs: rename data transfer module 2026-05-29 17:57:38 -04:00
a038855895
config: move parsed model into schema crate 2026-05-29 17:15:14 -04:00
c8a6b69bf1
config: move input mode model into config module 2026-05-29 17:11:14 -04:00
36b5a831fc
config: move simple command schema into schema crate 2026-05-29 17:10:00 -04:00
e21670f3f6
config: move keymap schema into schema crate 2026-05-29 17:08:55 -04:00
d9261414c2
config: move rule match schema into schema crate 2026-05-29 17:08:06 -04:00
f0e7bd31cb
config: move command schema into schema crate 2026-05-29 17:06:48 -04:00
81a1a865a1
config: move input match schema into schema crate 2026-05-29 17:05:46 -04:00
fb65585bfa
config: move output schema into schema crate 2026-05-29 17:04:27 -04:00
e94d8fec1f
config: move theme schema into schema crate 2026-05-29 17:02:33 -04:00
41e7fcc290
config: move parser option structs into schema crate 2026-05-29 17:01:28 -04:00
b550bb1025
config: move simple options into schema crate 2026-05-29 17:00:24 -04:00
902853955b
video: move drm object ids into type crate 2026-05-29 13:01:44 -04:00
524836bef3
input: move backend events into type crate 2026-05-29 12:58:17 -04:00
03f35d7944
output: move connector state into type crate 2026-05-29 12:54:24 -04:00
be1511a7be
backend: move interface ids into type crates 2026-05-29 12:51:31 -04:00
46d39becd4
input: move capability conversion to libinput boundary 2026-05-29 12:48:47 -04:00
a20deb0628
output: decouple identity from wayland output 2026-05-29 12:45:39 -04:00
59e4e6dfb7
input: decouple tablet contracts from wayland seat 2026-05-29 12:42:59 -04:00
e996d9528a
io_uring: move fd utility helpers into crate 2026-05-29 12:11:41 -04:00
9606e0892c
async_engine: move toplevel scheduler into crate 2026-05-29 12:10:40 -04:00
7d9cd198ba
keyboard: move keymap state into workspace crate 2026-05-29 12:09:20 -04:00
151dc313ba
input: move shared types into workspace crate 2026-05-29 12:03:45 -04:00
54aefd8c41
damage: move transform matrix into workspace crate 2026-05-29 11:58:22 -04:00
db94c9167f
animation: move curve primitives into layout crate 2026-05-29 11:56:39 -04:00
1558666601
udmabuf: move allocator implementation into workspace crate 2026-05-29 11:53:32 -04:00
81a718aa74
drm_feedback: move table generation into workspace crate 2026-05-29 11:51:32 -04:00
6d569bd4b7
output_schedule: move cursor scheduler into workspace crate 2026-05-29 11:48:49 -04:00
11940fb6a5
allocator: move buffer allocation traits into workspace crate 2026-05-29 11:45:26 -04:00
663cfb3ca3
clientmem: move shm mapping into workspace crate 2026-05-29 11:41:55 -04:00
bf3859a026
gfx: move shared memory contract into types crate 2026-05-29 11:39:18 -04:00
37ec1a4a3f
theme: move shared state into workspace crate 2026-05-29 11:37:04 -04:00
854e0474be
build: move fontconfig constants out of src 2026-05-29 11:33:08 -04:00
f5bcfaf73c
libinput: move bindings into workspace crate 2026-05-29 11:32:34 -04:00
4562a34890
pango: move bindings into workspace crate 2026-05-29 11:29:14 -04:00
ebaccd8762
video: move dma-buf types into workspace crate 2026-05-29 11:27:02 -04:00
f456905231
logger: move logging into workspace crate 2026-05-29 11:25:35 -04:00
061991218f
bugs: move application quirks into workspace crate 2026-05-29 11:21:42 -04:00
d50863bbd8
pr_caps: move process capabilities into workspace crate 2026-05-29 11:20:59 -04:00
c709ff997f
sighand: move signal handling into workspace crate 2026-05-29 11:20:12 -04:00
5a3b2a483b
cpu_worker: move runtime into workspace crate 2026-05-29 11:18:35 -04:00
1c24ca26fe
wheel: move timer wheel into workspace crate 2026-05-29 11:15:29 -04:00
aa70204881
eventfd: move cache into workspace crate 2026-05-29 11:14:42 -04:00
89dc6c91cf
tree: move shared tree types into workspace crate 2026-05-29 11:11:54 -04:00
a1e4641e82
wire: move message buffers into workspace crates 2026-05-29 11:07:43 -04:00
d8380b3dce
xcon: move wire core into workspace crate 2026-05-29 11:02:40 -04:00
61ec13def0
dbus: move protocol core into workspace crate 2026-05-29 10:58:23 -04:00
9e428510ca
dbus: remove unused server-side object support 2026-05-29 09:29:29 -04:00
6489c2821c
bufio: move socket buffering into workspace crate 2026-05-29 09:24:04 -04:00
c3b17db151
io_uring: move runtime into workspace crate 2026-05-29 09:22:59 -04:00
03d3876888
async_engine: move scheduler into workspace crate 2026-05-29 09:19:14 -04:00
b7ecf700fa
tracy: move profiler facade into workspace crate 2026-05-29 09:17:36 -04:00
657e7ce2f7
all: split reusable components into workspace crates 2026-05-29 09:14:53 -04:00
2a079ed800
feat: add window animations 2026-05-29 00:02:13 -04:00
eece44a59c
add config options for waking dpms on mouse and keyboard interaction 2026-05-25 23:58:23 -04:00
2167484861
add dpms on/off command 2026-05-25 21:56:48 -04:00
79 changed files with 157 additions and 1775 deletions

View file

@ -77,20 +77,6 @@ You can also right-click any title in a container to toggle mono mode.
In mono mode, scroll over the title bar to cycle between windows in the
container.
## Autotiling
Autotiling makes newly tiled windows alternate split direction from the focused
tiled window. The first split uses the containing group direction, then later
windows wrap the focused tile in the opposite direction, producing a horizontal,
vertical, horizontal pattern as the layout grows.
```toml
[shortcuts]
alt-a = "toggle-autotile"
```
Manual grouping and split commands still use the direction you request.
## Fullscreen
Press `alt-u` (`toggle-fullscreen`) to make the focused window fill the entire

View file

@ -17,9 +17,6 @@ pub enum SimpleCommand {
SetFloating(bool),
ToggleFullscreen,
SetFullscreen(bool),
SendToScratchpad,
ToggleScratchpad,
CycleScratchpad,
Forward(bool),
EnableWindowManagement(bool),
SetFloatAboveFullscreen(bool),

View file

@ -5,7 +5,6 @@ pub struct Exec {
pub prog: String,
pub args: Vec<String>,
pub envs: Vec<(String, String)>,
pub tag: Option<String>,
}
#[derive(Debug, Clone)]

View file

@ -20,9 +20,7 @@ pub use animations::{AnimationCurveConfig, Animations};
pub use command::{Exec, Status};
pub use input::InputMatch;
pub use keymap::ConfigKeymap;
pub use model::{
Action, ClientRule, Config, Input, InputMode, NamedAction, Scratchpad, Shortcut, WindowRule,
};
pub use model::{Action, ClientRule, Config, Input, InputMode, NamedAction, Shortcut, WindowRule};
pub use options::{
ColorManagement, Float, FocusHistory, Libei, RepeatRate, SimpleIm, Tearing, UiDrag, Vrr,
Xwayland,

View file

@ -48,15 +48,6 @@ pub enum Action {
MoveToWorkspace {
name: String,
},
SendToScratchpad {
name: String,
},
ToggleScratchpad {
name: String,
},
CycleScratchpad {
name: String,
},
Multi {
actions: Vec<Action>,
},
@ -245,12 +236,4 @@ pub struct Config {
pub simple_im: Option<SimpleIm>,
pub fallback_output_mode: Option<FallbackOutputMode>,
pub mouse_follows_focus: Option<bool>,
pub scratchpads: Vec<Scratchpad>,
pub autotile: Option<bool>,
}
#[derive(Debug, Clone)]
pub struct Scratchpad {
pub name: String,
pub exec: Option<Exec>,
}

View file

@ -32,8 +32,6 @@ pub struct ClientMatch {
pub comm_regex: Option<String>,
pub exe: Option<String>,
pub exe_regex: Option<String>,
pub tag: Option<String>,
pub tag_regex: Option<String>,
}
#[derive(Default, Debug, Clone)]

View file

@ -308,15 +308,7 @@ impl ConfigClient {
.drain()
.map(|(a, b)| (a, b.into_raw_fd()))
.collect();
if command.tag.is_some() {
self.send(&ClientMessage::Run3 {
prog: &command.prog,
args: command.args.clone(),
env,
fds,
tag: command.tag.as_deref(),
});
} else if fds.is_empty() {
if fds.is_empty() {
self.send(&ClientMessage::Run {
prog: &command.prog,
args: command.args.clone(),
@ -600,22 +592,6 @@ impl ConfigClient {
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 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) {
self.send(&ClientMessage::WindowSendToScratchpad { window, name });
}
pub fn seat_split(&self, seat: Seat) -> Axis {
let res = self.send_with_response(&ClientMessage::GetSeatSplit { seat });
get_response!(res, Axis::Horizontal, GetSplit { axis });
@ -1822,8 +1798,6 @@ impl ConfigClient {
ClientCriterion::CommRegex(t) => string!(t, Comm, true),
ClientCriterion::Exe(t) => string!(t, Exe, false),
ClientCriterion::ExeRegex(t) => string!(t, Exe, true),
ClientCriterion::Tag(t) => string!(t, Tag, false),
ClientCriterion::TagRegex(t) => string!(t, Tag, true),
};
let res = self.send_with_response(&ClientMessage::CreateClientMatcher { criterion });
get_response!(
@ -2042,12 +2016,6 @@ impl ConfigClient {
self.send(&ClientMessage::SetAutotile { enabled });
}
pub fn get_autotile(&self) -> bool {
let res = self.send_with_response(&ClientMessage::GetAutotile);
get_response!(res, false, GetAutotile { enabled });
enabled
}
pub fn set_tab_title_align(&self, align: u32) {
self.send(&ClientMessage::SetTabTitleAlign { align });
}

View file

@ -91,10 +91,6 @@ pub enum ClientCriterion<'a> {
Exe(&'a str),
/// Matches the `/proc/pid/exe` of the client with a regular expression.
ExeRegex(&'a str),
/// Matches the tag of the client verbatim.
Tag(&'a str),
/// Matches the tag of the client with a regular expression.
TagRegex(&'a str),
}
impl ClientCriterion<'_> {

View file

@ -22,7 +22,6 @@ pub struct Command {
pub(crate) args: Vec<String>,
pub(crate) env: HashMap<String, String>,
pub(crate) fds: RefCell<HashMap<i32, OwnedFd>>,
pub(crate) tag: Option<String>,
}
impl Command {
@ -38,7 +37,6 @@ impl Command {
args: vec![],
env: Default::default(),
fds: Default::default(),
tag: Default::default(),
}
}
@ -84,12 +82,6 @@ impl Command {
self.fd(2, fd)
}
/// Adds a tag to Wayland connections created by the spawned command.
pub fn tag(&mut self, tag: &str) -> &mut Self {
self.tag = Some(tag.to_owned());
self
}
/// Executes the command.
///
/// This consumes all attached file descriptors.

View file

@ -466,33 +466,6 @@ impl Seat {
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.
/// Scratchpad windows are always shown floating.
/// Use an empty string for the default scratchpad.
pub fn toggle_scratchpad(self, name: &str) {
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.
pub fn toggle_fullscreen(self) {
let c = get!();

View file

@ -437,21 +437,14 @@ pub fn get_corner_radius() -> f32 {
/// Enables or disables autotiling.
///
/// When enabled, newly tiled windows alternate split orientation from the
/// focused tiled window: the first split uses the containing group's direction,
/// then subsequent splits wrap the focused window in the perpendicular
/// direction.
/// When enabled, new windows are automatically placed in a perpendicular
/// sub-container if the predicted body would be narrower than tall (or vice versa).
///
/// The default is `false`.
pub fn set_autotile(enabled: bool) {
get!().set_autotile(enabled)
}
/// Returns whether autotiling is enabled.
pub fn get_autotile() -> bool {
get!(false).get_autotile()
}
/// Sets the horizontal alignment of title text within tab buttons.
///
/// - `"start"` — left-aligned (default)

View file

@ -116,7 +116,6 @@ pub enum ClientCriterionStringField {
SandboxInstanceId,
Comm,
Exe,
Tag,
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
@ -412,18 +411,6 @@ pub enum ClientMessage<'a> {
seat: Seat,
workspace: Workspace,
},
SeatSendToScratchpad {
seat: Seat,
name: &'a str,
},
SeatToggleScratchpad {
seat: Seat,
name: &'a str,
},
SeatCycleScratchpad {
seat: Seat,
name: &'a str,
},
GetTimer {
name: &'a str,
},
@ -570,13 +557,6 @@ pub enum ClientMessage<'a> {
env: Vec<(String, String)>,
fds: Vec<(i32, i32)>,
},
Run3 {
prog: &'a str,
args: Vec<String>,
env: Vec<(String, String)>,
fds: Vec<(i32, i32)>,
tag: Option<&'a str>,
},
DisableDefaultSeat,
DestroyKeymap {
keymap: Keymap,
@ -837,10 +817,6 @@ pub enum ClientMessage<'a> {
window: Window,
workspace: Workspace,
},
WindowSendToScratchpad {
window: Window,
name: &'a str,
},
SetWindowFullscreen {
window: Window,
fullscreen: bool,
@ -1062,7 +1038,6 @@ pub enum ClientMessage<'a> {
SetAutotile {
enabled: bool,
},
GetAutotile,
SetTabTitleAlign {
align: u32,
},
@ -1326,9 +1301,6 @@ pub enum Response {
GetCornerRadius {
radius: f32,
},
GetAutotile {
enabled: bool,
},
}
#[derive(Serialize, Deserialize, Clone, Debug)]

View file

@ -205,13 +205,6 @@ impl Window {
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.
pub fn toggle_fullscreen(self) {
self.set_fullscreen(!self.fullscreen())

View file

@ -28,8 +28,8 @@ pub use jay_config_schema::{
Action, AnimationCurveConfig, Animations, ClientMatch, ClientRule, ColorManagement, Config,
ConfigConnector, ConfigDrmDevice, ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Float,
FocusHistory, GenericMatch, Input, InputMatch, InputMode, Libei, MatchExactly, Mode,
NamedAction, Output, OutputMatch, RepeatRate, Scratchpad, Shortcut, SimpleCommand, SimpleIm,
Status, Tearing, Theme, UiDrag, Vrr, WindowMatch, WindowRule, Xwayland,
NamedAction, Output, OutputMatch, RepeatRate, Shortcut, SimpleCommand, SimpleIm, Status,
Tearing, Theme, UiDrag, Vrr, WindowMatch, WindowRule, Xwayland,
};
#[derive(Debug, Error)]

View file

@ -40,7 +40,6 @@ pub mod modified_keysym;
mod output;
mod output_match;
mod repeat_rate;
mod scratchpad;
pub mod shortcuts;
mod simple_im;
mod status;

View file

@ -117,9 +117,6 @@ impl ActionParser<'_> {
"toggle-fullscreen" => ToggleFullscreen,
"enter-fullscreen" => SetFullscreen(true),
"exit-fullscreen" => SetFullscreen(false),
"send-to-scratchpad" => SendToScratchpad,
"toggle-scratchpad" => ToggleScratchpad,
"cycle-scratchpad" => CycleScratchpad,
"focus-parent" => FocusParent,
"close" => Close,
"disable-pointer-constraint" => DisablePointerConstraint,
@ -224,33 +221,6 @@ impl ActionParser<'_> {
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_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> {
let con = ext
.extract(val("connector"))?
@ -580,9 +550,6 @@ impl Parser for ActionParser<'_> {
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
"show-workspace" => self.parse_show_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),
"cycle-scratchpad" => self.parse_cycle_scratchpad(&mut ext),
"configure-connector" => self.parse_configure_connector(&mut ext),
"configure-input" => self.parse_configure_input(&mut ext),
"configure-output" => self.parse_configure_output(&mut ext),

View file

@ -59,7 +59,7 @@ impl Parser for ClientMatchParser<'_> {
exe,
exe_regex,
),
(is_xwayland, tag, tag_regex),
(is_xwayland,),
) = ext.extract((
(
opt(str("name")),
@ -83,11 +83,7 @@ impl Parser for ClientMatchParser<'_> {
opt(str("exe")),
opt(str("exe-regex")),
),
(
opt(bol("is-xwayland")),
opt(str("tag")),
opt(str("tag-regex")),
),
(opt(bol("is-xwayland")),),
))?;
let mut not = None;
if let Some(value) = not_val {
@ -134,8 +130,6 @@ impl Parser for ClientMatchParser<'_> {
comm_regex: comm_regex.despan_into(),
exe: exe.despan_into(),
exe_regex: exe_regex.despan_into(),
tag: tag.despan_into(),
tag_regex: tag_regex.despan_into(),
})
}
}

View file

@ -28,7 +28,6 @@ use {
log_level::LogLevelParser,
output::OutputsParser,
repeat_rate::RepeatRateParser,
scratchpad::ScratchpadsParser,
shortcuts::{
ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError,
parse_modified_keysym_str,
@ -157,7 +156,6 @@ impl Parser for ConfigParser<'_> {
mouse_follows_focus,
animations_val,
),
(scratchpads_val, autotile),
) = ext.extract((
(
opt(val("keymap")),
@ -219,7 +217,6 @@ impl Parser for ConfigParser<'_> {
recover(opt(bol("unstable-mouse-follows-focus"))),
opt(val("animations")),
),
(opt(val("scratchpads")), recover(opt(bol("autotile")))),
))?;
let mut keymap = None;
if let Some(value) = keymap_val {
@ -575,13 +572,6 @@ 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 {
keymap,
repeat_rate,
@ -634,8 +624,6 @@ impl Parser for ConfigParser<'_> {
simple_im,
fallback_output_mode,
mouse_follows_focus: mouse_follows_focus.despan(),
scratchpads,
autotile: autotile.despan(),
})
}
}

View file

@ -11,7 +11,7 @@ use {
},
},
jay_toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
@ -52,7 +52,6 @@ impl Parser for ExecParser<'_> {
prog: string.to_string(),
args: vec![],
envs: vec![],
tag: None,
})
}
@ -69,7 +68,6 @@ impl Parser for ExecParser<'_> {
prog,
args,
envs: vec![],
tag: None,
})
}
@ -79,12 +77,11 @@ impl Parser for ExecParser<'_> {
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (prog_opt, shell_opt, args_val, envs_val, tag) = ext.extract((
let (prog_opt, shell_opt, args_val, envs_val) = ext.extract((
opt(str("prog")),
opt(str("shell")),
opt(arr("args")),
opt(val("env")),
opt(str("tag")),
))?;
let prog;
let mut args = vec![];
@ -116,7 +113,6 @@ impl Parser for ExecParser<'_> {
prog,
args,
envs,
tag: tag.despan_into(),
})
}
}

View file

@ -1,87 +0,0 @@
use {
crate::{
config::{
Scratchpad,
context::Context,
extractor::{Extractor, ExtractorError, opt, str, val},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::exec::{ExecParser, ExecParserError},
},
jay_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])
}
}

View file

@ -14,10 +14,9 @@ pub(crate) use jay_toml;
use {
crate::{
config::{
Action, AnimationCurveConfig, ClientMatch, ClientRule, Config, ConfigConnector,
ConfigDrmDevice, ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Input,
InputMatch, Output, OutputMatch, SimpleCommand, Status, Theme, WindowMatch,
WindowRule, parse_config,
Action, AnimationCurveConfig, ClientRule, Config, ConfigConnector, ConfigDrmDevice,
ConfigKeymap, ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output,
OutputMatch, SimpleCommand, Status, Theme, WindowRule, parse_config,
},
rules::{MatcherTemp, RuleMapper},
shortcuts::ModeState,
@ -29,7 +28,7 @@ use {
client::Client,
config_dir,
exec::{Command, set_env, unset_env},
get_autotile, get_workspace,
get_workspace,
input::{
FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, capability::CAP_SWITCH,
get_seat, input_devices, on_input_device_removed, on_new_input_device,
@ -42,11 +41,11 @@ use {
on_devices_enumerated, on_idle, on_unload, quit, set_animation_cubic_bezier,
set_animation_curve, set_animation_duration_ms, set_animation_style,
set_animations_enabled, set_autotile, set_color_management_enabled, set_corner_radius,
set_default_workspace_capture, set_explicit_sync_enabled, set_float_above_fullscreen,
set_floating_titles, set_idle, set_idle_grace_period, set_key_press_enables_dpms,
set_middle_click_paste_enabled, set_mouse_move_enables_dpms, set_show_bar,
set_show_float_pin_icon, set_show_titles, set_tab_title_align, set_ui_drag_enabled,
set_ui_drag_threshold,
set_default_workspace_capture,
set_explicit_sync_enabled, set_float_above_fullscreen, set_floating_titles, set_idle,
set_idle_grace_period, set_key_press_enables_dpms, set_middle_click_paste_enabled,
set_mouse_move_enables_dpms, set_show_bar, set_show_float_pin_icon, set_show_titles,
set_tab_title_align, set_ui_drag_enabled, set_ui_drag_threshold,
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
switch_to_vt,
tasks::{self, JoinHandle},
@ -186,9 +185,6 @@ impl ActionExt for Action {
SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)),
SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()),
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::CycleScratchpad => b.new(move || s.cycle_scratchpad("")),
SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
SimpleCommand::Close => window_or_seat!(s, s.close()),
SimpleCommand::DisablePointerConstraint => {
@ -284,7 +280,12 @@ impl ActionExt for Action {
SimpleCommand::MoveTabLeft => b.new(move || s.move_tab(false)),
SimpleCommand::MoveTabRight => b.new(move || s.move_tab(true)),
SimpleCommand::SetAutotile(enabled) => b.new(move || set_autotile(enabled)),
SimpleCommand::ToggleAutotile => b.new(move || set_autotile(!get_autotile())),
SimpleCommand::ToggleAutotile => {
b.new(move || {
// Toggle not directly supported; set to true
set_autotile(true)
})
}
},
Action::Multi { actions } => {
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
@ -321,9 +322,6 @@ impl ActionExt for Action {
let workspace = get_workspace(&name);
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::CycleScratchpad { name } => b.new(move || s.cycle_scratchpad(&name)),
Action::ConfigureConnector { con } => b.new(move || {
for c in connectors() {
if con.match_.matches(c) {
@ -1528,46 +1526,6 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
window: Default::default(),
});
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 {
client: Some(ClientMatch {
tag: Some(tag),
..Default::default()
}),
..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);
persistent.client_rules.set(client_rules);
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
@ -1854,9 +1812,6 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
.seat
.unstable_set_mouse_follows_focus(mouse_follows_focus);
}
if let Some(v) = config.autotile {
set_autotile(v);
}
}
fn create_command(exec: &Exec) -> Command {
@ -1867,9 +1822,6 @@ fn create_command(exec: &Exec) -> Command {
for (k, v) in &exec.envs {
command.env(k, v);
}
if let Some(tag) = &exec.tag {
command.tag(tag);
}
command
}

View file

@ -127,8 +127,6 @@ impl Rule for ClientRule {
value_ref!(CommRegex, comm_regex);
value_ref!(Exe, exe);
value_ref!(ExeRegex, exe_regex);
value_ref!(Tag, tag);
value_ref!(TagRegex, tag_regex);
value!(Uid, uid);
value!(Pid, pid);
bool!(Sandboxed, sandboxed);

View file

@ -162,54 +162,6 @@
"name"
]
},
{
"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",
"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.\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",
"properties": {
"type": {
"const": "toggle-scratchpad"
},
"name": {
"type": "string",
"description": "The name of the scratchpad."
}
},
"required": [
"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",
"type": "object",
@ -889,14 +841,6 @@
"exe-regex": {
"type": "string",
"description": "Matches the `/proc/pid/exe` of the client with a regular expression."
},
"tag": {
"type": "string",
"description": "Matches the tag of the client verbatim."
},
"tag-regex": {
"type": "string",
"description": "Matches the tag of the client with a regular expression."
}
},
"required": []
@ -1213,10 +1157,6 @@
"type": "boolean",
"description": "Configures whether middle-click pasting is enabled.\n\nChanging this has no effect on running applications.\n\nThe default is `true`.\n"
},
"autotile": {
"type": "boolean",
"description": "Configures whether autotiling is enabled by default.\n\nWhen enabled, newly mapped tiled windows alternate their split\norientation automatically. This can also be toggled at runtime via the\n`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.\n\nThe default is `false`.\n"
},
"modes": {
"description": "Configures the input modes.\n\nModes can be used to define shortcuts that are only active when the mode is\nactive.\n\n- Example\n\n ```toml\n [modes.\"navigation\".shortcuts]\n w = \"focus-up\"\n a = \"focus-left\"\n s = \"focus-down\"\n d = \"focus-right\"\n r = \"focus-above\"\n f = \"focus-below\"\n q = \"focus-prev\"\n e = \"focus-next\"\n ```\n\nModes can be activated with the `push-mode` and `latch-mode` actions.\n",
"type": "object",
@ -1244,14 +1184,6 @@
"egui": {
"description": "Sets the egui settings of the compositor.\n",
"$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": []
@ -1481,10 +1413,6 @@
"type": "string",
"description": ""
}
},
"tag": {
"type": "string",
"description": "Specifies a tag to apply to all spawned wayland connections.\n"
}
},
"required": []
@ -2062,23 +1990,6 @@
},
"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": {
"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",
@ -2097,15 +2008,9 @@
"make-group-tab",
"change-group-opposite",
"toggle-tab",
"enable-autotile",
"disable-autotile",
"toggle-autotile",
"toggle-fullscreen",
"enter-fullscreen",
"exit-fullscreen",
"send-to-scratchpad",
"toggle-scratchpad",
"cycle-scratchpad",
"focus-parent",
"close",
"disable-pointer-constraint",

View file

@ -286,76 +286,6 @@ This table is a tagged union. The variant is determined by the `type` field. It
The value of this field should be a string.
- `send-to-scratchpad`:
Sends the currently focused window to a scratchpad and hides it.
A scratchpad can hold any number of windows. 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.
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:
```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.
- `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`:
Moves a workspace to a different output.
@ -1479,18 +1409,6 @@ The table has the following fields:
The value of this field should be a string.
- `tag` (optional):
Matches the tag of the client verbatim.
The value of this field should be a string.
- `tag-regex` (optional):
Matches the tag of the client with a regular expression.
The value of this field should be a string.
<a name="types-ClientMatchExactly"></a>
### `ClientMatchExactly`
@ -2436,18 +2354,6 @@ The table has the following fields:
The value of this field should be a boolean.
- `autotile` (optional):
Configures whether autotiling is enabled by default.
When enabled, newly mapped tiled windows alternate their split
orientation automatically. This can also be toggled at runtime via the
`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.
The default is `false`.
The value of this field should be a boolean.
- `modes` (optional):
Configures the input modes.
@ -2548,32 +2454,6 @@ The table has the following fields:
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>
### `Connector`
@ -3053,12 +2933,6 @@ The table has the following fields:
The value of this field should be a table whose values are strings.
- `tag` (optional):
Specifies a tag to apply to all spawned wayland connections.
The value of this field should be a string.
<a name="types-FallbackOutputMode"></a>
### `FallbackOutputMode`
@ -4483,40 +4357,6 @@ The table has the following fields:
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>
### `SimpleActionName`
@ -4608,18 +4448,6 @@ The string should have one of the following values:
Toggles the current group between tabbed and split mode.
- `enable-autotile`:
Enables alternating split orientation for newly tiled windows.
- `disable-autotile`:
Disables alternating split orientation for newly tiled windows.
- `toggle-autotile`:
Toggles alternating split orientation for newly tiled windows.
- `toggle-fullscreen`:
Toggle the currently focused window between fullscreen and windowed.
@ -4632,18 +4460,6 @@ The string should have one of the following values:
Makes the currently focused window windowed.
- `send-to-scratchpad`:
Sends the currently focused window to the default scratchpad.
- `toggle-scratchpad`:
Toggles the default scratchpad.
- `cycle-scratchpad`:
Cycles through the windows of the default scratchpad.
- `focus-parent`:
Focus the parent of the currently focused window.

View file

@ -345,64 +345,6 @@ Action:
description: The name of the workspace.
required: true
kind: string
send-to-scratchpad:
description: |
Sends the currently focused window to a scratchpad and hides it.
A scratchpad can hold any number of windows. 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.
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:
```toml
[shortcuts]
alt-minus = { type = "toggle-scratchpad", name = "terminal" }
```
fields:
name:
description: The name of the scratchpad.
required: false
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:
description: |
Moves a workspace to a different output.
@ -1036,11 +978,6 @@ Exec:
values:
kind: string
description: The environment variables to pass to the executable.
tag:
kind: string
required: false
description: |
Specifies a tag to apply to all spawned wayland connections.
SimpleActionName:
@ -1102,24 +1039,12 @@ SimpleActionName:
description: Toggles the current group's direction.
- value: toggle-tab
description: Toggles the current group between tabbed and split mode.
- value: enable-autotile
description: Enables alternating split orientation for newly tiled windows.
- value: disable-autotile
description: Disables alternating split orientation for newly tiled windows.
- value: toggle-autotile
description: Toggles alternating split orientation for newly tiled windows.
- value: toggle-fullscreen
description: Toggle the currently focused window between fullscreen and windowed.
- value: enter-fullscreen
description: Makes the currently focused window fullscreen.
- value: exit-fullscreen
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: cycle-scratchpad
description: Cycles through the windows of the default scratchpad.
- value: focus-parent
description: Focus the parent of the currently focused window.
- value: close
@ -3177,21 +3102,10 @@ Config:
required: false
description: |
Configures whether middle-click pasting is enabled.
Changing this has no effect on running applications.
The default is `true`.
autotile:
kind: boolean
required: false
description: |
Configures whether autotiling is enabled by default.
When enabled, newly mapped tiled windows alternate their split
orientation automatically. This can also be toggled at runtime via the
`enable-autotile`, `disable-autotile`, and `toggle-autotile` actions.
The default is `false`.
modes:
kind: map
values:
@ -3288,61 +3202,6 @@ Config:
required: false
description: |
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:
@ -4251,14 +4110,6 @@ ClientMatch:
kind: string
required: false
description: Matches the `/proc/pid/exe` of the client with a regular expression.
tag:
kind: string
required: false
description: Matches the tag of the client verbatim.
tag-regex:
kind: string
required: false
description: Matches the tag of the client with a regular expression.
ClientMatchExactly:

View file

@ -21,7 +21,7 @@ use {
std::{io, os::unix::ffi::OsStrExt, path::PathBuf},
};
#[path = "../../../build/wire/parser.rs"]
#[path = "../../build/wire/parser.rs"]
#[allow(dead_code)]
mod parser;

View file

@ -80,7 +80,6 @@ pub struct ClientMetadata {
pub sandbox_engine: Option<String>,
pub app_id: Option<String>,
pub instance_id: Option<String>,
pub tag: Option<String>,
}
impl Clients {

View file

@ -286,7 +286,6 @@ fn start_compositor2(
display: Default::default(),
},
acceptor: Default::default(),
tagged_acceptors: Default::default(),
serial: Default::default(),
idle_inhibitor_ids: Default::default(),
run_toplevel,
@ -386,7 +385,6 @@ fn start_compositor2(
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
virtual_outputs: Default::default(),
clean_logs_older_than: Default::default(),
scratchpads: Default::default(),
});
state.tracker.register(ClientId::from_raw(0));
create_dummy_output(&state);

View file

@ -8,7 +8,7 @@ use {
},
client::{Client, ClientId},
cmm::cmm_eotf::Eotf,
compositor::{MAX_EXTENTS, WAYLAND_DISPLAY},
compositor::MAX_EXTENTS,
criteria::{
CritLiteralOrRegex, CritMgrExt, CritTarget, CritUpstreamNode,
clm::ClmLeafMatcher,
@ -25,7 +25,6 @@ use {
output_schedule::map_cursor_hz,
scale::Scale,
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
tagged_acceptor::TaggedAcceptorError,
theme::{ThemeColor, ThemeSized},
tree::{
ContainerSplit, OutputNode, TearingMode, TileState, ToplevelData, ToplevelIdentifier,
@ -407,8 +406,6 @@ enum CphError {
UnknownFallbackOutputMode(FallbackOutputMode),
#[error("Unknown tile state {0:?}")]
UnknownTileState(ConfigTileState),
#[error("Could not create tagged acceptor")]
CreateTaggedAcceptor(#[source] TaggedAcceptorError),
}
trait WithRequestName {

View file

@ -60,7 +60,7 @@ impl ConfigProxyHandler {
ClientMessage::GetSeats => self.handle_get_seats(),
ClientMessage::RemoveSeat { .. } => {}
ClientMessage::Run { prog, args, env } => {
self.handle_run(prog, args, env, vec![], None).wrn("run")?
self.handle_run(prog, args, env, vec![]).wrn("run")?
}
ClientMessage::GrabKb { kb, grab } => self.handle_grab(kb, grab).wrn("grab")?,
ClientMessage::SetColor { colorable, color } => {
@ -111,15 +111,6 @@ impl ConfigProxyHandler {
ClientMessage::SetSeatWorkspace { seat, workspace } => self
.handle_set_seat_workspace(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::SeatCycleScratchpad { seat, name } => self
.handle_seat_cycle_scratchpad(seat, name)
.wrn("seat_cycle_scratchpad")?,
ClientMessage::GetConnector { ty, idx } => {
self.handle_get_connector(ty, idx).wrn("get_connector")?
}
@ -277,14 +268,7 @@ impl ConfigProxyHandler {
args,
env,
fds,
} => self.handle_run(prog, args, env, fds, None).wrn("run")?,
ClientMessage::Run3 {
prog,
args,
env,
fds,
tag,
} => self.handle_run(prog, args, env, fds, tag).wrn("run")?,
} => self.handle_run(prog, args, env, fds).wrn("run")?,
ClientMessage::DisableDefaultSeat => self.state.create_default_seat.set(false),
ClientMessage::DestroyKeymap { keymap } => self.handle_destroy_keymap(keymap),
ClientMessage::GetConnectorName { connector } => self
@ -516,9 +500,6 @@ impl ConfigProxyHandler {
ClientMessage::SetWindowWorkspace { window, workspace } => self
.handle_set_window_workspace(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
.handle_set_window_fullscreen(window, fullscreen)
.wrn("set_window_fullscreen")?,
@ -720,11 +701,6 @@ impl ConfigProxyHandler {
ClientMessage::SetAutotile { enabled } => {
self.state.theme.autotile_enabled.set(enabled);
}
ClientMessage::GetAutotile => {
self.respond(Response::GetAutotile {
enabled: self.state.theme.autotile_enabled.get(),
});
}
ClientMessage::SeatToggleExpand { .. } => {
// Removed feature; kept for binary protocol compatibility.
}

View file

@ -105,7 +105,6 @@ impl ConfigProxyHandler {
}
ClientCriterionStringField::Comm => mgr.comm(needle),
ClientCriterionStringField::Exe => mgr.exe(needle),
ClientCriterionStringField::Tag => mgr.tag(needle),
}
}
ClientCriterionPayload::Sandboxed => mgr.sandboxed(),

View file

@ -58,18 +58,9 @@ impl ConfigProxyHandler {
&self,
prog: &str,
args: Vec<String>,
mut env: Vec<(String, String)>,
env: Vec<(String, String)>,
fds: Vec<(i32, i32)>,
tag: Option<&str>,
) -> Result<(), CphError> {
if let Some(tag) = tag {
let display = self
.state
.tagged_acceptors
.get(&self.state, tag)
.map_err(CphError::CreateTaggedAcceptor)?;
env.push((WAYLAND_DISPLAY.to_string(), display.to_string()));
}
let fds: Vec<_> = fds
.into_iter()
.map(|(a, b)| (a, Rc::new(OwnedFd::new(b))))

View file

@ -120,44 +120,6 @@ impl ConfigProxyHandler {
})
}
pub(super) 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(())
})
}
pub(super) 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(())
})
}
pub(super) 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(())
})
}
pub(super) fn handle_get_repeat_rate(&self, seat: Seat) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
let (rate, delay) = seat.get_rate();

View file

@ -124,18 +124,6 @@ impl ConfigProxyHandler {
})
}
pub(super) 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(())
})
}
pub(super) fn handle_window_exists(&self, window: Window) {
self.respond(Response::WindowExists {
exists: self.get_window(window).is_ok(),

View file

@ -13,7 +13,7 @@ use {
clmm_sandboxed::ClmMatchSandboxed,
clmm_string::{
ClmMatchComm, ClmMatchExe, ClmMatchSandboxAppId, ClmMatchSandboxEngine,
ClmMatchSandboxInstanceId, ClmMatchTag,
ClmMatchSandboxInstanceId,
},
clmm_uid::ClmMatchUid,
},
@ -62,7 +62,6 @@ pub struct RootMatchers {
pid: ClmRootMatcherMap<ClmMatchPid>,
comm: ClmRootMatcherMap<ClmMatchComm>,
exe: ClmRootMatcherMap<ClmMatchExe>,
tag: ClmRootMatcherMap<ClmMatchTag>,
id: ClmRootMatcherMap<ClmMatchId>,
}
@ -75,7 +74,6 @@ impl RootMatchers {
self.pid.clear();
self.comm.clear();
self.exe.clear();
self.tag.clear();
self.id.clear();
}
}
@ -187,7 +185,6 @@ impl ClMatcherManager {
unconditional!(pid);
unconditional!(comm);
unconditional!(exe);
unconditional!(tag);
unconditional!(id);
fixed!(sandboxed);
fixed!(is_xwayland);
@ -231,9 +228,6 @@ impl ClMatcherManager {
self.root(ClmMatchExe::new(string))
}
pub fn tag(&self, string: CritLiteralOrRegex) -> Rc<ClmUpstreamNode> {
self.root(ClmMatchTag::new(string))
}
}
pub struct ClientTargetOwner {

View file

@ -14,7 +14,6 @@ pub type ClmMatchString<T> = CritMatchString<Client, T>;
pub type ClmMatchSandboxEngine = ClmMatchString<ClientMetadataAccess<SandboxEngineField>>;
pub type ClmMatchSandboxAppId = ClmMatchString<ClientMetadataAccess<SandboxAppIdField>>;
pub type ClmMatchSandboxInstanceId = ClmMatchString<ClientMetadataAccess<SandboxInstanceIdField>>;
pub type ClmMatchTag = ClmMatchString<ClientMetadataAccess<TagField>>;
pub type ClmMatchComm = ClmMatchString<CommAccess>;
pub type ClmMatchExe = ClmMatchString<ExeAccess>;
@ -32,7 +31,6 @@ trait ClientMetadataField: Sized + 'static {
pub struct SandboxEngineField;
pub struct SandboxAppIdField;
pub struct SandboxInstanceIdField;
pub struct TagField;
impl<T> StringAccess<Client> for ClientMetadataAccess<T>
where
@ -83,18 +81,6 @@ impl ClientMetadataField for SandboxInstanceIdField {
}
}
impl ClientMetadataField for TagField {
fn field(meta: &ClientMetadata) -> &Option<String> {
&meta.tag
}
fn nodes(
roots: &RootMatchers,
) -> &ClmRootMatcherMap<ClmMatchString<ClientMetadataAccess<Self>>> {
&roots.tag
}
}
impl StringAccess<Client> for CommAccess {
fn with_string(data: &Client, f: impl FnOnce(&str) -> bool) -> bool {
f(&data.pid_info.comm)

View file

@ -1364,25 +1364,25 @@ impl WlSurface {
let bounds = self.toplevel.get().and_then(|tl| tl.tl_render_bounds());
let pos = self.buffer_abs_pos.get();
let apply_damage = |pos: Rect| {
let clip_damage = |mut damage: Rect| {
damage = damage.intersect(pos);
if pending.damage_full {
let mut damage = pos;
if let Some(bounds) = bounds {
damage = damage.intersect(bounds);
}
damage
};
if pending.damage_full {
self.client.state.damage(clip_damage(pos));
self.client.state.damage(damage);
} else {
let matrix = self.damage_matrix.get();
if let Some(buffer) = self.buffer.get() {
for damage in &pending.buffer_damage {
let damage = matrix.apply(
let mut damage = matrix.apply(
pos.x1(),
pos.y1(),
damage.intersect(buffer.buffer.buf.rect),
);
self.client.state.damage(clip_damage(damage));
if let Some(bounds) = bounds {
damage = damage.intersect(bounds);
}
self.client.state.damage(damage);
}
}
for damage in &pending.surface_damage {
@ -1394,7 +1394,8 @@ impl WlSurface {
let y2 = (damage.y2() + scale - 1) / scale;
damage = Rect::new_saturating(x1, y1, x2, y2);
}
self.client.state.damage(clip_damage(damage));
damage = damage.intersect(bounds.unwrap_or(pos));
self.client.state.damage(damage);
}
}
};

View file

@ -272,27 +272,6 @@ 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,
})
}
pub fn cycle_scratchpad(&self, seat: SeatId, name: &str) -> TestResult {
self.send(ClientMessage::SeatCycleScratchpad {
seat: Seat(seat.raw() as _),
name,
})
}
fn clear(&self) {
unsafe {
if let Some(srv) = self.srv.take() {
@ -340,10 +319,6 @@ impl TestConfig {
pub fn set_show_titles(&self, show: bool) -> TestResult {
self.send(ClientMessage::SetShowTitles { show })
}
pub fn set_autotile(&self, enabled: bool) -> TestResult {
self.send(ClientMessage::SetAutotile { enabled })
}
}
impl Drop for TestConfig {

View file

@ -29,17 +29,6 @@ impl TestViewport {
Ok(())
}
pub fn unset_source(&self) -> Result<(), TestError> {
self.tran.send(SetSource {
self_id: self.id,
x: Fixed::from_int(-1),
y: Fixed::from_int(-1),
width: Fixed::from_int(-1),
height: Fixed::from_int(-1),
})?;
Ok(())
}
pub fn set_destination(&self, width: i32, height: i32) -> Result<(), TestError> {
self.tran.send(SetDestination {
self_id: self.id,
@ -48,15 +37,6 @@ impl TestViewport {
})?;
Ok(())
}
pub fn unset_destination(&self) -> Result<(), TestError> {
self.tran.send(SetDestination {
self_id: self.id,
width: -1,
height: -1,
})?;
Ok(())
}
}
impl Drop for TestViewport {

View file

@ -85,8 +85,6 @@ mod t0051_pointer_warp;
mod t0052_bar;
mod t0053_theme;
mod t0054_subsurface_already_attached;
mod t0055_autotiling;
mod t0055_scratchpad;
pub trait TestCase: Sync {
fn name(&self) -> &'static str;
@ -160,7 +158,5 @@ pub fn tests() -> Vec<&'static dyn TestCase> {
t0052_bar,
t0053_theme,
t0054_subsurface_already_attached,
t0055_autotiling,
t0055_scratchpad,
}
}

View file

@ -1,6 +1,7 @@
use {
crate::{
it::{test_error::TestError, testrun::TestRun},
rect::Rect,
tree::Node,
},
std::rc::Rc,
@ -10,19 +11,29 @@ testcase!();
/// Create and map a single surface
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let ds = run.create_default_setup().await?;
run.backend.install_default()?;
let client = run.create_client().await?;
let window = client.create_window().await?;
window.map().await?;
let workspace_rect = ds.output.workspace_rect.get();
tassert_eq!(window.tl.core.width.get(), 800);
tassert_eq!(
window.tl.core.height.get(),
600 - 2 * run.state.theme.title_plus_underline_height()
);
tassert_eq!(window.tl.core.width.get(), workspace_rect.width());
tassert_eq!(window.tl.core.height.get(), workspace_rect.height());
tassert_eq!(window.tl.server.node_absolute_position(), workspace_rect);
tassert_eq!(
window.tl.server.node_absolute_position(),
Rect::new_sized(
0,
2 * run.state.theme.title_plus_underline_height(),
window.tl.core.width.get(),
window.tl.core.height.get(),
)
.unwrap()
);
Ok(())
}

View file

@ -11,7 +11,7 @@ testcase!();
/// Create and map two surfaces
async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let ds = run.create_default_setup().await?;
run.backend.install_default()?;
let client = run.create_client().await?;
@ -21,30 +21,17 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let window2 = client.create_window().await?;
window2.map().await?;
let workspace_rect = ds.output.workspace_rect.get();
let otop = 2 * run.state.theme.title_plus_underline_height();
let bw = run.state.theme.sizes.border_width.get();
let child_width = (workspace_rect.width() - bw) / 2;
tassert_eq!(
window.tl.server.node_absolute_position(),
Rect::new_sized(
workspace_rect.x1(),
workspace_rect.y1(),
child_width,
workspace_rect.height(),
)
.unwrap()
Rect::new_sized(0, otop, (800 - bw) / 2, 600 - otop).unwrap()
);
tassert_eq!(
window2.tl.server.node_absolute_position(),
Rect::new_sized(
workspace_rect.x1() + child_width + bw,
workspace_rect.y1(),
child_width,
workspace_rect.height(),
)
.unwrap()
Rect::new_sized((800 - bw) / 2 + bw, otop, (800 - bw) / 2, 600 - otop).unwrap()
);
Ok(())

View file

@ -48,18 +48,13 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let mono_container = w_mono2.tl.container_parent()?;
let container_pos = mono_container.tl_data().pos.get();
let (tab_x, tab_y) = {
let tab_bar = mono_container.tab_bar.borrow();
let Some(tab_bar) = tab_bar.as_ref() else {
bail!("no tab bar");
};
let w_mono1_title = &tab_bar.entries[0];
(
container_pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2,
container_pos.y1() + tab_bar.height / 2,
)
};
ds.mouse.abs(&ds.connector, tab_x as _, tab_y as _);
let w_mono1_title = mono_container.render_data.borrow_mut().title_rects[0]
.move_(container_pos.x1(), container_pos.y1());
ds.mouse.abs(
&ds.connector,
w_mono1_title.x1() as _,
w_mono1_title.y1() as _,
);
client.sync().await;
tassert!(enters.next().is_err());

View file

@ -26,18 +26,12 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let container = w_mono2.tl.container_parent()?;
let pos = container.tl_data().pos.get();
let (tab_x, tab_y) = {
let tab_bar = container.tab_bar.borrow();
let Some(tab_bar) = tab_bar.as_ref() else {
bail!("no tab bar");
};
let w_mono1_title = &tab_bar.entries[0];
(
pos.x1() + w_mono1_title.x.get() + w_mono1_title.width.get() / 2,
pos.y1() + tab_bar.height / 2,
)
};
ds.mouse.abs(&ds.connector, tab_x as f64, tab_y as f64);
let w_mono1_title = container.render_data.borrow_mut().title_rects[0].move_(pos.x1(), pos.y1());
ds.mouse.abs(
&ds.connector,
w_mono1_title.x1() as f64,
w_mono1_title.y1() as f64,
);
client.sync().await;
let enters = dss.kb.enter.expect()?;

View file

@ -2,7 +2,7 @@ use {
crate::{
ifs::wl_surface::xdg_surface::xdg_toplevel::STATE_SUSPENDED,
it::{
test_error::{TestErrorExt, TestResult},
test_error::TestResult,
test_utils::{
test_ouput_node_ext::TestOutputNodeExt, test_toplevel_node_ext::TestToplevelNodeExt,
},
@ -10,7 +10,7 @@ use {
},
},
isnt::std_1::collections::IsntHashSetExt,
std::{rc::Rc, time::Duration},
std::rc::Rc,
};
testcase!();
@ -19,7 +19,6 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let ds = run.create_default_setup().await?;
let client = run.create_client().await?;
let default_seat = client.get_default_seat().await?;
let win1 = client.create_window().await?;
win1.set_color(255, 0, 0, 255);
@ -45,23 +44,5 @@ async fn test(run: Rc<TestRun>) -> TestResult {
client.sync().await;
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
let leaves = default_seat.kb.leave.expect()?;
let enters = default_seat.kb.enter.expect()?;
run.cfg.set_idle(Duration::from_micros(100))?;
run.cfg.set_idle_grace_period(Duration::from_secs(0))?;
run.state.wheel.timeout(3).await?;
client.sync().await;
tassert!(win2.tl.core.states.borrow().contains(&STATE_SUSPENDED));
let leave = leaves.next().with_context(|| "no leave on suspend")?;
tassert_eq!(leave.surface, win2.surface.id);
ds.mouse.rel(1.0, 1.0);
client.sync().await;
tassert!(win2.tl.core.states.borrow().not_contains(&STATE_SUSPENDED));
let enter = enters.next().with_context(|| "no enter on restore")?;
tassert_eq!(enter.surface, win2.surface.id);
Ok(())
}

View file

@ -308,8 +308,9 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let output_damage = connector_data.damage.borrow();
tassert!(!output_damage.is_empty());
// The test window maps its 1x1 buffer through a viewport to the full window size.
let expected_buffer_damage = surface_pos;
// Buffer damage is transformed by the damage matrix which includes the surface position
// The buffer damage (0,0,1,1) should be transformed to surface coordinates
let expected_buffer_damage = buffer_damage.move_(surface_pos.x1(), surface_pos.y1());
// Find the exact output damage that matches our expected buffer damage
let mut found_exact_buffer_damage = false;
@ -330,12 +331,10 @@ async fn test(run: Rc<TestRun>) -> TestResult {
// Test 7: Check output damage from existing window's viewport (which already has scaling)
connector_data.damage.borrow_mut().clear();
// The existing window was created with create_surface_ext() which automatically creates a viewport.
// Commit the viewport size change separately; that commit intentionally damages the old/new extents.
window.surface.viewport.set_destination(150, 100)?;
window.surface.commit()?;
client.sync().await;
connector_data.damage.borrow_mut().clear();
// The existing window was created with create_surface_ext() which automatically creates a viewport
// Let's verify that the viewport's existing scaling affects buffer damage correctly
// First, let's modify the viewport scaling that already exists on the window
window.surface.viewport.set_destination(150, 100)?; // Change scaling to 150x100
// Add buffer damage to test viewport scaling coordinate transformation
window.surface.damage_buffer(0, 0, 1, 1)?; // Damage entire 1x1 buffer
@ -347,8 +346,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
let output_damage = connector_data.damage.borrow();
tassert!(!output_damage.is_empty());
// With viewporter scaling, the 1x1 buffer damage should scale to the viewport destination.
let surface_pos = window.surface.server.buffer_abs_pos.get();
// With viewporter scaling, the 1x1 buffer damage should scale to 150x100
// and be moved by surface position (0, 36) to get output coordinates (0, 36, 150, 136)
let expected_scaled_damage = Rect::new_sized(0, 0, 150, 100).unwrap();
let expected_output_damage =
expected_scaled_damage.move_(surface_pos.x1(), surface_pos.y1());
@ -403,9 +402,8 @@ async fn test(run: Rc<TestRun>) -> TestResult {
rotation_window.map().await?;
client.sync().await;
// Disable viewporter to rely purely on buffer dimensions.
rotation_window.surface.viewport.unset_source()?;
rotation_window.surface.viewport.unset_destination()?;
// Disable viewporter by setting destination to 0x0 to rely purely on buffer dimensions
rotation_window.surface.viewport.set_destination(0, 0)?; // Disable viewporter
// Use a rectangular buffer (4x2) so rotation has a visible geometric effect
// Attach AFTER mapping to avoid being overwritten by map()'s single-pixel buffer

View file

@ -1,58 +0,0 @@
use {
crate::{
it::{test_error::TestResult, testrun::TestRun},
tree::{ContainerSplit, Node, ToplevelNodeBase},
},
std::rc::Rc,
};
testcase!();
async fn test(run: Rc<TestRun>) -> TestResult {
run.backend.install_default()?;
run.cfg.set_autotile(true)?;
let client = run.create_client().await?;
let win1 = client.create_window().await?;
win1.map().await?;
let root = win1.tl.container_parent()?;
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
let win2 = client.create_window().await?;
win2.map().await?;
client.sync().await;
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
tassert_eq!(win1.tl.container_parent()?.node_id(), root.node_id());
tassert_eq!(win2.tl.container_parent()?.node_id(), root.node_id());
let win3 = client.create_window().await?;
win3.map().await?;
client.sync().await;
let v_group = win3.tl.container_parent()?;
tassert_eq!(root.split.get(), ContainerSplit::Horizontal);
tassert_eq!(v_group.split.get(), ContainerSplit::Vertical);
tassert_eq!(win2.tl.container_parent()?.node_id(), v_group.node_id());
let win4 = client.create_window().await?;
win4.map().await?;
client.sync().await;
let h_group = win4.tl.container_parent()?;
tassert_eq!(h_group.split.get(), ContainerSplit::Horizontal);
tassert_eq!(win3.tl.container_parent()?.node_id(), h_group.node_id());
let h_parent = match h_group
.tl_data()
.parent
.get()
.and_then(|p| p.node_into_container())
{
Some(parent) => parent,
None => bail!("autotile group does not have a container parent"),
};
tassert_eq!(h_parent.node_id(), v_group.node_id());
Ok(())
}

View file

@ -1,107 +0,0 @@
use {
crate::{
it::{test_error::TestResult, testrun::TestRun},
tree::{Node, ToplevelNodeBase},
},
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");
// 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(())
}

View file

@ -99,7 +99,6 @@ mod scale;
mod screenshoter;
mod sighand;
mod state;
mod tagged_acceptor;
mod tasks;
mod text;
mod theme;

View file

@ -87,7 +87,6 @@ use {
pr_caps::PrCapsThread,
rect::Rect,
scale::Scale,
tagged_acceptor::TaggedAcceptors,
theme::Theme,
time::Time,
tree::{
@ -186,7 +185,6 @@ pub struct State {
pub run_args: RunArgs,
pub xwayland: XWaylandState,
pub acceptor: CloneCell<Option<Rc<Acceptor>>>,
pub tagged_acceptors: TaggedAcceptors,
pub serial: NumCell<u64>,
pub run_toplevel: Rc<RunToplevel>,
pub config_dir: Option<String>,
@ -285,7 +283,6 @@ pub struct State {
pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
pub virtual_outputs: VirtualOutputs,
pub clean_logs_older_than: Cell<Option<SystemTime>>,
pub scratchpads: RefCell<AHashMap<String, Vec<Rc<ScratchpadEntry>>>>,
}
// impl Drop for State {
@ -305,27 +302,6 @@ pub struct ScreenlockState {
pub lock: CloneCell<Option<Rc<ExtSessionLockV1>>>,
}
pub struct ScratchpadEntry {
node: Weak<dyn ToplevelNode>,
identifier: ToplevelIdentifier,
hidden: Cell<bool>,
}
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
}
}
}
pub struct InputDeviceData {
pub _handler: SpawnedFuture<()>,
pub id: InputDeviceId,
@ -499,7 +475,6 @@ impl State {
self.eng.clear();
self.ei_acceptor.take();
self.ei_acceptor_future.take();
self.tagged_acceptors.clear();
self.ei_clients.clear();
self.slow_ei_clients.clear();
self.toplevels.clear();
@ -509,7 +484,6 @@ impl State {
self.node_at_tree.borrow_mut().clear();
self.position_hint_requests.clear();
self.pending_warp_mouse_to_focus.clear();
self.scratchpads.borrow_mut().clear();
self.head_managers.clear();
self.head_managers_async.clear();
self.const_40hz_latch.clear();

View file

@ -859,3 +859,4 @@ mod tests {
assert_eq!(merged[1].node_id, NodeId(2));
}
}

View file

@ -5,14 +5,13 @@ use {
tree::{
ContainerNode, ContainerSplit, Direction, FindTreeUsecase, FloatNode, FoundNode, Node,
OutputNode, TileState, ToplevelData, ToplevelNode, ToplevelNodeBase, WorkspaceNode,
WsMoveConfig, generic_node_visitor, move_ws_to_output, toplevel_hide_for_scratchpad,
toplevel_restore_from_scratchpad, toplevel_set_workspace,
WsMoveConfig, generic_node_visitor, move_ws_to_output,
},
},
std::{cell::Cell, ops::Deref, rc::Rc},
std::{ops::Deref, rc::Rc},
};
use super::{ScratchpadEntry, State};
use super::State;
impl State {
pub fn tree_changed(&self) {
@ -42,39 +41,19 @@ impl State {
&& node.tl_data().kind.is_app_window()
&& !node.tl_data().visible.get();
if animate_new_app_map {
self.with_layout_animations(|| self.do_map_tiled(seat.as_deref(), node.clone(), true));
self.with_layout_animations(|| self.do_map_tiled(seat.as_deref(), node.clone()));
} else {
self.do_map_tiled(seat.as_deref(), node.clone(), true);
self.do_map_tiled(seat.as_deref(), node.clone());
}
self.focus_after_map(node, seat.as_deref());
}
pub fn map_tiled_without_autotile(self: &Rc<Self>, node: Rc<dyn ToplevelNode>) {
let seat = self.seat_queue.last();
self.do_map_tiled(seat.as_deref(), node.clone(), false);
self.focus_after_map(node, seat.as_deref());
}
fn do_map_tiled(
self: &Rc<Self>,
seat: Option<&Rc<WlSeatGlobal>>,
node: Rc<dyn ToplevelNode>,
autotile: bool,
) {
fn do_map_tiled(self: &Rc<Self>, seat: Option<&Rc<WlSeatGlobal>>, node: Rc<dyn ToplevelNode>) {
let ws = self.ensure_map_workspace(seat);
self.map_tiled_on_(node, &ws, autotile);
self.map_tiled_on(node, &ws);
}
pub fn map_tiled_on(self: &Rc<Self>, node: Rc<dyn ToplevelNode>, ws: &Rc<WorkspaceNode>) {
self.map_tiled_on_(node, ws, false);
}
fn map_tiled_on_(
self: &Rc<Self>,
node: Rc<dyn ToplevelNode>,
ws: &Rc<WorkspaceNode>,
autotile: bool,
) {
if let Some(c) = ws.container.get() {
let la = c.clone().tl_last_active_child();
let lap = la
@ -83,11 +62,7 @@ impl State {
.get()
.and_then(|n| n.node_into_container());
if let Some(lap) = lap {
if autotile {
lap.add_tiled_child_after(&*la, node);
} else {
lap.add_child_after(&*la, node);
}
lap.add_child_after(&*la, node);
} else {
c.append_child(node);
}
@ -140,146 +115,6 @@ impl State {
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();
if !toplevel_hide_for_scratchpad(node.clone()) {
return;
}
let entry = Rc::new(ScratchpadEntry {
node: Rc::downgrade(&node),
identifier,
hidden: Cell::new(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);
}
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());
// Prefer the currently-shown window; otherwise act on the most recent.
entries
.iter()
.rev()
.find(|entry| !entry.hidden.get())
.or_else(|| entries.last())
.cloned()
};
let Some(entry) = entry else {
return;
};
if entry.hidden.get() {
self.show_scratchpad_entry(seat, name, &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);
}
}
/// 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) = &current {
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>) {
if entry.hidden.get() {
return;
}
let Some(node) = entry.node() else {
return;
};
if toplevel_hide_for_scratchpad(node) {
entry.hidden.set(true);
self.tree_changed();
}
}
fn show_scratchpad_entry(
self: &Rc<Self>,
seat: &Rc<WlSeatGlobal>,
name: &str,
entry: &Rc<ScratchpadEntry>,
) {
if !entry.hidden.get() {
return;
}
let Some(node) = entry.node() else {
return;
};
// Only one window of a scratchpad is visible at a time.
let siblings: Vec<_> = {
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();
toplevel_restore_from_scratchpad(self, node.clone(), &ws);
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>>) {
if !node.node_visible() {
return;

View file

@ -1,191 +0,0 @@
use {
crate::{
async_engine::SpawnedFuture,
client::ClientMetadata,
state::State,
utils::{
errorfmt::ErrorFmt,
numcell::NumCell,
oserror::{OsError, OsErrorExt, OsErrorExt2},
xrd::xrd,
},
},
ahash::AHashMap,
std::{
cell::{Cell, RefCell},
rc::Rc,
},
thiserror::Error,
uapi::{OwnedFd, Ustring, c, format_ustr},
};
#[derive(Debug, Error)]
pub enum TaggedAcceptorError {
#[error("XDG_RUNTIME_DIR is not set")]
XrdNotSet,
#[error("XDG_RUNTIME_DIR ({0:?}) is too long to form a unix socket address")]
XrdTooLong(String),
#[error("Could not create a wayland socket")]
SocketFailed(#[source] OsError),
#[error("Could not stat the existing socket")]
SocketStat(#[source] OsError),
#[error("Could not start listening for incoming connections")]
ListenFailed(#[source] OsError),
#[error("Could not open the lock file")]
OpenLockFile(#[source] OsError),
#[error("Could not lock the lock file")]
LockLockFile(#[source] OsError),
#[error("Could not bind the socket to an address")]
BindFailed(#[source] OsError),
}
#[derive(Default)]
pub struct TaggedAcceptors {
acceptors: RefCell<AHashMap<String, Rc<Acceptor>>>,
next_name: NumCell<u64>,
}
struct Acceptor {
socket: AllocatedSocket,
tag: String,
state: Rc<State>,
metadata: Rc<ClientMetadata>,
future: Cell<Option<SpawnedFuture<()>>>,
}
impl TaggedAcceptors {
pub fn clear(&self) {
let acceptors = self.acceptors.take();
for (_, acceptor) in acceptors {
acceptor.kill();
}
}
pub fn get(&self, state: &Rc<State>, tag: &str) -> Result<Rc<String>, TaggedAcceptorError> {
let acceptors = &mut *self.acceptors.borrow_mut();
if let Some(acceptor) = acceptors.get(tag) {
return Ok(acceptor.socket.name.clone());
}
let acceptor = Rc::new(Acceptor {
socket: self.allocate_socket()?,
tag: tag.to_owned(),
state: state.clone(),
metadata: Rc::new(ClientMetadata {
tag: Some(tag.to_owned()),
..Default::default()
}),
future: Default::default(),
});
log::info!("Creating tagged acceptor `{tag}`");
acceptor.future.set(Some(
state.eng.spawn("tagged accept", acceptor.clone().accept()),
));
acceptors.insert(tag.to_owned(), acceptor.clone());
Ok(acceptor.socket.name.clone())
}
fn allocate_socket(&self) -> Result<AllocatedSocket, TaggedAcceptorError> {
let xrd = xrd().ok_or(TaggedAcceptorError::XrdNotSet)?;
let socket = uapi::socket(c::AF_UNIX, c::SOCK_STREAM | c::SOCK_CLOEXEC, 0)
.map(Rc::new)
.map_os_err(TaggedAcceptorError::SocketFailed)?;
loop {
let i = self.next_name.fetch_add(1) + 1000;
if let Some(s) = bind_socket(&socket, &xrd, i)? {
return Ok(s);
}
}
}
}
impl Acceptor {
fn kill(&self) {
log::info!("Destroying tagged acceptor `{}`", self.tag);
self.future.take();
self.state
.tagged_acceptors
.acceptors
.borrow_mut()
.remove(&self.tag);
}
async fn accept(self: Rc<Self>) {
let state = &self.state;
loop {
let fd = match state.ring.accept(&self.socket.socket, c::SOCK_CLOEXEC).await {
Ok(fd) => fd,
Err(e) => {
log::error!("Could not accept a client: {}", ErrorFmt(e));
break;
}
};
let id = state.clients.id();
if let Err(e) = state.clients.spawn(id, state, fd, &self.metadata) {
log::error!("Could not spawn a client: {}", ErrorFmt(e));
break;
}
}
self.kill();
}
}
struct AllocatedSocket {
// wayland-x
name: Rc<String>,
// /run/user/1000/wayland-x
path: Ustring,
socket: Rc<OwnedFd>,
// /run/user/1000/wayland-x.lock
lock_path: Ustring,
_lock_fd: OwnedFd,
}
impl Drop for AllocatedSocket {
fn drop(&mut self) {
let _ = uapi::unlink(&self.path);
let _ = uapi::unlink(&self.lock_path);
}
}
fn bind_socket(
fd: &Rc<OwnedFd>,
xrd: &str,
id: u64,
) -> Result<Option<AllocatedSocket>, TaggedAcceptorError> {
let mut addr: c::sockaddr_un = uapi::pod_zeroed();
addr.sun_family = c::AF_UNIX as _;
let name = Rc::new(format!("wayland-{}", id));
let path = format_ustr!("{}/{}", xrd, name);
let lock_path = format_ustr!("{}.lock", path.display());
if path.len() + 1 > addr.sun_path.len() {
return Err(TaggedAcceptorError::XrdTooLong(xrd.to_string()));
}
let lock_fd = uapi::open(&*lock_path, c::O_CREAT | c::O_CLOEXEC | c::O_RDWR, 0o644)
.map_os_err(TaggedAcceptorError::OpenLockFile)?;
if let Err(e) = uapi::flock(lock_fd.raw(), c::LOCK_EX | c::LOCK_NB).to_os_error() {
if e.0 == c::EWOULDBLOCK {
return Ok(None);
}
return Err(TaggedAcceptorError::LockLockFile(e));
}
match uapi::lstat(&path).to_os_error() {
Ok(_) => {
log::info!("Unlinking {}", path.display());
let _ = uapi::unlink(&path);
}
Err(OsError(c::ENOENT)) => {}
Err(e) => return Err(TaggedAcceptorError::SocketStat(e)),
}
let sun_path = uapi::as_bytes_mut(&mut addr.sun_path[..]);
sun_path[..path.len()].copy_from_slice(path.as_bytes());
sun_path[path.len()] = 0;
uapi::bind(fd.raw(), &addr).map_os_err(TaggedAcceptorError::BindFailed)?;
uapi::listen(fd.raw(), 4096).map_os_err(TaggedAcceptorError::ListenFailed)?;
Ok(Some(AllocatedSocket {
name,
path,
socket: fd.clone(),
lock_path,
_lock_fd: lock_fd,
}))
}

View file

@ -36,7 +36,6 @@ use {
linkedlist::{LinkedList, LinkedNode, NodeRef},
numcell::NumCell,
rc_eq::rc_eq,
scroller::Scroller,
threshold_counter::ThresholdCounter,
},
},
@ -155,7 +154,6 @@ pub struct ContainerNode {
pub child_removed: Rc<LazyEventSource>,
pub all_children_resized: Rc<LazyEventSource>,
pub tab_bar: RefCell<Option<TabBar>>,
scroll: Scroller,
pub update_tab_textures_scheduled: Cell<bool>,
pub ephemeral: Cell<Ephemeral>,
}
@ -255,7 +253,6 @@ impl ContainerNode {
child_removed: state.lazy_event_sources.create_source(),
all_children_resized: state.post_layout_event_sources.create_source(),
tab_bar: RefCell::new(None),
scroll: Default::default(),
update_tab_textures_scheduled: Cell::new(false),
ephemeral: Cell::new(Ephemeral::Off),
});
@ -280,47 +277,6 @@ impl ContainerNode {
self.add_child_x(prev, new, |prev, new| self.add_child_after_(prev, new));
}
pub fn add_tiled_child_after(self: &Rc<Self>, prev: &dyn Node, new: Rc<dyn ToplevelNode>) {
if !self.state.theme.autotile_enabled.get()
|| self.mono_child.is_some()
|| self.num_children.get() <= 1
{
self.add_child_after(prev, new);
return;
}
let focused = self
.child_nodes
.borrow()
.get(&prev.node_id())
.map(|n| n.to_ref());
let Some(focused) = focused else {
log::error!(
"Tried to autotile a child into a container but the preceding node is not in the container"
);
return;
};
let focused_node = focused.node.clone();
let focused_active = focused_node.tl_data().active();
let sub = ContainerNode::new(
&self.state,
&self.workspace.get(),
focused_node.clone(),
self.split.get().other(),
);
// Autotile-created groups are structural and collapse once only one
// child remains. Explicit make-group commands control their own
// grouping through the regular manual paths.
sub.ephemeral.set(Ephemeral::On);
sub.append_child(new);
let sub_id = sub.node_id();
self.clone().cnode_replace_child(&*focused_node, sub);
if focused_active
&& let Some(group) = self.child_nodes.borrow().get(&sub_id).map(|n| n.to_ref())
{
self.update_child_active(&group, true, 1);
}
}
pub fn add_child_before(self: &Rc<Self>, prev: &dyn Node, new: Rc<dyn ToplevelNode>) {
self.add_child_x(prev, new, |prev, new| self.add_child_before_(prev, new));
}
@ -571,18 +527,6 @@ impl ContainerNode {
self.activate_child2(child, false);
}
fn activate_child_from_input(
self: &Rc<Self>,
child: &NodeRef<ContainerChild>,
seat: &Rc<WlSeatGlobal>,
) {
self.activate_child(child);
child
.node
.clone()
.node_do_focus(seat, Direction::Unspecified);
}
fn activate_child2(self: &Rc<Self>, child: &NodeRef<ContainerChild>, preserve_focus: bool) {
if let Some(mc) = self.mono_child.get() {
if mc.node.node_id() == child.node.node_id() {
@ -1200,6 +1144,42 @@ impl ContainerNode {
}
pub fn insert_child(self: &Rc<Self>, node: Rc<dyn ToplevelNode>, direction: Direction) {
// Autotile: if the container would become too narrow/tall, wrap the
// focused child and new node in a perpendicular sub-container.
if self.state.theme.autotile_enabled.get() && self.mono_child.is_none() {
let (pw, ph) = self.predict_child_body_size();
let opposite = match self.split.get() {
ContainerSplit::Horizontal if pw > 0 && ph > 0 && pw < ph => {
Some(ContainerSplit::Vertical)
}
ContainerSplit::Vertical if pw > 0 && ph > 0 && ph < pw => {
Some(ContainerSplit::Horizontal)
}
_ => None,
};
if let Some(opp_split) = opposite {
if let Some(focused) = self.focus_history.last() {
if self.num_children.get() <= 1 {
// Single child, autotile not applicable.
} else {
let focused_node = focused.node.clone();
let was_ephemeral = self.ephemeral.replace(Ephemeral::Off);
self.clone().cnode_remove_child2(&*focused_node, true);
self.ephemeral.set(was_ephemeral);
let sub = ContainerNode::new(
&self.state,
&self.workspace.get(),
focused_node,
opp_split,
);
sub.ephemeral.set(Ephemeral::On);
sub.append_child(node);
self.append_child(sub);
return;
}
}
}
}
let (split, right) = direction_to_split(direction);
if split != self.split.get() || right {
self.append_child(node);
@ -1309,7 +1289,7 @@ impl ContainerNode {
fn button(
self: Rc<Self>,
id: CursorType,
seat: &Rc<WlSeatGlobal>,
_seat: &Rc<WlSeatGlobal>,
_time_usec: u64,
pressed: bool,
button: u32,
@ -1339,7 +1319,7 @@ impl ContainerNode {
if let Some(child) = children.get(&child_id) {
let child_ref = child.to_ref();
drop(children);
self.activate_child_from_input(&child_ref, seat);
self.activate_child(&child_ref);
}
return;
}
@ -1712,33 +1692,31 @@ impl Node for ContainerNode {
self.button(id, seat, time_usec, state == ButtonState::Pressed, button);
}
fn node_on_axis_event(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
fn node_on_axis_event(self: Rc<Self>, _seat: &Rc<WlSeatGlobal>, event: &PendingScroll) {
if self.mono_child.is_none() {
return;
}
let steps = match self.scroll.handle(event) {
Some(steps) => steps,
// Use vertical scroll (index 1) to switch tabs.
let v = match event.v120[1].get() {
Some(v) if v != 0 => v,
_ => return,
};
let mut target = match self.mono_child.get() {
let mono = match self.mono_child.get() {
Some(m) => m,
None => return,
};
let current_id = target.node.node_id();
for _ in 0..steps.abs() {
let next = if steps > 0 {
target.next().or_else(|| self.children.first())
} else {
target.prev().or_else(|| self.children.last())
};
match next {
Some(next) => target = next,
None => break,
let next = if v > 0 {
// Scroll down → next tab.
mono.next().or_else(|| self.children.first())
} else {
// Scroll up → previous tab.
mono.prev().or_else(|| self.children.last())
};
if let Some(next) = next {
if next.node.node_id() != mono.node.node_id() {
self.activate_child(&next);
}
}
if target.node.node_id() != current_id {
self.activate_child_from_input(&target, seat);
}
}
fn node_on_leave(&self, seat: &WlSeatGlobal) {

View file

@ -154,3 +154,4 @@ impl ContainerNode {
self.damage();
}
}

View file

@ -8,25 +8,18 @@ use {
renderer::Renderer,
state::State,
tree::{
Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink,
NodeLocation, OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, NodeLayerLink, NodeLocation,
OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination,
WorkspaceNodeId, walker::NodeVisitor,
},
utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList},
},
std::{
cell::{Cell, RefCell},
mem,
ops::Deref,
rc::{Rc, Weak},
},
std::{cell::Cell, ops::Deref, rc::Rc},
};
pub struct DisplayNode {
pub id: NodeId,
pub extents: Cell<Rect>,
visible: Cell<bool>,
suspend_restore_kb_foci: RefCell<Vec<(Rc<WlSeatGlobal>, Weak<dyn Node>)>>,
pub outputs: CopyHashMap<ConnectorId, Rc<OutputNode>>,
pub stacked: Rc<LinkedList<Rc<dyn StackedNode>>>,
pub stacked_above_layers: Rc<LinkedList<Rc<dyn StackedNode>>>,
@ -38,8 +31,6 @@ impl DisplayNode {
let slf = Self {
id,
extents: Default::default(),
visible: Default::default(),
suspend_restore_kb_foci: Default::default(),
outputs: Default::default(),
stacked: Default::default(),
stacked_above_layers: Default::default(),
@ -80,17 +71,6 @@ impl DisplayNode {
pub fn update_visible(&self, state: &State) {
let visible = state.root_visible();
let was_visible = self.visible.replace(visible);
if !visible && was_visible {
let mut foci = self.suspend_restore_kb_foci.borrow_mut();
foci.clear();
for seat in state.globals.seats.lock().values() {
let node = seat.get_keyboard_node();
if node.node_id() != self.id {
foci.push((seat.clone(), Rc::downgrade(&node)));
}
}
}
for output in self.outputs.lock().values() {
output.update_visible();
}
@ -102,20 +82,6 @@ impl DisplayNode {
for seat in state.globals.seats.lock().values() {
seat.set_visible(visible);
}
if visible && !was_visible {
for (seat, node) in mem::take(&mut *self.suspend_restore_kb_foci.borrow_mut()) {
if seat.get_keyboard_node().node_id() == self.id {
if let Some(node) = node.upgrade()
&& node.node_visible()
{
seat.focus_node(node);
} else {
seat.get_fallback_output()
.take_keyboard_navigation_focus(&seat, Direction::Unspecified);
}
}
}
}
if visible {
state.damage(self.extents.get());
}

View file

@ -967,7 +967,7 @@ impl ToplevelData {
}
fd.workspace.remove_fullscreen_node();
if fd.placeholder.is_destroyed() {
state.map_tiled_without_autotile(node);
state.map_tiled(node);
return;
}
let parent = fd.placeholder.tl_data().parent.take().unwrap();
@ -1247,7 +1247,7 @@ pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floati
};
if !floating {
parent.cnode_remove_child2(&*tl, true);
state.map_tiled_without_autotile(tl);
state.map_tiled(tl);
} else if let Some(ws) = data.workspace.get() {
let node_id = data.node_id;
let old_body =
@ -1308,54 +1308,3 @@ pub fn toplevel_set_workspace(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, ws: &
tl.tl_set_fullscreen(true, Some(ws.clone()));
}
}
/// Removes a toplevel from the tree so it can be parked in a scratchpad.
///
/// Returns `true` if the window was hidden. A placeholder, a window without a
/// parent, or a window that refuses to leave fullscreen cannot be parked.
pub fn toplevel_hide_for_scratchpad(tl: Rc<dyn ToplevelNode>) -> bool {
if tl.node_is_placeholder() {
return false;
}
let data = tl.tl_data();
let workspace = data.workspace.get();
if data.is_fullscreen.get() {
tl.clone().tl_set_fullscreen(false, None);
if data.is_fullscreen.get() {
return false;
}
}
let Some(parent) = data.parent.get() else {
return false;
};
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) = &workspace {
for seat in kb_foci {
workspace
.clone()
.node_do_focus(&seat, Direction::Unspecified);
}
}
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(
state: &Rc<State>,
tl: Rc<dyn ToplevelNode>,
ws: &Rc<WorkspaceNode>,
) {
let (width, height) = tl.tl_data().float_size(ws);
state.map_floating(tl.clone(), width, height, ws, None);
}