Skip to content
This repository was archived by the owner on May 3, 2026. It is now read-only.

Commit f6e5b03

Browse files
committed
Add subcommand to set globally reserved zones
This expands on the fullscreen reserved zones introduced in 4172a10, by adding the option to reserve space for non-fullscreen applications. No application can render to these reserved spaces, however the gesture handle will expand into this area when set. To avoid putting too many subcommands on the top level, the command uses the `catacomb msg deadzone` subcommand instead, which the fullscreen deadzone subcommand has been moved to too. Since certain applications, like panels at the top of the screen, are generally better at dealing with these issues than other apps, a new `--orientation` flag has been added which allows applying deadzones to specific device orientations. This means that a panel can work around the deadzone by itself in portrait mode, while the area can be excluded entirely while the panel is outside of the deadzone.
1 parent 4172a10 commit f6e5b03

12 files changed

Lines changed: 297 additions & 173 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1111

1212
- IPC subcommand to retrieve output scale (`catacomb msg scale`)
1313
- IPC subcommand to set gesture handle height (`catacomb msg gesture-handle`)
14-
- IPC subcommand to set fullscreen reserved zones (`catacomb msg fullscreen-deadzone`)
14+
- IPC subcommand to manage exclusive zones (`catacomb msg deadzone`)
1515

1616
### Changed
1717

catacomb_ipc/src/lib.rs

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::{env, process};
2020
#[cfg(feature = "clap")]
2121
use clap::error::{Error as ClapError, ErrorKind as ClapErrorKind};
2222
#[cfg(feature = "clap")]
23-
use clap::{Subcommand, ValueEnum};
23+
use clap::{Args, Subcommand, ValueEnum};
2424
use regex::{Error as RegexError, Regex};
2525
use serde::{Deserialize, Serialize};
2626
#[cfg(feature = "smithay")]
@@ -199,26 +199,76 @@ pub enum IpcMessage {
199199
#[cfg_attr(feature = "clap", clap(long, value_name = "HEIGHT"))]
200200
height: u16,
201201
},
202-
/// Set reserved space in fullscreen mode.
203-
FullscreenDeadzone {
204-
/// Top edge reserved physical pixels.
205-
#[cfg_attr(feature = "clap", clap(long, value_name = "PIXELS", default_value = "0"))]
206-
top: u16,
207-
/// Right edge reserved physical pixels.
208-
#[cfg_attr(feature = "clap", clap(long, value_name = "PIXELS", default_value = "0"))]
209-
right: u16,
210-
/// Bottom edge reserved physical pixels.
211-
#[cfg_attr(feature = "clap", clap(long, value_name = "PIXELS", default_value = "0"))]
212-
bottom: u16,
213-
/// Left edge reserved physical pixels.
214-
#[cfg_attr(feature = "clap", clap(long, value_name = "PIXELS", default_value = "0"))]
215-
left: u16,
216-
},
202+
/// Manage compositor deadzones.
203+
#[cfg_attr(feature = "clap", clap(subcommand))]
204+
Deadzone(Deadzones),
205+
}
206+
207+
/// Compositor deadzones.
208+
#[cfg_attr(feature = "clap", derive(Subcommand))]
209+
#[derive(Deserialize, Serialize, Debug)]
210+
pub enum Deadzones {
211+
/// Set reserved space for fullscreen windows.
212+
Fullscreen(Deadzone),
213+
/// Set reserved space for all windows.
214+
Global(Deadzone),
215+
}
216+
217+
/// Compositor reserved area.
218+
#[cfg_attr(feature = "clap", derive(Args))]
219+
#[derive(Deserialize, Serialize, PartialEq, Eq, Copy, Clone, Default, Debug)]
220+
pub struct Deadzone {
221+
/// Top edge reserved physical pixels.
222+
#[cfg_attr(feature = "clap", clap(long, value_name = "PIXELS", default_value = "0"))]
223+
pub top: u16,
224+
/// Right edge reserved physical pixels.
225+
#[cfg_attr(feature = "clap", clap(long, value_name = "PIXELS", default_value = "0"))]
226+
pub right: u16,
227+
/// Bottom edge reserved physical pixels.
228+
#[cfg_attr(feature = "clap", clap(long, value_name = "PIXELS", default_value = "0"))]
229+
pub bottom: u16,
230+
/// Left edge reserved physical pixels.
231+
#[cfg_attr(feature = "clap", clap(long, value_name = "PIXELS", default_value = "0"))]
232+
pub left: u16,
233+
/// Limit this deadzone to a specific orientation.
234+
#[cfg_attr(feature = "clap", clap(long))]
235+
pub orientation: Option<Orientation>,
236+
}
237+
238+
impl Deadzone {
239+
/// Apply scale and output transform to get the logical deadzone size.
240+
pub fn transform(&self, orientation: Orientation, scale: f64) -> Self {
241+
let mut deadzone = match orientation {
242+
Orientation::Portrait => *self,
243+
Orientation::InversePortrait => Self { top: self.bottom, bottom: self.top, ..*self },
244+
Orientation::Landscape => Self {
245+
orientation: self.orientation,
246+
top: self.right,
247+
right: self.bottom,
248+
bottom: self.left,
249+
left: self.top,
250+
},
251+
Orientation::InverseLandscape => Self {
252+
orientation: self.orientation,
253+
top: self.left,
254+
right: self.top,
255+
bottom: self.right,
256+
left: self.bottom,
257+
},
258+
};
259+
260+
deadzone.top = (deadzone.top as f64 / scale).round() as u16;
261+
deadzone.right = (deadzone.right as f64 / scale).round() as u16;
262+
deadzone.bottom = (deadzone.bottom as f64 / scale).round() as u16;
263+
deadzone.left = (deadzone.left as f64 / scale).round() as u16;
264+
265+
deadzone
266+
}
217267
}
218268

