mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 75c79116a7 | |||
| 4f44ef081f | |||
| 4fc76b50d0 | |||
| e1f065ac23 | |||
| 7cc10ce1b5 | |||
| 9d8f640503 | |||
| b18cfbae23 | |||
| f64e7e14c3 | |||
| e8c9bfc06a | |||
| 07452f50a8 | |||
| 642c5acebb | |||
| 0886dedff1 | |||
| cc88a7d42e | |||
| c0829087da | |||
| b6f6d6a7c2 | |||
| 5ff8b89aaf | |||
| 927abad4b4 | |||
| 3d31f9860a | |||
| 8867a4f84c | |||
| 88f4c1d610 | |||
| ddcb5c5e10 | |||
| cd90dfc7be | |||
| a778ab3897 | |||
| 4c2f49d566 | |||
| 49d7052bb3 | |||
| 07be7e7eae | |||
| 97c8717d1e | |||
| 3ac0a751fe | |||
| 8b39f986d9 | |||
| 354c365a03 | |||
| e0ebf1bdff | |||
| 11633aef98 | |||
| 9193245871 | |||
| 7baf10b751 | |||
| f5d91c5ecc | |||
| 69e3edb5a3 | |||
| d58bb4eaa3 | |||
| c5fe25f422 | |||
| 600cffb009 | |||
| b9d14a9eda | |||
| 0e7e398df3 | |||
| 86bdc6898b | |||
| e5ca335115 | |||
| fce5d66878 | |||
| 05d218113c | |||
| ef6af6adc1 | |||
| 6632699e00 | |||
| d3e72245b0 | |||
| 13fe9c8ac3 | |||
| 6ecbf2db8a | |||
| c9be9056ef | |||
| 0866990b7d | |||
| f04befb567 | |||
| da3e5c4424 | |||
| 26ab4dfb87 | |||
| e887ee93a3 | |||
| d640e85158 | |||
| c8044a9b5d | |||
| 289ae3604d | |||
| 55fb885256 | |||
| 73a531f8bc | |||
| 10f04fd19d | |||
| 79fd309d6c | |||
| dd8b2be044 | |||
| 8d08782eba | |||
| 8555f37dbf | |||
| 4b837f429c | |||
| a480087618 | |||
| 84655d3b26 | |||
| 40843cbda1 | |||
| a13b9298c6 | |||
| 0c5e046820 | |||
| 907ebc4977 | |||
| e4161be1bf | |||
| be7fbd418f | |||
| 06ec9eecdb | |||
| 79eef5ee90 | |||
| 29602ca995 | |||
| d7156df842 | |||
| 33b39913c7 | |||
| d5cbc35811 | |||
| a038c5aaab | |||
| c9c985c927 | |||
| 859c0be0e5 | |||
| 810ea245f9 | |||
| 58fc5f3b06 | |||
| 7d4e99b760 | |||
| ab7d81aae0 | |||
| e24723125f | |||
| 03c603918d | |||
| 6fb60dacd2 | |||
| 42a9daec9d | |||
| 1ba2be3928 | |||
| 66be000410 | |||
| 5fc669c282 | |||
| 9b78b15ba5 | |||
| b9fd0a405e | |||
| 1b44e0cd20 | |||
| b3d4d4eacc | |||
| a835bdc940 | |||
| b258fd69d2 | |||
| 3ab3e778ab | |||
| e6203313ce | |||
| 938061dd5e | |||
| 0cca7a2116 | |||
| 39b46b3326 | |||
| 2aebd6bdbb | |||
| b501a9b303 | |||
| 94e5408f46 | |||
| eb190e3f94 | |||
| 80bb0d5876 | |||
| c04ccafd0a | |||
| 6ee5b5afa7 |
@@ -192,7 +192,7 @@ jobs:
|
|||||||
uses: DeterminateSystems/nix-installer-action@v3
|
uses: DeterminateSystems/nix-installer-action@v3
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- run: nix build
|
- run: nix flake check
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
publish-wiki:
|
publish-wiki:
|
||||||
|
|||||||
Generated
+614
-398
File diff suppressed because it is too large
Load Diff
+27
-25
@@ -2,7 +2,7 @@
|
|||||||
members = ["niri-visual-tests"]
|
members = ["niri-visual-tests"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.9"
|
version = "0.1.10-1"
|
||||||
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"
|
||||||
@@ -11,15 +11,15 @@ repository = "https://github.com/YaLTeR/niri"
|
|||||||
rust-version = "1.77"
|
rust-version = "1.77"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1.0.88"
|
anyhow = "1.0.93"
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.6.0"
|
||||||
clap = { version = "4.5.17", features = ["derive"] }
|
clap = { version = "4.5.20", features = ["derive"] }
|
||||||
k9 = "0.12.0"
|
k9 = "0.12.0"
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.214", features = ["derive"] }
|
||||||
serde_json = "1.0.128"
|
serde_json = "1.0.132"
|
||||||
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"] }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
tracy-client = { version = "0.17.3", default-features = false }
|
tracy-client = { version = "0.17.4", default-features = false }
|
||||||
|
|
||||||
[workspace.dependencies.smithay]
|
[workspace.dependencies.smithay]
|
||||||
git = "https://github.com/Smithay/smithay.git"
|
git = "https://github.com/Smithay/smithay.git"
|
||||||
@@ -50,38 +50,38 @@ async-channel = "2.3.1"
|
|||||||
async-io = { version = "1.13.0", optional = true }
|
async-io = { version = "1.13.0", optional = true }
|
||||||
atomic = "0.6.0"
|
atomic = "0.6.0"
|
||||||
bitflags.workspace = true
|
bitflags.workspace = true
|
||||||
bytemuck = { version = "1.18.0", features = ["derive"] }
|
bytemuck = { version = "1.19.0", features = ["derive"] }
|
||||||
calloop = { version = "0.14.1", features = ["executor", "futures-io"] }
|
calloop = { version = "0.14.1", features = ["executor", "futures-io"] }
|
||||||
clap = { workspace = true, features = ["string"] }
|
clap = { workspace = true, features = ["string"] }
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
drm-ffi = "0.8.0"
|
drm-ffi = "0.9.0"
|
||||||
fastrand = "2.1.1"
|
fastrand = "2.2.0"
|
||||||
futures-util = { version = "0.3.30", default-features = false, features = ["std", "io"] }
|
futures-util = { version = "0.3.31", default-features = false, features = ["std", "io"] }
|
||||||
git-version = "0.3.9"
|
git-version = "0.3.9"
|
||||||
glam = "0.29.0"
|
glam = "0.29.2"
|
||||||
input = { version = "0.9.0", features = ["libinput_1_21"] }
|
input = { version = "0.9.1", features = ["libinput_1_21"] }
|
||||||
keyframe = { version = "1.1.1", default-features = false }
|
keyframe = { version = "1.1.1", default-features = false }
|
||||||
libc = "0.2.158"
|
libc = "0.2.162"
|
||||||
libdisplay-info = "0.1.0"
|
libdisplay-info = "0.1.0"
|
||||||
log = { version = "0.4.22", features = ["max_level_trace", "release_max_level_debug"] }
|
log = { version = "0.4.22", features = ["max_level_trace", "release_max_level_debug"] }
|
||||||
niri-config = { version = "0.1.9", path = "niri-config" }
|
niri-config = { version = "0.1.10-1", path = "niri-config" }
|
||||||
niri-ipc = { version = "0.1.9", path = "niri-ipc", features = ["clap"] }
|
niri-ipc = { version = "0.1.10-1", path = "niri-ipc", features = ["clap"] }
|
||||||
notify-rust = { version = "~4.10.0", optional = true }
|
notify-rust = { version = "~4.10.0", optional = true }
|
||||||
ordered-float = "4.2.2"
|
ordered-float = "4.5.0"
|
||||||
pango = { version = "0.20.1", features = ["v1_44"] }
|
pango = { version = "0.20.4", features = ["v1_44"] }
|
||||||
pangocairo = "0.20.1"
|
pangocairo = "0.20.4"
|
||||||
pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", optional = true, features = ["v0_3_33"] }
|
pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", optional = true, features = ["v0_3_33"] }
|
||||||
png = "0.17.13"
|
png = "0.17.14"
|
||||||
portable-atomic = { version = "1.7.0", default-features = false, features = ["float"] }
|
portable-atomic = { version = "1.9.0", default-features = false, features = ["float"] }
|
||||||
profiling = "1.0.15"
|
profiling = "1.0.16"
|
||||||
sd-notify = "0.4.2"
|
sd-notify = "0.4.3"
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
smithay-drm-extras.workspace = true
|
smithay-drm-extras.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracy-client.workspace = true
|
tracy-client.workspace = true
|
||||||
url = { version = "2.5.2", optional = true }
|
url = { version = "2.5.3", optional = true }
|
||||||
wayland-backend = "0.3.7"
|
wayland-backend = "0.3.7"
|
||||||
wayland-scanner = "0.31.5"
|
wayland-scanner = "0.31.5"
|
||||||
xcursor = "0.3.8"
|
xcursor = "0.3.8"
|
||||||
@@ -109,7 +109,7 @@ features = [
|
|||||||
approx = "0.5.1"
|
approx = "0.5.1"
|
||||||
k9.workspace = true
|
k9.workspace = true
|
||||||
proptest = "1.5.0"
|
proptest = "1.5.0"
|
||||||
proptest-derive = "0.5.0"
|
proptest-derive = { version = "0.5.0", features = ["boxed_union"] }
|
||||||
xshell = "0.2.6"
|
xshell = "0.2.6"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@@ -124,6 +124,8 @@ xdp-gnome-screencast = ["dbus", "pipewire"]
|
|||||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"]
|
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"]
|
||||||
# Enables the on-demand Tracy profiler instrumentation.
|
# Enables the on-demand Tracy profiler instrumentation.
|
||||||
profile-with-tracy-ondemand = ["profile-with-tracy", "tracy-client/ondemand", "tracy-client/manual-lifetime"]
|
profile-with-tracy-ondemand = ["profile-with-tracy", "tracy-client/ondemand", "tracy-client/manual-lifetime"]
|
||||||
|
# Enables Tracy allocation profiling.
|
||||||
|
profile-with-tracy-allocations = ["profile-with-tracy"]
|
||||||
# Enables dinit integration (global environment).
|
# Enables dinit integration (global environment).
|
||||||
dinit = []
|
dinit = []
|
||||||
|
|
||||||
@@ -137,7 +139,7 @@ lto = "thin"
|
|||||||
debug = false
|
debug = false
|
||||||
|
|
||||||
[package.metadata.generate-rpm]
|
[package.metadata.generate-rpm]
|
||||||
version = "0.1.9"
|
version = "0.1.10.1"
|
||||||
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" },
|
||||||
|
|||||||
Generated
+18
-92
@@ -1,65 +1,5 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"crane": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1724533099,
|
|
||||||
"narHash": "sha256-ZIDtvVQHoCkNoBlLUB3wmqbqCb0Es3DfdUGeDI/58aY=",
|
|
||||||
"owner": "ipetkov",
|
|
||||||
"repo": "crane",
|
|
||||||
"rev": "7543c8d76f91b8844e0f3b3cc347a72d8fb4b49e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "ipetkov",
|
|
||||||
"repo": "crane",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fenix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1722493751,
|
|
||||||
"narHash": "sha256-l7/yMehbrL5d4AI8E2hKtNlT50BlUAau4EKTgPg9KcY=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"rev": "60ab4a085ef6ee40f2ef7921ca4061084dd8cf26",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "monthly",
|
|
||||||
"repo": "fenix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1710146030,
|
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix-filter": {
|
"nix-filter": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710156097,
|
"lastModified": 1710156097,
|
||||||
@@ -77,11 +17,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1724395761,
|
"lastModified": 1726365531,
|
||||||
"narHash": "sha256-zRkDV/nbrnp3Y8oCADf5ETl1sDrdmAW6/bBVJ8EbIdQ=",
|
"narHash": "sha256-luAKNxWZ+ZN0kaHchx1OdLQ71n81Y31ryNPWP1YRDZc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ae815cee91b417be55d43781eb4b73ae1ecc396c",
|
"rev": "9299cdf978e15f448cf82667b0ffdd480b44ee48",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -93,42 +33,28 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"crane": "crane",
|
|
||||||
"fenix": "fenix",
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nix-filter": "nix-filter",
|
"nix-filter": "nix-filter",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"rust-overlay": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1722449213,
|
"lastModified": 1727663505,
|
||||||
"narHash": "sha256-1na4m2PNH99syz2g/WQ+Hr3RfY7k4H8NBnmkr5dFDXw=",
|
"narHash": "sha256-83j/GrHsx8GFUcQofKh+PRPz6pz8sxAsZyT/HCNdey8=",
|
||||||
"owner": "rust-lang",
|
"owner": "oxalica",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-overlay",
|
||||||
"rev": "c8e41d95061543715b30880932ec3dc24c42d7ae",
|
"rev": "c2099c6c7599ea1980151b8b6247a8f93e1806ee",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "rust-lang",
|
"owner": "oxalica",
|
||||||
"ref": "nightly",
|
"repo": "rust-overlay",
|
||||||
"repo": "rust-analyzer",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,101 +4,248 @@
|
|||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
crane = {
|
|
||||||
url = "github:ipetkov/crane";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
nix-filter.url = "github:numtide/nix-filter";
|
nix-filter.url = "github:numtide/nix-filter";
|
||||||
fenix = {
|
|
||||||
url = "github:nix-community/fenix/monthly";
|
# NOTE: This is not necessary for end users
|
||||||
|
# You can omit it with `inputs.rust-overlay.follows = ""`
|
||||||
|
rust-overlay = {
|
||||||
|
url = "github:oxalica/rust-overlay";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs =
|
||||||
self,
|
{
|
||||||
nixpkgs,
|
self,
|
||||||
crane,
|
nixpkgs,
|
||||||
nix-filter,
|
nix-filter,
|
||||||
flake-utils,
|
rust-overlay,
|
||||||
fenix,
|
}:
|
||||||
...
|
let
|
||||||
}: let
|
niri-package =
|
||||||
systems = ["aarch64-linux" "x86_64-linux"];
|
{
|
||||||
in
|
lib,
|
||||||
flake-utils.lib.eachSystem systems (
|
cairo,
|
||||||
system: let
|
clang,
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
dbus,
|
||||||
toolchain = fenix.packages.${system}.complete.toolchain;
|
libGL,
|
||||||
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
|
libclang,
|
||||||
|
libdisplay-info,
|
||||||
|
libinput,
|
||||||
|
seatd,
|
||||||
|
libxkbcommon,
|
||||||
|
mesa,
|
||||||
|
pango,
|
||||||
|
pipewire,
|
||||||
|
pkg-config,
|
||||||
|
rustPlatform,
|
||||||
|
systemd,
|
||||||
|
wayland,
|
||||||
|
withDbus ? true,
|
||||||
|
withSystemd ? true,
|
||||||
|
withScreencastSupport ? true,
|
||||||
|
withDinit ? false,
|
||||||
|
}:
|
||||||
|
|
||||||
craneArgs = {
|
rustPlatform.buildRustPackage {
|
||||||
pname = "niri";
|
pname = "niri";
|
||||||
version = self.rev or "dirty";
|
version = self.shortRev or self.dirtyShortRev or "unknown";
|
||||||
|
|
||||||
src = nixpkgs.lib.cleanSourceWith {
|
src = nix-filter.lib.filter {
|
||||||
src = craneLib.path ./.;
|
root = self;
|
||||||
filter = path: type:
|
include = [
|
||||||
(builtins.match "resources" path == null)
|
"niri-config"
|
||||||
|| ((craneLib.filterCargoSources path type)
|
"niri-ipc"
|
||||||
&& (builtins.match "niri-visual-tests" path == null));
|
"niri-visual-tests"
|
||||||
|
"resources"
|
||||||
|
"src"
|
||||||
|
./Cargo.lock
|
||||||
|
./Cargo.toml
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
postPatch = ''
|
||||||
pkg-config
|
patchShebangs resources/niri-session
|
||||||
autoPatchelfHook
|
substituteInPlace resources/niri.service \
|
||||||
|
--replace-fail '/usr/bin' "$out/bin"
|
||||||
|
'';
|
||||||
|
|
||||||
|
cargoLock = {
|
||||||
|
# NOTE: This is only used for Git dependencies
|
||||||
|
allowBuiltinFetchGit = true;
|
||||||
|
lockFile = ./Cargo.lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
strictDeps = true;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
clang
|
clang
|
||||||
gdk-pixbuf
|
pkg-config
|
||||||
graphene
|
|
||||||
gtk4
|
|
||||||
libadwaita
|
|
||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs =
|
||||||
wayland
|
[
|
||||||
systemd # For libudev
|
cairo
|
||||||
seatd # For libseat
|
dbus
|
||||||
libxkbcommon
|
libGL
|
||||||
libdisplay-info
|
libdisplay-info
|
||||||
libinput
|
libinput
|
||||||
mesa # For libgbm
|
seatd
|
||||||
fontconfig
|
libxkbcommon
|
||||||
stdenv.cc.cc.lib
|
mesa # libgbm
|
||||||
pipewire
|
pango
|
||||||
pango
|
wayland
|
||||||
cairo
|
]
|
||||||
glib
|
++ lib.optional (withDbus || withScreencastSupport || withSystemd) dbus
|
||||||
pixman
|
++ lib.optional withScreencastSupport pipewire
|
||||||
];
|
# Also includes libudev
|
||||||
|
++ lib.optional withSystemd systemd;
|
||||||
|
|
||||||
runtimeDependencies = with pkgs; [
|
buildFeatures =
|
||||||
wayland
|
lib.optional withDbus "dbus"
|
||||||
mesa
|
++ lib.optional withDinit "dinit"
|
||||||
libglvnd # For libEGL
|
++ lib.optional withScreencastSupport "xdp-gnome-screencast"
|
||||||
xorg.libXcursor
|
++ lib.optional withSystemd "systemd";
|
||||||
xorg.libXi
|
buildNoDefaultFeatures = true;
|
||||||
libxkbcommon
|
|
||||||
];
|
|
||||||
|
|
||||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
postInstall =
|
||||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath craneArgs.runtimeDependencies; # Needed for tests to find libxkbcommon
|
''
|
||||||
|
install -Dm644 resources/niri.desktop -t $out/share/wayland-sessions
|
||||||
|
install -Dm644 resources/niri-portals.conf -t $out/share/xdg-desktop-portal
|
||||||
|
''
|
||||||
|
+ lib.optionalString withSystemd ''
|
||||||
|
install -Dm755 resources/niri-session $out/bin/niri-session
|
||||||
|
install -Dm644 resources/niri{.service,-shutdown.target} -t $out/share/systemd/user
|
||||||
|
'';
|
||||||
|
|
||||||
|
env = {
|
||||||
|
LIBCLANG_PATH = lib.getLib libclang + "/lib";
|
||||||
|
|
||||||
|
# Force linking with libEGL and libwayland-client
|
||||||
|
# so they can be discovered by `dlopen()`
|
||||||
|
RUSTFLAGS = toString (
|
||||||
|
map (arg: "-C link-arg=" + arg) [
|
||||||
|
"-Wl,--push-state,--no-as-needed"
|
||||||
|
"-lEGL"
|
||||||
|
"-lwayland-client"
|
||||||
|
"-Wl,--pop-state"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
passthru = {
|
||||||
|
providedSessions = [ "niri" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Scrollable-tiling Wayland compositor";
|
||||||
|
homepage = "https://github.com/YaLTeR/niri";
|
||||||
|
license = lib.licenses.gpl3Only;
|
||||||
|
mainProgram = "niri";
|
||||||
|
platforms = lib.platforms.linux;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
cargoArtifacts = craneLib.buildDepsOnly craneArgs;
|
inherit (nixpkgs) lib;
|
||||||
niri = craneLib.buildPackage (craneArgs // {inherit cargoArtifacts;});
|
# Support all Linux systems that the nixpkgs flake exposes
|
||||||
in {
|
systems = lib.intersectLists lib.systems.flakeExposed lib.platforms.linux;
|
||||||
formatter = pkgs.alejandra;
|
|
||||||
|
|
||||||
checks.niri = niri;
|
forAllSystems = lib.genAttrs systems;
|
||||||
packages.default = niri;
|
nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
checks = forAllSystems (system: {
|
||||||
|
# We use the debug build here to save a bit of time
|
||||||
|
inherit (self.packages.${system}) niri-debug;
|
||||||
|
});
|
||||||
|
|
||||||
devShells.default = craneLib.devShell {
|
devShells = forAllSystems (
|
||||||
inputsFrom = [niri];
|
system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgsFor.${system};
|
||||||
|
rust-bin = rust-overlay.lib.mkRustBin { } pkgs;
|
||||||
|
inherit (self.packages.${system}) niri;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
# We don't use the toolchain from nixpkgs
|
||||||
|
# because we prefer a nightly toolchain
|
||||||
|
# and we *require* a nightly rustfmt
|
||||||
|
(rust-bin.selectLatestNightlyWith (
|
||||||
|
toolchain:
|
||||||
|
toolchain.default.override {
|
||||||
|
extensions = [
|
||||||
|
# includes already:
|
||||||
|
# rustc
|
||||||
|
# cargo
|
||||||
|
# rust-std
|
||||||
|
# rust-docs
|
||||||
|
# rustfmt-preview
|
||||||
|
# clippy-preview
|
||||||
|
"rust-analyzer"
|
||||||
|
"rust-src"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
))
|
||||||
|
];
|
||||||
|
|
||||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (craneArgs.runtimeDependencies ++ craneArgs.nativeBuildInputs ++ craneArgs.buildInputs);
|
nativeBuildInputs = [
|
||||||
inherit (niri) LIBCLANG_PATH;
|
pkgs.clang
|
||||||
};
|
pkgs.pkg-config
|
||||||
}
|
pkgs.wrapGAppsHook4 # For `niri-visual-tests`
|
||||||
);
|
];
|
||||||
|
|
||||||
|
buildInputs = niri.buildInputs ++ [
|
||||||
|
pkgs.libadwaita # For `niri-visual-tests`
|
||||||
|
];
|
||||||
|
|
||||||
|
env = {
|
||||||
|
inherit (niri) LIBCLANG_PATH;
|
||||||
|
|
||||||
|
# WARN: Do not overwrite this variable in your shell!
|
||||||
|
# It is required for `dlopen()` to work on some libraries; see the comment
|
||||||
|
# in the package expression
|
||||||
|
#
|
||||||
|
# This should only be set with `CARGO_BUILD_RUSTFLAGS="$CARGO_BUILD_RUSTFLAGS -C your-flags"`
|
||||||
|
CARGO_BUILD_RUSTFLAGS = niri.RUSTFLAGS;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);
|
||||||
|
|
||||||
|
packages = forAllSystems (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
niri = nixpkgsFor.${system}.callPackage niri-package { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit niri;
|
||||||
|
|
||||||
|
# NOTE: This is for development purposes only
|
||||||
|
#
|
||||||
|
# It is primarily to help with quickly iterating on
|
||||||
|
# changes made to the above expression - though it is
|
||||||
|
# also not stripped in order to better debug niri itself
|
||||||
|
niri-debug = niri.overrideAttrs (
|
||||||
|
newAttrs: oldAttrs: {
|
||||||
|
pname = oldAttrs.pname + "-debug";
|
||||||
|
|
||||||
|
cargoBuildType = "debug";
|
||||||
|
cargoCheckType = newAttrs.cargoBuildType;
|
||||||
|
|
||||||
|
dontStrip = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
default = niri;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
overlays.default = final: _: {
|
||||||
|
niri = final.callPackage niri-package { };
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ bitflags.workspace = true
|
|||||||
csscolorparser = "0.7.0"
|
csscolorparser = "0.7.0"
|
||||||
knuffel = "3.2.0"
|
knuffel = "3.2.0"
|
||||||
miette = "5.10.0"
|
miette = "5.10.0"
|
||||||
niri-ipc = { version = "0.1.9", path = "../niri-ipc" }
|
niri-ipc = { version = "0.1.10-1", path = "../niri-ipc" }
|
||||||
regex = "1.10.6"
|
regex = "1.11.1"
|
||||||
smithay = { workspace = true, features = ["backend_libinput"] }
|
smithay = { workspace = true, features = ["backend_libinput"] }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracy-client.workspace = true
|
tracy-client.workspace = true
|
||||||
@@ -21,4 +21,4 @@ tracy-client.workspace = true
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
k9.workspace = true
|
k9.workspace = true
|
||||||
miette = { version = "5.10.0", features = ["fancy"] }
|
miette = { version = "5.10.0", features = ["fancy"] }
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.1"
|
||||||
|
|||||||
+188
-6
@@ -54,6 +54,8 @@ pub struct Config {
|
|||||||
#[knuffel(child, default)]
|
#[knuffel(child, default)]
|
||||||
pub binds: Binds,
|
pub binds: Binds,
|
||||||
#[knuffel(child, default)]
|
#[knuffel(child, default)]
|
||||||
|
pub switch_events: SwitchBinds,
|
||||||
|
#[knuffel(child, default)]
|
||||||
pub debug: DebugConfig,
|
pub debug: DebugConfig,
|
||||||
#[knuffel(children(name = "workspace"))]
|
#[knuffel(children(name = "workspace"))]
|
||||||
pub workspaces: Vec<Workspace>,
|
pub workspaces: Vec<Workspace>,
|
||||||
@@ -70,6 +72,8 @@ pub struct Input {
|
|||||||
#[knuffel(child, default)]
|
#[knuffel(child, default)]
|
||||||
pub trackpoint: Trackpoint,
|
pub trackpoint: Trackpoint,
|
||||||
#[knuffel(child, default)]
|
#[knuffel(child, default)]
|
||||||
|
pub trackball: Trackball,
|
||||||
|
#[knuffel(child, default)]
|
||||||
pub tablet: Tablet,
|
pub tablet: Tablet,
|
||||||
#[knuffel(child, default)]
|
#[knuffel(child, default)]
|
||||||
pub touch: Touch,
|
pub touch: Touch,
|
||||||
@@ -174,6 +178,8 @@ pub struct Touchpad {
|
|||||||
pub accel_profile: Option<AccelProfile>,
|
pub accel_profile: Option<AccelProfile>,
|
||||||
#[knuffel(child, unwrap(argument, str))]
|
#[knuffel(child, unwrap(argument, str))]
|
||||||
pub scroll_method: Option<ScrollMethod>,
|
pub scroll_method: Option<ScrollMethod>,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub scroll_button: Option<u32>,
|
||||||
#[knuffel(child, unwrap(argument, str))]
|
#[knuffel(child, unwrap(argument, str))]
|
||||||
pub tap_button_map: Option<TapButtonMap>,
|
pub tap_button_map: Option<TapButtonMap>,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
@@ -182,6 +188,8 @@ pub struct Touchpad {
|
|||||||
pub disabled_on_external_mouse: bool,
|
pub disabled_on_external_mouse: bool,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub middle_emulation: bool,
|
pub middle_emulation: bool,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub scroll_factor: Option<FloatOrInt<0, 100>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||||
@@ -196,10 +204,14 @@ pub struct Mouse {
|
|||||||
pub accel_profile: Option<AccelProfile>,
|
pub accel_profile: Option<AccelProfile>,
|
||||||
#[knuffel(child, unwrap(argument, str))]
|
#[knuffel(child, unwrap(argument, str))]
|
||||||
pub scroll_method: Option<ScrollMethod>,
|
pub scroll_method: Option<ScrollMethod>,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub scroll_button: Option<u32>,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub left_handed: bool,
|
pub left_handed: bool,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub middle_emulation: bool,
|
pub middle_emulation: bool,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub scroll_factor: Option<FloatOrInt<0, 100>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||||
@@ -214,6 +226,28 @@ pub struct Trackpoint {
|
|||||||
pub accel_profile: Option<AccelProfile>,
|
pub accel_profile: Option<AccelProfile>,
|
||||||
#[knuffel(child, unwrap(argument, str))]
|
#[knuffel(child, unwrap(argument, str))]
|
||||||
pub scroll_method: Option<ScrollMethod>,
|
pub scroll_method: Option<ScrollMethod>,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub scroll_button: Option<u32>,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub middle_emulation: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||||
|
pub struct Trackball {
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub off: bool,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub natural_scroll: bool,
|
||||||
|
#[knuffel(child, unwrap(argument), default)]
|
||||||
|
pub accel_speed: f64,
|
||||||
|
#[knuffel(child, unwrap(argument, str))]
|
||||||
|
pub accel_profile: Option<AccelProfile>,
|
||||||
|
#[knuffel(child, unwrap(argument, str))]
|
||||||
|
pub scroll_method: Option<ScrollMethod>,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub scroll_button: Option<u32>,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub left_handed: bool,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub middle_emulation: bool,
|
pub middle_emulation: bool,
|
||||||
}
|
}
|
||||||
@@ -391,6 +425,8 @@ pub struct Layout {
|
|||||||
pub focus_ring: FocusRing,
|
pub focus_ring: FocusRing,
|
||||||
#[knuffel(child, default)]
|
#[knuffel(child, default)]
|
||||||
pub border: Border,
|
pub border: Border,
|
||||||
|
#[knuffel(child, default)]
|
||||||
|
pub insert_hint: InsertHint,
|
||||||
#[knuffel(child, unwrap(children), default)]
|
#[knuffel(child, unwrap(children), default)]
|
||||||
pub preset_column_widths: Vec<PresetSize>,
|
pub preset_column_widths: Vec<PresetSize>,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
@@ -412,6 +448,7 @@ impl Default for Layout {
|
|||||||
Self {
|
Self {
|
||||||
focus_ring: Default::default(),
|
focus_ring: Default::default(),
|
||||||
border: Default::default(),
|
border: Default::default(),
|
||||||
|
insert_hint: Default::default(),
|
||||||
preset_column_widths: Default::default(),
|
preset_column_widths: Default::default(),
|
||||||
default_column_width: Default::default(),
|
default_column_width: Default::default(),
|
||||||
center_focused_column: Default::default(),
|
center_focused_column: Default::default(),
|
||||||
@@ -558,6 +595,26 @@ impl From<FocusRing> for Border {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct InsertHint {
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub off: bool,
|
||||||
|
#[knuffel(child, default = Self::default().color)]
|
||||||
|
pub color: Color,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub gradient: Option<Gradient>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InsertHint {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
off: false,
|
||||||
|
color: Color::from_rgba8_unpremul(127, 200, 255, 128),
|
||||||
|
gradient: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// RGB color in [0, 1] with unpremultiplied alpha.
|
/// RGB color in [0, 1] with unpremultiplied alpha.
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
@@ -615,6 +672,10 @@ pub struct Cursor {
|
|||||||
pub xcursor_theme: String,
|
pub xcursor_theme: String,
|
||||||
#[knuffel(child, unwrap(argument), default = 24)]
|
#[knuffel(child, unwrap(argument), default = 24)]
|
||||||
pub xcursor_size: u8,
|
pub xcursor_size: u8,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub hide_when_typing: bool,
|
||||||
|
#[knuffel(child, unwrap(argument))]
|
||||||
|
pub hide_after_inactive_ms: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Cursor {
|
impl Default for Cursor {
|
||||||
@@ -622,6 +683,8 @@ impl Default for Cursor {
|
|||||||
Self {
|
Self {
|
||||||
xcursor_theme: String::from("default"),
|
xcursor_theme: String::from("default"),
|
||||||
xcursor_size: 24,
|
xcursor_size: 24,
|
||||||
|
hide_when_typing: false,
|
||||||
|
hide_after_inactive_ms: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1054,6 +1117,24 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
|
||||||
|
pub struct SwitchBinds {
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub lid_open: Option<SwitchAction>,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub lid_close: Option<SwitchAction>,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub tablet_mode_on: Option<SwitchAction>,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub tablet_mode_off: Option<SwitchAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||||
|
pub struct SwitchAction {
|
||||||
|
#[knuffel(child, unwrap(arguments))]
|
||||||
|
pub spawn: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
// Remember to add new actions to the CLI enum too.
|
// Remember to add new actions to the CLI enum too.
|
||||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
@@ -1062,6 +1143,7 @@ pub enum Action {
|
|||||||
ChangeVt(i32),
|
ChangeVt(i32),
|
||||||
Suspend,
|
Suspend,
|
||||||
PowerOffMonitors,
|
PowerOffMonitors,
|
||||||
|
PowerOnMonitors,
|
||||||
ToggleDebugTint,
|
ToggleDebugTint,
|
||||||
DebugToggleOpaqueRegions,
|
DebugToggleOpaqueRegions,
|
||||||
DebugToggleDamage,
|
DebugToggleDamage,
|
||||||
@@ -1115,7 +1197,11 @@ pub enum Action {
|
|||||||
MoveWindowDownOrToWorkspaceDown,
|
MoveWindowDownOrToWorkspaceDown,
|
||||||
MoveWindowUpOrToWorkspaceUp,
|
MoveWindowUpOrToWorkspaceUp,
|
||||||
ConsumeOrExpelWindowLeft,
|
ConsumeOrExpelWindowLeft,
|
||||||
|
#[knuffel(skip)]
|
||||||
|
ConsumeOrExpelWindowLeftById(u64),
|
||||||
ConsumeOrExpelWindowRight,
|
ConsumeOrExpelWindowRight,
|
||||||
|
#[knuffel(skip)]
|
||||||
|
ConsumeOrExpelWindowRightById(u64),
|
||||||
ConsumeWindowIntoColumn,
|
ConsumeWindowIntoColumn,
|
||||||
ExpelWindowFromColumn,
|
ExpelWindowFromColumn,
|
||||||
CenterColumn,
|
CenterColumn,
|
||||||
@@ -1176,6 +1262,7 @@ impl From<niri_ipc::Action> for Action {
|
|||||||
match value {
|
match value {
|
||||||
niri_ipc::Action::Quit { skip_confirmation } => Self::Quit(skip_confirmation),
|
niri_ipc::Action::Quit { skip_confirmation } => Self::Quit(skip_confirmation),
|
||||||
niri_ipc::Action::PowerOffMonitors {} => Self::PowerOffMonitors,
|
niri_ipc::Action::PowerOffMonitors {} => Self::PowerOffMonitors,
|
||||||
|
niri_ipc::Action::PowerOnMonitors {} => Self::PowerOnMonitors,
|
||||||
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
|
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
|
||||||
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
|
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
|
||||||
niri_ipc::Action::Screenshot {} => Self::Screenshot,
|
niri_ipc::Action::Screenshot {} => Self::Screenshot,
|
||||||
@@ -1221,8 +1308,18 @@ impl From<niri_ipc::Action> for Action {
|
|||||||
Self::MoveWindowDownOrToWorkspaceDown
|
Self::MoveWindowDownOrToWorkspaceDown
|
||||||
}
|
}
|
||||||
niri_ipc::Action::MoveWindowUpOrToWorkspaceUp {} => Self::MoveWindowUpOrToWorkspaceUp,
|
niri_ipc::Action::MoveWindowUpOrToWorkspaceUp {} => Self::MoveWindowUpOrToWorkspaceUp,
|
||||||
niri_ipc::Action::ConsumeOrExpelWindowLeft {} => Self::ConsumeOrExpelWindowLeft,
|
niri_ipc::Action::ConsumeOrExpelWindowLeft { id: None } => {
|
||||||
niri_ipc::Action::ConsumeOrExpelWindowRight {} => Self::ConsumeOrExpelWindowRight,
|
Self::ConsumeOrExpelWindowLeft
|
||||||
|
}
|
||||||
|
niri_ipc::Action::ConsumeOrExpelWindowLeft { id: Some(id) } => {
|
||||||
|
Self::ConsumeOrExpelWindowLeftById(id)
|
||||||
|
}
|
||||||
|
niri_ipc::Action::ConsumeOrExpelWindowRight { id: None } => {
|
||||||
|
Self::ConsumeOrExpelWindowRight
|
||||||
|
}
|
||||||
|
niri_ipc::Action::ConsumeOrExpelWindowRight { id: Some(id) } => {
|
||||||
|
Self::ConsumeOrExpelWindowRightById(id)
|
||||||
|
}
|
||||||
niri_ipc::Action::ConsumeWindowIntoColumn {} => Self::ConsumeWindowIntoColumn,
|
niri_ipc::Action::ConsumeWindowIntoColumn {} => Self::ConsumeWindowIntoColumn,
|
||||||
niri_ipc::Action::ExpelWindowFromColumn {} => Self::ExpelWindowFromColumn,
|
niri_ipc::Action::ExpelWindowFromColumn {} => Self::ExpelWindowFromColumn,
|
||||||
niri_ipc::Action::CenterColumn {} => Self::CenterColumn,
|
niri_ipc::Action::CenterColumn {} => Self::CenterColumn,
|
||||||
@@ -1436,6 +1533,10 @@ pub struct DebugConfig {
|
|||||||
pub disable_resize_throttling: bool,
|
pub disable_resize_throttling: bool,
|
||||||
#[knuffel(child)]
|
#[knuffel(child)]
|
||||||
pub disable_transactions: bool,
|
pub disable_transactions: bool,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub keep_laptop_panel_on_when_lid_is_closed: bool,
|
||||||
|
#[knuffel(child)]
|
||||||
|
pub disable_monitor_names: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -1817,13 +1918,17 @@ impl OutputName {
|
|||||||
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
|
if self.make.is_none() && self.model.is_none() && self.serial.is_none() {
|
||||||
self.connector.to_string()
|
self.connector.to_string()
|
||||||
} else {
|
} else {
|
||||||
let make = self.make.as_deref().unwrap_or("Unknown");
|
self.format_make_model_serial()
|
||||||
let model = self.model.as_deref().unwrap_or("Unknown");
|
|
||||||
let serial = self.serial.as_deref().unwrap_or("Unknown");
|
|
||||||
format!("{make} {model} {serial}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn format_make_model_serial(&self) -> String {
|
||||||
|
let make = self.make.as_deref().unwrap_or("Unknown");
|
||||||
|
let model = self.model.as_deref().unwrap_or("Unknown");
|
||||||
|
let serial = self.serial.as_deref().unwrap_or("Unknown");
|
||||||
|
format!("{make} {model} {serial}")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn matches(&self, target: &str) -> bool {
|
pub fn matches(&self, target: &str) -> bool {
|
||||||
// Match by connector.
|
// Match by connector.
|
||||||
if target.eq_ignore_ascii_case(&self.connector) {
|
if target.eq_ignore_ascii_case(&self.connector) {
|
||||||
@@ -2862,8 +2967,10 @@ mod tests {
|
|||||||
accel-speed 0.2
|
accel-speed 0.2
|
||||||
accel-profile "flat"
|
accel-profile "flat"
|
||||||
scroll-method "two-finger"
|
scroll-method "two-finger"
|
||||||
|
scroll-button 272
|
||||||
tap-button-map "left-middle-right"
|
tap-button-map "left-middle-right"
|
||||||
disabled-on-external-mouse
|
disabled-on-external-mouse
|
||||||
|
scroll-factor 0.9
|
||||||
}
|
}
|
||||||
|
|
||||||
mouse {
|
mouse {
|
||||||
@@ -2871,7 +2978,9 @@ mod tests {
|
|||||||
accel-speed 0.4
|
accel-speed 0.4
|
||||||
accel-profile "flat"
|
accel-profile "flat"
|
||||||
scroll-method "no-scroll"
|
scroll-method "no-scroll"
|
||||||
|
scroll-button 273
|
||||||
middle-emulation
|
middle-emulation
|
||||||
|
scroll-factor 0.2
|
||||||
}
|
}
|
||||||
|
|
||||||
trackpoint {
|
trackpoint {
|
||||||
@@ -2880,6 +2989,18 @@ mod tests {
|
|||||||
accel-speed 0.0
|
accel-speed 0.0
|
||||||
accel-profile "flat"
|
accel-profile "flat"
|
||||||
scroll-method "on-button-down"
|
scroll-method "on-button-down"
|
||||||
|
scroll-button 274
|
||||||
|
}
|
||||||
|
|
||||||
|
trackball {
|
||||||
|
off
|
||||||
|
natural-scroll
|
||||||
|
accel-speed 0.0
|
||||||
|
accel-profile "flat"
|
||||||
|
scroll-method "edge"
|
||||||
|
scroll-button 275
|
||||||
|
left-handed
|
||||||
|
middle-emulation
|
||||||
}
|
}
|
||||||
|
|
||||||
tablet {
|
tablet {
|
||||||
@@ -2944,6 +3065,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
center-focused-column "on-overflow"
|
center-focused-column "on-overflow"
|
||||||
|
|
||||||
|
insert-hint {
|
||||||
|
color "rgb(255, 200, 127)"
|
||||||
|
gradient from="rgba(10, 20, 30, 1.0)" to="#0080ffff" relative-to="workspace-view"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn-at-startup "alacritty" "-e" "fish"
|
spawn-at-startup "alacritty" "-e" "fish"
|
||||||
@@ -2953,6 +3079,8 @@ mod tests {
|
|||||||
cursor {
|
cursor {
|
||||||
xcursor-theme "breeze_cursors"
|
xcursor-theme "breeze_cursors"
|
||||||
xcursor-size 16
|
xcursor-size 16
|
||||||
|
hide-when-typing
|
||||||
|
hide-after-inactive-ms 3000
|
||||||
}
|
}
|
||||||
|
|
||||||
screenshot-path "~/Screenshots/screenshot.png"
|
screenshot-path "~/Screenshots/screenshot.png"
|
||||||
@@ -3013,6 +3141,11 @@ mod tests {
|
|||||||
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
|
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch-events {
|
||||||
|
tablet-mode-on { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true"; }
|
||||||
|
tablet-mode-off { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled false"; }
|
||||||
|
}
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
render-drm-device "/dev/dri/renderD129"
|
render-drm-device "/dev/dri/renderD129"
|
||||||
}
|
}
|
||||||
@@ -3045,10 +3178,12 @@ mod tests {
|
|||||||
accel_speed: 0.2,
|
accel_speed: 0.2,
|
||||||
accel_profile: Some(AccelProfile::Flat),
|
accel_profile: Some(AccelProfile::Flat),
|
||||||
scroll_method: Some(ScrollMethod::TwoFinger),
|
scroll_method: Some(ScrollMethod::TwoFinger),
|
||||||
|
scroll_button: Some(272),
|
||||||
tap_button_map: Some(TapButtonMap::LeftMiddleRight),
|
tap_button_map: Some(TapButtonMap::LeftMiddleRight),
|
||||||
left_handed: false,
|
left_handed: false,
|
||||||
disabled_on_external_mouse: true,
|
disabled_on_external_mouse: true,
|
||||||
middle_emulation: false,
|
middle_emulation: false,
|
||||||
|
scroll_factor: Some(FloatOrInt(0.9)),
|
||||||
},
|
},
|
||||||
mouse: Mouse {
|
mouse: Mouse {
|
||||||
off: false,
|
off: false,
|
||||||
@@ -3056,8 +3191,10 @@ mod tests {
|
|||||||
accel_speed: 0.4,
|
accel_speed: 0.4,
|
||||||
accel_profile: Some(AccelProfile::Flat),
|
accel_profile: Some(AccelProfile::Flat),
|
||||||
scroll_method: Some(ScrollMethod::NoScroll),
|
scroll_method: Some(ScrollMethod::NoScroll),
|
||||||
|
scroll_button: Some(273),
|
||||||
left_handed: false,
|
left_handed: false,
|
||||||
middle_emulation: true,
|
middle_emulation: true,
|
||||||
|
scroll_factor: Some(FloatOrInt(0.2)),
|
||||||
},
|
},
|
||||||
trackpoint: Trackpoint {
|
trackpoint: Trackpoint {
|
||||||
off: true,
|
off: true,
|
||||||
@@ -3065,8 +3202,19 @@ mod tests {
|
|||||||
accel_speed: 0.0,
|
accel_speed: 0.0,
|
||||||
accel_profile: Some(AccelProfile::Flat),
|
accel_profile: Some(AccelProfile::Flat),
|
||||||
scroll_method: Some(ScrollMethod::OnButtonDown),
|
scroll_method: Some(ScrollMethod::OnButtonDown),
|
||||||
|
scroll_button: Some(274),
|
||||||
middle_emulation: false,
|
middle_emulation: false,
|
||||||
},
|
},
|
||||||
|
trackball: Trackball {
|
||||||
|
off: true,
|
||||||
|
natural_scroll: true,
|
||||||
|
accel_speed: 0.0,
|
||||||
|
accel_profile: Some(AccelProfile::Flat),
|
||||||
|
scroll_method: Some(ScrollMethod::Edge),
|
||||||
|
scroll_button: Some(275),
|
||||||
|
left_handed: true,
|
||||||
|
middle_emulation: true,
|
||||||
|
},
|
||||||
tablet: Tablet {
|
tablet: Tablet {
|
||||||
off: false,
|
off: false,
|
||||||
map_to_output: Some("eDP-1".to_owned()),
|
map_to_output: Some("eDP-1".to_owned()),
|
||||||
@@ -3122,6 +3270,20 @@ mod tests {
|
|||||||
active_gradient: None,
|
active_gradient: None,
|
||||||
inactive_gradient: None,
|
inactive_gradient: None,
|
||||||
},
|
},
|
||||||
|
insert_hint: InsertHint {
|
||||||
|
off: false,
|
||||||
|
color: Color::from_rgba8_unpremul(255, 200, 127, 255),
|
||||||
|
gradient: Some(Gradient {
|
||||||
|
from: Color::from_rgba8_unpremul(10, 20, 30, 255),
|
||||||
|
to: Color::from_rgba8_unpremul(0, 128, 255, 255),
|
||||||
|
angle: 180,
|
||||||
|
relative_to: GradientRelativeTo::WorkspaceView,
|
||||||
|
in_: GradientInterpolation {
|
||||||
|
color_space: GradientColorSpace::Srgb,
|
||||||
|
hue_interpolation: HueInterpolation::Shorter,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
preset_column_widths: vec![
|
preset_column_widths: vec![
|
||||||
PresetSize::Proportion(0.25),
|
PresetSize::Proportion(0.25),
|
||||||
PresetSize::Proportion(0.5),
|
PresetSize::Proportion(0.5),
|
||||||
@@ -3154,6 +3316,8 @@ mod tests {
|
|||||||
cursor: Cursor {
|
cursor: Cursor {
|
||||||
xcursor_theme: String::from("breeze_cursors"),
|
xcursor_theme: String::from("breeze_cursors"),
|
||||||
xcursor_size: 16,
|
xcursor_size: 16,
|
||||||
|
hide_when_typing: true,
|
||||||
|
hide_after_inactive_ms: Some(3000),
|
||||||
},
|
},
|
||||||
screenshot_path: Some(String::from("~/Screenshots/screenshot.png")),
|
screenshot_path: Some(String::from("~/Screenshots/screenshot.png")),
|
||||||
hotkey_overlay: HotkeyOverlay {
|
hotkey_overlay: HotkeyOverlay {
|
||||||
@@ -3345,6 +3509,24 @@ mod tests {
|
|||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
switch_events: SwitchBinds {
|
||||||
|
lid_open: None,
|
||||||
|
lid_close: None,
|
||||||
|
tablet_mode_on: Some(SwitchAction {
|
||||||
|
spawn: vec![
|
||||||
|
"bash".to_owned(),
|
||||||
|
"-c".to_owned(),
|
||||||
|
"gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true".to_owned(),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
tablet_mode_off: Some(SwitchAction {
|
||||||
|
spawn: vec![
|
||||||
|
"bash".to_owned(),
|
||||||
|
"-c".to_owned(),
|
||||||
|
"gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled false".to_owned(),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
debug: DebugConfig {
|
debug: DebugConfig {
|
||||||
render_drm_device: Some(PathBuf::from("/dev/dri/renderD129")),
|
render_drm_device: Some(PathBuf::from("/dev/dri/renderD129")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
+5
-1
@@ -1,12 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "niri-ipc"
|
name = "niri-ipc"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
description.workspace = true
|
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|
||||||
|
description = "Types and helpers for interfacing with the niri Wayland compositor."
|
||||||
|
keywords = ["wayland"]
|
||||||
|
categories = ["api-bindings", "os"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true, optional = true }
|
clap = { workspace = true, optional = true }
|
||||||
schemars = { version = "0.8.21", optional = true }
|
schemars = { version = "0.8.21", optional = true }
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# niri-ipc
|
||||||
|
|
||||||
|
Types and helpers for interfacing with the [niri](https://github.com/YaLTeR/niri) Wayland compositor.
|
||||||
|
|
||||||
|
## Backwards compatibility
|
||||||
|
|
||||||
|
This crate follows the niri version.
|
||||||
|
It is **not** API-stable in terms of the Rust semver.
|
||||||
|
In particular, expect new struct fields and enum variants to be added in patch version bumps.
|
||||||
|
|
||||||
|
Use an exact version requirement to avoid breaking changes:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
niri-ipc = "=0.1.10"
|
||||||
|
```
|
||||||
+40
-4
@@ -19,6 +19,20 @@
|
|||||||
//!
|
//!
|
||||||
//! This crate follows the niri version. It is **not** API-stable in terms of the Rust semver. In
|
//! This crate follows the niri version. It is **not** API-stable in terms of the Rust semver. In
|
||||||
//! particular, expect new struct fields and enum variants to be added in patch version bumps.
|
//! particular, expect new struct fields and enum variants to be added in patch version bumps.
|
||||||
|
//!
|
||||||
|
//! Use an exact version requirement to avoid breaking changes:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [dependencies]
|
||||||
|
//! niri-ipc = "=0.1.10"
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//!
|
||||||
|
//! This crate defines the following features:
|
||||||
|
//! - `json-schema`: derives the [schemars](https://lib.rs/crates/schemars) `JsonSchema` trait for
|
||||||
|
//! the types.
|
||||||
|
//! - `clap`: derives the clap CLI parsing traits for some types. Used internally by niri itself.
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -132,6 +146,8 @@ pub enum Action {
|
|||||||
},
|
},
|
||||||
/// Power off all monitors via DPMS.
|
/// Power off all monitors via DPMS.
|
||||||
PowerOffMonitors {},
|
PowerOffMonitors {},
|
||||||
|
/// Power on all monitors via DPMS.
|
||||||
|
PowerOnMonitors {},
|
||||||
/// Spawn a command.
|
/// Spawn a command.
|
||||||
Spawn {
|
Spawn {
|
||||||
/// Command to spawn.
|
/// Command to spawn.
|
||||||
@@ -240,10 +256,30 @@ pub enum Action {
|
|||||||
MoveWindowDownOrToWorkspaceDown {},
|
MoveWindowDownOrToWorkspaceDown {},
|
||||||
/// Move the focused window up in a column or to the workspace above.
|
/// Move the focused window up in a column or to the workspace above.
|
||||||
MoveWindowUpOrToWorkspaceUp {},
|
MoveWindowUpOrToWorkspaceUp {},
|
||||||
/// Consume or expel the focused window left.
|
/// Consume or expel a window left.
|
||||||
ConsumeOrExpelWindowLeft {},
|
#[cfg_attr(
|
||||||
/// Consume or expel the focused window right.
|
feature = "clap",
|
||||||
ConsumeOrExpelWindowRight {},
|
clap(about = "Consume or expel the focused window left")
|
||||||
|
)]
|
||||||
|
ConsumeOrExpelWindowLeft {
|
||||||
|
/// Id of the window to consume or expel.
|
||||||
|
///
|
||||||
|
/// If `None`, uses the focused window.
|
||||||
|
#[cfg_attr(feature = "clap", arg(long))]
|
||||||
|
id: Option<u64>,
|
||||||
|
},
|
||||||
|
/// Consume or expel a window right.
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "clap",
|
||||||
|
clap(about = "Consume or expel the focused window right")
|
||||||
|
)]
|
||||||
|
ConsumeOrExpelWindowRight {
|
||||||
|
/// Id of the window to consume or expel.
|
||||||
|
///
|
||||||
|
/// If `None`, uses the focused window.
|
||||||
|
#[cfg_attr(feature = "clap", arg(long))]
|
||||||
|
id: Option<u64>,
|
||||||
|
},
|
||||||
/// Consume the window to the right into the focused column.
|
/// Consume the window to the right into the focused column.
|
||||||
ConsumeWindowIntoColumn {},
|
ConsumeWindowIntoColumn {},
|
||||||
/// Expel the focused window from the column.
|
/// Expel the focused window from the column.
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ edition.workspace = true
|
|||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
adw = { version = "0.7.0", package = "libadwaita", features = ["v1_4"] }
|
adw = { version = "0.7.1", package = "libadwaita", features = ["v1_4"] }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
gtk = { version = "0.9.1", package = "gtk4", features = ["v4_12"] }
|
gtk = { version = "0.9.3", package = "gtk4", features = ["v4_12"] }
|
||||||
niri = { version = "0.1.9", path = ".." }
|
niri = { version = "0.1.10-1", path = ".." }
|
||||||
niri-config = { version = "0.1.9", path = "../niri-config" }
|
niri-config = { version = "0.1.10-1", path = "../niri-config" }
|
||||||
smithay.workspace = true
|
smithay.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
|
|||||||
@@ -198,11 +198,7 @@ impl TestCase for Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn are_animations_ongoing(&self) -> bool {
|
fn are_animations_ongoing(&self) -> bool {
|
||||||
self.layout
|
self.layout.are_animations_ongoing(Some(&self.output)) || !self.steps.is_empty()
|
||||||
.monitor_for_output(&self.output)
|
|
||||||
.unwrap()
|
|
||||||
.are_animations_ongoing()
|
|
||||||
|| !self.steps.is_empty()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance_animations(&mut self, mut current_time: Duration) {
|
fn advance_animations(&mut self, mut current_time: Duration) {
|
||||||
@@ -233,7 +229,6 @@ impl TestCase for Layout {
|
|||||||
.monitor_for_output(&self.output)
|
.monitor_for_output(&self.output)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.render_elements(renderer, RenderTarget::Output)
|
.render_elements(renderer, RenderTarget::Output)
|
||||||
.into_iter()
|
|
||||||
.map(|elem| Box::new(elem) as _)
|
.map(|elem| Box::new(elem) as _)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,16 @@ input {
|
|||||||
// scroll-method "no-scroll"
|
// scroll-method "no-scroll"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackpoint {
|
||||||
|
// off
|
||||||
|
// natural-scroll
|
||||||
|
// accel-speed 0.2
|
||||||
|
// accel-profile "flat"
|
||||||
|
// scroll-method "on-button-down"
|
||||||
|
// scroll-button 273
|
||||||
|
// middle-emulation
|
||||||
|
}
|
||||||
|
|
||||||
// Uncomment this to make the mouse warp to the center of newly focused windows.
|
// Uncomment this to make the mouse warp to the center of newly focused windows.
|
||||||
// warp-mouse-to-focus
|
// warp-mouse-to-focus
|
||||||
|
|
||||||
@@ -202,7 +212,9 @@ layout {
|
|||||||
|
|
||||||
// Uncomment this line to ask the clients to omit their client-side decorations if possible.
|
// Uncomment this line to ask the clients to omit their client-side decorations if possible.
|
||||||
// If the client will specifically ask for CSD, the request will be honored.
|
// If the client will specifically ask for CSD, the request will be honored.
|
||||||
// Additionally, clients will be informed that they are tiled, removing some rounded corners.
|
// Additionally, clients will be informed that they are tiled, removing some client-side rounded corners.
|
||||||
|
// This option will also fix border/focus ring drawing behind some semitransparent windows.
|
||||||
|
// After enabling or disabling this, you need to restart the apps for this to take effect.
|
||||||
// prefer-no-csd
|
// prefer-no-csd
|
||||||
|
|
||||||
// You can change the path where screenshots are saved.
|
// You can change the path where screenshots are saved.
|
||||||
@@ -250,6 +262,13 @@ window-rule {
|
|||||||
// block-out-from "screencast"
|
// block-out-from "screencast"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Example: enable rounded corners for all windows.
|
||||||
|
// (This example rule is commented out with a "/-" in front.)
|
||||||
|
/-window-rule {
|
||||||
|
geometry-corner-radius 12
|
||||||
|
clip-to-geometry true
|
||||||
|
}
|
||||||
|
|
||||||
binds {
|
binds {
|
||||||
// Keys consist of modifiers separated by + signs, followed by an XKB key name
|
// Keys consist of modifiers separated by + signs, followed by an XKB key name
|
||||||
// in the end. To find an XKB name for a particular key, you may use a program
|
// in the end. To find an XKB name for a particular key, you may use a program
|
||||||
@@ -422,7 +441,9 @@ binds {
|
|||||||
// Switches focus between the current and the previous workspace.
|
// Switches focus between the current and the previous workspace.
|
||||||
// Mod+Tab { focus-workspace-previous; }
|
// Mod+Tab { focus-workspace-previous; }
|
||||||
|
|
||||||
|
// Consume one window from the right into the focused column.
|
||||||
Mod+Comma { consume-window-into-column; }
|
Mod+Comma { consume-window-into-column; }
|
||||||
|
// Expel one window from the focused column to the right.
|
||||||
Mod+Period { expel-window-from-column; }
|
Mod+Period { expel-window-from-column; }
|
||||||
|
|
||||||
// There are also commands that consume or expel a single window to the side.
|
// There are also commands that consume or expel a single window to the side.
|
||||||
@@ -465,6 +486,7 @@ binds {
|
|||||||
|
|
||||||
// The quit action will show a confirmation dialog to avoid accidental exits.
|
// The quit action will show a confirmation dialog to avoid accidental exits.
|
||||||
Mod+Shift+E { quit; }
|
Mod+Shift+E { quit; }
|
||||||
|
Ctrl+Alt+Delete { quit; }
|
||||||
|
|
||||||
// Powers off the monitors. To turn them back on, do any input like
|
// Powers off the monitors. To turn them back on, do any input like
|
||||||
// moving the mouse or pressing any other key.
|
// moving the mouse or pressing any other key.
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
type = process
|
||||||
|
command = niri --session
|
||||||
|
restart = false
|
||||||
|
working-dir = $HOME
|
||||||
|
depends-on = dbus
|
||||||
|
after = niri-shutdown
|
||||||
|
chain-to = niri-shutdown
|
||||||
|
options: always-chain
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
type = scripted
|
||||||
|
command = dinitctl -u setenv WAYLAND_DISPLAY= XDG_SESSION_TYPE= XDG_CURRENT_DESKTOP= NIRI_SOCKET=
|
||||||
|
restart = false
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
[preferred]
|
[preferred]
|
||||||
default=gnome;gtk;
|
default=gnome;gtk;
|
||||||
|
org.freedesktop.impl.portal.Access=gtk;
|
||||||
org.freedesktop.impl.portal.Secret=gnome-keyring;
|
org.freedesktop.impl.portal.Secret=gnome-keyring;
|
||||||
|
|||||||
+47
-27
@@ -11,31 +11,51 @@ if [ -n "$SHELL" ] &&
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Make sure there's no already running session.
|
# Try to detect the service manager that is being used
|
||||||
if systemctl --user -q is-active niri.service; then
|
if hash systemctl &> /dev/null; then
|
||||||
echo 'A niri session is already running.'
|
# Make sure there's no already running session.
|
||||||
exit 1
|
if systemctl --user -q is-active niri.service; then
|
||||||
|
echo 'A niri session is already running.'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reset failed state of all user units.
|
||||||
|
systemctl --user reset-failed
|
||||||
|
|
||||||
|
# Import the login manager environment.
|
||||||
|
systemctl --user import-environment
|
||||||
|
|
||||||
|
# DBus activation environment is independent from systemd. While most of
|
||||||
|
# dbus-activated services are already using `SystemdService` directive, some
|
||||||
|
# still don't and thus we should set the dbus environment with a separate
|
||||||
|
# command.
|
||||||
|
if hash dbus-update-activation-environment 2>/dev/null; then
|
||||||
|
dbus-update-activation-environment --all
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start niri and wait for it to terminate.
|
||||||
|
systemctl --user --wait start niri.service
|
||||||
|
|
||||||
|
# Force stop of graphical-session.target.
|
||||||
|
systemctl --user start --job-mode=replace-irreversibly niri-shutdown.target
|
||||||
|
|
||||||
|
# Unset environment that we've set.
|
||||||
|
systemctl --user unset-environment WAYLAND_DISPLAY XDG_SESSION_TYPE XDG_CURRENT_DESKTOP NIRI_SOCKET
|
||||||
|
elif hash dinitctl &> /dev/null; then
|
||||||
|
# Check that the user dinit daemon is running
|
||||||
|
if ! pgrep -u $(id -u) dinit &> /dev/null; then
|
||||||
|
echo "dinit user daemon is not running."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure there's no already running session.
|
||||||
|
if dinitctl --user is-started niri &> /dev/null; then
|
||||||
|
echo 'A niri session is already running.'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start niri
|
||||||
|
dinitctl --user start niri
|
||||||
|
else
|
||||||
|
echo "No systemd or dinit detected, please use niri --session instead."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Reset failed state of all user units.
|
|
||||||
systemctl --user reset-failed
|
|
||||||
|
|
||||||
# Import the login manager environment.
|
|
||||||
systemctl --user import-environment
|
|
||||||
|
|
||||||
# DBus activation environment is independent from systemd. While most of
|
|
||||||
# dbus-activated services are already using `SystemdService` directive, some
|
|
||||||
# still don't and thus we should set the dbus environment with a separate
|
|
||||||
# command.
|
|
||||||
if hash dbus-update-activation-environment 2>/dev/null; then
|
|
||||||
dbus-update-activation-environment --all
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start niri and wait for it to terminate.
|
|
||||||
systemctl --user --wait start niri.service
|
|
||||||
|
|
||||||
# Force stop of graphical-session.target.
|
|
||||||
systemctl --user start --job-mode=replace-irreversibly niri-shutdown.target
|
|
||||||
|
|
||||||
# Unset environment that we've set.
|
|
||||||
systemctl --user unset-environment WAYLAND_DISPLAY XDG_SESSION_TYPE XDG_CURRENT_DESKTOP NIRI_SOCKET
|
|
||||||
|
|||||||
+107
-48
@@ -61,7 +61,7 @@ use crate::niri::{Niri, RedrawState, State};
|
|||||||
use crate::render_helpers::debug::draw_damage;
|
use crate::render_helpers::debug::draw_damage;
|
||||||
use crate::render_helpers::renderer::AsGlesRenderer;
|
use crate::render_helpers::renderer::AsGlesRenderer;
|
||||||
use crate::render_helpers::{resources, shaders, RenderTarget};
|
use crate::render_helpers::{resources, shaders, RenderTarget};
|
||||||
use crate::utils::{get_monotonic_time, logical_output};
|
use crate::utils::{get_monotonic_time, is_laptop_panel, logical_output};
|
||||||
|
|
||||||
const SUPPORTED_COLOR_FORMATS: &[Fourcc] = &[Fourcc::Argb8888, Fourcc::Abgr8888];
|
const SUPPORTED_COLOR_FORMATS: &[Fourcc] = &[Fourcc::Argb8888, Fourcc::Abgr8888];
|
||||||
|
|
||||||
@@ -636,22 +636,31 @@ impl Tty {
|
|||||||
connector,
|
connector,
|
||||||
crtc: Some(crtc),
|
crtc: Some(crtc),
|
||||||
} => {
|
} => {
|
||||||
if let Err(err) = self.connector_connected(niri, node, connector, crtc) {
|
let connector_name = format_connector_name(&connector);
|
||||||
warn!("error connecting connector: {err:?}");
|
let output_name =
|
||||||
}
|
make_output_name(&device.drm, connector.handle(), connector_name, false);
|
||||||
|
debug!(
|
||||||
|
"new connector: {} \"{}\"",
|
||||||
|
&output_name.connector,
|
||||||
|
output_name.format_make_model_serial(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assign an id to this crtc.
|
||||||
|
device.output_ids.insert(crtc, OutputId::next());
|
||||||
}
|
}
|
||||||
DrmScanEvent::Disconnected {
|
DrmScanEvent::Disconnected {
|
||||||
crtc: Some(crtc), ..
|
crtc: Some(crtc), ..
|
||||||
} => {
|
} => {
|
||||||
self.connector_disconnected(niri, node, crtc);
|
|
||||||
removed.push(crtc);
|
removed.push(crtc);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this is better done in connector_disconnected(), but currently we call that to
|
for crtc in &removed {
|
||||||
// turn off outputs temporarily, too. So we can't do this there.
|
self.connector_disconnected(niri, node, *crtc);
|
||||||
|
}
|
||||||
|
|
||||||
let Some(device) = self.devices.get_mut(&node) else {
|
let Some(device) = self.devices.get_mut(&node) else {
|
||||||
error!("device disappeared");
|
error!("device disappeared");
|
||||||
return;
|
return;
|
||||||
@@ -663,7 +672,12 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.refresh_ipc_outputs(niri);
|
// This will connect any new connectors if needed, and apply other changes, such as
|
||||||
|
// connecting back the internal laptop monitor once it becomes the only monitor left.
|
||||||
|
//
|
||||||
|
// It will also call refresh_ipc_outputs(), which will catch the disconnected connectors
|
||||||
|
// above.
|
||||||
|
self.on_output_config_changed(niri);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_removed(&mut self, device_id: dev_t, niri: &mut Niri) {
|
fn device_removed(&mut self, device_id: dev_t, niri: &mut Niri) {
|
||||||
@@ -749,7 +763,12 @@ impl Tty {
|
|||||||
|
|
||||||
let device = self.devices.get_mut(&node).context("missing device")?;
|
let device = self.devices.get_mut(&node).context("missing device")?;
|
||||||
|
|
||||||
let output_name = make_output_name(&device.drm, connector.handle(), connector_name.clone());
|
let output_name = make_output_name(
|
||||||
|
&device.drm,
|
||||||
|
connector.handle(),
|
||||||
|
connector_name.clone(),
|
||||||
|
self.config.borrow().debug.disable_monitor_names,
|
||||||
|
);
|
||||||
|
|
||||||
let non_desktop = find_drm_property(&device.drm, connector.handle(), "non-desktop")
|
let non_desktop = find_drm_property(&device.drm, connector.handle(), "non-desktop")
|
||||||
.and_then(|(_, info, value)| info.value_type().convert_value(value).as_boolean())
|
.and_then(|(_, info, value)| info.value_type().convert_value(value).as_boolean())
|
||||||
@@ -767,10 +786,6 @@ impl Tty {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should be unique per CRTC connection, however currently we can call
|
|
||||||
// connector_connected() multiple times for turning the output off and on.
|
|
||||||
device.output_ids.entry(crtc).or_insert_with(OutputId::next);
|
|
||||||
|
|
||||||
let config = self
|
let config = self
|
||||||
.config
|
.config
|
||||||
.borrow()
|
.borrow()
|
||||||
@@ -779,11 +794,6 @@ impl Tty {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if config.off {
|
|
||||||
debug!("output is disabled in the config");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
for m in connector.modes() {
|
for m in connector.modes() {
|
||||||
trace!("{m:?}");
|
trace!("{m:?}");
|
||||||
}
|
}
|
||||||
@@ -1019,6 +1029,14 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some buggy monitors replug upon powering off, so powering on here would prevent such
|
||||||
|
// monitors from powering off. Therefore, we avoid unconditionally powering on.
|
||||||
|
if !niri.monitors_active {
|
||||||
|
if let Err(err) = compositor.clear() {
|
||||||
|
warn!("error clearing drm surface: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let vblank_frame_name =
|
let vblank_frame_name =
|
||||||
tracy_client::FrameName::new_leak(format!("vblank on {connector_name}"));
|
tracy_client::FrameName::new_leak(format!("vblank on {connector_name}"));
|
||||||
let time_since_presentation_plot_name = tracy_client::PlotName::new_leak(format!(
|
let time_since_presentation_plot_name = tracy_client::PlotName::new_leak(format!(
|
||||||
@@ -1050,15 +1068,14 @@ impl Tty {
|
|||||||
|
|
||||||
niri.add_output(output.clone(), Some(refresh_interval(mode)), vrr_enabled);
|
niri.add_output(output.clone(), Some(refresh_interval(mode)), vrr_enabled);
|
||||||
|
|
||||||
// Some buggy monitors replug upon powering off, so powering on here would prevent such
|
|
||||||
// monitors from powering off. Therefore, we avoid unconditionally powering on.
|
|
||||||
if niri.monitors_active {
|
if niri.monitors_active {
|
||||||
// Redraw the new monitor.
|
// Redraw the new monitor.
|
||||||
niri.event_loop.insert_idle(move |state| {
|
niri.event_loop.insert_idle(move |state| {
|
||||||
state.niri.queue_redraw(&output);
|
// Guard against output disconnecting before the idle has a chance to run.
|
||||||
|
if state.niri.output_state.contains_key(&output) {
|
||||||
|
state.niri.queue_redraw(&output);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
set_crtc_active(&device.drm, crtc, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1557,8 +1574,12 @@ impl Tty {
|
|||||||
for (connector, crtc) in device.drm_scanner.crtcs() {
|
for (connector, crtc) in device.drm_scanner.crtcs() {
|
||||||
let connector_name = format_connector_name(connector);
|
let connector_name = format_connector_name(connector);
|
||||||
let physical_size = connector.size();
|
let physical_size = connector.size();
|
||||||
let output_name =
|
let output_name = make_output_name(
|
||||||
make_output_name(&device.drm, connector.handle(), connector_name.clone());
|
&device.drm,
|
||||||
|
connector.handle(),
|
||||||
|
connector_name.clone(),
|
||||||
|
self.config.borrow().debug.disable_monitor_names,
|
||||||
|
);
|
||||||
|
|
||||||
let surface = device.surfaces.get(&crtc);
|
let surface = device.surfaces.get(&crtc);
|
||||||
let current_crtc_mode = surface.map(|surface| surface.compositor.pending_mode());
|
let current_crtc_mode = surface.map(|surface| surface.compositor.pending_mode());
|
||||||
@@ -1659,10 +1680,9 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for device in self.devices.values_mut() {
|
for device in self.devices.values_mut() {
|
||||||
for (crtc, surface) in device.surfaces.iter_mut() {
|
for surface in device.surfaces.values_mut() {
|
||||||
set_crtc_active(&device.drm, *crtc, false);
|
if let Err(err) = surface.compositor.clear() {
|
||||||
if let Err(err) = surface.compositor.reset_state() {
|
warn!("error clearing drm surface: {err:?}");
|
||||||
warn!("error resetting surface state: {err:?}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1705,6 +1725,24 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
self.update_output_config_on_resume = false;
|
self.update_output_config_on_resume = false;
|
||||||
|
|
||||||
|
// Figure out if we should disable laptop panels.
|
||||||
|
let mut disable_laptop_panels = false;
|
||||||
|
if niri.is_lid_closed {
|
||||||
|
let config = self.config.borrow();
|
||||||
|
if !config.debug.keep_laptop_panel_on_when_lid_is_closed {
|
||||||
|
// Check if any external monitor is connected.
|
||||||
|
'outer: for device in self.devices.values() {
|
||||||
|
for (connector, _crtc) in device.drm_scanner.crtcs() {
|
||||||
|
if !is_laptop_panel(&format_connector_name(connector)) {
|
||||||
|
disable_laptop_panels = true;
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let should_disable = |connector: &str| disable_laptop_panels && is_laptop_panel(connector);
|
||||||
|
|
||||||
let mut to_disconnect = vec![];
|
let mut to_disconnect = vec![];
|
||||||
let mut to_connect = vec![];
|
let mut to_connect = vec![];
|
||||||
|
|
||||||
@@ -1717,7 +1755,7 @@ impl Tty {
|
|||||||
.find(&surface.name)
|
.find(&surface.name)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if config.off {
|
if config.off || should_disable(&surface.name.connector) {
|
||||||
to_disconnect.push((node, crtc));
|
to_disconnect.push((node, crtc));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1813,12 +1851,21 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if already enabled.
|
// Check if already enabled.
|
||||||
if device.surfaces.contains_key(&crtc) {
|
if device.surfaces.contains_key(&crtc)
|
||||||
|
|| device
|
||||||
|
.non_desktop_connectors
|
||||||
|
.contains(&(connector.handle(), crtc))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let connector_name = format_connector_name(connector);
|
let connector_name = format_connector_name(connector);
|
||||||
let output_name = make_output_name(&device.drm, connector.handle(), connector_name);
|
let output_name = make_output_name(
|
||||||
|
&device.drm,
|
||||||
|
connector.handle(),
|
||||||
|
connector_name,
|
||||||
|
self.config.borrow().debug.disable_monitor_names,
|
||||||
|
);
|
||||||
let config = self
|
let config = self
|
||||||
.config
|
.config
|
||||||
.borrow()
|
.borrow()
|
||||||
@@ -1827,8 +1874,8 @@ impl Tty {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if !config.off {
|
if !(config.off || should_disable(&output_name.connector)) {
|
||||||
to_connect.push((node, connector.clone(), crtc));
|
to_connect.push((node, connector.clone(), crtc, output_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1837,7 +1884,11 @@ impl Tty {
|
|||||||
self.connector_disconnected(niri, node, crtc);
|
self.connector_disconnected(niri, node, crtc);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (node, connector, crtc) in to_connect {
|
// Sort by output name to get more predictable first focused output at initial compositor
|
||||||
|
// startup, when multiple connectors appear at once.
|
||||||
|
to_connect.sort_unstable_by(|a, b| a.3.compare(&b.3));
|
||||||
|
|
||||||
|
for (node, connector, crtc, _name) in to_connect {
|
||||||
if let Err(err) = self.connector_connected(niri, node, connector, crtc) {
|
if let Err(err) = self.connector_connected(niri, node, connector, crtc) {
|
||||||
warn!("error connecting connector: {err:?}");
|
warn!("error connecting connector: {err:?}");
|
||||||
}
|
}
|
||||||
@@ -1872,12 +1923,21 @@ impl Tty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if already enabled.
|
// Check if already enabled.
|
||||||
if device.surfaces.contains_key(&crtc) {
|
if device.surfaces.contains_key(&crtc)
|
||||||
|
|| device
|
||||||
|
.non_desktop_connectors
|
||||||
|
.contains(&(connector.handle(), crtc))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let connector_name = format_connector_name(connector);
|
let connector_name = format_connector_name(connector);
|
||||||
let output_name = make_output_name(&device.drm, connector.handle(), connector_name);
|
let output_name = make_output_name(
|
||||||
|
&device.drm,
|
||||||
|
connector.handle(),
|
||||||
|
connector_name,
|
||||||
|
self.config.borrow().debug.disable_monitor_names,
|
||||||
|
);
|
||||||
if output_name.matches(target) {
|
if output_name.matches(target) {
|
||||||
return Some(output_name);
|
return Some(output_name);
|
||||||
}
|
}
|
||||||
@@ -2154,17 +2214,6 @@ fn get_drm_property(
|
|||||||
.find_map(|(handle, value)| (handle == prop).then_some(value))
|
.find_map(|(handle, value)| (handle == prop).then_some(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_crtc_active(drm: &DrmDevice, crtc: crtc::Handle, active: bool) {
|
|
||||||
let Some((prop, _, _)) = find_drm_property(drm, crtc, "ACTIVE") else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = property::Value::Boolean(active);
|
|
||||||
if let Err(err) = drm.set_property(crtc, prop, value.into()) {
|
|
||||||
warn!("error setting CRTC property: {err:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refresh_interval(mode: DrmMode) -> Duration {
|
fn refresh_interval(mode: DrmMode) -> Duration {
|
||||||
let clock = mode.clock() as u64;
|
let clock = mode.clock() as u64;
|
||||||
let htotal = mode.hsync().2 as u64;
|
let htotal = mode.hsync().2 as u64;
|
||||||
@@ -2486,7 +2535,17 @@ fn make_output_name(
|
|||||||
device: &DrmDevice,
|
device: &DrmDevice,
|
||||||
connector: connector::Handle,
|
connector: connector::Handle,
|
||||||
connector_name: String,
|
connector_name: String,
|
||||||
|
disable_monitor_names: bool,
|
||||||
) -> OutputName {
|
) -> OutputName {
|
||||||
|
if disable_monitor_names {
|
||||||
|
return OutputName {
|
||||||
|
connector: connector_name,
|
||||||
|
make: None,
|
||||||
|
model: None,
|
||||||
|
serial: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let info = get_edid_info(device, connector)
|
let info = get_edid_info(device, connector)
|
||||||
.map_err(|err| warn!("error getting EDID info for {connector_name}: {err:?}"))
|
.map_err(|err| warn!("error getting EDID info for {connector_name}: {err:?}"))
|
||||||
.ok();
|
.ok();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use zbus::{dbus_interface, fdo, SignalContext};
|
|||||||
|
|
||||||
use super::Start;
|
use super::Start;
|
||||||
use crate::backend::IpcOutputMap;
|
use crate::backend::IpcOutputMap;
|
||||||
|
use crate::utils::is_laptop_panel;
|
||||||
|
|
||||||
pub struct DisplayConfig {
|
pub struct DisplayConfig {
|
||||||
ipc_outputs: Arc<Mutex<IpcOutputMap>>,
|
ipc_outputs: Arc<Mutex<IpcOutputMap>>,
|
||||||
@@ -63,7 +64,7 @@ impl DisplayConfig {
|
|||||||
.map(|output| {
|
.map(|output| {
|
||||||
// Loosely matches the check in Mutter.
|
// Loosely matches the check in Mutter.
|
||||||
let c = &output.name;
|
let c = &output.name;
|
||||||
let is_laptop_panel = matches!(c.get(..4), Some("eDP-" | "LVDS" | "DSI-"));
|
let is_laptop_panel = is_laptop_panel(c);
|
||||||
let display_name = make_display_name(output, is_laptop_panel);
|
let display_name = make_display_name(output, is_laptop_panel);
|
||||||
|
|
||||||
let mut properties = HashMap::new();
|
let mut properties = HashMap::new();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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::input::pointer::CursorImageStatus;
|
use smithay::input::pointer::{CursorImageStatus, CursorImageSurfaceData};
|
||||||
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;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
@@ -297,13 +297,52 @@ impl CompositorHandler for State {
|
|||||||
&self.niri.cursor_manager.cursor_image(),
|
&self.niri.cursor_manager.cursor_image(),
|
||||||
CursorImageStatus::Surface(s) if s == &root_surface
|
CursorImageStatus::Surface(s) if s == &root_surface
|
||||||
) {
|
) {
|
||||||
|
// In case the cursor surface has been committed handle the role specific
|
||||||
|
// buffer offset by applying the offset on the cursor image hotspot
|
||||||
|
if surface == &root_surface {
|
||||||
|
with_states(surface, |states| {
|
||||||
|
let cursor_image_attributes = states.data_map.get::<CursorImageSurfaceData>();
|
||||||
|
|
||||||
|
if let Some(mut cursor_image_attributes) =
|
||||||
|
cursor_image_attributes.map(|attrs| attrs.lock().unwrap())
|
||||||
|
{
|
||||||
|
let buffer_delta = states
|
||||||
|
.cached_state
|
||||||
|
.get::<SurfaceAttributes>()
|
||||||
|
.current()
|
||||||
|
.buffer_delta
|
||||||
|
.take();
|
||||||
|
if let Some(buffer_delta) = buffer_delta {
|
||||||
|
cursor_image_attributes.hotspot -= buffer_delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: granular redraws for cursors.
|
// FIXME: granular redraws for cursors.
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This might be a DnD icon surface.
|
// This might be a DnD icon surface.
|
||||||
if self.niri.dnd_icon.as_ref() == Some(&root_surface) {
|
if matches!(&self.niri.dnd_icon, Some(icon) if icon.surface == root_surface) {
|
||||||
|
let dnd_icon = self.niri.dnd_icon.as_mut().unwrap();
|
||||||
|
|
||||||
|
// In case the dnd surface has been committed handle the role specific
|
||||||
|
// buffer offset by applying the offset on the dnd icon offset
|
||||||
|
if surface == &dnd_icon.surface {
|
||||||
|
with_states(&dnd_icon.surface, |states| {
|
||||||
|
let buffer_delta = states
|
||||||
|
.cached_state
|
||||||
|
.get::<SurfaceAttributes>()
|
||||||
|
.current()
|
||||||
|
.buffer_delta
|
||||||
|
.take()
|
||||||
|
.unwrap_or_default();
|
||||||
|
dnd_icon.offset += buffer_delta;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: granular redraws for cursors.
|
// FIXME: granular redraws for cursors.
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
return;
|
return;
|
||||||
|
|||||||
+99
-21
@@ -7,12 +7,15 @@ use std::io::Write;
|
|||||||
use std::os::fd::OwnedFd;
|
use std::os::fd::OwnedFd;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||||
use smithay::backend::drm::DrmNode;
|
use smithay::backend::drm::DrmNode;
|
||||||
use smithay::backend::input::TabletToolDescriptor;
|
use smithay::backend::input::TabletToolDescriptor;
|
||||||
use smithay::desktop::{PopupKind, PopupManager};
|
use smithay::desktop::{PopupKind, PopupManager};
|
||||||
use smithay::input::pointer::{CursorIcon, CursorImageStatus, PointerHandle};
|
use smithay::input::pointer::{
|
||||||
|
CursorIcon, CursorImageStatus, CursorImageSurfaceData, PointerHandle,
|
||||||
|
};
|
||||||
use smithay::input::{keyboard, Seat, SeatHandler, SeatState};
|
use smithay::input::{keyboard, Seat, SeatHandler, SeatState};
|
||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
use smithay::reexports::rustix::fs::{fcntl_setfl, OFlags};
|
use smithay::reexports::rustix::fs::{fcntl_setfl, OFlags};
|
||||||
@@ -22,8 +25,8 @@ use smithay::reexports::wayland_server::protocol::wl_data_source::WlDataSource;
|
|||||||
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
|
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
use smithay::reexports::wayland_server::Resource;
|
use smithay::reexports::wayland_server::Resource;
|
||||||
use smithay::utils::{Logical, Rectangle, Size};
|
use smithay::utils::{Logical, Point, Rectangle, Size};
|
||||||
use smithay::wayland::compositor::with_states;
|
use smithay::wayland::compositor::{get_parent, with_states};
|
||||||
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
|
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
|
||||||
use smithay::wayland::drm_lease::{
|
use smithay::wayland::drm_lease::{
|
||||||
DrmLease, DrmLeaseBuilder, DrmLeaseHandler, DrmLeaseRequest, DrmLeaseState, LeaseRejected,
|
DrmLease, DrmLeaseBuilder, DrmLeaseHandler, DrmLeaseRequest, DrmLeaseState, LeaseRejected,
|
||||||
@@ -33,7 +36,7 @@ use smithay::wayland::idle_inhibit::IdleInhibitHandler;
|
|||||||
use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
|
use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
|
||||||
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
||||||
use smithay::wayland::output::OutputHandler;
|
use smithay::wayland::output::OutputHandler;
|
||||||
use smithay::wayland::pointer_constraints::PointerConstraintsHandler;
|
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler};
|
||||||
use smithay::wayland::security_context::{
|
use smithay::wayland::security_context::{
|
||||||
SecurityContext, SecurityContextHandler, SecurityContextListenerSource,
|
SecurityContext, SecurityContextHandler, SecurityContextListenerSource,
|
||||||
};
|
};
|
||||||
@@ -64,7 +67,7 @@ use smithay::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::handlers::xdg_shell::KdeDecorationsModeState;
|
pub use crate::handlers::xdg_shell::KdeDecorationsModeState;
|
||||||
use crate::niri::{ClientState, State};
|
use crate::niri::{ClientState, DndIcon, State};
|
||||||
use crate::protocols::foreign_toplevel::{
|
use crate::protocols::foreign_toplevel::{
|
||||||
self, ForeignToplevelHandler, ForeignToplevelManagerState,
|
self, ForeignToplevelHandler, ForeignToplevelManagerState,
|
||||||
};
|
};
|
||||||
@@ -72,12 +75,14 @@ use crate::protocols::gamma_control::{GammaControlHandler, GammaControlManagerSt
|
|||||||
use crate::protocols::mutter_x11_interop::MutterX11InteropHandler;
|
use crate::protocols::mutter_x11_interop::MutterX11InteropHandler;
|
||||||
use crate::protocols::output_management::{OutputManagementHandler, OutputManagementManagerState};
|
use crate::protocols::output_management::{OutputManagementHandler, OutputManagementManagerState};
|
||||||
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler, ScreencopyManagerState};
|
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler, ScreencopyManagerState};
|
||||||
use crate::utils::{output_size, send_scale_transform};
|
use crate::utils::{output_size, send_scale_transform, with_toplevel_role};
|
||||||
use crate::{
|
use crate::{
|
||||||
delegate_foreign_toplevel, delegate_gamma_control, delegate_mutter_x11_interop,
|
delegate_foreign_toplevel, delegate_gamma_control, delegate_mutter_x11_interop,
|
||||||
delegate_output_management, delegate_screencopy,
|
delegate_output_management, delegate_screencopy,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const XDG_ACTIVATION_TOKEN_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
impl SeatHandler for State {
|
impl SeatHandler for State {
|
||||||
type KeyboardFocus = WlSurface;
|
type KeyboardFocus = WlSurface;
|
||||||
type PointerFocus = WlSurface;
|
type PointerFocus = WlSurface;
|
||||||
@@ -135,11 +140,66 @@ impl TabletSeatHandler for State {
|
|||||||
delegate_tablet_manager!(State);
|
delegate_tablet_manager!(State);
|
||||||
|
|
||||||
impl PointerConstraintsHandler for State {
|
impl PointerConstraintsHandler for State {
|
||||||
fn new_constraint(&mut self, _surface: &WlSurface, pointer: &PointerHandle<Self>) {
|
fn new_constraint(&mut self, _surface: &WlSurface, _pointer: &PointerHandle<Self>) {
|
||||||
self.niri.maybe_activate_pointer_constraint(
|
// Pointer constraints track pointer focus internally, so make sure it's up to date before
|
||||||
pointer.current_location(),
|
// activating a new one.
|
||||||
&self.niri.pointer_focus,
|
self.refresh_pointer_contents();
|
||||||
);
|
|
||||||
|
self.niri.maybe_activate_pointer_constraint();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_position_hint(
|
||||||
|
&mut self,
|
||||||
|
surface: &WlSurface,
|
||||||
|
pointer: &PointerHandle<Self>,
|
||||||
|
location: Point<f64, Logical>,
|
||||||
|
) {
|
||||||
|
let is_constraint_active = with_pointer_constraint(surface, pointer, |constraint| {
|
||||||
|
constraint.map_or(false, |c| c.is_active())
|
||||||
|
});
|
||||||
|
|
||||||
|
if !is_constraint_active {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this is surface under pointer, not pointer focus. So if you start, say, a
|
||||||
|
// middle-drag in Blender, then touchpad-swipe the window away, the surface under pointer
|
||||||
|
// will change, even though the real pointer focus remains on the Blender surface due to
|
||||||
|
// the click grab.
|
||||||
|
//
|
||||||
|
// Ideally we would just use the constraint surface, but we need its origin. So this is
|
||||||
|
// more of a hack because pointer contents has the surface origin available.
|
||||||
|
//
|
||||||
|
// FIXME: use the constraint surface somehow, don't use pointer contents.
|
||||||
|
let Some((ref surface_under_pointer, origin)) = self.niri.pointer_contents.surface else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if surface_under_pointer != surface {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut root = surface.clone();
|
||||||
|
while let Some(parent) = get_parent(&root) {
|
||||||
|
root = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = self
|
||||||
|
.niri
|
||||||
|
.output_for_root(&root)
|
||||||
|
.and_then(|output| self.niri.global_space.output_geometry(output))
|
||||||
|
.map_or(origin + location, |mut output_geometry| {
|
||||||
|
// i32 sizes are exclusive, but f64 sizes are inclusive.
|
||||||
|
output_geometry.size -= (1, 1).into();
|
||||||
|
(origin + location).constrain(output_geometry.to_f64())
|
||||||
|
});
|
||||||
|
pointer.set_location(target);
|
||||||
|
|
||||||
|
// Redraw to update the cursor position if it's visible.
|
||||||
|
if !self.niri.pointer_hidden {
|
||||||
|
// FIXME: redraw only outputs overlapping the cursor.
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delegate_pointer_constraints!(State);
|
delegate_pointer_constraints!(State);
|
||||||
@@ -225,7 +285,23 @@ impl ClientDndGrabHandler for State {
|
|||||||
icon: Option<WlSurface>,
|
icon: Option<WlSurface>,
|
||||||
_seat: Seat<Self>,
|
_seat: Seat<Self>,
|
||||||
) {
|
) {
|
||||||
self.niri.dnd_icon = icon;
|
let offset = if let CursorImageStatus::Surface(ref surface) =
|
||||||
|
self.niri.cursor_manager.cursor_image()
|
||||||
|
{
|
||||||
|
with_states(surface, |states| {
|
||||||
|
let hotspot = states
|
||||||
|
.data_map
|
||||||
|
.get::<CursorImageSurfaceData>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.hotspot;
|
||||||
|
Point::from((-hotspot.x, -hotspot.y))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
(0, 0).into()
|
||||||
|
};
|
||||||
|
self.niri.dnd_icon = icon.map(|surface| DndIcon { surface, offset });
|
||||||
// FIXME: more granular
|
// FIXME: more granular
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
@@ -388,12 +464,12 @@ impl ForeignToplevelHandler for State {
|
|||||||
fn set_fullscreen(&mut self, wl_surface: WlSurface, wl_output: Option<WlOutput>) {
|
fn set_fullscreen(&mut self, wl_surface: WlSurface, wl_output: Option<WlOutput>) {
|
||||||
if let Some((mapped, current_output)) = self.niri.layout.find_window_and_output(&wl_surface)
|
if let Some((mapped, current_output)) = self.niri.layout.find_window_and_output(&wl_surface)
|
||||||
{
|
{
|
||||||
if !mapped
|
let has_fullscreen_cap = with_toplevel_role(mapped.toplevel(), |role| {
|
||||||
.toplevel()
|
role.current
|
||||||
.current_state()
|
.capabilities
|
||||||
.capabilities
|
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
|
||||||
.contains(xdg_toplevel::WmCapabilities::Fullscreen)
|
});
|
||||||
{
|
if !has_fullscreen_cap {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +479,7 @@ impl ForeignToplevelHandler for State {
|
|||||||
if &requested_output != current_output {
|
if &requested_output != current_output {
|
||||||
self.niri
|
self.niri
|
||||||
.layout
|
.layout
|
||||||
.move_window_to_output(&window, &requested_output);
|
.move_to_output(Some(&window), &requested_output, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,16 +627,18 @@ impl XdgActivationHandler for State {
|
|||||||
|
|
||||||
fn request_activation(
|
fn request_activation(
|
||||||
&mut self,
|
&mut self,
|
||||||
_token: XdgActivationToken,
|
token: XdgActivationToken,
|
||||||
token_data: XdgActivationTokenData,
|
token_data: XdgActivationTokenData,
|
||||||
surface: WlSurface,
|
surface: WlSurface,
|
||||||
) {
|
) {
|
||||||
if token_data.timestamp.elapsed().as_secs() < 10 {
|
if token_data.timestamp.elapsed() < XDG_ACTIVATION_TOKEN_TIMEOUT {
|
||||||
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&surface) {
|
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&surface) {
|
||||||
let window = mapped.window.clone();
|
let window = mapped.window.clone();
|
||||||
self.niri.layout.activate_window(&window);
|
self.niri.layout.activate_window(&window);
|
||||||
self.niri.layer_shell_on_demand_focus = None;
|
self.niri.layer_shell_on_demand_focus = None;
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
|
|
||||||
|
self.niri.activation_state.remove_token(&token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+150
-55
@@ -23,12 +23,13 @@ use smithay::wayland::compositor::{
|
|||||||
};
|
};
|
||||||
use smithay::wayland::dmabuf::get_dmabuf;
|
use smithay::wayland::dmabuf::get_dmabuf;
|
||||||
use smithay::wayland::input_method::InputMethodSeat;
|
use smithay::wayland::input_method::InputMethodSeat;
|
||||||
|
use smithay::wayland::selection::data_device::DnDGrab;
|
||||||
use smithay::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState};
|
use smithay::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState};
|
||||||
use smithay::wayland::shell::wlr_layer::{self, Layer};
|
use smithay::wayland::shell::wlr_layer::{self, Layer};
|
||||||
use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;
|
use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;
|
||||||
use smithay::wayland::shell::xdg::{
|
use smithay::wayland::shell::xdg::{
|
||||||
PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, XdgShellHandler,
|
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
|
||||||
XdgShellState, XdgToplevelSurfaceData,
|
XdgToplevelSurfaceData,
|
||||||
};
|
};
|
||||||
use smithay::wayland::xdg_foreign::{XdgForeignHandler, XdgForeignState};
|
use smithay::wayland::xdg_foreign::{XdgForeignHandler, XdgForeignState};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
@@ -36,8 +37,11 @@ use smithay::{
|
|||||||
};
|
};
|
||||||
use tracing::field::Empty;
|
use tracing::field::Empty;
|
||||||
|
|
||||||
|
use crate::input::move_grab::MoveGrab;
|
||||||
use crate::input::resize_grab::ResizeGrab;
|
use crate::input::resize_grab::ResizeGrab;
|
||||||
use crate::input::DOUBLE_CLICK_TIME;
|
use crate::input::touch_move_grab::TouchMoveGrab;
|
||||||
|
use crate::input::touch_resize_grab::TouchResizeGrab;
|
||||||
|
use crate::input::{PointerOrTouchStartData, DOUBLE_CLICK_TIME};
|
||||||
use crate::layout::workspace::ColumnWidth;
|
use crate::layout::workspace::ColumnWidth;
|
||||||
use crate::niri::{PopupGrabState, State};
|
use crate::niri::{PopupGrabState, State};
|
||||||
use crate::utils::transaction::Transaction;
|
use crate::utils::transaction::Transaction;
|
||||||
@@ -65,8 +69,94 @@ impl XdgShellHandler for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_request(&mut self, _surface: ToplevelSurface, _seat: WlSeat, _serial: Serial) {
|
fn move_request(&mut self, surface: ToplevelSurface, _seat: WlSeat, serial: Serial) {
|
||||||
// FIXME
|
let wl_surface = surface.wl_surface();
|
||||||
|
|
||||||
|
let mut grab_start_data = None;
|
||||||
|
|
||||||
|
// See if this comes from a pointer grab.
|
||||||
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
|
pointer.with_grab(|grab_serial, grab| {
|
||||||
|
if grab_serial == serial {
|
||||||
|
let start_data = grab.start_data();
|
||||||
|
if let Some((focus, _)) = &start_data.focus {
|
||||||
|
if focus.id().same_client_as(&wl_surface.id()) {
|
||||||
|
// Deny move requests from DnD grabs to work around
|
||||||
|
// https://gitlab.gnome.org/GNOME/gtk/-/issues/7113
|
||||||
|
let is_dnd_grab = grab.as_any().is::<DnDGrab<Self>>();
|
||||||
|
|
||||||
|
if !is_dnd_grab {
|
||||||
|
grab_start_data =
|
||||||
|
Some(PointerOrTouchStartData::Pointer(start_data.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// See if this comes from a touch grab.
|
||||||
|
if let Some(touch) = self.niri.seat.get_touch() {
|
||||||
|
touch.with_grab(|grab_serial, grab| {
|
||||||
|
if grab_serial == serial {
|
||||||
|
let start_data = grab.start_data();
|
||||||
|
if let Some((focus, _)) = &start_data.focus {
|
||||||
|
if focus.id().same_client_as(&wl_surface.id()) {
|
||||||
|
// Deny move requests from DnD grabs to work around
|
||||||
|
// https://gitlab.gnome.org/GNOME/gtk/-/issues/7113
|
||||||
|
let is_dnd_grab = grab.as_any().is::<DnDGrab<Self>>();
|
||||||
|
|
||||||
|
if !is_dnd_grab {
|
||||||
|
grab_start_data =
|
||||||
|
Some(PointerOrTouchStartData::Touch(start_data.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(start_data) = grab_start_data else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((mapped, output)) = self.niri.layout.find_window_and_output(wl_surface) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let window = mapped.window.clone();
|
||||||
|
let output = output.clone();
|
||||||
|
|
||||||
|
let output_pos = self
|
||||||
|
.niri
|
||||||
|
.global_space
|
||||||
|
.output_geometry(&output)
|
||||||
|
.unwrap()
|
||||||
|
.loc
|
||||||
|
.to_f64();
|
||||||
|
|
||||||
|
let pos_within_output = start_data.location() - output_pos;
|
||||||
|
|
||||||
|
if !self
|
||||||
|
.niri
|
||||||
|
.layout
|
||||||
|
.interactive_move_begin(window.clone(), &output, pos_within_output)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match start_data {
|
||||||
|
PointerOrTouchStartData::Pointer(start_data) => {
|
||||||
|
let grab = MoveGrab::new(start_data, window);
|
||||||
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||||
|
}
|
||||||
|
PointerOrTouchStartData::Touch(start_data) => {
|
||||||
|
let touch = self.niri.seat.get_touch().unwrap();
|
||||||
|
let grab = TouchMoveGrab::new(start_data, window);
|
||||||
|
touch.set_grab(self, grab, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.niri.queue_redraw(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize_request(
|
fn resize_request(
|
||||||
@@ -76,24 +166,39 @@ impl XdgShellHandler for State {
|
|||||||
serial: Serial,
|
serial: Serial,
|
||||||
edges: xdg_toplevel::ResizeEdge,
|
edges: xdg_toplevel::ResizeEdge,
|
||||||
) {
|
) {
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
|
||||||
if !pointer.has_grab(serial) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(start_data) = pointer.grab_start_data() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some((focus, _)) = &start_data.focus else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let wl_surface = surface.wl_surface();
|
let wl_surface = surface.wl_surface();
|
||||||
if !focus.id().same_client_as(&wl_surface.id()) {
|
|
||||||
return;
|
let mut grab_start_data = None;
|
||||||
|
|
||||||
|
// See if this comes from a pointer grab.
|
||||||
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
|
if pointer.has_grab(serial) {
|
||||||
|
if let Some(start_data) = pointer.grab_start_data() {
|
||||||
|
if let Some((focus, _)) = &start_data.focus {
|
||||||
|
if focus.id().same_client_as(&wl_surface.id()) {
|
||||||
|
grab_start_data = Some(PointerOrTouchStartData::Pointer(start_data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See if this comes from a touch grab.
|
||||||
|
if let Some(touch) = self.niri.seat.get_touch() {
|
||||||
|
if touch.has_grab(serial) {
|
||||||
|
if let Some(start_data) = touch.grab_start_data() {
|
||||||
|
if let Some((focus, _)) = &start_data.focus {
|
||||||
|
if focus.id().same_client_as(&wl_surface.id()) {
|
||||||
|
grab_start_data = Some(PointerOrTouchStartData::Touch(start_data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(start_data) = grab_start_data else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let Some((mapped, _)) = self.niri.layout.find_window_and_output(wl_surface) else {
|
let Some((mapped, _)) = self.niri.layout.find_window_and_output(wl_surface) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -128,14 +233,25 @@ impl XdgShellHandler for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let grab = ResizeGrab::new(start_data, window.clone());
|
if !self
|
||||||
|
.niri
|
||||||
if !self.niri.layout.interactive_resize_begin(window, edges) {
|
.layout
|
||||||
|
.interactive_resize_begin(window.clone(), edges)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
match start_data {
|
||||||
self.niri.pointer_grab_ongoing = true;
|
PointerOrTouchStartData::Pointer(start_data) => {
|
||||||
|
let grab = ResizeGrab::new(start_data, window);
|
||||||
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||||
|
}
|
||||||
|
PointerOrTouchStartData::Touch(start_data) => {
|
||||||
|
let touch = self.niri.seat.get_touch().unwrap();
|
||||||
|
let grab = TouchResizeGrab::new(start_data, window);
|
||||||
|
touch.set_grab(self, grab, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reposition_request(
|
fn reposition_request(
|
||||||
@@ -261,7 +377,7 @@ impl XdgShellHandler for State {
|
|||||||
|
|
||||||
// A configure is required in response to this event. However, if an initial configure
|
// A configure is required in response to this event. However, if an initial configure
|
||||||
// wasn't sent, then we will send this as part of the initial configure later.
|
// wasn't sent, then we will send this as part of the initial configure later.
|
||||||
if initial_configure_sent(&surface) {
|
if surface.is_initial_configure_sent() {
|
||||||
surface.send_configure();
|
surface.send_configure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,7 +404,7 @@ impl XdgShellHandler for State {
|
|||||||
if &requested_output != current_output {
|
if &requested_output != current_output {
|
||||||
self.niri
|
self.niri
|
||||||
.layout
|
.layout
|
||||||
.move_window_to_output(&window, &requested_output);
|
.move_to_output(Some(&window), &requested_output, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +448,7 @@ impl XdgShellHandler for State {
|
|||||||
|
|
||||||
*output = mon
|
*output = mon
|
||||||
.filter(|(_, parent)| !parent)
|
.filter(|(_, parent)| !parent)
|
||||||
.map(|(mon, _)| mon.output.clone());
|
.map(|(mon, _)| mon.output().clone());
|
||||||
let mon = mon.map(|(mon, _)| mon);
|
let mon = mon.map(|(mon, _)| mon);
|
||||||
|
|
||||||
let ws = mon
|
let ws = mon
|
||||||
@@ -416,7 +532,7 @@ impl XdgShellHandler for State {
|
|||||||
|
|
||||||
*output = mon
|
*output = mon
|
||||||
.filter(|(_, parent)| !parent)
|
.filter(|(_, parent)| !parent)
|
||||||
.map(|(mon, _)| mon.output.clone());
|
.map(|(mon, _)| mon.output().clone());
|
||||||
let mon = mon.map(|(mon, _)| mon);
|
let mon = mon.map(|(mon, _)| mon);
|
||||||
|
|
||||||
let ws = workspace_name
|
let ws = workspace_name
|
||||||
@@ -552,7 +668,7 @@ impl XdgDecorationHandler for State {
|
|||||||
|
|
||||||
// A configure is required in response to this event. However, if an initial configure
|
// A configure is required in response to this event. However, if an initial configure
|
||||||
// wasn't sent, then we will send this as part of the initial configure later.
|
// wasn't sent, then we will send this as part of the initial configure later.
|
||||||
if initial_configure_sent(&toplevel) {
|
if toplevel.is_initial_configure_sent() {
|
||||||
toplevel.send_configure();
|
toplevel.send_configure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,7 +681,7 @@ impl XdgDecorationHandler for State {
|
|||||||
|
|
||||||
// A configure is required in response to this event. However, if an initial configure
|
// A configure is required in response to this event. However, if an initial configure
|
||||||
// wasn't sent, then we will send this as part of the initial configure later.
|
// wasn't sent, then we will send this as part of the initial configure later.
|
||||||
if initial_configure_sent(&toplevel) {
|
if toplevel.is_initial_configure_sent() {
|
||||||
toplevel.send_configure();
|
toplevel.send_configure();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -620,18 +736,6 @@ impl XdgForeignHandler for State {
|
|||||||
}
|
}
|
||||||
delegate_xdg_foreign!(State);
|
delegate_xdg_foreign!(State);
|
||||||
|
|
||||||
fn initial_configure_sent(toplevel: &ToplevelSurface) -> bool {
|
|
||||||
with_states(toplevel.wl_surface(), |states| {
|
|
||||||
states
|
|
||||||
.data_map
|
|
||||||
.get::<XdgToplevelSurfaceData>()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.initial_configure_sent
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn send_initial_configure(&mut self, toplevel: &ToplevelSurface) {
|
pub fn send_initial_configure(&mut self, toplevel: &ToplevelSurface) {
|
||||||
let _span = tracy_client::span!("State::send_initial_configure");
|
let _span = tracy_client::span!("State::send_initial_configure");
|
||||||
@@ -706,7 +810,7 @@ impl State {
|
|||||||
// mapped, it fetches the possibly changed parent's output again, and shows up there.
|
// mapped, it fetches the possibly changed parent's output again, and shows up there.
|
||||||
let output = mon
|
let output = mon
|
||||||
.filter(|(_, parent)| !parent)
|
.filter(|(_, parent)| !parent)
|
||||||
.map(|(mon, _)| mon.output.clone());
|
.map(|(mon, _)| mon.output().clone());
|
||||||
let mon = mon.map(|(mon, _)| mon);
|
let mon = mon.map(|(mon, _)| mon);
|
||||||
|
|
||||||
let mut width = None;
|
let mut width = None;
|
||||||
@@ -759,7 +863,7 @@ impl State {
|
|||||||
width,
|
width,
|
||||||
is_full_width,
|
is_full_width,
|
||||||
output,
|
output,
|
||||||
workspace_name: ws.and_then(|w| w.name.clone()),
|
workspace_name: ws.and_then(|w| w.name().cloned()),
|
||||||
};
|
};
|
||||||
|
|
||||||
toplevel.send_configure();
|
toplevel.send_configure();
|
||||||
@@ -788,16 +892,7 @@ impl State {
|
|||||||
if let Some(popup) = self.niri.popups.find_popup(surface) {
|
if let Some(popup) = self.niri.popups.find_popup(surface) {
|
||||||
match popup {
|
match popup {
|
||||||
PopupKind::Xdg(ref popup) => {
|
PopupKind::Xdg(ref popup) => {
|
||||||
let initial_configure_sent = with_states(surface, |states| {
|
if !popup.is_initial_configure_sent() {
|
||||||
states
|
|
||||||
.data_map
|
|
||||||
.get::<XdgPopupSurfaceData>()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.initial_configure_sent
|
|
||||||
});
|
|
||||||
if !initial_configure_sent {
|
|
||||||
if let Some(output) = self.output_for_popup(&PopupKind::Xdg(popup.clone()))
|
if let Some(output) = self.output_for_popup(&PopupKind::Xdg(popup.clone()))
|
||||||
{
|
{
|
||||||
let scale = output.current_scale();
|
let scale = output.current_scale();
|
||||||
|
|||||||
+375
-71
@@ -6,14 +6,15 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use calloop::timer::{TimeoutAction, Timer};
|
use calloop::timer::{TimeoutAction, Timer};
|
||||||
use input::event::gesture::GestureEventCoordinates as _;
|
use input::event::gesture::GestureEventCoordinates as _;
|
||||||
use niri_config::{Action, Bind, Binds, Key, Modifiers, Trigger};
|
use niri_config::{Action, Bind, Binds, Key, Modifiers, SwitchBinds, Trigger};
|
||||||
use niri_ipc::LayoutSwitchTarget;
|
use niri_ipc::LayoutSwitchTarget;
|
||||||
use smithay::backend::input::{
|
use smithay::backend::input::{
|
||||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
|
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
|
||||||
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _,
|
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _,
|
||||||
InputBackend, InputEvent, KeyState, KeyboardKeyEvent, MouseButton, PointerAxisEvent,
|
InputBackend, InputEvent, KeyState, KeyboardKeyEvent, Keycode, MouseButton, PointerAxisEvent,
|
||||||
PointerButtonEvent, PointerMotionEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
|
PointerButtonEvent, PointerMotionEvent, ProximityState, Switch, SwitchState, SwitchToggleEvent,
|
||||||
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
|
TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent,
|
||||||
|
TabletToolTipState, TouchEvent,
|
||||||
};
|
};
|
||||||
use smithay::backend::libinput::LibinputInputBackend;
|
use smithay::backend::libinput::LibinputInputBackend;
|
||||||
use smithay::input::keyboard::{keysyms, FilterResult, Keysym, ModifiersState};
|
use smithay::input::keyboard::{keysyms, FilterResult, Keysym, ModifiersState};
|
||||||
@@ -23,11 +24,16 @@ use smithay::input::pointer::{
|
|||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||||
GrabStartData as PointerGrabStartData, MotionEvent, RelativeMotionEvent,
|
GrabStartData as PointerGrabStartData, MotionEvent, RelativeMotionEvent,
|
||||||
};
|
};
|
||||||
use smithay::input::touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent};
|
use smithay::input::touch::{
|
||||||
use smithay::utils::{Logical, Point, Rectangle, SERIAL_COUNTER};
|
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent, UpEvent,
|
||||||
|
};
|
||||||
|
use smithay::input::SeatHandler;
|
||||||
|
use smithay::utils::{Logical, Point, Rectangle, Transform, SERIAL_COUNTER};
|
||||||
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
|
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
|
||||||
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
|
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
|
||||||
|
use touch_move_grab::TouchMoveGrab;
|
||||||
|
|
||||||
|
use self::move_grab::MoveGrab;
|
||||||
use self::resize_grab::ResizeGrab;
|
use self::resize_grab::ResizeGrab;
|
||||||
use self::spatial_movement_grab::SpatialMovementGrab;
|
use self::spatial_movement_grab::SpatialMovementGrab;
|
||||||
use crate::niri::State;
|
use crate::niri::State;
|
||||||
@@ -35,10 +41,13 @@ use crate::ui::screenshot_ui::ScreenshotUi;
|
|||||||
use crate::utils::spawning::spawn;
|
use crate::utils::spawning::spawn;
|
||||||
use crate::utils::{center, get_monotonic_time, ResizeEdge};
|
use crate::utils::{center, get_monotonic_time, ResizeEdge};
|
||||||
|
|
||||||
|
pub mod move_grab;
|
||||||
pub mod resize_grab;
|
pub mod resize_grab;
|
||||||
pub mod scroll_tracker;
|
pub mod scroll_tracker;
|
||||||
pub mod spatial_movement_grab;
|
pub mod spatial_movement_grab;
|
||||||
pub mod swipe_tracker;
|
pub mod swipe_tracker;
|
||||||
|
pub mod touch_move_grab;
|
||||||
|
pub mod touch_resize_grab;
|
||||||
|
|
||||||
pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400);
|
pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400);
|
||||||
|
|
||||||
@@ -53,6 +62,20 @@ pub struct TabletData {
|
|||||||
pub aspect_ratio: f64,
|
pub aspect_ratio: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum PointerOrTouchStartData<D: SeatHandler> {
|
||||||
|
Pointer(PointerGrabStartData<D>),
|
||||||
|
Touch(TouchGrabStartData<D>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: SeatHandler> PointerOrTouchStartData<D> {
|
||||||
|
pub fn location(&self) -> Point<f64, Logical> {
|
||||||
|
match self {
|
||||||
|
PointerOrTouchStartData::Pointer(x) => x.location,
|
||||||
|
PointerOrTouchStartData::Touch(x) => x.location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn process_input_event<I: InputBackend + 'static>(&mut self, event: InputEvent<I>)
|
pub fn process_input_event<I: InputBackend + 'static>(&mut self, event: InputEvent<I>)
|
||||||
where
|
where
|
||||||
@@ -87,6 +110,10 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if should_reset_pointer_inactivity_timer(&event) {
|
||||||
|
self.niri.reset_pointer_inactivity_timer();
|
||||||
|
}
|
||||||
|
|
||||||
let hide_hotkey_overlay =
|
let hide_hotkey_overlay =
|
||||||
self.niri.hotkey_overlay.is_open() && should_hide_hotkey_overlay(&event);
|
self.niri.hotkey_overlay.is_open() && should_hide_hotkey_overlay(&event);
|
||||||
|
|
||||||
@@ -123,7 +150,7 @@ impl State {
|
|||||||
TouchUp { event } => self.on_touch_up::<I>(event),
|
TouchUp { event } => self.on_touch_up::<I>(event),
|
||||||
TouchCancel { event } => self.on_touch_cancel::<I>(event),
|
TouchCancel { event } => self.on_touch_cancel::<I>(event),
|
||||||
TouchFrame { event } => self.on_touch_frame::<I>(event),
|
TouchFrame { event } => self.on_touch_frame::<I>(event),
|
||||||
SwitchToggle { .. } => (),
|
SwitchToggle { event } => self.on_switch_toggle::<I>(event),
|
||||||
Special(_) => (),
|
Special(_) => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,26 +266,31 @@ impl State {
|
|||||||
where
|
where
|
||||||
I::Device: 'static,
|
I::Device: 'static,
|
||||||
{
|
{
|
||||||
let (target_geo, keep_ratio, px) = if let Some(output) = self.niri.output_for_tablet() {
|
let (target_geo, keep_ratio, px, transform) =
|
||||||
(
|
if let Some(output) = self.niri.output_for_tablet() {
|
||||||
self.niri.global_space.output_geometry(output).unwrap(),
|
(
|
||||||
true,
|
self.niri.global_space.output_geometry(output).unwrap(),
|
||||||
1. / output.current_scale().fractional_scale(),
|
true,
|
||||||
)
|
1. / output.current_scale().fractional_scale(),
|
||||||
} else {
|
output.current_transform(),
|
||||||
let geo = self.global_bounding_rectangle()?;
|
)
|
||||||
|
} else {
|
||||||
|
let geo = self.global_bounding_rectangle()?;
|
||||||
|
|
||||||
// FIXME: this 1 px size should ideally somehow be computed for the rightmost output
|
// FIXME: this 1 px size should ideally somehow be computed for the rightmost output
|
||||||
// corresponding to the position on the right when clamping.
|
// corresponding to the position on the right when clamping.
|
||||||
let output = self.niri.global_space.outputs().next().unwrap();
|
let output = self.niri.global_space.outputs().next().unwrap();
|
||||||
let scale = output.current_scale().fractional_scale();
|
let scale = output.current_scale().fractional_scale();
|
||||||
|
|
||||||
// Do not keep ratio for the unified mode as this is what OpenTabletDriver expects.
|
// Do not keep ratio for the unified mode as this is what OpenTabletDriver expects.
|
||||||
(geo, false, 1. / scale)
|
(geo, false, 1. / scale, Transform::Normal)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pos = {
|
||||||
|
let size = transform.invert().transform_size(target_geo.size);
|
||||||
|
transform.transform_point_in(event.position_transformed(size), &size.to_f64())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut pos = event.position_transformed(target_geo.size);
|
|
||||||
|
|
||||||
if keep_ratio {
|
if keep_ratio {
|
||||||
pos.x /= target_geo.size.w as f64;
|
pos.x /= target_geo.size.w as f64;
|
||||||
pos.y /= target_geo.size.h as f64;
|
pos.y /= target_geo.size.h as f64;
|
||||||
@@ -267,7 +299,8 @@ impl State {
|
|||||||
if let Some(device) = (&device as &dyn Any).downcast_ref::<input::Device>() {
|
if let Some(device) = (&device as &dyn Any).downcast_ref::<input::Device>() {
|
||||||
if let Some(data) = self.niri.tablets.get(device) {
|
if let Some(data) = self.niri.tablets.get(device) {
|
||||||
// This code does the same thing as mutter with "keep aspect ratio" enabled.
|
// This code does the same thing as mutter with "keep aspect ratio" enabled.
|
||||||
let output_aspect_ratio = target_geo.size.w as f64 / target_geo.size.h as f64;
|
let size = transform.invert().transform_size(target_geo.size);
|
||||||
|
let output_aspect_ratio = size.w as f64 / size.h as f64;
|
||||||
let ratio = data.aspect_ratio / output_aspect_ratio;
|
let ratio = data.aspect_ratio / output_aspect_ratio;
|
||||||
|
|
||||||
if ratio > 1. {
|
if ratio > 1. {
|
||||||
@@ -307,6 +340,10 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pressed {
|
||||||
|
self.hide_cursor_if_needed();
|
||||||
|
}
|
||||||
|
|
||||||
let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input(
|
let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input(
|
||||||
self,
|
self,
|
||||||
event.key_code(),
|
event.key_code(),
|
||||||
@@ -349,7 +386,10 @@ impl State {
|
|||||||
|
|
||||||
self.handle_bind(bind.clone());
|
self.handle_bind(bind.clone());
|
||||||
|
|
||||||
// Start the key repeat timer if necessary.
|
self.start_key_repeat(bind);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_key_repeat(&mut self, bind: Bind) {
|
||||||
if !bind.repeat {
|
if !bind.repeat {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -383,6 +423,22 @@ impl State {
|
|||||||
self.niri.bind_repeat_timer = Some(token);
|
self.niri.bind_repeat_timer = Some(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hide_cursor_if_needed(&mut self) {
|
||||||
|
if !self.niri.config.borrow().cursor.hide_when_typing {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// niri keeps this set only while actively using a tablet, which means the cursor position
|
||||||
|
// is likely to change almost immediately, causing pointer_hidden to just flicker back and
|
||||||
|
// forth.
|
||||||
|
if self.niri.tablet_cursor_location.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.niri.pointer_hidden = true;
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn handle_bind(&mut self, bind: Bind) {
|
pub fn handle_bind(&mut self, bind: Bind) {
|
||||||
let Some(cooldown) = bind.cooldown else {
|
let Some(cooldown) = bind.cooldown else {
|
||||||
self.do_action(bind.action, bind.allow_when_locked);
|
self.do_action(bind.action, bind.allow_when_locked);
|
||||||
@@ -452,6 +508,9 @@ impl State {
|
|||||||
Action::PowerOffMonitors => {
|
Action::PowerOffMonitors => {
|
||||||
self.niri.deactivate_monitors(&mut self.backend);
|
self.niri.deactivate_monitors(&mut self.backend);
|
||||||
}
|
}
|
||||||
|
Action::PowerOnMonitors => {
|
||||||
|
self.niri.activate_monitors(&mut self.backend);
|
||||||
|
}
|
||||||
Action::ToggleDebugTint => {
|
Action::ToggleDebugTint => {
|
||||||
self.backend.toggle_debug_tint();
|
self.backend.toggle_debug_tint();
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
@@ -482,6 +541,10 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::ConfirmScreenshot => {
|
Action::ConfirmScreenshot => {
|
||||||
|
if !self.niri.screenshot_ui.is_open() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.backend.with_primary_renderer(|renderer| {
|
self.backend.with_primary_renderer(|renderer| {
|
||||||
match self.niri.screenshot_ui.capture(renderer) {
|
match self.niri.screenshot_ui.capture(renderer) {
|
||||||
Ok((size, pixels)) => {
|
Ok((size, pixels)) => {
|
||||||
@@ -502,6 +565,10 @@ impl State {
|
|||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
Action::CancelScreenshot => {
|
Action::CancelScreenshot => {
|
||||||
|
if !self.niri.screenshot_ui.is_open() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.niri.screenshot_ui.close();
|
self.niri.screenshot_ui.close();
|
||||||
self.niri
|
self.niri
|
||||||
.cursor_manager
|
.cursor_manager
|
||||||
@@ -529,7 +596,7 @@ impl State {
|
|||||||
let mut windows = self.niri.layout.windows();
|
let mut windows = self.niri.layout.windows();
|
||||||
let window = windows.find(|(_, m)| m.id().get() == id);
|
let window = windows.find(|(_, m)| m.id().get() == id);
|
||||||
if let Some((Some(monitor), mapped)) = window {
|
if let Some((Some(monitor), mapped)) = window {
|
||||||
let output = &monitor.output;
|
let output = monitor.output();
|
||||||
self.backend.with_primary_renderer(|renderer| {
|
self.backend.with_primary_renderer(|renderer| {
|
||||||
if let Err(err) = self.niri.screenshot_window(renderer, output, mapped) {
|
if let Err(err) = self.niri.screenshot_window(renderer, output, mapped) {
|
||||||
warn!("error taking screenshot: {err:?}");
|
warn!("error taking screenshot: {err:?}");
|
||||||
@@ -677,17 +744,39 @@ impl State {
|
|||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
Action::ConsumeOrExpelWindowLeft => {
|
Action::ConsumeOrExpelWindowLeft => {
|
||||||
self.niri.layout.consume_or_expel_window_left();
|
self.niri.layout.consume_or_expel_window_left(None);
|
||||||
self.maybe_warp_cursor_to_focus();
|
self.maybe_warp_cursor_to_focus();
|
||||||
// FIXME: granular
|
// FIXME: granular
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
|
Action::ConsumeOrExpelWindowLeftById(id) => {
|
||||||
|
let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id);
|
||||||
|
let window = window.map(|(_, m)| m.window.clone());
|
||||||
|
if let Some(window) = window {
|
||||||
|
self.niri.layout.consume_or_expel_window_left(Some(&window));
|
||||||
|
self.maybe_warp_cursor_to_focus();
|
||||||
|
// FIXME: granular
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
Action::ConsumeOrExpelWindowRight => {
|
Action::ConsumeOrExpelWindowRight => {
|
||||||
self.niri.layout.consume_or_expel_window_right();
|
self.niri.layout.consume_or_expel_window_right(None);
|
||||||
self.maybe_warp_cursor_to_focus();
|
self.maybe_warp_cursor_to_focus();
|
||||||
// FIXME: granular
|
// FIXME: granular
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
|
Action::ConsumeOrExpelWindowRightById(id) => {
|
||||||
|
let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id);
|
||||||
|
let window = window.map(|(_, m)| m.window.clone());
|
||||||
|
if let Some(window) = window {
|
||||||
|
self.niri
|
||||||
|
.layout
|
||||||
|
.consume_or_expel_window_right(Some(&window));
|
||||||
|
self.maybe_warp_cursor_to_focus();
|
||||||
|
// FIXME: granular
|
||||||
|
self.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
Action::FocusColumnLeft => {
|
Action::FocusColumnLeft => {
|
||||||
self.niri.layout.focus_left();
|
self.niri.layout.focus_left();
|
||||||
self.maybe_warp_cursor_to_focus();
|
self.maybe_warp_cursor_to_focus();
|
||||||
@@ -1240,12 +1329,17 @@ impl State {
|
|||||||
self.niri.tablet_cursor_location = None;
|
self.niri.tablet_cursor_location = None;
|
||||||
|
|
||||||
// Check if we have an active pointer constraint.
|
// Check if we have an active pointer constraint.
|
||||||
|
//
|
||||||
|
// FIXME: ideally this should use the pointer focus with up-to-date global location.
|
||||||
let mut pointer_confined = None;
|
let mut pointer_confined = None;
|
||||||
if let Some(focus) = &self.niri.pointer_focus.surface {
|
if let Some(under) = &self.niri.pointer_contents.surface {
|
||||||
let pos_within_surface = pos - focus.1;
|
// No need to check if the pointer focus surface matches, because here we're checking
|
||||||
|
// for an already-active constraint, and the constraint is deactivated when the focused
|
||||||
|
// surface changes.
|
||||||
|
let pos_within_surface = pos - under.1;
|
||||||
|
|
||||||
let mut pointer_locked = false;
|
let mut pointer_locked = false;
|
||||||
with_pointer_constraint(&focus.0, &pointer, |constraint| {
|
with_pointer_constraint(&under.0, &pointer, |constraint| {
|
||||||
let Some(constraint) = constraint else { return };
|
let Some(constraint) = constraint else { return };
|
||||||
if !constraint.is_active() {
|
if !constraint.is_active() {
|
||||||
return;
|
return;
|
||||||
@@ -1263,7 +1357,7 @@ impl State {
|
|||||||
pointer_locked = true;
|
pointer_locked = true;
|
||||||
}
|
}
|
||||||
PointerConstraint::Confined(confine) => {
|
PointerConstraint::Confined(confine) => {
|
||||||
pointer_confined = Some((focus.clone(), confine.region().cloned()));
|
pointer_confined = Some((under.clone(), confine.region().cloned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1272,7 +1366,7 @@ impl State {
|
|||||||
if pointer_locked {
|
if pointer_locked {
|
||||||
pointer.relative_motion(
|
pointer.relative_motion(
|
||||||
self,
|
self,
|
||||||
Some(focus.clone()),
|
Some(under.clone()),
|
||||||
&RelativeMotionEvent {
|
&RelativeMotionEvent {
|
||||||
delta: event.delta(),
|
delta: event.delta(),
|
||||||
delta_unaccel: event.delta_unaccel(),
|
delta_unaccel: event.delta_unaccel(),
|
||||||
@@ -1330,7 +1424,7 @@ impl State {
|
|||||||
self.niri.screenshot_ui.pointer_motion(point);
|
self.niri.screenshot_ui.pointer_motion(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(new_pos);
|
let under = self.niri.contents_under(new_pos);
|
||||||
|
|
||||||
// Handle confined pointer.
|
// Handle confined pointer.
|
||||||
if let Some((focus_surface, region)) = pointer_confined {
|
if let Some((focus_surface, region)) = pointer_confined {
|
||||||
@@ -1368,10 +1462,7 @@ impl State {
|
|||||||
|
|
||||||
self.niri.handle_focus_follows_mouse(&under);
|
self.niri.handle_focus_follows_mouse(&under);
|
||||||
|
|
||||||
// Activate a new confinement if necessary.
|
self.niri.pointer_contents.clone_from(&under);
|
||||||
self.niri.maybe_activate_pointer_constraint(new_pos, &under);
|
|
||||||
|
|
||||||
self.niri.pointer_focus.clone_from(&under);
|
|
||||||
|
|
||||||
pointer.motion(
|
pointer.motion(
|
||||||
self,
|
self,
|
||||||
@@ -1395,6 +1486,9 @@ impl State {
|
|||||||
|
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
|
|
||||||
|
// Activate a new confinement if necessary.
|
||||||
|
self.niri.maybe_activate_pointer_constraint();
|
||||||
|
|
||||||
// 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();
|
||||||
@@ -1429,12 +1523,11 @@ impl State {
|
|||||||
self.niri.screenshot_ui.pointer_motion(point);
|
self.niri.screenshot_ui.pointer_motion(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(pos);
|
let under = self.niri.contents_under(pos);
|
||||||
|
|
||||||
self.niri.handle_focus_follows_mouse(&under);
|
self.niri.handle_focus_follows_mouse(&under);
|
||||||
|
|
||||||
self.niri.maybe_activate_pointer_constraint(pos, &under);
|
self.niri.pointer_contents.clone_from(&under);
|
||||||
self.niri.pointer_focus.clone_from(&under);
|
|
||||||
|
|
||||||
pointer.motion(
|
pointer.motion(
|
||||||
self,
|
self,
|
||||||
@@ -1448,6 +1541,8 @@ impl State {
|
|||||||
|
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
|
|
||||||
|
self.niri.maybe_activate_pointer_constraint();
|
||||||
|
|
||||||
// We moved the pointer, show it.
|
// We moved the pointer, show it.
|
||||||
self.niri.pointer_hidden = false;
|
self.niri.pointer_hidden = false;
|
||||||
|
|
||||||
@@ -1469,11 +1564,47 @@ impl State {
|
|||||||
let button_state = event.state();
|
let button_state = event.state();
|
||||||
|
|
||||||
if ButtonState::Pressed == button_state {
|
if ButtonState::Pressed == button_state {
|
||||||
|
// We received an event for the regular pointer, so show it now.
|
||||||
|
self.niri.pointer_hidden = false;
|
||||||
|
self.niri.tablet_cursor_location = None;
|
||||||
|
|
||||||
if let Some(mapped) = self.niri.window_under_cursor() {
|
if let Some(mapped) = self.niri.window_under_cursor() {
|
||||||
let window = mapped.window.clone();
|
let window = mapped.window.clone();
|
||||||
|
|
||||||
|
// Check if we need to start an interactive move.
|
||||||
|
if event.button() == Some(MouseButton::Left) && !pointer.is_grabbed() {
|
||||||
|
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||||
|
let mod_down = match self.backend.mod_key() {
|
||||||
|
CompositorMod::Super => mods.logo,
|
||||||
|
CompositorMod::Alt => mods.alt,
|
||||||
|
};
|
||||||
|
if mod_down {
|
||||||
|
let location = pointer.current_location();
|
||||||
|
let (output, pos_within_output) = self.niri.output_under(location).unwrap();
|
||||||
|
let output = output.clone();
|
||||||
|
|
||||||
|
self.niri.layout.activate_window(&window);
|
||||||
|
|
||||||
|
if self.niri.layout.interactive_move_begin(
|
||||||
|
window.clone(),
|
||||||
|
&output,
|
||||||
|
pos_within_output,
|
||||||
|
) {
|
||||||
|
let start_data = PointerGrabStartData {
|
||||||
|
focus: None,
|
||||||
|
button: event.button_code(),
|
||||||
|
location,
|
||||||
|
};
|
||||||
|
let grab = MoveGrab::new(start_data, window.clone());
|
||||||
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||||
|
self.niri
|
||||||
|
.cursor_manager
|
||||||
|
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Move));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Check if we need to start an interactive resize.
|
// Check if we need to start an interactive resize.
|
||||||
if event.button() == Some(MouseButton::Right) && !pointer.is_grabbed() {
|
else if event.button() == Some(MouseButton::Right) && !pointer.is_grabbed() {
|
||||||
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||||
let mod_down = match self.backend.mod_key() {
|
let mod_down = match self.backend.mod_key() {
|
||||||
CompositorMod::Super => mods.logo,
|
CompositorMod::Super => mods.logo,
|
||||||
@@ -1531,7 +1662,6 @@ impl State {
|
|||||||
};
|
};
|
||||||
let grab = ResizeGrab::new(start_data, window.clone());
|
let grab = ResizeGrab::new(start_data, window.clone());
|
||||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||||
self.niri.pointer_grab_ongoing = true;
|
|
||||||
self.niri.cursor_manager.set_cursor_image(
|
self.niri.cursor_manager.set_cursor_image(
|
||||||
CursorImageStatus::Named(edges.cursor_icon()),
|
CursorImageStatus::Named(edges.cursor_icon()),
|
||||||
);
|
);
|
||||||
@@ -1567,7 +1697,6 @@ impl State {
|
|||||||
};
|
};
|
||||||
let grab = SpatialMovementGrab::new(start_data, output);
|
let grab = SpatialMovementGrab::new(start_data, output);
|
||||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||||
self.niri.pointer_grab_ongoing = true;
|
|
||||||
self.niri
|
self.niri
|
||||||
.cursor_manager
|
.cursor_manager
|
||||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::AllScroll));
|
.set_cursor_image(CursorImageStatus::Named(CursorIcon::AllScroll));
|
||||||
@@ -1576,11 +1705,11 @@ impl State {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.update_pointer_focus();
|
self.update_pointer_contents();
|
||||||
|
|
||||||
if ButtonState::Pressed == button_state {
|
if ButtonState::Pressed == button_state {
|
||||||
let layer_focus = self.niri.pointer_focus.layer.clone();
|
let layer_under = self.niri.pointer_contents.layer.clone();
|
||||||
self.niri.focus_layer_surface_if_on_demand(layer_focus);
|
self.niri.focus_layer_surface_if_on_demand(layer_under);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(button) = event.button() {
|
if let Some(button) = event.button() {
|
||||||
@@ -1623,6 +1752,12 @@ impl State {
|
|||||||
fn on_pointer_axis<I: InputBackend>(&mut self, event: I::PointerAxisEvent) {
|
fn on_pointer_axis<I: InputBackend>(&mut self, event: I::PointerAxisEvent) {
|
||||||
let source = event.source();
|
let source = event.source();
|
||||||
|
|
||||||
|
// We received an event for the regular pointer, so show it now. This is also needed for
|
||||||
|
// update_pointer_contents() below to return the real contents, necessary for the pointer
|
||||||
|
// axis event to reach the window.
|
||||||
|
self.niri.pointer_hidden = false;
|
||||||
|
self.niri.tablet_cursor_location = None;
|
||||||
|
|
||||||
let horizontal_amount_v120 = event.amount_v120(Axis::Horizontal);
|
let horizontal_amount_v120 = event.amount_v120(Axis::Horizontal);
|
||||||
let vertical_amount_v120 = event.amount_v120(Axis::Vertical);
|
let vertical_amount_v120 = event.amount_v120(Axis::Vertical);
|
||||||
|
|
||||||
@@ -1761,14 +1896,24 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let scroll_factor = match source {
|
||||||
|
AxisSource::Wheel => self.niri.config.borrow().input.mouse.scroll_factor,
|
||||||
|
AxisSource::Finger => self.niri.config.borrow().input.touchpad.scroll_factor,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let scroll_factor = scroll_factor.map(|x| x.0).unwrap_or(1.);
|
||||||
|
|
||||||
let horizontal_amount = horizontal_amount.unwrap_or_else(|| {
|
let horizontal_amount = horizontal_amount.unwrap_or_else(|| {
|
||||||
// Winit backend, discrete scrolling.
|
// Winit backend, discrete scrolling.
|
||||||
horizontal_amount_v120.unwrap_or(0.0) / 120. * 15.
|
horizontal_amount_v120.unwrap_or(0.0) / 120. * 15.
|
||||||
});
|
}) * scroll_factor;
|
||||||
let vertical_amount = vertical_amount.unwrap_or_else(|| {
|
let vertical_amount = vertical_amount.unwrap_or_else(|| {
|
||||||
// Winit backend, discrete scrolling.
|
// Winit backend, discrete scrolling.
|
||||||
vertical_amount_v120.unwrap_or(0.0) / 120. * 15.
|
vertical_amount_v120.unwrap_or(0.0) / 120. * 15.
|
||||||
});
|
}) * scroll_factor;
|
||||||
|
|
||||||
|
let horizontal_amount_v120 = horizontal_amount_v120.map(|x| x * scroll_factor);
|
||||||
|
let vertical_amount_v120 = vertical_amount_v120.map(|x| x * scroll_factor);
|
||||||
|
|
||||||
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 {
|
||||||
@@ -1797,7 +1942,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_pointer_focus();
|
self.update_pointer_contents();
|
||||||
|
|
||||||
let pointer = &self.niri.seat.get_pointer().unwrap();
|
let pointer = &self.niri.seat.get_pointer().unwrap();
|
||||||
pointer.axis(self, frame);
|
pointer.axis(self, frame);
|
||||||
@@ -1812,7 +1957,7 @@ impl State {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(pos);
|
let under = self.niri.contents_under(pos);
|
||||||
|
|
||||||
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()));
|
||||||
@@ -1864,7 +2009,7 @@ impl State {
|
|||||||
tool.tip_down(serial, event.time_msec());
|
tool.tip_down(serial, event.time_msec());
|
||||||
|
|
||||||
if let Some(pos) = self.niri.tablet_cursor_location {
|
if let Some(pos) = self.niri.tablet_cursor_location {
|
||||||
let under = self.niri.surface_under_and_global_space(pos);
|
let under = self.niri.contents_under(pos);
|
||||||
if let Some(window) = under.window {
|
if let Some(window) = under.window {
|
||||||
self.niri.layout.activate_window(&window);
|
self.niri.layout.activate_window(&window);
|
||||||
|
|
||||||
@@ -1894,7 +2039,7 @@ impl State {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(pos);
|
let under = self.niri.contents_under(pos);
|
||||||
|
|
||||||
let tablet_seat = self.niri.seat.tablet_seat();
|
let tablet_seat = self.niri.seat.tablet_seat();
|
||||||
let display_handle = self.niri.display_handle.clone();
|
let display_handle = self.niri.display_handle.clone();
|
||||||
@@ -1926,6 +2071,7 @@ impl State {
|
|||||||
self.move_cursor(pos);
|
self.move_cursor(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.niri.pointer_hidden = false;
|
||||||
self.niri.tablet_cursor_location = None;
|
self.niri.tablet_cursor_location = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1959,7 +2105,7 @@ impl State {
|
|||||||
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();
|
||||||
|
|
||||||
if self.update_pointer_focus() {
|
if self.update_pointer_contents() {
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2050,7 +2196,7 @@ impl State {
|
|||||||
|
|
||||||
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_contents() {
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2093,7 +2239,7 @@ impl State {
|
|||||||
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();
|
||||||
|
|
||||||
if self.update_pointer_focus() {
|
if self.update_pointer_contents() {
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2111,7 +2257,7 @@ impl State {
|
|||||||
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();
|
||||||
|
|
||||||
if self.update_pointer_focus() {
|
if self.update_pointer_contents() {
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2128,7 +2274,7 @@ impl State {
|
|||||||
fn on_gesture_pinch_update<I: InputBackend>(&mut self, event: I::GesturePinchUpdateEvent) {
|
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_contents() {
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2147,7 +2293,7 @@ impl State {
|
|||||||
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();
|
||||||
|
|
||||||
if self.update_pointer_focus() {
|
if self.update_pointer_contents() {
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2165,7 +2311,7 @@ impl State {
|
|||||||
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();
|
||||||
|
|
||||||
if self.update_pointer_focus() {
|
if self.update_pointer_contents() {
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2183,7 +2329,7 @@ impl State {
|
|||||||
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();
|
||||||
|
|
||||||
if self.update_pointer_focus() {
|
if self.update_pointer_contents() {
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2222,12 +2368,40 @@ impl State {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let under = self.niri.surface_under_and_global_space(touch_location);
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
|
||||||
|
let under = self.niri.contents_under(touch_location);
|
||||||
|
|
||||||
if !handle.is_grabbed() {
|
if !handle.is_grabbed() {
|
||||||
if let Some(window) = under.window {
|
if let Some(window) = under.window {
|
||||||
self.niri.layout.activate_window(&window);
|
self.niri.layout.activate_window(&window);
|
||||||
|
|
||||||
|
// Check if we need to start an interactive move.
|
||||||
|
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||||
|
let mod_down = match self.backend.mod_key() {
|
||||||
|
CompositorMod::Super => mods.logo,
|
||||||
|
CompositorMod::Alt => mods.alt,
|
||||||
|
};
|
||||||
|
if mod_down {
|
||||||
|
let (output, pos_within_output) =
|
||||||
|
self.niri.output_under(touch_location).unwrap();
|
||||||
|
let output = output.clone();
|
||||||
|
|
||||||
|
if self.niri.layout.interactive_move_begin(
|
||||||
|
window.clone(),
|
||||||
|
&output,
|
||||||
|
pos_within_output,
|
||||||
|
) {
|
||||||
|
let start_data = TouchGrabStartData {
|
||||||
|
focus: None,
|
||||||
|
slot: evt.slot(),
|
||||||
|
location: touch_location,
|
||||||
|
};
|
||||||
|
let grab = TouchMoveGrab::new(start_data, window.clone());
|
||||||
|
handle.set_grab(self, grab, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: granular.
|
// FIXME: granular.
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
} else if let Some(output) = under.output {
|
} else if let Some(output) = under.output {
|
||||||
@@ -2239,7 +2413,6 @@ impl State {
|
|||||||
self.niri.focus_layer_surface_if_on_demand(under.layer);
|
self.niri.focus_layer_surface_if_on_demand(under.layer);
|
||||||
};
|
};
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
|
||||||
handle.down(
|
handle.down(
|
||||||
self,
|
self,
|
||||||
under.surface,
|
under.surface,
|
||||||
@@ -2275,7 +2448,7 @@ impl State {
|
|||||||
let Some(touch_location) = self.compute_touch_location(&evt) else {
|
let Some(touch_location) = self.compute_touch_location(&evt) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let under = self.niri.surface_under_and_global_space(touch_location);
|
let under = self.niri.contents_under(touch_location);
|
||||||
handle.motion(
|
handle.motion(
|
||||||
self,
|
self,
|
||||||
under.surface,
|
under.surface,
|
||||||
@@ -2298,6 +2471,28 @@ impl State {
|
|||||||
};
|
};
|
||||||
handle.cancel(self);
|
handle.cancel(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_switch_toggle<I: InputBackend>(&mut self, evt: I::SwitchToggleEvent) {
|
||||||
|
let Some(switch) = evt.switch() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if switch == Switch::Lid {
|
||||||
|
let is_closed = evt.state() == SwitchState::On;
|
||||||
|
debug!("lid switch {}", if is_closed { "closed" } else { "opened" });
|
||||||
|
self.niri.is_lid_closed = is_closed;
|
||||||
|
self.backend.on_output_config_changed(&mut self.niri);
|
||||||
|
}
|
||||||
|
|
||||||
|
let action = {
|
||||||
|
let bindings = &self.niri.config.borrow().switch_events;
|
||||||
|
find_configured_switch_action(bindings, switch, evt.state())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(action) = action {
|
||||||
|
self.do_action(action, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the key should be intercepted and mark intercepted
|
/// Check whether the key should be intercepted and mark intercepted
|
||||||
@@ -2305,10 +2500,10 @@ impl State {
|
|||||||
/// to them from being delivered.
|
/// to them from being delivered.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn should_intercept_key(
|
fn should_intercept_key(
|
||||||
suppressed_keys: &mut HashSet<u32>,
|
suppressed_keys: &mut HashSet<Keycode>,
|
||||||
bindings: &Binds,
|
bindings: &Binds,
|
||||||
comp_mod: CompositorMod,
|
comp_mod: CompositorMod,
|
||||||
key_code: u32,
|
key_code: Keycode,
|
||||||
modified: Keysym,
|
modified: Keysym,
|
||||||
raw: Option<Keysym>,
|
raw: Option<Keysym>,
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
@@ -2449,6 +2644,23 @@ fn find_configured_bind(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_configured_switch_action(
|
||||||
|
bindings: &SwitchBinds,
|
||||||
|
switch: Switch,
|
||||||
|
state: SwitchState,
|
||||||
|
) -> Option<Action> {
|
||||||
|
let switch_action = match (switch, state) {
|
||||||
|
(Switch::Lid, SwitchState::Off) => &bindings.lid_open,
|
||||||
|
(Switch::Lid, SwitchState::On) => &bindings.lid_close,
|
||||||
|
(Switch::TabletMode, SwitchState::Off) => &bindings.tablet_mode_off,
|
||||||
|
(Switch::TabletMode, SwitchState::On) => &bindings.tablet_mode_on,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
switch_action
|
||||||
|
.as_ref()
|
||||||
|
.map(|switch_action| Action::Spawn(switch_action.spawn.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
fn modifiers_from_state(mods: ModifiersState) -> Modifiers {
|
fn modifiers_from_state(mods: ModifiersState) -> Modifiers {
|
||||||
let mut modifiers = Modifiers::empty();
|
let mut modifiers = Modifiers::empty();
|
||||||
if mods.ctrl {
|
if mods.ctrl {
|
||||||
@@ -2530,6 +2742,20 @@ fn should_notify_activity<I: InputBackend>(event: &InputEvent<I>) -> bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_reset_pointer_inactivity_timer<I: InputBackend>(event: &InputEvent<I>) -> bool {
|
||||||
|
matches!(
|
||||||
|
event,
|
||||||
|
InputEvent::PointerAxis { .. }
|
||||||
|
| InputEvent::PointerButton { .. }
|
||||||
|
| InputEvent::PointerMotion { .. }
|
||||||
|
| InputEvent::PointerMotionAbsolute { .. }
|
||||||
|
| InputEvent::TabletToolAxis { .. }
|
||||||
|
| InputEvent::TabletToolButton { .. }
|
||||||
|
| InputEvent::TabletToolProximity { .. }
|
||||||
|
| InputEvent::TabletToolTip { .. }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn allowed_when_locked(action: &Action) -> bool {
|
fn allowed_when_locked(action: &Action) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
action,
|
action,
|
||||||
@@ -2537,6 +2763,7 @@ fn allowed_when_locked(action: &Action) -> bool {
|
|||||||
| Action::ChangeVt(_)
|
| Action::ChangeVt(_)
|
||||||
| Action::Suspend
|
| Action::Suspend
|
||||||
| Action::PowerOffMonitors
|
| Action::PowerOffMonitors
|
||||||
|
| Action::PowerOnMonitors
|
||||||
| Action::SwitchLayout(_)
|
| Action::SwitchLayout(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2544,7 +2771,11 @@ fn allowed_when_locked(action: &Action) -> bool {
|
|||||||
fn allowed_during_screenshot(action: &Action) -> bool {
|
fn allowed_during_screenshot(action: &Action) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
action,
|
action,
|
||||||
Action::Quit(_) | Action::ChangeVt(_) | Action::Suspend | Action::PowerOffMonitors
|
Action::Quit(_)
|
||||||
|
| Action::ChangeVt(_)
|
||||||
|
| Action::Suspend
|
||||||
|
| Action::PowerOffMonitors
|
||||||
|
| Action::PowerOnMonitors
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2576,8 +2807,20 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
|||||||
|
|
||||||
if let Some(method) = c.scroll_method {
|
if let Some(method) = c.scroll_method {
|
||||||
let _ = device.config_scroll_set_method(method.into());
|
let _ = device.config_scroll_set_method(method.into());
|
||||||
|
|
||||||
|
if method == niri_config::ScrollMethod::OnButtonDown {
|
||||||
|
if let Some(button) = c.scroll_button {
|
||||||
|
let _ = device.config_scroll_set_button(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if let Some(default) = device.config_scroll_default_method() {
|
} else if let Some(default) = device.config_scroll_default_method() {
|
||||||
let _ = device.config_scroll_set_method(default);
|
let _ = device.config_scroll_set_method(default);
|
||||||
|
|
||||||
|
if default == input::ScrollMethod::OnButtonDown {
|
||||||
|
if let Some(button) = c.scroll_button {
|
||||||
|
let _ = device.config_scroll_set_button(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tap_button_map) = c.tap_button_map {
|
if let Some(tap_button_map) = c.tap_button_map {
|
||||||
@@ -2632,8 +2875,57 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
|||||||
|
|
||||||
if let Some(method) = c.scroll_method {
|
if let Some(method) = c.scroll_method {
|
||||||
let _ = device.config_scroll_set_method(method.into());
|
let _ = device.config_scroll_set_method(method.into());
|
||||||
|
|
||||||
|
if method == niri_config::ScrollMethod::OnButtonDown {
|
||||||
|
if let Some(button) = c.scroll_button {
|
||||||
|
let _ = device.config_scroll_set_button(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if let Some(default) = device.config_scroll_default_method() {
|
} else if let Some(default) = device.config_scroll_default_method() {
|
||||||
let _ = device.config_scroll_set_method(default);
|
let _ = device.config_scroll_set_method(default);
|
||||||
|
|
||||||
|
if default == input::ScrollMethod::OnButtonDown {
|
||||||
|
if let Some(button) = c.scroll_button {
|
||||||
|
let _ = device.config_scroll_set_button(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_trackball {
|
||||||
|
let c = &config.trackball;
|
||||||
|
let _ = device.config_send_events_set_mode(if c.off {
|
||||||
|
input::SendEventsMode::DISABLED
|
||||||
|
} else {
|
||||||
|
input::SendEventsMode::ENABLED
|
||||||
|
});
|
||||||
|
let _ = device.config_scroll_set_natural_scroll_enabled(c.natural_scroll);
|
||||||
|
let _ = device.config_accel_set_speed(c.accel_speed);
|
||||||
|
let _ = device.config_middle_emulation_set_enabled(c.middle_emulation);
|
||||||
|
let _ = device.config_left_handed_set(c.left_handed);
|
||||||
|
|
||||||
|
if let Some(accel_profile) = c.accel_profile {
|
||||||
|
let _ = device.config_accel_set_profile(accel_profile.into());
|
||||||
|
} else if let Some(default) = device.config_accel_default_profile() {
|
||||||
|
let _ = device.config_accel_set_profile(default);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(method) = c.scroll_method {
|
||||||
|
let _ = device.config_scroll_set_method(method.into());
|
||||||
|
|
||||||
|
if method == niri_config::ScrollMethod::OnButtonDown {
|
||||||
|
if let Some(button) = c.scroll_button {
|
||||||
|
let _ = device.config_scroll_set_button(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(default) = device.config_scroll_default_method() {
|
||||||
|
let _ = device.config_scroll_set_method(default);
|
||||||
|
|
||||||
|
if default == input::ScrollMethod::OnButtonDown {
|
||||||
|
if let Some(button) = c.scroll_button {
|
||||||
|
let _ = device.config_scroll_set_button(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2656,8 +2948,20 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
|||||||
|
|
||||||
if let Some(method) = c.scroll_method {
|
if let Some(method) = c.scroll_method {
|
||||||
let _ = device.config_scroll_set_method(method.into());
|
let _ = device.config_scroll_set_method(method.into());
|
||||||
|
|
||||||
|
if method == niri_config::ScrollMethod::OnButtonDown {
|
||||||
|
if let Some(button) = c.scroll_button {
|
||||||
|
let _ = device.config_scroll_set_button(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if let Some(default) = device.config_scroll_default_method() {
|
} else if let Some(default) = device.config_scroll_default_method() {
|
||||||
let _ = device.config_scroll_set_method(default);
|
let _ = device.config_scroll_set_method(default);
|
||||||
|
|
||||||
|
if default == input::ScrollMethod::OnButtonDown {
|
||||||
|
if let Some(button) = c.scroll_button {
|
||||||
|
let _ = device.config_scroll_set_button(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2754,8 +3058,8 @@ mod tests {
|
|||||||
// The key_code we pick is arbitrary, the only thing
|
// The key_code we pick is arbitrary, the only thing
|
||||||
// that matters is that they are different between cases.
|
// that matters is that they are different between cases.
|
||||||
|
|
||||||
let close_key_code = close_keysym.into();
|
let close_key_code = Keycode::from(close_keysym.raw() + 8u32);
|
||||||
let close_key_event = |suppr: &mut HashSet<u32>, mods: ModifiersState, pressed| {
|
let close_key_event = |suppr: &mut HashSet<Keycode>, mods: ModifiersState, pressed| {
|
||||||
should_intercept_key(
|
should_intercept_key(
|
||||||
suppr,
|
suppr,
|
||||||
&bindings,
|
&bindings,
|
||||||
@@ -2771,12 +3075,12 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Key event with the code which can't trigger any action.
|
// Key event with the code which can't trigger any action.
|
||||||
let none_key_event = |suppr: &mut HashSet<u32>, mods: ModifiersState, pressed| {
|
let none_key_event = |suppr: &mut HashSet<Keycode>, mods: ModifiersState, pressed| {
|
||||||
should_intercept_key(
|
should_intercept_key(
|
||||||
suppr,
|
suppr,
|
||||||
&bindings,
|
&bindings,
|
||||||
comp_mod,
|
comp_mod,
|
||||||
Keysym::l.into(),
|
Keycode::from(Keysym::l.raw() + 8),
|
||||||
Keysym::l,
|
Keysym::l,
|
||||||
Some(Keysym::l),
|
Some(Keysym::l),
|
||||||
pressed,
|
pressed,
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use smithay::backend::input::ButtonState;
|
||||||
|
use smithay::desktop::Window;
|
||||||
|
use smithay::input::pointer::{
|
||||||
|
AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
||||||
|
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||||
|
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
|
||||||
|
};
|
||||||
|
use smithay::input::SeatHandler;
|
||||||
|
use smithay::utils::{IsAlive, Logical, Point};
|
||||||
|
|
||||||
|
use crate::niri::State;
|
||||||
|
|
||||||
|
pub struct MoveGrab {
|
||||||
|
start_data: PointerGrabStartData<State>,
|
||||||
|
last_location: Point<f64, Logical>,
|
||||||
|
window: Window,
|
||||||
|
is_moving: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MoveGrab {
|
||||||
|
pub fn new(start_data: PointerGrabStartData<State>, window: Window) -> Self {
|
||||||
|
Self {
|
||||||
|
last_location: start_data.location,
|
||||||
|
start_data,
|
||||||
|
window,
|
||||||
|
is_moving: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_ungrab(&mut self, state: &mut State) {
|
||||||
|
state.niri.layout.interactive_move_end(&self.window);
|
||||||
|
// FIXME: only redraw the window output.
|
||||||
|
state.niri.queue_redraw_all();
|
||||||
|
state
|
||||||
|
.niri
|
||||||
|
.cursor_manager
|
||||||
|
.set_cursor_image(CursorImageStatus::default_named());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerGrab<State> for MoveGrab {
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
|
||||||
|
event: &MotionEvent,
|
||||||
|
) {
|
||||||
|
// While the grab is active, no client has pointer focus.
|
||||||
|
handle.motion(data, None, event);
|
||||||
|
|
||||||
|
if self.window.alive() {
|
||||||
|
if let Some((output, pos_within_output)) = data.niri.output_under(event.location) {
|
||||||
|
let output = output.clone();
|
||||||
|
let event_delta = event.location - self.last_location;
|
||||||
|
self.last_location = event.location;
|
||||||
|
let ongoing = data.niri.layout.interactive_move_update(
|
||||||
|
&self.window,
|
||||||
|
event_delta,
|
||||||
|
output,
|
||||||
|
pos_within_output,
|
||||||
|
);
|
||||||
|
if ongoing {
|
||||||
|
let timestamp = Duration::from_millis(u64::from(event.time));
|
||||||
|
if self.is_moving {
|
||||||
|
data.niri.layout.view_offset_gesture_update(
|
||||||
|
-event_delta.x,
|
||||||
|
timestamp,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// FIXME: only redraw the previous and the new output.
|
||||||
|
data.niri.queue_redraw_all();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The move is no longer ongoing.
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relative_motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
|
||||||
|
event: &RelativeMotionEvent,
|
||||||
|
) {
|
||||||
|
// While the grab is active, no client has pointer focus.
|
||||||
|
handle.relative_motion(data, None, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &ButtonEvent,
|
||||||
|
) {
|
||||||
|
handle.button(data, event);
|
||||||
|
|
||||||
|
// MouseButton::Middle
|
||||||
|
if event.button == 0x112 {
|
||||||
|
if event.state == ButtonState::Pressed {
|
||||||
|
let output = data
|
||||||
|
.niri
|
||||||
|
.output_under(handle.current_location())
|
||||||
|
.map(|(output, _)| output)
|
||||||
|
.cloned();
|
||||||
|
// FIXME: workspace switch gesture.
|
||||||
|
if let Some(output) = output {
|
||||||
|
self.is_moving = true;
|
||||||
|
data.niri.layout.view_offset_gesture_begin(&output, false);
|
||||||
|
}
|
||||||
|
} else if event.state == ButtonState::Released {
|
||||||
|
self.is_moving = false;
|
||||||
|
data.niri.layout.view_offset_gesture_end(false, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle.current_pressed().is_empty() {
|
||||||
|
// No more buttons are pressed, release the grab.
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
details: AxisFrame,
|
||||||
|
) {
|
||||||
|
handle.axis(data, details);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) {
|
||||||
|
handle.frame(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gesture_swipe_begin(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &GestureSwipeBeginEvent,
|
||||||
|
) {
|
||||||
|
handle.gesture_swipe_begin(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gesture_swipe_update(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &GestureSwipeUpdateEvent,
|
||||||
|
) {
|
||||||
|
handle.gesture_swipe_update(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gesture_swipe_end(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &GestureSwipeEndEvent,
|
||||||
|
) {
|
||||||
|
handle.gesture_swipe_end(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gesture_pinch_begin(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &GesturePinchBeginEvent,
|
||||||
|
) {
|
||||||
|
handle.gesture_pinch_begin(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gesture_pinch_update(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &GesturePinchUpdateEvent,
|
||||||
|
) {
|
||||||
|
handle.gesture_pinch_update(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gesture_pinch_end(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &GesturePinchEndEvent,
|
||||||
|
) {
|
||||||
|
handle.gesture_pinch_end(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gesture_hold_begin(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &GestureHoldBeginEvent,
|
||||||
|
) {
|
||||||
|
handle.gesture_hold_begin(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gesture_hold_end(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
|
event: &GestureHoldEndEvent,
|
||||||
|
) {
|
||||||
|
handle.gesture_hold_end(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_data(&self) -> &PointerGrabStartData<State> {
|
||||||
|
&self.start_data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, data: &mut State) {
|
||||||
|
self.on_ungrab(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,6 @@ impl ResizeGrab {
|
|||||||
|
|
||||||
fn on_ungrab(&mut self, state: &mut State) {
|
fn on_ungrab(&mut self, state: &mut State) {
|
||||||
state.niri.layout.interactive_resize_end(&self.window);
|
state.niri.layout.interactive_resize_end(&self.window);
|
||||||
state.niri.pointer_grab_ongoing = false;
|
|
||||||
state
|
state
|
||||||
.niri
|
.niri
|
||||||
.cursor_manager
|
.cursor_manager
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ impl SpatialMovementGrab {
|
|||||||
state.niri.queue_redraw(&output);
|
state.niri.queue_redraw(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.niri.pointer_grab_ongoing = false;
|
|
||||||
state
|
state
|
||||||
.niri
|
.niri
|
||||||
.cursor_manager
|
.cursor_manager
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
use smithay::desktop::Window;
|
||||||
|
use smithay::input::touch::{
|
||||||
|
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent, OrientationEvent, ShapeEvent,
|
||||||
|
TouchGrab, TouchInnerHandle, UpEvent,
|
||||||
|
};
|
||||||
|
use smithay::input::SeatHandler;
|
||||||
|
use smithay::utils::{IsAlive, Logical, Point, Serial};
|
||||||
|
|
||||||
|
use crate::niri::State;
|
||||||
|
|
||||||
|
pub struct TouchMoveGrab {
|
||||||
|
start_data: TouchGrabStartData<State>,
|
||||||
|
last_location: Point<f64, Logical>,
|
||||||
|
window: Window,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TouchMoveGrab {
|
||||||
|
pub fn new(start_data: TouchGrabStartData<State>, window: Window) -> Self {
|
||||||
|
Self {
|
||||||
|
last_location: start_data.location,
|
||||||
|
start_data,
|
||||||
|
window,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_ungrab(&mut self, state: &mut State) {
|
||||||
|
state.niri.layout.interactive_move_end(&self.window);
|
||||||
|
// FIXME: only redraw the window output.
|
||||||
|
state.niri.queue_redraw_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TouchGrab<State> for TouchMoveGrab {
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
|
||||||
|
event: &DownEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.down(data, None, event, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &UpEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.up(data, event, seq);
|
||||||
|
|
||||||
|
if event.slot != self.start_data.slot {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.unset_grab(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
|
||||||
|
event: &MotionEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.motion(data, None, event, seq);
|
||||||
|
|
||||||
|
if event.slot != self.start_data.slot {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.window.alive() {
|
||||||
|
if let Some((output, pos_within_output)) = data.niri.output_under(event.location) {
|
||||||
|
let output = output.clone();
|
||||||
|
let event_delta = event.location - self.last_location;
|
||||||
|
self.last_location = event.location;
|
||||||
|
let ongoing = data.niri.layout.interactive_move_update(
|
||||||
|
&self.window,
|
||||||
|
event_delta,
|
||||||
|
output,
|
||||||
|
pos_within_output,
|
||||||
|
);
|
||||||
|
if ongoing {
|
||||||
|
// FIXME: only redraw the previous and the new output.
|
||||||
|
data.niri.queue_redraw_all();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The move is no longer ongoing.
|
||||||
|
handle.unset_grab(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
|
||||||
|
handle.frame(data, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
|
||||||
|
handle.cancel(data, seq);
|
||||||
|
handle.unset_grab(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shape(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &ShapeEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.shape(data, event, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn orientation(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &OrientationEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.orientation(data, event, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_data(&self) -> &TouchGrabStartData<State> {
|
||||||
|
&self.start_data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, data: &mut State) {
|
||||||
|
self.on_ungrab(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
use smithay::desktop::Window;
|
||||||
|
use smithay::input::touch::{
|
||||||
|
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent, OrientationEvent, ShapeEvent,
|
||||||
|
TouchGrab, TouchInnerHandle, UpEvent,
|
||||||
|
};
|
||||||
|
use smithay::input::SeatHandler;
|
||||||
|
use smithay::utils::{IsAlive, Logical, Point, Serial};
|
||||||
|
|
||||||
|
use crate::niri::State;
|
||||||
|
|
||||||
|
pub struct TouchResizeGrab {
|
||||||
|
start_data: TouchGrabStartData<State>,
|
||||||
|
window: Window,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TouchResizeGrab {
|
||||||
|
pub fn new(start_data: TouchGrabStartData<State>, window: Window) -> Self {
|
||||||
|
Self { start_data, window }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_ungrab(&mut self, state: &mut State) {
|
||||||
|
state.niri.layout.interactive_resize_end(&self.window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TouchGrab<State> for TouchResizeGrab {
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
|
||||||
|
event: &DownEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.down(data, None, event, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &UpEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.up(data, event, seq);
|
||||||
|
|
||||||
|
if event.slot != self.start_data.slot {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.unset_grab(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
|
||||||
|
event: &MotionEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.motion(data, None, event, seq);
|
||||||
|
|
||||||
|
if event.slot != self.start_data.slot {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.window.alive() {
|
||||||
|
let delta = event.location - self.start_data.location;
|
||||||
|
let ongoing = data
|
||||||
|
.niri
|
||||||
|
.layout
|
||||||
|
.interactive_resize_update(&self.window, delta);
|
||||||
|
if ongoing {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resize is no longer ongoing.
|
||||||
|
handle.unset_grab(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
|
||||||
|
handle.frame(data, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) {
|
||||||
|
handle.cancel(data, seq);
|
||||||
|
handle.unset_grab(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shape(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &ShapeEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.shape(data, event, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn orientation(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &OrientationEvent,
|
||||||
|
seq: Serial,
|
||||||
|
) {
|
||||||
|
handle.orientation(data, event, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_data(&self) -> &TouchGrabStartData<State> {
|
||||||
|
&self.start_data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, data: &mut State) {
|
||||||
|
self.on_ungrab(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-40
@@ -16,17 +16,14 @@ use futures_util::{select_biased, AsyncBufReadExt, AsyncWrite, AsyncWriteExt, Fu
|
|||||||
use niri_config::OutputName;
|
use niri_config::OutputName;
|
||||||
use niri_ipc::state::{EventStreamState, EventStreamStatePart as _};
|
use niri_ipc::state::{EventStreamState, EventStreamStatePart as _};
|
||||||
use niri_ipc::{Event, KeyboardLayouts, OutputConfigChanged, Reply, Request, Response, Workspace};
|
use niri_ipc::{Event, KeyboardLayouts, OutputConfigChanged, Reply, Request, Response, Workspace};
|
||||||
use smithay::input::keyboard::XkbContextHandler;
|
|
||||||
use smithay::reexports::calloop::generic::Generic;
|
use smithay::reexports::calloop::generic::Generic;
|
||||||
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction};
|
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction};
|
||||||
use smithay::reexports::rustix::fs::unlink;
|
use smithay::reexports::rustix::fs::unlink;
|
||||||
use smithay::wayland::compositor::with_states;
|
|
||||||
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
|
|
||||||
|
|
||||||
use crate::backend::IpcOutputMap;
|
use crate::backend::IpcOutputMap;
|
||||||
use crate::layout::workspace::WorkspaceId;
|
use crate::layout::workspace::WorkspaceId;
|
||||||
use crate::niri::State;
|
use crate::niri::State;
|
||||||
use crate::utils::version;
|
use crate::utils::{version, with_toplevel_role};
|
||||||
use crate::window::Mapped;
|
use crate::window::Mapped;
|
||||||
|
|
||||||
// If an event stream client fails to read events fast enough that we accumulate more than this
|
// If an event stream client fails to read events fast enough that we accumulate more than this
|
||||||
@@ -362,22 +359,12 @@ async fn handle_event_stream_client(client: EventStreamClient) -> anyhow::Result
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn make_ipc_window(mapped: &Mapped, workspace_id: Option<WorkspaceId>) -> niri_ipc::Window {
|
fn make_ipc_window(mapped: &Mapped, workspace_id: Option<WorkspaceId>) -> niri_ipc::Window {
|
||||||
let wl_surface = mapped.toplevel().wl_surface();
|
with_toplevel_role(mapped.toplevel(), |role| niri_ipc::Window {
|
||||||
with_states(wl_surface, |states| {
|
id: mapped.id().get(),
|
||||||
let role = states
|
title: role.title.clone(),
|
||||||
.data_map
|
app_id: role.app_id.clone(),
|
||||||
.get::<XdgToplevelSurfaceData>()
|
workspace_id: workspace_id.map(|id| id.get()),
|
||||||
.unwrap()
|
is_focused: mapped.is_focused(),
|
||||||
.lock()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
niri_ipc::Window {
|
|
||||||
id: mapped.id().get(),
|
|
||||||
title: role.title.clone(),
|
|
||||||
app_id: role.app_id.clone(),
|
|
||||||
workspace_id: workspace_id.map(|id| id.get()),
|
|
||||||
is_focused: mapped.is_focused(),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,10 +372,13 @@ impl State {
|
|||||||
pub fn ipc_keyboard_layouts_changed(&mut self) {
|
pub fn ipc_keyboard_layouts_changed(&mut self) {
|
||||||
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
||||||
let keyboard_layouts = keyboard.with_xkb_state(self, |context| {
|
let keyboard_layouts = keyboard.with_xkb_state(self, |context| {
|
||||||
let layouts = context.keymap().layouts();
|
let xkb = context.xkb().lock().unwrap();
|
||||||
|
let layouts = xkb.layouts();
|
||||||
KeyboardLayouts {
|
KeyboardLayouts {
|
||||||
names: layouts.map(str::to_owned).collect(),
|
names: layouts
|
||||||
current_idx: context.active_layout().0 as u8,
|
.map(|layout| xkb.layout_name(layout).to_owned())
|
||||||
|
.collect(),
|
||||||
|
current_idx: xkb.active_layout().0 as u8,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -406,7 +396,10 @@ impl State {
|
|||||||
|
|
||||||
pub fn ipc_refresh_keyboard_layout_index(&mut self) {
|
pub fn ipc_refresh_keyboard_layout_index(&mut self) {
|
||||||
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
||||||
let idx = keyboard.with_xkb_state(self, |context| context.active_layout().0 as u8);
|
let idx = keyboard.with_xkb_state(self, |context| {
|
||||||
|
let xkb = context.xkb().lock().unwrap();
|
||||||
|
xkb.active_layout().0 as u8
|
||||||
|
});
|
||||||
|
|
||||||
let Some(server) = &self.niri.ipc_server else {
|
let Some(server) = &self.niri.ipc_server else {
|
||||||
return;
|
return;
|
||||||
@@ -459,7 +452,7 @@ impl State {
|
|||||||
// Check for any changes that we can't signal as individual events.
|
// Check for any changes that we can't signal as individual events.
|
||||||
let output_name = mon.map(|mon| mon.output_name());
|
let output_name = mon.map(|mon| mon.output_name());
|
||||||
if ipc_ws.idx != u8::try_from(ws_idx + 1).unwrap_or(u8::MAX)
|
if ipc_ws.idx != u8::try_from(ws_idx + 1).unwrap_or(u8::MAX)
|
||||||
|| ipc_ws.name != ws.name
|
|| ipc_ws.name.as_ref() != ws.name()
|
||||||
|| ipc_ws.output.as_ref() != output_name
|
|| ipc_ws.output.as_ref() != output_name
|
||||||
{
|
{
|
||||||
need_workspaces_changed = true;
|
need_workspaces_changed = true;
|
||||||
@@ -482,7 +475,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if this workspace became active.
|
// Check if this workspace became active.
|
||||||
let is_active = mon.map_or(false, |mon| mon.active_workspace_idx == ws_idx);
|
let is_active = mon.map_or(false, |mon| mon.active_workspace_idx() == ws_idx);
|
||||||
if is_active && !ipc_ws.is_active {
|
if is_active && !ipc_ws.is_active {
|
||||||
events.push(Event::WorkspaceActivated { id, focused: false });
|
events.push(Event::WorkspaceActivated { id, focused: false });
|
||||||
}
|
}
|
||||||
@@ -503,9 +496,9 @@ impl State {
|
|||||||
Workspace {
|
Workspace {
|
||||||
id,
|
id,
|
||||||
idx: u8::try_from(ws_idx + 1).unwrap_or(u8::MAX),
|
idx: u8::try_from(ws_idx + 1).unwrap_or(u8::MAX),
|
||||||
name: ws.name.clone(),
|
name: ws.name().cloned(),
|
||||||
output: mon.map(|mon| mon.output_name().clone()),
|
output: mon.map(|mon| mon.output_name().clone()),
|
||||||
is_active: mon.map_or(false, |mon| mon.active_workspace_idx == ws_idx),
|
is_active: mon.map_or(false, |mon| mon.active_workspace_idx() == ws_idx),
|
||||||
is_focused: Some(id) == focused_ws_id,
|
is_focused: Some(id) == focused_ws_id,
|
||||||
active_window_id: ws.active_window().map(|win| win.id().get()),
|
active_window_id: ws.active_window().map(|win| win.id().get()),
|
||||||
}
|
}
|
||||||
@@ -546,28 +539,20 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(ipc_win) = state.windows.get(&id) else {
|
let Some(ipc_win) = state.windows.get(&id) else {
|
||||||
let window = make_ipc_window(mapped, Some(ws_id));
|
let window = make_ipc_window(mapped, ws_id);
|
||||||
events.push(Event::WindowOpenedOrChanged { window });
|
events.push(Event::WindowOpenedOrChanged { window });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace_id = Some(ws_id.get());
|
let workspace_id = ws_id.map(|id| id.get());
|
||||||
let mut changed = ipc_win.workspace_id != workspace_id;
|
let mut changed = ipc_win.workspace_id != workspace_id;
|
||||||
|
|
||||||
let wl_surface = mapped.toplevel().wl_surface();
|
changed |= with_toplevel_role(mapped.toplevel(), |role| {
|
||||||
changed |= with_states(wl_surface, |states| {
|
|
||||||
let role = states
|
|
||||||
.data_map
|
|
||||||
.get::<XdgToplevelSurfaceData>()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
ipc_win.title != role.title || ipc_win.app_id != role.app_id
|
ipc_win.title != role.title || ipc_win.app_id != role.app_id
|
||||||
});
|
});
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
let window = make_ipc_window(mapped, Some(ws_id));
|
let window = make_ipc_window(mapped, ws_id);
|
||||||
events.push(Event::WindowOpenedOrChanged { window });
|
events.push(Event::WindowOpenedOrChanged { window });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
use niri_config::{CornerRadius, FloatOrInt};
|
||||||
|
use smithay::utils::{Logical, Point, Rectangle, Size};
|
||||||
|
|
||||||
|
use super::focus_ring::{FocusRing, FocusRingRenderElement};
|
||||||
|
use crate::render_helpers::renderer::NiriRenderer;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InsertHintElement {
|
||||||
|
inner: FocusRing,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type InsertHintRenderElement = FocusRingRenderElement;
|
||||||
|
|
||||||
|
impl InsertHintElement {
|
||||||
|
pub fn new(config: niri_config::InsertHint) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: FocusRing::new(niri_config::FocusRing {
|
||||||
|
off: config.off,
|
||||||
|
width: FloatOrInt(0.),
|
||||||
|
active_color: config.color,
|
||||||
|
inactive_color: config.color,
|
||||||
|
active_gradient: config.gradient,
|
||||||
|
inactive_gradient: config.gradient,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_config(&mut self, config: niri_config::InsertHint) {
|
||||||
|
self.inner.update_config(niri_config::FocusRing {
|
||||||
|
off: config.off,
|
||||||
|
width: FloatOrInt(0.),
|
||||||
|
active_color: config.color,
|
||||||
|
inactive_color: config.color,
|
||||||
|
active_gradient: config.gradient,
|
||||||
|
inactive_gradient: config.gradient,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_shaders(&mut self) {
|
||||||
|
self.inner.update_shaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_render_elements(
|
||||||
|
&mut self,
|
||||||
|
size: Size<f64, Logical>,
|
||||||
|
view_rect: Rectangle<f64, Logical>,
|
||||||
|
radius: CornerRadius,
|
||||||
|
scale: f64,
|
||||||
|
) {
|
||||||
|
self.inner
|
||||||
|
.update_render_elements(size, true, false, view_rect, radius, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
renderer: &mut impl NiriRenderer,
|
||||||
|
location: Point<f64, Logical>,
|
||||||
|
) -> impl Iterator<Item = FocusRingRenderElement> {
|
||||||
|
self.inner.render(renderer, location)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1216
-166
File diff suppressed because it is too large
Load Diff
+210
-216
@@ -9,6 +9,7 @@ use smithay::backend::renderer::element::utils::{
|
|||||||
use smithay::output::Output;
|
use smithay::output::Output;
|
||||||
use smithay::utils::{Logical, Point, Rectangle};
|
use smithay::utils::{Logical, Point, Rectangle};
|
||||||
|
|
||||||
|
use super::tile::Tile;
|
||||||
use super::workspace::{
|
use super::workspace::{
|
||||||
compute_working_area, Column, ColumnWidth, OutputId, Workspace, WorkspaceId,
|
compute_working_area, Column, ColumnWidth, OutputId, Workspace, WorkspaceId,
|
||||||
WorkspaceRenderElement,
|
WorkspaceRenderElement,
|
||||||
@@ -20,7 +21,7 @@ use crate::render_helpers::renderer::NiriRenderer;
|
|||||||
use crate::render_helpers::RenderTarget;
|
use crate::render_helpers::RenderTarget;
|
||||||
use crate::rubber_band::RubberBand;
|
use crate::rubber_band::RubberBand;
|
||||||
use crate::utils::transaction::Transaction;
|
use crate::utils::transaction::Transaction;
|
||||||
use crate::utils::{output_size, to_physical_precise_round, ResizeEdge};
|
use crate::utils::{output_size, round_logical_in_physical, ResizeEdge};
|
||||||
|
|
||||||
/// Amount of touchpad movement to scroll the height of one workspace.
|
/// Amount of touchpad movement to scroll the height of one workspace.
|
||||||
const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.;
|
const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.;
|
||||||
@@ -33,19 +34,19 @@ const WORKSPACE_GESTURE_RUBBER_BAND: RubberBand = RubberBand {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Monitor<W: LayoutElement> {
|
pub struct Monitor<W: LayoutElement> {
|
||||||
/// Output for this monitor.
|
/// Output for this monitor.
|
||||||
pub output: Output,
|
pub(super) output: Output,
|
||||||
/// Cached name of the output.
|
/// Cached name of the output.
|
||||||
output_name: String,
|
output_name: String,
|
||||||
// Must always contain at least one.
|
// Must always contain at least one.
|
||||||
pub workspaces: Vec<Workspace<W>>,
|
pub(super) workspaces: Vec<Workspace<W>>,
|
||||||
/// Index of the currently active workspace.
|
/// Index of the currently active workspace.
|
||||||
pub active_workspace_idx: usize,
|
pub(super) active_workspace_idx: usize,
|
||||||
/// ID of the previously active workspace.
|
/// ID of the previously active workspace.
|
||||||
pub previous_workspace_id: Option<WorkspaceId>,
|
pub(super) previous_workspace_id: Option<WorkspaceId>,
|
||||||
/// In-progress switch between workspaces.
|
/// In-progress switch between workspaces.
|
||||||
pub workspace_switch: Option<WorkspaceSwitch>,
|
pub(super) workspace_switch: Option<WorkspaceSwitch>,
|
||||||
/// Configurable properties of the layout.
|
/// Configurable properties of the layout.
|
||||||
pub options: Rc<Options>,
|
pub(super) options: Rc<Options>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -59,7 +60,7 @@ pub struct WorkspaceSwitchGesture {
|
|||||||
/// Index of the workspace where the gesture was started.
|
/// Index of the workspace where the gesture was started.
|
||||||
center_idx: usize,
|
center_idx: usize,
|
||||||
/// Current, fractional workspace index.
|
/// Current, fractional workspace index.
|
||||||
pub current_idx: f64,
|
pub(super) current_idx: f64,
|
||||||
tracker: SwipeTracker,
|
tracker: SwipeTracker,
|
||||||
/// Whether the gesture is controlled by the touchpad.
|
/// Whether the gesture is controlled by the touchpad.
|
||||||
is_touchpad: bool,
|
is_touchpad: bool,
|
||||||
@@ -105,10 +106,18 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn output(&self) -> &Output {
|
||||||
|
&self.output
|
||||||
|
}
|
||||||
|
|
||||||
pub fn output_name(&self) -> &String {
|
pub fn output_name(&self) -> &String {
|
||||||
&self.output_name
|
&self.output_name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn active_workspace_idx(&self) -> usize {
|
||||||
|
self.active_workspace_idx
|
||||||
|
}
|
||||||
|
|
||||||
pub fn active_workspace_ref(&self) -> &Workspace<W> {
|
pub fn active_workspace_ref(&self) -> &Workspace<W> {
|
||||||
&self.workspaces[self.active_workspace_idx]
|
&self.workspaces[self.active_workspace_idx]
|
||||||
}
|
}
|
||||||
@@ -175,7 +184,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
) {
|
) {
|
||||||
let workspace = &mut self.workspaces[workspace_idx];
|
let workspace = &mut self.workspaces[workspace_idx];
|
||||||
|
|
||||||
workspace.add_window(window, activate, width, is_full_width);
|
workspace.add_window(None, window, activate, width, is_full_width);
|
||||||
|
|
||||||
// After adding a new window, workspace becomes this output's own.
|
// After adding a new window, workspace becomes this output's own.
|
||||||
workspace.original_output = OutputId::new(&self.output);
|
workspace.original_output = OutputId::new(&self.output);
|
||||||
@@ -209,12 +218,15 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
|
|
||||||
// After adding a new window, workspace becomes this output's own.
|
// After adding a new window, workspace becomes this output's own.
|
||||||
workspace.original_output = OutputId::new(&self.output);
|
workspace.original_output = OutputId::new(&self.output);
|
||||||
|
|
||||||
|
// Since we're adding window right of something, the workspace isn't empty, and therefore
|
||||||
|
// cannot be the last one, so we never need to insert a new empty workspace.
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_column(&mut self, workspace_idx: usize, column: Column<W>, activate: bool) {
|
pub fn add_column(&mut self, workspace_idx: usize, column: Column<W>, activate: bool) {
|
||||||
let workspace = &mut self.workspaces[workspace_idx];
|
let workspace = &mut self.workspaces[workspace_idx];
|
||||||
|
|
||||||
workspace.add_column(column, activate);
|
workspace.add_column(None, column, activate, None);
|
||||||
|
|
||||||
// After adding a new window, workspace becomes this output's own.
|
// After adding a new window, workspace becomes this output's own.
|
||||||
workspace.original_output = OutputId::new(&self.output);
|
workspace.original_output = OutputId::new(&self.output);
|
||||||
@@ -230,6 +242,56 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_tile(
|
||||||
|
&mut self,
|
||||||
|
workspace_idx: usize,
|
||||||
|
column_idx: Option<usize>,
|
||||||
|
tile: Tile<W>,
|
||||||
|
activate: bool,
|
||||||
|
width: ColumnWidth,
|
||||||
|
is_full_width: bool,
|
||||||
|
) {
|
||||||
|
let workspace = &mut self.workspaces[workspace_idx];
|
||||||
|
|
||||||
|
workspace.add_tile(column_idx, tile, activate, width, is_full_width, None);
|
||||||
|
|
||||||
|
// After adding a new window, workspace becomes this output's own.
|
||||||
|
workspace.original_output = OutputId::new(&self.output);
|
||||||
|
|
||||||
|
if workspace_idx == self.workspaces.len() - 1 {
|
||||||
|
// Insert a new empty workspace.
|
||||||
|
let ws = Workspace::new(self.output.clone(), self.options.clone());
|
||||||
|
self.workspaces.push(ws);
|
||||||
|
}
|
||||||
|
|
||||||
|
if activate {
|
||||||
|
self.activate_workspace(workspace_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_tile_to_column(
|
||||||
|
&mut self,
|
||||||
|
workspace_idx: usize,
|
||||||
|
column_idx: usize,
|
||||||
|
tile_idx: Option<usize>,
|
||||||
|
tile: Tile<W>,
|
||||||
|
activate: bool,
|
||||||
|
) {
|
||||||
|
let workspace = &mut self.workspaces[workspace_idx];
|
||||||
|
|
||||||
|
workspace.add_tile_to_column(column_idx, tile_idx, tile, activate);
|
||||||
|
|
||||||
|
// After adding a new window, workspace becomes this output's own.
|
||||||
|
workspace.original_output = OutputId::new(&self.output);
|
||||||
|
|
||||||
|
// Since we're adding window to an existing column, the workspace isn't empty, and
|
||||||
|
// therefore cannot be the last one, so we never need to insert a new empty workspace.
|
||||||
|
|
||||||
|
if activate {
|
||||||
|
self.activate_workspace(workspace_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clean_up_workspaces(&mut self) {
|
pub fn clean_up_workspaces(&mut self) {
|
||||||
assert!(self.workspace_switch.is_none());
|
assert!(self.workspace_switch.is_none());
|
||||||
|
|
||||||
@@ -314,14 +376,6 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn consume_or_expel_window_left(&mut self) {
|
|
||||||
self.active_workspace().consume_or_expel_window_left();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn consume_or_expel_window_right(&mut self) {
|
|
||||||
self.active_workspace().consume_or_expel_window_right();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus_left(&mut self) {
|
pub fn focus_left(&mut self) {
|
||||||
self.active_workspace().focus_left();
|
self.active_workspace().focus_left();
|
||||||
}
|
}
|
||||||
@@ -403,7 +457,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||||
let new_idx = curr_idx.saturating_sub(1);
|
let new_idx = curr_idx.saturating_sub(1);
|
||||||
if curr_idx == new_idx {
|
if curr_idx == new_idx {
|
||||||
self.focus_left();
|
self.focus_right();
|
||||||
} else {
|
} else {
|
||||||
workspace.focus_up();
|
workspace.focus_up();
|
||||||
}
|
}
|
||||||
@@ -455,18 +509,20 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let column = &workspace.columns[workspace.active_column_idx];
|
let column = &workspace.columns[workspace.active_column_idx];
|
||||||
let width = column.width;
|
let removed = workspace.remove_tile_by_idx(
|
||||||
let is_full_width = column.is_full_width;
|
workspace.active_column_idx,
|
||||||
let window = workspace
|
column.active_tile_idx,
|
||||||
.remove_tile_by_idx(
|
Transaction::new(),
|
||||||
workspace.active_column_idx,
|
None,
|
||||||
column.active_tile_idx,
|
);
|
||||||
Transaction::new(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.into_window();
|
|
||||||
|
|
||||||
self.add_window(new_idx, window, true, width, is_full_width);
|
self.add_window(
|
||||||
|
new_idx,
|
||||||
|
removed.tile.into_window(),
|
||||||
|
true,
|
||||||
|
removed.width,
|
||||||
|
removed.is_full_width,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_to_workspace_down(&mut self) {
|
pub fn move_to_workspace_down(&mut self) {
|
||||||
@@ -483,18 +539,20 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let column = &workspace.columns[workspace.active_column_idx];
|
let column = &workspace.columns[workspace.active_column_idx];
|
||||||
let width = column.width;
|
let removed = workspace.remove_tile_by_idx(
|
||||||
let is_full_width = column.is_full_width;
|
workspace.active_column_idx,
|
||||||
let window = workspace
|
column.active_tile_idx,
|
||||||
.remove_tile_by_idx(
|
Transaction::new(),
|
||||||
workspace.active_column_idx,
|
None,
|
||||||
column.active_tile_idx,
|
);
|
||||||
Transaction::new(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.into_window();
|
|
||||||
|
|
||||||
self.add_window(new_idx, window, true, width, is_full_width);
|
self.add_window(
|
||||||
|
new_idx,
|
||||||
|
removed.tile.into_window(),
|
||||||
|
true,
|
||||||
|
removed.width,
|
||||||
|
removed.is_full_width,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) {
|
pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) {
|
||||||
@@ -531,17 +589,19 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
|
|
||||||
let workspace = &mut self.workspaces[source_workspace_idx];
|
let workspace = &mut self.workspaces[source_workspace_idx];
|
||||||
let column = &workspace.columns[col_idx];
|
let column = &workspace.columns[col_idx];
|
||||||
let width = column.width;
|
|
||||||
let is_full_width = column.is_full_width;
|
|
||||||
let activate = source_workspace_idx == self.active_workspace_idx
|
let activate = source_workspace_idx == self.active_workspace_idx
|
||||||
&& col_idx == workspace.active_column_idx
|
&& col_idx == workspace.active_column_idx
|
||||||
&& tile_idx == column.active_tile_idx;
|
&& tile_idx == column.active_tile_idx;
|
||||||
|
|
||||||
let window = workspace
|
let removed = workspace.remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None);
|
||||||
.remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None)
|
|
||||||
.into_window();
|
|
||||||
|
|
||||||
self.add_window(new_idx, window, activate, width, is_full_width);
|
self.add_window(
|
||||||
|
new_idx,
|
||||||
|
removed.tile.into_window(),
|
||||||
|
activate,
|
||||||
|
removed.width,
|
||||||
|
removed.is_full_width,
|
||||||
|
);
|
||||||
|
|
||||||
if self.workspace_switch.is_none() {
|
if self.workspace_switch.is_none() {
|
||||||
self.clean_up_workspaces();
|
self.clean_up_workspaces();
|
||||||
@@ -561,7 +621,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let column = workspace.remove_column_by_idx(workspace.active_column_idx);
|
let column = workspace.remove_column_by_idx(workspace.active_column_idx, None);
|
||||||
self.add_column(new_idx, column, true);
|
self.add_column(new_idx, column, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,7 +638,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let column = workspace.remove_column_by_idx(workspace.active_column_idx);
|
let column = workspace.remove_column_by_idx(workspace.active_column_idx, None);
|
||||||
self.add_column(new_idx, column, true);
|
self.add_column(new_idx, column, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,7 +655,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let column = workspace.remove_column_by_idx(workspace.active_column_idx);
|
let column = workspace.remove_column_by_idx(workspace.active_column_idx, None);
|
||||||
self.add_column(new_idx, column, true);
|
self.add_column(new_idx, column, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,7 +733,7 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn are_animations_ongoing(&self) -> bool {
|
pub(super) fn are_animations_ongoing(&self) -> bool {
|
||||||
self.workspace_switch
|
self.workspace_switch
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|s| s.is_animation())
|
.is_some_and(|s| s.is_animation())
|
||||||
@@ -811,90 +871,75 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
Some(rect)
|
Some(rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn workspaces_with_render_positions(
|
||||||
|
&self,
|
||||||
|
) -> impl Iterator<Item = (&Workspace<W>, Point<f64, Logical>)> {
|
||||||
|
let mut first = None;
|
||||||
|
let mut second = None;
|
||||||
|
|
||||||
|
match &self.workspace_switch {
|
||||||
|
Some(switch) => {
|
||||||
|
let render_idx = switch.current_idx();
|
||||||
|
let before_idx = render_idx.floor();
|
||||||
|
let after_idx = render_idx.ceil();
|
||||||
|
|
||||||
|
if after_idx >= 0. && before_idx < self.workspaces.len() as f64 {
|
||||||
|
let scale = self.output.current_scale().fractional_scale();
|
||||||
|
let size = output_size(&self.output);
|
||||||
|
let offset =
|
||||||
|
round_logical_in_physical(scale, (render_idx - before_idx) * size.h);
|
||||||
|
|
||||||
|
// Ceil the height in physical pixels.
|
||||||
|
let height = (size.h * scale).ceil() / scale;
|
||||||
|
|
||||||
|
if before_idx >= 0. {
|
||||||
|
let before_idx = before_idx as usize;
|
||||||
|
let before_offset = Point::from((0., -offset));
|
||||||
|
first = Some((&self.workspaces[before_idx], before_offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
let after_idx = after_idx as usize;
|
||||||
|
if after_idx < self.workspaces.len() {
|
||||||
|
let after_offset = Point::from((0., -offset + height));
|
||||||
|
second = Some((&self.workspaces[after_idx], after_offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
first = Some((
|
||||||
|
&self.workspaces[self.active_workspace_idx],
|
||||||
|
Point::from((0., 0.)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
first.into_iter().chain(second)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn workspace_under(
|
||||||
|
&self,
|
||||||
|
pos_within_output: Point<f64, Logical>,
|
||||||
|
) -> Option<(&Workspace<W>, Point<f64, Logical>)> {
|
||||||
|
let size = output_size(&self.output);
|
||||||
|
let (ws, bounds) = self
|
||||||
|
.workspaces_with_render_positions()
|
||||||
|
.map(|(ws, offset)| (ws, Rectangle::from_loc_and_size(offset, size)))
|
||||||
|
.find(|(_, bounds)| bounds.contains(pos_within_output))?;
|
||||||
|
Some((ws, bounds.loc))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn window_under(
|
pub fn window_under(
|
||||||
&self,
|
&self,
|
||||||
pos_within_output: Point<f64, Logical>,
|
pos_within_output: Point<f64, Logical>,
|
||||||
) -> Option<(&W, Option<Point<f64, Logical>>)> {
|
) -> Option<(&W, Option<Point<f64, Logical>>)> {
|
||||||
match &self.workspace_switch {
|
let (ws, offset) = self.workspace_under(pos_within_output)?;
|
||||||
Some(switch) => {
|
let (win, win_pos) = ws.window_under(pos_within_output - offset)?;
|
||||||
let size = output_size(&self.output).to_f64();
|
Some((win, win_pos.map(|p| p + offset)))
|
||||||
|
|
||||||
let render_idx = switch.current_idx();
|
|
||||||
let before_idx = render_idx.floor();
|
|
||||||
let after_idx = render_idx.ceil();
|
|
||||||
|
|
||||||
let offset = (render_idx - before_idx) * size.h;
|
|
||||||
|
|
||||||
if after_idx < 0. || before_idx as usize >= self.workspaces.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let after_idx = after_idx as usize;
|
|
||||||
|
|
||||||
let (idx, ws_offset) = if pos_within_output.y < size.h - offset {
|
|
||||||
if before_idx < 0. {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
(before_idx as usize, Point::from((0., offset)))
|
|
||||||
} else {
|
|
||||||
if after_idx >= self.workspaces.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
(after_idx, Point::from((0., -size.h + offset)))
|
|
||||||
};
|
|
||||||
|
|
||||||
let ws = &self.workspaces[idx];
|
|
||||||
let (win, win_pos) = ws.window_under(pos_within_output + ws_offset)?;
|
|
||||||
Some((win, win_pos.map(|p| p - ws_offset)))
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let ws = &self.workspaces[self.active_workspace_idx];
|
|
||||||
ws.window_under(pos_within_output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize_edges_under(&self, pos_within_output: Point<f64, Logical>) -> Option<ResizeEdge> {
|
pub fn resize_edges_under(&self, pos_within_output: Point<f64, Logical>) -> Option<ResizeEdge> {
|
||||||
match &self.workspace_switch {
|
let (ws, offset) = self.workspace_under(pos_within_output)?;
|
||||||
Some(switch) => {
|
ws.resize_edges_under(pos_within_output - offset)
|
||||||
let size = output_size(&self.output);
|
|
||||||
|
|
||||||
let render_idx = switch.current_idx();
|
|
||||||
let before_idx = render_idx.floor();
|
|
||||||
let after_idx = render_idx.ceil();
|
|
||||||
|
|
||||||
let offset = (render_idx - before_idx) * size.h;
|
|
||||||
|
|
||||||
if after_idx < 0. || before_idx as usize >= self.workspaces.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let after_idx = after_idx as usize;
|
|
||||||
|
|
||||||
let (idx, ws_offset) = if pos_within_output.y < size.h - offset {
|
|
||||||
if before_idx < 0. {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
(before_idx as usize, Point::from((0., offset)))
|
|
||||||
} else {
|
|
||||||
if after_idx >= self.workspaces.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
(after_idx, Point::from((0., -size.h + offset)))
|
|
||||||
};
|
|
||||||
|
|
||||||
let ws = &self.workspaces[idx];
|
|
||||||
ws.resize_edges_under(pos_within_output + ws_offset)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let ws = &self.workspaces[self.active_workspace_idx];
|
|
||||||
ws.resize_edges_under(pos_within_output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_above_top_layer(&self) -> bool {
|
pub fn render_above_top_layer(&self) -> bool {
|
||||||
@@ -907,103 +952,52 @@ impl<W: LayoutElement> Monitor<W> {
|
|||||||
ws.render_above_top_layer()
|
ws.render_above_top_layer()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_elements<R: NiriRenderer>(
|
pub fn render_elements<'a, R: NiriRenderer>(
|
||||||
&self,
|
&'a self,
|
||||||
renderer: &mut R,
|
renderer: &'a mut R,
|
||||||
target: RenderTarget,
|
target: RenderTarget,
|
||||||
) -> Vec<MonitorRenderElement<R>> {
|
) -> impl Iterator<Item = MonitorRenderElement<R>> + '_ {
|
||||||
let _span = tracy_client::span!("Monitor::render_elements");
|
let _span = tracy_client::span!("Monitor::render_elements");
|
||||||
|
|
||||||
let scale = self.output.current_scale().fractional_scale();
|
let scale = self.output.current_scale().fractional_scale();
|
||||||
let size = output_size(&self.output);
|
let size = output_size(&self.output);
|
||||||
|
// Ceil the height in physical pixels.
|
||||||
|
let height = (size.h * scale).ceil() as i32;
|
||||||
|
|
||||||
match &self.workspace_switch {
|
// Crop the elements to prevent them overflowing, currently visible during a workspace
|
||||||
Some(switch) => {
|
// switch.
|
||||||
let render_idx = switch.current_idx();
|
//
|
||||||
let before_idx = render_idx.floor();
|
// HACK: crop to infinite bounds at least horizontally where we
|
||||||
let after_idx = render_idx.ceil();
|
// know there's no workspace joining or monitor bounds, otherwise
|
||||||
|
// it will cut pixel shaders and mess up the coordinate space.
|
||||||
|
// There's also a damage tracking bug which causes glitched
|
||||||
|
// rendering for maximized GTK windows.
|
||||||
|
//
|
||||||
|
// FIXME: use proper bounds after fixing the Crop element.
|
||||||
|
let crop_bounds = if self.workspace_switch.is_some() {
|
||||||
|
Rectangle::from_loc_and_size((-i32::MAX / 2, 0), (i32::MAX, height))
|
||||||
|
} else {
|
||||||
|
Rectangle::from_loc_and_size((-i32::MAX / 2, -i32::MAX / 2), (i32::MAX, i32::MAX))
|
||||||
|
};
|
||||||
|
|
||||||
let offset = (render_idx - before_idx) * size.h;
|
self.workspaces_with_render_positions()
|
||||||
|
.flat_map(move |(ws, offset)| {
|
||||||
if after_idx < 0. || before_idx as usize >= self.workspaces.len() {
|
ws.render_elements(renderer, target)
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
let after_idx = after_idx as usize;
|
|
||||||
let after = if after_idx < self.workspaces.len() {
|
|
||||||
let after = self.workspaces[after_idx].render_elements(renderer, target);
|
|
||||||
let after = after.into_iter().filter_map(|elem| {
|
|
||||||
Some(RelocateRenderElement::from_element(
|
|
||||||
CropRenderElement::from_element(
|
|
||||||
elem,
|
|
||||||
scale,
|
|
||||||
// HACK: crop to infinite bounds for all sides except the side
|
|
||||||
// where the workspaces join,
|
|
||||||
// otherwise it will cut pixel shaders and mess up
|
|
||||||
// the coordinate space.
|
|
||||||
Rectangle::from_extemities(
|
|
||||||
(-i32::MAX / 2, 0),
|
|
||||||
(i32::MAX / 2, i32::MAX / 2),
|
|
||||||
),
|
|
||||||
)?,
|
|
||||||
Point::from((0., -offset + size.h)).to_physical_precise_round(scale),
|
|
||||||
Relocate::Relative,
|
|
||||||
))
|
|
||||||
});
|
|
||||||
|
|
||||||
if before_idx < 0. {
|
|
||||||
return after.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(after)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let before_idx = before_idx as usize;
|
|
||||||
let before = self.workspaces[before_idx].render_elements(renderer, target);
|
|
||||||
let before = before.into_iter().filter_map(|elem| {
|
|
||||||
Some(RelocateRenderElement::from_element(
|
|
||||||
CropRenderElement::from_element(
|
|
||||||
elem,
|
|
||||||
scale,
|
|
||||||
Rectangle::from_extemities(
|
|
||||||
(-i32::MAX / 2, -i32::MAX / 2),
|
|
||||||
(i32::MAX / 2, to_physical_precise_round(scale, size.h)),
|
|
||||||
),
|
|
||||||
)?,
|
|
||||||
Point::from((0., -offset)).to_physical_precise_round(scale),
|
|
||||||
Relocate::Relative,
|
|
||||||
))
|
|
||||||
});
|
|
||||||
before.chain(after.into_iter().flatten()).collect()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let elements =
|
|
||||||
self.workspaces[self.active_workspace_idx].render_elements(renderer, target);
|
|
||||||
elements
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|elem| {
|
.filter_map(move |elem| {
|
||||||
Some(RelocateRenderElement::from_element(
|
CropRenderElement::from_element(elem, scale, crop_bounds)
|
||||||
CropRenderElement::from_element(
|
|
||||||
elem,
|
|
||||||
scale,
|
|
||||||
// HACK: set infinite crop bounds due to a damage tracking bug
|
|
||||||
// which causes glitched rendering for maximized GTK windows.
|
|
||||||
// FIXME: use proper bounds after fixing the Crop element.
|
|
||||||
Rectangle::from_loc_and_size(
|
|
||||||
(-i32::MAX / 2, -i32::MAX / 2),
|
|
||||||
(i32::MAX, i32::MAX),
|
|
||||||
),
|
|
||||||
// Rectangle::from_loc_and_size((0, 0), size),
|
|
||||||
)?,
|
|
||||||
(0, 0),
|
|
||||||
Relocate::Relative,
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
.collect()
|
.map(move |elem| {
|
||||||
}
|
RelocateRenderElement::from_element(
|
||||||
}
|
elem,
|
||||||
|
// The offset we get from workspaces_with_render_positions() is already
|
||||||
|
// rounded to physical pixels, but it's in the logical coordinate
|
||||||
|
// space, so we need to convert it to physical.
|
||||||
|
offset.to_physical_precise_round(scale),
|
||||||
|
Relocate::Relative,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn workspace_switch_gesture_begin(&mut self, is_touchpad: bool) {
|
pub fn workspace_switch_gesture_begin(&mut self, is_touchpad: bool) {
|
||||||
|
|||||||
+14
-3
@@ -64,6 +64,9 @@ pub struct Tile<W: LayoutElement> {
|
|||||||
/// The animation of a tile visually moving vertically.
|
/// The animation of a tile visually moving vertically.
|
||||||
move_y_animation: Option<MoveAnimation>,
|
move_y_animation: Option<MoveAnimation>,
|
||||||
|
|
||||||
|
/// Offset during the initial interactive move rubberband.
|
||||||
|
pub(super) interactive_move_offset: Point<f64, Logical>,
|
||||||
|
|
||||||
/// Snapshot of the last render for use in the close animation.
|
/// Snapshot of the last render for use in the close animation.
|
||||||
unmap_snapshot: Option<TileRenderSnapshot>,
|
unmap_snapshot: Option<TileRenderSnapshot>,
|
||||||
|
|
||||||
@@ -74,7 +77,7 @@ pub struct Tile<W: LayoutElement> {
|
|||||||
scale: f64,
|
scale: f64,
|
||||||
|
|
||||||
/// Configurable properties of the layout.
|
/// Configurable properties of the layout.
|
||||||
pub options: Rc<Options>,
|
pub(super) options: Rc<Options>,
|
||||||
}
|
}
|
||||||
|
|
||||||
niri_render_elements! {
|
niri_render_elements! {
|
||||||
@@ -90,7 +93,7 @@ niri_render_elements! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TileRenderSnapshot =
|
pub type TileRenderSnapshot =
|
||||||
RenderSnapshot<TileRenderElement<GlesRenderer>, TileRenderElement<GlesRenderer>>;
|
RenderSnapshot<TileRenderElement<GlesRenderer>, TileRenderElement<GlesRenderer>>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -123,6 +126,7 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
resize_animation: None,
|
resize_animation: None,
|
||||||
move_x_animation: None,
|
move_x_animation: None,
|
||||||
move_y_animation: None,
|
move_y_animation: None,
|
||||||
|
interactive_move_offset: Point::from((0., 0.)),
|
||||||
unmap_snapshot: None,
|
unmap_snapshot: None,
|
||||||
rounded_corner_damage: Default::default(),
|
rounded_corner_damage: Default::default(),
|
||||||
scale,
|
scale,
|
||||||
@@ -305,6 +309,8 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
offset.y += move_.from * move_.anim.value();
|
offset.y += move_.from * move_.anim.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset += self.interactive_move_offset;
|
||||||
|
|
||||||
offset
|
offset
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,6 +370,11 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stop_move_animations(&mut self) {
|
||||||
|
self.move_x_animation = None;
|
||||||
|
self.move_y_animation = None;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn window(&self) -> &W {
|
pub fn window(&self) -> &W {
|
||||||
&self.window
|
&self.window
|
||||||
}
|
}
|
||||||
@@ -381,7 +392,7 @@ impl<W: LayoutElement> Tile<W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `None` if the border is hidden and `Some(width)` if it should be shown.
|
/// Returns `None` if the border is hidden and `Some(width)` if it should be shown.
|
||||||
fn effective_border_width(&self) -> Option<f64> {
|
pub fn effective_border_width(&self) -> Option<f64> {
|
||||||
if self.is_fullscreen {
|
if self.is_fullscreen {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
+606
-425
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,11 @@ use tracing_subscriber::EnvFilter;
|
|||||||
|
|
||||||
const DEFAULT_LOG_FILTER: &str = "niri=debug,smithay::backend::renderer::gles=error";
|
const DEFAULT_LOG_FILTER: &str = "niri=debug,smithay::backend::renderer::gles=error";
|
||||||
|
|
||||||
|
#[cfg(feature = "profile-with-tracy-allocations")]
|
||||||
|
#[global_allocator]
|
||||||
|
static GLOBAL: tracy_client::ProfiledAllocator<std::alloc::System> =
|
||||||
|
tracy_client::ProfiledAllocator::new(std::alloc::System, 100);
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
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() {
|
||||||
|
|||||||
+225
-104
@@ -17,6 +17,7 @@ use niri_config::{
|
|||||||
DEFAULT_BACKGROUND_COLOR,
|
DEFAULT_BACKGROUND_COLOR,
|
||||||
};
|
};
|
||||||
use smithay::backend::allocator::Fourcc;
|
use smithay::backend::allocator::Fourcc;
|
||||||
|
use smithay::backend::input::Keycode;
|
||||||
use smithay::backend::renderer::damage::OutputDamageTracker;
|
use smithay::backend::renderer::damage::OutputDamageTracker;
|
||||||
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
|
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
|
||||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||||
@@ -43,7 +44,7 @@ use smithay::desktop::{
|
|||||||
layer_map_for_output, LayerSurface, PopupGrab, PopupManager, PopupUngrabStrategy, Space,
|
layer_map_for_output, LayerSurface, PopupGrab, PopupManager, PopupUngrabStrategy, Space,
|
||||||
Window, WindowSurfaceType,
|
Window, WindowSurfaceType,
|
||||||
};
|
};
|
||||||
use smithay::input::keyboard::{Layout as KeyboardLayout, XkbContextHandler};
|
use smithay::input::keyboard::Layout as KeyboardLayout;
|
||||||
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::{self, Output, OutputModeSource, PhysicalProperties, Subpixel};
|
use smithay::output::{self, Output, OutputModeSource, PhysicalProperties, Subpixel};
|
||||||
@@ -109,12 +110,13 @@ use crate::dbus::gnome_shell_screenshot::{NiriToScreenshot, ScreenshotToNiri};
|
|||||||
#[cfg(feature = "xdp-gnome-screencast")]
|
#[cfg(feature = "xdp-gnome-screencast")]
|
||||||
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, XDG_ACTIVATION_TOKEN_TIMEOUT};
|
||||||
use crate::input::scroll_tracker::ScrollTracker;
|
use crate::input::scroll_tracker::ScrollTracker;
|
||||||
use crate::input::{
|
use crate::input::{
|
||||||
apply_libinput_settings, mods_with_finger_scroll_binds, mods_with_wheel_binds, TabletData,
|
apply_libinput_settings, mods_with_finger_scroll_binds, mods_with_wheel_binds, TabletData,
|
||||||
};
|
};
|
||||||
use crate::ipc::server::IpcServer;
|
use crate::ipc::server::IpcServer;
|
||||||
|
use crate::layout::tile::TileRenderElement;
|
||||||
use crate::layout::workspace::WorkspaceId;
|
use crate::layout::workspace::WorkspaceId;
|
||||||
use crate::layout::{Layout, LayoutElement as _, MonitorRenderElement};
|
use crate::layout::{Layout, LayoutElement as _, MonitorRenderElement};
|
||||||
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
||||||
@@ -205,6 +207,12 @@ 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,
|
||||||
|
|
||||||
|
/// Whether the laptop lid is closed.
|
||||||
|
///
|
||||||
|
/// Libinput guarantees that the lid switch starts in open state, and if it was closed during
|
||||||
|
/// startup, libinput will immediately send a closed event.
|
||||||
|
pub is_lid_closed: bool,
|
||||||
|
|
||||||
pub devices: HashSet<input::Device>,
|
pub devices: HashSet<input::Device>,
|
||||||
pub tablets: HashMap<input::Device, TabletData>,
|
pub tablets: HashMap<input::Device, TabletData>,
|
||||||
pub touch: HashSet<input::Device>,
|
pub touch: HashSet<input::Device>,
|
||||||
@@ -248,7 +256,7 @@ pub struct Niri {
|
|||||||
|
|
||||||
pub seat: Seat<State>,
|
pub seat: Seat<State>,
|
||||||
/// Scancodes of the keys to suppress.
|
/// Scancodes of the keys to suppress.
|
||||||
pub suppressed_keys: HashSet<u32>,
|
pub suppressed_keys: HashSet<Keycode>,
|
||||||
pub bind_cooldown_timers: HashMap<Key, RegistrationToken>,
|
pub bind_cooldown_timers: HashMap<Key, RegistrationToken>,
|
||||||
pub bind_repeat_timer: Option<RegistrationToken>,
|
pub bind_repeat_timer: Option<RegistrationToken>,
|
||||||
pub keyboard_focus: KeyboardFocus,
|
pub keyboard_focus: KeyboardFocus,
|
||||||
@@ -259,15 +267,27 @@ pub struct Niri {
|
|||||||
pub cursor_manager: CursorManager,
|
pub cursor_manager: CursorManager,
|
||||||
pub cursor_texture_cache: CursorTextureCache,
|
pub cursor_texture_cache: CursorTextureCache,
|
||||||
pub cursor_shape_manager_state: CursorShapeManagerState,
|
pub cursor_shape_manager_state: CursorShapeManagerState,
|
||||||
pub dnd_icon: Option<WlSurface>,
|
pub dnd_icon: Option<DndIcon>,
|
||||||
pub pointer_focus: PointerFocus,
|
/// Contents under pointer.
|
||||||
|
///
|
||||||
|
/// Periodically updated: on motion and other events and in the loop callback. If you require
|
||||||
|
/// the real up-to-date contents somewhere, it's better to recompute on the spot.
|
||||||
|
///
|
||||||
|
/// This is not pointer focus. I.e. during a click grab, the pointer focus remains on the
|
||||||
|
/// client with the grab, but this field will keep updating to the latest contents as if no
|
||||||
|
/// grab was active.
|
||||||
|
///
|
||||||
|
/// This is primarily useful for emitting pointer motion events for surfaces that move
|
||||||
|
/// underneath the cursor on their own (i.e. when the tiling layout moves). In this case, not
|
||||||
|
/// taking grabs into account is expected, because we pass the information to pointer.motion()
|
||||||
|
/// which passes it down through grabs, which decide what to do with it as they see fit.
|
||||||
|
pub pointer_contents: PointContents,
|
||||||
/// Whether the pointer is hidden, for example due to a previous touch input.
|
/// Whether the pointer is hidden, for example due to a previous touch input.
|
||||||
///
|
///
|
||||||
/// When this happens, the pointer also loses any focus. This is so that touch can prevent
|
/// When this happens, the pointer also loses any focus. This is so that touch can prevent
|
||||||
/// various tooltips from sticking around.
|
/// various tooltips from sticking around.
|
||||||
pub pointer_hidden: bool,
|
pub pointer_hidden: bool,
|
||||||
// FIXME: this should be able to be removed once PointerFocus takes grabs into account.
|
pub pointer_inactivity_timer: Option<RegistrationToken>,
|
||||||
pub pointer_grab_ongoing: bool,
|
|
||||||
pub tablet_cursor_location: Option<Point<f64, Logical>>,
|
pub tablet_cursor_location: Option<Point<f64, Logical>>,
|
||||||
pub gesture_swipe_3f_cumulative: Option<(f64, f64)>,
|
pub gesture_swipe_3f_cumulative: Option<(f64, f64)>,
|
||||||
pub vertical_wheel_tracker: ScrollTracker,
|
pub vertical_wheel_tracker: ScrollTracker,
|
||||||
@@ -304,6 +324,12 @@ pub struct Niri {
|
|||||||
pub mapped_cast_output: HashMap<Window, Output>,
|
pub mapped_cast_output: HashMap<Window, Output>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DndIcon {
|
||||||
|
pub surface: WlSurface,
|
||||||
|
pub offset: Point<i32, Logical>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OutputState {
|
pub struct OutputState {
|
||||||
pub global: GlobalId,
|
pub global: GlobalId,
|
||||||
pub frame_clock: FrameClock,
|
pub frame_clock: FrameClock,
|
||||||
@@ -377,10 +403,10 @@ pub enum KeyboardFocus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct PointerFocus {
|
pub struct PointContents {
|
||||||
// Output under pointer.
|
// Output under point.
|
||||||
pub output: Option<Output>,
|
pub output: Option<Output>,
|
||||||
// Surface under pointer and its location in global coordinate space.
|
// Surface under point and its location in the global coordinate space.
|
||||||
pub surface: Option<(WlSurface, Point<f64, Logical>)>,
|
pub surface: Option<(WlSurface, Point<f64, Logical>)>,
|
||||||
// If surface belongs to a window, this is that window.
|
// If surface belongs to a window, this is that window.
|
||||||
pub window: Option<Window>,
|
pub window: Option<Window>,
|
||||||
@@ -533,15 +559,18 @@ impl State {
|
|||||||
self.notify_blocker_cleared();
|
self.notify_blocker_cleared();
|
||||||
|
|
||||||
// These should be called periodically, before flushing the clients.
|
// These should be called periodically, before flushing the clients.
|
||||||
self.niri.layout.refresh();
|
|
||||||
self.niri.cursor_manager.check_cursor_image_surface_alive();
|
|
||||||
self.niri.refresh_pointer_outputs();
|
|
||||||
self.niri.popups.cleanup();
|
self.niri.popups.cleanup();
|
||||||
self.niri.global_space.refresh();
|
|
||||||
self.niri.refresh_idle_inhibit();
|
|
||||||
self.refresh_popup_grab();
|
self.refresh_popup_grab();
|
||||||
self.update_keyboard_focus();
|
self.update_keyboard_focus();
|
||||||
self.refresh_pointer_focus();
|
|
||||||
|
// Needs to be called after updating the keyboard focus.
|
||||||
|
self.niri.refresh_layout();
|
||||||
|
|
||||||
|
self.niri.cursor_manager.check_cursor_image_surface_alive();
|
||||||
|
self.niri.refresh_pointer_outputs();
|
||||||
|
self.niri.global_space.refresh();
|
||||||
|
self.niri.refresh_idle_inhibit();
|
||||||
|
self.refresh_pointer_contents();
|
||||||
foreign_toplevel::refresh(self);
|
foreign_toplevel::refresh(self);
|
||||||
self.niri.refresh_window_rules();
|
self.niri.refresh_window_rules();
|
||||||
self.refresh_ipc_outputs();
|
self.refresh_ipc_outputs();
|
||||||
@@ -562,10 +591,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.contents_under(location);
|
||||||
self.niri
|
self.niri.pointer_contents.clone_from(&under);
|
||||||
.maybe_activate_pointer_constraint(location, &under);
|
|
||||||
self.niri.pointer_focus.clone_from(&under);
|
|
||||||
|
|
||||||
let pointer = &self.niri.seat.get_pointer().unwrap();
|
let pointer = &self.niri.seat.get_pointer().unwrap();
|
||||||
pointer.motion(
|
pointer.motion(
|
||||||
@@ -579,8 +606,9 @@ impl State {
|
|||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
|
|
||||||
// We moved the pointer, show it.
|
self.niri.maybe_activate_pointer_constraint();
|
||||||
self.niri.pointer_hidden = false;
|
|
||||||
|
// We do not show the pointer on programmatic or keyboard movement.
|
||||||
|
|
||||||
// FIXME: granular
|
// FIXME: granular
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
@@ -634,14 +662,13 @@ impl State {
|
|||||||
let Some(output) = self.niri.layout.active_output() else {
|
let Some(output) = self.niri.layout.active_output() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let output = output.clone();
|
let monitor = self.niri.layout.monitor_for_output(output).unwrap();
|
||||||
let monitor = self.niri.layout.monitor_for_output(&output).unwrap();
|
|
||||||
|
|
||||||
let mut rv = false;
|
let mut rv = false;
|
||||||
let rect = monitor.active_tile_visual_rectangle();
|
let rect = monitor.active_tile_visual_rectangle();
|
||||||
|
|
||||||
if let Some(rect) = rect {
|
if let Some(rect) = rect {
|
||||||
let output_geo = self.niri.global_space.output_geometry(&output).unwrap();
|
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||||
let mut rect = rect;
|
let mut rect = rect;
|
||||||
rect.loc += output_geo.loc.to_f64();
|
rect.loc += output_geo.loc.to_f64();
|
||||||
rv = self.move_cursor_to_rect(rect, mode);
|
rv = self.move_cursor_to_rect(rect, mode);
|
||||||
@@ -666,8 +693,8 @@ impl State {
|
|||||||
self.move_cursor_to_focused_tile(CenterCoords::Both)
|
self.move_cursor_to_focused_tile(CenterCoords::Both)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_pointer_focus(&mut self) {
|
pub fn refresh_pointer_contents(&mut self) {
|
||||||
let _span = tracy_client::span!("Niri::refresh_pointer_focus");
|
let _span = tracy_client::span!("Niri::refresh_pointer_contents");
|
||||||
|
|
||||||
let pointer = &self.niri.seat.get_pointer().unwrap();
|
let pointer = &self.niri.seat.get_pointer().unwrap();
|
||||||
let location = pointer.current_location();
|
let location = pointer.current_location();
|
||||||
@@ -682,36 +709,37 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.update_pointer_focus() {
|
if !self.update_pointer_contents() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
|
|
||||||
|
// Pointer motion from a surface to nothing triggers a cursor change to default, which
|
||||||
|
// means we may need to redraw.
|
||||||
|
|
||||||
// FIXME: granular
|
// FIXME: granular
|
||||||
self.niri.queue_redraw_all();
|
self.niri.queue_redraw_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_pointer_focus(&mut self) -> bool {
|
pub fn update_pointer_contents(&mut self) -> bool {
|
||||||
let _span = tracy_client::span!("Niri::update_pointer_focus");
|
let _span = tracy_client::span!("Niri::update_pointer_contents");
|
||||||
|
|
||||||
let pointer = &self.niri.seat.get_pointer().unwrap();
|
let pointer = &self.niri.seat.get_pointer().unwrap();
|
||||||
let location = pointer.current_location();
|
let location = pointer.current_location();
|
||||||
let under = if self.niri.pointer_hidden {
|
let under = if self.niri.pointer_hidden {
|
||||||
PointerFocus::default()
|
PointContents::default()
|
||||||
} else {
|
} else {
|
||||||
self.niri.surface_under_and_global_space(location)
|
self.niri.contents_under(location)
|
||||||
};
|
};
|
||||||
|
|
||||||
// We're not changing the global cursor location here, so if the focus did not change, then
|
// We're not changing the global cursor location here, so if the contents did not change,
|
||||||
// nothing changed.
|
// then nothing changed.
|
||||||
if self.niri.pointer_focus == under {
|
if self.niri.pointer_contents == under {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.niri
|
self.niri.pointer_contents.clone_from(&under);
|
||||||
.maybe_activate_pointer_constraint(location, &under);
|
|
||||||
|
|
||||||
self.niri.pointer_focus.clone_from(&under);
|
|
||||||
|
|
||||||
pointer.motion(
|
pointer.motion(
|
||||||
self,
|
self,
|
||||||
@@ -723,6 +751,8 @@ impl State {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.niri.maybe_activate_pointer_constraint();
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -893,8 +923,10 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.niri.config.borrow().input.keyboard.track_layout == TrackLayout::Window {
|
if self.niri.config.borrow().input.keyboard.track_layout == TrackLayout::Window {
|
||||||
let current_layout =
|
let current_layout = keyboard.with_xkb_state(self, |context| {
|
||||||
keyboard.with_xkb_state(self, |context| context.active_layout());
|
let xkb = context.xkb().lock().unwrap();
|
||||||
|
xkb.active_layout()
|
||||||
|
});
|
||||||
|
|
||||||
let mut new_layout = current_layout;
|
let mut new_layout = current_layout;
|
||||||
// Store the currently active layout for the surface.
|
// Store the currently active layout for the surface.
|
||||||
@@ -982,6 +1014,7 @@ impl State {
|
|||||||
let mut window_rules_changed = false;
|
let mut window_rules_changed = false;
|
||||||
let mut debug_config_changed = false;
|
let mut debug_config_changed = false;
|
||||||
let mut shaders_changed = false;
|
let mut shaders_changed = false;
|
||||||
|
let mut cursor_inactivity_timeout_changed = false;
|
||||||
let mut old_config = self.niri.config.borrow_mut();
|
let mut old_config = self.niri.config.borrow_mut();
|
||||||
|
|
||||||
// Reload the cursor.
|
// Reload the cursor.
|
||||||
@@ -1068,8 +1101,18 @@ impl State {
|
|||||||
shaders_changed = true;
|
shaders_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.cursor.hide_after_inactive_ms != old_config.cursor.hide_after_inactive_ms {
|
||||||
|
cursor_inactivity_timeout_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if config.debug != old_config.debug {
|
if config.debug != old_config.debug {
|
||||||
debug_config_changed = true;
|
debug_config_changed = true;
|
||||||
|
|
||||||
|
if config.debug.keep_laptop_panel_on_when_lid_is_closed
|
||||||
|
!= old_config.debug.keep_laptop_panel_on_when_lid_is_closed
|
||||||
|
{
|
||||||
|
output_config_changed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*old_config = config;
|
*old_config = config;
|
||||||
@@ -1114,6 +1157,10 @@ impl State {
|
|||||||
self.niri.layout.update_shaders();
|
self.niri.layout.update_shaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cursor_inactivity_timeout_changed {
|
||||||
|
self.niri.reset_pointer_inactivity_timer();
|
||||||
|
}
|
||||||
|
|
||||||
// Can't really update xdg-decoration settings since we have to hide the globals for CSD
|
// Can't really update xdg-decoration settings since we have to hide the globals for CSD
|
||||||
// due to the SDL2 bug... I don't imagine clients are prepared for the xdg-decoration
|
// due to the SDL2 bug... I don't imagine clients are prepared for the xdg-decoration
|
||||||
// global suddenly appearing? Either way, right now it's live-reloaded in a sense that new
|
// global suddenly appearing? Either way, right now it's live-reloaded in a sense that new
|
||||||
@@ -1520,7 +1567,7 @@ impl State {
|
|||||||
to_introspect: &async_channel::Sender<NiriToIntrospect>,
|
to_introspect: &async_channel::Sender<NiriToIntrospect>,
|
||||||
msg: IntrospectToNiri,
|
msg: IntrospectToNiri,
|
||||||
) {
|
) {
|
||||||
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
|
use crate::utils::with_toplevel_role;
|
||||||
|
|
||||||
let IntrospectToNiri::GetWindows = msg;
|
let IntrospectToNiri::GetWindows = msg;
|
||||||
let _span = tracy_client::span!("GetWindows");
|
let _span = tracy_client::span!("GetWindows");
|
||||||
@@ -1528,21 +1575,8 @@ impl State {
|
|||||||
let mut windows = HashMap::new();
|
let mut windows = HashMap::new();
|
||||||
|
|
||||||
self.niri.layout.with_windows(|mapped, _, _| {
|
self.niri.layout.with_windows(|mapped, _, _| {
|
||||||
let wl_surface = mapped
|
|
||||||
.window
|
|
||||||
.toplevel()
|
|
||||||
.expect("no X11 support")
|
|
||||||
.wl_surface();
|
|
||||||
|
|
||||||
let id = mapped.id().get();
|
let id = mapped.id().get();
|
||||||
let props = with_states(wl_surface, |states| {
|
let props = with_toplevel_role(mapped.toplevel(), |role| {
|
||||||
let role = states
|
|
||||||
.data_map
|
|
||||||
.get::<XdgToplevelSurfaceData>()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
gnome_shell_introspect::WindowProperties {
|
gnome_shell_introspect::WindowProperties {
|
||||||
title: role.title.clone().unwrap_or_default(),
|
title: role.title.clone().unwrap_or_default(),
|
||||||
app_id: role.app_id.clone().unwrap_or_default(),
|
app_id: role.app_id.clone().unwrap_or_default(),
|
||||||
@@ -1672,6 +1706,18 @@ impl Niri {
|
|||||||
is_tty && !client.get_data::<ClientState>().unwrap().restricted
|
is_tty && !client.get_data::<ClientState>().unwrap().restricted
|
||||||
});
|
});
|
||||||
let activation_state = XdgActivationState::new::<State>(&display_handle);
|
let activation_state = XdgActivationState::new::<State>(&display_handle);
|
||||||
|
event_loop
|
||||||
|
.insert_source(
|
||||||
|
Timer::from_duration(XDG_ACTIVATION_TOKEN_TIMEOUT),
|
||||||
|
|_, _, state| {
|
||||||
|
state.niri.activation_state.retain_tokens(|_, token_data| {
|
||||||
|
token_data.timestamp.elapsed() < XDG_ACTIVATION_TOKEN_TIMEOUT
|
||||||
|
});
|
||||||
|
TimeoutAction::ToDuration(XDG_ACTIVATION_TOKEN_TIMEOUT)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mutter_x11_interop_state =
|
let mutter_x11_interop_state =
|
||||||
MutterX11InteropManagerState::new::<State, _>(&display_handle, move |_| true);
|
MutterX11InteropManagerState::new::<State, _>(&display_handle, move |_| true);
|
||||||
|
|
||||||
@@ -1775,7 +1821,7 @@ impl Niri {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
drop(config_);
|
drop(config_);
|
||||||
Self {
|
let mut niri = Self {
|
||||||
config,
|
config,
|
||||||
config_file_output_config,
|
config_file_output_config,
|
||||||
|
|
||||||
@@ -1797,6 +1843,7 @@ impl Niri {
|
|||||||
blocker_cleared_tx,
|
blocker_cleared_tx,
|
||||||
blocker_cleared_rx,
|
blocker_cleared_rx,
|
||||||
monitors_active: true,
|
monitors_active: true,
|
||||||
|
is_lid_closed: false,
|
||||||
|
|
||||||
devices: HashSet::new(),
|
devices: HashSet::new(),
|
||||||
tablets: HashMap::new(),
|
tablets: HashMap::new(),
|
||||||
@@ -1850,9 +1897,9 @@ impl Niri {
|
|||||||
cursor_texture_cache: Default::default(),
|
cursor_texture_cache: Default::default(),
|
||||||
cursor_shape_manager_state,
|
cursor_shape_manager_state,
|
||||||
dnd_icon: None,
|
dnd_icon: None,
|
||||||
pointer_focus: PointerFocus::default(),
|
pointer_contents: PointContents::default(),
|
||||||
pointer_hidden: false,
|
pointer_hidden: false,
|
||||||
pointer_grab_ongoing: false,
|
pointer_inactivity_timer: None,
|
||||||
tablet_cursor_location: None,
|
tablet_cursor_location: None,
|
||||||
gesture_swipe_3f_cumulative: None,
|
gesture_swipe_3f_cumulative: None,
|
||||||
vertical_wheel_tracker: ScrollTracker::new(120),
|
vertical_wheel_tracker: ScrollTracker::new(120),
|
||||||
@@ -1887,11 +1934,19 @@ impl Niri {
|
|||||||
|
|
||||||
#[cfg(feature = "xdp-gnome-screencast")]
|
#[cfg(feature = "xdp-gnome-screencast")]
|
||||||
mapped_cast_output: HashMap::new(),
|
mapped_cast_output: HashMap::new(),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
niri.reset_pointer_inactivity_timer();
|
||||||
|
|
||||||
|
niri
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dbus")]
|
#[cfg(feature = "dbus")]
|
||||||
pub fn inhibit_power_key(&mut self) -> anyhow::Result<()> {
|
pub fn inhibit_power_key(&mut self) -> anyhow::Result<()> {
|
||||||
|
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||||
|
|
||||||
|
use smithay::reexports::rustix::io::{fcntl_setfd, FdFlags};
|
||||||
|
|
||||||
let conn = zbus::blocking::ConnectionBuilder::system()?.build()?;
|
let conn = zbus::blocking::ConnectionBuilder::system()?.build()?;
|
||||||
|
|
||||||
let message = conn.call_method(
|
let message = conn.call_method(
|
||||||
@@ -1902,7 +1957,14 @@ impl Niri {
|
|||||||
&("handle-power-key", "niri", "Power key handling", "block"),
|
&("handle-power-key", "niri", "Power key handling", "block"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let fd = message.body()?;
|
let fd: zbus::zvariant::OwnedFd = message.body()?;
|
||||||
|
|
||||||
|
// Don't leak the fd to child processes.
|
||||||
|
let borrowed = unsafe { BorrowedFd::borrow_raw(fd.as_raw_fd()) };
|
||||||
|
if let Err(err) = fcntl_setfd(borrowed, FdFlags::CLOEXEC) {
|
||||||
|
warn!("error setting CLOEXEC on inhibit fd: {err:?}");
|
||||||
|
};
|
||||||
|
|
||||||
self.inhibit_power_key_fd = Some(fd);
|
self.inhibit_power_key_fd = Some(fd);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -2284,13 +2346,14 @@ impl Niri {
|
|||||||
self.window_under(pos)
|
self.window_under(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the surface under cursor and its position in the global space.
|
/// Returns contents under the given point.
|
||||||
///
|
///
|
||||||
/// Pointer needs location in global space, and focused window location compatible with that
|
/// We don't have a proper global space for all windows, so this function converts window
|
||||||
/// global space. We don't have a global space for all windows, but this function converts the
|
/// locations to global space according to where they are rendered.
|
||||||
/// window location temporarily to the current global space.
|
///
|
||||||
pub fn surface_under_and_global_space(&mut self, pos: Point<f64, Logical>) -> PointerFocus {
|
/// This function does not take pointer or touch grabs into account.
|
||||||
let mut rv = PointerFocus::default();
|
pub fn contents_under(&mut self, pos: Point<f64, Logical>) -> PointContents {
|
||||||
|
let mut rv = PointContents::default();
|
||||||
|
|
||||||
let Some((output, pos_within_output)) = self.output_under(pos) else {
|
let Some((output, pos_within_output)) = self.output_under(pos) else {
|
||||||
return rv;
|
return rv;
|
||||||
@@ -2589,22 +2652,20 @@ impl Niri {
|
|||||||
|
|
||||||
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
||||||
|
|
||||||
let (mut pointer_elements, pointer_pos) = match render_cursor {
|
let mut pointer_elements = match render_cursor {
|
||||||
RenderCursor::Hidden => (vec![], pointer_pos.to_physical_precise_round(output_scale)),
|
RenderCursor::Hidden => vec![],
|
||||||
RenderCursor::Surface { surface, hotspot } => {
|
RenderCursor::Surface { surface, hotspot } => {
|
||||||
let pointer_pos =
|
let pointer_pos =
|
||||||
(pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
(pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
||||||
|
|
||||||
let pointer_elements = render_elements_from_surface_tree(
|
render_elements_from_surface_tree(
|
||||||
renderer,
|
renderer,
|
||||||
&surface,
|
&surface,
|
||||||
pointer_pos,
|
pointer_pos,
|
||||||
output_scale,
|
output_scale,
|
||||||
1.,
|
1.,
|
||||||
Kind::Cursor,
|
Kind::Cursor,
|
||||||
);
|
)
|
||||||
|
|
||||||
(pointer_elements, pointer_pos)
|
|
||||||
}
|
}
|
||||||
RenderCursor::Named {
|
RenderCursor::Named {
|
||||||
icon,
|
icon,
|
||||||
@@ -2620,7 +2681,7 @@ impl Niri {
|
|||||||
let mut pointer_elements = vec![];
|
let mut pointer_elements = vec![];
|
||||||
let pointer_element = match MemoryRenderBufferRenderElement::from_buffer(
|
let pointer_element = match MemoryRenderBufferRenderElement::from_buffer(
|
||||||
renderer,
|
renderer,
|
||||||
pointer_pos.to_f64(),
|
pointer_pos,
|
||||||
&texture,
|
&texture,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@@ -2637,14 +2698,16 @@ impl Niri {
|
|||||||
pointer_elements.push(OutputRenderElements::NamedPointer(element));
|
pointer_elements.push(OutputRenderElements::NamedPointer(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
(pointer_elements, pointer_pos)
|
pointer_elements
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(dnd_icon) = &self.dnd_icon {
|
if let Some(dnd_icon) = self.dnd_icon.as_ref() {
|
||||||
|
let pointer_pos =
|
||||||
|
(pointer_pos + dnd_icon.offset.to_f64()).to_physical_precise_round(output_scale);
|
||||||
pointer_elements.extend(render_elements_from_surface_tree(
|
pointer_elements.extend(render_elements_from_surface_tree(
|
||||||
renderer,
|
renderer,
|
||||||
dnd_icon,
|
&dnd_icon.surface,
|
||||||
pointer_pos,
|
pointer_pos,
|
||||||
output_scale,
|
output_scale,
|
||||||
1.,
|
1.,
|
||||||
@@ -2667,7 +2730,7 @@ impl Niri {
|
|||||||
.tablet_cursor_location
|
.tablet_cursor_location
|
||||||
.unwrap_or_else(|| self.seat.get_pointer().unwrap().current_location());
|
.unwrap_or_else(|| self.seat.get_pointer().unwrap().current_location());
|
||||||
|
|
||||||
match self.cursor_manager.cursor_image().clone() {
|
match self.cursor_manager.cursor_image() {
|
||||||
CursorImageStatus::Surface(ref surface) => {
|
CursorImageStatus::Surface(ref surface) => {
|
||||||
let hotspot = with_states(surface, |states| {
|
let hotspot = with_states(surface, |states| {
|
||||||
states
|
states
|
||||||
@@ -2685,6 +2748,7 @@ impl Niri {
|
|||||||
let dnd = self
|
let dnd = self
|
||||||
.dnd_icon
|
.dnd_icon
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
.map(|icon| &icon.surface)
|
||||||
.map(|surface| (surface, bbox_from_surface_tree(surface, surface_pos)));
|
.map(|surface| (surface, bbox_from_surface_tree(surface, surface_pos)));
|
||||||
|
|
||||||
// FIXME we basically need to pick the largest scale factor across the overlapping
|
// FIXME we basically need to pick the largest scale factor across the overlapping
|
||||||
@@ -2746,12 +2810,12 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
cursor_image => {
|
cursor_image => {
|
||||||
// There's no cursor surface, but there might be a DnD icon.
|
// There's no cursor surface, but there might be a DnD icon.
|
||||||
let Some(surface) = &self.dnd_icon else {
|
let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon = if let CursorImageStatus::Named(icon) = cursor_image {
|
let icon = if let CursorImageStatus::Named(icon) = cursor_image {
|
||||||
icon
|
*icon
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
Default::default()
|
||||||
};
|
};
|
||||||
@@ -2800,6 +2864,23 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn refresh_layout(&mut self) {
|
||||||
|
let layout_is_active = match &self.keyboard_focus {
|
||||||
|
KeyboardFocus::Layout { .. } => true,
|
||||||
|
KeyboardFocus::LayerShell { .. } => false,
|
||||||
|
|
||||||
|
// Draw layout as active in these cases to reduce unnecessary window animations.
|
||||||
|
// There's no confusion because these are both fullscreen modes.
|
||||||
|
//
|
||||||
|
// FIXME: when going into the screenshot UI from a layer-shell focus, and then back to
|
||||||
|
// layer-shell, the layout will briefly draw as active, despite never having focus.
|
||||||
|
KeyboardFocus::LockScreen { .. } => true,
|
||||||
|
KeyboardFocus::ScreenshotUi => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.layout.refresh(layout_is_active);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn refresh_idle_inhibit(&mut self) {
|
pub fn refresh_idle_inhibit(&mut self) {
|
||||||
let _span = tracy_client::span!("Niri::refresh_idle_inhibit");
|
let _span = tracy_client::span!("Niri::refresh_idle_inhibit");
|
||||||
|
|
||||||
@@ -3019,7 +3100,11 @@ impl Niri {
|
|||||||
|
|
||||||
// Get monitor elements.
|
// Get monitor elements.
|
||||||
let mon = self.layout.monitor_for_output(output).unwrap();
|
let mon = self.layout.monitor_for_output(output).unwrap();
|
||||||
let monitor_elements = mon.render_elements(renderer, target);
|
let monitor_elements: Vec<_> = mon.render_elements(renderer, target).collect();
|
||||||
|
let float_elements: Vec<_> = self
|
||||||
|
.layout
|
||||||
|
.render_floating_for_output(renderer, output, target)
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Get layer-shell elements.
|
// Get layer-shell elements.
|
||||||
let layer_map = layer_map_for_output(output);
|
let layer_map = layer_map_for_output(output);
|
||||||
@@ -3050,10 +3135,12 @@ impl Niri {
|
|||||||
|
|
||||||
// Then the regular monitor elements and the top layer in varying order.
|
// Then the regular monitor elements and the top layer in varying order.
|
||||||
if mon.render_above_top_layer() {
|
if mon.render_above_top_layer() {
|
||||||
|
elements.extend(float_elements.into_iter().map(OutputRenderElements::from));
|
||||||
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
||||||
extend_from_layer(&mut elements, Layer::Top);
|
extend_from_layer(&mut elements, Layer::Top);
|
||||||
} else {
|
} else {
|
||||||
extend_from_layer(&mut elements, Layer::Top);
|
extend_from_layer(&mut elements, Layer::Top);
|
||||||
|
elements.extend(float_elements.into_iter().map(OutputRenderElements::from));
|
||||||
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3095,11 +3182,7 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.unfinished_animations_remain = self
|
state.unfinished_animations_remain = self.layout.are_animations_ongoing(Some(output));
|
||||||
.layout
|
|
||||||
.monitor_for_output(output)
|
|
||||||
.unwrap()
|
|
||||||
.are_animations_ongoing();
|
|
||||||
|
|
||||||
self.config_error_notification
|
self.config_error_notification
|
||||||
.advance_animations(target_presentation_time);
|
.advance_animations(target_presentation_time);
|
||||||
@@ -3268,7 +3351,7 @@ impl Niri {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(surface) = &self.dnd_icon {
|
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
||||||
with_surface_tree_downward(
|
with_surface_tree_downward(
|
||||||
surface,
|
surface,
|
||||||
(),
|
(),
|
||||||
@@ -3406,7 +3489,7 @@ impl Niri {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(surface) = &self.dnd_icon {
|
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
||||||
send_dmabuf_feedback_surface_tree(
|
send_dmabuf_feedback_surface_tree(
|
||||||
surface,
|
surface,
|
||||||
output,
|
output,
|
||||||
@@ -3508,7 +3591,7 @@ impl Niri {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(surface) = &self.dnd_icon {
|
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
||||||
send_frames_surface_tree(
|
send_frames_surface_tree(
|
||||||
surface,
|
surface,
|
||||||
output,
|
output,
|
||||||
@@ -3576,7 +3659,7 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(surface) = &self.dnd_icon {
|
if let Some(surface) = &self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
||||||
send_frames_surface_tree(
|
send_frames_surface_tree(
|
||||||
surface,
|
surface,
|
||||||
output,
|
output,
|
||||||
@@ -3615,7 +3698,7 @@ impl Niri {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(surface) = &self.dnd_icon {
|
if let Some(surface) = self.dnd_icon.as_ref().map(|icon| &icon.surface) {
|
||||||
take_presentation_feedback_surface_tree(
|
take_presentation_feedback_surface_tree(
|
||||||
surface,
|
surface,
|
||||||
&mut feedback,
|
&mut feedback,
|
||||||
@@ -4427,8 +4510,16 @@ impl Niri {
|
|||||||
self.cursor_manager
|
self.cursor_manager
|
||||||
.set_cursor_image(CursorImageStatus::default_named());
|
.set_cursor_image(CursorImageStatus::default_named());
|
||||||
|
|
||||||
self.lock_state = LockState::Locking(confirmation);
|
if self.output_state.is_empty() {
|
||||||
self.queue_redraw_all();
|
// There are no outputs, lock the session right away.
|
||||||
|
let lock = confirmation.ext_session_lock().clone();
|
||||||
|
confirmation.lock();
|
||||||
|
self.lock_state = LockState::Locked(lock);
|
||||||
|
} else {
|
||||||
|
// There are outputs, which we need to redraw before locking.
|
||||||
|
self.lock_state = LockState::Locking(confirmation);
|
||||||
|
self.queue_redraw_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unlock(&mut self) {
|
pub fn unlock(&mut self) {
|
||||||
@@ -4455,17 +4546,20 @@ impl Niri {
|
|||||||
output_state.lock_surface = Some(surface);
|
output_state.lock_surface = Some(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maybe_activate_pointer_constraint(
|
/// Activates the pointer constraint if necessary according to the current pointer contents.
|
||||||
&self,
|
///
|
||||||
new_pos: Point<f64, Logical>,
|
/// Make sure the pointer location and contents are up to date before calling this.
|
||||||
new_under: &PointerFocus,
|
pub fn maybe_activate_pointer_constraint(&self) {
|
||||||
) {
|
let pointer = self.seat.get_pointer().unwrap();
|
||||||
let Some((surface, surface_loc)) = &new_under.surface else {
|
let pointer_pos = pointer.current_location();
|
||||||
|
|
||||||
|
let Some((surface, surface_loc)) = &self.pointer_contents.surface else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if self.pointer_grab_ongoing {
|
if Some(surface) != pointer.current_focus().as_ref() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pointer = &self.seat.get_pointer().unwrap();
|
let pointer = &self.seat.get_pointer().unwrap();
|
||||||
with_pointer_constraint(surface, pointer, |constraint| {
|
with_pointer_constraint(surface, pointer, |constraint| {
|
||||||
let Some(constraint) = constraint else { return };
|
let Some(constraint) = constraint else { return };
|
||||||
@@ -4476,8 +4570,8 @@ impl Niri {
|
|||||||
|
|
||||||
// Constraint does not apply if not within region.
|
// Constraint does not apply if not within region.
|
||||||
if let Some(region) = constraint.region() {
|
if let Some(region) = constraint.region() {
|
||||||
let new_pos_within_surface = new_pos - *surface_loc;
|
let pos_within_surface = pointer_pos - *surface_loc;
|
||||||
if !region.contains(new_pos_within_surface.to_i32_round()) {
|
if !region.contains(pos_within_surface.to_i32_round()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4549,7 +4643,7 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_focus_follows_mouse(&mut self, new_focus: &PointerFocus) {
|
pub fn handle_focus_follows_mouse(&mut self, new_focus: &PointContents) {
|
||||||
let Some(ffm) = self.config.borrow().input.focus_follows_mouse else {
|
let Some(ffm) = self.config.borrow().input.focus_follows_mouse else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -4560,7 +4654,7 @@ impl Niri {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recompute the current pointer focus because we don't update it during animations.
|
// Recompute the current pointer focus because we don't update it during animations.
|
||||||
let current_focus = self.surface_under_and_global_space(pointer.current_location());
|
let current_focus = self.contents_under(pointer.current_location());
|
||||||
|
|
||||||
if let Some(output) = &new_focus.output {
|
if let Some(output) = &new_focus.output {
|
||||||
if current_focus.output.as_ref() != Some(output) {
|
if current_focus.output.as_ref() != Some(output) {
|
||||||
@@ -4698,6 +4792,32 @@ impl Niri {
|
|||||||
self.queue_redraw_all();
|
self.queue_redraw_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_pointer_inactivity_timer(&mut self) {
|
||||||
|
let _span = tracy_client::span!("Niri::reset_pointer_inactivity_timer");
|
||||||
|
|
||||||
|
if let Some(token) = self.pointer_inactivity_timer.take() {
|
||||||
|
self.event_loop.remove(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(timeout_ms) = self.config.borrow().cursor.hide_after_inactive_ms else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let duration = Duration::from_millis(timeout_ms as u64);
|
||||||
|
let timer = Timer::from_duration(duration);
|
||||||
|
let token = self
|
||||||
|
.event_loop
|
||||||
|
.insert_source(timer, move |_, _, state| {
|
||||||
|
state.niri.pointer_inactivity_timer = None;
|
||||||
|
state.niri.pointer_hidden = true;
|
||||||
|
state.niri.queue_redraw_all();
|
||||||
|
|
||||||
|
TimeoutAction::Drop
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
self.pointer_inactivity_timer = Some(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClientState {
|
pub struct ClientState {
|
||||||
@@ -4715,6 +4835,7 @@ impl ClientData for ClientState {
|
|||||||
niri_render_elements! {
|
niri_render_elements! {
|
||||||
OutputRenderElements<R> => {
|
OutputRenderElements<R> => {
|
||||||
Monitor = MonitorRenderElement<R>,
|
Monitor = MonitorRenderElement<R>,
|
||||||
|
Tile = TileRenderElement<R>,
|
||||||
Wayland = WaylandSurfaceRenderElement<R>,
|
Wayland = WaylandSurfaceRenderElement<R>,
|
||||||
NamedPointer = MemoryRenderBufferRenderElement<R>,
|
NamedPointer = MemoryRenderBufferRenderElement<R>,
|
||||||
SolidColor = SolidColorRenderElement,
|
SolidColor = SolidColorRenderElement,
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
|||||||
use smithay::reexports::wayland_server::{
|
use smithay::reexports::wayland_server::{
|
||||||
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||||
};
|
};
|
||||||
use smithay::wayland::compositor::with_states;
|
use smithay::wayland::shell::xdg::{ToplevelStateSet, XdgToplevelSurfaceRoleAttributes};
|
||||||
use smithay::wayland::shell::xdg::{
|
|
||||||
ToplevelStateSet, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
|
||||||
};
|
|
||||||
use wayland_protocols_wlr::foreign_toplevel::v1::server::{
|
use wayland_protocols_wlr::foreign_toplevel::v1::server::{
|
||||||
zwlr_foreign_toplevel_handle_v1, zwlr_foreign_toplevel_manager_v1,
|
zwlr_foreign_toplevel_handle_v1, zwlr_foreign_toplevel_manager_v1,
|
||||||
};
|
};
|
||||||
@@ -22,6 +19,7 @@ use zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1;
|
|||||||
use zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
|
use zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
|
||||||
|
|
||||||
use crate::niri::State;
|
use crate::niri::State;
|
||||||
|
use crate::utils::with_toplevel_role;
|
||||||
|
|
||||||
const VERSION: u32 = 3;
|
const VERSION: u32 = 3;
|
||||||
|
|
||||||
@@ -96,37 +94,23 @@ pub fn refresh(state: &mut State) {
|
|||||||
// the previous window and only then activate the newly focused window.
|
// the previous window and only then activate the newly focused window.
|
||||||
let mut focused = None;
|
let mut focused = None;
|
||||||
state.niri.layout.with_windows(|mapped, output, _| {
|
state.niri.layout.with_windows(|mapped, output, _| {
|
||||||
let wl_surface = mapped.toplevel().wl_surface();
|
let toplevel = mapped.toplevel();
|
||||||
|
let wl_surface = toplevel.wl_surface();
|
||||||
with_states(wl_surface, |states| {
|
with_toplevel_role(toplevel, |role| {
|
||||||
let role = states
|
|
||||||
.data_map
|
|
||||||
.get::<XdgToplevelSurfaceData>()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if state.niri.keyboard_focus.surface() == Some(wl_surface) {
|
if state.niri.keyboard_focus.surface() == Some(wl_surface) {
|
||||||
focused = Some((mapped.window.clone(), output.cloned()));
|
focused = Some((mapped.window.clone(), output.cloned()));
|
||||||
} else {
|
} else {
|
||||||
refresh_toplevel(protocol_state, wl_surface, &role, output, false);
|
refresh_toplevel(protocol_state, wl_surface, role, output, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Finally, refresh the focused window.
|
// Finally, refresh the focused window.
|
||||||
if let Some((window, output)) = focused {
|
if let Some((window, output)) = focused {
|
||||||
let wl_surface = window.toplevel().expect("no x11 support").wl_surface();
|
let toplevel = window.toplevel().expect("no X11 support");
|
||||||
|
let wl_surface = toplevel.wl_surface();
|
||||||
with_states(wl_surface, |states| {
|
with_toplevel_role(toplevel, |role| {
|
||||||
let role = states
|
refresh_toplevel(protocol_state, wl_surface, role, output.as_ref(), true);
|
||||||
.data_map
|
|
||||||
.get::<XdgToplevelSurfaceData>()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
refresh_toplevel(protocol_state, wl_surface, &role, output.as_ref(), true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::CString;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use glam::{Mat3, Vec2};
|
use glam::{Mat3, Vec2};
|
||||||
@@ -76,38 +76,31 @@ unsafe fn compile_program(
|
|||||||
let debug_program =
|
let debug_program =
|
||||||
unsafe { link_program(gl, include_str!("shaders/texture.vert"), &debug_shader)? };
|
unsafe { link_program(gl, include_str!("shaders/texture.vert"), &debug_shader)? };
|
||||||
|
|
||||||
let vert = CStr::from_bytes_with_nul(b"vert\0").expect("NULL terminated");
|
let vert = c"vert";
|
||||||
let vert_position = CStr::from_bytes_with_nul(b"vert_position\0").expect("NULL terminated");
|
let vert_position = c"vert_position";
|
||||||
let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated");
|
let matrix = c"matrix";
|
||||||
let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated");
|
let tex_matrix = c"tex_matrix";
|
||||||
let size = CStr::from_bytes_with_nul(b"niri_size\0").expect("NULL terminated");
|
let size = c"niri_size";
|
||||||
let scale = CStr::from_bytes_with_nul(b"niri_scale\0").expect("NULL terminated");
|
let scale = c"niri_scale";
|
||||||
let alpha = CStr::from_bytes_with_nul(b"niri_alpha\0").expect("NULL terminated");
|
let alpha = c"niri_alpha";
|
||||||
let tint = CStr::from_bytes_with_nul(b"niri_tint\0").expect("NULL terminated");
|
let tint = c"niri_tint";
|
||||||
|
|
||||||
Ok(ShaderProgram(Rc::new(ShaderProgramInner {
|
Ok(ShaderProgram(Rc::new(ShaderProgramInner {
|
||||||
normal: ShaderProgramInternal {
|
normal: ShaderProgramInternal {
|
||||||
program,
|
program,
|
||||||
uniform_matrix: gl
|
uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr()),
|
||||||
.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
|
uniform_tex_matrix: gl.GetUniformLocation(program, tex_matrix.as_ptr()),
|
||||||
uniform_tex_matrix: gl
|
uniform_size: gl.GetUniformLocation(program, size.as_ptr()),
|
||||||
.GetUniformLocation(program, tex_matrix.as_ptr() as *const ffi::types::GLchar),
|
uniform_scale: gl.GetUniformLocation(program, scale.as_ptr()),
|
||||||
uniform_size: gl
|
uniform_alpha: gl.GetUniformLocation(program, alpha.as_ptr()),
|
||||||
.GetUniformLocation(program, size.as_ptr() as *const ffi::types::GLchar),
|
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr()),
|
||||||
uniform_scale: gl
|
attrib_vert_position: gl.GetAttribLocation(program, vert_position.as_ptr()),
|
||||||
.GetUniformLocation(program, scale.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
uniform_alpha: gl
|
|
||||||
.GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
attrib_vert_position: gl
|
|
||||||
.GetAttribLocation(program, vert_position.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
additional_uniforms: additional_uniforms
|
additional_uniforms: additional_uniforms
|
||||||
.iter()
|
.iter()
|
||||||
.map(|uniform| {
|
.map(|uniform| {
|
||||||
let name =
|
let name =
|
||||||
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
|
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
|
||||||
let location =
|
let location = gl.GetUniformLocation(program, name.as_ptr());
|
||||||
gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
|
|
||||||
(
|
(
|
||||||
uniform.name.clone().into_owned(),
|
uniform.name.clone().into_owned(),
|
||||||
UniformDesc {
|
UniformDesc {
|
||||||
@@ -121,41 +114,26 @@ unsafe fn compile_program(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|name_| {
|
.map(|name_| {
|
||||||
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
|
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
|
||||||
let location =
|
let location = gl.GetUniformLocation(program, name.as_ptr());
|
||||||
gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
|
|
||||||
(name_.to_string(), location)
|
(name_.to_string(), location)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
debug: ShaderProgramInternal {
|
debug: ShaderProgramInternal {
|
||||||
program: debug_program,
|
program: debug_program,
|
||||||
uniform_matrix: gl
|
uniform_matrix: gl.GetUniformLocation(debug_program, matrix.as_ptr()),
|
||||||
.GetUniformLocation(debug_program, matrix.as_ptr() as *const ffi::types::GLchar),
|
uniform_tex_matrix: gl.GetUniformLocation(debug_program, tex_matrix.as_ptr()),
|
||||||
uniform_tex_matrix: gl.GetUniformLocation(
|
uniform_size: gl.GetUniformLocation(debug_program, size.as_ptr()),
|
||||||
debug_program,
|
uniform_scale: gl.GetUniformLocation(debug_program, scale.as_ptr()),
|
||||||
tex_matrix.as_ptr() as *const ffi::types::GLchar,
|
uniform_alpha: gl.GetUniformLocation(debug_program, alpha.as_ptr()),
|
||||||
),
|
attrib_vert: gl.GetAttribLocation(debug_program, vert.as_ptr()),
|
||||||
uniform_size: gl
|
attrib_vert_position: gl.GetAttribLocation(debug_program, vert_position.as_ptr()),
|
||||||
.GetUniformLocation(debug_program, size.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
uniform_scale: gl
|
|
||||||
.GetUniformLocation(debug_program, scale.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
uniform_alpha: gl
|
|
||||||
.GetUniformLocation(debug_program, alpha.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
attrib_vert: gl
|
|
||||||
.GetAttribLocation(debug_program, vert.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
attrib_vert_position: gl.GetAttribLocation(
|
|
||||||
debug_program,
|
|
||||||
vert_position.as_ptr() as *const ffi::types::GLchar,
|
|
||||||
),
|
|
||||||
additional_uniforms: additional_uniforms
|
additional_uniforms: additional_uniforms
|
||||||
.iter()
|
.iter()
|
||||||
.map(|uniform| {
|
.map(|uniform| {
|
||||||
let name =
|
let name =
|
||||||
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
|
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
|
||||||
let location = gl.GetUniformLocation(
|
let location = gl.GetUniformLocation(debug_program, name.as_ptr());
|
||||||
debug_program,
|
|
||||||
name.as_ptr() as *const ffi::types::GLchar,
|
|
||||||
);
|
|
||||||
(
|
(
|
||||||
uniform.name.clone().into_owned(),
|
uniform.name.clone().into_owned(),
|
||||||
UniformDesc {
|
UniformDesc {
|
||||||
@@ -169,16 +147,12 @@ unsafe fn compile_program(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|name_| {
|
.map(|name_| {
|
||||||
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
|
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
|
||||||
let location = gl.GetUniformLocation(
|
let location = gl.GetUniformLocation(debug_program, name.as_ptr());
|
||||||
debug_program,
|
|
||||||
name.as_ptr() as *const ffi::types::GLchar,
|
|
||||||
);
|
|
||||||
(name_.to_string(), location)
|
(name_.to_string(), location)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
uniform_tint: gl
|
uniform_tint: gl.GetUniformLocation(debug_program, tint.as_ptr()),
|
||||||
.GetUniformLocation(debug_program, tint.as_ptr() as *const ffi::types::GLchar),
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +185,7 @@ impl ShaderRenderElement {
|
|||||||
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
|
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
|
||||||
scale: f32,
|
scale: f32,
|
||||||
alpha: f32,
|
alpha: f32,
|
||||||
uniforms: Vec<Uniform<'_>>,
|
additional_uniforms: Vec<Uniform<'static>>,
|
||||||
textures: HashMap<String, GlesTexture>,
|
textures: HashMap<String, GlesTexture>,
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -223,7 +197,7 @@ impl ShaderRenderElement {
|
|||||||
opaque_regions: opaque_regions.unwrap_or_default(),
|
opaque_regions: opaque_regions.unwrap_or_default(),
|
||||||
scale,
|
scale,
|
||||||
alpha,
|
alpha,
|
||||||
additional_uniforms: uniforms.into_iter().map(|u| u.into_owned()).collect(),
|
additional_uniforms,
|
||||||
textures,
|
textures,
|
||||||
kind,
|
kind,
|
||||||
}
|
}
|
||||||
@@ -253,13 +227,13 @@ impl ShaderRenderElement {
|
|||||||
size: Size<f64, Logical>,
|
size: Size<f64, Logical>,
|
||||||
opaque_regions: Option<Vec<Rectangle<f64, Logical>>>,
|
opaque_regions: Option<Vec<Rectangle<f64, Logical>>>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
uniforms: Vec<Uniform<'_>>,
|
uniforms: Vec<Uniform<'static>>,
|
||||||
textures: HashMap<String, GlesTexture>,
|
textures: HashMap<String, GlesTexture>,
|
||||||
) {
|
) {
|
||||||
self.area.size = size;
|
self.area.size = size;
|
||||||
self.opaque_regions = opaque_regions.unwrap_or_default();
|
self.opaque_regions = opaque_regions.unwrap_or_default();
|
||||||
self.scale = scale;
|
self.scale = scale;
|
||||||
self.additional_uniforms = uniforms.into_iter().map(|u| u.into_owned()).collect();
|
self.additional_uniforms = uniforms;
|
||||||
self.textures = textures;
|
self.textures = textures;
|
||||||
|
|
||||||
self.commit_counter.increment();
|
self.commit_counter.increment();
|
||||||
|
|||||||
+24
-1
@@ -17,8 +17,11 @@ use smithay::reexports::rustix::time::{clock_gettime, ClockId};
|
|||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
use smithay::utils::{Coordinate, Logical, Point, Rectangle, Size, Transform};
|
use smithay::utils::{Coordinate, Logical, Point, Rectangle, Size, Transform};
|
||||||
use smithay::wayland::compositor::{send_surface_state, SurfaceData};
|
use smithay::wayland::compositor::{send_surface_state, with_states, SurfaceData};
|
||||||
use smithay::wayland::fractional_scale::with_fractional_scale;
|
use smithay::wayland::fractional_scale::with_fractional_scale;
|
||||||
|
use smithay::wayland::shell::xdg::{
|
||||||
|
ToplevelSurface, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod id;
|
pub mod id;
|
||||||
pub mod scale;
|
pub mod scale;
|
||||||
@@ -221,6 +224,26 @@ pub fn output_matches_name(output: &Output, target: &str) -> bool {
|
|||||||
name.matches(target)
|
name.matches(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_laptop_panel(connector: &str) -> bool {
|
||||||
|
matches!(connector.get(..4), Some("eDP-" | "LVDS" | "DSI-"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_toplevel_role<T>(
|
||||||
|
toplevel: &ToplevelSurface,
|
||||||
|
f: impl FnOnce(&mut XdgToplevelSurfaceRoleAttributes) -> T,
|
||||||
|
) -> T {
|
||||||
|
with_states(toplevel.wl_surface(), |states| {
|
||||||
|
let mut role = states
|
||||||
|
.data_map
|
||||||
|
.get::<XdgToplevelSurfaceData>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
f(&mut role)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dbus")]
|
#[cfg(feature = "dbus")]
|
||||||
pub fn show_screenshot_notification(image_path: Option<PathBuf>) {
|
pub fn show_screenshot_notification(image_path: Option<PathBuf>) {
|
||||||
let mut notification = notify_rust::Notification::new();
|
let mut notification = notify_rust::Notification::new();
|
||||||
|
|||||||
+9
-15
@@ -15,7 +15,7 @@ use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
|||||||
use smithay::reexports::wayland_server::Resource as _;
|
use smithay::reexports::wayland_server::Resource as _;
|
||||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
|
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
|
||||||
use smithay::wayland::compositor::{remove_pre_commit_hook, with_states, HookId};
|
use smithay::wayland::compositor::{remove_pre_commit_hook, with_states, HookId};
|
||||||
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface, XdgToplevelSurfaceData};
|
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface};
|
||||||
|
|
||||||
use super::{ResolvedWindowRules, WindowRef};
|
use super::{ResolvedWindowRules, WindowRef};
|
||||||
use crate::handlers::KdeDecorationsModeState;
|
use crate::handlers::KdeDecorationsModeState;
|
||||||
@@ -33,7 +33,7 @@ use crate::render_helpers::surface::render_snapshot_from_surface_tree;
|
|||||||
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
|
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
|
||||||
use crate::utils::id::IdCounter;
|
use crate::utils::id::IdCounter;
|
||||||
use crate::utils::transaction::Transaction;
|
use crate::utils::transaction::Transaction;
|
||||||
use crate::utils::{send_scale_transform, ResizeEdge};
|
use crate::utils::{send_scale_transform, with_toplevel_role, ResizeEdge};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Mapped {
|
pub struct Mapped {
|
||||||
@@ -571,7 +571,7 @@ impl LayoutElement for Mapped {
|
|||||||
|
|
||||||
fn has_ssd(&self) -> bool {
|
fn has_ssd(&self) -> bool {
|
||||||
let toplevel = self.toplevel();
|
let toplevel = self.toplevel();
|
||||||
let mode = toplevel.current_state().decoration_mode;
|
let mode = with_toplevel_role(self.toplevel(), |role| role.current.decoration_mode);
|
||||||
|
|
||||||
match mode {
|
match mode {
|
||||||
Some(zxdg_toplevel_decoration_v1::Mode::ServerSide) => true,
|
Some(zxdg_toplevel_decoration_v1::Mode::ServerSide) => true,
|
||||||
@@ -631,14 +631,7 @@ impl LayoutElement for Mapped {
|
|||||||
let _span =
|
let _span =
|
||||||
trace_span!("configure_intent", surface = ?self.toplevel().wl_surface().id()).entered();
|
trace_span!("configure_intent", surface = ?self.toplevel().wl_surface().id()).entered();
|
||||||
|
|
||||||
with_states(self.toplevel().wl_surface(), |states| {
|
with_toplevel_role(self.toplevel(), |attributes| {
|
||||||
let attributes = states
|
|
||||||
.data_map
|
|
||||||
.get::<XdgToplevelSurfaceData>()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Some(server_pending) = &attributes.server_pending {
|
if let Some(server_pending) = &attributes.server_pending {
|
||||||
let current_server = attributes.current_server_state();
|
let current_server = attributes.current_server_state();
|
||||||
if server_pending != current_server {
|
if server_pending != current_server {
|
||||||
@@ -719,10 +712,11 @@ impl LayoutElement for Mapped {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_fullscreen(&self) -> bool {
|
fn is_fullscreen(&self) -> bool {
|
||||||
self.toplevel()
|
with_toplevel_role(self.toplevel(), |role| {
|
||||||
.current_state()
|
role.current
|
||||||
.states
|
.states
|
||||||
.contains(xdg_toplevel::State::Fullscreen)
|
.contains(xdg_toplevel::State::Fullscreen)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_pending_fullscreen(&self) -> bool {
|
fn is_pending_fullscreen(&self) -> bool {
|
||||||
|
|||||||
+4
-14
@@ -1,11 +1,9 @@
|
|||||||
use niri_config::{BlockOutFrom, BorderRule, CornerRadius, Match, WindowRule};
|
use niri_config::{BlockOutFrom, BorderRule, CornerRadius, Match, WindowRule};
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||||
use smithay::wayland::compositor::with_states;
|
use smithay::wayland::shell::xdg::{ToplevelSurface, XdgToplevelSurfaceRoleAttributes};
|
||||||
use smithay::wayland::shell::xdg::{
|
|
||||||
ToplevelSurface, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::layout::workspace::ColumnWidth;
|
use crate::layout::workspace::ColumnWidth;
|
||||||
|
use crate::utils::with_toplevel_role;
|
||||||
|
|
||||||
pub mod mapped;
|
pub mod mapped;
|
||||||
pub use mapped::Mapped;
|
pub use mapped::Mapped;
|
||||||
@@ -144,15 +142,7 @@ impl ResolvedWindowRules {
|
|||||||
|
|
||||||
let mut resolved = ResolvedWindowRules::empty();
|
let mut resolved = ResolvedWindowRules::empty();
|
||||||
|
|
||||||
let toplevel = window.toplevel();
|
with_toplevel_role(window.toplevel(), |role| {
|
||||||
with_states(toplevel.wl_surface(), |states| {
|
|
||||||
let mut role = states
|
|
||||||
.data_map
|
|
||||||
.get::<XdgToplevelSurfaceData>()
|
|
||||||
.unwrap()
|
|
||||||
.lock()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Ensure server_pending like in Smithay's with_pending_state().
|
// Ensure server_pending like in Smithay's with_pending_state().
|
||||||
if role.server_pending.is_none() {
|
if role.server_pending.is_none() {
|
||||||
role.server_pending = Some(role.current_server_state().clone());
|
role.server_pending = Some(role.current_server_state().clone());
|
||||||
@@ -169,7 +159,7 @@ impl ResolvedWindowRules {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window_matches(window, &role, m)
|
window_matches(window, role, m)
|
||||||
};
|
};
|
||||||
|
|
||||||
if !(rule.matches.is_empty() || rule.matches.iter().any(matches)) {
|
if !(rule.matches.is_empty() || rule.matches.iter().any(matches)) {
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
### VSCode
|
### VSCode
|
||||||
|
|
||||||
There seems to be a bug in VSCode's Wayland backend until 1.86.0 which causes the window to not show up when using server-side decorations. So, to run VSCode:
|
If you're having issues with some VSCode hotkeys, try starting `Xwayland` and setting the `DISPLAY=:0` environment variable for VSCode.
|
||||||
|
That is, still running VSCode with the Wayland backend, but with `DISPLAY` set to a running Xwayland instance.
|
||||||
1. Make sure VSCode is 1.86.0 or above, or that `prefer-no-csd` is **not set** in the niri config
|
Apparently, VSCode currently unconditionally queries the X server for a keymap.
|
||||||
2. Run `code --ozone-platform-hint=auto --enable-features=WaylandWindowDecorations`
|
|
||||||
|
|
||||||
Also, if you're having issues with some VSCode hotkeys, try starting `Xwayland` and setting the `DISPLAY=:0` environment variable for VSCode. That is, still running VSCode with the Wayland backend, but with `DISPLAY` set to a running Xwayland instance. Apparently, VSCode currently unconditionally queries the X server for a keymap.
|
|
||||||
|
|
||||||
### Chromium
|
|
||||||
|
|
||||||
When creating new windows within Chromium (e.g. with <kbd>Ctrl</kbd><kbd>N</kbd>), there's a Chromium bug with sizing:
|
|
||||||
|
|
||||||
- With CSD (`prefer-no-csd` unset), the window will be a bit smaller than needed
|
|
||||||
- With SSD (`prefer-no-csd` set), the window buffer will be offset to the top-left
|
|
||||||
|
|
||||||
Both of these can be fixed by resizing the new Chromium window.
|
|
||||||
|
|
||||||
### WezTerm
|
### WezTerm
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ debug {
|
|||||||
emulate-zero-presentation-time
|
emulate-zero-presentation-time
|
||||||
disable-resize-throttling
|
disable-resize-throttling
|
||||||
disable-transactions
|
disable-transactions
|
||||||
|
keep-laptop-panel-on-when-lid-is-closed
|
||||||
|
disable-monitor-names
|
||||||
}
|
}
|
||||||
|
|
||||||
binds {
|
binds {
|
||||||
@@ -165,6 +167,33 @@ debug {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `keep-laptop-panel-on-when-lid-is-closed`
|
||||||
|
|
||||||
|
<sup>Since: 0.1.10</sup>
|
||||||
|
|
||||||
|
By default, niri will disable the internal laptop monitor when the laptop lid is closed.
|
||||||
|
This flag turns off this behavior and will leave the internal laptop monitor on.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
debug {
|
||||||
|
keep-laptop-panel-on-when-lid-is-closed
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `disable-monitor-names`
|
||||||
|
|
||||||
|
<sup>Since: 0.1.10</sup>
|
||||||
|
|
||||||
|
Disables the make/model/serial monitor names, as if niri fails to read them from the EDID.
|
||||||
|
|
||||||
|
Use this flag to work around a crash present in 0.1.9 and 0.1.10 when connecting two monitors with matching make/model/serial.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
debug {
|
||||||
|
disable-monitor-names
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Key Bindings
|
### Key Bindings
|
||||||
|
|
||||||
These are not debug options, but rather key bindings.
|
These are not debug options, but rather key bindings.
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ input {
|
|||||||
natural-scroll
|
natural-scroll
|
||||||
// accel-speed 0.2
|
// accel-speed 0.2
|
||||||
// accel-profile "flat"
|
// accel-profile "flat"
|
||||||
|
// scroll-factor 1.0
|
||||||
// scroll-method "two-finger"
|
// scroll-method "two-finger"
|
||||||
|
// scroll-button 273
|
||||||
// tap-button-map "left-middle-right"
|
// tap-button-map "left-middle-right"
|
||||||
// click-method "clickfinger"
|
// click-method "clickfinger"
|
||||||
// left-handed
|
// left-handed
|
||||||
@@ -45,7 +47,9 @@ input {
|
|||||||
// natural-scroll
|
// natural-scroll
|
||||||
// accel-speed 0.2
|
// accel-speed 0.2
|
||||||
// accel-profile "flat"
|
// accel-profile "flat"
|
||||||
|
// scroll-factor 1.0
|
||||||
// scroll-method "no-scroll"
|
// scroll-method "no-scroll"
|
||||||
|
// scroll-button 273
|
||||||
// left-handed
|
// left-handed
|
||||||
// middle-emulation
|
// middle-emulation
|
||||||
}
|
}
|
||||||
@@ -56,6 +60,18 @@ input {
|
|||||||
// accel-speed 0.2
|
// accel-speed 0.2
|
||||||
// accel-profile "flat"
|
// accel-profile "flat"
|
||||||
// scroll-method "on-button-down"
|
// scroll-method "on-button-down"
|
||||||
|
// scroll-button 273
|
||||||
|
// middle-emulation
|
||||||
|
}
|
||||||
|
|
||||||
|
trackball {
|
||||||
|
// off
|
||||||
|
// natural-scroll
|
||||||
|
// accel-speed 0.2
|
||||||
|
// accel-profile "flat"
|
||||||
|
// scroll-method "on-button-down"
|
||||||
|
// scroll-button 273
|
||||||
|
// left-handed
|
||||||
// middle-emulation
|
// middle-emulation
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,13 +150,14 @@ A few settings are common between input devices:
|
|||||||
|
|
||||||
- `off`: if set, no events will be sent from this device.
|
- `off`: if set, no events will be sent from this device.
|
||||||
|
|
||||||
A few settings are common between `touchpad`, `mouse` and `trackpoint`:
|
A few settings are common between `touchpad`, `mouse`, `trackpoint`, and `trackball`:
|
||||||
|
|
||||||
- `natural-scroll`: if set, inverts the scrolling direction.
|
- `natural-scroll`: if set, inverts the scrolling direction.
|
||||||
- `accel-speed`: pointer acceleration speed, valid values are from `-1.0` to `1.0` where the default is `0.0`.
|
- `accel-speed`: pointer acceleration speed, valid values are from `-1.0` to `1.0` where the default is `0.0`.
|
||||||
- `accel-profile`: can be `adaptive` (the default) or `flat` (disables pointer acceleration).
|
- `accel-profile`: can be `adaptive` (the default) or `flat` (disables pointer acceleration).
|
||||||
- `scroll-method`: when to generate scroll events instead of pointer motion events, can be `no-scroll`, `two-finger`, `edge`, or `on-button-down`.
|
- `scroll-method`: when to generate scroll events instead of pointer motion events, can be `no-scroll`, `two-finger`, `edge`, or `on-button-down`.
|
||||||
The default and supported methods vary depending on the device type.
|
The default and supported methods vary depending on the device type.
|
||||||
|
- `scroll-button`: <sup>Since: 0.1.10</sup> the button code used for the `on-button-down` scroll method. You can find it in `libinput debug-events`.
|
||||||
- `middle-emulation`: emulate a middle mouse click by pressing left and right mouse buttons at once.
|
- `middle-emulation`: emulate a middle mouse click by pressing left and right mouse buttons at once.
|
||||||
|
|
||||||
Settings specific to `touchpad`s:
|
Settings specific to `touchpad`s:
|
||||||
@@ -152,6 +169,10 @@ Settings specific to `touchpad`s:
|
|||||||
- `click-method`: can be `button-areas` or `clickfinger`, changes the [click method](https://wayland.freedesktop.org/libinput/doc/latest/clickpad-softbuttons.html).
|
- `click-method`: can be `button-areas` or `clickfinger`, changes the [click method](https://wayland.freedesktop.org/libinput/doc/latest/clickpad-softbuttons.html).
|
||||||
- `disabled-on-external-mouse`: do not send events while external pointer device is plugged in.
|
- `disabled-on-external-mouse`: do not send events while external pointer device is plugged in.
|
||||||
|
|
||||||
|
Settings specific to `touchpad` and `mouse`:
|
||||||
|
|
||||||
|
- `scroll-factor`: <sup>Since: 0.1.10</sup> scales the scrolling speed by this value.
|
||||||
|
|
||||||
Settings specific to `touchpad`, `mouse` and `tablet`:
|
Settings specific to `touchpad`, `mouse` and `tablet`:
|
||||||
|
|
||||||
- `left-handed`: if set, changes the device to left-handed mode.
|
- `left-handed`: if set, changes the device to left-handed mode.
|
||||||
|
|||||||
@@ -42,6 +42,12 @@ layout {
|
|||||||
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" in="srgb-linear"
|
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" in="srgb-linear"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
insert-hint {
|
||||||
|
// off
|
||||||
|
color "#ffc87f80"
|
||||||
|
// gradient from="#ffbb6680" to="#ffc88080" angle=45 relative-to="workspace-view"
|
||||||
|
}
|
||||||
|
|
||||||
struts {
|
struts {
|
||||||
// left 64
|
// left 64
|
||||||
// right 64
|
// right 64
|
||||||
@@ -303,6 +309,26 @@ layout {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `insert-hint`
|
||||||
|
|
||||||
|
<sup>Since: 0.1.10</sup>
|
||||||
|
|
||||||
|
Settings for the window insert position hint during an interactive window move.
|
||||||
|
|
||||||
|
`off` disables the insert hint altogether.
|
||||||
|
|
||||||
|
`color` and `gradient` let you change the color of the hint and have the same syntax as colors and gradients in border and focus ring.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
layout {
|
||||||
|
insert-hint {
|
||||||
|
// off
|
||||||
|
color "#ffc87f80"
|
||||||
|
gradient from="#ffbb6680" to="#ffc88080" angle=45 relative-to="workspace-view"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `struts`
|
### `struts`
|
||||||
|
|
||||||
Struts shrink the area occupied by windows, similarly to layer-shell panels.
|
Struts shrink the area occupied by windows, similarly to layer-shell panels.
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ environment {
|
|||||||
cursor {
|
cursor {
|
||||||
xcursor-theme "breeze_cursors"
|
xcursor-theme "breeze_cursors"
|
||||||
xcursor-size 48
|
xcursor-size 48
|
||||||
|
|
||||||
|
hide-when-typing
|
||||||
|
hide-after-inactive-ms 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
hotkey-overlay {
|
hotkey-overlay {
|
||||||
@@ -106,6 +109,31 @@ cursor {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `hide-when-typing`
|
||||||
|
|
||||||
|
<sup>Since: 0.1.10</sup>
|
||||||
|
|
||||||
|
If set, hides the cursor when pressing a key on the keyboard.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
cursor {
|
||||||
|
hide-when-typing
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `hide-after-inactive-ms`
|
||||||
|
|
||||||
|
<sup>Since: 0.1.10</sup>
|
||||||
|
|
||||||
|
If set, the cursor will automatically hide once this number of milliseconds passes since the last cursor movement.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
cursor {
|
||||||
|
// Hide the cursor after one second of inactivity.
|
||||||
|
hide-after-inactive-ms 1000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `hotkey-overlay`
|
### `hotkey-overlay`
|
||||||
|
|
||||||
Settings for the "Important Hotkeys" overlay.
|
Settings for the "Important Hotkeys" overlay.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ You can find documentation for various sections of the config on these wiki page
|
|||||||
* [`input {}`](./Configuration:-Input.md)
|
* [`input {}`](./Configuration:-Input.md)
|
||||||
* [`output "eDP-1" {}`](./Configuration:-Outputs.md)
|
* [`output "eDP-1" {}`](./Configuration:-Outputs.md)
|
||||||
* [`binds {}`](./Configuration:-Key-Bindings.md)
|
* [`binds {}`](./Configuration:-Key-Bindings.md)
|
||||||
|
* [`switch-events {}`](./Configuration:-Switch-Events.md)
|
||||||
* [`layout {}`](./Configuration:-Layout.md)
|
* [`layout {}`](./Configuration:-Layout.md)
|
||||||
* [top-level options](./Configuration:-Miscellaneous.md)
|
* [top-level options](./Configuration:-Miscellaneous.md)
|
||||||
* [`window-rule {}`](./Configuration:-Window-Rules.md)
|
* [`window-rule {}`](./Configuration:-Window-Rules.md)
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
### Overview
|
||||||
|
|
||||||
|
<sup>Since: 0.1.10</sup>
|
||||||
|
|
||||||
|
Switch event bindings are declared in the `switch-events {}` section of the config.
|
||||||
|
|
||||||
|
Here are all the events that you can bind at a glance:
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
switch-events {
|
||||||
|
lid-close { spawn "notify-send" "The laptop lid is closed!"; }
|
||||||
|
lid-open { spawn "notify-send" "The laptop lid is open!"; }
|
||||||
|
tablet-mode-on { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true"; }
|
||||||
|
tablet-mode-off { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled false"; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The syntax is similar to key bindings.
|
||||||
|
Currently, only the `spawn` action are supported.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> In contrast to key bindings, switch event bindings are *always* executed, even when the session is locked.
|
||||||
|
|
||||||
|
### `lid-close`, `lid-open`
|
||||||
|
|
||||||
|
These events correspond to closing and opening of the laptop lid.
|
||||||
|
|
||||||
|
Note that niri will already automatically turn the internal laptop monitor on and off in accordance with the laptop lid.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
switch-events {
|
||||||
|
lid-close { spawn "notify-send" "The laptop lid is closed!"; }
|
||||||
|
lid-open { spawn "notify-send" "The laptop lid is open!"; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `tablet-mode-on`, `tablet-mode-off`
|
||||||
|
|
||||||
|
These events trigger when a convertible laptop goes into or out of tablet mode.
|
||||||
|
In tablet mode, the keyboard and mouse are usually inaccessible, so you can use these events to activate the on-screen keyboard.
|
||||||
|
|
||||||
|
```kdl
|
||||||
|
switch-events {
|
||||||
|
tablet-mode-on { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true"; }
|
||||||
|
tablet-mode-off { spawn "bash" "-c" "gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled false"; }
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -72,3 +72,5 @@ pub fn some_function() {
|
|||||||
// Code of the function.
|
// Code of the function.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also enable Rust memory allocation profiling with `--features=profile-with-tracy-allocations`.
|
||||||
|
|||||||
@@ -46,3 +46,10 @@ hotkey-overlay {
|
|||||||
skip-at-startup
|
skip-at-startup
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### How to run X11 apps like Steam or Discord?
|
||||||
|
|
||||||
|
To run X11 apps, you can use [xwayland-satellite](https://github.com/Supreeeme/xwayland-satellite).
|
||||||
|
Check [the Xwayland wiki page](./Xwayland.md) for instructions.
|
||||||
|
|
||||||
|
Keep in mind that you can run many Electron apps such as VSCode natively on Wayland by passing the right flags, e.g. `code --ozone-platform-hint=auto`
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ There are several gestures in niri.
|
|||||||
|
|
||||||
### Mouse
|
### Mouse
|
||||||
|
|
||||||
|
#### Interactive Move
|
||||||
|
|
||||||
|
<sup>Since: 0.1.10</sup>
|
||||||
|
|
||||||
|
You can move windows by holding <kbd>Mod</kbd> and the left mouse button.
|
||||||
|
|
||||||
|
You can customize the look of the window insertion preview in the `insert-hint` [layout config](./Configuration:-Layout.md) section.
|
||||||
|
|
||||||
#### Interactive Resize
|
#### Interactive Resize
|
||||||
|
|
||||||
<sup>Since: 0.1.6</sup>
|
<sup>Since: 0.1.6</sup>
|
||||||
|
|||||||
+13
-15
@@ -8,8 +8,9 @@ After installing, start niri from your display manager like GDM.
|
|||||||
Press <kbd>Super</kbd><kbd>T</kbd> to run a terminal ([Alacritty]) and <kbd>Super</kbd><kbd>D</kbd> to run an application launcher ([fuzzel]).
|
Press <kbd>Super</kbd><kbd>T</kbd> to run a terminal ([Alacritty]) and <kbd>Super</kbd><kbd>D</kbd> to run an application launcher ([fuzzel]).
|
||||||
To exit niri, press <kbd>Super</kbd><kbd>Shift</kbd><kbd>E</kbd>.
|
To exit niri, press <kbd>Super</kbd><kbd>Shift</kbd><kbd>E</kbd>.
|
||||||
|
|
||||||
If you're not using a display manager, you should run `niri-session` (systemd) or `niri --session` (not systemd) from a TTY.
|
If you're not using a display manager, you should run `niri-session` (systemd/dinit) or `niri --session` (others) from a TTY.
|
||||||
The `--session` flag will make niri import its environment variables globally into systemd and D-Bus, and start its D-Bus services.
|
The `--session` flag will make niri import its environment variables globally into the system manager and D-Bus, and start its D-Bus services.
|
||||||
|
The `niri-session` script will additionally start niri as a systemd/dinit service, which starts up a graphical session target required by some services like portals.
|
||||||
|
|
||||||
You can also run `niri` inside an existing desktop session.
|
You can also run `niri` inside an existing desktop session.
|
||||||
Then it will open as a window, where you can give it a try.
|
Then it will open as a window, where you can give it a try.
|
||||||
@@ -22,21 +23,13 @@ Finally, the [Xwayland](./Xwayland.md) page explains how to run X11 applications
|
|||||||
|
|
||||||
### NVIDIA
|
### NVIDIA
|
||||||
|
|
||||||
NVIDIA GPUs tend to have problems running niri (for example, the screen remains black upon starting from a TTY).
|
NVIDIA GPUs can have problems running niri (for example, the screen remains black upon starting from a TTY).
|
||||||
Sometimes, the problems can be fixed.
|
Sometimes, the problems can be fixed.
|
||||||
You can try the following:
|
You can try the following:
|
||||||
|
|
||||||
1. Update NVIDIA drivers. You need a GPU and drivers recent enough to support GBM.
|
1. Update NVIDIA drivers. You need a GPU and drivers recent enough to support GBM.
|
||||||
2. Make sure kernel modesetting is enabled. This usually involves adding `nvidia-drm.modeset=1` to the kernel command line. Find and follow a guide for your distribution. Guides from other Wayland compositors can help.
|
2. Make sure kernel modesetting is enabled. This usually involves adding `nvidia-drm.modeset=1` to the kernel command line. Find and follow a guide for your distribution. Guides from other Wayland compositors can help.
|
||||||
|
|
||||||
If niri runs but the screen flickers, try adding this into your niri config:
|
|
||||||
|
|
||||||
```
|
|
||||||
debug {
|
|
||||||
wait-for-frame-completion-before-queueing
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Asahi, ARM, and other kmsro devices
|
### Asahi, ARM, and other kmsro devices
|
||||||
|
|
||||||
On some of these systems, niri fails to correctly detect the primary render device.
|
On some of these systems, niri fails to correctly detect the primary render device.
|
||||||
@@ -69,6 +62,7 @@ If you still get a black screen, try using each of the `card` devices.
|
|||||||
### Nix/NixOS
|
### Nix/NixOS
|
||||||
|
|
||||||
There's a common problem of mesa drivers going out of sync with niri, so make sure your system mesa version matches the niri mesa version.
|
There's a common problem of mesa drivers going out of sync with niri, so make sure your system mesa version matches the niri mesa version.
|
||||||
|
When this happens, you usually see a black screen when trying to start niri from a TTY.
|
||||||
|
|
||||||
Also, on Intel graphics, you may need a workaround described [here](https://nixos.wiki/wiki/Intel_Graphics).
|
Also, on Intel graphics, you may need a workaround described [here](https://nixos.wiki/wiki/Intel_Graphics).
|
||||||
|
|
||||||
@@ -178,8 +172,10 @@ To do that, put files into the correct directories according to this table.
|
|||||||
| `resources/niri-session` | `/usr/bin/` |
|
| `resources/niri-session` | `/usr/bin/` |
|
||||||
| `resources/niri.desktop` | `/usr/share/wayland-sessions/` |
|
| `resources/niri.desktop` | `/usr/share/wayland-sessions/` |
|
||||||
| `resources/niri-portals.conf` | `/usr/share/xdg-desktop-portal/` |
|
| `resources/niri-portals.conf` | `/usr/share/xdg-desktop-portal/` |
|
||||||
| `resources/niri.service` | `/usr/lib/systemd/user/` |
|
| `resources/niri.service` (systemd) | `/usr/lib/systemd/user/` |
|
||||||
| `resources/niri-shutdown.target` | `/usr/lib/systemd/user/` |
|
| `resources/niri-shutdown.target` (systemd) | `/usr/lib/systemd/user/` |
|
||||||
|
| `resources/dinit/niri` (dinit) | `/usr/lib/dinit.d/user/` |
|
||||||
|
| `resources/dinit/niri-shutdown` (dinit) | `/usr/lib/dinit.d/user/` |
|
||||||
|
|
||||||
Doing this will make niri appear in GDM and other display managers.
|
Doing this will make niri appear in GDM and other display managers.
|
||||||
|
|
||||||
@@ -195,8 +191,10 @@ These may vary depending on your distribution.
|
|||||||
| `resources/niri-session` | `/usr/local/bin/` |
|
| `resources/niri-session` | `/usr/local/bin/` |
|
||||||
| `resources/niri.desktop` | `/usr/local/share/wayland-sessions/` |
|
| `resources/niri.desktop` | `/usr/local/share/wayland-sessions/` |
|
||||||
| `resources/niri-portals.conf` | `/usr/local/share/xdg-desktop-portal/` |
|
| `resources/niri-portals.conf` | `/usr/local/share/xdg-desktop-portal/` |
|
||||||
| `resources/niri.service` | `/etc/systemd/user/` |
|
| `resources/niri.service` (systemd) | `/etc/systemd/user/` |
|
||||||
| `resources/niri-shutdown.target` | `/etc/systemd/user/` |
|
| `resources/niri-shutdown.target` (systemd) | `/etc/systemd/user/` |
|
||||||
|
| `resources/dinit/niri` (dinit) | `/etc/dinit.d/user/` |
|
||||||
|
| `resources/dinit/niri-shutdown` (dinit) | `/etc/dinit.d/user/` |
|
||||||
|
|
||||||
[Alacritty]: https://github.com/alacritty/alacritty
|
[Alacritty]: https://github.com/alacritty/alacritty
|
||||||
[fuzzel]: https://codeberg.org/dnkl/fuzzel
|
[fuzzel]: https://codeberg.org/dnkl/fuzzel
|
||||||
|
|||||||
@@ -33,3 +33,10 @@ systemctl --user edit --full plasma-polkit-agent.service
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then add `After=graphical-session.target`.
|
Then add `After=graphical-session.target`.
|
||||||
|
|
||||||
|
### Xwayland
|
||||||
|
|
||||||
|
To run X11 apps like Steam or Discord, you can use [xwayland-satellite].
|
||||||
|
Check [the Xwayland wiki page](./Xwayland.md) for instructions.
|
||||||
|
|
||||||
|
[xwayland-satellite]: https://github.com/Supreeeme/xwayland-satellite
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
* [Input](./Configuration:-Input.md)
|
* [Input](./Configuration:-Input.md)
|
||||||
* [Outputs](./Configuration:-Outputs.md)
|
* [Outputs](./Configuration:-Outputs.md)
|
||||||
* [Key Bindings](./Configuration:-Key-Bindings.md)
|
* [Key Bindings](./Configuration:-Key-Bindings.md)
|
||||||
|
* [Switch Events](./Configuration:-Switch-Events.md)
|
||||||
* [Layout](./Configuration:-Layout.md)
|
* [Layout](./Configuration:-Layout.md)
|
||||||
* [Named Workspaces](./Configuration:-Named-Workspaces.md)
|
* [Named Workspaces](./Configuration:-Named-Workspaces.md)
|
||||||
* [Miscellaneous](./Configuration:-Miscellaneous.md)
|
* [Miscellaneous](./Configuration:-Miscellaneous.md)
|
||||||
|
|||||||
Reference in New Issue
Block a user