mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 012340c5f4 | |||
| 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
|
||||
continue-on-error: true
|
||||
|
||||
- run: nix build
|
||||
- run: nix flake check
|
||||
continue-on-error: true
|
||||
|
||||
publish-wiki:
|
||||
|
||||
Generated
+335
-353
File diff suppressed because it is too large
Load Diff
+17
-17
@@ -11,15 +11,15 @@ repository = "https://github.com/YaLTeR/niri"
|
||||
rust-version = "1.77"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.88"
|
||||
anyhow = "1.0.90"
|
||||
bitflags = "2.6.0"
|
||||
clap = { version = "4.5.17", features = ["derive"] }
|
||||
clap = { version = "4.5.20", features = ["derive"] }
|
||||
k9 = "0.12.0"
|
||||
serde = { version = "1.0.210", 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-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]
|
||||
git = "https://github.com/Smithay/smithay.git"
|
||||
@@ -50,31 +50,31 @@ async-channel = "2.3.1"
|
||||
async-io = { version = "1.13.0", optional = true }
|
||||
atomic = "0.6.0"
|
||||
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"] }
|
||||
clap = { workspace = true, features = ["string"] }
|
||||
directories = "5.0.1"
|
||||
drm-ffi = "0.8.0"
|
||||
drm-ffi = "0.9.0"
|
||||
fastrand = "2.1.1"
|
||||
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"
|
||||
glam = "0.29.0"
|
||||
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 }
|
||||
libc = "0.2.158"
|
||||
libc = "0.2.161"
|
||||
libdisplay-info = "0.1.0"
|
||||
log = { version = "0.4.22", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
niri-config = { version = "0.1.9", path = "niri-config" }
|
||||
niri-ipc = { version = "0.1.9", path = "niri-ipc", features = ["clap"] }
|
||||
notify-rust = { version = "~4.10.0", optional = true }
|
||||
ordered-float = "4.2.2"
|
||||
pango = { version = "0.20.1", features = ["v1_44"] }
|
||||
pangocairo = "0.20.1"
|
||||
ordered-float = "4.4.0"
|
||||
pango = { version = "0.20.4", features = ["v1_44"] }
|
||||
pangocairo = "0.20.4"
|
||||
pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", optional = true, features = ["v0_3_33"] }
|
||||
png = "0.17.13"
|
||||
portable-atomic = { version = "1.7.0", default-features = false, features = ["float"] }
|
||||
profiling = "1.0.15"
|
||||
sd-notify = "0.4.2"
|
||||
png = "0.17.14"
|
||||
portable-atomic = { version = "1.9.0", default-features = false, features = ["float"] }
|
||||
profiling = "1.0.16"
|
||||
sd-notify = "0.4.3"
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
smithay-drm-extras.workspace = true
|
||||
@@ -109,7 +109,7 @@ features = [
|
||||
approx = "0.5.1"
|
||||
k9.workspace = true
|
||||
proptest = "1.5.0"
|
||||
proptest-derive = "0.5.0"
|
||||
proptest-derive = { version = "0.5.0", features = ["boxed_union"] }
|
||||
xshell = "0.2.6"
|
||||
|
||||
[features]
|
||||
|
||||
Generated
+18
-92
@@ -1,65 +1,5 @@
|
||||
{
|
||||
"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": {
|
||||
"locked": {
|
||||
"lastModified": 1710156097,
|
||||
@@ -77,11 +17,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1724395761,
|
||||
"narHash": "sha256-zRkDV/nbrnp3Y8oCADf5ETl1sDrdmAW6/bBVJ8EbIdQ=",
|
||||
"lastModified": 1726365531,
|
||||
"narHash": "sha256-luAKNxWZ+ZN0kaHchx1OdLQ71n81Y31ryNPWP1YRDZc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ae815cee91b417be55d43781eb4b73ae1ecc396c",
|
||||
"rev": "9299cdf978e15f448cf82667b0ffdd480b44ee48",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -93,42 +33,28 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1722449213,
|
||||
"narHash": "sha256-1na4m2PNH99syz2g/WQ+Hr3RfY7k4H8NBnmkr5dFDXw=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "c8e41d95061543715b30880932ec3dc24c42d7ae",
|
||||
"lastModified": 1727663505,
|
||||
"narHash": "sha256-83j/GrHsx8GFUcQofKh+PRPz6pz8sxAsZyT/HCNdey8=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "c2099c6c7599ea1980151b8b6247a8f93e1806ee",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"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",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,101 +4,248 @@
|
||||
|
||||
inputs = {
|
||||
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";
|
||||
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";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
crane,
|
||||
nix-filter,
|
||||
flake-utils,
|
||||
fenix,
|
||||
...
|
||||
}: let
|
||||
systems = ["aarch64-linux" "x86_64-linux"];
|
||||
in
|
||||
flake-utils.lib.eachSystem systems (
|
||||
system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
toolchain = fenix.packages.${system}.complete.toolchain;
|
||||
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
nix-filter,
|
||||
rust-overlay,
|
||||
}:
|
||||
let
|
||||
niri-package =
|
||||
{
|
||||
lib,
|
||||
cairo,
|
||||
clang,
|
||||
dbus,
|
||||
libGL,
|
||||
libclang,
|
||||
libdisplay-info,
|
||||
libinput,
|
||||
libseat,
|
||||
libxkbcommon,
|
||||
mesa,
|
||||
pango,
|
||||
pipewire,
|
||||
pkg-config,
|
||||
rustPlatform,
|
||||
systemd,
|
||||
wayland,
|
||||
withDbus ? true,
|
||||
withSystemd ? true,
|
||||
withScreencastSupport ? true,
|
||||
withDinit ? false,
|
||||
}:
|
||||
|
||||
craneArgs = {
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "niri";
|
||||
version = self.rev or "dirty";
|
||||
version = self.shortRev or self.dirtyShortRev or "unknown";
|
||||
|
||||
src = nixpkgs.lib.cleanSourceWith {
|
||||
src = craneLib.path ./.;
|
||||
filter = path: type:
|
||||
(builtins.match "resources" path == null)
|
||||
|| ((craneLib.filterCargoSources path type)
|
||||
&& (builtins.match "niri-visual-tests" path == null));
|
||||
src = nix-filter.lib.filter {
|
||||
root = self;
|
||||
include = [
|
||||
"niri-config"
|
||||
"niri-ipc"
|
||||
"niri-visual-tests"
|
||||
"resources"
|
||||
"src"
|
||||
./Cargo.lock
|
||||
./Cargo.toml
|
||||
];
|
||||
};
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkg-config
|
||||
autoPatchelfHook
|
||||
postPatch = ''
|
||||
patchShebangs resources/niri-session
|
||||
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
|
||||
gdk-pixbuf
|
||||
graphene
|
||||
gtk4
|
||||
libadwaita
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
wayland
|
||||
systemd # For libudev
|
||||
seatd # For libseat
|
||||
libxkbcommon
|
||||
libdisplay-info
|
||||
libinput
|
||||
mesa # For libgbm
|
||||
fontconfig
|
||||
stdenv.cc.cc.lib
|
||||
pipewire
|
||||
pango
|
||||
cairo
|
||||
glib
|
||||
pixman
|
||||
];
|
||||
buildInputs =
|
||||
[
|
||||
cairo
|
||||
dbus
|
||||
libGL
|
||||
libdisplay-info
|
||||
libinput
|
||||
libseat
|
||||
libxkbcommon
|
||||
mesa # libgbm
|
||||
pango
|
||||
wayland
|
||||
]
|
||||
++ lib.optional (withDbus || withScreencastSupport || withSystemd) dbus
|
||||
++ lib.optional withScreencastSupport pipewire
|
||||
# Also includes libudev
|
||||
++ lib.optional withSystemd systemd;
|
||||
|
||||
runtimeDependencies = with pkgs; [
|
||||
wayland
|
||||
mesa
|
||||
libglvnd # For libEGL
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
libxkbcommon
|
||||
];
|
||||
buildFeatures =
|
||||
lib.optional withDbus "dbus"
|
||||
++ lib.optional withDinit "dinit"
|
||||
++ lib.optional withScreencastSupport "xdp-gnome-screencast"
|
||||
++ lib.optional withSystemd "systemd";
|
||||
buildNoDefaultFeatures = true;
|
||||
|
||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath craneArgs.runtimeDependencies; # Needed for tests to find libxkbcommon
|
||||
postInstall =
|
||||
''
|
||||
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()`
|
||||
CARGO_BUILD_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;
|
||||
niri = craneLib.buildPackage (craneArgs // {inherit cargoArtifacts;});
|
||||
in {
|
||||
formatter = pkgs.alejandra;
|
||||
inherit (nixpkgs) lib;
|
||||
# Support all Linux systems that the nixpkgs flake exposes
|
||||
systems = lib.intersectLists lib.systems.flakeExposed lib.platforms.linux;
|
||||
|
||||
checks.niri = niri;
|
||||
packages.default = niri;
|
||||
forAllSystems = lib.genAttrs systems;
|
||||
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 {
|
||||
inputsFrom = [niri];
|
||||
devShells = forAllSystems (
|
||||
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);
|
||||
inherit (niri) LIBCLANG_PATH;
|
||||
};
|
||||
}
|
||||
);
|
||||
nativeBuildInputs = [
|
||||
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"`
|
||||
inherit (niri) CARGO_BUILD_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 { };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ csscolorparser = "0.7.0"
|
||||
knuffel = "3.2.0"
|
||||
miette = "5.10.0"
|
||||
niri-ipc = { version = "0.1.9", path = "../niri-ipc" }
|
||||
regex = "1.10.6"
|
||||
regex = "1.11.0"
|
||||
smithay = { workspace = true, features = ["backend_libinput"] }
|
||||
tracing.workspace = true
|
||||
tracy-client.workspace = true
|
||||
@@ -21,4 +21,4 @@ tracy-client.workspace = true
|
||||
[dev-dependencies]
|
||||
k9.workspace = true
|
||||
miette = { version = "5.10.0", features = ["fancy"] }
|
||||
pretty_assertions = "1.4.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
|
||||
+154
-2
@@ -54,6 +54,8 @@ pub struct Config {
|
||||
#[knuffel(child, default)]
|
||||
pub binds: Binds,
|
||||
#[knuffel(child, default)]
|
||||
pub switch_events: SwitchBinds,
|
||||
#[knuffel(child, default)]
|
||||
pub debug: DebugConfig,
|
||||
#[knuffel(children(name = "workspace"))]
|
||||
pub workspaces: Vec<Workspace>,
|
||||
@@ -70,6 +72,8 @@ pub struct Input {
|
||||
#[knuffel(child, default)]
|
||||
pub trackpoint: Trackpoint,
|
||||
#[knuffel(child, default)]
|
||||
pub trackball: Trackball,
|
||||
#[knuffel(child, default)]
|
||||
pub tablet: Tablet,
|
||||
#[knuffel(child, default)]
|
||||
pub touch: Touch,
|
||||
@@ -174,6 +178,8 @@ pub struct Touchpad {
|
||||
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, unwrap(argument, str))]
|
||||
pub tap_button_map: Option<TapButtonMap>,
|
||||
#[knuffel(child)]
|
||||
@@ -196,6 +202,8 @@ pub struct Mouse {
|
||||
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)]
|
||||
@@ -214,6 +222,28 @@ pub struct Trackpoint {
|
||||
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 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)]
|
||||
pub middle_emulation: bool,
|
||||
}
|
||||
@@ -391,6 +421,8 @@ pub struct Layout {
|
||||
pub focus_ring: FocusRing,
|
||||
#[knuffel(child, default)]
|
||||
pub border: Border,
|
||||
#[knuffel(child, default)]
|
||||
pub insert_hint: InsertHint,
|
||||
#[knuffel(child, unwrap(children), default)]
|
||||
pub preset_column_widths: Vec<PresetSize>,
|
||||
#[knuffel(child)]
|
||||
@@ -412,6 +444,7 @@ impl Default for Layout {
|
||||
Self {
|
||||
focus_ring: Default::default(),
|
||||
border: Default::default(),
|
||||
insert_hint: Default::default(),
|
||||
preset_column_widths: Default::default(),
|
||||
default_column_width: Default::default(),
|
||||
center_focused_column: Default::default(),
|
||||
@@ -558,6 +591,23 @@ 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,
|
||||
}
|
||||
|
||||
impl Default for InsertHint {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
color: Color::from_rgba8_unpremul(127, 200, 255, 128),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RGB color in [0, 1] with unpremultiplied alpha.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Color {
|
||||
@@ -615,6 +665,10 @@ pub struct Cursor {
|
||||
pub xcursor_theme: String,
|
||||
#[knuffel(child, unwrap(argument), default = 24)]
|
||||
pub xcursor_size: u8,
|
||||
#[knuffel(child)]
|
||||
pub hide_on_key_press: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub hide_after_inactive_ms: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for Cursor {
|
||||
@@ -622,6 +676,8 @@ impl Default for Cursor {
|
||||
Self {
|
||||
xcursor_theme: String::from("default"),
|
||||
xcursor_size: 24,
|
||||
hide_on_key_press: false,
|
||||
hide_after_inactive_ms: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1054,6 +1110,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.
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub enum Action {
|
||||
@@ -1062,6 +1136,7 @@ pub enum Action {
|
||||
ChangeVt(i32),
|
||||
Suspend,
|
||||
PowerOffMonitors,
|
||||
PowerOnMonitors,
|
||||
ToggleDebugTint,
|
||||
DebugToggleOpaqueRegions,
|
||||
DebugToggleDamage,
|
||||
@@ -1115,7 +1190,11 @@ pub enum Action {
|
||||
MoveWindowDownOrToWorkspaceDown,
|
||||
MoveWindowUpOrToWorkspaceUp,
|
||||
ConsumeOrExpelWindowLeft,
|
||||
#[knuffel(skip)]
|
||||
ConsumeOrExpelWindowLeftById(u64),
|
||||
ConsumeOrExpelWindowRight,
|
||||
#[knuffel(skip)]
|
||||
ConsumeOrExpelWindowRightById(u64),
|
||||
ConsumeWindowIntoColumn,
|
||||
ExpelWindowFromColumn,
|
||||
CenterColumn,
|
||||
@@ -1176,6 +1255,7 @@ impl From<niri_ipc::Action> for Action {
|
||||
match value {
|
||||
niri_ipc::Action::Quit { skip_confirmation } => Self::Quit(skip_confirmation),
|
||||
niri_ipc::Action::PowerOffMonitors {} => Self::PowerOffMonitors,
|
||||
niri_ipc::Action::PowerOnMonitors {} => Self::PowerOnMonitors,
|
||||
niri_ipc::Action::Spawn { command } => Self::Spawn(command),
|
||||
niri_ipc::Action::DoScreenTransition { delay_ms } => Self::DoScreenTransition(delay_ms),
|
||||
niri_ipc::Action::Screenshot {} => Self::Screenshot,
|
||||
@@ -1221,8 +1301,18 @@ impl From<niri_ipc::Action> for Action {
|
||||
Self::MoveWindowDownOrToWorkspaceDown
|
||||
}
|
||||
niri_ipc::Action::MoveWindowUpOrToWorkspaceUp {} => Self::MoveWindowUpOrToWorkspaceUp,
|
||||
niri_ipc::Action::ConsumeOrExpelWindowLeft {} => Self::ConsumeOrExpelWindowLeft,
|
||||
niri_ipc::Action::ConsumeOrExpelWindowRight {} => Self::ConsumeOrExpelWindowRight,
|
||||
niri_ipc::Action::ConsumeOrExpelWindowLeft { id: None } => {
|
||||
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::ExpelWindowFromColumn {} => Self::ExpelWindowFromColumn,
|
||||
niri_ipc::Action::CenterColumn {} => Self::CenterColumn,
|
||||
@@ -2862,6 +2952,7 @@ mod tests {
|
||||
accel-speed 0.2
|
||||
accel-profile "flat"
|
||||
scroll-method "two-finger"
|
||||
scroll-button 272
|
||||
tap-button-map "left-middle-right"
|
||||
disabled-on-external-mouse
|
||||
}
|
||||
@@ -2871,6 +2962,7 @@ mod tests {
|
||||
accel-speed 0.4
|
||||
accel-profile "flat"
|
||||
scroll-method "no-scroll"
|
||||
scroll-button 273
|
||||
middle-emulation
|
||||
}
|
||||
|
||||
@@ -2880,6 +2972,18 @@ mod tests {
|
||||
accel-speed 0.0
|
||||
accel-profile "flat"
|
||||
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 {
|
||||
@@ -2944,6 +3048,10 @@ mod tests {
|
||||
}
|
||||
|
||||
center-focused-column "on-overflow"
|
||||
|
||||
insert-hint {
|
||||
color "rgb(255, 200, 127)"
|
||||
}
|
||||
}
|
||||
|
||||
spawn-at-startup "alacritty" "-e" "fish"
|
||||
@@ -2953,6 +3061,8 @@ mod tests {
|
||||
cursor {
|
||||
xcursor-theme "breeze_cursors"
|
||||
xcursor-size 16
|
||||
hide-on-key-press
|
||||
hide-after-inactive-ms 3000
|
||||
}
|
||||
|
||||
screenshot-path "~/Screenshots/screenshot.png"
|
||||
@@ -3013,6 +3123,11 @@ mod tests {
|
||||
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 {
|
||||
render-drm-device "/dev/dri/renderD129"
|
||||
}
|
||||
@@ -3045,6 +3160,7 @@ mod tests {
|
||||
accel_speed: 0.2,
|
||||
accel_profile: Some(AccelProfile::Flat),
|
||||
scroll_method: Some(ScrollMethod::TwoFinger),
|
||||
scroll_button: Some(272),
|
||||
tap_button_map: Some(TapButtonMap::LeftMiddleRight),
|
||||
left_handed: false,
|
||||
disabled_on_external_mouse: true,
|
||||
@@ -3056,6 +3172,7 @@ mod tests {
|
||||
accel_speed: 0.4,
|
||||
accel_profile: Some(AccelProfile::Flat),
|
||||
scroll_method: Some(ScrollMethod::NoScroll),
|
||||
scroll_button: Some(273),
|
||||
left_handed: false,
|
||||
middle_emulation: true,
|
||||
},
|
||||
@@ -3065,8 +3182,19 @@ mod tests {
|
||||
accel_speed: 0.0,
|
||||
accel_profile: Some(AccelProfile::Flat),
|
||||
scroll_method: Some(ScrollMethod::OnButtonDown),
|
||||
scroll_button: Some(274),
|
||||
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 {
|
||||
off: false,
|
||||
map_to_output: Some("eDP-1".to_owned()),
|
||||
@@ -3122,6 +3250,10 @@ mod tests {
|
||||
active_gradient: None,
|
||||
inactive_gradient: None,
|
||||
},
|
||||
insert_hint: InsertHint {
|
||||
off: false,
|
||||
color: Color::from_rgba8_unpremul(255, 200, 127, 255),
|
||||
},
|
||||
preset_column_widths: vec![
|
||||
PresetSize::Proportion(0.25),
|
||||
PresetSize::Proportion(0.5),
|
||||
@@ -3154,6 +3286,8 @@ mod tests {
|
||||
cursor: Cursor {
|
||||
xcursor_theme: String::from("breeze_cursors"),
|
||||
xcursor_size: 16,
|
||||
hide_on_key_press: true,
|
||||
hide_after_inactive_ms: Some(3000),
|
||||
},
|
||||
screenshot_path: Some(String::from("~/Screenshots/screenshot.png")),
|
||||
hotkey_overlay: HotkeyOverlay {
|
||||
@@ -3345,6 +3479,24 @@ mod tests {
|
||||
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 {
|
||||
render_drm_device: Some(PathBuf::from("/dev/dri/renderD129")),
|
||||
..Default::default()
|
||||
|
||||
+26
-4
@@ -132,6 +132,8 @@ pub enum Action {
|
||||
},
|
||||
/// Power off all monitors via DPMS.
|
||||
PowerOffMonitors {},
|
||||
/// Power on all monitors via DPMS.
|
||||
PowerOnMonitors {},
|
||||
/// Spawn a command.
|
||||
Spawn {
|
||||
/// Command to spawn.
|
||||
@@ -240,10 +242,30 @@ pub enum Action {
|
||||
MoveWindowDownOrToWorkspaceDown {},
|
||||
/// Move the focused window up in a column or to the workspace above.
|
||||
MoveWindowUpOrToWorkspaceUp {},
|
||||
/// Consume or expel the focused window left.
|
||||
ConsumeOrExpelWindowLeft {},
|
||||
/// Consume or expel the focused window right.
|
||||
ConsumeOrExpelWindowRight {},
|
||||
/// Consume or expel a window left.
|
||||
#[cfg_attr(
|
||||
feature = "clap",
|
||||
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.
|
||||
ConsumeWindowIntoColumn {},
|
||||
/// Expel the focused window from the column.
|
||||
|
||||
@@ -10,7 +10,7 @@ repository.workspace = true
|
||||
[dependencies]
|
||||
adw = { version = "0.7.0", package = "libadwaita", features = ["v1_4"] }
|
||||
anyhow.workspace = true
|
||||
gtk = { version = "0.9.1", package = "gtk4", features = ["v4_12"] }
|
||||
gtk = { version = "0.9.2", package = "gtk4", features = ["v4_12"] }
|
||||
niri = { version = "0.1.9", path = ".." }
|
||||
niri-config = { version = "0.1.9", path = "../niri-config" }
|
||||
smithay.workspace = true
|
||||
|
||||
@@ -198,11 +198,7 @@ impl TestCase for Layout {
|
||||
}
|
||||
|
||||
fn are_animations_ongoing(&self) -> bool {
|
||||
self.layout
|
||||
.monitor_for_output(&self.output)
|
||||
.unwrap()
|
||||
.are_animations_ongoing()
|
||||
|| !self.steps.is_empty()
|
||||
self.layout.are_animations_ongoing(Some(&self.output)) || !self.steps.is_empty()
|
||||
}
|
||||
|
||||
fn advance_animations(&mut self, mut current_time: Duration) {
|
||||
@@ -233,7 +229,6 @@ impl TestCase for Layout {
|
||||
.monitor_for_output(&self.output)
|
||||
.unwrap()
|
||||
.render_elements(renderer, RenderTarget::Output)
|
||||
.into_iter()
|
||||
.map(|elem| Box::new(elem) as _)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -40,6 +40,16 @@ input {
|
||||
// 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.
|
||||
// warp-mouse-to-focus
|
||||
|
||||
@@ -202,7 +212,9 @@ layout {
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
|
||||
// You can change the path where screenshots are saved.
|
||||
@@ -250,6 +262,13 @@ window-rule {
|
||||
// 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 {
|
||||
// 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
|
||||
@@ -422,7 +441,9 @@ binds {
|
||||
// Switches focus between the current and the previous workspace.
|
||||
// Mod+Tab { focus-workspace-previous; }
|
||||
|
||||
// Consume one window from the right into the focused column.
|
||||
Mod+Comma { consume-window-into-column; }
|
||||
// Expel one window from the focused column to the right.
|
||||
Mod+Period { expel-window-from-column; }
|
||||
|
||||
// There are also commands that consume or expel a single window to the side.
|
||||
|
||||
@@ -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
|
||||
+47
-27
@@ -11,31 +11,51 @@ if [ -n "$SHELL" ] &&
|
||||
fi
|
||||
fi
|
||||
|
||||
# Make sure there's no already running session.
|
||||
if systemctl --user -q is-active niri.service; then
|
||||
echo 'A niri session is already running.'
|
||||
exit 1
|
||||
# Try to detect the service manager that is being used
|
||||
if hash systemctl &> /dev/null; then
|
||||
# Make sure there's no already running session.
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
+15
-20
@@ -1019,6 +1019,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 =
|
||||
tracy_client::FrameName::new_leak(format!("vblank on {connector_name}"));
|
||||
let time_since_presentation_plot_name = tracy_client::PlotName::new_leak(format!(
|
||||
@@ -1050,15 +1058,14 @@ impl Tty {
|
||||
|
||||
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 {
|
||||
// Redraw the new monitor.
|
||||
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(())
|
||||
@@ -1659,10 +1666,9 @@ impl Tty {
|
||||
}
|
||||
|
||||
for device in self.devices.values_mut() {
|
||||
for (crtc, surface) in device.surfaces.iter_mut() {
|
||||
set_crtc_active(&device.drm, *crtc, false);
|
||||
if let Err(err) = surface.compositor.reset_state() {
|
||||
warn!("error resetting surface state: {err:?}");
|
||||
for surface in device.surfaces.values_mut() {
|
||||
if let Err(err) = surface.compositor.clear() {
|
||||
warn!("error clearing drm surface: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2154,17 +2160,6 @@ fn get_drm_property(
|
||||
.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 {
|
||||
let clock = mode.clock() as u64;
|
||||
let htotal = mode.hsync().2 as u64;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
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::wayland_server::protocol::wl_buffer;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
@@ -297,13 +297,52 @@ impl CompositorHandler for State {
|
||||
&self.niri.cursor_manager.cursor_image(),
|
||||
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.
|
||||
self.niri.queue_redraw_all();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
self.niri.queue_redraw_all();
|
||||
return;
|
||||
|
||||
+78
-7
@@ -12,7 +12,9 @@ use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||
use smithay::backend::drm::DrmNode;
|
||||
use smithay::backend::input::TabletToolDescriptor;
|
||||
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::output::Output;
|
||||
use smithay::reexports::rustix::fs::{fcntl_setfl, OFlags};
|
||||
@@ -22,8 +24,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_surface::WlSurface;
|
||||
use smithay::reexports::wayland_server::Resource;
|
||||
use smithay::utils::{Logical, Rectangle, Size};
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size};
|
||||
use smithay::wayland::compositor::{get_parent, with_states};
|
||||
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
|
||||
use smithay::wayland::drm_lease::{
|
||||
DrmLease, DrmLeaseBuilder, DrmLeaseHandler, DrmLeaseRequest, DrmLeaseState, LeaseRejected,
|
||||
@@ -33,7 +35,7 @@ use smithay::wayland::idle_inhibit::IdleInhibitHandler;
|
||||
use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
|
||||
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
||||
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::{
|
||||
SecurityContext, SecurityContextHandler, SecurityContextListenerSource,
|
||||
};
|
||||
@@ -64,7 +66,7 @@ use smithay::{
|
||||
};
|
||||
|
||||
pub use crate::handlers::xdg_shell::KdeDecorationsModeState;
|
||||
use crate::niri::{ClientState, State};
|
||||
use crate::niri::{ClientState, DndIcon, State};
|
||||
use crate::protocols::foreign_toplevel::{
|
||||
self, ForeignToplevelHandler, ForeignToplevelManagerState,
|
||||
};
|
||||
@@ -141,6 +143,59 @@ impl PointerConstraintsHandler for State {
|
||||
&self.niri.pointer_focus,
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Logically the following two checks should always succeed (so, they should print
|
||||
// error!()s if they fail). However, currently both can fail because niri's pointer focus
|
||||
// doesn't take pointer grabs into account. So if you start, say, a middle-drag in Blender,
|
||||
// then touchpad-swipe the window away, the niri pointer focus will change, even though the
|
||||
// real pointer focus remains on the Blender surface due to the click grab.
|
||||
//
|
||||
// FIXME: add error!()s when niri pointer focus takes grabs into account. Alternatively,
|
||||
// recompute the surface origin here (but that is a bit clunky).
|
||||
let Some((ref focused_surface, origin)) = self.niri.pointer_focus.surface else {
|
||||
return;
|
||||
};
|
||||
|
||||
if focused_surface != 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);
|
||||
|
||||
@@ -225,7 +280,23 @@ impl ClientDndGrabHandler for State {
|
||||
icon: Option<WlSurface>,
|
||||
_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
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
@@ -403,7 +474,7 @@ impl ForeignToplevelHandler for State {
|
||||
if &requested_output != current_output {
|
||||
self.niri
|
||||
.layout
|
||||
.move_window_to_output(&window, &requested_output);
|
||||
.move_to_output(Some(&window), &requested_output, None);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+153
-55
@@ -23,12 +23,13 @@ use smithay::wayland::compositor::{
|
||||
};
|
||||
use smithay::wayland::dmabuf::get_dmabuf;
|
||||
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::wlr_layer::{self, Layer};
|
||||
use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler;
|
||||
use smithay::wayland::shell::xdg::{
|
||||
PopupSurface, PositionerState, ToplevelSurface, XdgPopupSurfaceData, XdgShellHandler,
|
||||
XdgShellState, XdgToplevelSurfaceData,
|
||||
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
|
||||
XdgToplevelSurfaceData,
|
||||
};
|
||||
use smithay::wayland::xdg_foreign::{XdgForeignHandler, XdgForeignState};
|
||||
use smithay::{
|
||||
@@ -36,8 +37,11 @@ use smithay::{
|
||||
};
|
||||
use tracing::field::Empty;
|
||||
|
||||
use crate::input::move_grab::MoveGrab;
|
||||
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::niri::{PopupGrabState, State};
|
||||
use crate::utils::transaction::Transaction;
|
||||
@@ -65,8 +69,96 @@ impl XdgShellHandler for State {
|
||||
}
|
||||
}
|
||||
|
||||
fn move_request(&mut self, _surface: ToplevelSurface, _seat: WlSeat, _serial: Serial) {
|
||||
// FIXME
|
||||
fn move_request(&mut self, surface: ToplevelSurface, _seat: WlSeat, serial: Serial) {
|
||||
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().downcast_ref::<DnDGrab<Self>>().is_some();
|
||||
|
||||
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().downcast_ref::<DnDGrab<Self>>().is_some();
|
||||
|
||||
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);
|
||||
self.niri.pointer_grab_ongoing = true;
|
||||
}
|
||||
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(
|
||||
@@ -76,24 +168,39 @@ impl XdgShellHandler for State {
|
||||
serial: Serial,
|
||||
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();
|
||||
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 {
|
||||
return;
|
||||
};
|
||||
@@ -128,14 +235,26 @@ impl XdgShellHandler for State {
|
||||
}
|
||||
}
|
||||
|
||||
let grab = ResizeGrab::new(start_data, window.clone());
|
||||
|
||||
if !self.niri.layout.interactive_resize_begin(window, edges) {
|
||||
if !self
|
||||
.niri
|
||||
.layout
|
||||
.interactive_resize_begin(window.clone(), edges)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri.pointer_grab_ongoing = true;
|
||||
match start_data {
|
||||
PointerOrTouchStartData::Pointer(start_data) => {
|
||||
let grab = ResizeGrab::new(start_data, window);
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri.pointer_grab_ongoing = true;
|
||||
}
|
||||
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(
|
||||
@@ -261,7 +380,7 @@ impl XdgShellHandler for State {
|
||||
|
||||
// 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.
|
||||
if initial_configure_sent(&surface) {
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
}
|
||||
}
|
||||
@@ -288,7 +407,7 @@ impl XdgShellHandler for State {
|
||||
if &requested_output != current_output {
|
||||
self.niri
|
||||
.layout
|
||||
.move_window_to_output(&window, &requested_output);
|
||||
.move_to_output(Some(&window), &requested_output, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +451,7 @@ impl XdgShellHandler for State {
|
||||
|
||||
*output = mon
|
||||
.filter(|(_, parent)| !parent)
|
||||
.map(|(mon, _)| mon.output.clone());
|
||||
.map(|(mon, _)| mon.output().clone());
|
||||
let mon = mon.map(|(mon, _)| mon);
|
||||
|
||||
let ws = mon
|
||||
@@ -416,7 +535,7 @@ impl XdgShellHandler for State {
|
||||
|
||||
*output = mon
|
||||
.filter(|(_, parent)| !parent)
|
||||
.map(|(mon, _)| mon.output.clone());
|
||||
.map(|(mon, _)| mon.output().clone());
|
||||
let mon = mon.map(|(mon, _)| mon);
|
||||
|
||||
let ws = workspace_name
|
||||
@@ -552,7 +671,7 @@ impl XdgDecorationHandler for State {
|
||||
|
||||
// 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.
|
||||
if initial_configure_sent(&toplevel) {
|
||||
if toplevel.is_initial_configure_sent() {
|
||||
toplevel.send_configure();
|
||||
}
|
||||
}
|
||||
@@ -565,7 +684,7 @@ impl XdgDecorationHandler for State {
|
||||
|
||||
// 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.
|
||||
if initial_configure_sent(&toplevel) {
|
||||
if toplevel.is_initial_configure_sent() {
|
||||
toplevel.send_configure();
|
||||
}
|
||||
}
|
||||
@@ -620,18 +739,6 @@ impl XdgForeignHandler for 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 {
|
||||
pub fn send_initial_configure(&mut self, toplevel: &ToplevelSurface) {
|
||||
let _span = tracy_client::span!("State::send_initial_configure");
|
||||
@@ -706,7 +813,7 @@ impl State {
|
||||
// mapped, it fetches the possibly changed parent's output again, and shows up there.
|
||||
let output = mon
|
||||
.filter(|(_, parent)| !parent)
|
||||
.map(|(mon, _)| mon.output.clone());
|
||||
.map(|(mon, _)| mon.output().clone());
|
||||
let mon = mon.map(|(mon, _)| mon);
|
||||
|
||||
let mut width = None;
|
||||
@@ -759,7 +866,7 @@ impl State {
|
||||
width,
|
||||
is_full_width,
|
||||
output,
|
||||
workspace_name: ws.and_then(|w| w.name.clone()),
|
||||
workspace_name: ws.and_then(|w| w.name().cloned()),
|
||||
};
|
||||
|
||||
toplevel.send_configure();
|
||||
@@ -788,16 +895,7 @@ impl State {
|
||||
if let Some(popup) = self.niri.popups.find_popup(surface) {
|
||||
match popup {
|
||||
PopupKind::Xdg(ref popup) => {
|
||||
let initial_configure_sent = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgPopupSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
if !initial_configure_sent {
|
||||
if !popup.is_initial_configure_sent() {
|
||||
if let Some(output) = self.output_for_popup(&PopupKind::Xdg(popup.clone()))
|
||||
{
|
||||
let scale = output.current_scale();
|
||||
|
||||
+281
-36
@@ -6,14 +6,15 @@ use std::time::Duration;
|
||||
|
||||
use calloop::timer::{TimeoutAction, Timer};
|
||||
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 smithay::backend::input::{
|
||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
|
||||
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _,
|
||||
InputBackend, InputEvent, KeyState, KeyboardKeyEvent, MouseButton, PointerAxisEvent,
|
||||
PointerButtonEvent, PointerMotionEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
|
||||
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
|
||||
InputBackend, InputEvent, KeyState, KeyboardKeyEvent, Keycode, MouseButton, PointerAxisEvent,
|
||||
PointerButtonEvent, PointerMotionEvent, ProximityState, Switch, SwitchState, SwitchToggleEvent,
|
||||
TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent,
|
||||
TabletToolTipState, TouchEvent,
|
||||
};
|
||||
use smithay::backend::libinput::LibinputInputBackend;
|
||||
use smithay::input::keyboard::{keysyms, FilterResult, Keysym, ModifiersState};
|
||||
@@ -23,11 +24,15 @@ use smithay::input::pointer::{
|
||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
GrabStartData as PointerGrabStartData, MotionEvent, RelativeMotionEvent,
|
||||
};
|
||||
use smithay::input::touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent};
|
||||
use smithay::utils::{Logical, Point, Rectangle, SERIAL_COUNTER};
|
||||
use smithay::input::touch::{
|
||||
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::tablet_manager::{TabletDescriptor, TabletSeatTrait};
|
||||
|
||||
use self::move_grab::MoveGrab;
|
||||
use self::resize_grab::ResizeGrab;
|
||||
use self::spatial_movement_grab::SpatialMovementGrab;
|
||||
use crate::niri::State;
|
||||
@@ -35,10 +40,13 @@ use crate::ui::screenshot_ui::ScreenshotUi;
|
||||
use crate::utils::spawning::spawn;
|
||||
use crate::utils::{center, get_monotonic_time, ResizeEdge};
|
||||
|
||||
pub mod move_grab;
|
||||
pub mod resize_grab;
|
||||
pub mod scroll_tracker;
|
||||
pub mod spatial_movement_grab;
|
||||
pub mod swipe_tracker;
|
||||
pub mod touch_move_grab;
|
||||
pub mod touch_resize_grab;
|
||||
|
||||
pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400);
|
||||
|
||||
@@ -53,6 +61,20 @@ pub struct TabletData {
|
||||
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 {
|
||||
pub fn process_input_event<I: InputBackend + 'static>(&mut self, event: InputEvent<I>)
|
||||
where
|
||||
@@ -87,6 +109,10 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
if should_reset_pointer_inactivity_timer(&event) {
|
||||
self.niri.reset_pointer_inactivity_timer();
|
||||
}
|
||||
|
||||
let hide_hotkey_overlay =
|
||||
self.niri.hotkey_overlay.is_open() && should_hide_hotkey_overlay(&event);
|
||||
|
||||
@@ -123,7 +149,7 @@ impl State {
|
||||
TouchUp { event } => self.on_touch_up::<I>(event),
|
||||
TouchCancel { event } => self.on_touch_cancel::<I>(event),
|
||||
TouchFrame { event } => self.on_touch_frame::<I>(event),
|
||||
SwitchToggle { .. } => (),
|
||||
SwitchToggle { event } => self.on_switch_toggle::<I>(event),
|
||||
Special(_) => (),
|
||||
}
|
||||
|
||||
@@ -239,26 +265,31 @@ impl State {
|
||||
where
|
||||
I::Device: 'static,
|
||||
{
|
||||
let (target_geo, keep_ratio, px) = if let Some(output) = self.niri.output_for_tablet() {
|
||||
(
|
||||
self.niri.global_space.output_geometry(output).unwrap(),
|
||||
true,
|
||||
1. / output.current_scale().fractional_scale(),
|
||||
)
|
||||
} else {
|
||||
let geo = self.global_bounding_rectangle()?;
|
||||
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,
|
||||
1. / output.current_scale().fractional_scale(),
|
||||
output.current_transform(),
|
||||
)
|
||||
} else {
|
||||
let geo = self.global_bounding_rectangle()?;
|
||||
|
||||
// FIXME: this 1 px size should ideally somehow be computed for the rightmost output
|
||||
// corresponding to the position on the right when clamping.
|
||||
let output = self.niri.global_space.outputs().next().unwrap();
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
// FIXME: this 1 px size should ideally somehow be computed for the rightmost output
|
||||
// corresponding to the position on the right when clamping.
|
||||
let output = self.niri.global_space.outputs().next().unwrap();
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
|
||||
// Do not keep ratio for the unified mode as this is what OpenTabletDriver expects.
|
||||
(geo, false, 1. / scale)
|
||||
// Do not keep ratio for the unified mode as this is what OpenTabletDriver expects.
|
||||
(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 {
|
||||
pos.x /= target_geo.size.w as f64;
|
||||
pos.y /= target_geo.size.h as f64;
|
||||
@@ -267,7 +298,8 @@ impl State {
|
||||
if let Some(device) = (&device as &dyn Any).downcast_ref::<input::Device>() {
|
||||
if let Some(data) = self.niri.tablets.get(device) {
|
||||
// This code does the same thing as mutter with "keep aspect ratio" enabled.
|
||||
let output_aspect_ratio = 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;
|
||||
|
||||
if ratio > 1. {
|
||||
@@ -307,6 +339,10 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
if pressed {
|
||||
self.hide_cursor_if_needed();
|
||||
}
|
||||
|
||||
let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input(
|
||||
self,
|
||||
event.key_code(),
|
||||
@@ -349,7 +385,10 @@ impl State {
|
||||
|
||||
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 {
|
||||
return;
|
||||
}
|
||||
@@ -383,6 +422,22 @@ impl State {
|
||||
self.niri.bind_repeat_timer = Some(token);
|
||||
}
|
||||
|
||||
fn hide_cursor_if_needed(&mut self) {
|
||||
if !self.niri.config.borrow().cursor.hide_on_key_press {
|
||||
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) {
|
||||
let Some(cooldown) = bind.cooldown else {
|
||||
self.do_action(bind.action, bind.allow_when_locked);
|
||||
@@ -452,6 +507,9 @@ impl State {
|
||||
Action::PowerOffMonitors => {
|
||||
self.niri.deactivate_monitors(&mut self.backend);
|
||||
}
|
||||
Action::PowerOnMonitors => {
|
||||
self.niri.activate_monitors(&mut self.backend);
|
||||
}
|
||||
Action::ToggleDebugTint => {
|
||||
self.backend.toggle_debug_tint();
|
||||
self.niri.queue_redraw_all();
|
||||
@@ -529,7 +587,7 @@ impl State {
|
||||
let mut windows = self.niri.layout.windows();
|
||||
let window = windows.find(|(_, m)| m.id().get() == id);
|
||||
if let Some((Some(monitor), mapped)) = window {
|
||||
let output = &monitor.output;
|
||||
let output = monitor.output();
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
if let Err(err) = self.niri.screenshot_window(renderer, output, mapped) {
|
||||
warn!("error taking screenshot: {err:?}");
|
||||
@@ -677,17 +735,39 @@ impl State {
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
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();
|
||||
// FIXME: granular
|
||||
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 => {
|
||||
self.niri.layout.consume_or_expel_window_right();
|
||||
self.niri.layout.consume_or_expel_window_right(None);
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
// FIXME: granular
|
||||
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 => {
|
||||
self.niri.layout.focus_left();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
@@ -1469,11 +1549,48 @@ impl State {
|
||||
let button_state = event.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() {
|
||||
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.pointer_grab_ongoing = true;
|
||||
self.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Move));
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 mod_down = match self.backend.mod_key() {
|
||||
CompositorMod::Super => mods.logo,
|
||||
@@ -1618,6 +1735,8 @@ impl State {
|
||||
},
|
||||
);
|
||||
pointer.frame(self);
|
||||
|
||||
self.niri.refresh_layout_is_grabbed();
|
||||
}
|
||||
|
||||
fn on_pointer_axis<I: InputBackend>(&mut self, event: I::PointerAxisEvent) {
|
||||
@@ -2253,6 +2372,8 @@ impl State {
|
||||
|
||||
// We're using touch, hide the pointer.
|
||||
self.niri.pointer_hidden = true;
|
||||
|
||||
self.niri.refresh_layout_is_grabbed();
|
||||
}
|
||||
fn on_touch_up<I: InputBackend>(&mut self, evt: I::TouchUpEvent) {
|
||||
let Some(handle) = self.niri.seat.get_touch() else {
|
||||
@@ -2298,6 +2419,21 @@ impl State {
|
||||
};
|
||||
handle.cancel(self);
|
||||
}
|
||||
|
||||
fn on_switch_toggle<I: InputBackend>(&mut self, evt: I::SwitchToggleEvent) {
|
||||
let Some(switch) = evt.switch() else {
|
||||
return;
|
||||
};
|
||||
|
||||
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
|
||||
@@ -2305,10 +2441,10 @@ impl State {
|
||||
/// to them from being delivered.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn should_intercept_key(
|
||||
suppressed_keys: &mut HashSet<u32>,
|
||||
suppressed_keys: &mut HashSet<Keycode>,
|
||||
bindings: &Binds,
|
||||
comp_mod: CompositorMod,
|
||||
key_code: u32,
|
||||
key_code: Keycode,
|
||||
modified: Keysym,
|
||||
raw: Option<Keysym>,
|
||||
pressed: bool,
|
||||
@@ -2449,6 +2585,23 @@ fn find_configured_bind(
|
||||
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 {
|
||||
let mut modifiers = Modifiers::empty();
|
||||
if mods.ctrl {
|
||||
@@ -2530,6 +2683,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 {
|
||||
matches!(
|
||||
action,
|
||||
@@ -2537,6 +2704,7 @@ fn allowed_when_locked(action: &Action) -> bool {
|
||||
| Action::ChangeVt(_)
|
||||
| Action::Suspend
|
||||
| Action::PowerOffMonitors
|
||||
| Action::PowerOnMonitors
|
||||
| Action::SwitchLayout(_)
|
||||
)
|
||||
}
|
||||
@@ -2544,7 +2712,11 @@ fn allowed_when_locked(action: &Action) -> bool {
|
||||
fn allowed_during_screenshot(action: &Action) -> bool {
|
||||
matches!(
|
||||
action,
|
||||
Action::Quit(_) | Action::ChangeVt(_) | Action::Suspend | Action::PowerOffMonitors
|
||||
Action::Quit(_)
|
||||
| Action::ChangeVt(_)
|
||||
| Action::Suspend
|
||||
| Action::PowerOffMonitors
|
||||
| Action::PowerOnMonitors
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2576,8 +2748,20 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tap_button_map) = c.tap_button_map {
|
||||
@@ -2632,8 +2816,57 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 +2889,20 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2754,8 +2999,8 @@ mod tests {
|
||||
// The key_code we pick is arbitrary, the only thing
|
||||
// that matters is that they are different between cases.
|
||||
|
||||
let close_key_code = close_keysym.into();
|
||||
let close_key_event = |suppr: &mut HashSet<u32>, mods: ModifiersState, pressed| {
|
||||
let close_key_code = Keycode::from(close_keysym.raw() + 8u32);
|
||||
let close_key_event = |suppr: &mut HashSet<Keycode>, mods: ModifiersState, pressed| {
|
||||
should_intercept_key(
|
||||
suppr,
|
||||
&bindings,
|
||||
@@ -2771,12 +3016,12 @@ mod tests {
|
||||
};
|
||||
|
||||
// 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(
|
||||
suppr,
|
||||
&bindings,
|
||||
comp_mod,
|
||||
Keysym::l.into(),
|
||||
Keycode::from(Keysym::l.raw() + 8),
|
||||
Keysym::l,
|
||||
Some(Keysym::l),
|
||||
pressed,
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
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.pointer_grab_ongoing = false;
|
||||
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();
|
||||
// TODO: 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+17
-12
@@ -16,7 +16,6 @@ use futures_util::{select_biased, AsyncBufReadExt, AsyncWrite, AsyncWriteExt, Fu
|
||||
use niri_config::OutputName;
|
||||
use niri_ipc::state::{EventStreamState, EventStreamStatePart as _};
|
||||
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::{Interest, LoopHandle, Mode, PostAction};
|
||||
use smithay::reexports::rustix::fs::unlink;
|
||||
@@ -385,10 +384,13 @@ impl State {
|
||||
pub fn ipc_keyboard_layouts_changed(&mut self) {
|
||||
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
||||
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 {
|
||||
names: layouts.map(str::to_owned).collect(),
|
||||
current_idx: context.active_layout().0 as u8,
|
||||
names: layouts
|
||||
.map(|layout| xkb.layout_name(layout).to_owned())
|
||||
.collect(),
|
||||
current_idx: xkb.active_layout().0 as u8,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -406,7 +408,10 @@ impl State {
|
||||
|
||||
pub fn ipc_refresh_keyboard_layout_index(&mut self) {
|
||||
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 {
|
||||
return;
|
||||
@@ -459,7 +464,7 @@ impl State {
|
||||
// Check for any changes that we can't signal as individual events.
|
||||
let output_name = mon.map(|mon| mon.output_name());
|
||||
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
|
||||
{
|
||||
need_workspaces_changed = true;
|
||||
@@ -482,7 +487,7 @@ impl State {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
events.push(Event::WorkspaceActivated { id, focused: false });
|
||||
}
|
||||
@@ -503,9 +508,9 @@ impl State {
|
||||
Workspace {
|
||||
id,
|
||||
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()),
|
||||
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,
|
||||
active_window_id: ws.active_window().map(|win| win.id().get()),
|
||||
}
|
||||
@@ -546,12 +551,12 @@ impl State {
|
||||
}
|
||||
|
||||
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 });
|
||||
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 wl_surface = mapped.toplevel().wl_surface();
|
||||
@@ -567,7 +572,7 @@ impl State {
|
||||
});
|
||||
|
||||
if changed {
|
||||
let window = make_ipc_window(mapped, Some(ws_id));
|
||||
let window = make_ipc_window(mapped, ws_id);
|
||||
events.push(Event::WindowOpenedOrChanged { window });
|
||||
return;
|
||||
}
|
||||
|
||||
+1096
-145
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::utils::{Logical, Point, Rectangle};
|
||||
|
||||
use super::tile::Tile;
|
||||
use super::workspace::{
|
||||
compute_working_area, Column, ColumnWidth, OutputId, Workspace, WorkspaceId,
|
||||
WorkspaceRenderElement,
|
||||
@@ -20,7 +21,7 @@ use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::RenderTarget;
|
||||
use crate::rubber_band::RubberBand;
|
||||
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.
|
||||
const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.;
|
||||
@@ -33,19 +34,19 @@ const WORKSPACE_GESTURE_RUBBER_BAND: RubberBand = RubberBand {
|
||||
#[derive(Debug)]
|
||||
pub struct Monitor<W: LayoutElement> {
|
||||
/// Output for this monitor.
|
||||
pub output: Output,
|
||||
pub(super) output: Output,
|
||||
/// Cached name of the output.
|
||||
output_name: String,
|
||||
// Must always contain at least one.
|
||||
pub workspaces: Vec<Workspace<W>>,
|
||||
pub(super) workspaces: Vec<Workspace<W>>,
|
||||
/// Index of the currently active workspace.
|
||||
pub active_workspace_idx: usize,
|
||||
pub(super) active_workspace_idx: usize,
|
||||
/// ID of the previously active workspace.
|
||||
pub previous_workspace_id: Option<WorkspaceId>,
|
||||
pub(super) previous_workspace_id: Option<WorkspaceId>,
|
||||
/// In-progress switch between workspaces.
|
||||
pub workspace_switch: Option<WorkspaceSwitch>,
|
||||
pub(super) workspace_switch: Option<WorkspaceSwitch>,
|
||||
/// Configurable properties of the layout.
|
||||
pub options: Rc<Options>,
|
||||
pub(super) options: Rc<Options>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -59,7 +60,7 @@ pub struct WorkspaceSwitchGesture {
|
||||
/// Index of the workspace where the gesture was started.
|
||||
center_idx: usize,
|
||||
/// Current, fractional workspace index.
|
||||
pub current_idx: f64,
|
||||
pub(super) current_idx: f64,
|
||||
tracker: SwipeTracker,
|
||||
/// Whether the gesture is controlled by the touchpad.
|
||||
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 {
|
||||
&self.output_name
|
||||
}
|
||||
|
||||
pub fn active_workspace_idx(&self) -> usize {
|
||||
self.active_workspace_idx
|
||||
}
|
||||
|
||||
pub fn active_workspace_ref(&self) -> &Workspace<W> {
|
||||
&self.workspaces[self.active_workspace_idx]
|
||||
}
|
||||
@@ -175,7 +184,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
) {
|
||||
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.
|
||||
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.
|
||||
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) {
|
||||
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.
|
||||
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) {
|
||||
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) {
|
||||
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 new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_left();
|
||||
self.focus_right();
|
||||
} else {
|
||||
workspace.focus_up();
|
||||
}
|
||||
@@ -455,18 +509,20 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let width = column.width;
|
||||
let is_full_width = column.is_full_width;
|
||||
let window = workspace
|
||||
.remove_tile_by_idx(
|
||||
workspace.active_column_idx,
|
||||
column.active_tile_idx,
|
||||
Transaction::new(),
|
||||
None,
|
||||
)
|
||||
.into_window();
|
||||
let removed = workspace.remove_tile_by_idx(
|
||||
workspace.active_column_idx,
|
||||
column.active_tile_idx,
|
||||
Transaction::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
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) {
|
||||
@@ -483,18 +539,20 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let width = column.width;
|
||||
let is_full_width = column.is_full_width;
|
||||
let window = workspace
|
||||
.remove_tile_by_idx(
|
||||
workspace.active_column_idx,
|
||||
column.active_tile_idx,
|
||||
Transaction::new(),
|
||||
None,
|
||||
)
|
||||
.into_window();
|
||||
let removed = workspace.remove_tile_by_idx(
|
||||
workspace.active_column_idx,
|
||||
column.active_tile_idx,
|
||||
Transaction::new(),
|
||||
None,
|
||||
);
|
||||
|
||||
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) {
|
||||
@@ -531,17 +589,19 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
|
||||
let workspace = &mut self.workspaces[source_workspace_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
|
||||
&& col_idx == workspace.active_column_idx
|
||||
&& tile_idx == column.active_tile_idx;
|
||||
|
||||
let window = workspace
|
||||
.remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None)
|
||||
.into_window();
|
||||
let removed = workspace.remove_tile_by_idx(col_idx, tile_idx, Transaction::new(), None);
|
||||
|
||||
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() {
|
||||
self.clean_up_workspaces();
|
||||
@@ -561,7 +621,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -578,7 +638,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -595,7 +655,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
.as_ref()
|
||||
.is_some_and(|s| s.is_animation())
|
||||
@@ -811,90 +871,75 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
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(
|
||||
&self,
|
||||
pos_within_output: Point<f64, Logical>,
|
||||
) -> Option<(&W, Option<Point<f64, Logical>>)> {
|
||||
match &self.workspace_switch {
|
||||
Some(switch) => {
|
||||
let size = output_size(&self.output).to_f64();
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
let (ws, offset) = self.workspace_under(pos_within_output)?;
|
||||
let (win, win_pos) = ws.window_under(pos_within_output - offset)?;
|
||||
Some((win, win_pos.map(|p| p + offset)))
|
||||
}
|
||||
|
||||
pub fn resize_edges_under(&self, pos_within_output: Point<f64, Logical>) -> Option<ResizeEdge> {
|
||||
match &self.workspace_switch {
|
||||
Some(switch) => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
let (ws, offset) = self.workspace_under(pos_within_output)?;
|
||||
ws.resize_edges_under(pos_within_output - offset)
|
||||
}
|
||||
|
||||
pub fn render_above_top_layer(&self) -> bool {
|
||||
@@ -907,103 +952,52 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
ws.render_above_top_layer()
|
||||
}
|
||||
|
||||
pub fn render_elements<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
pub fn render_elements<'a, R: NiriRenderer>(
|
||||
&'a self,
|
||||
renderer: &'a mut R,
|
||||
target: RenderTarget,
|
||||
) -> Vec<MonitorRenderElement<R>> {
|
||||
) -> impl Iterator<Item = MonitorRenderElement<R>> + '_ {
|
||||
let _span = tracy_client::span!("Monitor::render_elements");
|
||||
|
||||
let scale = self.output.current_scale().fractional_scale();
|
||||
let size = output_size(&self.output);
|
||||
// Ceil the height in physical pixels.
|
||||
let height = (size.h * scale).ceil() as i32;
|
||||
|
||||
match &self.workspace_switch {
|
||||
Some(switch) => {
|
||||
let render_idx = switch.current_idx();
|
||||
let before_idx = render_idx.floor();
|
||||
let after_idx = render_idx.ceil();
|
||||
// Crop the elements to prevent them overflowing, currently visible during a workspace
|
||||
// switch.
|
||||
//
|
||||
// HACK: crop to infinite bounds at least horizontally where we
|
||||
// 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;
|
||||
|
||||
if after_idx < 0. || before_idx as usize >= self.workspaces.len() {
|
||||
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
|
||||
self.workspaces_with_render_positions()
|
||||
.flat_map(move |(ws, offset)| {
|
||||
ws.render_elements(renderer, target)
|
||||
.into_iter()
|
||||
.filter_map(|elem| {
|
||||
Some(RelocateRenderElement::from_element(
|
||||
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,
|
||||
))
|
||||
.filter_map(move |elem| {
|
||||
CropRenderElement::from_element(elem, scale, crop_bounds)
|
||||
})
|
||||
.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) {
|
||||
|
||||
+13
-2
@@ -64,6 +64,9 @@ pub struct Tile<W: LayoutElement> {
|
||||
/// The animation of a tile visually moving vertically.
|
||||
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.
|
||||
unmap_snapshot: Option<TileRenderSnapshot>,
|
||||
|
||||
@@ -74,7 +77,7 @@ pub struct Tile<W: LayoutElement> {
|
||||
scale: f64,
|
||||
|
||||
/// Configurable properties of the layout.
|
||||
pub options: Rc<Options>,
|
||||
pub(super) options: Rc<Options>,
|
||||
}
|
||||
|
||||
niri_render_elements! {
|
||||
@@ -90,7 +93,7 @@ niri_render_elements! {
|
||||
}
|
||||
}
|
||||
|
||||
type TileRenderSnapshot =
|
||||
pub type TileRenderSnapshot =
|
||||
RenderSnapshot<TileRenderElement<GlesRenderer>, TileRenderElement<GlesRenderer>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -123,6 +126,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
resize_animation: None,
|
||||
move_x_animation: None,
|
||||
move_y_animation: None,
|
||||
interactive_move_offset: Point::from((0., 0.)),
|
||||
unmap_snapshot: None,
|
||||
rounded_corner_damage: Default::default(),
|
||||
scale,
|
||||
@@ -305,6 +309,8 @@ impl<W: LayoutElement> Tile<W> {
|
||||
offset.y += move_.from * move_.anim.value();
|
||||
}
|
||||
|
||||
offset += self.interactive_move_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 {
|
||||
&self.window
|
||||
}
|
||||
|
||||
+639
-428
File diff suppressed because it is too large
Load Diff
+136
-40
@@ -17,6 +17,7 @@ use niri_config::{
|
||||
DEFAULT_BACKGROUND_COLOR,
|
||||
};
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::input::Keycode;
|
||||
use smithay::backend::renderer::damage::OutputDamageTracker;
|
||||
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
@@ -43,7 +44,7 @@ use smithay::desktop::{
|
||||
layer_map_for_output, LayerSurface, PopupGrab, PopupManager, PopupUngrabStrategy, Space,
|
||||
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::{Seat, SeatState};
|
||||
use smithay::output::{self, Output, OutputModeSource, PhysicalProperties, Subpixel};
|
||||
@@ -115,6 +116,7 @@ use crate::input::{
|
||||
apply_libinput_settings, mods_with_finger_scroll_binds, mods_with_wheel_binds, TabletData,
|
||||
};
|
||||
use crate::ipc::server::IpcServer;
|
||||
use crate::layout::tile::TileRenderElement;
|
||||
use crate::layout::workspace::WorkspaceId;
|
||||
use crate::layout::{Layout, LayoutElement as _, MonitorRenderElement};
|
||||
use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
|
||||
@@ -248,7 +250,7 @@ pub struct Niri {
|
||||
|
||||
pub seat: Seat<State>,
|
||||
/// 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_repeat_timer: Option<RegistrationToken>,
|
||||
pub keyboard_focus: KeyboardFocus,
|
||||
@@ -259,13 +261,14 @@ pub struct Niri {
|
||||
pub cursor_manager: CursorManager,
|
||||
pub cursor_texture_cache: CursorTextureCache,
|
||||
pub cursor_shape_manager_state: CursorShapeManagerState,
|
||||
pub dnd_icon: Option<WlSurface>,
|
||||
pub dnd_icon: Option<DndIcon>,
|
||||
pub pointer_focus: PointerFocus,
|
||||
/// 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
|
||||
/// various tooltips from sticking around.
|
||||
pub pointer_hidden: bool,
|
||||
pub pointer_inactivity_timer: Option<RegistrationToken>,
|
||||
// FIXME: this should be able to be removed once PointerFocus takes grabs into account.
|
||||
pub pointer_grab_ongoing: bool,
|
||||
pub tablet_cursor_location: Option<Point<f64, Logical>>,
|
||||
@@ -304,6 +307,12 @@ pub struct Niri {
|
||||
pub mapped_cast_output: HashMap<Window, Output>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DndIcon {
|
||||
pub surface: WlSurface,
|
||||
pub offset: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
pub struct OutputState {
|
||||
pub global: GlobalId,
|
||||
pub frame_clock: FrameClock,
|
||||
@@ -533,14 +542,17 @@ impl State {
|
||||
self.notify_blocker_cleared();
|
||||
|
||||
// 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.global_space.refresh();
|
||||
self.niri.refresh_idle_inhibit();
|
||||
self.refresh_popup_grab();
|
||||
self.update_keyboard_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_focus();
|
||||
foreign_toplevel::refresh(self);
|
||||
self.niri.refresh_window_rules();
|
||||
@@ -581,6 +593,7 @@ impl State {
|
||||
|
||||
// We moved the pointer, show it.
|
||||
self.niri.pointer_hidden = false;
|
||||
self.niri.reset_pointer_inactivity_timer();
|
||||
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
@@ -634,14 +647,13 @@ impl State {
|
||||
let Some(output) = self.niri.layout.active_output() else {
|
||||
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 rect = monitor.active_tile_visual_rectangle();
|
||||
|
||||
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;
|
||||
rect.loc += output_geo.loc.to_f64();
|
||||
rv = self.move_cursor_to_rect(rect, mode);
|
||||
@@ -893,8 +905,10 @@ impl State {
|
||||
}
|
||||
|
||||
if self.niri.config.borrow().input.keyboard.track_layout == TrackLayout::Window {
|
||||
let current_layout =
|
||||
keyboard.with_xkb_state(self, |context| context.active_layout());
|
||||
let current_layout = keyboard.with_xkb_state(self, |context| {
|
||||
let xkb = context.xkb().lock().unwrap();
|
||||
xkb.active_layout()
|
||||
});
|
||||
|
||||
let mut new_layout = current_layout;
|
||||
// Store the currently active layout for the surface.
|
||||
@@ -982,6 +996,7 @@ impl State {
|
||||
let mut window_rules_changed = false;
|
||||
let mut debug_config_changed = false;
|
||||
let mut shaders_changed = false;
|
||||
let mut cursor_inactivity_timeout_changed = false;
|
||||
let mut old_config = self.niri.config.borrow_mut();
|
||||
|
||||
// Reload the cursor.
|
||||
@@ -1068,6 +1083,10 @@ impl State {
|
||||
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 {
|
||||
debug_config_changed = true;
|
||||
}
|
||||
@@ -1114,6 +1133,10 @@ impl State {
|
||||
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
|
||||
// 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
|
||||
@@ -1775,7 +1798,7 @@ impl Niri {
|
||||
.unwrap();
|
||||
|
||||
drop(config_);
|
||||
Self {
|
||||
let mut niri = Self {
|
||||
config,
|
||||
config_file_output_config,
|
||||
|
||||
@@ -1852,6 +1875,7 @@ impl Niri {
|
||||
dnd_icon: None,
|
||||
pointer_focus: PointerFocus::default(),
|
||||
pointer_hidden: false,
|
||||
pointer_inactivity_timer: None,
|
||||
pointer_grab_ongoing: false,
|
||||
tablet_cursor_location: None,
|
||||
gesture_swipe_3f_cumulative: None,
|
||||
@@ -1887,11 +1911,19 @@ impl Niri {
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
mapped_cast_output: HashMap::new(),
|
||||
}
|
||||
};
|
||||
|
||||
niri.reset_pointer_inactivity_timer();
|
||||
|
||||
niri
|
||||
}
|
||||
|
||||
#[cfg(feature = "dbus")]
|
||||
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 message = conn.call_method(
|
||||
@@ -1902,7 +1934,14 @@ impl Niri {
|
||||
&("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);
|
||||
|
||||
Ok(())
|
||||
@@ -2589,22 +2628,20 @@ impl Niri {
|
||||
|
||||
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
||||
|
||||
let (mut pointer_elements, pointer_pos) = match render_cursor {
|
||||
RenderCursor::Hidden => (vec![], pointer_pos.to_physical_precise_round(output_scale)),
|
||||
let mut pointer_elements = match render_cursor {
|
||||
RenderCursor::Hidden => vec![],
|
||||
RenderCursor::Surface { surface, hotspot } => {
|
||||
let pointer_pos =
|
||||
(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,
|
||||
&surface,
|
||||
pointer_pos,
|
||||
output_scale,
|
||||
1.,
|
||||
Kind::Cursor,
|
||||
);
|
||||
|
||||
(pointer_elements, pointer_pos)
|
||||
)
|
||||
}
|
||||
RenderCursor::Named {
|
||||
icon,
|
||||
@@ -2620,7 +2657,7 @@ impl Niri {
|
||||
let mut pointer_elements = vec![];
|
||||
let pointer_element = match MemoryRenderBufferRenderElement::from_buffer(
|
||||
renderer,
|
||||
pointer_pos.to_f64(),
|
||||
pointer_pos,
|
||||
&texture,
|
||||
None,
|
||||
None,
|
||||
@@ -2637,14 +2674,16 @@ impl Niri {
|
||||
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(
|
||||
renderer,
|
||||
dnd_icon,
|
||||
&dnd_icon.surface,
|
||||
pointer_pos,
|
||||
output_scale,
|
||||
1.,
|
||||
@@ -2667,7 +2706,7 @@ impl Niri {
|
||||
.tablet_cursor_location
|
||||
.unwrap_or_else(|| self.seat.get_pointer().unwrap().current_location());
|
||||
|
||||
match self.cursor_manager.cursor_image().clone() {
|
||||
match self.cursor_manager.cursor_image() {
|
||||
CursorImageStatus::Surface(ref surface) => {
|
||||
let hotspot = with_states(surface, |states| {
|
||||
states
|
||||
@@ -2685,6 +2724,7 @@ impl Niri {
|
||||
let dnd = self
|
||||
.dnd_icon
|
||||
.as_ref()
|
||||
.map(|icon| &icon.surface)
|
||||
.map(|surface| (surface, bbox_from_surface_tree(surface, surface_pos)));
|
||||
|
||||
// FIXME we basically need to pick the largest scale factor across the overlapping
|
||||
@@ -2746,12 +2786,12 @@ impl Niri {
|
||||
}
|
||||
cursor_image => {
|
||||
// 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;
|
||||
};
|
||||
|
||||
let icon = if let CursorImageStatus::Named(icon) = cursor_image {
|
||||
icon
|
||||
*icon
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
@@ -2800,6 +2840,33 @@ impl Niri {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_layout_is_grabbed(&mut self) {
|
||||
let pointer = self.seat.get_pointer().unwrap();
|
||||
let touch = self.seat.get_touch();
|
||||
let touch_is_grabbed = touch.map_or(false, |touch| touch.is_grabbed());
|
||||
self.layout
|
||||
.set_pointer_grabbed(pointer.is_grabbed() || touch_is_grabbed);
|
||||
}
|
||||
|
||||
pub fn refresh_layout(&mut self) {
|
||||
self.refresh_layout_is_grabbed();
|
||||
|
||||
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) {
|
||||
let _span = tracy_client::span!("Niri::refresh_idle_inhibit");
|
||||
|
||||
@@ -3019,7 +3086,11 @@ impl Niri {
|
||||
|
||||
// Get monitor elements.
|
||||
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.
|
||||
let layer_map = layer_map_for_output(output);
|
||||
@@ -3050,10 +3121,12 @@ impl Niri {
|
||||
|
||||
// Then the regular monitor elements and the top layer in varying order.
|
||||
if mon.render_above_top_layer() {
|
||||
elements.extend(float_elements.into_iter().map(OutputRenderElements::from));
|
||||
elements.extend(monitor_elements.into_iter().map(OutputRenderElements::from));
|
||||
extend_from_layer(&mut elements, Layer::Top);
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -3095,11 +3168,7 @@ impl Niri {
|
||||
}
|
||||
}
|
||||
|
||||
state.unfinished_animations_remain = self
|
||||
.layout
|
||||
.monitor_for_output(output)
|
||||
.unwrap()
|
||||
.are_animations_ongoing();
|
||||
state.unfinished_animations_remain = self.layout.are_animations_ongoing(Some(output));
|
||||
|
||||
self.config_error_notification
|
||||
.advance_animations(target_presentation_time);
|
||||
@@ -3268,7 +3337,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(
|
||||
surface,
|
||||
(),
|
||||
@@ -3406,7 +3475,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(
|
||||
surface,
|
||||
output,
|
||||
@@ -3508,7 +3577,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(
|
||||
surface,
|
||||
output,
|
||||
@@ -3576,7 +3645,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(
|
||||
surface,
|
||||
output,
|
||||
@@ -3615,7 +3684,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(
|
||||
surface,
|
||||
&mut feedback,
|
||||
@@ -4698,6 +4767,32 @@ impl Niri {
|
||||
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 {
|
||||
@@ -4715,6 +4810,7 @@ impl ClientData for ClientState {
|
||||
niri_render_elements! {
|
||||
OutputRenderElements<R> => {
|
||||
Monitor = MonitorRenderElement<R>,
|
||||
Tile = TileRenderElement<R>,
|
||||
Wayland = WaylandSurfaceRenderElement<R>,
|
||||
NamedPointer = MemoryRenderBufferRenderElement<R>,
|
||||
SolidColor = SolidColorRenderElement,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ffi::CString;
|
||||
use std::rc::Rc;
|
||||
|
||||
use glam::{Mat3, Vec2};
|
||||
@@ -76,38 +76,31 @@ unsafe fn compile_program(
|
||||
let debug_program =
|
||||
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_position = CStr::from_bytes_with_nul(b"vert_position\0").expect("NULL terminated");
|
||||
let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated");
|
||||
let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated");
|
||||
let size = CStr::from_bytes_with_nul(b"niri_size\0").expect("NULL terminated");
|
||||
let scale = CStr::from_bytes_with_nul(b"niri_scale\0").expect("NULL terminated");
|
||||
let alpha = CStr::from_bytes_with_nul(b"niri_alpha\0").expect("NULL terminated");
|
||||
let tint = CStr::from_bytes_with_nul(b"niri_tint\0").expect("NULL terminated");
|
||||
let vert = c"vert";
|
||||
let vert_position = c"vert_position";
|
||||
let matrix = c"matrix";
|
||||
let tex_matrix = c"tex_matrix";
|
||||
let size = c"niri_size";
|
||||
let scale = c"niri_scale";
|
||||
let alpha = c"niri_alpha";
|
||||
let tint = c"niri_tint";
|
||||
|
||||
Ok(ShaderProgram(Rc::new(ShaderProgramInner {
|
||||
normal: ShaderProgramInternal {
|
||||
program,
|
||||
uniform_matrix: gl
|
||||
.GetUniformLocation(program, matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_tex_matrix: gl
|
||||
.GetUniformLocation(program, tex_matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_size: gl
|
||||
.GetUniformLocation(program, size.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_scale: gl
|
||||
.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),
|
||||
uniform_matrix: gl.GetUniformLocation(program, matrix.as_ptr()),
|
||||
uniform_tex_matrix: gl.GetUniformLocation(program, tex_matrix.as_ptr()),
|
||||
uniform_size: gl.GetUniformLocation(program, size.as_ptr()),
|
||||
uniform_scale: gl.GetUniformLocation(program, scale.as_ptr()),
|
||||
uniform_alpha: gl.GetUniformLocation(program, alpha.as_ptr()),
|
||||
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr()),
|
||||
attrib_vert_position: gl.GetAttribLocation(program, vert_position.as_ptr()),
|
||||
additional_uniforms: additional_uniforms
|
||||
.iter()
|
||||
.map(|uniform| {
|
||||
let name =
|
||||
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
|
||||
let location =
|
||||
gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
|
||||
let location = gl.GetUniformLocation(program, name.as_ptr());
|
||||
(
|
||||
uniform.name.clone().into_owned(),
|
||||
UniformDesc {
|
||||
@@ -121,41 +114,26 @@ unsafe fn compile_program(
|
||||
.iter()
|
||||
.map(|name_| {
|
||||
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
|
||||
let location =
|
||||
gl.GetUniformLocation(program, name.as_ptr() as *const ffi::types::GLchar);
|
||||
let location = gl.GetUniformLocation(program, name.as_ptr());
|
||||
(name_.to_string(), location)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
debug: ShaderProgramInternal {
|
||||
program: debug_program,
|
||||
uniform_matrix: gl
|
||||
.GetUniformLocation(debug_program, matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_tex_matrix: gl.GetUniformLocation(
|
||||
debug_program,
|
||||
tex_matrix.as_ptr() as *const ffi::types::GLchar,
|
||||
),
|
||||
uniform_size: gl
|
||||
.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,
|
||||
),
|
||||
uniform_matrix: gl.GetUniformLocation(debug_program, matrix.as_ptr()),
|
||||
uniform_tex_matrix: gl.GetUniformLocation(debug_program, tex_matrix.as_ptr()),
|
||||
uniform_size: gl.GetUniformLocation(debug_program, size.as_ptr()),
|
||||
uniform_scale: gl.GetUniformLocation(debug_program, scale.as_ptr()),
|
||||
uniform_alpha: gl.GetUniformLocation(debug_program, alpha.as_ptr()),
|
||||
attrib_vert: gl.GetAttribLocation(debug_program, vert.as_ptr()),
|
||||
attrib_vert_position: gl.GetAttribLocation(debug_program, vert_position.as_ptr()),
|
||||
additional_uniforms: additional_uniforms
|
||||
.iter()
|
||||
.map(|uniform| {
|
||||
let name =
|
||||
CString::new(uniform.name.as_bytes()).expect("Interior null in name");
|
||||
let location = gl.GetUniformLocation(
|
||||
debug_program,
|
||||
name.as_ptr() as *const ffi::types::GLchar,
|
||||
);
|
||||
let location = gl.GetUniformLocation(debug_program, name.as_ptr());
|
||||
(
|
||||
uniform.name.clone().into_owned(),
|
||||
UniformDesc {
|
||||
@@ -169,16 +147,12 @@ unsafe fn compile_program(
|
||||
.iter()
|
||||
.map(|name_| {
|
||||
let name = CString::new(name_.as_bytes()).expect("Interior null in name");
|
||||
let location = gl.GetUniformLocation(
|
||||
debug_program,
|
||||
name.as_ptr() as *const ffi::types::GLchar,
|
||||
);
|
||||
let location = gl.GetUniformLocation(debug_program, name.as_ptr());
|
||||
(name_.to_string(), location)
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
uniform_tint: gl
|
||||
.GetUniformLocation(debug_program, tint.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_tint: gl.GetUniformLocation(debug_program, tint.as_ptr()),
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -211,7 +185,7 @@ impl ShaderRenderElement {
|
||||
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
|
||||
scale: f32,
|
||||
alpha: f32,
|
||||
uniforms: Vec<Uniform<'_>>,
|
||||
additional_uniforms: Vec<Uniform<'static>>,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
kind: Kind,
|
||||
) -> Self {
|
||||
@@ -223,7 +197,7 @@ impl ShaderRenderElement {
|
||||
opaque_regions: opaque_regions.unwrap_or_default(),
|
||||
scale,
|
||||
alpha,
|
||||
additional_uniforms: uniforms.into_iter().map(|u| u.into_owned()).collect(),
|
||||
additional_uniforms,
|
||||
textures,
|
||||
kind,
|
||||
}
|
||||
@@ -253,13 +227,13 @@ impl ShaderRenderElement {
|
||||
size: Size<f64, Logical>,
|
||||
opaque_regions: Option<Vec<Rectangle<f64, Logical>>>,
|
||||
scale: f32,
|
||||
uniforms: Vec<Uniform<'_>>,
|
||||
uniforms: Vec<Uniform<'static>>,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
) {
|
||||
self.area.size = size;
|
||||
self.opaque_regions = opaque_regions.unwrap_or_default();
|
||||
self.scale = scale;
|
||||
self.additional_uniforms = uniforms.into_iter().map(|u| u.into_owned()).collect();
|
||||
self.additional_uniforms = uniforms;
|
||||
self.textures = textures;
|
||||
|
||||
self.commit_counter.increment();
|
||||
|
||||
@@ -56,6 +56,7 @@ input {
|
||||
// accel-speed 0.2
|
||||
// accel-profile "flat"
|
||||
// scroll-method "on-button-down"
|
||||
// scroll-button 273
|
||||
// middle-emulation
|
||||
}
|
||||
|
||||
@@ -134,13 +135,14 @@ A few settings are common between input devices:
|
||||
|
||||
- `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.
|
||||
- `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).
|
||||
- `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.
|
||||
- `scroll-button`: 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.
|
||||
|
||||
Settings specific to `touchpad`s:
|
||||
|
||||
@@ -42,6 +42,11 @@ layout {
|
||||
// inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" in="srgb-linear"
|
||||
}
|
||||
|
||||
insert-hint {
|
||||
// off
|
||||
color "#ffc87f80"
|
||||
}
|
||||
|
||||
struts {
|
||||
// left 64
|
||||
// right 64
|
||||
@@ -303,6 +308,25 @@ 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` lets you change the color of the hint and has the same syntax as colors in border and focus ring.
|
||||
|
||||
```kdl
|
||||
layout {
|
||||
insert-hint {
|
||||
// off
|
||||
color "#ffc87f80"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `struts`
|
||||
|
||||
Struts shrink the area occupied by windows, similarly to layer-shell panels.
|
||||
|
||||
@@ -20,6 +20,9 @@ environment {
|
||||
cursor {
|
||||
xcursor-theme "breeze_cursors"
|
||||
xcursor-size 48
|
||||
|
||||
hide-on-key-press
|
||||
hide-after-inactive-ms 1000
|
||||
}
|
||||
|
||||
hotkey-overlay {
|
||||
@@ -106,6 +109,31 @@ cursor {
|
||||
}
|
||||
```
|
||||
|
||||
#### `hide-on-key-press`
|
||||
|
||||
<sup>Since: 0.1.10</sup>
|
||||
|
||||
If set, hides the cursor when pressing a key on the keyboard.
|
||||
|
||||
```kdl
|
||||
cursor {
|
||||
hide-on-key-press
|
||||
}
|
||||
```
|
||||
|
||||
#### `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`
|
||||
|
||||
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)
|
||||
* [`output "eDP-1" {}`](./Configuration:-Outputs.md)
|
||||
* [`binds {}`](./Configuration:-Key-Bindings.md)
|
||||
* [`switch-events {}`](./Configuration:-Switch-Events.md)
|
||||
* [`layout {}`](./Configuration:-Layout.md)
|
||||
* [top-level options](./Configuration:-Miscellaneous.md)
|
||||
* [`window-rule {}`](./Configuration:-Window-Rules.md)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
### Overview
|
||||
|
||||
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 "bash" "-c" "niri msg output \"eDP-1\" off"; }
|
||||
lid-open { spawn "bash" "-c" "niri msg output \"eDP-1\" on"; }
|
||||
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.
|
||||
|
||||
You could use them to turn the laptop internal monitor off and on (until niri gets this functionality built-in).
|
||||
|
||||
```kdl
|
||||
switch-events {
|
||||
lid-close { spawn "bash" "-c" "niri msg output \"eDP-1\" off"; }
|
||||
lid-open { spawn "bash" "-c" "niri msg output \"eDP-1\" on"; }
|
||||
}
|
||||
```
|
||||
|
||||
### `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"; }
|
||||
}
|
||||
```
|
||||
@@ -4,6 +4,12 @@ There are several gestures in niri.
|
||||
|
||||
### Mouse
|
||||
|
||||
#### Interactive Move
|
||||
|
||||
<sup>Since: 0.1.10</sup>
|
||||
|
||||
You can move windows by holding <kbd>Mod</kbd> and the left mouse button.
|
||||
|
||||
#### Interactive Resize
|
||||
|
||||
<sup>Since: 0.1.6</sup>
|
||||
|
||||
+10
-6
@@ -8,8 +8,8 @@ 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]).
|
||||
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.
|
||||
The `--session` flag will make niri import its environment variables globally into systemd and D-Bus, and start its D-Bus services.
|
||||
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 the system manager and D-Bus, and start its D-Bus services.
|
||||
|
||||
You can also run `niri` inside an existing desktop session.
|
||||
Then it will open as a window, where you can give it a try.
|
||||
@@ -178,8 +178,10 @@ To do that, put files into the correct directories according to this table.
|
||||
| `resources/niri-session` | `/usr/bin/` |
|
||||
| `resources/niri.desktop` | `/usr/share/wayland-sessions/` |
|
||||
| `resources/niri-portals.conf` | `/usr/share/xdg-desktop-portal/` |
|
||||
| `resources/niri.service` | `/usr/lib/systemd/user/` |
|
||||
| `resources/niri-shutdown.target` | `/usr/lib/systemd/user/` |
|
||||
| `resources/niri.service` (systemd) | `/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.
|
||||
|
||||
@@ -195,8 +197,10 @@ These may vary depending on your distribution.
|
||||
| `resources/niri-session` | `/usr/local/bin/` |
|
||||
| `resources/niri.desktop` | `/usr/local/share/wayland-sessions/` |
|
||||
| `resources/niri-portals.conf` | `/usr/local/share/xdg-desktop-portal/` |
|
||||
| `resources/niri.service` | `/etc/systemd/user/` |
|
||||
| `resources/niri-shutdown.target` | `/etc/systemd/user/` |
|
||||
| `resources/niri.service` (systemd) | `/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
|
||||
[fuzzel]: https://codeberg.org/dnkl/fuzzel
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* [Input](./Configuration:-Input.md)
|
||||
* [Outputs](./Configuration:-Outputs.md)
|
||||
* [Key Bindings](./Configuration:-Key-Bindings.md)
|
||||
* [Switch Events](./Configuration:-Switch-Events.md)
|
||||
* [Layout](./Configuration:-Layout.md)
|
||||
* [Named Workspaces](./Configuration:-Named-Workspaces.md)
|
||||
* [Miscellaneous](./Configuration:-Miscellaneous.md)
|
||||
|
||||
Reference in New Issue
Block a user