219269
/// Device orientation.
220270
#[cfg_attr(feature = "clap", derive(ValueEnum))]
221-
#[derive(Deserialize, Serialize, Default, PartialEq, Eq, Copy, Clone, Debug)]
271+
#[derive(Deserialize, Serialize, Default, Hash, PartialEq, Eq, Copy, Clone, Debug)]
222272
#[serde(rename_all = "kebab-case")]
223273
pub enum Orientation {
224274
/// Portrait mode.

src/drawing.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -479,9 +479,11 @@ impl Graphics {
479479

480480
// Initialize texture or replace it after scale change.
481481
let scale = canvas.scale();
482-
let width = canvas.physical_size().w;
482+
let width = canvas.output_size_physical().w;
483483
let height = canvas.gesture_handle_height() as i32;
484-
if handle.as_ref().is_none_or(|handle| handle.buffer_size() != (width, height).into()) {
484+
if handle.as_ref().is_none_or(|handle| {
485+
handle.buffer_size() != (width, height).into() || handle.scale != scale
486+
}) {
485487
// Initialize a black buffer with the correct size.
486488
let mut buffer = vec![0; (height * width * 4) as usize];
487489

@@ -515,7 +517,9 @@ impl Graphics {
515517
pub fn cursor(&mut self, renderer: &mut GlesRenderer, canvas: &Canvas) -> RenderTexture {
516518
let scale = canvas.scale();
517519
let size = (CURSOR_SIZE * scale).round() as i32;
518-
if self.cursor.as_ref().is_none_or(|cursor| cursor.buffer_size() != (size, size).into()) {
520+
if self.cursor.as_ref().is_none_or(|cursor| {
521+
cursor.buffer_size() != (size, size).into() || cursor.scale != scale
522+
}) {
519523
// Create a texture with a circle inside it.
520524
let mut buffer = vec![0; (size * size * 4) as usize];
521525
for x in 0..size {

src/input.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl TouchState {
230230
start: Point<f64, Logical>,
231231
end: Option<Point<f64, Logical>>,
232232
) -> impl Iterator<Item = &'a GestureBinding> {
233-
let canvas_size = canvas.size().to_f64();
233+
let canvas_size = canvas.output_size().to_f64();
234234
let start_sector = GestureSector::from_point(canvas_size, start);
235235
let end_sector = end.map(|end| GestureSector::from_point(canvas_size, end));
236236

@@ -325,8 +325,9 @@ impl HandleGesture {
325325

326326
/// Check if a touch should start a new gesture.
327327
fn is_start(canvas: &Canvas, position: Point<f64, Logical>) -> bool {
328-
let wm_size = canvas.wm_size().to_f64();
329-
Rectangle::new((0., wm_size.h).into(), wm_size).contains(position)
328+
let ui_rect = canvas.ui_rect(false).to_f64();
329+
let handle_y = ui_rect.loc.y + ui_rect.size.h;
330+
Rectangle::new((0., handle_y).into(), (f64::MAX, f64::MAX).into()).contains(position)
330331
}
331332
}
332333

@@ -1065,6 +1066,11 @@ impl Catacomb {
10651066
Orientation::InverseLandscape => (height - y, x),
10661067
};
10671068

1069+
// Clamp position within global deadzones.
1070+
let ui_rect = canvas.ui_rect(true).to_f64();
1071+
x = x.clamp(ui_rect.loc.x, ui_rect.loc.x + ui_rect.size.w);
1072+
y = y.clamp(ui_rect.loc.y, ui_rect.loc.y + ui_rect.size.h);
1073+
10681074
(x, y).into()
10691075
}
10701076

src/ipc_server.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ use std::io::{BufRead, BufReader, Write};
66
use std::os::unix::net::{UnixListener, UnixStream};
77
use std::path::PathBuf;
88

9-
use catacomb_ipc::{AppIdMatcher, CliToggle, IpcMessage, Keysym, WindowScale};
9+
use catacomb_ipc::{AppIdMatcher, CliToggle, Deadzones, IpcMessage, Keysym, WindowScale};
1010
use smithay::input::keyboard::XkbConfig;
1111
use smithay::reexports::calloop::LoopHandle;
1212
use tracing::{error, warn};
1313

1414
use crate::catacomb::Catacomb;
1515
use crate::config::{GestureBinding, GestureBindingAction, KeyBinding};
16-
use crate::output::FullscreenDeadzone;
1716
use crate::socket::SocketSource;
1817

1918
/// Create an IPC socket.
@@ -175,10 +174,12 @@ fn handle_message(buffer: &mut String, mut stream: UnixStream, catacomb: &mut Ca
175174
catacomb.draw_cursor = state == CliToggle::On;
176175
},
177176
IpcMessage::GestureHandle { height } => catacomb.windows.set_gesture_handle_height(height),
178-
IpcMessage::FullscreenDeadzone { top, right, bottom, left } => {
179-
let deadzone = FullscreenDeadzone::new(top, right, bottom, left);
177+
IpcMessage::Deadzone(Deadzones::Fullscreen(deadzone)) => {
180178
catacomb.windows.set_fullscreen_deadzone(deadzone);
181179
},
180+
IpcMessage::Deadzone(Deadzones::Global(deadzone)) => {
181+
catacomb.windows.set_global_deadzone(deadzone);
182+
},
182183
// Ignore IPC replies.
183184
IpcMessage::DpmsReply { .. } | IpcMessage::ScaleReply { .. } => (),
184185
}

0 commit comments

Comments
 (0)