Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 49fc6117fd | |||
| 165a6d1ee8 | |||
| fdb6d85fc7 | |||
| 188c5300f7 | |||
| a4b5539baa | |||
| 6f1a2c5f0e | |||
| f717ae030f | |||
| f3696081d1 | |||
| 4b60cbe537 | |||
| f9f43d826a | |||
| 3d49db3870 | |||
| 9bd6c2cadd | |||
| c5253968b4 | |||
| 9a6f31012d | |||
| 4294948cf1 | |||
| cd5ac3e5e0 | |||
| 38191826cb | |||
| 0200670d9e | |||
| 90366886b2 |
@@ -1,2 +0,0 @@
|
||||
# LFS configuration for images from the wiki
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Report a bug or a crash
|
||||
title: ''
|
||||
labels: bug
|
||||
type: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
@@ -4,39 +4,42 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "accesskit"
|
||||
version = "0.21.1"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf203f9d3bd8f29f98833d1fbef628df18f759248a547e7e01cfbf63cda36a99"
|
||||
checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_atspi_common"
|
||||
version = "0.14.2"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "890d241cf51fc784f0ac5ac34dfc847421f8d39da6c7c91a0fcc987db62a8267"
|
||||
checksum = "7e98018dbef3583d751dbb96e07b8728fb99581360e1c3df408af16f4a80b821"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"atspi-common",
|
||||
"phf",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_consumer"
|
||||
version = "0.31.0"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db81010a6895d8707f9072e6ce98070579b43b717193d2614014abd5cb17dd43"
|
||||
checksum = "f950720ce064757a1b629caad3a408e8d2c63bb01f29b8a3ff8daa331053ffeb"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_unix"
|
||||
version = "0.17.2"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301e55b39cfc15d9c48943ce5f572204a551646700d0e8efa424585f94fec528"
|
||||
checksum = "5376ba4cc23312587634abb5250b1ce8618f01a55915608209aafd01efb4bf8c"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_atspi_common",
|
||||
@@ -371,20 +374,19 @@ checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a"
|
||||
|
||||
[[package]]
|
||||
name = "atspi"
|
||||
version = "0.25.0"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c83247582e7508838caf5f316c00791eee0e15c0bf743e6880585b867e16815c"
|
||||
checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d"
|
||||
dependencies = [
|
||||
"atspi-common",
|
||||
"atspi-connection",
|
||||
"atspi-proxies",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atspi-common"
|
||||
version = "0.9.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33dfc05e7cdf90988a197803bf24f5788f94f7c94a69efa95683e8ffe76cfdfb"
|
||||
checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"serde",
|
||||
@@ -396,23 +398,11 @@ dependencies = [
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atspi-connection"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4193d51303d8332304056ae0004714256b46b6635a5c556109b319c0d3784938"
|
||||
dependencies = [
|
||||
"atspi-common",
|
||||
"atspi-proxies",
|
||||
"futures-lite",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atspi-proxies"
|
||||
version = "0.9.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2eebcb9e7e76f26d0bcfd6f0295e1cd1e6f33bedbc5698a971db8dc43d7751c"
|
||||
checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc"
|
||||
dependencies = [
|
||||
"atspi-common",
|
||||
"serde",
|
||||
@@ -1150,6 +1140,12 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@@ -1656,7 +1652,16 @@ version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
"foldhash 0.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -51,11 +51,8 @@ readme = "README.md"
|
||||
keywords = ["wayland", "compositor", "tiling", "smithay", "wm"]
|
||||
|
||||
[dependencies]
|
||||
# accesskit_unix 0.18 has a regression where it doesn't work in normal configurations.
|
||||
# accesskit 0.21 is its correct dependent version.
|
||||
# https://github.com/niri-wm/niri/issues/3594
|
||||
accesskit = { version = "0.21", optional = true }
|
||||
accesskit_unix = { version = "0.17", optional = true }
|
||||
accesskit = { version = "0.24", optional = true }
|
||||
accesskit_unix = { version = "0.22", optional = true }
|
||||
anyhow.workspace = true
|
||||
arrayvec = "0.7.6"
|
||||
async-channel = "2.5.0"
|
||||
|
||||
@@ -33,6 +33,8 @@ JetBrains IDEs can run directly on Wayland, but it's not the default.
|
||||
|
||||
For JetBrainsRuntime > 17, you can set the flag `-Dawt.toolkit.name=WLToolkit` inside of `help -> edit custom vm options -> add`.
|
||||
|
||||
If the settings window fails to load under Wayland, and the UI becomes unresponsive afterwards, also set the flag `-Dsun.awt.wl.WindowDecorationStyle=builtin` in the custom vm options. This gives the settings window a titlebar, but it at least makes the IDE functional.
|
||||
|
||||
### WezTerm
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
@@ -324,27 +324,6 @@ debug {
|
||||
}
|
||||
```
|
||||
|
||||
### `keep-max-bpc-unchanged`
|
||||
|
||||
<sup>Since: 25.08</sup>
|
||||
|
||||
When connecting monitors, niri sets their max bpc to 8 in order to reduce display bandwidth and to potentially allow more monitors to be connected at once.
|
||||
Restricting bpc to 8 is not a problem since we don't support HDR or color management yet and can't really make use of higher bpc.
|
||||
|
||||
Apparently, setting max bpc to 8 breaks some displays driven by AMDGPU.
|
||||
If this happens to you, set this debug flag, which will prevent niri from changing max bpc.
|
||||
AMDGPU bug report: https://gitlab.freedesktop.org/drm/amd/-/issues/4487.
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
This setting is deprecated and does nothing: niri no longer sets max bpc.
|
||||
The old niri behavior with this setting enabled matches the new behavior.
|
||||
|
||||
```kdl
|
||||
debug {
|
||||
keep-max-bpc-unchanged
|
||||
}
|
||||
```
|
||||
|
||||
### Key Bindings
|
||||
|
||||
These are not debug options, but rather key bindings.
|
||||
|
||||
@@ -15,6 +15,7 @@ output "eDP-1" {
|
||||
variable-refresh-rate // on-demand=true
|
||||
focus-at-startup
|
||||
backdrop-color "#001100"
|
||||
// max-bpc 8
|
||||
|
||||
hot-corners {
|
||||
// off
|
||||
@@ -279,6 +280,27 @@ output "HDMI-A-1" {
|
||||
}
|
||||
```
|
||||
|
||||
### `max-bpc`
|
||||
|
||||
<sup>Since: next release</sup>
|
||||
|
||||
Set the maximum bits per channel (BPC) for this output.
|
||||
|
||||
You *do not* need to set this option normally.
|
||||
It influences the encoding of the display signal on the wire and *is not* directly related to the color bitness or framebuffer format.
|
||||
|
||||
Setting `max-bpc` to a low value may help if you hit a bandwidth issue (can't set a monitor configuration that works on other compositor).
|
||||
Otherwise, you're advised to leave it unset (keeping a default, usually high value) and let the GPU driver figure things out automatically.
|
||||
|
||||
Valid values are `6`, `8`, `10`, `12`, `14`, `16`.
|
||||
|
||||
```kdl
|
||||
// Set 8 max-bpc on HDMI-A-1 to lower the bandwidth.
|
||||
output "HDMI-A-1" {
|
||||
max-bpc 8
|
||||
}
|
||||
```
|
||||
|
||||
### `hot-corners`
|
||||
|
||||
<sup>Since: 25.11</sup>
|
||||
|
||||
@@ -53,12 +53,12 @@ It shows up as "niri Dynamic Cast Target" in the screencast window dialog.
|
||||
|
||||

|
||||
|
||||
When you select it, it will start as an empty, transparent video stream.
|
||||
Then, you can use the following binds to change what it shows:
|
||||
Choose it, then use the following binds to change what it shows.
|
||||
The stream won't start until you make your first target selection.
|
||||
|
||||
- `set-dynamic-cast-window` to cast the focused window.
|
||||
- `set-dynamic-cast-monitor` to cast the focused monitor.
|
||||
- `clear-dynamic-cast-target` to go back to an empty stream.
|
||||
- `clear-dynamic-cast-target` to reset to an empty video stream.
|
||||
|
||||
You can also use these actions from the command line, for example to interactively pick which window to cast:
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 121 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 9.3 KiB |
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1757967192,
|
||||
"narHash": "sha256-/aA9A/OBmnuOMgwfzdsXRusqzUpd8rQnQY8jtrHK+To=",
|
||||
"lastModified": 1781607440,
|
||||
"narHash": "sha256-rxO+uc/KFbSJp+pgyXRuAX6QlG9hJdnt0BXpEQRXY+U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0d7c15863b251a7a50265e57c1dca1a7add2e291",
|
||||
"rev": "3e41b24abd260e8f71dbe2f5737d24122f972158",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,28 +18,7 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1757989933,
|
||||
"narHash": "sha256-9cpKYWWPCFhgwQTww8S94rTXgg8Q8ydFv9fXM6I8xQM=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "8249aa3442fb9b45e615a35f39eca2fe5510d7c3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,22 +2,12 @@
|
||||
{
|
||||
description = "Niri: A scrollable-tiling Wayland compositor.";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
|
||||
# NOTE: This is not necessary for end users
|
||||
# You can omit it with `inputs.rust-overlay.follows = ""`
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
rust-overlay,
|
||||
}:
|
||||
let
|
||||
revision = self.shortRev or self.dirtyShortRev or "unknown";
|
||||
@@ -135,12 +125,12 @@
|
||||
''
|
||||
+ lib.optionalString withSystemd ''
|
||||
install -Dm755 resources/niri-session $out/bin/niri-session
|
||||
install -Dm644 resources/niri{.service,-shutdown.target} -t $out/share/systemd/user
|
||||
install -Dm644 resources/niri{.service,-shutdown.target} -t $out/lib/systemd/user
|
||||
'';
|
||||
|
||||
env = {
|
||||
# Force linking with libEGL and libwayland-client
|
||||
# so they can be discovered by `dlopen()`
|
||||
# Force linking with libEGL and libwayland-client so they end up in RPATH and
|
||||
# can be discovered by `dlopen()`
|
||||
RUSTFLAGS = toString (
|
||||
map (arg: "-C link-arg=" + arg) [
|
||||
"-Wl,--push-state,--no-as-needed"
|
||||
@@ -182,33 +172,20 @@
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgsFor.${system};
|
||||
rust-bin = rust-overlay.lib.mkRustBin { } pkgs;
|
||||
rustfmt' = pkgs.rustfmt.override { asNightly = true; };
|
||||
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"
|
||||
];
|
||||
}
|
||||
))
|
||||
pkgs.cargo-insta
|
||||
];
|
||||
packages = builtins.attrValues {
|
||||
inherit (pkgs)
|
||||
rustc
|
||||
cargo
|
||||
clippy
|
||||
cargo-insta
|
||||
;
|
||||
inherit rustfmt';
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.rustPlatform.bindgenHook
|
||||
@@ -225,8 +202,8 @@
|
||||
# It is required for `dlopen()` to work on some libraries; see the comment
|
||||
# in the package expression
|
||||
#
|
||||
# This should only be set with `CARGO_BUILD_RUSTFLAGS="$CARGO_BUILD_RUSTFLAGS -C your-flags"`
|
||||
CARGO_BUILD_RUSTFLAGS = niri.RUSTFLAGS;
|
||||
# This should only be set with `RUSTFLAGS="$RUSTFLAGS -C your-flags"`
|
||||
RUSTFLAGS = niri.RUSTFLAGS;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ pub enum Trigger {
|
||||
TouchpadScrollUp,
|
||||
TouchpadScrollLeft,
|
||||
TouchpadScrollRight,
|
||||
TabletStylusButton1,
|
||||
TabletStylusButton2,
|
||||
TabletStylusButton3,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
@@ -1000,6 +1003,12 @@ impl FromStr for Key {
|
||||
Trigger::TouchpadScrollLeft
|
||||
} else if key.eq_ignore_ascii_case("TouchpadScrollRight") {
|
||||
Trigger::TouchpadScrollRight
|
||||
} else if key.eq_ignore_ascii_case("TabletStylusButton1") {
|
||||
Trigger::TabletStylusButton1
|
||||
} else if key.eq_ignore_ascii_case("TabletStylusButton2") {
|
||||
Trigger::TabletStylusButton2
|
||||
} else if key.eq_ignore_ascii_case("TabletStylusButton3") {
|
||||
Trigger::TabletStylusButton3
|
||||
} else {
|
||||
let mut keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE);
|
||||
// The keyboard event handling code can receive either
|
||||
|
||||
@@ -10,7 +10,6 @@ pub struct Debug {
|
||||
pub enable_overlay_planes: bool,
|
||||
pub disable_cursor_plane: bool,
|
||||
pub disable_direct_scanout: bool,
|
||||
pub keep_max_bpc_unchanged: bool,
|
||||
pub restrict_primary_scanout_to_matching_format: bool,
|
||||
pub force_disable_connectors_on_resume: bool,
|
||||
pub render_drm_device: Option<PathBuf>,
|
||||
@@ -42,8 +41,6 @@ pub struct DebugPart {
|
||||
#[knuffel(child)]
|
||||
pub disable_direct_scanout: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub keep_max_bpc_unchanged: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub restrict_primary_scanout_to_matching_format: Option<Flag>,
|
||||
#[knuffel(child)]
|
||||
pub force_disable_connectors_on_resume: Option<Flag>,
|
||||
@@ -82,7 +79,6 @@ impl MergeWith<DebugPart> for Debug {
|
||||
enable_overlay_planes,
|
||||
disable_cursor_plane,
|
||||
disable_direct_scanout,
|
||||
keep_max_bpc_unchanged,
|
||||
restrict_primary_scanout_to_matching_format,
|
||||
force_disable_connectors_on_resume,
|
||||
force_pipewire_invalid_modifier,
|
||||
|
||||
@@ -745,6 +745,7 @@ mod tests {
|
||||
transform "flipped-90"
|
||||
position x=10 y=20
|
||||
mode "1920x1080@144"
|
||||
max-bpc 10
|
||||
variable-refresh-rate on-demand=true
|
||||
background-color "rgba(25, 25, 102, 1.0)"
|
||||
hot-corners {
|
||||
@@ -857,7 +858,7 @@ mod tests {
|
||||
window-open { off; }
|
||||
|
||||
window-close {
|
||||
curve "cubic-bezier" 0.05 0.7 0.1 1
|
||||
curve "cubic-bezier" 0.05 0.7 0.1 1
|
||||
}
|
||||
|
||||
recent-windows-close {
|
||||
@@ -1160,6 +1161,11 @@ mod tests {
|
||||
y: 20,
|
||||
},
|
||||
),
|
||||
max_bpc: Some(
|
||||
MaxBpc(
|
||||
_10,
|
||||
),
|
||||
),
|
||||
mode: Some(
|
||||
Mode {
|
||||
custom: false,
|
||||
@@ -1205,6 +1211,7 @@ mod tests {
|
||||
scale: None,
|
||||
transform: Normal,
|
||||
position: None,
|
||||
max_bpc: None,
|
||||
mode: Some(
|
||||
Mode {
|
||||
custom: true,
|
||||
@@ -1231,6 +1238,7 @@ mod tests {
|
||||
scale: None,
|
||||
transform: Normal,
|
||||
position: None,
|
||||
max_bpc: None,
|
||||
mode: None,
|
||||
modeline: Some(
|
||||
Modeline {
|
||||
@@ -2244,7 +2252,6 @@ mod tests {
|
||||
enable_overlay_planes: false,
|
||||
disable_cursor_plane: false,
|
||||
disable_direct_scanout: false,
|
||||
keep_max_bpc_unchanged: false,
|
||||
restrict_primary_scanout_to_matching_format: false,
|
||||
force_disable_connectors_on_resume: false,
|
||||
render_drm_device: Some(
|
||||
|
||||
@@ -59,6 +59,8 @@ pub struct Output {
|
||||
pub transform: Transform,
|
||||
#[knuffel(child)]
|
||||
pub position: Option<Position>,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub max_bpc: Option<MaxBpc>,
|
||||
#[knuffel(child)]
|
||||
pub mode: Option<Mode>,
|
||||
#[knuffel(child)]
|
||||
@@ -101,6 +103,7 @@ impl Default for Output {
|
||||
scale: None,
|
||||
transform: Transform::Normal,
|
||||
position: None,
|
||||
max_bpc: None,
|
||||
mode: None,
|
||||
modeline: None,
|
||||
variable_refresh_rate: None,
|
||||
@@ -128,6 +131,9 @@ pub struct Position {
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct MaxBpc(pub niri_ipc::MaxBpc);
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Default)]
|
||||
pub struct Vrr {
|
||||
#[knuffel(property, default = false)]
|
||||
@@ -257,6 +263,42 @@ impl OutputName {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ErrorSpan> knuffel::DecodeScalar<S> for MaxBpc {
|
||||
fn type_check(
|
||||
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
|
||||
ctx: &mut Context<S>,
|
||||
) {
|
||||
if let Some(type_name) = &type_name {
|
||||
ctx.emit_error(DecodeError::unexpected(
|
||||
type_name,
|
||||
"type name",
|
||||
"no type name expected for this node",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_decode(
|
||||
value: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
|
||||
ctx: &mut Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
match &**value {
|
||||
knuffel::ast::Literal::Int(ref val) => match u8::try_from(val) {
|
||||
Ok(v) => niri_ipc::MaxBpc::try_from(v)
|
||||
.map(MaxBpc)
|
||||
.map_err(|e| DecodeError::conversion(value, e)),
|
||||
Err(e) => {
|
||||
ctx.emit_error(DecodeError::conversion(value, e));
|
||||
Ok(Self::default())
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
ctx.emit_error(DecodeError::scalar_kind(knuffel::decode::Kind::Int, value));
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ErrorSpan> knuffel::Decode<S> for Mode {
|
||||
fn decode_node(node: &SpannedNode<S>, ctx: &mut Context<S>) -> Result<Self, DecodeError<S>> {
|
||||
if let Some(type_name) = &node.type_name {
|
||||
|
||||
@@ -1097,6 +1097,12 @@ pub enum OutputAction {
|
||||
#[cfg_attr(feature = "clap", command(flatten))]
|
||||
vrr: VrrToSet,
|
||||
},
|
||||
/// Set the maximum bits per channel (bit depth).
|
||||
MaxBpc {
|
||||
/// Maximum bits per channel to set.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
max_bpc: MaxBpc,
|
||||
},
|
||||
}
|
||||
|
||||
/// Output mode to set.
|
||||
@@ -1228,6 +1234,8 @@ pub struct Output {
|
||||
///
|
||||
/// `None` if the output is not mapped to any logical output (for example, if it is disabled).
|
||||
pub logical: Option<LogicalOutput>,
|
||||
/// Maximum bits per channel (bit depth), if known.
|
||||
pub max_bpc: Option<u8>,
|
||||
}
|
||||
|
||||
/// Output mode.
|
||||
@@ -1291,6 +1299,32 @@ pub enum Transform {
|
||||
Flipped270,
|
||||
}
|
||||
|
||||
/// Output maximum bits per channel.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
pub enum MaxBpc {
|
||||
/// 6-bit.
|
||||
#[serde(rename = "6")]
|
||||
_6 = 6,
|
||||
/// 8-bit.
|
||||
#[default]
|
||||
#[serde(rename = "8")]
|
||||
_8 = 8,
|
||||
/// 10-bit.
|
||||
#[serde(rename = "10")]
|
||||
_10 = 10,
|
||||
/// 12-bit.
|
||||
#[serde(rename = "12")]
|
||||
_12 = 12,
|
||||
/// 14-bit.
|
||||
#[serde(rename = "14")]
|
||||
_14 = 14,
|
||||
/// 16-bit.
|
||||
#[serde(rename = "16")]
|
||||
_16 = 16,
|
||||
}
|
||||
|
||||
/// Toplevel window.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
|
||||
@@ -1868,6 +1902,30 @@ impl FromStr for Transform {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for MaxBpc {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
6 => Ok(MaxBpc::_6),
|
||||
8 => Ok(MaxBpc::_8),
|
||||
10 => Ok(MaxBpc::_10),
|
||||
12 => Ok(MaxBpc::_12),
|
||||
14 => Ok(MaxBpc::_14),
|
||||
16 => Ok(MaxBpc::_16),
|
||||
_ => Err("invalid max-bpc, can be 6, 8, 10, 12, 14, 16"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MaxBpc {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::try_from(s.parse::<u8>().unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Layer {
|
||||
type Err = &'static str;
|
||||
|
||||
|
||||
@@ -383,6 +383,7 @@ binds {
|
||||
// Example media keys mapping using playerctl.
|
||||
// This will work with any MPRIS-enabled media player.
|
||||
XF86AudioPlay allow-when-locked=true { spawn-sh "playerctl play-pause"; }
|
||||
XF86AudioPause allow-when-locked=true { spawn-sh "playerctl play-pause"; }
|
||||
XF86AudioStop allow-when-locked=true { spawn-sh "playerctl stop"; }
|
||||
XF86AudioPrev allow-when-locked=true { spawn-sh "playerctl previous"; }
|
||||
XF86AudioNext allow-when-locked=true { spawn-sh "playerctl next"; }
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::thread;
|
||||
|
||||
use accesskit::{
|
||||
ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Live, Node, NodeId, Role,
|
||||
Tree, TreeUpdate,
|
||||
Tree, TreeId, TreeUpdate,
|
||||
};
|
||||
use accesskit_unix::Adapter;
|
||||
use calloop::LoopHandle;
|
||||
@@ -220,6 +220,7 @@ impl Niri {
|
||||
let update = TreeUpdate {
|
||||
nodes,
|
||||
tree: None,
|
||||
tree_id: TreeId::ROOT,
|
||||
focus,
|
||||
};
|
||||
|
||||
@@ -246,6 +247,7 @@ impl Niri {
|
||||
let update = TreeUpdate {
|
||||
nodes: vec![(ID_ANNOUNCEMENT, node)],
|
||||
tree: None,
|
||||
tree_id: TreeId::ROOT,
|
||||
focus: self.a11y.focus,
|
||||
};
|
||||
|
||||
@@ -339,6 +341,7 @@ impl Niri {
|
||||
(ID_MRU, mru),
|
||||
],
|
||||
tree: Some(tree),
|
||||
tree_id: TreeId::ROOT,
|
||||
focus,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ impl Headless {
|
||||
vrr_supported: false,
|
||||
vrr_enabled: false,
|
||||
logical: Some(logical_output(&output)),
|
||||
max_bpc: None,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use anyhow::{anyhow, bail, ensure, Context};
|
||||
use bytemuck::cast_slice_mut;
|
||||
use drm_ffi::drm_mode_modeinfo;
|
||||
use libc::dev_t;
|
||||
use niri_config::output::Modeline;
|
||||
use niri_config::output::{MaxBpc, Modeline};
|
||||
use niri_config::{Config, OutputName};
|
||||
use niri_ipc::{HSyncPolarity, VSyncPolarity};
|
||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||
@@ -70,7 +70,11 @@ use crate::render_helpers::renderer::AsGlesRenderer;
|
||||
use crate::render_helpers::{resources, shaders, RenderCtx, RenderTarget};
|
||||
use crate::utils::{get_monotonic_time, is_laptop_panel, logical_output, PanelOrientation};
|
||||
|
||||
const SUPPORTED_COLOR_FORMATS: [Fourcc; 4] = [
|
||||
const SUPPORTED_COLOR_FORMATS: [Fourcc; 8] = [
|
||||
Fourcc::Xrgb2101010,
|
||||
Fourcc::Xbgr2101010,
|
||||
Fourcc::Argb2101010,
|
||||
Fourcc::Abgr2101010,
|
||||
Fourcc::Xrgb8888,
|
||||
Fourcc::Xbgr8888,
|
||||
Fourcc::Argb8888,
|
||||
@@ -405,6 +409,8 @@ struct ConnectorProperties<'a> {
|
||||
device: &'a DrmDevice,
|
||||
connector: connector::Handle,
|
||||
properties: Vec<(property::Info, property::RawValue)>,
|
||||
has_change: bool,
|
||||
requests: AtomicModeReq,
|
||||
}
|
||||
|
||||
impl Tty {
|
||||
@@ -676,16 +682,19 @@ impl Tty {
|
||||
// Apply pending gamma changes and restore our existing gamma.
|
||||
let device = self.devices.get_mut(&node).unwrap();
|
||||
for (crtc, surface) in device.surfaces.iter_mut() {
|
||||
if let Ok(props) =
|
||||
if let Ok(mut props) =
|
||||
ConnectorProperties::try_new(&device.drm, surface.connector)
|
||||
{
|
||||
match reset_hdr(&props) {
|
||||
Ok(()) => (),
|
||||
Err(err) => debug!("couldn't reset HDR properties: {err:?}"),
|
||||
}
|
||||
let max_bpc = self
|
||||
.config
|
||||
.borrow()
|
||||
.outputs
|
||||
.find(&surface.name)
|
||||
.and_then(|o| o.max_bpc);
|
||||
set_connector_properties(&mut props, max_bpc, true);
|
||||
} else {
|
||||
warn!("failed to get connector properties");
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(ramp) = surface.pending_gamma_change.take() {
|
||||
let ramp = ramp.as_deref();
|
||||
@@ -1302,13 +1311,10 @@ impl Tty {
|
||||
debug!("picking mode: {mode:?}");
|
||||
|
||||
let mut orientation = None;
|
||||
if let Ok(props) = ConnectorProperties::try_new(&device.drm, connector.handle()) {
|
||||
match reset_hdr(&props) {
|
||||
Ok(()) => (),
|
||||
Err(err) => debug!("couldn't reset HDR properties: {err:?}"),
|
||||
}
|
||||
if let Ok(mut props) = ConnectorProperties::try_new(&device.drm, connector.handle()) {
|
||||
set_connector_properties(&mut props, config.max_bpc, true);
|
||||
|
||||
match get_panel_orientation(&props) {
|
||||
match props.get_panel_orientation() {
|
||||
Ok(x) => orientation = Some(x),
|
||||
Err(err) => {
|
||||
trace!("couldn't get panel orientation: {err:?}");
|
||||
@@ -1316,7 +1322,7 @@ impl Tty {
|
||||
}
|
||||
} else {
|
||||
warn!("failed to get connector properties");
|
||||
};
|
||||
}
|
||||
|
||||
let mut gamma_props = GammaProps::new(&device.drm, crtc)
|
||||
.map_err(|err| debug!("couldn't get gamma properties: {err:?}"))
|
||||
@@ -2194,6 +2200,15 @@ impl Tty {
|
||||
OutputId::next()
|
||||
});
|
||||
|
||||
let props = ConnectorProperties::try_new(&device.drm, connector.handle()).ok();
|
||||
let max_bpc = props.as_ref().and_then(|p| p.find(c"max bpc").ok());
|
||||
let max_bpc = max_bpc.and_then(|(info, value)| {
|
||||
info.value_type()
|
||||
.convert_value(*value)
|
||||
.as_unsigned_range()
|
||||
.map(|v| v as u8)
|
||||
});
|
||||
|
||||
let ipc_output = niri_ipc::Output {
|
||||
name: connector_name,
|
||||
make: output_name.make.unwrap_or_else(|| "Unknown".into()),
|
||||
@@ -2206,6 +2221,7 @@ impl Tty {
|
||||
vrr_supported,
|
||||
vrr_enabled,
|
||||
logical,
|
||||
max_bpc,
|
||||
};
|
||||
|
||||
ipc_outputs.insert(id, ipc_output);
|
||||
@@ -2422,6 +2438,13 @@ impl Tty {
|
||||
},
|
||||
};
|
||||
|
||||
if let Ok(mut props) = ConnectorProperties::try_new(&device.drm, surface.connector)
|
||||
{
|
||||
set_connector_properties(&mut props, config.max_bpc, false);
|
||||
} else {
|
||||
warn!("failed to get connector properties");
|
||||
}
|
||||
|
||||
let change_mode = surface.compositor.pending_mode() != mode;
|
||||
|
||||
let vrr_enabled = surface.compositor.vrr_enabled();
|
||||
@@ -3250,6 +3273,8 @@ impl<'a> ConnectorProperties<'a> {
|
||||
device,
|
||||
connector,
|
||||
properties,
|
||||
has_change: false,
|
||||
requests: AtomicModeReq::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3262,35 +3287,115 @@ impl<'a> ConnectorProperties<'a> {
|
||||
|
||||
Err(anyhow!("couldn't find property: {name:?}"))
|
||||
}
|
||||
|
||||
fn get_panel_orientation(&self) -> anyhow::Result<Transform> {
|
||||
let (info, value) = self.find(c"panel orientation")?;
|
||||
match info.value_type().convert_value(*value) {
|
||||
property::Value::Enum(Some(val)) => match val.value() {
|
||||
// "Normal"
|
||||
0 => Ok(Transform::Normal),
|
||||
// "Upside Down"
|
||||
1 => Ok(Transform::_180),
|
||||
// "Left Side Up"
|
||||
2 => Ok(Transform::_90),
|
||||
// "Right Side Up"
|
||||
3 => Ok(Transform::_270),
|
||||
_ => bail!("panel orientation has invalid value: {:?}", val),
|
||||
},
|
||||
_ => bail!("panel orientation has wrong value type"),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_hdr(&mut self) -> anyhow::Result<()> {
|
||||
const DRM_MODE_COLORIMETRY_DEFAULT: u64 = 0;
|
||||
|
||||
let (info, value) = self.find(c"HDR_OUTPUT_METADATA")?;
|
||||
|
||||
let property::ValueType::Blob = info.value_type() else {
|
||||
bail!("wrong property type")
|
||||
};
|
||||
if *value != 0 {
|
||||
self.requests
|
||||
.add_raw_property(self.connector.into(), info.handle(), 0);
|
||||
self.has_change = true;
|
||||
}
|
||||
|
||||
let (info, value) = self.find(c"Colorspace")?;
|
||||
let property::ValueType::Enum(_) = info.value_type() else {
|
||||
bail!("wrong property type")
|
||||
};
|
||||
if *value != DRM_MODE_COLORIMETRY_DEFAULT {
|
||||
self.requests.add_raw_property(
|
||||
self.connector.into(),
|
||||
info.handle(),
|
||||
DRM_MODE_COLORIMETRY_DEFAULT,
|
||||
);
|
||||
self.has_change = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_max_bpc(&mut self, max_bpc: MaxBpc) -> anyhow::Result<u64> {
|
||||
let (info, value) = self.find(c"max bpc")?;
|
||||
|
||||
let property::ValueType::UnsignedRange(min, max) = info.value_type() else {
|
||||
bail!("wrong property type")
|
||||
};
|
||||
|
||||
let max_bpc = max_bpc.0 as u64;
|
||||
if !(min..=max).contains(&max_bpc) {
|
||||
bail!("max-bpc {max_bpc} outside valid range of [{min}, {max}]");
|
||||
}
|
||||
|
||||
let property::Value::UnsignedRange(value) = info.value_type().convert_value(*value) else {
|
||||
bail!("wrong property type")
|
||||
};
|
||||
|
||||
if value != max_bpc {
|
||||
self.requests.add_raw_property(
|
||||
self.connector.into(),
|
||||
info.handle(),
|
||||
property::Value::UnsignedRange(max_bpc).into(),
|
||||
);
|
||||
self.has_change = true;
|
||||
}
|
||||
|
||||
Ok(max_bpc)
|
||||
}
|
||||
|
||||
fn commit(&mut self) -> anyhow::Result<()> {
|
||||
if self.has_change {
|
||||
self.device.atomic_commit(
|
||||
AtomicCommitFlags::ALLOW_MODESET,
|
||||
std::mem::take(&mut self.requests),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const DRM_MODE_COLORIMETRY_DEFAULT: u64 = 0;
|
||||
|
||||
fn reset_hdr(props: &ConnectorProperties) -> anyhow::Result<()> {
|
||||
let (info, value) = props.find(c"HDR_OUTPUT_METADATA")?;
|
||||
let property::ValueType::Blob = info.value_type() else {
|
||||
bail!("wrong property type")
|
||||
};
|
||||
|
||||
if *value != 0 {
|
||||
props
|
||||
.device
|
||||
.set_property(props.connector, info.handle(), 0)
|
||||
.context("error setting property")?;
|
||||
fn set_connector_properties(
|
||||
props: &mut ConnectorProperties,
|
||||
max_bpc: Option<MaxBpc>,
|
||||
reset_hdr: bool,
|
||||
) {
|
||||
if let Some(max_bpc) = max_bpc {
|
||||
if let Err(err) = props.set_max_bpc(max_bpc) {
|
||||
debug!("failed to set `max bpc` property: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
let (info, value) = props.find(c"Colorspace")?;
|
||||
let property::ValueType::Enum(_) = info.value_type() else {
|
||||
bail!("wrong property type")
|
||||
};
|
||||
if *value != DRM_MODE_COLORIMETRY_DEFAULT {
|
||||
props
|
||||
.device
|
||||
.set_property(props.connector, info.handle(), DRM_MODE_COLORIMETRY_DEFAULT)
|
||||
.context("error setting property")?;
|
||||
if reset_hdr {
|
||||
if let Err(err) = props.reset_hdr() {
|
||||
debug!("failed to set HDR properties: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if let Err(err) = props.commit() {
|
||||
warn!("failed to atomically commit properties: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
fn is_vrr_capable(device: &DrmDevice, connector: connector::Handle) -> Option<bool> {
|
||||
@@ -3298,24 +3403,6 @@ fn is_vrr_capable(device: &DrmDevice, connector: connector::Handle) -> Option<bo
|
||||
info.value_type().convert_value(value).as_boolean()
|
||||
}
|
||||
|
||||
fn get_panel_orientation(props: &ConnectorProperties) -> anyhow::Result<Transform> {
|
||||
let (info, value) = props.find(c"panel orientation")?;
|
||||
match info.value_type().convert_value(*value) {
|
||||
property::Value::Enum(Some(val)) => match val.value() {
|
||||
// "Normal"
|
||||
0 => Ok(Transform::Normal),
|
||||
// "Upside Down"
|
||||
1 => Ok(Transform::_180),
|
||||
// "Left Side Up"
|
||||
2 => Ok(Transform::_90),
|
||||
// "Right Side Up"
|
||||
3 => Ok(Transform::_270),
|
||||
_ => bail!("panel orientation has invalid value: {:?}", val),
|
||||
},
|
||||
_ => bail!("panel orientation has wrong value type"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_gamma_for_crtc(
|
||||
device: &DrmDevice,
|
||||
crtc: crtc::Handle,
|
||||
|
||||
@@ -95,6 +95,7 @@ impl Winit {
|
||||
vrr_supported: false,
|
||||
vrr_enabled: false,
|
||||
logical: Some(logical_output(&output)),
|
||||
max_bpc: None,
|
||||
},
|
||||
)])));
|
||||
|
||||
|
||||
@@ -3778,11 +3778,53 @@ impl State {
|
||||
}
|
||||
|
||||
fn on_tablet_tool_button<I: InputBackend>(&mut self, event: I::TabletToolButtonEvent) {
|
||||
const BTN_STYLUS3: u32 = 0x149;
|
||||
const BTN_STYLUS: u32 = 0x14b;
|
||||
const BTN_STYLUS2: u32 = 0x14c;
|
||||
|
||||
let tool = self.niri.seat.tablet_seat().get_tool(&event.tool());
|
||||
|
||||
if let Some(tool) = tool {
|
||||
let button = event.button();
|
||||
|
||||
if self.niri.suppressed_buttons.remove(&button) {
|
||||
return;
|
||||
}
|
||||
|
||||
let trigger = match button {
|
||||
BTN_STYLUS => Some(Trigger::TabletStylusButton1),
|
||||
BTN_STYLUS2 => Some(Trigger::TabletStylusButton2),
|
||||
BTN_STYLUS3 => Some(Trigger::TabletStylusButton3),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(trigger) = trigger {
|
||||
if event.button_state() == ButtonState::Pressed {
|
||||
let mod_key = self.backend.mod_key(&self.niri.config.borrow());
|
||||
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||
let modifiers = modifiers_from_state(mods);
|
||||
|
||||
if self.niri.mods_with_tablet_stylus_binds.contains(&modifiers) {
|
||||
let bind = {
|
||||
let config = self.niri.config.borrow();
|
||||
let bindings = config.binds.0.iter();
|
||||
find_configured_bind(bindings, mod_key, trigger, mods)
|
||||
}
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
if let Some(bind) = bind {
|
||||
self.niri.suppressed_buttons.insert(button);
|
||||
self.handle_bind(bind.clone());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tool.button(
|
||||
event.button(),
|
||||
button,
|
||||
event.button_state(),
|
||||
SERIAL_COUNTER.next_serial(),
|
||||
event.time_msec(),
|
||||
@@ -5069,6 +5111,18 @@ pub fn mods_with_finger_scroll_binds(mod_key: ModKey, binds: &Binds) -> HashSet<
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mods_with_tablet_stylus_binds(mod_key: ModKey, binds: &Binds) -> HashSet<Modifiers> {
|
||||
mods_with_binds(
|
||||
mod_key,
|
||||
binds,
|
||||
&[
|
||||
Trigger::TabletStylusButton1,
|
||||
Trigger::TabletStylusButton2,
|
||||
Trigger::TabletStylusButton3,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn grab_allows_hot_corner(grab: &(dyn PointerGrab<State> + 'static)) -> bool {
|
||||
let grab = grab.as_any();
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ pub fn handle_msg(mut msg: Msg, json: bool) -> anyhow::Result<()> {
|
||||
|
||||
let print = |surface: &niri_ipc::LayerSurface| {
|
||||
println!(" Surface:");
|
||||
println!(" Namespace: \"{}\"", &surface.namespace);
|
||||
println!(" Namespace: \"{}\"", surface.namespace);
|
||||
|
||||
let interactivity = match surface.keyboard_interactivity {
|
||||
niri_ipc::LayerSurfaceKeyboardInteractivity::None => "none",
|
||||
@@ -568,6 +568,7 @@ fn print_output(output: Output) -> anyhow::Result<()> {
|
||||
vrr_supported,
|
||||
vrr_enabled,
|
||||
logical,
|
||||
max_bpc,
|
||||
} = output;
|
||||
|
||||
let serial = serial.as_deref().unwrap_or("Unknown");
|
||||
@@ -651,6 +652,10 @@ fn print_output(output: Output) -> anyhow::Result<()> {
|
||||
println!(" Transform: {transform}");
|
||||
}
|
||||
|
||||
if let Some(max_bpc) = max_bpc {
|
||||
println!(" Max bits per channel: {max_bpc}");
|
||||
}
|
||||
|
||||
println!(" Available modes:");
|
||||
for (idx, mode) in modes.into_iter().enumerate() {
|
||||
let Mode {
|
||||
|
||||
@@ -14,6 +14,7 @@ use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as
|
||||
use anyhow::{bail, ensure, Context};
|
||||
use calloop::futures::Scheduler;
|
||||
use niri_config::debug::PreviewRender;
|
||||
use niri_config::output::MaxBpc;
|
||||
use niri_config::{
|
||||
Config, FloatOrInt, Key, Modifiers, OutputName, TrackLayout, WarpMouseToFocusMode,
|
||||
WorkspaceReference, Xkb,
|
||||
@@ -133,7 +134,7 @@ use crate::input::scroll_swipe_gesture::ScrollSwipeGesture;
|
||||
use crate::input::scroll_tracker::ScrollTracker;
|
||||
use crate::input::{
|
||||
apply_libinput_settings, mods_with_finger_scroll_binds, mods_with_mouse_binds,
|
||||
mods_with_wheel_binds, TabletData,
|
||||
mods_with_tablet_stylus_binds, mods_with_wheel_binds, TabletData,
|
||||
};
|
||||
use crate::ipc::server::IpcServer;
|
||||
use crate::layer::mapped::LayerSurfaceRenderElement;
|
||||
@@ -375,6 +376,7 @@ pub struct Niri {
|
||||
pub horizontal_wheel_tracker: ScrollTracker,
|
||||
pub mods_with_mouse_binds: HashSet<Modifiers>,
|
||||
pub mods_with_wheel_binds: HashSet<Modifiers>,
|
||||
pub mods_with_tablet_stylus_binds: HashSet<Modifiers>,
|
||||
pub vertical_finger_scroll_tracker: ScrollTracker,
|
||||
pub horizontal_finger_scroll_tracker: ScrollTracker,
|
||||
pub mods_with_finger_scroll_binds: HashSet<Modifiers>,
|
||||
@@ -991,6 +993,12 @@ impl State {
|
||||
|
||||
pub fn confirm_mru(&mut self) {
|
||||
if let Some(window) = self.niri.close_mru(MruCloseRequest::Confirm) {
|
||||
// focus_window() will warp the cursor to the window only when the keyboard focus is on
|
||||
// the layout. However, right now the keyboard focus is still on the MRU (that we had
|
||||
// just closed) since it's only updated at the end of the event loop cycle. Force-update
|
||||
// the keyboard focus here to make cursor warping work.
|
||||
self.update_keyboard_focus();
|
||||
|
||||
self.focus_window(&window);
|
||||
}
|
||||
}
|
||||
@@ -1533,6 +1541,8 @@ impl State {
|
||||
.on_hotkey_config_updated(new_mod_key);
|
||||
self.niri.mods_with_mouse_binds = mods_with_mouse_binds(new_mod_key, &config.binds);
|
||||
self.niri.mods_with_wheel_binds = mods_with_wheel_binds(new_mod_key, &config.binds);
|
||||
self.niri.mods_with_tablet_stylus_binds =
|
||||
mods_with_tablet_stylus_binds(new_mod_key, &config.binds);
|
||||
self.niri.mods_with_finger_scroll_binds =
|
||||
mods_with_finger_scroll_binds(new_mod_key, &config.binds);
|
||||
}
|
||||
@@ -1925,6 +1935,7 @@ impl State {
|
||||
None
|
||||
}
|
||||
}
|
||||
niri_ipc::OutputAction::MaxBpc { max_bpc } => config.max_bpc = Some(MaxBpc(max_bpc)),
|
||||
});
|
||||
|
||||
self.reload_output_config();
|
||||
@@ -2405,6 +2416,7 @@ impl Niri {
|
||||
let mods_with_mouse_binds = mods_with_mouse_binds(mod_key, &config_.binds);
|
||||
let mods_with_wheel_binds = mods_with_wheel_binds(mod_key, &config_.binds);
|
||||
let mods_with_finger_scroll_binds = mods_with_finger_scroll_binds(mod_key, &config_.binds);
|
||||
let mods_with_tablet_stylus_binds = mods_with_tablet_stylus_binds(mod_key, &config_.binds);
|
||||
|
||||
let screenshot_ui = ScreenshotUi::new(animation_clock.clone(), config.clone());
|
||||
let window_mru_ui = WindowMruUi::new(config.clone());
|
||||
@@ -2586,6 +2598,7 @@ impl Niri {
|
||||
horizontal_wheel_tracker: ScrollTracker::new(120),
|
||||
mods_with_mouse_binds,
|
||||
mods_with_wheel_binds,
|
||||
mods_with_tablet_stylus_binds,
|
||||
|
||||
// 10 is copied from Clutter: DISCRETE_SCROLL_STEP.
|
||||
vertical_finger_scroll_tracker: ScrollTracker::new(10),
|
||||
|
||||
@@ -200,7 +200,7 @@ fn refresh_workspace_group(protocol_state: &mut ExtWorkspaceManagerState, output
|
||||
// Send workspace_enter for all existing workspaces on this output.
|
||||
for group in &data.instances {
|
||||
let manager: &ExtWorkspaceManagerV1 = group.data().unwrap();
|
||||
for (_, ws) in protocol_state.workspaces.iter() {
|
||||
for ws in protocol_state.workspaces.values() {
|
||||
if ws.output.as_ref() != Some(output) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ impl OutputManagementManagerState {
|
||||
notify_new_head(self, output, conf);
|
||||
}
|
||||
}
|
||||
for (old, _) in self.current_state.iter() {
|
||||
for old in self.current_state.keys() {
|
||||
if !new_state.contains_key(old) {
|
||||
changed = true;
|
||||
notify_removed_head(&mut self.clients, old);
|
||||
|
||||
@@ -560,6 +560,9 @@ fn key_name(screen_reader: bool, mod_key: ModKey, key: &Key) -> String {
|
||||
Trigger::TouchpadScrollUp => String::from("Touchpad Scroll Up"),
|
||||
Trigger::TouchpadScrollLeft => String::from("Touchpad Scroll Left"),
|
||||
Trigger::TouchpadScrollRight => String::from("Touchpad Scroll Right"),
|
||||
Trigger::TabletStylusButton1 => String::from("Tablet Stylus Button 1"),
|
||||
Trigger::TabletStylusButton2 => String::from("Tablet Stylus Button 2"),
|
||||
Trigger::TabletStylusButton3 => String::from("Tablet Stylus Button 3"),
|
||||
};
|
||||
name.push_str(&pretty);
|
||||
|
||||
|
||||