mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57f267454f | |||
| 86c4c1368e | |||
| 17c23dc50f | |||
| 5b1de86d33 | |||
| 58162ce685 | |||
| 9ac925ea0c | |||
| 0f83eacb42 | |||
| e259061cbc | |||
| 206493bb35 | |||
| c29a049245 | |||
| d6b62ad09d | |||
| d155f5cd6c | |||
| 74ff4f1903 | |||
| 8c3107af7b | |||
| 8bcd18ace2 | |||
| 4fefab7d6b | |||
| 675932c05b | |||
| 475d6e4be1 | |||
| d9e27988a7 | |||
| 1be860c527 | |||
| b3e0a6c543 | |||
| 23a5bd3670 | |||
| d397375d57 | |||
| cb3ba5105d | |||
| 243519598e | |||
| 0b5f232bc2 | |||
| 9b3478a3d7 | |||
| cb1e5d6c19 | |||
| 11ae17b220 | |||
| 40b633be5c | |||
| 0e29e7f6ff | |||
| 626c720b7a | |||
| 3f76b71115 | |||
| 1599a01f3b |
Generated
+260
-197
File diff suppressed because it is too large
Load Diff
+9
-9
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "niri"
|
name = "niri"
|
||||||
version = "0.1.0-alpha.1"
|
version = "0.1.0-alpha.2"
|
||||||
description = "A scrollable-tiling Wayland compositor"
|
description = "A scrollable-tiling Wayland compositor"
|
||||||
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
@@ -13,30 +13,30 @@ keywords = ["wayland", "compositor", "tiling", "smithay", "wm"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.75" }
|
anyhow = { version = "1.0.75" }
|
||||||
arrayvec = "0.7.4"
|
arrayvec = "0.7.4"
|
||||||
async-channel = { version = "2.1.0", optional = true }
|
async-channel = { version = "2.1.1", optional = true }
|
||||||
async-io = { version = "1.13.0", optional = true }
|
async-io = { version = "1.13.0", optional = true }
|
||||||
bitflags = "2.4.1"
|
bitflags = "2.4.1"
|
||||||
clap = { version = "4.4.8", features = ["derive"] }
|
clap = { version = "4.4.11", features = ["derive"] }
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
git-version = "0.3.8"
|
git-version = "0.3.9"
|
||||||
keyframe = { version = "1.1.1", default-features = false }
|
keyframe = { version = "1.1.1", default-features = false }
|
||||||
knuffel = "3.2.0"
|
knuffel = "3.2.0"
|
||||||
libc = "0.2.150"
|
libc = "0.2.151"
|
||||||
logind-zbus = { version = "3.1.2", optional = true }
|
logind-zbus = { version = "3.1.2", optional = true }
|
||||||
log = { version = "0.4.20", features = ["max_level_trace", "release_max_level_debug"] }
|
log = { version = "0.4.20", features = ["max_level_trace", "release_max_level_debug"] }
|
||||||
miette = "5.10.0"
|
miette = "5.10.0"
|
||||||
notify-rust = { version = "4.10.0", optional = true }
|
notify-rust = { version = "4.10.0", optional = true }
|
||||||
pipewire = { version = "0.7.2", optional = true }
|
pipewire = { version = "0.7.2", optional = true }
|
||||||
png = "0.17.10"
|
png = "0.17.10"
|
||||||
portable-atomic = { version = "1.5.1", default-features = false, features = ["float"] }
|
portable-atomic = { version = "1.6.0", default-features = false, features = ["float"] }
|
||||||
profiling = "1.0.11"
|
profiling = "1.0.12"
|
||||||
sd-notify = "0.4.1"
|
sd-notify = "0.4.1"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
tracing = { version = "0.1.40", features = ["max_level_trace", "release_max_level_debug"] }
|
tracing = { version = "0.1.40", features = ["max_level_trace", "release_max_level_debug"] }
|
||||||
tracy-client = { version = "0.16.4", default-features = false }
|
tracy-client = { version = "0.16.4", default-features = false }
|
||||||
url = { version = "2.5.0", optional = true }
|
url = { version = "2.5.0", optional = true }
|
||||||
xcursor = "0.3.4"
|
xcursor = "0.3.5"
|
||||||
zbus = { version = "3.14.1", optional = true }
|
zbus = { version = "3.14.1", optional = true }
|
||||||
|
|
||||||
[dependencies.smithay]
|
[dependencies.smithay]
|
||||||
@@ -52,7 +52,6 @@ features = [
|
|||||||
"backend_udev",
|
"backend_udev",
|
||||||
"backend_winit",
|
"backend_winit",
|
||||||
"desktop",
|
"desktop",
|
||||||
"libinput_1_19",
|
|
||||||
"renderer_gl",
|
"renderer_gl",
|
||||||
"renderer_multi",
|
"renderer_multi",
|
||||||
"use_system_lib",
|
"use_system_lib",
|
||||||
@@ -82,6 +81,7 @@ overflow-checks = true
|
|||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|
||||||
[package.metadata.generate-rpm]
|
[package.metadata.generate-rpm]
|
||||||
|
version = "0.1.0~alpha.2"
|
||||||
assets = [
|
assets = [
|
||||||
{ source = "target/release/niri", dest = "/usr/bin/", mode = "755" },
|
{ source = "target/release/niri", dest = "/usr/bin/", mode = "755" },
|
||||||
{ source = "resources/niri-session", dest = "/usr/bin/", mode = "755" },
|
{ source = "resources/niri-session", dest = "/usr/bin/", mode = "755" },
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ When a monitor disconnects, its workspaces will move to another monitor, but upo
|
|||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
For Fedora users, there's a COPR with built and packaged niri: https://copr.fedorainfracloud.org/coprs/yalter/niri/
|
||||||
|
|
||||||
First, install the dependencies for your distribution.
|
First, install the dependencies for your distribution.
|
||||||
|
|
||||||
- Ubuntu:
|
- Ubuntu:
|
||||||
@@ -84,6 +86,7 @@ Starting it from there will run niri as a desktop session.
|
|||||||
|
|
||||||
The niri session will autostart apps through the systemd xdg-autostart target.
|
The niri session will autostart apps through the systemd xdg-autostart target.
|
||||||
You can also autostart systemd services like [mako] by symlinking them into `$HOME/.config/systemd/user/niri.service.wants/`.
|
You can also autostart systemd services like [mako] by symlinking them into `$HOME/.config/systemd/user/niri.service.wants/`.
|
||||||
|
A step-by-step process for this is explained [on the wiki](https://github.com/YaLTeR/niri/wiki/Example-systemd-Setup).
|
||||||
|
|
||||||
Niri also works with some parts of xdg-desktop-portal-gnome.
|
Niri also works with some parts of xdg-desktop-portal-gnome.
|
||||||
In particular, it supports file choosers and monitor screencasting (e.g. to [OBS]).
|
In particular, it supports file choosers and monitor screencasting (e.g. to [OBS]).
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ input {
|
|||||||
// The built-in laptop monitor is usually called "eDP-1".
|
// The built-in laptop monitor is usually called "eDP-1".
|
||||||
// Remember to uncommend the node by removing "/-"!
|
// Remember to uncommend the node by removing "/-"!
|
||||||
/-output "eDP-1" {
|
/-output "eDP-1" {
|
||||||
|
// Uncomment this line to disable this output.
|
||||||
|
// off
|
||||||
|
|
||||||
// Scale is a floating-point number, but at the moment only integer values work.
|
// Scale is a floating-point number, but at the moment only integer values work.
|
||||||
scale 2.0
|
scale 2.0
|
||||||
|
|
||||||
@@ -119,6 +122,18 @@ default-column-width { proportion 0.5; }
|
|||||||
// Set gaps around windows in logical pixels.
|
// Set gaps around windows in logical pixels.
|
||||||
gaps 16
|
gaps 16
|
||||||
|
|
||||||
|
// Struts shrink the area occupied by windows, similarly to layer-shell panels.
|
||||||
|
// You can think of them as a kind of outer gaps. They are set in logical pixels.
|
||||||
|
// Left and right struts will cause the next window to the side to always be visible.
|
||||||
|
// Top and bottom struts will simply add outer gaps in addition to the area occupied by
|
||||||
|
// layer-shell panels and regular gaps.
|
||||||
|
struts {
|
||||||
|
// left 64
|
||||||
|
// right 64
|
||||||
|
// top 64
|
||||||
|
// bottom 64
|
||||||
|
}
|
||||||
|
|
||||||
// You can change the path where screenshots are saved.
|
// You can change the path where screenshots are saved.
|
||||||
// A ~ at the front will be expanded to the home directory.
|
// A ~ at the front will be expanded to the home directory.
|
||||||
// The path is formatted with strftime(3) to give you the screenshot date and time.
|
// The path is formatted with strftime(3) to give you the screenshot date and time.
|
||||||
@@ -164,6 +179,13 @@ binds {
|
|||||||
Mod+Ctrl+Up { move-window-up; }
|
Mod+Ctrl+Up { move-window-up; }
|
||||||
Mod+Ctrl+Right { move-column-right; }
|
Mod+Ctrl+Right { move-column-right; }
|
||||||
|
|
||||||
|
// Alternative commands that move across workspaces when reaching
|
||||||
|
// the first or last window in a column.
|
||||||
|
// Mod+J { focus-window-or-workspace-down; }
|
||||||
|
// Mod+K { focus-window-or-workspace-up; }
|
||||||
|
// Mod+Ctrl+J { move-window-down-or-to-workspace-down; }
|
||||||
|
// Mod+Ctrl+K { move-window-up-or-to-workspace-up; }
|
||||||
|
|
||||||
Mod+Shift+H { focus-monitor-left; }
|
Mod+Shift+H { focus-monitor-left; }
|
||||||
Mod+Shift+J { focus-monitor-down; }
|
Mod+Shift+J { focus-monitor-down; }
|
||||||
Mod+Shift+K { focus-monitor-up; }
|
Mod+Shift+K { focus-monitor-up; }
|
||||||
|
|||||||
@@ -431,6 +431,11 @@ impl Tty {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if config.off {
|
||||||
|
debug!("output is disabled in the config");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let device = self
|
let device = self
|
||||||
.output_device
|
.output_device
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|||||||
+48
-2
@@ -6,7 +6,7 @@ use directories::ProjectDirs;
|
|||||||
use miette::{miette, Context, IntoDiagnostic};
|
use miette::{miette, Context, IntoDiagnostic};
|
||||||
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
|
use smithay::input::keyboard::keysyms::KEY_NoSymbol;
|
||||||
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
|
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
|
||||||
use smithay::input::keyboard::Keysym;
|
use smithay::input::keyboard::{Keysym, XkbConfig};
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, PartialEq)]
|
#[derive(knuffel::Decode, Debug, PartialEq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@@ -28,6 +28,8 @@ pub struct Config {
|
|||||||
pub default_column_width: Option<DefaultColumnWidth>,
|
pub default_column_width: Option<DefaultColumnWidth>,
|
||||||
#[knuffel(child, unwrap(argument), default = 16)]
|
#[knuffel(child, unwrap(argument), default = 16)]
|
||||||
pub gaps: u16,
|
pub gaps: u16,
|
||||||
|
#[knuffel(child, default)]
|
||||||
|
pub struts: Struts,
|
||||||
#[knuffel(
|
#[knuffel(
|
||||||
child,
|
child,
|
||||||
unwrap(argument),
|
unwrap(argument),
|
||||||
@@ -66,7 +68,7 @@ pub struct Keyboard {
|
|||||||
pub track_layout: TrackLayout,
|
pub track_layout: TrackLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
|
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq, Clone)]
|
||||||
pub struct Xkb {
|
pub struct Xkb {
|
||||||
#[knuffel(child, unwrap(argument), default)]
|
#[knuffel(child, unwrap(argument), default)]
|
||||||
pub rules: String,
|
pub rules: String,
|
||||||
@@ -80,6 +82,18 @@ pub struct Xkb {
|
|||||||
pub options: Option<String>,
|
pub options: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Xkb {
|
||||||
|
pub fn to_xkb_config(&self) -> XkbConfig {
|
||||||
|
XkbConfig {
|
||||||
|
rules: &self.rules,
|
||||||
|
model: &self.model,
|
||||||
|
layout: self.layout.as_deref().unwrap_or("us"),
|
||||||
|
variant: &self.variant,
|
||||||
|
options: self.options.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)]
|
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum TrackLayout {
|
pub enum TrackLayout {
|
||||||
/// The layout change is global.
|
/// The layout change is global.
|
||||||
@@ -108,6 +122,8 @@ pub struct Tablet {
|
|||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub off: bool,
|
||||||
#[knuffel(argument)]
|
#[knuffel(argument)]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[knuffel(child, unwrap(argument), default = 1.)]
|
#[knuffel(child, unwrap(argument), default = 1.)]
|
||||||
@@ -121,6 +137,7 @@ pub struct Output {
|
|||||||
impl Default for Output {
|
impl Default for Output {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
off: false,
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
scale: 1.,
|
scale: 1.,
|
||||||
position: None,
|
position: None,
|
||||||
@@ -223,6 +240,18 @@ pub enum PresetWidth {
|
|||||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||||
pub struct DefaultColumnWidth(#[knuffel(children)] pub Vec<PresetWidth>);
|
pub struct DefaultColumnWidth(#[knuffel(children)] pub Vec<PresetWidth>);
|
||||||
|
|
||||||
|
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Struts {
|
||||||
|
#[knuffel(child, unwrap(argument), default)]
|
||||||
|
pub left: u16,
|
||||||
|
#[knuffel(child, unwrap(argument), default)]
|
||||||
|
pub right: u16,
|
||||||
|
#[knuffel(child, unwrap(argument), default)]
|
||||||
|
pub top: u16,
|
||||||
|
#[knuffel(child, unwrap(argument), default)]
|
||||||
|
pub bottom: u16,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||||
pub struct Binds(#[knuffel(children)] pub Vec<Bind>);
|
pub struct Binds(#[knuffel(children)] pub Vec<Bind>);
|
||||||
|
|
||||||
@@ -273,10 +302,14 @@ pub enum Action {
|
|||||||
FocusColumnRight,
|
FocusColumnRight,
|
||||||
FocusWindowDown,
|
FocusWindowDown,
|
||||||
FocusWindowUp,
|
FocusWindowUp,
|
||||||
|
FocusWindowOrWorkspaceDown,
|
||||||
|
FocusWindowOrWorkspaceUp,
|
||||||
MoveColumnLeft,
|
MoveColumnLeft,
|
||||||
MoveColumnRight,
|
MoveColumnRight,
|
||||||
MoveWindowDown,
|
MoveWindowDown,
|
||||||
MoveWindowUp,
|
MoveWindowUp,
|
||||||
|
MoveWindowDownOrToWorkspaceDown,
|
||||||
|
MoveWindowUpOrToWorkspaceUp,
|
||||||
ConsumeWindowIntoColumn,
|
ConsumeWindowIntoColumn,
|
||||||
ExpelWindowFromColumn,
|
ExpelWindowFromColumn,
|
||||||
CenterColumn,
|
CenterColumn,
|
||||||
@@ -575,6 +608,12 @@ mod tests {
|
|||||||
|
|
||||||
gaps 8
|
gaps 8
|
||||||
|
|
||||||
|
struts {
|
||||||
|
left 1
|
||||||
|
right 2
|
||||||
|
top 3
|
||||||
|
}
|
||||||
|
|
||||||
screenshot-path "~/Screenshots/screenshot.png"
|
screenshot-path "~/Screenshots/screenshot.png"
|
||||||
|
|
||||||
binds {
|
binds {
|
||||||
@@ -612,6 +651,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
outputs: vec![Output {
|
outputs: vec![Output {
|
||||||
|
off: false,
|
||||||
name: "eDP-1".to_owned(),
|
name: "eDP-1".to_owned(),
|
||||||
scale: 2.,
|
scale: 2.,
|
||||||
position: Some(Position { x: 10, y: 20 }),
|
position: Some(Position { x: 10, y: 20 }),
|
||||||
@@ -653,6 +693,12 @@ mod tests {
|
|||||||
],
|
],
|
||||||
default_column_width: Some(DefaultColumnWidth(vec![PresetWidth::Proportion(0.25)])),
|
default_column_width: Some(DefaultColumnWidth(vec![PresetWidth::Proportion(0.25)])),
|
||||||
gaps: 8,
|
gaps: 8,
|
||||||
|
struts: Struts {
|
||||||
|
left: 1,
|
||||||
|
right: 2,
|
||||||
|
top: 3,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
screenshot_path: Some(String::from("~/Screenshots/screenshot.png")),
|
screenshot_path: Some(String::from("~/Screenshots/screenshot.png")),
|
||||||
binds: Binds(vec![
|
binds: Binds(vec![
|
||||||
Bind {
|
Bind {
|
||||||
|
|||||||
+11
-11
@@ -1,7 +1,6 @@
|
|||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
use smithay::backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state};
|
use smithay::backend::renderer::utils::{on_commit_buffer_handler, with_renderer_surface_state};
|
||||||
use smithay::desktop::find_popup_root_surface;
|
|
||||||
use smithay::input::pointer::CursorImageStatus;
|
use smithay::input::pointer::CursorImageStatus;
|
||||||
use smithay::reexports::calloop::Interest;
|
use smithay::reexports::calloop::Interest;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_buffer;
|
use smithay::reexports::wayland_server::protocol::wl_buffer;
|
||||||
@@ -30,7 +29,12 @@ impl CompositorHandler for State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_subsurface(&mut self, surface: &WlSurface, parent: &WlSurface) {
|
fn new_subsurface(&mut self, surface: &WlSurface, parent: &WlSurface) {
|
||||||
if let Some((_, output)) = self.niri.layout.find_window_and_output(parent) {
|
let mut root = parent.clone();
|
||||||
|
while let Some(parent) = get_parent(&root) {
|
||||||
|
root = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(output) = self.niri.output_for_root(&root) {
|
||||||
let scale = output.current_scale().integer_scale();
|
let scale = output.current_scale().integer_scale();
|
||||||
let transform = output.current_transform();
|
let transform = output.current_transform();
|
||||||
with_states(surface, |data| {
|
with_states(surface, |data| {
|
||||||
@@ -98,11 +102,7 @@ impl CompositorHandler for State {
|
|||||||
let window = entry.remove();
|
let window = entry.remove();
|
||||||
window.on_commit();
|
window.on_commit();
|
||||||
|
|
||||||
if let Some(output) = self
|
if let Some(output) = self.niri.layout.add_window(window, None, false).cloned()
|
||||||
.niri
|
|
||||||
.layout
|
|
||||||
.add_window(window, true, None, false)
|
|
||||||
.cloned()
|
|
||||||
{
|
{
|
||||||
self.niri.queue_redraw(output);
|
self.niri.queue_redraw(output);
|
||||||
}
|
}
|
||||||
@@ -135,6 +135,9 @@ impl CompositorHandler for State {
|
|||||||
// The toplevel remains mapped.
|
// The toplevel remains mapped.
|
||||||
self.niri.layout.update_window(&window);
|
self.niri.layout.update_window(&window);
|
||||||
|
|
||||||
|
// Popup placement depends on window size which might have changed.
|
||||||
|
self.update_reactive_popups(&window, &output);
|
||||||
|
|
||||||
self.niri.queue_redraw(output);
|
self.niri.queue_redraw(output);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -154,13 +157,10 @@ impl CompositorHandler for State {
|
|||||||
// This might be a popup.
|
// This might be a popup.
|
||||||
self.popups_handle_commit(surface);
|
self.popups_handle_commit(surface);
|
||||||
if let Some(popup) = self.niri.popups.find_popup(surface) {
|
if let Some(popup) = self.niri.popups.find_popup(surface) {
|
||||||
if let Ok(root) = find_popup_root_surface(&popup) {
|
if let Some(output) = self.output_for_popup(&popup) {
|
||||||
let root_window_output = self.niri.layout.find_window_and_output(&root);
|
|
||||||
if let Some((_window, output)) = root_window_output {
|
|
||||||
self.niri.queue_redraw(output);
|
self.niri.queue_redraw(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// This might be a layer-shell surface.
|
// This might be a layer-shell surface.
|
||||||
self.layer_shell_handle_commit(surface);
|
self.layer_shell_handle_commit(surface);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use smithay::wayland::shell::wlr_layer::{
|
|||||||
Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler,
|
Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler,
|
||||||
WlrLayerShellState,
|
WlrLayerShellState,
|
||||||
};
|
};
|
||||||
|
use smithay::wayland::shell::xdg::PopupSurface;
|
||||||
|
|
||||||
use crate::niri::State;
|
use crate::niri::State;
|
||||||
|
|
||||||
@@ -52,6 +53,10 @@ impl WlrLayerShellHandler for State {
|
|||||||
self.niri.output_resized(output);
|
self.niri.output_resized(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_popup(&mut self, _parent: WlrLayerSurface, popup: PopupSurface) {
|
||||||
|
self.unconstrain_popup(&popup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delegate_layer_shell!(State);
|
delegate_layer_shell!(State);
|
||||||
|
|
||||||
|
|||||||
+20
-9
@@ -11,7 +11,7 @@ use std::thread;
|
|||||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||||
use smithay::backend::renderer::ImportDma;
|
use smithay::backend::renderer::ImportDma;
|
||||||
use smithay::desktop::{PopupKind, PopupManager};
|
use smithay::desktop::{PopupKind, PopupManager};
|
||||||
use smithay::input::pointer::{CursorIcon, CursorImageStatus};
|
use smithay::input::pointer::{CursorIcon, CursorImageStatus, PointerHandle};
|
||||||
use smithay::input::{Seat, SeatHandler, SeatState};
|
use smithay::input::{Seat, SeatHandler, SeatState};
|
||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_data_source::WlDataSource;
|
use smithay::reexports::wayland_server::protocol::wl_data_source::WlDataSource;
|
||||||
@@ -22,6 +22,7 @@ use smithay::utils::{Logical, Rectangle, Size};
|
|||||||
use smithay::wayland::compositor::{send_surface_state, with_states};
|
use smithay::wayland::compositor::{send_surface_state, with_states};
|
||||||
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
|
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
|
||||||
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
||||||
|
use smithay::wayland::pointer_constraints::PointerConstraintsHandler;
|
||||||
use smithay::wayland::selection::data_device::{
|
use smithay::wayland::selection::data_device::{
|
||||||
set_data_device_focus, ClientDndGrabHandler, DataDeviceHandler, DataDeviceState,
|
set_data_device_focus, ClientDndGrabHandler, DataDeviceHandler, DataDeviceState,
|
||||||
ServerDndGrabHandler,
|
ServerDndGrabHandler,
|
||||||
@@ -36,9 +37,10 @@ use smithay::wayland::session_lock::{
|
|||||||
};
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
delegate_cursor_shape, delegate_data_control, delegate_data_device, delegate_dmabuf,
|
delegate_cursor_shape, delegate_data_control, delegate_data_device, delegate_dmabuf,
|
||||||
delegate_input_method_manager, delegate_output, delegate_pointer_gestures,
|
delegate_input_method_manager, delegate_output, delegate_pointer_constraints,
|
||||||
delegate_presentation, delegate_primary_selection, delegate_seat, delegate_session_lock,
|
delegate_pointer_gestures, delegate_presentation, delegate_primary_selection,
|
||||||
delegate_tablet_manager, delegate_text_input_manager, delegate_virtual_keyboard_manager,
|
delegate_relative_pointer, delegate_seat, delegate_session_lock, delegate_tablet_manager,
|
||||||
|
delegate_text_input_manager, delegate_virtual_keyboard_manager,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::layout::output_size;
|
use crate::layout::output_size;
|
||||||
@@ -74,14 +76,23 @@ delegate_seat!(State);
|
|||||||
delegate_cursor_shape!(State);
|
delegate_cursor_shape!(State);
|
||||||
delegate_tablet_manager!(State);
|
delegate_tablet_manager!(State);
|
||||||
delegate_pointer_gestures!(State);
|
delegate_pointer_gestures!(State);
|
||||||
|
delegate_relative_pointer!(State);
|
||||||
delegate_text_input_manager!(State);
|
delegate_text_input_manager!(State);
|
||||||
|
|
||||||
|
impl PointerConstraintsHandler for State {
|
||||||
|
fn new_constraint(&mut self, _surface: &WlSurface, pointer: &PointerHandle<Self>) {
|
||||||
|
self.niri.maybe_activate_pointer_constraint(
|
||||||
|
pointer.current_location(),
|
||||||
|
&self.niri.pointer_focus,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate_pointer_constraints!(State);
|
||||||
|
|
||||||
impl InputMethodHandler for State {
|
impl InputMethodHandler for State {
|
||||||
fn new_popup(&mut self, surface: PopupSurface) {
|
fn new_popup(&mut self, surface: PopupSurface) {
|
||||||
if let Some((_, output)) = surface
|
let popup = PopupKind::from(surface.clone());
|
||||||
.get_parent()
|
if let Some(output) = self.output_for_popup(&popup) {
|
||||||
.and_then(|parent| self.niri.layout.find_window_and_output(&parent.surface))
|
|
||||||
{
|
|
||||||
let scale = output.current_scale().integer_scale();
|
let scale = output.current_scale().integer_scale();
|
||||||
let transform = output.current_transform();
|
let transform = output.current_transform();
|
||||||
let wl_surface = surface.wl_surface();
|
let wl_surface = surface.wl_surface();
|
||||||
@@ -89,7 +100,7 @@ impl InputMethodHandler for State {
|
|||||||
send_surface_state(wl_surface, data, scale, transform);
|
send_surface_state(wl_surface, data, scale, transform);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if let Err(err) = self.niri.popups.track_popup(PopupKind::from(surface)) {
|
if let Err(err) = self.niri.popups.track_popup(popup) {
|
||||||
warn!("error tracking ime popup {err:?}");
|
warn!("error tracking ime popup {err:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+140
-16
@@ -1,11 +1,15 @@
|
|||||||
use smithay::desktop::{find_popup_root_surface, PopupKind, Window};
|
use smithay::desktop::{
|
||||||
|
find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, LayerSurface,
|
||||||
|
PopupKind, PopupManager, Window, WindowSurfaceType,
|
||||||
|
};
|
||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||||
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_positioner::ConstraintAdjustment;
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge};
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge};
|
||||||
use smithay::reexports::wayland_server::protocol::wl_output;
|
use smithay::reexports::wayland_server::protocol::wl_output;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
|
use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
use smithay::utils::Serial;
|
use smithay::utils::{Logical, Rectangle, Serial};
|
||||||
use smithay::wayland::compositor::{send_surface_state, with_states};
|
use smithay::wayland::compositor::{send_surface_state, with_states};
|
||||||
use smithay::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState};
|
use smithay::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState};
|
||||||
use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;
|
use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;
|
||||||
@@ -49,8 +53,8 @@ impl XdgShellHandler for State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
|
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
|
||||||
// FIXME: adjust the geometry so the popup doesn't overflow at least off the top and bottom
|
self.unconstrain_popup(&surface);
|
||||||
// screen edges, and ideally off the view size.
|
|
||||||
if let Err(err) = self.niri.popups.track_popup(PopupKind::Xdg(surface)) {
|
if let Err(err) = self.niri.popups.track_popup(PopupKind::Xdg(surface)) {
|
||||||
warn!("error tracking popup: {err:?}");
|
warn!("error tracking popup: {err:?}");
|
||||||
}
|
}
|
||||||
@@ -76,13 +80,12 @@ impl XdgShellHandler for State {
|
|||||||
positioner: PositionerState,
|
positioner: PositionerState,
|
||||||
token: u32,
|
token: u32,
|
||||||
) {
|
) {
|
||||||
// FIXME: adjust the geometry so the popup doesn't overflow at least off the top and bottom
|
|
||||||
// screen edges, and ideally off the view size.
|
|
||||||
surface.with_pending_state(|state| {
|
surface.with_pending_state(|state| {
|
||||||
let geometry = positioner.get_geometry();
|
let geometry = positioner.get_geometry();
|
||||||
state.geometry = geometry;
|
state.geometry = geometry;
|
||||||
state.positioner = positioner;
|
state.positioner = positioner;
|
||||||
});
|
});
|
||||||
|
self.unconstrain_popup(&surface);
|
||||||
surface.send_repositioned(token);
|
surface.send_repositioned(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,14 +178,11 @@ impl XdgShellHandler for State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn popup_destroyed(&mut self, surface: PopupSurface) {
|
fn popup_destroyed(&mut self, surface: PopupSurface) {
|
||||||
if let Ok(root) = find_popup_root_surface(&surface.into()) {
|
if let Some(output) = self.output_for_popup(&PopupKind::Xdg(surface)) {
|
||||||
let root_window_output = self.niri.layout.find_window_and_output(&root);
|
|
||||||
if let Some((_window, output)) = root_window_output {
|
|
||||||
self.niri.queue_redraw(output);
|
self.niri.queue_redraw(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
delegate_xdg_shell!(State);
|
delegate_xdg_shell!(State);
|
||||||
|
|
||||||
@@ -271,12 +271,8 @@ impl State {
|
|||||||
.initial_configure_sent
|
.initial_configure_sent
|
||||||
});
|
});
|
||||||
if !initial_configure_sent {
|
if !initial_configure_sent {
|
||||||
if let Some(output) = popup.get_parent_surface().and_then(|parent| {
|
if let Some(output) = self.output_for_popup(&PopupKind::Xdg(popup.clone()))
|
||||||
self.niri
|
{
|
||||||
.layout
|
|
||||||
.find_window_and_output(&parent)
|
|
||||||
.map(|(_, output)| output)
|
|
||||||
}) {
|
|
||||||
let scale = output.current_scale().integer_scale();
|
let scale = output.current_scale().integer_scale();
|
||||||
let transform = output.current_transform();
|
let transform = output.current_transform();
|
||||||
with_states(surface, |data| {
|
with_states(surface, |data| {
|
||||||
@@ -291,4 +287,132 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn output_for_popup(&self, popup: &PopupKind) -> Option<Output> {
|
||||||
|
let root = find_popup_root_surface(popup).ok()?;
|
||||||
|
self.niri.output_for_root(&root)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unconstrain_popup(&self, popup: &PopupSurface) {
|
||||||
|
let _span = tracy_client::span!("Niri::unconstrain_popup");
|
||||||
|
|
||||||
|
// Popups with a NULL parent will get repositioned in their respective protocol handlers
|
||||||
|
// (i.e. layer-shell).
|
||||||
|
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Figure out if the root is a window or a layer surface.
|
||||||
|
if let Some((window, output)) = self.niri.layout.find_window_and_output(&root) {
|
||||||
|
self.unconstrain_window_popup(popup, &window, &output);
|
||||||
|
} else if let Some((layer_surface, output)) = self.niri.layout.outputs().find_map(|o| {
|
||||||
|
let map = layer_map_for_output(o);
|
||||||
|
let layer_surface = map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)?;
|
||||||
|
Some((layer_surface.clone(), o))
|
||||||
|
}) {
|
||||||
|
self.unconstrain_layer_shell_popup(popup, &layer_surface, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unconstrain_window_popup(&self, popup: &PopupSurface, window: &Window, output: &Output) {
|
||||||
|
let window_geo = window.geometry();
|
||||||
|
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||||
|
|
||||||
|
// The target geometry for the positioner should be relative to its parent's geometry, so
|
||||||
|
// we will compute that here.
|
||||||
|
//
|
||||||
|
// We try to keep regular window popups within the window itself horizontally (since the
|
||||||
|
// window can be scrolled to both edges of the screen), but within the whole monitor's
|
||||||
|
// height.
|
||||||
|
let mut target =
|
||||||
|
Rectangle::from_loc_and_size((0, 0), (window_geo.size.w, output_geo.size.h));
|
||||||
|
target.loc.y -= self.niri.layout.window_y(window).unwrap();
|
||||||
|
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
|
||||||
|
|
||||||
|
popup.with_pending_state(|state| {
|
||||||
|
state.geometry = unconstrain_with_padding(state.positioner, target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unconstrain_layer_shell_popup(
|
||||||
|
&self,
|
||||||
|
popup: &PopupSurface,
|
||||||
|
layer_surface: &LayerSurface,
|
||||||
|
output: &Output,
|
||||||
|
) {
|
||||||
|
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||||
|
let map = layer_map_for_output(output);
|
||||||
|
let Some(layer_geo) = map.layer_geometry(layer_surface) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The target geometry for the positioner should be relative to its parent's geometry, so
|
||||||
|
// we will compute that here.
|
||||||
|
let mut target = Rectangle::from_loc_and_size((0, 0), output_geo.size);
|
||||||
|
target.loc -= layer_geo.loc;
|
||||||
|
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
|
||||||
|
|
||||||
|
popup.with_pending_state(|state| {
|
||||||
|
state.geometry = unconstrain_with_padding(state.positioner, target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_reactive_popups(&self, window: &Window, output: &Output) {
|
||||||
|
let _span = tracy_client::span!("Niri::update_reactive_popups");
|
||||||
|
|
||||||
|
for (popup, _) in PopupManager::popups_for_surface(window.toplevel().wl_surface()) {
|
||||||
|
match popup {
|
||||||
|
PopupKind::Xdg(ref popup) => {
|
||||||
|
if popup.with_pending_state(|state| state.positioner.reactive) {
|
||||||
|
self.unconstrain_window_popup(popup, window, output);
|
||||||
|
if let Err(err) = popup.send_pending_configure() {
|
||||||
|
warn!("error re-configuring reactive popup: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PopupKind::InputMethod(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unconstrain_with_padding(
|
||||||
|
positioner: PositionerState,
|
||||||
|
target: Rectangle<i32, Logical>,
|
||||||
|
) -> Rectangle<i32, Logical> {
|
||||||
|
// Try unconstraining with a small padding first which looks nicer, then if it doesn't fit try
|
||||||
|
// unconstraining without padding.
|
||||||
|
const PADDING: i32 = 8;
|
||||||
|
|
||||||
|
let mut padded = target;
|
||||||
|
if PADDING * 2 < padded.size.w {
|
||||||
|
padded.loc.x += PADDING;
|
||||||
|
padded.size.w -= PADDING * 2;
|
||||||
|
}
|
||||||
|
if PADDING * 2 < padded.size.h {
|
||||||
|
padded.loc.y += PADDING;
|
||||||
|
padded.size.h -= PADDING * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No padding, so just unconstrain with the original target.
|
||||||
|
if padded == target {
|
||||||
|
return positioner.get_unconstrained_geometry(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not try to resize to fit the padded target rectangle.
|
||||||
|
let mut no_resize = positioner;
|
||||||
|
no_resize
|
||||||
|
.constraint_adjustment
|
||||||
|
.remove(ConstraintAdjustment::ResizeX);
|
||||||
|
no_resize
|
||||||
|
.constraint_adjustment
|
||||||
|
.remove(ConstraintAdjustment::ResizeY);
|
||||||
|
|
||||||
|
let geo = no_resize.get_unconstrained_geometry(padded);
|
||||||
|
if padded.contains_rect(geo) {
|
||||||
|
return geo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Could not unconstrain into the padded target, so resort to the regular one.
|
||||||
|
positioner.get_unconstrained_geometry(target)
|
||||||
}
|
}
|
||||||
|
|||||||
+387
-153
@@ -1,3 +1,4 @@
|
|||||||
|
use std::any::Any;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use smithay::backend::input::{
|
use smithay::backend::input::{
|
||||||
@@ -14,7 +15,9 @@ use smithay::input::pointer::{
|
|||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent, RelativeMotionEvent,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent, RelativeMotionEvent,
|
||||||
};
|
};
|
||||||
use smithay::utils::SERIAL_COUNTER;
|
use smithay::reexports::input;
|
||||||
|
use smithay::utils::{Logical, Point, SERIAL_COUNTER};
|
||||||
|
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
|
||||||
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
|
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
|
||||||
|
|
||||||
use crate::config::{Action, Binds, LayoutAction, Modifiers};
|
use crate::config::{Action, Binds, LayoutAction, Modifiers};
|
||||||
@@ -28,8 +31,16 @@ pub enum CompositorMod {
|
|||||||
Alt,
|
Alt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct TabletData {
|
||||||
|
pub aspect_ratio: f64,
|
||||||
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) {
|
pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>)
|
||||||
|
where
|
||||||
|
I::Device: 'static, // Needed for downcasting.
|
||||||
|
{
|
||||||
let _span = tracy_client::span!("process_input_event");
|
let _span = tracy_client::span!("process_input_event");
|
||||||
|
|
||||||
// A bit of a hack, but animation end runs some logic (i.e. workspace clean-up) and it
|
// A bit of a hack, but animation end runs some logic (i.e. workspace clean-up) and it
|
||||||
@@ -43,10 +54,136 @@ impl State {
|
|||||||
self.niri.activate_monitors(&self.backend);
|
self.niri.activate_monitors(&self.backend);
|
||||||
}
|
}
|
||||||
|
|
||||||
let comp_mod = self.backend.mod_key();
|
use InputEvent::*;
|
||||||
|
match event {
|
||||||
|
DeviceAdded { device } => self.on_device_added(device),
|
||||||
|
DeviceRemoved { device } => self.on_device_removed(device),
|
||||||
|
Keyboard { event } => self.on_keyboard::<I>(event),
|
||||||
|
PointerMotion { event } => self.on_pointer_motion::<I>(event),
|
||||||
|
PointerMotionAbsolute { event } => self.on_pointer_motion_absolute::<I>(event),
|
||||||
|
PointerButton { event } => self.on_pointer_button::<I>(event),
|
||||||
|
PointerAxis { event } => self.on_pointer_axis::<I>(event),
|
||||||
|
TabletToolAxis { event } => self.on_tablet_tool_axis::<I>(event),
|
||||||
|
TabletToolTip { event } => self.on_tablet_tool_tip::<I>(event),
|
||||||
|
TabletToolProximity { event } => self.on_tablet_tool_proximity::<I>(event),
|
||||||
|
TabletToolButton { event } => self.on_tablet_tool_button::<I>(event),
|
||||||
|
GestureSwipeBegin { event } => self.on_gesture_swipe_begin::<I>(event),
|
||||||
|
GestureSwipeUpdate { event } => self.on_gesture_swipe_update::<I>(event),
|
||||||
|
GestureSwipeEnd { event } => self.on_gesture_swipe_end::<I>(event),
|
||||||
|
GesturePinchBegin { event } => self.on_gesture_pinch_begin::<I>(event),
|
||||||
|
GesturePinchUpdate { event } => self.on_gesture_pinch_update::<I>(event),
|
||||||
|
GesturePinchEnd { event } => self.on_gesture_pinch_end::<I>(event),
|
||||||
|
GestureHoldBegin { event } => self.on_gesture_hold_begin::<I>(event),
|
||||||
|
GestureHoldEnd { event } => self.on_gesture_hold_end::<I>(event),
|
||||||
|
TouchDown { .. } => (),
|
||||||
|
TouchMotion { .. } => (),
|
||||||
|
TouchUp { .. } => (),
|
||||||
|
TouchCancel { .. } => (),
|
||||||
|
TouchFrame { .. } => (),
|
||||||
|
Special(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_libinput_event(&mut self, event: &mut InputEvent<LibinputInputBackend>) {
|
||||||
|
let _span = tracy_client::span!("process_libinput_event");
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
InputEvent::Keyboard { event, .. } => {
|
InputEvent::DeviceAdded { device } => {
|
||||||
|
// According to Mutter code, this setting is specific to touchpads.
|
||||||
|
let is_touchpad = device.config_tap_finger_count() > 0;
|
||||||
|
if is_touchpad {
|
||||||
|
let c = &self.niri.config.borrow().input.touchpad;
|
||||||
|
let _ = device.config_tap_set_enabled(c.tap);
|
||||||
|
let _ = device.config_scroll_set_natural_scroll_enabled(c.natural_scroll);
|
||||||
|
let _ = device.config_accel_set_speed(c.accel_speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.has_capability(input::DeviceCapability::TabletTool) {
|
||||||
|
match device.size() {
|
||||||
|
Some((w, h)) => {
|
||||||
|
let aspect_ratio = w / h;
|
||||||
|
let data = TabletData { aspect_ratio };
|
||||||
|
self.niri.tablets.insert(device.clone(), data);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
warn!("tablet tool device has no size");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputEvent::DeviceRemoved { device } => {
|
||||||
|
self.niri.tablets.remove(device);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_device_added(&mut self, device: impl Device) {
|
||||||
|
if device.has_capability(DeviceCapability::TabletTool) {
|
||||||
|
let tablet_seat = self.niri.seat.tablet_seat();
|
||||||
|
|
||||||
|
let desc = TabletDescriptor::from(&device);
|
||||||
|
tablet_seat.add_tablet::<Self>(&self.niri.display_handle, &desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_device_removed(&mut self, device: impl Device) {
|
||||||
|
if device.has_capability(DeviceCapability::TabletTool) {
|
||||||
|
let tablet_seat = self.niri.seat.tablet_seat();
|
||||||
|
|
||||||
|
let desc = TabletDescriptor::from(&device);
|
||||||
|
tablet_seat.remove_tablet(&desc);
|
||||||
|
|
||||||
|
// If there are no tablets in seat we can remove all tools
|
||||||
|
if tablet_seat.count_tablets() == 0 {
|
||||||
|
tablet_seat.clear_tools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the cursor position for the tablet event.
|
||||||
|
///
|
||||||
|
/// This function handles the tablet output mapping, as well as coordinate clamping and aspect
|
||||||
|
/// ratio correction.
|
||||||
|
fn compute_tablet_position<I: InputBackend>(
|
||||||
|
&self,
|
||||||
|
event: &(impl Event<I> + TabletToolEvent<I>),
|
||||||
|
) -> Option<Point<f64, Logical>>
|
||||||
|
where
|
||||||
|
I::Device: 'static,
|
||||||
|
{
|
||||||
|
let output = self.niri.output_for_tablet()?;
|
||||||
|
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||||
|
|
||||||
|
let mut pos = event.position_transformed(output_geo.size);
|
||||||
|
pos.x /= output_geo.size.w as f64;
|
||||||
|
pos.y /= output_geo.size.h as f64;
|
||||||
|
|
||||||
|
let device = event.device();
|
||||||
|
if let Some(device) = (&device as &dyn Any).downcast_ref::<input::Device>() {
|
||||||
|
if let Some(data) = self.niri.tablets.get(device) {
|
||||||
|
// This code does the same thing as mutter with "keep aspect ratio" enabled.
|
||||||
|
let output_aspect_ratio = output_geo.size.w as f64 / output_geo.size.h as f64;
|
||||||
|
let ratio = data.aspect_ratio / output_aspect_ratio;
|
||||||
|
|
||||||
|
if ratio > 1. {
|
||||||
|
pos.x *= ratio;
|
||||||
|
} else {
|
||||||
|
pos.y /= ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pos.x *= output_geo.size.w as f64;
|
||||||
|
pos.y *= output_geo.size.h as f64;
|
||||||
|
pos.x = pos.x.clamp(0.0, output_geo.size.w as f64 - 1.);
|
||||||
|
pos.y = pos.y.clamp(0.0, output_geo.size.h as f64 - 1.);
|
||||||
|
Some(pos + output_geo.loc.to_f64())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_keyboard<I: InputBackend>(&mut self, event: I::KeyboardKeyEvent) {
|
||||||
|
let comp_mod = self.backend.mod_key();
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let time = Event::time_msec(&event);
|
let time = Event::time_msec(&event);
|
||||||
let pressed = event.state() == KeyState::Pressed;
|
let pressed = event.state() == KeyState::Pressed;
|
||||||
@@ -83,16 +220,7 @@ impl State {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.niri.is_locked()
|
if self.niri.is_locked() && !allowed_when_locked(&action) {
|
||||||
&& !matches!(
|
|
||||||
action,
|
|
||||||
Action::Quit
|
|
||||||
| Action::ChangeVt(_)
|
|
||||||
| Action::Suspend
|
|
||||||
| Action::PowerOffMonitors
|
|
||||||
| Action::SwitchLayout(_)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,9 +294,7 @@ impl State {
|
|||||||
let active = self.niri.layout.active_window();
|
let active = self.niri.layout.active_window();
|
||||||
if let Some((window, output)) = active {
|
if let Some((window, output)) = active {
|
||||||
if let Some(renderer) = self.backend.renderer() {
|
if let Some(renderer) = self.backend.renderer() {
|
||||||
if let Err(err) =
|
if let Err(err) = self.niri.screenshot_window(renderer, &output, &window) {
|
||||||
self.niri.screenshot_window(renderer, &output, &window)
|
|
||||||
{
|
|
||||||
warn!("error taking screenshot: {err:?}");
|
warn!("error taking screenshot: {err:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,14 +312,13 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::SwitchLayout(action) => {
|
Action::SwitchLayout(action) => {
|
||||||
self.niri
|
self.niri.seat.get_keyboard().unwrap().with_xkb_state(
|
||||||
.seat
|
self,
|
||||||
.get_keyboard()
|
|mut state| match action {
|
||||||
.unwrap()
|
|
||||||
.with_xkb_state(self, |mut state| match action {
|
|
||||||
LayoutAction::Next => state.cycle_next_layout(),
|
LayoutAction::Next => state.cycle_next_layout(),
|
||||||
LayoutAction::Prev => state.cycle_prev_layout(),
|
LayoutAction::Prev => state.cycle_prev_layout(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Action::MoveColumnLeft => {
|
Action::MoveColumnLeft => {
|
||||||
self.niri.layout.move_left();
|
self.niri.layout.move_left();
|
||||||
@@ -215,6 +340,16 @@ impl State {
|
|||||||
// FIXME: granular
|
// FIXME: granular
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
|
Action::MoveWindowDownOrToWorkspaceDown => {
|
||||||
|
self.niri.layout.move_down_or_to_workspace_down();
|
||||||
|
// FIXME: granular
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
|
Action::MoveWindowUpOrToWorkspaceUp => {
|
||||||
|
self.niri.layout.move_up_or_to_workspace_up();
|
||||||
|
// FIXME: granular
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
Action::FocusColumnLeft => {
|
Action::FocusColumnLeft => {
|
||||||
self.niri.layout.focus_left();
|
self.niri.layout.focus_left();
|
||||||
}
|
}
|
||||||
@@ -227,6 +362,16 @@ impl State {
|
|||||||
Action::FocusWindowUp => {
|
Action::FocusWindowUp => {
|
||||||
self.niri.layout.focus_up();
|
self.niri.layout.focus_up();
|
||||||
}
|
}
|
||||||
|
Action::FocusWindowOrWorkspaceDown => {
|
||||||
|
self.niri.layout.focus_window_or_workspace_down();
|
||||||
|
// FIXME: granular
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
|
Action::FocusWindowOrWorkspaceUp => {
|
||||||
|
self.niri.layout.focus_window_or_workspace_up();
|
||||||
|
// FIXME: granular
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
Action::MoveWindowToWorkspaceDown => {
|
Action::MoveWindowToWorkspaceDown => {
|
||||||
self.niri.layout.move_to_workspace_down();
|
self.niri.layout.move_to_workspace_down();
|
||||||
// FIXME: granular
|
// FIXME: granular
|
||||||
@@ -346,7 +491,8 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::PointerMotion { event, .. } => {
|
|
||||||
|
fn on_pointer_motion<I: InputBackend>(&mut self, event: I::PointerMotionEvent) {
|
||||||
// We need an output to be able to move the pointer.
|
// We need an output to be able to move the pointer.
|
||||||
if self.niri.global_space.outputs().next().is_none() {
|
if self.niri.global_space.outputs().next().is_none() {
|
||||||
return;
|
return;
|
||||||
@@ -361,6 +507,59 @@ impl State {
|
|||||||
// We have an output, so we can compute the new location and focus.
|
// We have an output, so we can compute the new location and focus.
|
||||||
let mut new_pos = pos + event.delta();
|
let mut new_pos = pos + event.delta();
|
||||||
|
|
||||||
|
// We received an event for the regular pointer, so show it now.
|
||||||
|
self.niri.tablet_cursor_location = None;
|
||||||
|
|
||||||
|
// Check if we have an active pointer constraint.
|
||||||
|
let mut pointer_confined = None;
|
||||||
|
if let Some(focus) = self.niri.pointer_focus.as_ref() {
|
||||||
|
let focus_surface_loc = focus.surface.1;
|
||||||
|
let pos_within_surface = pos.to_i32_round() - focus_surface_loc;
|
||||||
|
|
||||||
|
let mut pointer_locked = false;
|
||||||
|
with_pointer_constraint(&focus.surface.0, &pointer, |constraint| {
|
||||||
|
let Some(constraint) = constraint else { return };
|
||||||
|
if !constraint.is_active() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constraint does not apply if not within region.
|
||||||
|
if let Some(region) = constraint.region() {
|
||||||
|
if !region.contains(pos_within_surface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match &*constraint {
|
||||||
|
PointerConstraint::Locked(_locked) => {
|
||||||
|
pointer_locked = true;
|
||||||
|
}
|
||||||
|
PointerConstraint::Confined(confine) => {
|
||||||
|
pointer_confined = Some((focus.surface.clone(), confine.region().cloned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the pointer is locked, only send relative motion.
|
||||||
|
if pointer_locked {
|
||||||
|
pointer.relative_motion(
|
||||||
|
self,
|
||||||
|
Some(focus.surface.clone()),
|
||||||
|
&RelativeMotionEvent {
|
||||||
|
delta: event.delta(),
|
||||||
|
delta_unaccel: event.delta_unaccel(),
|
||||||
|
utime: event.time(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
pointer.frame(self);
|
||||||
|
|
||||||
|
// I guess a redraw to hide the tablet cursor could be nice? Doesn't matter too
|
||||||
|
// much here I think.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.niri
|
.niri
|
||||||
.global_space
|
.global_space
|
||||||
@@ -404,6 +603,44 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(new_pos);
|
let under = self.niri.surface_under_and_global_space(new_pos);
|
||||||
|
|
||||||
|
// Handle confined pointer.
|
||||||
|
if let Some((focus_surface, region)) = pointer_confined {
|
||||||
|
let mut prevent = false;
|
||||||
|
|
||||||
|
// Prevent the pointer from leaving the focused surface.
|
||||||
|
if Some(&focus_surface.0) != under.as_ref().map(|x| &x.surface.0) {
|
||||||
|
prevent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent the pointer from leaving the confine region, if any.
|
||||||
|
if let Some(region) = region {
|
||||||
|
let new_pos_within_surface = new_pos.to_i32_round() - focus_surface.1;
|
||||||
|
if !region.contains(new_pos_within_surface) {
|
||||||
|
prevent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prevent {
|
||||||
|
pointer.relative_motion(
|
||||||
|
self,
|
||||||
|
Some(focus_surface),
|
||||||
|
&RelativeMotionEvent {
|
||||||
|
delta: event.delta(),
|
||||||
|
delta_unaccel: event.delta_unaccel(),
|
||||||
|
utime: event.time(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
pointer.frame(self);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate a new confinement if necessary.
|
||||||
|
self.niri.maybe_activate_pointer_constraint(new_pos, &under);
|
||||||
|
|
||||||
self.niri.pointer_focus = under.clone();
|
self.niri.pointer_focus = under.clone();
|
||||||
let under = under.map(|u| u.surface);
|
let under = under.map(|u| u.surface);
|
||||||
|
|
||||||
@@ -433,7 +670,11 @@ impl State {
|
|||||||
// FIXME: redraw only outputs overlapping the cursor.
|
// FIXME: redraw only outputs overlapping the cursor.
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
InputEvent::PointerMotionAbsolute { event, .. } => {
|
|
||||||
|
fn on_pointer_motion_absolute<I: InputBackend>(
|
||||||
|
&mut self,
|
||||||
|
event: I::PointerMotionAbsoluteEvent,
|
||||||
|
) {
|
||||||
let Some(output) = self.niri.global_space.outputs().next() else {
|
let Some(output) = self.niri.global_space.outputs().next() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -462,6 +703,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(pos);
|
let under = self.niri.surface_under_and_global_space(pos);
|
||||||
|
self.niri.maybe_activate_pointer_constraint(pos, &under);
|
||||||
self.niri.pointer_focus = under.clone();
|
self.niri.pointer_focus = under.clone();
|
||||||
let under = under.map(|u| u.surface);
|
let under = under.map(|u| u.surface);
|
||||||
|
|
||||||
@@ -477,11 +719,15 @@ impl State {
|
|||||||
|
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
|
|
||||||
|
// We moved the regular pointer, so show it now.
|
||||||
|
self.niri.tablet_cursor_location = None;
|
||||||
|
|
||||||
// Redraw to update the cursor position.
|
// Redraw to update the cursor position.
|
||||||
// FIXME: redraw only outputs overlapping the cursor.
|
// FIXME: redraw only outputs overlapping the cursor.
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
InputEvent::PointerButton { event, .. } => {
|
|
||||||
|
fn on_pointer_button<I: InputBackend>(&mut self, event: I::PointerButtonEvent) {
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
@@ -518,12 +764,11 @@ impl State {
|
|||||||
let point = (point - geom.loc.to_f64())
|
let point = (point - geom.loc.to_f64())
|
||||||
.to_physical(output.current_scale().fractional_scale())
|
.to_physical(output.current_scale().fractional_scale())
|
||||||
.to_i32_round();
|
.to_i32_round();
|
||||||
if self.niri.screenshot_ui.pointer_button(
|
if self
|
||||||
output,
|
.niri
|
||||||
point,
|
.screenshot_ui
|
||||||
button,
|
.pointer_button(output, point, button, button_state)
|
||||||
button_state,
|
{
|
||||||
) {
|
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -540,29 +785,30 @@ impl State {
|
|||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
InputEvent::PointerAxis { event, .. } => {
|
|
||||||
|
fn on_pointer_axis<I: InputBackend>(&mut self, event: I::PointerAxisEvent) {
|
||||||
let source = event.source();
|
let source = event.source();
|
||||||
|
|
||||||
let horizontal_amount = event.amount(Axis::Horizontal).unwrap_or_else(|| {
|
let horizontal_amount = event
|
||||||
event.amount_discrete(Axis::Horizontal).unwrap_or(0.0) * 3.0
|
.amount(Axis::Horizontal)
|
||||||
});
|
.unwrap_or_else(|| event.amount_v120(Axis::Horizontal).unwrap_or(0.0) * 3.0 / 120.);
|
||||||
let vertical_amount = event
|
let vertical_amount = event
|
||||||
.amount(Axis::Vertical)
|
.amount(Axis::Vertical)
|
||||||
.unwrap_or_else(|| event.amount_discrete(Axis::Vertical).unwrap_or(0.0) * 3.0);
|
.unwrap_or_else(|| event.amount_v120(Axis::Vertical).unwrap_or(0.0) * 3.0 / 120.);
|
||||||
let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal);
|
let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal);
|
||||||
let vertical_amount_discrete = event.amount_discrete(Axis::Vertical);
|
let vertical_amount_discrete = event.amount_v120(Axis::Vertical);
|
||||||
|
|
||||||
let mut frame = AxisFrame::new(event.time_msec()).source(source);
|
let mut frame = AxisFrame::new(event.time_msec()).source(source);
|
||||||
if horizontal_amount != 0.0 {
|
if horizontal_amount != 0.0 {
|
||||||
frame = frame.value(Axis::Horizontal, horizontal_amount);
|
frame = frame.value(Axis::Horizontal, horizontal_amount);
|
||||||
if let Some(discrete) = horizontal_amount_discrete {
|
if let Some(discrete) = horizontal_amount_discrete {
|
||||||
frame = frame.discrete(Axis::Horizontal, discrete as i32);
|
frame = frame.v120(Axis::Horizontal, discrete as i32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if vertical_amount != 0.0 {
|
if vertical_amount != 0.0 {
|
||||||
frame = frame.value(Axis::Vertical, vertical_amount);
|
frame = frame.value(Axis::Vertical, vertical_amount);
|
||||||
if let Some(discrete) = vertical_amount_discrete {
|
if let Some(discrete) = vertical_amount_discrete {
|
||||||
frame = frame.discrete(Axis::Vertical, discrete as i32);
|
frame = frame.v120(Axis::Vertical, discrete as i32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,34 +827,18 @@ impl State {
|
|||||||
pointer.axis(self, frame);
|
pointer.axis(self, frame);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
InputEvent::TabletToolAxis { event, .. } => {
|
|
||||||
let Some(output) = self.niri.output_for_tablet() else {
|
fn on_tablet_tool_axis<I: InputBackend>(&mut self, event: I::TabletToolAxisEvent)
|
||||||
|
where
|
||||||
|
I::Device: 'static, // Needed for downcasting.
|
||||||
|
{
|
||||||
|
let Some(pos) = self.compute_tablet_position(&event) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
|
||||||
|
|
||||||
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
|
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
|
||||||
|
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(pos);
|
let under = self.niri.surface_under_and_global_space(pos);
|
||||||
self.niri.pointer_focus = under.clone();
|
|
||||||
let under = under.map(|u| u.surface);
|
let under = under.map(|u| u.surface);
|
||||||
|
|
||||||
pointer.motion(
|
|
||||||
self,
|
|
||||||
under.clone(),
|
|
||||||
&MotionEvent {
|
|
||||||
location: pos,
|
|
||||||
serial,
|
|
||||||
time: event.time_msec(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
pointer.frame(self);
|
|
||||||
|
|
||||||
let tablet_seat = self.niri.seat.tablet_seat();
|
let tablet_seat = self.niri.seat.tablet_seat();
|
||||||
let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device()));
|
let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device()));
|
||||||
let tool = tablet_seat.get_tool(&event.tool());
|
let tool = tablet_seat.get_tool(&event.tool());
|
||||||
@@ -639,13 +869,16 @@ impl State {
|
|||||||
SERIAL_COUNTER.next_serial(),
|
SERIAL_COUNTER.next_serial(),
|
||||||
event.time_msec(),
|
event.time_msec(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.niri.tablet_cursor_location = Some(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redraw to update the cursor position.
|
// Redraw to update the cursor position.
|
||||||
// FIXME: redraw only outputs overlapping the cursor.
|
// FIXME: redraw only outputs overlapping the cursor.
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
InputEvent::TabletToolTip { event, .. } => {
|
|
||||||
|
fn on_tablet_tool_tip<I: InputBackend>(&mut self, event: I::TabletToolTipEvent) {
|
||||||
let tool = self.niri.seat.tablet_seat().get_tool(&event.tool());
|
let tool = self.niri.seat.tablet_seat().get_tool(&event.tool());
|
||||||
|
|
||||||
if let Some(tool) = tool {
|
if let Some(tool) = tool {
|
||||||
@@ -654,15 +887,21 @@ impl State {
|
|||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
tool.tip_down(serial, event.time_msec());
|
tool.tip_down(serial, event.time_msec());
|
||||||
|
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
if let Some(pos) = self.niri.tablet_cursor_location {
|
||||||
if !pointer.is_grabbed() {
|
if let Some(window) = self.niri.window_under(pos) {
|
||||||
if let Some(window) = self.niri.window_under_cursor() {
|
|
||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
self.niri.layout.activate_window(&window);
|
self.niri.layout.activate_window(&window);
|
||||||
} else if let Some(output) = self.niri.output_under_cursor() {
|
|
||||||
|
// FIXME: granular.
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
} else if let Some((output, _)) = self.niri.output_under(pos) {
|
||||||
|
let output = output.clone();
|
||||||
self.niri.layout.activate_output(&output);
|
self.niri.layout.activate_output(&output);
|
||||||
|
|
||||||
|
// FIXME: granular.
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
TabletToolTipState::Up => {
|
TabletToolTipState::Up => {
|
||||||
tool.tip_up(event.time_msec());
|
tool.tip_up(event.time_msec());
|
||||||
@@ -670,51 +909,56 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::TabletToolProximity { event, .. } => {
|
|
||||||
let Some(output) = self.niri.output_for_tablet() else {
|
fn on_tablet_tool_proximity<I: InputBackend>(&mut self, event: I::TabletToolProximityEvent)
|
||||||
|
where
|
||||||
|
I::Device: 'static, // Needed for downcasting.
|
||||||
|
{
|
||||||
|
let Some(pos) = self.compute_tablet_position(&event) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
|
||||||
|
|
||||||
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
|
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
|
||||||
|
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(pos);
|
let under = self.niri.surface_under_and_global_space(pos);
|
||||||
self.niri.pointer_focus = under.clone();
|
|
||||||
let under = under.map(|u| u.surface);
|
let under = under.map(|u| u.surface);
|
||||||
|
|
||||||
pointer.motion(
|
|
||||||
self,
|
|
||||||
under.clone(),
|
|
||||||
&MotionEvent {
|
|
||||||
location: pos,
|
|
||||||
serial,
|
|
||||||
time: event.time_msec(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
pointer.frame(self);
|
|
||||||
|
|
||||||
let tablet_seat = self.niri.seat.tablet_seat();
|
let tablet_seat = self.niri.seat.tablet_seat();
|
||||||
let tool = tablet_seat.add_tool::<Self>(&self.niri.display_handle, &event.tool());
|
let tool = tablet_seat.add_tool::<Self>(&self.niri.display_handle, &event.tool());
|
||||||
let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device()));
|
let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device()));
|
||||||
if let (Some(under), Some(tablet)) = (under, tablet) {
|
if let Some(tablet) = tablet {
|
||||||
match event.state() {
|
match event.state() {
|
||||||
ProximityState::In => tool.proximity_in(
|
ProximityState::In => {
|
||||||
|
if let Some(under) = under {
|
||||||
|
tool.proximity_in(
|
||||||
pos,
|
pos,
|
||||||
under,
|
under,
|
||||||
&tablet,
|
&tablet,
|
||||||
SERIAL_COUNTER.next_serial(),
|
SERIAL_COUNTER.next_serial(),
|
||||||
event.time_msec(),
|
event.time_msec(),
|
||||||
),
|
);
|
||||||
ProximityState::Out => tool.proximity_out(event.time_msec()),
|
}
|
||||||
|
self.niri.tablet_cursor_location = Some(pos);
|
||||||
|
}
|
||||||
|
ProximityState::Out => {
|
||||||
|
tool.proximity_out(event.time_msec());
|
||||||
|
|
||||||
|
// Move the mouse pointer here to avoid discontinuity.
|
||||||
|
//
|
||||||
|
// Plus, Wayland SDL2 currently warps the pointer into some weird
|
||||||
|
// location on proximity out, so this shuold help it a little.
|
||||||
|
if let Some(pos) = self.niri.tablet_cursor_location {
|
||||||
|
self.move_cursor(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.niri.tablet_cursor_location = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: granular.
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
InputEvent::TabletToolButton { event, .. } => {
|
}
|
||||||
|
|
||||||
|
fn on_tablet_tool_button<I: InputBackend>(&mut self, event: I::TabletToolButtonEvent) {
|
||||||
let tool = self.niri.seat.tablet_seat().get_tool(&event.tool());
|
let tool = self.niri.seat.tablet_seat().get_tool(&event.tool());
|
||||||
|
|
||||||
if let Some(tool) = tool {
|
if let Some(tool) = tool {
|
||||||
@@ -726,27 +970,8 @@ impl State {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::DeviceAdded { device } => {
|
|
||||||
if device.has_capability(DeviceCapability::TabletTool) {
|
|
||||||
self.niri.seat.tablet_seat().add_tablet::<Self>(
|
|
||||||
&self.niri.display_handle,
|
|
||||||
&TabletDescriptor::from(&device),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InputEvent::DeviceRemoved { device } => {
|
|
||||||
if device.has_capability(DeviceCapability::TabletTool) {
|
|
||||||
let tablet_seat = self.niri.seat.tablet_seat();
|
|
||||||
|
|
||||||
tablet_seat.remove_tablet(&TabletDescriptor::from(&device));
|
fn on_gesture_swipe_begin<I: InputBackend>(&mut self, event: I::GestureSwipeBeginEvent) {
|
||||||
|
|
||||||
// If there are no tablets in seat we can remove all tools
|
|
||||||
if tablet_seat.count_tablets() == 0 {
|
|
||||||
tablet_seat.clear_tools();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InputEvent::GestureSwipeBegin { event } => {
|
|
||||||
if event.fingers() == 3 {
|
if event.fingers() == 3 {
|
||||||
if let Some(output) = self.niri.output_under_cursor() {
|
if let Some(output) = self.niri.output_under_cursor() {
|
||||||
self.niri.layout.workspace_switch_gesture_begin(&output);
|
self.niri.layout.workspace_switch_gesture_begin(&output);
|
||||||
@@ -776,7 +1001,8 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::GestureSwipeUpdate { event } => {
|
|
||||||
|
fn on_gesture_swipe_update<I: InputBackend>(&mut self, event: I::GestureSwipeUpdateEvent) {
|
||||||
let res = self
|
let res = self
|
||||||
.niri
|
.niri
|
||||||
.layout
|
.layout
|
||||||
@@ -804,7 +1030,8 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::GestureSwipeEnd { event } => {
|
|
||||||
|
fn on_gesture_swipe_end<I: InputBackend>(&mut self, event: I::GestureSwipeEndEvent) {
|
||||||
let res = self
|
let res = self
|
||||||
.niri
|
.niri
|
||||||
.layout
|
.layout
|
||||||
@@ -832,7 +1059,8 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::GesturePinchBegin { event } => {
|
|
||||||
|
fn on_gesture_pinch_begin<I: InputBackend>(&mut self, event: I::GesturePinchBeginEvent) {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
|
|
||||||
@@ -849,7 +1077,8 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::GesturePinchUpdate { event } => {
|
|
||||||
|
fn on_gesture_pinch_update<I: InputBackend>(&mut self, event: I::GesturePinchUpdateEvent) {
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
|
|
||||||
if self.update_pointer_focus() {
|
if self.update_pointer_focus() {
|
||||||
@@ -866,7 +1095,8 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::GesturePinchEnd { event } => {
|
|
||||||
|
fn on_gesture_pinch_end<I: InputBackend>(&mut self, event: I::GesturePinchEndEvent) {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
|
|
||||||
@@ -883,7 +1113,8 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::GestureHoldBegin { event } => {
|
|
||||||
|
fn on_gesture_hold_begin<I: InputBackend>(&mut self, event: I::GestureHoldBeginEvent) {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
|
|
||||||
@@ -900,7 +1131,8 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::GestureHoldEnd { event } => {
|
|
||||||
|
fn on_gesture_hold_end<I: InputBackend>(&mut self, event: I::GestureHoldEndEvent) {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
|
|
||||||
@@ -917,27 +1149,6 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
InputEvent::TouchDown { .. } => (),
|
|
||||||
InputEvent::TouchMotion { .. } => (),
|
|
||||||
InputEvent::TouchUp { .. } => (),
|
|
||||||
InputEvent::TouchCancel { .. } => (),
|
|
||||||
InputEvent::TouchFrame { .. } => (),
|
|
||||||
InputEvent::Special(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_libinput_event(&mut self, event: &mut InputEvent<LibinputInputBackend>) {
|
|
||||||
if let InputEvent::DeviceAdded { device } = event {
|
|
||||||
// According to Mutter code, this setting is specific to touchpads.
|
|
||||||
let is_touchpad = device.config_tap_finger_count() > 0;
|
|
||||||
if is_touchpad {
|
|
||||||
let c = &self.niri.config.borrow().input.touchpad;
|
|
||||||
let _ = device.config_tap_set_enabled(c.tap);
|
|
||||||
let _ = device.config_scroll_set_natural_scroll_enabled(c.natural_scroll);
|
|
||||||
let _ = device.config_accel_set_speed(c.accel_speed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the key should be intercepted and mark intercepted
|
/// Check whether the key should be intercepted and mark intercepted
|
||||||
@@ -963,17 +1174,22 @@ fn should_intercept_key(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut final_action = action(bindings, comp_mod, modified, raw, mods);
|
let mut final_action = action(bindings, comp_mod, modified, raw, mods);
|
||||||
if screenshot_ui.is_open()
|
|
||||||
// Allow only a subset of compositor actions while the screenshot UI is open,
|
// Allow only a subset of compositor actions while the screenshot UI is open, since the user
|
||||||
// since the user cannot see the screen.
|
// cannot see the screen.
|
||||||
&& !matches!(
|
if screenshot_ui.is_open() {
|
||||||
final_action,
|
let mut use_screenshot_ui_action = true;
|
||||||
Some(Action::Quit | Action::ChangeVt(_) | Action::Suspend | Action::PowerOffMonitors)
|
|
||||||
)
|
if let Some(action) = &final_action {
|
||||||
{
|
if allowed_during_screenshot(action) {
|
||||||
// Otherwise, use the screenshot UI action.
|
use_screenshot_ui_action = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if use_screenshot_ui_action {
|
||||||
final_action = screenshot_ui.action(raw, mods);
|
final_action = screenshot_ui.action(raw, mods);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match (final_action, pressed) {
|
match (final_action, pressed) {
|
||||||
(Some(action), true) => {
|
(Some(action), true) => {
|
||||||
@@ -1071,6 +1287,24 @@ fn should_activate_monitors<I: InputBackend>(event: &InputEvent<I>) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allowed_when_locked(action: &Action) -> bool {
|
||||||
|
matches!(
|
||||||
|
action,
|
||||||
|
Action::Quit
|
||||||
|
| Action::ChangeVt(_)
|
||||||
|
| Action::Suspend
|
||||||
|
| Action::PowerOffMonitors
|
||||||
|
| Action::SwitchLayout(_)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allowed_during_screenshot(action: &Action) -> bool {
|
||||||
|
matches!(
|
||||||
|
action,
|
||||||
|
Action::Quit | Action::ChangeVt(_) | Action::Suspend | Action::PowerOffMonitors
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+184
-29
@@ -56,7 +56,7 @@ use smithay::wayland::compositor::{send_surface_state, with_states};
|
|||||||
use smithay::wayland::shell::xdg::SurfaceCachedState;
|
use smithay::wayland::shell::xdg::SurfaceCachedState;
|
||||||
|
|
||||||
use crate::animation::Animation;
|
use crate::animation::Animation;
|
||||||
use crate::config::{self, Color, Config, PresetWidth, SizeChange};
|
use crate::config::{self, Color, Config, PresetWidth, SizeChange, Struts};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct OutputId(String);
|
pub struct OutputId(String);
|
||||||
@@ -205,6 +205,8 @@ struct FocusRing {
|
|||||||
struct Options {
|
struct Options {
|
||||||
/// Padding around windows in logical pixels.
|
/// Padding around windows in logical pixels.
|
||||||
gaps: i32,
|
gaps: i32,
|
||||||
|
/// Extra padding around the working area in logical pixels.
|
||||||
|
struts: Struts,
|
||||||
focus_ring: config::FocusRing,
|
focus_ring: config::FocusRing,
|
||||||
/// Column widths that `toggle_width()` switches between.
|
/// Column widths that `toggle_width()` switches between.
|
||||||
preset_widths: Vec<ColumnWidth>,
|
preset_widths: Vec<ColumnWidth>,
|
||||||
@@ -216,6 +218,7 @@ impl Default for Options {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
gaps: 16,
|
gaps: 16,
|
||||||
|
struts: Default::default(),
|
||||||
focus_ring: Default::default(),
|
focus_ring: Default::default(),
|
||||||
preset_widths: vec![
|
preset_widths: vec![
|
||||||
ColumnWidth::Proportion(1. / 3.),
|
ColumnWidth::Proportion(1. / 3.),
|
||||||
@@ -251,6 +254,7 @@ impl Options {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
gaps: config.gaps.into(),
|
gaps: config.gaps.into(),
|
||||||
|
struts: config.struts,
|
||||||
focus_ring: config.focus_ring,
|
focus_ring: config.focus_ring,
|
||||||
preset_widths,
|
preset_widths,
|
||||||
default_width,
|
default_width,
|
||||||
@@ -672,7 +676,6 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
pub fn add_window(
|
pub fn add_window(
|
||||||
&mut self,
|
&mut self,
|
||||||
window: W,
|
window: W,
|
||||||
activate: bool,
|
|
||||||
width: Option<ColumnWidth>,
|
width: Option<ColumnWidth>,
|
||||||
is_full_width: bool,
|
is_full_width: bool,
|
||||||
) -> Option<&Output> {
|
) -> Option<&Output> {
|
||||||
@@ -687,6 +690,14 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let mon = &mut monitors[*active_monitor_idx];
|
let mon = &mut monitors[*active_monitor_idx];
|
||||||
|
|
||||||
|
// Don't steal focus from an active fullscreen window.
|
||||||
|
let mut activate = true;
|
||||||
|
let ws = &mon.workspaces[mon.active_workspace_idx];
|
||||||
|
if !ws.columns.is_empty() && ws.columns[ws.active_column_idx].is_fullscreen {
|
||||||
|
activate = false;
|
||||||
|
}
|
||||||
|
|
||||||
mon.add_window(
|
mon.add_window(
|
||||||
mon.active_workspace_idx,
|
mon.active_workspace_idx,
|
||||||
window,
|
window,
|
||||||
@@ -703,7 +714,7 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
workspaces.push(Workspace::new_no_outputs(self.options.clone()));
|
workspaces.push(Workspace::new_no_outputs(self.options.clone()));
|
||||||
&mut workspaces[0]
|
&mut workspaces[0]
|
||||||
};
|
};
|
||||||
ws.add_window(window, activate, width, is_full_width);
|
ws.add_window(window, true, width, is_full_width);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -788,6 +799,33 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn window_y(&self, window: &W) -> Option<i32> {
|
||||||
|
match &self.monitor_set {
|
||||||
|
MonitorSet::Normal { monitors, .. } => {
|
||||||
|
for mon in monitors {
|
||||||
|
for ws in &mon.workspaces {
|
||||||
|
for col in &ws.columns {
|
||||||
|
if let Some(idx) = col.windows.iter().position(|w| w == window) {
|
||||||
|
return Some(col.window_y(idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MonitorSet::NoOutputs { workspaces, .. } => {
|
||||||
|
for ws in workspaces {
|
||||||
|
for col in &ws.columns {
|
||||||
|
if let Some(idx) = col.windows.iter().position(|w| w == window) {
|
||||||
|
return Some(col.window_y(idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_output_size(&mut self, output: &Output) {
|
pub fn update_output_size(&mut self, output: &Output) {
|
||||||
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
|
let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else {
|
||||||
panic!()
|
panic!()
|
||||||
@@ -796,7 +834,7 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
for mon in monitors {
|
for mon in monitors {
|
||||||
if &mon.output == output {
|
if &mon.output == output {
|
||||||
let view_size = output_size(output);
|
let view_size = output_size(output);
|
||||||
let working_area = layer_map_for_output(output).non_exclusive_zone();
|
let working_area = compute_working_area(output, self.options.struts);
|
||||||
|
|
||||||
for ws in &mut mon.workspaces {
|
for ws in &mut mon.workspaces {
|
||||||
ws.set_view_size(view_size, working_area);
|
ws.set_view_size(view_size, working_area);
|
||||||
@@ -970,6 +1008,20 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
monitor.move_up();
|
monitor.move_up();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_down_or_to_workspace_down(&mut self) {
|
||||||
|
let Some(monitor) = self.active_monitor() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
monitor.move_down_or_to_workspace_down();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_up_or_to_workspace_up(&mut self) {
|
||||||
|
let Some(monitor) = self.active_monitor() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
monitor.move_up_or_to_workspace_up();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn focus_left(&mut self) {
|
pub fn focus_left(&mut self) {
|
||||||
let Some(monitor) = self.active_monitor() else {
|
let Some(monitor) = self.active_monitor() else {
|
||||||
return;
|
return;
|
||||||
@@ -998,6 +1050,20 @@ impl<W: LayoutElement> Layout<W> {
|
|||||||
monitor.focus_up();
|
monitor.focus_up();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn focus_window_or_workspace_down(&mut self) {
|
||||||
|
let Some(monitor) = self.active_monitor() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
monitor.focus_window_or_workspace_down();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_window_or_workspace_up(&mut self) {
|
||||||
|
let Some(monitor) = self.active_monitor() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
monitor.focus_window_or_workspace_up();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_to_workspace_up(&mut self) {
|
pub fn move_to_workspace_up(&mut self) {
|
||||||
let Some(monitor) = self.active_monitor() else {
|
let Some(monitor) = self.active_monitor() else {
|
||||||
return;
|
return;
|
||||||
@@ -1605,6 +1671,35 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
self.active_workspace().move_up();
|
self.active_workspace().move_up();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_down_or_to_workspace_down(&mut self) {
|
||||||
|
let workspace = self.active_workspace();
|
||||||
|
if workspace.columns.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let column = &mut workspace.columns[workspace.active_column_idx];
|
||||||
|
let curr_idx = column.active_window_idx;
|
||||||
|
let new_idx = min(column.active_window_idx + 1, column.windows.len() - 1);
|
||||||
|
if curr_idx == new_idx {
|
||||||
|
self.move_to_workspace_down();
|
||||||
|
} else {
|
||||||
|
workspace.move_down();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_up_or_to_workspace_up(&mut self) {
|
||||||
|
let workspace = self.active_workspace();
|
||||||
|
if workspace.columns.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let curr_idx = workspace.columns[workspace.active_column_idx].active_window_idx;
|
||||||
|
let new_idx = curr_idx.saturating_sub(1);
|
||||||
|
if curr_idx == new_idx {
|
||||||
|
self.move_to_workspace_up();
|
||||||
|
} else {
|
||||||
|
workspace.move_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn focus_left(&mut self) {
|
pub fn focus_left(&mut self) {
|
||||||
self.active_workspace().focus_left();
|
self.active_workspace().focus_left();
|
||||||
}
|
}
|
||||||
@@ -1621,6 +1716,37 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
self.active_workspace().focus_up();
|
self.active_workspace().focus_up();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn focus_window_or_workspace_down(&mut self) {
|
||||||
|
let workspace = self.active_workspace();
|
||||||
|
if workspace.columns.is_empty() {
|
||||||
|
self.switch_workspace_down();
|
||||||
|
} else {
|
||||||
|
let column = &workspace.columns[workspace.active_column_idx];
|
||||||
|
let curr_idx = column.active_window_idx;
|
||||||
|
let new_idx = min(column.active_window_idx + 1, column.windows.len() - 1);
|
||||||
|
if curr_idx == new_idx {
|
||||||
|
self.switch_workspace_down();
|
||||||
|
} else {
|
||||||
|
workspace.focus_down();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_window_or_workspace_up(&mut self) {
|
||||||
|
let workspace = self.active_workspace();
|
||||||
|
if workspace.columns.is_empty() {
|
||||||
|
self.switch_workspace_up();
|
||||||
|
} else {
|
||||||
|
let curr_idx = workspace.columns[workspace.active_column_idx].active_window_idx;
|
||||||
|
let new_idx = curr_idx.saturating_sub(1);
|
||||||
|
if curr_idx == new_idx {
|
||||||
|
self.switch_workspace_up();
|
||||||
|
} else {
|
||||||
|
workspace.focus_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_to_workspace_up(&mut self) {
|
pub fn move_to_workspace_up(&mut self) {
|
||||||
let source_workspace_idx = self.active_workspace_idx;
|
let source_workspace_idx = self.active_workspace_idx;
|
||||||
|
|
||||||
@@ -1764,6 +1890,15 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
ws.update_config(options.clone());
|
ws.update_config(options.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.options.struts != options.struts {
|
||||||
|
let view_size = output_size(&self.output);
|
||||||
|
let working_area = compute_working_area(&self.output, options.struts);
|
||||||
|
|
||||||
|
for ws in &mut self.workspaces {
|
||||||
|
ws.set_view_size(view_size, working_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.options = options;
|
self.options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1942,7 +2077,7 @@ impl Monitor<Window> {
|
|||||||
|
|
||||||
impl<W: LayoutElement> Workspace<W> {
|
impl<W: LayoutElement> Workspace<W> {
|
||||||
fn new(output: Output, options: Rc<Options>) -> Self {
|
fn new(output: Output, options: Rc<Options>) -> Self {
|
||||||
let working_area = layer_map_for_output(&output).non_exclusive_zone();
|
let working_area = compute_working_area(&output, options.struts);
|
||||||
Self {
|
Self {
|
||||||
original_output: OutputId::new(&output),
|
original_output: OutputId::new(&output),
|
||||||
view_size: output_size(&output),
|
view_size: output_size(&output),
|
||||||
@@ -2042,7 +2177,7 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
self.output = output;
|
self.output = output;
|
||||||
|
|
||||||
if let Some(output) = &self.output {
|
if let Some(output) = &self.output {
|
||||||
let working_area = layer_map_for_output(output).non_exclusive_zone();
|
let working_area = compute_working_area(output, self.options.struts);
|
||||||
self.set_view_size(output_size(output), working_area);
|
self.set_view_size(output_size(output), working_area);
|
||||||
|
|
||||||
for win in self.windows() {
|
for win in self.windows() {
|
||||||
@@ -3139,6 +3274,27 @@ pub fn output_size(output: &Output) -> Size<i32, Logical> {
|
|||||||
.to_logical(output_scale)
|
.to_logical(output_scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compute_working_area(output: &Output, struts: Struts) -> Rectangle<i32, Logical> {
|
||||||
|
// Start with the layer-shell non-exclusive zone.
|
||||||
|
let mut working_area = layer_map_for_output(output).non_exclusive_zone();
|
||||||
|
|
||||||
|
// Add struts.
|
||||||
|
let w = working_area.size.w;
|
||||||
|
let h = working_area.size.h;
|
||||||
|
|
||||||
|
working_area.size.w = w
|
||||||
|
.saturating_sub(struts.left.into())
|
||||||
|
.saturating_sub(struts.right.into());
|
||||||
|
working_area.loc.x += struts.left as i32;
|
||||||
|
|
||||||
|
working_area.size.h = h
|
||||||
|
.saturating_sub(struts.top.into())
|
||||||
|
.saturating_sub(struts.bottom.into());
|
||||||
|
working_area.loc.y += struts.top as i32;
|
||||||
|
|
||||||
|
working_area
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_new_view_offset(
|
fn compute_new_view_offset(
|
||||||
cur_x: i32,
|
cur_x: i32,
|
||||||
view_width: i32,
|
view_width: i32,
|
||||||
@@ -3323,7 +3479,6 @@ mod tests {
|
|||||||
id: usize,
|
id: usize,
|
||||||
#[proptest(strategy = "arbitrary_bbox()")]
|
#[proptest(strategy = "arbitrary_bbox()")]
|
||||||
bbox: Rectangle<i32, Logical>,
|
bbox: Rectangle<i32, Logical>,
|
||||||
activate: bool,
|
|
||||||
},
|
},
|
||||||
CloseWindow(#[proptest(strategy = "1..=5usize")] usize),
|
CloseWindow(#[proptest(strategy = "1..=5usize")] usize),
|
||||||
FullscreenWindow(#[proptest(strategy = "1..=5usize")] usize),
|
FullscreenWindow(#[proptest(strategy = "1..=5usize")] usize),
|
||||||
@@ -3331,10 +3486,14 @@ mod tests {
|
|||||||
FocusColumnRight,
|
FocusColumnRight,
|
||||||
FocusWindowDown,
|
FocusWindowDown,
|
||||||
FocusWindowUp,
|
FocusWindowUp,
|
||||||
|
FocusWindowOrWorkspaceDown,
|
||||||
|
FocusWindowOrWorkspaceUp,
|
||||||
MoveColumnLeft,
|
MoveColumnLeft,
|
||||||
MoveColumnRight,
|
MoveColumnRight,
|
||||||
MoveWindowDown,
|
MoveWindowDown,
|
||||||
MoveWindowUp,
|
MoveWindowUp,
|
||||||
|
MoveWindowDownOrToWorkspaceDown,
|
||||||
|
MoveWindowUpOrToWorkspaceUp,
|
||||||
ConsumeWindowIntoColumn,
|
ConsumeWindowIntoColumn,
|
||||||
ExpelWindowFromColumn,
|
ExpelWindowFromColumn,
|
||||||
CenterColumn,
|
CenterColumn,
|
||||||
@@ -3399,7 +3558,7 @@ mod tests {
|
|||||||
|
|
||||||
layout.focus_output(&output);
|
layout.focus_output(&output);
|
||||||
}
|
}
|
||||||
Op::AddWindow { id, bbox, activate } => {
|
Op::AddWindow { id, bbox } => {
|
||||||
match &mut layout.monitor_set {
|
match &mut layout.monitor_set {
|
||||||
MonitorSet::Normal { monitors, .. } => {
|
MonitorSet::Normal { monitors, .. } => {
|
||||||
for mon in monitors {
|
for mon in monitors {
|
||||||
@@ -3424,7 +3583,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let win = TestWindow::new(id, bbox);
|
let win = TestWindow::new(id, bbox);
|
||||||
layout.add_window(win, activate, None, false);
|
layout.add_window(win, None, false);
|
||||||
}
|
}
|
||||||
Op::CloseWindow(id) => {
|
Op::CloseWindow(id) => {
|
||||||
let dummy = TestWindow::new(id, Rectangle::default());
|
let dummy = TestWindow::new(id, Rectangle::default());
|
||||||
@@ -3438,10 +3597,14 @@ mod tests {
|
|||||||
Op::FocusColumnRight => layout.focus_right(),
|
Op::FocusColumnRight => layout.focus_right(),
|
||||||
Op::FocusWindowDown => layout.focus_down(),
|
Op::FocusWindowDown => layout.focus_down(),
|
||||||
Op::FocusWindowUp => layout.focus_up(),
|
Op::FocusWindowUp => layout.focus_up(),
|
||||||
|
Op::FocusWindowOrWorkspaceDown => layout.focus_window_or_workspace_down(),
|
||||||
|
Op::FocusWindowOrWorkspaceUp => layout.focus_window_or_workspace_up(),
|
||||||
Op::MoveColumnLeft => layout.move_left(),
|
Op::MoveColumnLeft => layout.move_left(),
|
||||||
Op::MoveColumnRight => layout.move_right(),
|
Op::MoveColumnRight => layout.move_right(),
|
||||||
Op::MoveWindowDown => layout.move_down(),
|
Op::MoveWindowDown => layout.move_down(),
|
||||||
Op::MoveWindowUp => layout.move_up(),
|
Op::MoveWindowUp => layout.move_up(),
|
||||||
|
Op::MoveWindowDownOrToWorkspaceDown => layout.move_down_or_to_workspace_down(),
|
||||||
|
Op::MoveWindowUpOrToWorkspaceUp => layout.move_up_or_to_workspace_up(),
|
||||||
Op::ConsumeWindowIntoColumn => layout.consume_into_column(),
|
Op::ConsumeWindowIntoColumn => layout.consume_into_column(),
|
||||||
Op::ExpelWindowFromColumn => layout.expel_from_column(),
|
Op::ExpelWindowFromColumn => layout.expel_from_column(),
|
||||||
Op::CenterColumn => layout.center_column(),
|
Op::CenterColumn => layout.center_column(),
|
||||||
@@ -3528,23 +3691,24 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 0,
|
id: 0,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 1,
|
id: 1,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 2,
|
id: 2,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::CloseWindow(0),
|
Op::CloseWindow(0),
|
||||||
Op::CloseWindow(1),
|
Op::CloseWindow(1),
|
||||||
Op::CloseWindow(2),
|
Op::CloseWindow(2),
|
||||||
Op::FocusColumnLeft,
|
Op::FocusColumnLeft,
|
||||||
Op::FocusColumnRight,
|
Op::FocusColumnRight,
|
||||||
|
Op::FocusWindowUp,
|
||||||
|
Op::FocusWindowOrWorkspaceUp,
|
||||||
|
Op::FocusWindowDown,
|
||||||
|
Op::FocusWindowOrWorkspaceDown,
|
||||||
Op::MoveColumnLeft,
|
Op::MoveColumnLeft,
|
||||||
Op::MoveColumnRight,
|
Op::MoveColumnRight,
|
||||||
Op::ConsumeWindowIntoColumn,
|
Op::ConsumeWindowIntoColumn,
|
||||||
@@ -3561,7 +3725,9 @@ mod tests {
|
|||||||
Op::MoveWindowToWorkspace(2),
|
Op::MoveWindowToWorkspace(2),
|
||||||
Op::MoveWindowToWorkspace(3),
|
Op::MoveWindowToWorkspace(3),
|
||||||
Op::MoveWindowDown,
|
Op::MoveWindowDown,
|
||||||
|
Op::MoveWindowDownOrToWorkspaceDown,
|
||||||
Op::MoveWindowUp,
|
Op::MoveWindowUp,
|
||||||
|
Op::MoveWindowUpOrToWorkspaceUp,
|
||||||
];
|
];
|
||||||
|
|
||||||
for third in every_op {
|
for third in every_op {
|
||||||
@@ -3596,31 +3762,26 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 1,
|
id: 1,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::MoveWindowToWorkspaceDown,
|
Op::MoveWindowToWorkspaceDown,
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 2,
|
id: 2,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 3,
|
id: 3,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::FocusColumnLeft,
|
Op::FocusColumnLeft,
|
||||||
Op::ConsumeWindowIntoColumn,
|
Op::ConsumeWindowIntoColumn,
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 4,
|
id: 4,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::AddOutput(2),
|
Op::AddOutput(2),
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 5,
|
id: 5,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::MoveWindowToOutput(2),
|
Op::MoveWindowToOutput(2),
|
||||||
Op::FocusOutput(1),
|
Op::FocusOutput(1),
|
||||||
@@ -3644,23 +3805,24 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 0,
|
id: 0,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 1,
|
id: 1,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 2,
|
id: 2,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::CloseWindow(0),
|
Op::CloseWindow(0),
|
||||||
Op::CloseWindow(1),
|
Op::CloseWindow(1),
|
||||||
Op::CloseWindow(2),
|
Op::CloseWindow(2),
|
||||||
Op::FocusColumnLeft,
|
Op::FocusColumnLeft,
|
||||||
Op::FocusColumnRight,
|
Op::FocusColumnRight,
|
||||||
|
Op::FocusWindowUp,
|
||||||
|
Op::FocusWindowOrWorkspaceUp,
|
||||||
|
Op::FocusWindowDown,
|
||||||
|
Op::FocusWindowOrWorkspaceDown,
|
||||||
Op::MoveColumnLeft,
|
Op::MoveColumnLeft,
|
||||||
Op::MoveColumnRight,
|
Op::MoveColumnRight,
|
||||||
Op::ConsumeWindowIntoColumn,
|
Op::ConsumeWindowIntoColumn,
|
||||||
@@ -3677,7 +3839,9 @@ mod tests {
|
|||||||
Op::MoveWindowToWorkspace(2),
|
Op::MoveWindowToWorkspace(2),
|
||||||
Op::MoveWindowToWorkspace(3),
|
Op::MoveWindowToWorkspace(3),
|
||||||
Op::MoveWindowDown,
|
Op::MoveWindowDown,
|
||||||
|
Op::MoveWindowDownOrToWorkspaceDown,
|
||||||
Op::MoveWindowUp,
|
Op::MoveWindowUp,
|
||||||
|
Op::MoveWindowUpOrToWorkspaceUp,
|
||||||
];
|
];
|
||||||
|
|
||||||
for third in every_op {
|
for third in every_op {
|
||||||
@@ -3710,13 +3874,11 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 0,
|
id: 0,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::FocusOutput(2),
|
Op::FocusOutput(2),
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 1,
|
id: 1,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::RemoveOutput(2),
|
Op::RemoveOutput(2),
|
||||||
Op::FocusWorkspace(3),
|
Op::FocusWorkspace(3),
|
||||||
@@ -3733,7 +3895,6 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 0,
|
id: 0,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::FocusWorkspaceDown,
|
Op::FocusWorkspaceDown,
|
||||||
Op::CloseWindow(0),
|
Op::CloseWindow(0),
|
||||||
@@ -3749,7 +3910,6 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 0,
|
id: 0,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::AddOutput(2),
|
Op::AddOutput(2),
|
||||||
Op::RemoveOutput(1),
|
Op::RemoveOutput(1),
|
||||||
@@ -3776,7 +3936,6 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 0,
|
id: 0,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::MoveWindowToWorkspace(2),
|
Op::MoveWindowToWorkspace(2),
|
||||||
];
|
];
|
||||||
@@ -3800,13 +3959,11 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 0,
|
id: 0,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::FocusWorkspaceDown,
|
Op::FocusWorkspaceDown,
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 1,
|
id: 1,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::FocusWorkspaceUp,
|
Op::FocusWorkspaceUp,
|
||||||
Op::CloseWindow(0),
|
Op::CloseWindow(0),
|
||||||
@@ -3832,13 +3989,11 @@ mod tests {
|
|||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 1,
|
id: 1,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::FocusWorkspaceDown,
|
Op::FocusWorkspaceDown,
|
||||||
Op::AddWindow {
|
Op::AddWindow {
|
||||||
id: 2,
|
id: 2,
|
||||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||||
activate: true,
|
|
||||||
},
|
},
|
||||||
Op::AddOutput(2),
|
Op::AddOutput(2),
|
||||||
Op::RemoveOutput(1),
|
Op::RemoveOutput(1),
|
||||||
|
|||||||
+34
-3
@@ -26,7 +26,7 @@ use std::path::PathBuf;
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::{env, mem};
|
use std::{env, mem};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::{Parser, Subcommand};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
#[cfg(not(feature = "xdp-gnome-screencast"))]
|
#[cfg(not(feature = "xdp-gnome-screencast"))]
|
||||||
use dummy_pw_utils as pw_utils;
|
use dummy_pw_utils as pw_utils;
|
||||||
@@ -45,6 +45,9 @@ use crate::utils::{REMOVE_ENV_RUST_BACKTRACE, REMOVE_ENV_RUST_LIB_BACKTRACE};
|
|||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
|
#[command(args_conflicts_with_subcommands = true)]
|
||||||
|
#[command(subcommand_value_name = "SUBCOMMAND")]
|
||||||
|
#[command(subcommand_help_heading = "Subcommands")]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// Path to config file (default: `$XDG_CONFIG_HOME/niri/config.kdl`).
|
/// Path to config file (default: `$XDG_CONFIG_HOME/niri/config.kdl`).
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
@@ -52,9 +55,22 @@ struct Cli {
|
|||||||
/// Command to run upon compositor startup.
|
/// Command to run upon compositor startup.
|
||||||
#[arg(last = true)]
|
#[arg(last = true)]
|
||||||
command: Vec<OsString>,
|
command: Vec<OsString>,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
subcommand: Option<Sub>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[derive(Subcommand)]
|
||||||
|
enum Sub {
|
||||||
|
/// Validate the config file.
|
||||||
|
Validate {
|
||||||
|
/// Path to config file (default: `$XDG_CONFIG_HOME/niri/config.kdl`).
|
||||||
|
#[arg(short, long)]
|
||||||
|
config: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Set backtrace defaults if not set.
|
// Set backtrace defaults if not set.
|
||||||
if env::var_os("RUST_BACKTRACE").is_none() {
|
if env::var_os("RUST_BACKTRACE").is_none() {
|
||||||
env::set_var("RUST_BACKTRACE", "1");
|
env::set_var("RUST_BACKTRACE", "1");
|
||||||
@@ -92,6 +108,20 @@ fn main() {
|
|||||||
|
|
||||||
let _client = tracy_client::Client::start();
|
let _client = tracy_client::Client::start();
|
||||||
|
|
||||||
|
// Set a better error printer for config loading.
|
||||||
|
miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new()))).unwrap();
|
||||||
|
|
||||||
|
// Handle subcommands.
|
||||||
|
if let Some(subcommand) = cli.subcommand {
|
||||||
|
match subcommand {
|
||||||
|
Sub::Validate { config } => {
|
||||||
|
Config::load(config).context("error loading config")?;
|
||||||
|
info!("config is valid");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"starting version {} ({})",
|
"starting version {} ({})",
|
||||||
env!("CARGO_PKG_VERSION"),
|
env!("CARGO_PKG_VERSION"),
|
||||||
@@ -99,7 +129,6 @@ fn main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Load the config.
|
// Load the config.
|
||||||
miette::set_hook(Box::new(|_| Box::new(NarratableReportHandler::new()))).unwrap();
|
|
||||||
let (mut config, path) = match Config::load(cli.config).context("error loading config") {
|
let (mut config, path) = match Config::load(cli.config).context("error loading config") {
|
||||||
Ok((config, path)) => (config, Some(path)),
|
Ok((config, path)) => (config, Some(path)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -174,6 +203,8 @@ fn main() {
|
|||||||
event_loop
|
event_loop
|
||||||
.run(None, &mut state, |state| state.refresh_and_flush_clients())
|
.run(None, &mut state, |state| state.refresh_and_flush_clients())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_env_to_systemd() {
|
fn import_env_to_systemd() {
|
||||||
|
|||||||
+127
-19
@@ -32,7 +32,7 @@ use smithay::desktop::utils::{
|
|||||||
use smithay::desktop::{
|
use smithay::desktop::{
|
||||||
layer_map_for_output, LayerSurface, PopupManager, Space, Window, WindowSurfaceType,
|
layer_map_for_output, LayerSurface, PopupManager, Space, Window, WindowSurfaceType,
|
||||||
};
|
};
|
||||||
use smithay::input::keyboard::{Layout as KeyboardLayout, XkbConfig, XkbContextHandler};
|
use smithay::input::keyboard::{Layout as KeyboardLayout, XkbContextHandler};
|
||||||
use smithay::input::pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus, MotionEvent};
|
use smithay::input::pointer::{CursorIcon, CursorImageAttributes, CursorImageStatus, MotionEvent};
|
||||||
use smithay::input::{Seat, SeatState};
|
use smithay::input::{Seat, SeatState};
|
||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
@@ -41,6 +41,7 @@ use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
|||||||
use smithay::reexports::calloop::{
|
use smithay::reexports::calloop::{
|
||||||
self, Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction, RegistrationToken,
|
self, Idle, Interest, LoopHandle, LoopSignal, Mode, PostAction, RegistrationToken,
|
||||||
};
|
};
|
||||||
|
use smithay::reexports::input;
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::WmCapabilities;
|
||||||
use smithay::reexports::wayland_protocols_misc::server_decoration as _server_decoration;
|
use smithay::reexports::wayland_protocols_misc::server_decoration as _server_decoration;
|
||||||
use smithay::reexports::wayland_server::backend::{
|
use smithay::reexports::wayland_server::backend::{
|
||||||
@@ -60,8 +61,10 @@ use smithay::wayland::cursor_shape::CursorShapeManagerState;
|
|||||||
use smithay::wayland::dmabuf::DmabufFeedback;
|
use smithay::wayland::dmabuf::DmabufFeedback;
|
||||||
use smithay::wayland::input_method::InputMethodManagerState;
|
use smithay::wayland::input_method::InputMethodManagerState;
|
||||||
use smithay::wayland::output::OutputManagerState;
|
use smithay::wayland::output::OutputManagerState;
|
||||||
|
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsState};
|
||||||
use smithay::wayland::pointer_gestures::PointerGesturesState;
|
use smithay::wayland::pointer_gestures::PointerGesturesState;
|
||||||
use smithay::wayland::presentation::PresentationState;
|
use smithay::wayland::presentation::PresentationState;
|
||||||
|
use smithay::wayland::relative_pointer::RelativePointerManagerState;
|
||||||
use smithay::wayland::selection::data_device::{set_data_device_selection, DataDeviceState};
|
use smithay::wayland::selection::data_device::{set_data_device_selection, DataDeviceState};
|
||||||
use smithay::wayland::selection::primary_selection::PrimarySelectionState;
|
use smithay::wayland::selection::primary_selection::PrimarySelectionState;
|
||||||
use smithay::wayland::selection::wlr_data_control::DataControlState;
|
use smithay::wayland::selection::wlr_data_control::DataControlState;
|
||||||
@@ -72,7 +75,7 @@ use smithay::wayland::shell::xdg::decoration::XdgDecorationState;
|
|||||||
use smithay::wayland::shell::xdg::XdgShellState;
|
use smithay::wayland::shell::xdg::XdgShellState;
|
||||||
use smithay::wayland::shm::ShmState;
|
use smithay::wayland::shm::ShmState;
|
||||||
use smithay::wayland::socket::ListeningSocketSource;
|
use smithay::wayland::socket::ListeningSocketSource;
|
||||||
use smithay::wayland::tablet_manager::TabletManagerState;
|
use smithay::wayland::tablet_manager::{TabletManagerState, TabletSeatTrait};
|
||||||
use smithay::wayland::text_input::TextInputManagerState;
|
use smithay::wayland::text_input::TextInputManagerState;
|
||||||
use smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState;
|
use smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState;
|
||||||
|
|
||||||
@@ -86,6 +89,7 @@ use crate::dbus::gnome_shell_screenshot::{NiriToScreenshot, ScreenshotToNiri};
|
|||||||
use crate::dbus::mutter_screen_cast::{self, ScreenCastToNiri};
|
use crate::dbus::mutter_screen_cast::{self, ScreenCastToNiri};
|
||||||
use crate::frame_clock::FrameClock;
|
use crate::frame_clock::FrameClock;
|
||||||
use crate::handlers::configure_lock_surface;
|
use crate::handlers::configure_lock_surface;
|
||||||
|
use crate::input::TabletData;
|
||||||
use crate::layout::{output_size, Layout, MonitorRenderElement};
|
use crate::layout::{output_size, Layout, MonitorRenderElement};
|
||||||
use crate::pw_utils::{Cast, PipeWire};
|
use crate::pw_utils::{Cast, PipeWire};
|
||||||
use crate::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement};
|
use crate::screenshot_ui::{ScreenshotUi, ScreenshotUiRenderElement};
|
||||||
@@ -121,6 +125,8 @@ pub struct Niri {
|
|||||||
// When false, we're idling with monitors powered off.
|
// When false, we're idling with monitors powered off.
|
||||||
pub monitors_active: bool,
|
pub monitors_active: bool,
|
||||||
|
|
||||||
|
pub tablets: HashMap<input::Device, TabletData>,
|
||||||
|
|
||||||
// Smithay state.
|
// Smithay state.
|
||||||
pub compositor_state: CompositorState,
|
pub compositor_state: CompositorState,
|
||||||
pub xdg_shell_state: XdgShellState,
|
pub xdg_shell_state: XdgShellState,
|
||||||
@@ -136,6 +142,8 @@ pub struct Niri {
|
|||||||
pub input_method_state: InputMethodManagerState,
|
pub input_method_state: InputMethodManagerState,
|
||||||
pub virtual_keyboard_state: VirtualKeyboardManagerState,
|
pub virtual_keyboard_state: VirtualKeyboardManagerState,
|
||||||
pub pointer_gestures_state: PointerGesturesState,
|
pub pointer_gestures_state: PointerGesturesState,
|
||||||
|
pub relative_pointer_state: RelativePointerManagerState,
|
||||||
|
pub pointer_constraints_state: PointerConstraintsState,
|
||||||
pub data_device_state: DataDeviceState,
|
pub data_device_state: DataDeviceState,
|
||||||
pub primary_selection_state: PrimarySelectionState,
|
pub primary_selection_state: PrimarySelectionState,
|
||||||
pub data_control_state: DataControlState,
|
pub data_control_state: DataControlState,
|
||||||
@@ -151,6 +159,7 @@ pub struct Niri {
|
|||||||
pub cursor_shape_manager_state: CursorShapeManagerState,
|
pub cursor_shape_manager_state: CursorShapeManagerState,
|
||||||
pub dnd_icon: Option<WlSurface>,
|
pub dnd_icon: Option<WlSurface>,
|
||||||
pub pointer_focus: Option<PointerFocus>,
|
pub pointer_focus: Option<PointerFocus>,
|
||||||
|
pub tablet_cursor_location: Option<Point<f64, Logical>>,
|
||||||
|
|
||||||
pub lock_state: LockState,
|
pub lock_state: LockState,
|
||||||
|
|
||||||
@@ -292,6 +301,8 @@ impl State {
|
|||||||
|
|
||||||
pub fn move_cursor(&mut self, location: Point<f64, Logical>) {
|
pub fn move_cursor(&mut self, location: Point<f64, Logical>) {
|
||||||
let under = self.niri.surface_under_and_global_space(location);
|
let under = self.niri.surface_under_and_global_space(location);
|
||||||
|
self.niri
|
||||||
|
.maybe_activate_pointer_constraint(location, &under);
|
||||||
self.niri.pointer_focus = under.clone();
|
self.niri.pointer_focus = under.clone();
|
||||||
let under = under.map(|u| u.surface);
|
let under = under.map(|u| u.surface);
|
||||||
|
|
||||||
@@ -348,6 +359,9 @@ impl State {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.niri
|
||||||
|
.maybe_activate_pointer_constraint(location, &under);
|
||||||
|
|
||||||
self.niri.pointer_focus = under.clone();
|
self.niri.pointer_focus = under.clone();
|
||||||
let under = under.map(|u| u.surface);
|
let under = under.map(|u| u.surface);
|
||||||
|
|
||||||
@@ -461,8 +475,10 @@ impl State {
|
|||||||
self.niri.layout.update_config(&config);
|
self.niri.layout.update_config(&config);
|
||||||
animation::ANIMATION_SLOWDOWN.store(config.debug.animation_slowdown, Ordering::Relaxed);
|
animation::ANIMATION_SLOWDOWN.store(config.debug.animation_slowdown, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let mut reload_xkb = None;
|
||||||
let mut old_config = self.niri.config.borrow_mut();
|
let mut old_config = self.niri.config.borrow_mut();
|
||||||
|
|
||||||
|
// Reload the cursor.
|
||||||
if config.cursor != old_config.cursor {
|
if config.cursor != old_config.cursor {
|
||||||
self.niri
|
self.niri
|
||||||
.cursor_manager
|
.cursor_manager
|
||||||
@@ -470,15 +486,38 @@ impl State {
|
|||||||
self.niri.cursor_texture_cache.clear();
|
self.niri.cursor_texture_cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need &mut self to reload the xkb config, so just store it here.
|
||||||
|
if config.input.keyboard.xkb != old_config.input.keyboard.xkb {
|
||||||
|
reload_xkb = Some(config.input.keyboard.xkb.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the repeat info.
|
||||||
|
if config.input.keyboard.repeat_rate != old_config.input.keyboard.repeat_rate
|
||||||
|
|| config.input.keyboard.repeat_delay != old_config.input.keyboard.repeat_delay
|
||||||
|
{
|
||||||
|
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
||||||
|
keyboard.change_repeat_info(
|
||||||
|
config.input.keyboard.repeat_rate.into(),
|
||||||
|
config.input.keyboard.repeat_delay.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
*old_config = config;
|
*old_config = config;
|
||||||
|
|
||||||
// Release the borrow.
|
// Release the borrow.
|
||||||
drop(old_config);
|
drop(old_config);
|
||||||
|
|
||||||
|
// Now with a &mut self we can reload the xkb config.
|
||||||
|
if let Some(xkb) = reload_xkb {
|
||||||
|
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
||||||
|
if let Err(err) = keyboard.set_xkb_config(self, xkb.to_xkb_config()) {
|
||||||
|
warn!("error updating xkb config: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
// FIXME: apply output scale and whatnot.
|
// FIXME: apply output scale and whatnot.
|
||||||
// FIXME: apply libinput device settings.
|
// FIXME: apply libinput device settings.
|
||||||
// FIXME: apply xkb settings.
|
|
||||||
// FIXME: apply xdg decoration settings.
|
// FIXME: apply xdg decoration settings.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,6 +648,8 @@ impl Niri {
|
|||||||
let mut seat_state = SeatState::new();
|
let mut seat_state = SeatState::new();
|
||||||
let tablet_state = TabletManagerState::new::<State>(&display_handle);
|
let tablet_state = TabletManagerState::new::<State>(&display_handle);
|
||||||
let pointer_gestures_state = PointerGesturesState::new::<State>(&display_handle);
|
let pointer_gestures_state = PointerGesturesState::new::<State>(&display_handle);
|
||||||
|
let relative_pointer_state = RelativePointerManagerState::new::<State>(&display_handle);
|
||||||
|
let pointer_constraints_state = PointerConstraintsState::new::<State>(&display_handle);
|
||||||
let data_device_state = DataDeviceState::new::<State>(&display_handle);
|
let data_device_state = DataDeviceState::new::<State>(&display_handle);
|
||||||
let primary_selection_state = PrimarySelectionState::new::<State>(&display_handle);
|
let primary_selection_state = PrimarySelectionState::new::<State>(&display_handle);
|
||||||
let data_control_state = DataControlState::new::<State, _>(
|
let data_control_state = DataControlState::new::<State, _>(
|
||||||
@@ -626,17 +667,10 @@ impl Niri {
|
|||||||
VirtualKeyboardManagerState::new::<State, _>(&display_handle, |_| true);
|
VirtualKeyboardManagerState::new::<State, _>(&display_handle, |_| true);
|
||||||
|
|
||||||
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
|
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
|
||||||
let xkb = XkbConfig {
|
|
||||||
rules: &config_.input.keyboard.xkb.rules,
|
|
||||||
model: &config_.input.keyboard.xkb.model,
|
|
||||||
layout: config_.input.keyboard.xkb.layout.as_deref().unwrap_or("us"),
|
|
||||||
variant: &config_.input.keyboard.xkb.variant,
|
|
||||||
options: config_.input.keyboard.xkb.options.clone(),
|
|
||||||
};
|
|
||||||
seat.add_keyboard(
|
seat.add_keyboard(
|
||||||
xkb,
|
config_.input.keyboard.xkb.to_xkb_config(),
|
||||||
config_.input.keyboard.repeat_delay as i32,
|
config_.input.keyboard.repeat_delay.into(),
|
||||||
config_.input.keyboard.repeat_rate as i32,
|
config_.input.keyboard.repeat_rate.into(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
seat.add_pointer();
|
seat.add_pointer();
|
||||||
@@ -645,6 +679,23 @@ impl Niri {
|
|||||||
let cursor_manager =
|
let cursor_manager =
|
||||||
CursorManager::new(&config_.cursor.xcursor_theme, config_.cursor.xcursor_size);
|
CursorManager::new(&config_.cursor.xcursor_theme, config_.cursor.xcursor_size);
|
||||||
|
|
||||||
|
let (tx, rx) = calloop::channel::channel();
|
||||||
|
event_loop
|
||||||
|
.insert_source(rx, move |event, _, state| {
|
||||||
|
if let calloop::channel::Event::Msg(image) = event {
|
||||||
|
state.niri.cursor_manager.set_cursor_image(image);
|
||||||
|
// FIXME: granular.
|
||||||
|
state.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
seat.tablet_seat()
|
||||||
|
.on_cursor_surface(move |_tool, new_image| {
|
||||||
|
if let Err(err) = tx.send(new_image) {
|
||||||
|
warn!("error sending new tablet cursor image: {err:?}");
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
let screenshot_ui = ScreenshotUi::new();
|
let screenshot_ui = ScreenshotUi::new();
|
||||||
|
|
||||||
let socket_source = ListeningSocketSource::new_auto().unwrap();
|
let socket_source = ListeningSocketSource::new_auto().unwrap();
|
||||||
@@ -694,6 +745,8 @@ impl Niri {
|
|||||||
unmapped_windows: HashMap::new(),
|
unmapped_windows: HashMap::new(),
|
||||||
monitors_active: true,
|
monitors_active: true,
|
||||||
|
|
||||||
|
tablets: HashMap::new(),
|
||||||
|
|
||||||
compositor_state,
|
compositor_state,
|
||||||
xdg_shell_state,
|
xdg_shell_state,
|
||||||
xdg_decoration_state,
|
xdg_decoration_state,
|
||||||
@@ -708,6 +761,8 @@ impl Niri {
|
|||||||
seat_state,
|
seat_state,
|
||||||
tablet_state,
|
tablet_state,
|
||||||
pointer_gestures_state,
|
pointer_gestures_state,
|
||||||
|
relative_pointer_state,
|
||||||
|
pointer_constraints_state,
|
||||||
data_device_state,
|
data_device_state,
|
||||||
primary_selection_state,
|
primary_selection_state,
|
||||||
data_control_state,
|
data_control_state,
|
||||||
@@ -721,6 +776,7 @@ impl Niri {
|
|||||||
cursor_shape_manager_state,
|
cursor_shape_manager_state,
|
||||||
dnd_icon: None,
|
dnd_icon: None,
|
||||||
pointer_focus: None,
|
pointer_focus: None,
|
||||||
|
tablet_cursor_location: None,
|
||||||
|
|
||||||
lock_state: LockState::Unlocked,
|
lock_state: LockState::Unlocked,
|
||||||
|
|
||||||
@@ -977,17 +1033,21 @@ impl Niri {
|
|||||||
Some((output, pos_within_output))
|
Some((output, pos_within_output))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn window_under_cursor(&self) -> Option<&Window> {
|
pub fn window_under(&self, pos: Point<f64, Logical>) -> Option<&Window> {
|
||||||
if self.is_locked() || self.screenshot_ui.is_open() {
|
if self.is_locked() || self.screenshot_ui.is_open() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pos = self.seat.get_pointer().unwrap().current_location();
|
|
||||||
let (output, pos_within_output) = self.output_under(pos)?;
|
let (output, pos_within_output) = self.output_under(pos)?;
|
||||||
let (window, _loc) = self.layout.window_under(output, pos_within_output)?;
|
let (window, _loc) = self.layout.window_under(output, pos_within_output)?;
|
||||||
Some(window)
|
Some(window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn window_under_cursor(&self) -> Option<&Window> {
|
||||||
|
let pos = self.seat.get_pointer().unwrap().current_location();
|
||||||
|
self.window_under(pos)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the surface under cursor and its position in the global space.
|
/// Returns the surface under cursor and its position in the global space.
|
||||||
///
|
///
|
||||||
/// Pointer needs location in global space, and focused window location compatible with that
|
/// Pointer needs location in global space, and focused window location compatible with that
|
||||||
@@ -1160,6 +1220,22 @@ impl Niri {
|
|||||||
.or_else(|| self.global_space.outputs().next())
|
.or_else(|| self.global_space.outputs().next())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn output_for_root(&self, root: &WlSurface) -> Option<Output> {
|
||||||
|
// Check the main layout.
|
||||||
|
let win_out = self.layout.find_window_and_output(root);
|
||||||
|
let layout_output = win_out.map(|(_, output)| output);
|
||||||
|
|
||||||
|
// Check layer-shell.
|
||||||
|
let has_layer_surface = |o: &&Output| {
|
||||||
|
layer_map_for_output(o)
|
||||||
|
.layer_for_surface(root, WindowSurfaceType::TOPLEVEL)
|
||||||
|
.is_some()
|
||||||
|
};
|
||||||
|
let layer_shell_output = || self.layout.outputs().find(has_layer_surface).cloned();
|
||||||
|
|
||||||
|
layout_output.or_else(layer_shell_output)
|
||||||
|
}
|
||||||
|
|
||||||
fn lock_surface_focus(&self) -> Option<WlSurface> {
|
fn lock_surface_focus(&self) -> Option<WlSurface> {
|
||||||
let output_under_cursor = self.output_under_cursor();
|
let output_under_cursor = self.output_under_cursor();
|
||||||
let output = output_under_cursor
|
let output = output_under_cursor
|
||||||
@@ -1220,7 +1296,12 @@ impl Niri {
|
|||||||
let _span = tracy_client::span!("Niri::pointer_element");
|
let _span = tracy_client::span!("Niri::pointer_element");
|
||||||
let output_scale = output.current_scale();
|
let output_scale = output.current_scale();
|
||||||
let output_pos = self.global_space.output_geometry(output).unwrap().loc;
|
let output_pos = self.global_space.output_geometry(output).unwrap().loc;
|
||||||
let pointer_pos = self.seat.get_pointer().unwrap().current_location() - output_pos.to_f64();
|
|
||||||
|
// Check whether we need to draw the tablet cursor or the regular cursor.
|
||||||
|
let pointer_pos = self
|
||||||
|
.tablet_cursor_location
|
||||||
|
.unwrap_or_else(|| self.seat.get_pointer().unwrap().current_location());
|
||||||
|
let pointer_pos = pointer_pos - output_pos.to_f64();
|
||||||
|
|
||||||
// Get the render cursor to draw.
|
// Get the render cursor to draw.
|
||||||
let cursor_scale = output_scale.integer_scale();
|
let cursor_scale = output_scale.integer_scale();
|
||||||
@@ -1291,6 +1372,11 @@ impl Niri {
|
|||||||
pub fn refresh_pointer_outputs(&mut self) {
|
pub fn refresh_pointer_outputs(&mut self) {
|
||||||
let _span = tracy_client::span!("Niri::refresh_pointer_outputs");
|
let _span = tracy_client::span!("Niri::refresh_pointer_outputs");
|
||||||
|
|
||||||
|
// Check whether we need to draw the tablet cursor or the regular cursor.
|
||||||
|
let pointer_pos = self
|
||||||
|
.tablet_cursor_location
|
||||||
|
.unwrap_or_else(|| self.seat.get_pointer().unwrap().current_location());
|
||||||
|
|
||||||
match self.cursor_manager.cursor_image().clone() {
|
match self.cursor_manager.cursor_image().clone() {
|
||||||
CursorImageStatus::Surface(ref surface) => {
|
CursorImageStatus::Surface(ref surface) => {
|
||||||
let hotspot = with_states(surface, |states| {
|
let hotspot = with_states(surface, |states| {
|
||||||
@@ -1303,7 +1389,6 @@ impl Niri {
|
|||||||
.hotspot
|
.hotspot
|
||||||
});
|
});
|
||||||
|
|
||||||
let pointer_pos = self.seat.get_pointer().unwrap().current_location();
|
|
||||||
let surface_pos = pointer_pos.to_i32_round() - hotspot;
|
let surface_pos = pointer_pos.to_i32_round() - hotspot;
|
||||||
let bbox = bbox_from_surface_tree(surface, surface_pos);
|
let bbox = bbox_from_surface_tree(surface, surface_pos);
|
||||||
|
|
||||||
@@ -1361,8 +1446,6 @@ impl Niri {
|
|||||||
Default::default()
|
Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let pointer_pos = self.seat.get_pointer().unwrap().current_location();
|
|
||||||
|
|
||||||
let mut dnd_scale = 1;
|
let mut dnd_scale = 1;
|
||||||
for output in self.global_space.outputs() {
|
for output in self.global_space.outputs() {
|
||||||
let geo = self.global_space.output_geometry(output).unwrap();
|
let geo = self.global_space.output_geometry(output).unwrap();
|
||||||
@@ -2278,6 +2361,31 @@ impl Niri {
|
|||||||
|
|
||||||
output_state.lock_surface = Some(surface);
|
output_state.lock_surface = Some(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn maybe_activate_pointer_constraint(
|
||||||
|
&self,
|
||||||
|
new_pos: Point<f64, Logical>,
|
||||||
|
new_under: &Option<PointerFocus>,
|
||||||
|
) {
|
||||||
|
let Some(under) = new_under else { return };
|
||||||
|
let pointer = &self.seat.get_pointer().unwrap();
|
||||||
|
with_pointer_constraint(&under.surface.0, pointer, |constraint| {
|
||||||
|
let Some(constraint) = constraint else { return };
|
||||||
|
if constraint.is_active() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constraint does not apply if not within region.
|
||||||
|
if let Some(region) = constraint.region() {
|
||||||
|
let new_pos_within_surface = new_pos.to_i32_round() - under.surface.1;
|
||||||
|
if !region.contains(new_pos_within_surface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint.activate();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render_elements! {
|
render_elements! {
|
||||||
|
|||||||
Reference in New Issue
Block a user