mirror of
https://github.com/YaLTeR/niri.git
synced 2024-09-11 20:37:25 +03:00
Compare commits
14 Commits
c05c69a856
...
f70e1018da
Author | SHA1 | Date | |
---|---|---|---|
|
f70e1018da | ||
|
2f73dd5b59 | ||
|
c658424c9f | ||
|
bb58f2d162 | ||
|
f54297f242 | ||
|
b72d946062 | ||
|
3d6040ceab | ||
|
c498ffcc34 | ||
|
7bcb649d7a | ||
|
176de91342 | ||
|
13911640b0 | ||
|
5da2ad89dd | ||
|
f76b3bb794 | ||
|
b12396d8a1 |
24
flake.lock
24
flake.lock
@ -7,11 +7,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1720226507,
|
||||
"narHash": "sha256-yHVvNsgrpyNTXZBEokL8uyB2J6gB1wEx0KOJzoeZi1A=",
|
||||
"lastModified": 1722960479,
|
||||
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "0aed560c5c0a61c9385bddff471a13036203e11c",
|
||||
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -28,11 +28,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1719815435,
|
||||
"narHash": "sha256-K2xFp142onP35jcx7li10xUxNVEVRWjAdY8DSuR7Naw=",
|
||||
"lastModified": 1722493751,
|
||||
"narHash": "sha256-l7/yMehbrL5d4AI8E2hKtNlT50BlUAau4EKTgPg9KcY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "ebfe2c639111d7e82972a12711206afaeeda2450",
|
||||
"rev": "60ab4a085ef6ee40f2ef7921ca4061084dd8cf26",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -77,11 +77,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1720368505,
|
||||
"narHash": "sha256-5r0pInVo5d6Enti0YwUSQK4TebITypB42bWy5su3MrQ=",
|
||||
"lastModified": 1723572004,
|
||||
"narHash": "sha256-U5gKtbKuPahB02iGeGHFPlKr/HqrvSsHlEDEXoVyaPc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ab82a9612aa45284d4adf69ee81871a389669a9e",
|
||||
"rev": "19674872444bb3e0768249e724d99c8649c3bd78",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -103,11 +103,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1719760370,
|
||||
"narHash": "sha256-fsxAuW6RxKZYjAP3biUC6C4vaYFhDfWv8lp1Tmx3ZCY=",
|
||||
"lastModified": 1722449213,
|
||||
"narHash": "sha256-1na4m2PNH99syz2g/WQ+Hr3RfY7k4H8NBnmkr5dFDXw=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "ea7fdada6a0940b239ddbde2048a4d7dac1efe1e",
|
||||
"rev": "c8e41d95061543715b30880932ec3dc24c42d7ae",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -33,7 +33,7 @@
|
||||
system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
toolchain = fenix.packages.${system}.complete.toolchain;
|
||||
craneLib = crane.lib.${system}.overrideToolchain toolchain;
|
||||
craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;
|
||||
|
||||
craneArgs = {
|
||||
pname = "niri";
|
||||
@ -80,6 +80,7 @@
|
||||
];
|
||||
|
||||
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath craneArgs.runtimeDependencies; # Needed for tests to find libxkbcommon
|
||||
};
|
||||
|
||||
cargoArtifacts = craneLib.buildDepsOnly craneArgs;
|
||||
|
@ -60,8 +60,8 @@ pub struct Config {
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct Input {
|
||||
#[knuffel(child, default)]
|
||||
pub keyboard: Keyboard,
|
||||
#[knuffel(children(name = "keyboard"), default)]
|
||||
pub keyboards: Vec<Keyboard>,
|
||||
#[knuffel(child, default)]
|
||||
pub touchpad: Touchpad,
|
||||
#[knuffel(child, default)]
|
||||
@ -82,8 +82,28 @@ pub struct Input {
|
||||
pub workspace_auto_back_and_forth: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, PartialEq, Eq)]
|
||||
impl Input {
|
||||
pub fn fallback_keyboard(&self) -> Keyboard {
|
||||
self.keyboards
|
||||
.iter()
|
||||
.find(|keyboard| keyboard.name.is_none())
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn keyboard_named<K: AsRef<str>>(&self, name: K) -> Keyboard {
|
||||
self.keyboards
|
||||
.iter()
|
||||
.find(|keyboard| keyboard.name.as_deref() == Some(name.as_ref()))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.fallback_keyboard())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Keyboard {
|
||||
#[knuffel(argument, unwrap(argument))]
|
||||
pub name: Option<String>,
|
||||
#[knuffel(child, default)]
|
||||
pub xkb: Xkb,
|
||||
// The defaults were chosen to match wlroots and sway.
|
||||
@ -98,6 +118,7 @@ pub struct Keyboard {
|
||||
impl Default for Keyboard {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
xkb: Default::default(),
|
||||
repeat_delay: 600,
|
||||
repeat_rate: 25,
|
||||
@ -144,7 +165,7 @@ pub enum CenterFocusedColumn {
|
||||
OnOverflow,
|
||||
}
|
||||
|
||||
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq)]
|
||||
#[derive(knuffel::DecodeScalar, Debug, Default, PartialEq, Eq, Clone)]
|
||||
pub enum TrackLayout {
|
||||
/// The layout change is global.
|
||||
#[default]
|
||||
@ -2657,6 +2678,16 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
keyboard "test_keyboard" {
|
||||
repeat-delay 500
|
||||
repeat-rate 30
|
||||
track-layout "window"
|
||||
xkb {
|
||||
layout "us,ru"
|
||||
options "grp:win_space_toggle"
|
||||
}
|
||||
}
|
||||
|
||||
touchpad {
|
||||
tap
|
||||
dwt
|
||||
@ -2821,16 +2852,30 @@ mod tests {
|
||||
"##,
|
||||
Config {
|
||||
input: Input {
|
||||
keyboard: Keyboard {
|
||||
xkb: Xkb {
|
||||
layout: "us,ru".to_owned(),
|
||||
options: Some("grp:win_space_toggle".to_owned()),
|
||||
keyboards: vec![
|
||||
Keyboard {
|
||||
xkb: Xkb {
|
||||
layout: "us,ru".to_owned(),
|
||||
options: Some("grp:win_space_toggle".to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
repeat_delay: 600,
|
||||
repeat_rate: 25,
|
||||
track_layout: TrackLayout::Window,
|
||||
..Default::default()
|
||||
},
|
||||
repeat_delay: 600,
|
||||
repeat_rate: 25,
|
||||
track_layout: TrackLayout::Window,
|
||||
},
|
||||
Keyboard {
|
||||
xkb: Xkb {
|
||||
layout: "us,ru".to_owned(),
|
||||
options: Some("grp:win_space_toggle".to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
repeat_delay: 500,
|
||||
repeat_rate: 30,
|
||||
track_layout: TrackLayout::Window,
|
||||
name: Some("test_keyboard".to_owned()),
|
||||
},
|
||||
],
|
||||
touchpad: Touchpad {
|
||||
off: false,
|
||||
tap: true,
|
||||
@ -3315,7 +3360,7 @@ mod tests {
|
||||
#[test]
|
||||
fn default_repeat_params() {
|
||||
let config = Config::parse("config.kdl", "").unwrap();
|
||||
assert_eq!(config.input.keyboard.repeat_delay, 600);
|
||||
assert_eq!(config.input.keyboard.repeat_rate, 25);
|
||||
assert_eq!(config.input.fallback_keyboard().repeat_delay, 600);
|
||||
assert_eq!(config.input.fallback_keyboard().repeat_rate, 25);
|
||||
}
|
||||
}
|
||||
|
@ -287,7 +287,12 @@ impl State {
|
||||
Some(pos + target_geo.loc.to_f64())
|
||||
}
|
||||
|
||||
fn on_keyboard<I: InputBackend>(&mut self, event: I::KeyboardKeyEvent) {
|
||||
fn on_keyboard<I: InputBackend>(&mut self, event: I::KeyboardKeyEvent)
|
||||
where
|
||||
I::Device: 'static,
|
||||
{
|
||||
self.apply_keyboard_config::<I>(&event);
|
||||
|
||||
let comp_mod = self.backend.mod_key();
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
@ -383,6 +388,57 @@ impl State {
|
||||
self.niri.bind_repeat_timer = Some(token);
|
||||
}
|
||||
|
||||
fn apply_keyboard_config<I: InputBackend>(&mut self, event: &I::KeyboardKeyEvent)
|
||||
where
|
||||
I::Device: 'static,
|
||||
{
|
||||
let device = event.device();
|
||||
|
||||
let keyboard_config =
|
||||
if let Some(input_device) = (&device as &dyn Any).downcast_ref::<input::Device>() {
|
||||
if let Some(cached) = self.niri.keyboard_device_cache.get(input_device).cloned() {
|
||||
cached
|
||||
} else {
|
||||
let keyboard_config = self
|
||||
.niri
|
||||
.config
|
||||
.borrow()
|
||||
.input
|
||||
.keyboard_named(device.name());
|
||||
|
||||
self.niri
|
||||
.keyboard_device_cache
|
||||
.insert(input_device.clone(), keyboard_config.clone());
|
||||
|
||||
keyboard_config
|
||||
}
|
||||
} else {
|
||||
self.niri.config.borrow().input.fallback_keyboard()
|
||||
};
|
||||
|
||||
if keyboard_config != self.niri.current_keyboard {
|
||||
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
||||
|
||||
if keyboard_config.xkb != self.niri.current_keyboard.xkb {
|
||||
if let Err(err) = keyboard.set_xkb_config(self, keyboard_config.xkb.to_xkb_config())
|
||||
{
|
||||
warn!("error updating xkb config: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
if keyboard_config.repeat_rate != self.niri.current_keyboard.repeat_rate
|
||||
|| keyboard_config.repeat_delay != self.niri.current_keyboard.repeat_delay
|
||||
{
|
||||
keyboard.change_repeat_info(
|
||||
keyboard_config.repeat_rate.into(),
|
||||
keyboard_config.repeat_delay.into(),
|
||||
);
|
||||
}
|
||||
|
||||
self.niri.current_keyboard = keyboard_config;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_bind(&mut self, bind: Bind) {
|
||||
let Some(cooldown) = bind.cooldown else {
|
||||
self.do_action(bind.action, bind.allow_when_locked);
|
||||
|
28
src/niri.rs
28
src/niri.rs
@ -195,7 +195,9 @@ pub struct Niri {
|
||||
// When false, we're idling with monitors powered off.
|
||||
pub monitors_active: bool,
|
||||
|
||||
pub current_keyboard: niri_config::Keyboard,
|
||||
pub devices: HashSet<input::Device>,
|
||||
pub keyboard_device_cache: HashMap<input::Device, niri_config::Keyboard>,
|
||||
pub tablets: HashMap<input::Device, TabletData>,
|
||||
pub touch: HashSet<input::Device>,
|
||||
|
||||
@ -860,7 +862,7 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
if self.niri.config.borrow().input.keyboard.track_layout == TrackLayout::Window {
|
||||
if self.niri.current_keyboard.track_layout == TrackLayout::Window {
|
||||
let current_layout =
|
||||
keyboard.with_xkb_state(self, |context| context.active_layout());
|
||||
|
||||
@ -960,19 +962,23 @@ impl State {
|
||||
self.niri.cursor_texture_cache.clear();
|
||||
}
|
||||
|
||||
let default_keyboard = config.input.fallback_keyboard();
|
||||
|
||||
let current_keyboard = self.niri.current_keyboard.clone();
|
||||
|
||||
// We need &mut self to reload the xkb config, so just store it here.
|
||||
if config.input.keyboard.xkb != old_config.input.keyboard.xkb {
|
||||
reload_xkb = Some(config.input.keyboard.xkb.clone());
|
||||
if default_keyboard.xkb != current_keyboard.xkb {
|
||||
reload_xkb = Some(default_keyboard.xkb.clone());
|
||||
}
|
||||
|
||||
// Reload the repeat info.
|
||||
if config.input.keyboard.repeat_rate != old_config.input.keyboard.repeat_rate
|
||||
|| config.input.keyboard.repeat_delay != old_config.input.keyboard.repeat_delay
|
||||
if default_keyboard.repeat_rate != current_keyboard.repeat_rate
|
||||
|| default_keyboard.repeat_delay != current_keyboard.repeat_delay
|
||||
{
|
||||
let keyboard = self.niri.seat.get_keyboard().unwrap();
|
||||
keyboard.change_repeat_info(
|
||||
config.input.keyboard.repeat_rate.into(),
|
||||
config.input.keyboard.repeat_delay.into(),
|
||||
default_keyboard.repeat_rate.into(),
|
||||
default_keyboard.repeat_delay.into(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1609,9 +1615,9 @@ impl Niri {
|
||||
|
||||
let mut seat: Seat<State> = seat_state.new_wl_seat(&display_handle, backend.seat_name());
|
||||
seat.add_keyboard(
|
||||
config_.input.keyboard.xkb.to_xkb_config(),
|
||||
config_.input.keyboard.repeat_delay.into(),
|
||||
config_.input.keyboard.repeat_rate.into(),
|
||||
config_.input.fallback_keyboard().xkb.to_xkb_config(),
|
||||
config_.input.fallback_keyboard().repeat_delay.into(),
|
||||
config_.input.fallback_keyboard().repeat_rate.into(),
|
||||
)
|
||||
.unwrap();
|
||||
seat.add_pointer();
|
||||
@ -1727,7 +1733,9 @@ impl Niri {
|
||||
root_surface: HashMap::new(),
|
||||
monitors_active: true,
|
||||
|
||||
current_keyboard: Default::default(),
|
||||
devices: HashSet::new(),
|
||||
keyboard_device_cache: HashMap::new(),
|
||||
tablets: HashMap::new(),
|
||||
touch: HashSet::new(),
|
||||
|
||||
|
@ -24,7 +24,7 @@ workspace "chat" {
|
||||
open-on-output "DP-2"
|
||||
}
|
||||
|
||||
// Open Fractal on the "chat" workspace at niri startup.
|
||||
// Open Fractal on the "chat" workspace, if it runs at niri startup.
|
||||
window-rule {
|
||||
match at-startup=true app-id=r#"^org\.gnome\.Fractal$"#
|
||||
open-on-workspace "chat"
|
||||
|
@ -22,3 +22,9 @@ And here are some more principles I try to follow throughout niri.
|
||||
1. Eye-candy features should not cause unreasonable excessive rendering.
|
||||
- For example, clip-to-geometry will prevent direct scanout in many cases (since the window surface is not completely visible). But in the cases where the surface or the subsurface *is* completely visible (fully within the clipped region), it will still allow for direct scanout.
|
||||
- For example, animations *can* cause damage and even draw to an offscreen every frame, because they are expected to be short (and can be disabled). However, something like the rounded corners shader should not offscreen or cause excessive damage every frame, because it is long-running and constantly active.
|
||||
1. Be mindful of invisible state.
|
||||
|
||||
This is niri state that is not immediately apparent from looking at the screen. This is not bad per se, but you should carefully consider how to reduce the surprise factor.
|
||||
|
||||
- For example, when a monitor disconnects, all its workspaces move to another connected monitor. In order to be able to restore these workspaces when the first monitor connects again, these workspaces keep the knowledge of which was their *original monitor*—this is an example of invisible state, since you can't tell it in any way by looking at the screen. This can have surprising consequences: imagine disconnecting a monitor at home, going to work, completely rearranging the windows there, then coming back home, and suddenly some random workspaces end up on your home monitor. In order to reduce this surprise factor, whenever a new window appears on a workspace, that workspace resets its *original monitor* to its current monitor. This way, the workspaces you actively worked on remain where they were.
|
||||
- For example, niri preserves the view position whenever a window appears, or whenever a window goes full-screen, to restore it afterward. This way, dealing with temporary things like dialogs opening and closing, or toggling full-screen, becomes less annoying, since it doesn't mess up the view position. This is also invisible state, as you cannot tell by looking at the screen where closing a window will restore the view position. If taken to the extreme (previous view position saved forever for every open window), this can be surprising, as closing long-running windows would result in the view shifting around pretty much randomly. To reduce this surprise factor, niri remembers only one last view position per workspace, and forgets this stored view position upon window focus change.
|
||||
|
Loading…
Reference in New Issue
Block a user