mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66d6355b25 | |||
| 56654034e9 | |||
| 1f07cffa9f | |||
| cb3a06cd54 | |||
| f115f2e5e7 | |||
| 2e07282977 | |||
| cba0454c94 | |||
| 5f6f131b24 | |||
| adb5b3cd2c | |||
| 0650e7b640 | |||
| dd1c3bcb9f | |||
| e5d463e15b | |||
| 26100096e8 | |||
| a85b922919 | |||
| 7d2b620ce9 | |||
| a48f2645d9 | |||
| 83e839762f | |||
| 4c1196f45b |
@@ -1,12 +1,2 @@
|
||||
# LFS configuration for images from the wiki
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# Exclude LFS-tracked files from the tarball
|
||||
/docs/wiki/img/ export-ignore
|
||||
|
||||
# exclude .gitattributes itself from the tarball
|
||||
.gitattributes export-ignore
|
||||
|
||||
# tip: can be tested using
|
||||
# git archive --format=tar.gz --output=source.tar.gz HEAD && \
|
||||
# tar tfvz source.tar.gz | grep -e '.png' -e '.gitattributes'
|
||||
|
||||
Generated
+23
-32
@@ -714,9 +714,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.6.2"
|
||||
version = "4.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb"
|
||||
checksum = "660c0520455b1013b9bcb0393d5f643d7e4454fb69c915b8d6d2aa0e9a45acc3"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
@@ -1963,9 +1963,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.185"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libdisplay-info"
|
||||
@@ -3764,7 +3764,7 @@ dependencies = [
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 1.0.1",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3785,7 +3785,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow 1.0.1",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3794,7 +3794,7 @@ version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow 1.0.1",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4556,15 +4556,6 @@ dependencies = [
|
||||
"xkbcommon-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.1"
|
||||
@@ -4765,9 +4756,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.13.2"
|
||||
version = "5.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1"
|
||||
checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
@@ -4792,7 +4783,7 @@ dependencies = [
|
||||
"uds_windows",
|
||||
"uuid",
|
||||
"windows-sys 0.61.2",
|
||||
"winnow 0.7.15",
|
||||
"winnow",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
@@ -4824,9 +4815,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.13.2"
|
||||
version = "5.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1"
|
||||
checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -4839,12 +4830,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "4.3.1"
|
||||
version = "4.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f"
|
||||
checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winnow 0.7.15",
|
||||
"winnow",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
@@ -4888,23 +4879,23 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.9.2"
|
||||
version = "5.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4"
|
||||
checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"winnow 0.7.15",
|
||||
"winnow",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "5.9.2"
|
||||
version = "5.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c"
|
||||
checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -4915,13 +4906,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "3.3.0"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9"
|
||||
checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.117",
|
||||
"winnow 0.7.15",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -48,7 +48,10 @@ When a monitor disconnects, its workspaces will move to another monitor, but upo
|
||||
|
||||
https://github.com/niri-wm/niri/assets/1794388/bce834b0-f205-434e-a027-b373495f9729
|
||||
|
||||
Also check out this video from Brodie Robertson that showcases a lot of the niri functionality: [Niri Is My New Favorite Wayland Compositor](https://youtu.be/DeYx2exm04M)
|
||||
Also check out these videos that showcase a lot of the niri functionality:
|
||||
|
||||
- [Niri Is My New Favorite Wayland Compositor](https://www.youtube.com/watch?v=DeYx2exm04M) by Brodie Robertson
|
||||
- [How Is niri This Good? Live Demo + Config](https://www.youtube.com/watch?v=7XmD5UyyhZQ) by Nick Janetakis
|
||||
|
||||
## Status
|
||||
|
||||
@@ -110,7 +113,7 @@ Here are some other projects which implement a similar workflow:
|
||||
- [PaperWM]: scrollable tiling on top of GNOME Shell.
|
||||
- [karousel]: scrollable tiling on top of KDE.
|
||||
- [scroll](https://github.com/dawsers/scroll) and [papersway]: scrollable tiling on top of sway/i3.
|
||||
- Hyprland has a built-in [scrolling layout](https://wiki.hypr.land/Configuring/Scrolling-Layout/).
|
||||
- Hyprland has a built-in [scrolling layout](https://wiki.hypr.land/Configuring/Layouts/Scrolling-Layout/).
|
||||
- [Paneru] and [PaperWM.spoon]: scrollable tiling on top of macOS.
|
||||
|
||||
## Contact
|
||||
|
||||
@@ -88,6 +88,7 @@ nav:
|
||||
- Window Effects: Window-Effects.md
|
||||
- Packaging niri: Packaging-niri.md
|
||||
- Integrating niri: Integrating-niri.md
|
||||
- Security Model: Security-Model.md
|
||||
- Accessibility: Accessibility.md
|
||||
- Name and Logo: Name-and-Logo.md
|
||||
- FAQ: FAQ.md
|
||||
|
||||
@@ -90,6 +90,7 @@ input {
|
||||
// off
|
||||
map-to-output "eDP-1"
|
||||
// map-to-focused-output
|
||||
// map-to-focused-window
|
||||
// left-handed
|
||||
// calibration-matrix 1.0 0.0 0.0 0.0 1.0 0.0
|
||||
}
|
||||
@@ -282,10 +283,16 @@ Valid output names are the same as the ones used for output configuration.
|
||||
|
||||
<sup>Since: 0.1.7</sup> When a tablet is not mapped to any output, it will map to the union of all connected outputs, without aspect ratio correction.
|
||||
|
||||
Setting specific to `tablet`:
|
||||
Settings specific to `tablet`:
|
||||
|
||||
- `map-to-focused-output`: <sup>Since: 26.04</sup> will map the tablet to the focused output, takes precedence over `map-to-output`.
|
||||
|
||||
- `map-to-focused-window`: <sup>Since: next release</sup> will map the tablet to the focused window's geometry, takes precedence over `map-to-focused-output` and `map-to-output`.
|
||||
Falls back to those when no window is focused (for example, in the overview).
|
||||
|
||||
When the tablet is also mapped to a specific output via `map-to-output`, the `map-to-focused-window` flag will map the tablet to the active window on that output.
|
||||
If the tablet isn't mapped to any specific output, it will map the tablet to the current focused window regardless of where it is.
|
||||
|
||||
### General Settings
|
||||
|
||||
These settings are not specific to a particular input device.
|
||||
|
||||
@@ -128,3 +128,19 @@ copr-cli build niri niri.spec
|
||||
## Announce the release
|
||||
|
||||
Chat rooms, social media, etc.
|
||||
|
||||
## Update wayland.app protocol data
|
||||
|
||||
- Install [wlprobe](https://github.com/PolyMeilex/wlprobe).
|
||||
- Clone https://github.com/vially/wayland-explorer.
|
||||
- Generate data:
|
||||
|
||||
```
|
||||
wlprobe > ./src/data/compositors/niri.json
|
||||
```
|
||||
|
||||
- Manually add `"version": "26.04"`, then clean up the diff from unrelated changes, for example:
|
||||
- The number of `wl_output`s will change depending on how many monitors you have connected.
|
||||
- The number of `wp_drm_lease_device_v1` will change depending on your number of GPUs.
|
||||
- `org_kde_kwin_server_decoration_manager` and `zxdg_decoration_manager_v1` will only appear with `prefer-no-csd`.
|
||||
- Create a pull request.
|
||||
|
||||
@@ -63,3 +63,7 @@ Alternatively, some desktop environments and shells work with niri, and can give
|
||||
- Many [XFCE](https://www.xfce.org/) components work on Wayland, including niri. See [their wiki](https://wiki.xfce.org/releng/wayland_roadmap#component_specific_status) for details.
|
||||
- There are complete desktop shells based on Quickshell that support niri, for example [DankMaterialShell](https://github.com/AvengeMedia/DankMaterialShell) and [Noctalia](https://github.com/noctalia-dev/noctalia-shell).
|
||||
- You can run a [COSMIC](https://system76.com/cosmic/) session with niri using [cosmic-ext-extra-sessions](https://github.com/Drakulix/cosmic-ext-extra-sessions).
|
||||
|
||||
### Security model
|
||||
|
||||
See the [Security Model](./Security-Model.md) page for an overview of niri's security model.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
Niri assumes that programs running unsandboxed on the host are **trusted**.
|
||||
|
||||
This is a reasonable assumption because programs running on the host have a wide variety of ways to get all access they need, even without niri.
|
||||
For instance:
|
||||
|
||||
- They can set `$LD_PRELOAD` in `.bashrc` or similar files to load an arbitrary library into all processes.
|
||||
- They can replace binaries in `$PATH` with malicious code.
|
||||
- They can interpose any socket in `$XDG_RUNTIME_DIR`, like Wayland, and do keylogging or record window contents.
|
||||
- They can scan the filesystem for secrets: SSH keys, password stores, etc.
|
||||
- They can connect to an unlocked keyring and steal credentials.
|
||||
- And so on and so forth.
|
||||
|
||||
## Unsandboxed clients
|
||||
|
||||
Anything with access to niri's Wayland socket can, among other things:
|
||||
|
||||
- Record the user's screen via [wlr-screencopy](https://wayland.app/protocols/wlr-screencopy-unstable-v1).
|
||||
- Emulate input via [wlr-virtual-pointer](https://wayland.app/protocols/wlr-virtual-pointer-unstable-v1) and [virtual-keyboard](https://wayland.app/protocols/virtual-keyboard-unstable-v1).
|
||||
- Get the user's clipboard contents via [wlr-data-control](https://wayland.app/protocols/ext-data-control-v1).
|
||||
- Create arbitrary fullscreen surfaces through [wlr-layer-shell](https://wayland.app/protocols/wlr-layer-shell-unstable-v1) that can steal the user's input, pretend to be a password entry, or lock the user out of their session.
|
||||
- Kill a running lockscreen, create a new lock surface, and tell niri to unlock a locked session.
|
||||
|
||||
Anything with access to niri's [IPC](./IPC.md) socket can, among other things:
|
||||
|
||||
- Spawn a Wayland client which can do everything in the list above.
|
||||
|
||||
Anything with access to niri's D-Bus interfaces can, among other things:
|
||||
|
||||
- Record the user's screen via the screencast interface.
|
||||
- Fully listen to and emulate input from the user's keyboard via the accessibility interface.
|
||||
|
||||
Also, while niri doesn't directly integrate Xwayland, it's worth reminding that anything with access to the X11 `$DISPLAY` (which comes both as a socket file on disk **and** as an abstract socket in the network namespace) can intercept and emulate all input and record the contents of any X11 windows on the same `$DISPLAY` (but not Wayland windows).
|
||||
|
||||
## Running untrusted clients
|
||||
|
||||
Considering all of the above, for running untrusted clients, you need a proper sandbox that:
|
||||
|
||||
- Removes niri's IPC socket.
|
||||
- Prevents D-Bus access to host services.
|
||||
- Uses a filtered Wayland socket.
|
||||
|
||||
For creating a filtered Wayland socket, you can use the [security-context](https://wayland.app/protocols/security-context-v1) protocol which niri implements.
|
||||
All unsafe protocols are made inaccessible through this filtered Wayland socket.
|
||||
|
||||
One sandbox that satisfies all of these criteria is the [Flatpak](https://flatpak.org/) sandbox.
|
||||
|
||||
Importantly, filtering just the Wayland socket (and leaving, for example, unrestricted D-Bus access) is **not enough** to prevent untrusted clients from doing bad things.
|
||||
|
||||
## Lock screen
|
||||
|
||||
When the session is locked via [ext-session-lock](https://wayland.app/protocols/ext-session-lock-v1), most actions (keybindings) are automatically disabled.
|
||||
Only a very small set of safe actions is allowed.
|
||||
In particular, spawning will not work, with the exception of binds explicitly configured with `allow-when-locked=true`.
|
||||
|
||||
Importantly, the **quit** action is allowed—you can always quit niri, even when on a lock screen.
|
||||
Therefore, you must ensure that quitting niri does not drop you into an unprotected TTY commandline.
|
||||
Usually, a display manager, like GDM, will do this for you: when niri exits (via the quit bind or if it crashes), it'll put you back into a safe password prompt.
|
||||
|
||||
Other than quitting, the only way to exit a lock screen is for the lock screen client to tell niri to unlock the session.
|
||||
If the lock screen client crashes, the session remains locked with a solid red background.
|
||||
In this case, another lock screen client can take over (so you can start a fresh lock screen if it crashes, and still unlock your session).
|
||||
@@ -17,6 +17,7 @@
|
||||
* [Window Effects](./Window-Effects.md)
|
||||
* [Packaging niri](./Packaging-niri.md)
|
||||
* [Integrating niri](./Integrating-niri.md)
|
||||
* [Security Model](./Security-Model.md)
|
||||
* [Accessibility](./Accessibility.md)
|
||||
* [Name and Logo](./Name-and-Logo.md)
|
||||
* [FAQ](./FAQ.md)
|
||||
|
||||
@@ -366,6 +366,8 @@ pub struct Tablet {
|
||||
#[knuffel(child)]
|
||||
pub map_to_focused_output: bool,
|
||||
#[knuffel(child)]
|
||||
pub map_to_focused_window: bool,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -720,6 +720,7 @@ mod tests {
|
||||
tablet {
|
||||
map-to-output "eDP-1"
|
||||
map-to-focused-output
|
||||
map-to-focused-window
|
||||
calibration-matrix 1.0 2.0 3.0 \
|
||||
4.0 5.0 6.0
|
||||
}
|
||||
@@ -1113,6 +1114,7 @@ mod tests {
|
||||
"eDP-1",
|
||||
),
|
||||
map_to_focused_output: true,
|
||||
map_to_focused_window: true,
|
||||
left_handed: false,
|
||||
},
|
||||
touch: Touch {
|
||||
|
||||
@@ -15,7 +15,7 @@ if [ -n "$SHELL" ] &&
|
||||
! (echo "$SHELL" | grep -q "false") &&
|
||||
! (echo "$SHELL" | grep -q "nologin"); then
|
||||
if [ "$1" != '-l' ]; then
|
||||
exec bash -c "exec -l '$SHELL' -c '$0 -l $*'"
|
||||
exec bash -c "exec -l '$SHELL' -c 'exec $0 -l $*'"
|
||||
else
|
||||
shift
|
||||
fi
|
||||
|
||||
+247
-198
@@ -1,5 +1,4 @@
|
||||
use std::any::Any;
|
||||
use std::cmp::min;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
@@ -297,41 +296,68 @@ impl State {
|
||||
let device_output = event.device().output(self);
|
||||
let device_output = device_output.filter(|output| self.niri.output_exists(output));
|
||||
let device_output = device_output.as_ref();
|
||||
let (target_geo, keep_ratio, px, transform) =
|
||||
if let Some(output) = device_output.or_else(|| 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()?;
|
||||
let mapped_output = device_output.or_else(|| self.niri.output_for_tablet());
|
||||
|
||||
// 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();
|
||||
// If the tablet is configured to map to the focused window, use that window's geometry on
|
||||
// the mapped output (or on the focused output if no specific output is mapped).
|
||||
let map_to_focused_window = self.niri.config.borrow().input.tablet.map_to_focused_window;
|
||||
// But only if the keyboard focus is on the layout, so that it doesn't trigger on the lock
|
||||
// screen and such.
|
||||
let window_target = if map_to_focused_window && self.niri.keyboard_focus.is_layout() {
|
||||
let output = mapped_output.or_else(|| self.niri.layout.active_output());
|
||||
output.and_then(|output| {
|
||||
let monitor = self.niri.layout.monitor_for_output(output)?;
|
||||
let mut rect = monitor.active_window_visual_rectangle()?;
|
||||
let output_geo = self.niri.global_space.output_geometry(output)?;
|
||||
rect.loc += output_geo.loc.to_f64();
|
||||
Some((rect, output))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Do not keep ratio for the unified mode as this is what OpenTabletDriver expects.
|
||||
(geo, false, 1. / scale, Transform::Normal)
|
||||
};
|
||||
let (target_geo, keep_ratio, px, transform) = if let Some((rect, output)) = window_target {
|
||||
(
|
||||
rect,
|
||||
true,
|
||||
1. / output.current_scale().fractional_scale(),
|
||||
output.current_transform(),
|
||||
)
|
||||
} else if let Some(output) = mapped_output {
|
||||
let geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||
(
|
||||
geo.to_f64(),
|
||||
true,
|
||||
1. / output.current_scale().fractional_scale(),
|
||||
output.current_transform(),
|
||||
)
|
||||
} else {
|
||||
let geo = self.global_bounding_rectangle()?.to_f64();
|
||||
|
||||
// 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, 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())
|
||||
transform.transform_point_in(event.position_transformed(size.to_i32_round()), &size)
|
||||
};
|
||||
|
||||
if keep_ratio {
|
||||
pos.x /= target_geo.size.w as f64;
|
||||
pos.y /= target_geo.size.h as f64;
|
||||
pos.x /= target_geo.size.w;
|
||||
pos.y /= target_geo.size.h;
|
||||
|
||||
let device = event.device();
|
||||
if let Some(device) = (&device as &dyn Any).downcast_ref::<input::Device>() {
|
||||
if let Some(data) = self.niri.tablets.get(device) {
|
||||
// This code does the same thing as mutter with "keep aspect ratio" enabled.
|
||||
let size = transform.invert().transform_size(target_geo.size);
|
||||
let output_aspect_ratio = size.w as f64 / size.h as f64;
|
||||
let output_aspect_ratio = size.w / size.h;
|
||||
let ratio = data.aspect_ratio / output_aspect_ratio;
|
||||
|
||||
if ratio > 1. {
|
||||
@@ -342,13 +368,13 @@ impl State {
|
||||
}
|
||||
};
|
||||
|
||||
pos.x *= target_geo.size.w as f64;
|
||||
pos.y *= target_geo.size.h as f64;
|
||||
pos.x *= target_geo.size.w;
|
||||
pos.y *= target_geo.size.h;
|
||||
}
|
||||
|
||||
pos.x = pos.x.clamp(0.0, target_geo.size.w as f64 - px);
|
||||
pos.y = pos.y.clamp(0.0, target_geo.size.h as f64 - px);
|
||||
Some(pos + target_geo.loc.to_f64())
|
||||
pos.x = pos.x.clamp(0.0, target_geo.size.w - px);
|
||||
pos.y = pos.y.clamp(0.0, target_geo.size.h - px);
|
||||
Some(pos + target_geo.loc)
|
||||
}
|
||||
|
||||
fn is_inhibiting_shortcuts(&self) -> bool {
|
||||
@@ -2520,16 +2546,10 @@ impl State {
|
||||
|
||||
if let Some(output) = self.niri.screenshot_ui.selection_output() {
|
||||
let geom = self.niri.global_space.output_geometry(output).unwrap();
|
||||
let mut point = (new_pos - geom.loc.to_f64())
|
||||
let point = (new_pos - geom.loc.to_f64())
|
||||
.to_physical(output.current_scale().fractional_scale())
|
||||
.to_i32_round::<i32>();
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
point.x = point.x.clamp(0, size.w - 1);
|
||||
point.y = point.y.clamp(0, size.h - 1);
|
||||
|
||||
self.niri.screenshot_ui.pointer_motion(point, None);
|
||||
}
|
||||
|
||||
@@ -2657,16 +2677,10 @@ impl State {
|
||||
|
||||
if let Some(output) = self.niri.screenshot_ui.selection_output() {
|
||||
let geom = self.niri.global_space.output_geometry(output).unwrap();
|
||||
let mut point = (pos - geom.loc.to_f64())
|
||||
let point = (pos - geom.loc.to_f64())
|
||||
.to_physical(output.current_scale().fractional_scale())
|
||||
.to_i32_round::<i32>();
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
point.x = point.x.clamp(0, size.w - 1);
|
||||
point.y = point.y.clamp(0, size.h - 1);
|
||||
|
||||
self.niri.screenshot_ui.pointer_motion(point, None);
|
||||
}
|
||||
|
||||
@@ -2751,10 +2765,11 @@ impl State {
|
||||
return;
|
||||
}
|
||||
|
||||
if ButtonState::Pressed == button_state {
|
||||
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||
let modifiers = modifiers_from_state(mods);
|
||||
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||
let modifiers = modifiers_from_state(mods);
|
||||
let mod_down = modifiers.contains(mod_key.to_modifiers());
|
||||
|
||||
if ButtonState::Pressed == button_state {
|
||||
let mut is_mru_open = false;
|
||||
if let Some(mru_output) = self.niri.window_mru_ui.output() {
|
||||
is_mru_open = true;
|
||||
@@ -2791,6 +2806,9 @@ impl State {
|
||||
let bindings =
|
||||
make_binds_iter(&config, &mut self.niri.window_mru_ui, modifiers);
|
||||
find_configured_bind(bindings, mod_key, trigger, mods)
|
||||
})
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open() || allowed_during_screenshot(&bind.action)
|
||||
}) {
|
||||
self.niri.suppressed_buttons.insert(button_code);
|
||||
self.handle_bind(bind.clone());
|
||||
@@ -2832,44 +2850,41 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
if button == Some(MouseButton::Middle) && !pointer.is_grabbed() {
|
||||
let mod_down = modifiers_from_state(mods).contains(mod_key.to_modifiers());
|
||||
if mod_down {
|
||||
let output_ws = if is_overview_open {
|
||||
self.niri.workspace_under_cursor(true)
|
||||
} else {
|
||||
// We don't want to accidentally "catch" the wrong workspace during
|
||||
// animations.
|
||||
self.niri.output_under_cursor().and_then(|output| {
|
||||
let mon = self.niri.layout.monitor_for_output(&output)?;
|
||||
Some((output, mon.active_workspace_ref()))
|
||||
})
|
||||
if button == Some(MouseButton::Middle) && !pointer.is_grabbed() && mod_down {
|
||||
let output_ws = if is_overview_open {
|
||||
self.niri.workspace_under_cursor(true)
|
||||
} else {
|
||||
// We don't want to accidentally "catch" the wrong workspace during
|
||||
// animations.
|
||||
self.niri.output_under_cursor().and_then(|output| {
|
||||
let mon = self.niri.layout.monitor_for_output(&output)?;
|
||||
Some((output, mon.active_workspace_ref()))
|
||||
})
|
||||
};
|
||||
|
||||
if let Some((output, ws)) = output_ws {
|
||||
let ws_id = ws.id();
|
||||
|
||||
self.niri.layout.focus_output(&output);
|
||||
|
||||
let location = pointer.current_location();
|
||||
let start_data = PointerGrabStartData {
|
||||
focus: None,
|
||||
button: button_code,
|
||||
location,
|
||||
};
|
||||
let grab = SpatialMovementGrab::new(start_data, output, ws_id, false);
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::AllScroll));
|
||||
|
||||
if let Some((output, ws)) = output_ws {
|
||||
let ws_id = ws.id();
|
||||
// FIXME: granular.
|
||||
self.niri.queue_redraw_all();
|
||||
|
||||
self.niri.layout.focus_output(&output);
|
||||
|
||||
let location = pointer.current_location();
|
||||
let start_data = PointerGrabStartData {
|
||||
focus: None,
|
||||
button: button_code,
|
||||
location,
|
||||
};
|
||||
let grab = SpatialMovementGrab::new(start_data, output, ws_id, false);
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(CursorIcon::AllScroll));
|
||||
|
||||
// FIXME: granular.
|
||||
self.niri.queue_redraw_all();
|
||||
|
||||
// Don't activate the window under the cursor to avoid unnecessary
|
||||
// scrolling when e.g. Mod+MMB clicking on a partially off-screen window.
|
||||
return;
|
||||
}
|
||||
// Don't activate the window under the cursor to avoid unnecessary
|
||||
// scrolling when e.g. Mod+MMB clicking on a partially off-screen window.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2878,7 +2893,6 @@ impl State {
|
||||
|
||||
// Check if we need to start an interactive move.
|
||||
if button == Some(MouseButton::Left) && !pointer.is_grabbed() {
|
||||
let mod_down = modifiers_from_state(mods).contains(mod_key.to_modifiers());
|
||||
if is_overview_open || mod_down {
|
||||
let location = pointer.current_location();
|
||||
|
||||
@@ -2912,72 +2926,69 @@ impl State {
|
||||
}
|
||||
}
|
||||
// Check if we need to start an interactive resize.
|
||||
else if button == Some(MouseButton::Right) && !pointer.is_grabbed() {
|
||||
let mod_down = modifiers_from_state(mods).contains(mod_key.to_modifiers());
|
||||
if mod_down {
|
||||
let location = pointer.current_location();
|
||||
let (output, pos_within_output) = self.niri.output_under(location).unwrap();
|
||||
let edges = self
|
||||
else if button == Some(MouseButton::Right) && !pointer.is_grabbed() && mod_down {
|
||||
let location = pointer.current_location();
|
||||
let (output, pos_within_output) = self.niri.output_under(location).unwrap();
|
||||
let edges = self
|
||||
.niri
|
||||
.layout
|
||||
.resize_edges_under(output, pos_within_output)
|
||||
.unwrap_or(ResizeEdge::empty());
|
||||
|
||||
if !edges.is_empty() {
|
||||
// See if we got a double resize-click gesture.
|
||||
// FIXME: deduplicate with resize_request in xdg-shell somehow.
|
||||
let time = get_monotonic_time();
|
||||
let last_cell = mapped.last_interactive_resize_start();
|
||||
let mut last = last_cell.get();
|
||||
last_cell.set(Some((time, edges)));
|
||||
|
||||
// Floating windows don't have either of the double-resize-click
|
||||
// gestures, so just allow it to resize.
|
||||
if mapped.is_floating() {
|
||||
last = None;
|
||||
last_cell.set(None);
|
||||
}
|
||||
|
||||
if let Some((last_time, last_edges)) = last {
|
||||
if time.saturating_sub(last_time) <= DOUBLE_CLICK_TIME {
|
||||
// Allow quick resize after a triple click.
|
||||
last_cell.set(None);
|
||||
|
||||
let intersection = edges.intersection(last_edges);
|
||||
if intersection.intersects(ResizeEdge::LEFT_RIGHT) {
|
||||
// FIXME: don't activate once we can pass specific windows
|
||||
// to actions.
|
||||
self.niri.layout.activate_window(&window);
|
||||
self.niri.layout.toggle_full_width();
|
||||
}
|
||||
if intersection.intersects(ResizeEdge::TOP_BOTTOM) {
|
||||
self.niri.layout.activate_window(&window);
|
||||
self.niri.layout.reset_window_height(Some(&window));
|
||||
}
|
||||
// FIXME: granular.
|
||||
self.niri.queue_redraw_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.niri.layout.activate_window(&window);
|
||||
|
||||
if self
|
||||
.niri
|
||||
.layout
|
||||
.resize_edges_under(output, pos_within_output)
|
||||
.unwrap_or(ResizeEdge::empty());
|
||||
|
||||
if !edges.is_empty() {
|
||||
// See if we got a double resize-click gesture.
|
||||
// FIXME: deduplicate with resize_request in xdg-shell somehow.
|
||||
let time = get_monotonic_time();
|
||||
let last_cell = mapped.last_interactive_resize_start();
|
||||
let mut last = last_cell.get();
|
||||
last_cell.set(Some((time, edges)));
|
||||
|
||||
// Floating windows don't have either of the double-resize-click
|
||||
// gestures, so just allow it to resize.
|
||||
if mapped.is_floating() {
|
||||
last = None;
|
||||
last_cell.set(None);
|
||||
}
|
||||
|
||||
if let Some((last_time, last_edges)) = last {
|
||||
if time.saturating_sub(last_time) <= DOUBLE_CLICK_TIME {
|
||||
// Allow quick resize after a triple click.
|
||||
last_cell.set(None);
|
||||
|
||||
let intersection = edges.intersection(last_edges);
|
||||
if intersection.intersects(ResizeEdge::LEFT_RIGHT) {
|
||||
// FIXME: don't activate once we can pass specific windows
|
||||
// to actions.
|
||||
self.niri.layout.activate_window(&window);
|
||||
self.niri.layout.toggle_full_width();
|
||||
}
|
||||
if intersection.intersects(ResizeEdge::TOP_BOTTOM) {
|
||||
self.niri.layout.activate_window(&window);
|
||||
self.niri.layout.reset_window_height(Some(&window));
|
||||
}
|
||||
// FIXME: granular.
|
||||
self.niri.queue_redraw_all();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.niri.layout.activate_window(&window);
|
||||
|
||||
if self
|
||||
.niri
|
||||
.layout
|
||||
.interactive_resize_begin(window.clone(), edges)
|
||||
{
|
||||
let start_data = PointerGrabStartData {
|
||||
focus: None,
|
||||
button: button_code,
|
||||
location,
|
||||
};
|
||||
let grab = ResizeGrab::new(start_data, window.clone());
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri.cursor_manager.set_cursor_image(
|
||||
CursorImageStatus::Named(edges.cursor_icon()),
|
||||
);
|
||||
}
|
||||
.interactive_resize_begin(window.clone(), edges)
|
||||
{
|
||||
let start_data = PointerGrabStartData {
|
||||
focus: None,
|
||||
button: button_code,
|
||||
location,
|
||||
};
|
||||
let grab = ResizeGrab::new(start_data, window.clone());
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri
|
||||
.cursor_manager
|
||||
.set_cursor_image(CursorImageStatus::Named(edges.cursor_icon()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3017,20 +3028,25 @@ impl State {
|
||||
if button == Some(MouseButton::Left) && self.niri.screenshot_ui.is_open() {
|
||||
if button_state == ButtonState::Pressed {
|
||||
let pos = pointer.current_location();
|
||||
if let Some((output, _)) = self.niri.output_under(pos) {
|
||||
let output = output.clone();
|
||||
|
||||
// If we'll be moving the existing selection, use the selection output.
|
||||
let output = if mod_down {
|
||||
self.niri.screenshot_ui.selection_output()
|
||||
} else {
|
||||
self.niri.output_under(pos).map(|(out, _)| out)
|
||||
};
|
||||
|
||||
if let Some(output) = output.cloned() {
|
||||
let geom = self.niri.global_space.output_geometry(&output).unwrap();
|
||||
let mut point = (pos - geom.loc.to_f64())
|
||||
let point = (pos - geom.loc.to_f64())
|
||||
.to_physical(output.current_scale().fractional_scale())
|
||||
.to_i32_round();
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
point.x = min(size.w - 1, point.x);
|
||||
point.y = min(size.h - 1, point.y);
|
||||
|
||||
if self.niri.screenshot_ui.pointer_down(output, point, None) {
|
||||
if self
|
||||
.niri
|
||||
.screenshot_ui
|
||||
.pointer_down(output, point, None, mod_down)
|
||||
{
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
@@ -3146,13 +3162,21 @@ impl State {
|
||||
mod_key,
|
||||
Trigger::WheelScrollLeft,
|
||||
mods,
|
||||
);
|
||||
)
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
let bind_right = find_configured_bind(
|
||||
bindings,
|
||||
mod_key,
|
||||
Trigger::WheelScrollRight,
|
||||
mods,
|
||||
);
|
||||
)
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
(bind_left, bind_right)
|
||||
};
|
||||
|
||||
@@ -3233,9 +3257,17 @@ impl State {
|
||||
mod_key,
|
||||
Trigger::WheelScrollUp,
|
||||
mods,
|
||||
);
|
||||
)
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
let bind_down =
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollDown, mods);
|
||||
find_configured_bind(bindings, mod_key, Trigger::WheelScrollDown, mods)
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
(bind_up, bind_down)
|
||||
};
|
||||
|
||||
@@ -3378,9 +3410,17 @@ impl State {
|
||||
mod_key,
|
||||
Trigger::TouchpadScrollLeft,
|
||||
mods,
|
||||
);
|
||||
)
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
let bind_right =
|
||||
find_configured_bind(bindings, mod_key, Trigger::TouchpadScrollRight, mods);
|
||||
find_configured_bind(bindings, mod_key, Trigger::TouchpadScrollRight, mods)
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
drop(config);
|
||||
|
||||
if let Some(right) = bind_right {
|
||||
@@ -3408,9 +3448,17 @@ impl State {
|
||||
mod_key,
|
||||
Trigger::TouchpadScrollUp,
|
||||
mods,
|
||||
);
|
||||
)
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
let bind_down =
|
||||
find_configured_bind(bindings, mod_key, Trigger::TouchpadScrollDown, mods);
|
||||
find_configured_bind(bindings, mod_key, Trigger::TouchpadScrollDown, mods)
|
||||
.filter(|bind| {
|
||||
!self.niri.screenshot_ui.is_open()
|
||||
|| allowed_during_screenshot(&bind.action)
|
||||
});
|
||||
drop(config);
|
||||
|
||||
if let Some(down) = bind_down {
|
||||
@@ -3514,16 +3562,10 @@ impl State {
|
||||
|
||||
if let Some(output) = self.niri.screenshot_ui.selection_output() {
|
||||
let geom = self.niri.global_space.output_geometry(output).unwrap();
|
||||
let mut point = (pos - geom.loc.to_f64())
|
||||
let point = (pos - geom.loc.to_f64())
|
||||
.to_physical(output.current_scale().fractional_scale())
|
||||
.to_i32_round::<i32>();
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
point.x = point.x.clamp(0, size.w - 1);
|
||||
point.y = point.y.clamp(0, size.h - 1);
|
||||
|
||||
self.niri.screenshot_ui.pointer_motion(point, None);
|
||||
}
|
||||
|
||||
@@ -3596,19 +3638,29 @@ impl State {
|
||||
let under = self.niri.contents_under(pos);
|
||||
|
||||
if self.niri.screenshot_ui.is_open() {
|
||||
if let Some(output) = under.output.clone() {
|
||||
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);
|
||||
let mod_down = modifiers.contains(mod_key.to_modifiers());
|
||||
|
||||
// If we'll be moving the existing selection, use the selection output.
|
||||
let output = if mod_down {
|
||||
self.niri.screenshot_ui.selection_output()
|
||||
} else {
|
||||
under.output.as_ref()
|
||||
};
|
||||
|
||||
if let Some(output) = output.cloned() {
|
||||
let geom = self.niri.global_space.output_geometry(&output).unwrap();
|
||||
let mut point = (pos - geom.loc.to_f64())
|
||||
let point = (pos - geom.loc.to_f64())
|
||||
.to_physical(output.current_scale().fractional_scale())
|
||||
.to_i32_round();
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
point.x = min(size.w - 1, point.x);
|
||||
point.y = min(size.h - 1, point.y);
|
||||
|
||||
if self.niri.screenshot_ui.pointer_down(output, point, None) {
|
||||
if self
|
||||
.niri
|
||||
.screenshot_ui
|
||||
.pointer_down(output, point, None, mod_down)
|
||||
{
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
@@ -4070,24 +4122,28 @@ impl State {
|
||||
let under = self.niri.contents_under(pos);
|
||||
|
||||
let mod_key = self.backend.mod_key(&self.niri.config.borrow());
|
||||
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||
let mods = modifiers_from_state(mods);
|
||||
let mod_down = mods.contains(mod_key.to_modifiers());
|
||||
|
||||
if self.niri.screenshot_ui.is_open() {
|
||||
if let Some(output) = under.output.clone() {
|
||||
// If we'll be moving the existing selection, use the selection output.
|
||||
let output = if mod_down {
|
||||
self.niri.screenshot_ui.selection_output()
|
||||
} else {
|
||||
under.output.as_ref()
|
||||
};
|
||||
|
||||
if let Some(output) = output.cloned() {
|
||||
let geom = self.niri.global_space.output_geometry(&output).unwrap();
|
||||
let mut point = (pos - geom.loc.to_f64())
|
||||
let point = (pos - geom.loc.to_f64())
|
||||
.to_physical(output.current_scale().fractional_scale())
|
||||
.to_i32_round();
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
point.x = min(size.w - 1, point.x);
|
||||
point.y = min(size.h - 1, point.y);
|
||||
|
||||
if self
|
||||
.niri
|
||||
.screenshot_ui
|
||||
.pointer_down(output, point, Some(slot))
|
||||
.pointer_down(output, point, Some(slot), mod_down)
|
||||
{
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
@@ -4106,10 +4162,6 @@ impl State {
|
||||
}
|
||||
}
|
||||
} else if !handle.is_grabbed() {
|
||||
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
|
||||
let mods = modifiers_from_state(mods);
|
||||
let mod_down = mods.contains(mod_key.to_modifiers());
|
||||
|
||||
if self.niri.layout.is_overview_open()
|
||||
&& !mod_down
|
||||
&& under.layer.is_none()
|
||||
@@ -4222,16 +4274,10 @@ impl State {
|
||||
|
||||
if let Some(output) = self.niri.screenshot_ui.selection_output().cloned() {
|
||||
let geom = self.niri.global_space.output_geometry(&output).unwrap();
|
||||
let mut point = (pos - geom.loc.to_f64())
|
||||
let point = (pos - geom.loc.to_f64())
|
||||
.to_physical(output.current_scale().fractional_scale())
|
||||
.to_i32_round::<i32>();
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
point.x = point.x.clamp(0, size.w - 1);
|
||||
point.y = point.y.clamp(0, size.h - 1);
|
||||
|
||||
self.niri.screenshot_ui.pointer_motion(point, Some(slot));
|
||||
self.niri.queue_redraw(&output);
|
||||
}
|
||||
@@ -4607,6 +4653,9 @@ fn allowed_during_screenshot(action: &Action) -> bool {
|
||||
| Action::Suspend
|
||||
| Action::PowerOffMonitors
|
||||
| Action::PowerOnMonitors
|
||||
// Intended for binds such as volume up/down, lock the screen, etc.
|
||||
| Action::Spawn(_)
|
||||
| Action::SpawnSh(_)
|
||||
// The screenshot UI can handle these.
|
||||
| Action::MoveColumnLeft
|
||||
| Action::MoveColumnLeftOrToMonitorLeft
|
||||
|
||||
@@ -345,16 +345,17 @@ impl<W: LayoutElement> FloatingSpace<W> {
|
||||
compute_toplevel_bounds(border_config, self.working_area.size)
|
||||
}
|
||||
|
||||
/// Returns the geometry of the active tile relative to and clamped to the working area.
|
||||
/// Returns the geometry of the active window relative to and clamped to the working area.
|
||||
///
|
||||
/// During animations, assumes the final tile position.
|
||||
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
pub fn active_window_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
let (tile, offset) = self.tiles_with_offsets().next()?;
|
||||
|
||||
let tile_size = tile.tile_size();
|
||||
let tile_rect = Rectangle::new(offset, tile_size);
|
||||
let window_pos = offset + tile.window_loc();
|
||||
let window_size = tile.window_size();
|
||||
let window_rect = Rectangle::new(window_pos, window_size);
|
||||
|
||||
self.working_area.intersection(tile_rect)
|
||||
self.working_area.intersection(window_rect)
|
||||
}
|
||||
|
||||
pub fn popup_target_rect(&self, id: &W::Id) -> Option<Rectangle<f64, Logical>> {
|
||||
|
||||
@@ -1342,15 +1342,15 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.clean_up_workspaces();
|
||||
}
|
||||
|
||||
/// Returns the geometry of the active tile relative to and clamped to the output.
|
||||
/// Returns the geometry of the active window relative to and clamped to the output.
|
||||
///
|
||||
/// During animations, assumes the final view position.
|
||||
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
pub fn active_window_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
if self.overview_open {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.active_workspace_ref().active_tile_visual_rectangle()
|
||||
self.active_workspace_ref().active_window_visual_rectangle()
|
||||
}
|
||||
|
||||
fn workspace_size(&self, zoom: f64) -> Size<f64, Logical> {
|
||||
|
||||
@@ -2540,10 +2540,10 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
Some(hint_area)
|
||||
}
|
||||
|
||||
/// Returns the geometry of the active tile relative to and clamped to the view.
|
||||
/// Returns the geometry of the active window relative to and clamped to the view.
|
||||
///
|
||||
/// During animations, assumes the final view position.
|
||||
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
pub fn active_window_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
let col = self.columns.get(self.active_column_idx)?;
|
||||
|
||||
let final_view_offset = self.view_offset.target();
|
||||
@@ -2551,12 +2551,12 @@ impl<W: LayoutElement> ScrollingSpace<W> {
|
||||
|
||||
let (tile, tile_off) = col.tiles().nth(col.active_tile_idx).unwrap();
|
||||
|
||||
let tile_pos = view_off + tile_off;
|
||||
let tile_size = tile.tile_size();
|
||||
let tile_rect = Rectangle::new(tile_pos, tile_size);
|
||||
let window_pos = view_off + tile_off + tile.window_loc();
|
||||
let window_size = tile.window_size();
|
||||
let window_rect = Rectangle::new(window_pos, window_size);
|
||||
|
||||
let view = Rectangle::from_size(self.view_size);
|
||||
view.intersection(tile_rect)
|
||||
view.intersection(window_rect)
|
||||
}
|
||||
|
||||
pub fn popup_target_rect(&self, id: &W::Id) -> Option<Rectangle<f64, Logical>> {
|
||||
|
||||
@@ -1609,11 +1609,11 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
floating.chain(scrolling)
|
||||
}
|
||||
|
||||
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
pub fn active_window_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
if self.floating_is_active.get() {
|
||||
self.floating.active_tile_visual_rectangle()
|
||||
self.floating.active_window_visual_rectangle()
|
||||
} else {
|
||||
self.scrolling.active_tile_visual_rectangle()
|
||||
self.scrolling.active_window_visual_rectangle()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -934,7 +934,7 @@ impl State {
|
||||
let monitor = self.niri.layout.monitor_for_output(output).unwrap();
|
||||
|
||||
let mut rv = false;
|
||||
let rect = monitor.active_tile_visual_rectangle();
|
||||
let rect = monitor.active_window_visual_rectangle();
|
||||
|
||||
if let Some(rect) = rect {
|
||||
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||
|
||||
@@ -209,7 +209,7 @@ impl Blur {
|
||||
|
||||
let mut fbos = [0; 2];
|
||||
gl.GenFramebuffers(fbos.len() as _, fbos.as_mut_ptr());
|
||||
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, fbos[0]);
|
||||
gl.BindFramebuffer(ffi::FRAMEBUFFER, fbos[0]);
|
||||
|
||||
let program = &self.program.0.down;
|
||||
gl.UseProgram(program.program);
|
||||
@@ -244,7 +244,7 @@ impl Blur {
|
||||
|
||||
trace!("drawing down {src} to {dst}");
|
||||
gl.FramebufferTexture2D(
|
||||
ffi::DRAW_FRAMEBUFFER,
|
||||
ffi::FRAMEBUFFER,
|
||||
ffi::COLOR_ATTACHMENT0,
|
||||
ffi::TEXTURE_2D,
|
||||
dst,
|
||||
@@ -307,7 +307,7 @@ impl Blur {
|
||||
|
||||
trace!("drawing up {src} to {dst}");
|
||||
gl.FramebufferTexture2D(
|
||||
ffi::DRAW_FRAMEBUFFER,
|
||||
ffi::FRAMEBUFFER,
|
||||
ffi::COLOR_ATTACHMENT0,
|
||||
ffi::TEXTURE_2D,
|
||||
dst,
|
||||
@@ -333,7 +333,7 @@ impl Blur {
|
||||
|
||||
gl.DisableVertexAttribArray(program.attrib_vert as u32);
|
||||
|
||||
gl.BindFramebuffer(ffi::DRAW_FRAMEBUFFER, 0);
|
||||
gl.BindFramebuffer(ffi::FRAMEBUFFER, 0);
|
||||
gl.DeleteFramebuffers(fbos.len() as _, fbos.as_ptr());
|
||||
})?;
|
||||
|
||||
|
||||
+34
-9
@@ -799,6 +799,8 @@ impl ScreenshotUi {
|
||||
}
|
||||
|
||||
/// The pointer has moved to `point` relative to the current selection output.
|
||||
///
|
||||
/// The point may be outside output bounds.
|
||||
pub fn pointer_motion(&mut self, point: Point<i32, Physical>, slot: Option<TouchSlot>) {
|
||||
let Self::Open {
|
||||
selection,
|
||||
@@ -838,7 +840,8 @@ impl ScreenshotUi {
|
||||
selection.1 += delta;
|
||||
selection.2 += delta;
|
||||
} else {
|
||||
selection.2 = point;
|
||||
let size = output_data[&selection.0].size;
|
||||
selection.2 = Point::new(point.x.clamp(0, size.w - 1), point.y.clamp(0, size.h - 1));
|
||||
}
|
||||
|
||||
self.update_buffers();
|
||||
@@ -849,6 +852,7 @@ impl ScreenshotUi {
|
||||
output: Output,
|
||||
point: Point<i32, Physical>,
|
||||
slot: Option<TouchSlot>,
|
||||
move_existing: bool,
|
||||
) -> bool {
|
||||
let Self::Open {
|
||||
selection,
|
||||
@@ -883,6 +887,23 @@ impl ScreenshotUi {
|
||||
return false;
|
||||
}
|
||||
|
||||
if move_existing {
|
||||
if output != selection.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
*button = Button::Down {
|
||||
touch_slot: slot,
|
||||
on_capture_button: false,
|
||||
last_pos: (output, point),
|
||||
move_state: Some(MoveState {
|
||||
pointer_offset: point - selection.1,
|
||||
touch_slot: slot,
|
||||
}),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(output_data) = output_data.get(&output) else {
|
||||
return false;
|
||||
};
|
||||
@@ -909,6 +930,11 @@ impl ScreenshotUi {
|
||||
last_pos: (output.clone(), point),
|
||||
move_state: None,
|
||||
};
|
||||
|
||||
let point = Point::new(
|
||||
point.x.clamp(0, output_data.size.w - 1),
|
||||
point.y.clamp(0, output_data.size.h - 1),
|
||||
);
|
||||
*selection = (output, point, point);
|
||||
|
||||
self.update_buffers();
|
||||
@@ -939,15 +965,14 @@ impl ScreenshotUi {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Check if this is a move touch and if so, stop the move.
|
||||
if let Some(state) = move_state {
|
||||
if state.touch_slot.is_some_and(|m_slot| Some(m_slot) == slot) {
|
||||
*move_state = None;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
if touch_slot != slot {
|
||||
// This is not our main touch, but it might be the move touch. If so, stop the move.
|
||||
if let Some(state) = move_state {
|
||||
if state.touch_slot.is_some_and(|m_slot| Some(m_slot) == slot) {
|
||||
*move_state = None;
|
||||
}
|
||||
};
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user