mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7c201abba | |||
| 4fd04951e6 | |||
| 747c186293 | |||
| bdf9894020 | |||
| d180e60e05 | |||
| 65addefd09 | |||
| 697fcbac12 | |||
| a8e281e95f | |||
| 4d60eae82e | |||
| 2b5215c244 | |||
| a43f30b7f5 | |||
| 88f7b08e56 | |||
| dc92d80b9f | |||
| 0757ad08e7 | |||
| 5577021475 | |||
| 40aff3a094 | |||
| 6c5f10035a | |||
| 96d2baa2b5 | |||
| 5d2754f831 | |||
| ebaf1b0620 | |||
| 589e5a600c | |||
| 198b5a502d | |||
| cb0ebd35ce | |||
| 29cf80a3dd | |||
| db89d4d3dd | |||
| 226273f660 | |||
| c0ded35783 | |||
| 39632e9c1e | |||
| 66202992c9 | |||
| eb59b10050 | |||
| 986f2c14ab | |||
| 793e1bdbc5 | |||
| d62721d5f8 | |||
| d54619e1d1 | |||
| 8425493ef5 | |||
| 6121e64338 | |||
| 33b5beaeee | |||
| 1dae45c58d | |||
| 997119c443 | |||
| 032589446a | |||
| 9ae98e09cb | |||
| 2ffa1ae705 | |||
| fee72b87cf | |||
| 6c47bd6e80 | |||
| 02c2972e74 | |||
| 4b830ee7ff | |||
| 8e41568ffd | |||
| dbe810d3d8 | |||
| a1563b9132 | |||
| 98aea9579f | |||
| 7019172b67 | |||
| be62bd123a | |||
| 3c63be6261 | |||
| e3406ac255 | |||
| 22a948cc75 | |||
| bc3d6cac80 | |||
| a55e385b12 | |||
| af6d84a7f8 | |||
| f203c8729a | |||
| dbf0dddfcc | |||
| c6c17cccac | |||
| b5ad0e12fd | |||
| c8e46b9d17 | |||
| f2ce84b243 | |||
| ae7fb4c4f4 | |||
| 4746a0da7d | |||
| 2ac8d84034 | |||
| eb0f7aa429 | |||
| bcca03cce7 | |||
| efb39e466b | |||
| 14d637f4ef | |||
| c9d90afe59 | |||
| d088ce248f | |||
| f4cdde1f4f | |||
| 56e02a398d | |||
| 2552b129c4 |
Generated
+512
-276
File diff suppressed because it is too large
Load Diff
+15
-12
@@ -2,7 +2,7 @@
|
||||
members = ["niri-visual-tests"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
description = "A scrollable-tiling Wayland compositor"
|
||||
authors = ["Ivan Molodetskikh <yalterz@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
@@ -10,10 +10,10 @@ edition = "2021"
|
||||
repository = "https://github.com/YaLTeR/niri"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.83"
|
||||
anyhow = "1.0.86"
|
||||
bitflags = "2.5.0"
|
||||
clap = { version = "~4.4.18", features = ["derive"] }
|
||||
serde = { version = "1.0.202", features = ["derive"] }
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
tracing = { version = "0.1.40", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
@@ -45,24 +45,26 @@ anyhow.workspace = true
|
||||
arrayvec = "0.7.4"
|
||||
async-channel = "2.3.1"
|
||||
async-io = { version = "1.13.0", optional = true }
|
||||
atomic = "0.6.0"
|
||||
bitflags.workspace = true
|
||||
bytemuck = { version = "1.16.0", features = ["derive"] }
|
||||
calloop = { version = "0.13.0", features = ["executor", "futures-io"] }
|
||||
bytemuck = { version = "1.16.1", features = ["derive"] }
|
||||
calloop = { version = "0.14.0", features = ["executor", "futures-io"] }
|
||||
clap = { workspace = true, features = ["string"] }
|
||||
directories = "5.0.1"
|
||||
drm-ffi = "0.8.0"
|
||||
fastrand = "2.1.0"
|
||||
futures-util = { version = "0.3.30", default-features = false, features = ["std", "io"] }
|
||||
git-version = "0.3.9"
|
||||
glam = "0.27.0"
|
||||
glam = "0.28.0"
|
||||
input = { version = "0.9.0", features = ["libinput_1_21"] }
|
||||
keyframe = { version = "1.1.1", default-features = false }
|
||||
libc = "0.2.154"
|
||||
libc = "0.2.155"
|
||||
log = { version = "0.4.21", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
niri-config = { version = "0.1.6", path = "niri-config" }
|
||||
niri-ipc = { version = "0.1.6", path = "niri-ipc", features = ["clap"] }
|
||||
niri-config = { version = "0.1.7", path = "niri-config" }
|
||||
niri-ipc = { version = "0.1.7", path = "niri-ipc", features = ["clap"] }
|
||||
notify-rust = { version = "~4.10.0", optional = true }
|
||||
pangocairo = "0.19.2"
|
||||
ordered-float = "4.2.0"
|
||||
pangocairo = "0.19.8"
|
||||
pipewire = { version = "0.8.0", optional = true }
|
||||
png = "0.17.13"
|
||||
portable-atomic = { version = "1.6.0", default-features = false, features = ["float"] }
|
||||
@@ -74,7 +76,7 @@ smithay-drm-extras.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
tracing.workspace = true
|
||||
tracy-client.workspace = true
|
||||
url = { version = "2.5.0", optional = true }
|
||||
url = { version = "2.5.2", optional = true }
|
||||
xcursor = "0.3.5"
|
||||
zbus = { version = "~3.15.2", optional = true }
|
||||
|
||||
@@ -97,6 +99,7 @@ features = [
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
approx = "0.5.1"
|
||||
k9 = "0.12.0"
|
||||
proptest = "1.4.0"
|
||||
proptest-derive = "0.4.0"
|
||||
@@ -125,7 +128,7 @@ lto = "thin"
|
||||
debug = false
|
||||
|
||||
[package.metadata.generate-rpm]
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
assets = [
|
||||
{ source = "target/release/niri", dest = "/usr/bin/", mode = "755" },
|
||||
{ source = "resources/niri-session", dest = "/usr/bin/", mode = "755" },
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/YaLTeR/niri/wiki/Getting-Started">Getting Started</a> | <a href="https://github.com/YaLTeR/niri/wiki/Configuration:-Overview">Configuration</a>
|
||||
<a href="https://github.com/YaLTeR/niri/wiki/Getting-Started">Getting Started</a> | <a href="https://github.com/YaLTeR/niri/wiki/Configuration:-Overview">Configuration</a> | <a href="https://github.com/YaLTeR/niri/discussions/325">Setup Showcase</a>
|
||||
</p>
|
||||
|
||||

|
||||
@@ -31,7 +31,7 @@ When a monitor disconnects, its workspaces will move to another monitor, but upo
|
||||
- Scrollable tiling
|
||||
- Dynamic workspaces like in GNOME
|
||||
- Built-in screenshot UI
|
||||
- Monitor screencasting through xdg-desktop-portal-gnome
|
||||
- Monitor and window screencasting through xdg-desktop-portal-gnome
|
||||
- You can [block out](https://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules#block-out-from) sensitive windows from screencasts
|
||||
- [Touchpad](https://github.com/YaLTeR/niri/assets/1794388/946a910e-9bec-4cd1-a923-4a9421707515) and [mouse](https://github.com/YaLTeR/niri/assets/1794388/8464e65d-4bf2-44fa-8c8e-5883355bd000) gestures
|
||||
- Configurable layout: gaps, borders, struts, window sizes
|
||||
|
||||
@@ -12,8 +12,11 @@ bitflags.workspace = true
|
||||
csscolorparser = "0.6.2"
|
||||
knuffel = "3.2.0"
|
||||
miette = "5.10.0"
|
||||
niri-ipc = { version = "0.1.6", path = "../niri-ipc" }
|
||||
regex = "1.10.4"
|
||||
niri-ipc = { version = "0.1.7", path = "../niri-ipc" }
|
||||
regex = "1.10.5"
|
||||
smithay = { workspace = true, features = ["backend_libinput"] }
|
||||
tracing.workspace = true
|
||||
tracy-client.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
+216
-39
@@ -56,7 +56,6 @@ pub struct Config {
|
||||
pub workspaces: Vec<Workspace>,
|
||||
}
|
||||
|
||||
// FIXME: Add other devices.
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct Input {
|
||||
#[knuffel(child, default)]
|
||||
@@ -81,19 +80,30 @@ pub struct Input {
|
||||
pub workspace_auto_back_and_forth: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
|
||||
#[derive(knuffel::Decode, Debug, PartialEq, Eq)]
|
||||
pub struct Keyboard {
|
||||
#[knuffel(child, default)]
|
||||
pub xkb: Xkb,
|
||||
// The defaults were chosen to match wlroots and sway.
|
||||
#[knuffel(child, unwrap(argument), default = 600)]
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().repeat_delay)]
|
||||
pub repeat_delay: u16,
|
||||
#[knuffel(child, unwrap(argument), default = 25)]
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().repeat_rate)]
|
||||
pub repeat_rate: u8,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub track_layout: TrackLayout,
|
||||
}
|
||||
|
||||
impl Default for Keyboard {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
xkb: Default::default(),
|
||||
repeat_delay: 600,
|
||||
repeat_rate: 25,
|
||||
track_layout: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq, Clone)]
|
||||
pub struct Xkb {
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
@@ -141,9 +151,10 @@ pub enum TrackLayout {
|
||||
Window,
|
||||
}
|
||||
|
||||
// FIXME: Add the rest of the settings.
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct Touchpad {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub tap: bool,
|
||||
#[knuffel(child)]
|
||||
@@ -159,27 +170,43 @@ pub struct Touchpad {
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub scroll_method: Option<ScrollMethod>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub tap_button_map: Option<TapButtonMap>,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
#[knuffel(child)]
|
||||
pub disabled_on_external_mouse: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct Mouse {
|
||||
#[knuffel(child)]
|
||||
pub natural_scroll: bool,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub accel_speed: f64,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct Trackpoint {
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub natural_scroll: bool,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub accel_speed: f64,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub scroll_method: Option<ScrollMethod>,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct Trackpoint {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child)]
|
||||
pub natural_scroll: bool,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub accel_speed: f64,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub accel_profile: Option<AccelProfile>,
|
||||
#[knuffel(child, unwrap(argument, str))]
|
||||
pub scroll_method: Option<ScrollMethod>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -212,6 +239,25 @@ impl From<AccelProfile> for input::AccelProfile {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ScrollMethod {
|
||||
NoScroll,
|
||||
TwoFinger,
|
||||
Edge,
|
||||
OnButtonDown,
|
||||
}
|
||||
|
||||
impl From<ScrollMethod> for input::ScrollMethod {
|
||||
fn from(value: ScrollMethod) -> Self {
|
||||
match value {
|
||||
ScrollMethod::NoScroll => Self::NoScroll,
|
||||
ScrollMethod::TwoFinger => Self::TwoFinger,
|
||||
ScrollMethod::Edge => Self::Edge,
|
||||
ScrollMethod::OnButtonDown => Self::OnButtonDown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TapButtonMap {
|
||||
LeftRightMiddle,
|
||||
@@ -229,8 +275,12 @@ impl From<TapButtonMap> for input::TapButtonMap {
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct Tablet {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub map_to_output: Option<String>,
|
||||
#[knuffel(child)]
|
||||
pub left_handed: bool,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
@@ -246,7 +296,7 @@ pub struct Output {
|
||||
#[knuffel(argument)]
|
||||
pub name: String,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub scale: Option<f64>,
|
||||
pub scale: Option<FloatOrInt<0, 10>>,
|
||||
#[knuffel(child, unwrap(argument, str), default = Transform::Normal)]
|
||||
pub transform: Transform,
|
||||
#[knuffel(child)]
|
||||
@@ -279,6 +329,10 @@ pub struct Position {
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
// MIN and MAX generics are only used during parsing to check the value.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Layout {
|
||||
#[knuffel(child, default)]
|
||||
@@ -292,7 +346,7 @@ pub struct Layout {
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub center_focused_column: CenterFocusedColumn,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().gaps)]
|
||||
pub gaps: u16,
|
||||
pub gaps: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, default)]
|
||||
pub struts: Struts,
|
||||
}
|
||||
@@ -305,7 +359,7 @@ impl Default for Layout {
|
||||
preset_column_widths: Default::default(),
|
||||
default_column_width: Default::default(),
|
||||
center_focused_column: Default::default(),
|
||||
gaps: 16,
|
||||
gaps: FloatOrInt(16.),
|
||||
struts: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -322,7 +376,7 @@ pub struct FocusRing {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().width)]
|
||||
pub width: u16,
|
||||
pub width: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, default = Self::default().active_color)]
|
||||
pub active_color: Color,
|
||||
#[knuffel(child, default = Self::default().inactive_color)]
|
||||
@@ -337,7 +391,7 @@ impl Default for FocusRing {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: false,
|
||||
width: 4,
|
||||
width: FloatOrInt(4.),
|
||||
active_color: Color::new(127, 200, 255, 255),
|
||||
inactive_color: Color::new(80, 80, 80, 255),
|
||||
active_gradient: None,
|
||||
@@ -370,7 +424,7 @@ pub struct Border {
|
||||
#[knuffel(child)]
|
||||
pub off: bool,
|
||||
#[knuffel(child, unwrap(argument), default = Self::default().width)]
|
||||
pub width: u16,
|
||||
pub width: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, default = Self::default().active_color)]
|
||||
pub active_color: Color,
|
||||
#[knuffel(child, default = Self::default().inactive_color)]
|
||||
@@ -385,7 +439,7 @@ impl Default for Border {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
off: true,
|
||||
width: 4,
|
||||
width: FloatOrInt(4.),
|
||||
active_color: Color::new(255, 200, 127, 255),
|
||||
inactive_color: Color::new(80, 80, 80, 255),
|
||||
active_gradient: None,
|
||||
@@ -467,16 +521,16 @@ pub enum PresetWidth {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DefaultColumnWidth(pub Option<PresetWidth>);
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Struts {
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub left: u16,
|
||||
pub left: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub right: u16,
|
||||
pub right: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub top: u16,
|
||||
pub top: FloatOrInt<0, 65535>,
|
||||
#[knuffel(child, unwrap(argument), default)]
|
||||
pub bottom: u16,
|
||||
pub bottom: FloatOrInt<0, 65535>,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -811,7 +865,7 @@ pub struct BorderRule {
|
||||
#[knuffel(child)]
|
||||
pub on: bool,
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
pub width: Option<u16>,
|
||||
pub width: Option<FloatOrInt<0, 65535>>,
|
||||
#[knuffel(child)]
|
||||
pub active_color: Option<Color>,
|
||||
#[knuffel(child)]
|
||||
@@ -890,8 +944,16 @@ pub enum Action {
|
||||
FocusColumnRight,
|
||||
FocusColumnFirst,
|
||||
FocusColumnLast,
|
||||
FocusColumnRightOrFirst,
|
||||
FocusColumnLeftOrLast,
|
||||
FocusColumnOrMonitorLeft,
|
||||
FocusColumnOrMonitorRight,
|
||||
FocusWindowDown,
|
||||
FocusWindowUp,
|
||||
FocusWindowDownOrColumnLeft,
|
||||
FocusWindowDownOrColumnRight,
|
||||
FocusWindowUpOrColumnLeft,
|
||||
FocusWindowUpOrColumnRight,
|
||||
FocusWindowOrWorkspaceDown,
|
||||
FocusWindowOrWorkspaceUp,
|
||||
MoveColumnLeft,
|
||||
@@ -960,8 +1022,16 @@ impl From<niri_ipc::Action> for Action {
|
||||
niri_ipc::Action::FocusColumnRight => Self::FocusColumnRight,
|
||||
niri_ipc::Action::FocusColumnFirst => Self::FocusColumnFirst,
|
||||
niri_ipc::Action::FocusColumnLast => Self::FocusColumnLast,
|
||||
niri_ipc::Action::FocusColumnRightOrFirst => Self::FocusColumnRightOrFirst,
|
||||
niri_ipc::Action::FocusColumnLeftOrLast => Self::FocusColumnLeftOrLast,
|
||||
niri_ipc::Action::FocusColumnOrMonitorLeft => Self::FocusColumnOrMonitorLeft,
|
||||
niri_ipc::Action::FocusColumnOrMonitorRight => Self::FocusColumnOrMonitorRight,
|
||||
niri_ipc::Action::FocusWindowDown => Self::FocusWindowDown,
|
||||
niri_ipc::Action::FocusWindowUp => Self::FocusWindowUp,
|
||||
niri_ipc::Action::FocusWindowDownOrColumnLeft => Self::FocusWindowDownOrColumnLeft,
|
||||
niri_ipc::Action::FocusWindowDownOrColumnRight => Self::FocusWindowDownOrColumnRight,
|
||||
niri_ipc::Action::FocusWindowUpOrColumnLeft => Self::FocusWindowUpOrColumnLeft,
|
||||
niri_ipc::Action::FocusWindowUpOrColumnRight => Self::FocusWindowUpOrColumnRight,
|
||||
niri_ipc::Action::FocusWindowOrWorkspaceDown => Self::FocusWindowOrWorkspaceDown,
|
||||
niri_ipc::Action::FocusWindowOrWorkspaceUp => Self::FocusWindowOrWorkspaceUp,
|
||||
niri_ipc::Action::MoveColumnLeft => Self::MoveColumnLeft,
|
||||
@@ -1080,6 +1150,72 @@ impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceRefere
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: knuffel::traits::ErrorSpan, const MIN: i32, const MAX: i32> knuffel::DecodeScalar<S>
|
||||
for FloatOrInt<MIN, MAX>
|
||||
{
|
||||
fn type_check(
|
||||
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
|
||||
ctx: &mut knuffel::decode::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(
|
||||
val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
|
||||
ctx: &mut knuffel::decode::Context<S>,
|
||||
) -> Result<Self, DecodeError<S>> {
|
||||
match &**val {
|
||||
knuffel::ast::Literal::Int(ref value) => match value.try_into() {
|
||||
Ok(v) => {
|
||||
if (MIN..=MAX).contains(&v) {
|
||||
Ok(FloatOrInt(f64::from(v)))
|
||||
} else {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
val,
|
||||
format!("value must be between {MIN} and {MAX}"),
|
||||
));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
ctx.emit_error(DecodeError::conversion(val, e));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
},
|
||||
knuffel::ast::Literal::Decimal(ref value) => match value.try_into() {
|
||||
Ok(v) => {
|
||||
if (f64::from(MIN)..=f64::from(MAX)).contains(&v) {
|
||||
Ok(FloatOrInt(v))
|
||||
} else {
|
||||
ctx.emit_error(DecodeError::conversion(
|
||||
val,
|
||||
format!("value must be between {MIN} and {MAX}"),
|
||||
));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
ctx.emit_error(DecodeError::conversion(val, e));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
ctx.emit_error(DecodeError::unsupported(
|
||||
val,
|
||||
"Unsupported value, only numbers are recognized",
|
||||
));
|
||||
Ok(FloatOrInt::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
|
||||
pub struct DebugConfig {
|
||||
#[knuffel(child, unwrap(argument))]
|
||||
@@ -2220,6 +2356,22 @@ impl FromStr for AccelProfile {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ScrollMethod {
|
||||
type Err = miette::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"no-scroll" => Ok(Self::NoScroll),
|
||||
"two-finger" => Ok(Self::TwoFinger),
|
||||
"edge" => Ok(Self::Edge),
|
||||
"on-button-down" => Ok(Self::OnButtonDown),
|
||||
_ => Err(miette!(
|
||||
r#"invalid scroll method, can be "no-scroll", "two-finger", "edge", or "on-button-down""#
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for TapButtonMap {
|
||||
type Err = miette::Error;
|
||||
|
||||
@@ -2240,6 +2392,8 @@ pub fn set_miette_hook() -> Result<(), miette::InstallError> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[track_caller]
|
||||
@@ -2274,19 +2428,24 @@ mod tests {
|
||||
click-method "clickfinger"
|
||||
accel-speed 0.2
|
||||
accel-profile "flat"
|
||||
scroll-method "two-finger"
|
||||
tap-button-map "left-middle-right"
|
||||
disabled-on-external-mouse
|
||||
}
|
||||
|
||||
mouse {
|
||||
natural-scroll
|
||||
accel-speed 0.4
|
||||
accel-profile "flat"
|
||||
scroll-method "no-scroll"
|
||||
}
|
||||
|
||||
trackpoint {
|
||||
off
|
||||
natural-scroll
|
||||
accel-speed 0.0
|
||||
accel-profile "flat"
|
||||
scroll-method "on-button-down"
|
||||
}
|
||||
|
||||
tablet {
|
||||
@@ -2305,7 +2464,7 @@ mod tests {
|
||||
}
|
||||
|
||||
output "eDP-1" {
|
||||
scale 2.0
|
||||
scale 2
|
||||
transform "flipped-90"
|
||||
position x=10 y=20
|
||||
mode "1920x1080@144"
|
||||
@@ -2396,7 +2555,7 @@ mod tests {
|
||||
|
||||
border {
|
||||
on
|
||||
width 8
|
||||
width 8.5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2435,6 +2594,7 @@ mod tests {
|
||||
track_layout: TrackLayout::Window,
|
||||
},
|
||||
touchpad: Touchpad {
|
||||
off: false,
|
||||
tap: true,
|
||||
dwt: true,
|
||||
dwtp: true,
|
||||
@@ -2442,20 +2602,30 @@ mod tests {
|
||||
natural_scroll: false,
|
||||
accel_speed: 0.2,
|
||||
accel_profile: Some(AccelProfile::Flat),
|
||||
scroll_method: Some(ScrollMethod::TwoFinger),
|
||||
tap_button_map: Some(TapButtonMap::LeftMiddleRight),
|
||||
left_handed: false,
|
||||
disabled_on_external_mouse: true,
|
||||
},
|
||||
mouse: Mouse {
|
||||
off: false,
|
||||
natural_scroll: true,
|
||||
accel_speed: 0.4,
|
||||
accel_profile: Some(AccelProfile::Flat),
|
||||
scroll_method: Some(ScrollMethod::NoScroll),
|
||||
left_handed: false,
|
||||
},
|
||||
trackpoint: Trackpoint {
|
||||
off: true,
|
||||
natural_scroll: true,
|
||||
accel_speed: 0.0,
|
||||
accel_profile: Some(AccelProfile::Flat),
|
||||
scroll_method: Some(ScrollMethod::OnButtonDown),
|
||||
},
|
||||
tablet: Tablet {
|
||||
off: false,
|
||||
map_to_output: Some("eDP-1".to_owned()),
|
||||
left_handed: false,
|
||||
},
|
||||
touch: Touch {
|
||||
map_to_output: Some("eDP-1".to_owned()),
|
||||
@@ -2468,7 +2638,7 @@ mod tests {
|
||||
outputs: vec![Output {
|
||||
off: false,
|
||||
name: "eDP-1".to_owned(),
|
||||
scale: Some(2.),
|
||||
scale: Some(FloatOrInt(2.)),
|
||||
transform: Transform::Flipped90,
|
||||
position: Some(Position { x: 10, y: 20 }),
|
||||
mode: Some(ConfiguredMode {
|
||||
@@ -2481,7 +2651,7 @@ mod tests {
|
||||
layout: Layout {
|
||||
focus_ring: FocusRing {
|
||||
off: false,
|
||||
width: 5,
|
||||
width: FloatOrInt(5.),
|
||||
active_color: Color {
|
||||
r: 0,
|
||||
g: 100,
|
||||
@@ -2504,7 +2674,7 @@ mod tests {
|
||||
},
|
||||
border: Border {
|
||||
off: false,
|
||||
width: 3,
|
||||
width: FloatOrInt(3.),
|
||||
active_color: Color {
|
||||
r: 255,
|
||||
g: 200,
|
||||
@@ -2529,12 +2699,12 @@ mod tests {
|
||||
default_column_width: Some(DefaultColumnWidth(Some(PresetWidth::Proportion(
|
||||
0.25,
|
||||
)))),
|
||||
gaps: 8,
|
||||
gaps: FloatOrInt(8.),
|
||||
struts: Struts {
|
||||
left: 1,
|
||||
right: 2,
|
||||
top: 3,
|
||||
bottom: 0,
|
||||
left: FloatOrInt(1.),
|
||||
right: FloatOrInt(2.),
|
||||
top: FloatOrInt(3.),
|
||||
bottom: FloatOrInt(0.),
|
||||
},
|
||||
center_focused_column: CenterFocusedColumn::OnOverflow,
|
||||
},
|
||||
@@ -2618,12 +2788,12 @@ mod tests {
|
||||
open_fullscreen: Some(false),
|
||||
focus_ring: BorderRule {
|
||||
off: true,
|
||||
width: Some(3),
|
||||
width: Some(FloatOrInt(3.)),
|
||||
..Default::default()
|
||||
},
|
||||
border: BorderRule {
|
||||
on: true,
|
||||
width: Some(8),
|
||||
width: Some(FloatOrInt(8.5)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -2814,4 +2984,11 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -33,6 +33,8 @@ pub enum Request {
|
||||
},
|
||||
/// Request information about workspaces.
|
||||
Workspaces,
|
||||
/// Request information about the focused output.
|
||||
FocusedOutput,
|
||||
/// Respond with an error (for testing error handling).
|
||||
ReturnError,
|
||||
}
|
||||
@@ -64,6 +66,8 @@ pub enum Response {
|
||||
OutputConfigChanged(OutputConfigChanged),
|
||||
/// Information about workspaces.
|
||||
Workspaces(Vec<Workspace>),
|
||||
/// Information about the focused output.
|
||||
FocusedOutput(Option<Output>),
|
||||
}
|
||||
|
||||
/// Actions that niri can perform.
|
||||
@@ -112,10 +116,26 @@ pub enum Action {
|
||||
FocusColumnFirst,
|
||||
/// Focus the last column.
|
||||
FocusColumnLast,
|
||||
/// Focus the next column to the right, looping if at end.
|
||||
FocusColumnRightOrFirst,
|
||||
/// Focus the next column to the left, looping if at start.
|
||||
FocusColumnLeftOrLast,
|
||||
/// Focus the column or the monitor to the left.
|
||||
FocusColumnOrMonitorLeft,
|
||||
/// Focus the column or the monitor to the right.
|
||||
FocusColumnOrMonitorRight,
|
||||
/// Focus the window below.
|
||||
FocusWindowDown,
|
||||
/// Focus the window above.
|
||||
FocusWindowUp,
|
||||
/// Focus the window below or the column to the left.
|
||||
FocusWindowDownOrColumnLeft,
|
||||
/// Focus the window below or the column to the right.
|
||||
FocusWindowDownOrColumnRight,
|
||||
/// Focus the window above or the column to the left.
|
||||
FocusWindowUpOrColumnLeft,
|
||||
/// Focus the window above or the column to the right.
|
||||
FocusWindowUpOrColumnRight,
|
||||
/// Focus the window or the workspace above.
|
||||
FocusWindowOrWorkspaceDown,
|
||||
/// Focus the window or the workspace above.
|
||||
@@ -295,7 +315,7 @@ pub enum OutputAction {
|
||||
Mode {
|
||||
/// Mode to set, or "auto" for automatic selection.
|
||||
///
|
||||
/// Run `niri msg outputs` to see the avaliable modes.
|
||||
/// Run `niri msg outputs` to see the available modes.
|
||||
#[cfg_attr(feature = "clap", arg())]
|
||||
mode: ModeToSet,
|
||||
},
|
||||
|
||||
@@ -11,8 +11,8 @@ repository.workspace = true
|
||||
adw = { version = "0.6.0", package = "libadwaita", features = ["v1_4"] }
|
||||
anyhow.workspace = true
|
||||
gtk = { version = "0.8.2", package = "gtk4", features = ["v4_12"] }
|
||||
niri = { version = "0.1.6", path = ".." }
|
||||
niri-config = { version = "0.1.6", path = "../niri-config" }
|
||||
niri = { version = "0.1.7", path = ".." }
|
||||
niri-config = { version = "0.1.7", path = "../niri-config" }
|
||||
smithay.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
@@ -59,17 +59,18 @@ impl TestCase for GradientAngle {
|
||||
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
|
||||
let (a, b) = (size.w / 4, size.h / 4);
|
||||
let size = (size.w - a * 2, size.h - b * 2);
|
||||
let area = Rectangle::from_loc_and_size((a, b), size);
|
||||
let area = Rectangle::from_loc_and_size((a, b), size).to_f64();
|
||||
|
||||
[BorderRenderElement::new(
|
||||
area.size,
|
||||
Rectangle::from_loc_and_size((0, 0), area.size),
|
||||
Rectangle::from_loc_and_size((0., 0.), area.size),
|
||||
[1., 0., 0., 1.],
|
||||
[0., 1., 0., 1.],
|
||||
self.angle - FRAC_PI_2,
|
||||
Rectangle::from_loc_and_size((0, 0), area.size),
|
||||
Rectangle::from_loc_and_size((0., 0.), area.size),
|
||||
0.,
|
||||
CornerRadius::default(),
|
||||
1.,
|
||||
)
|
||||
.with_location(area.loc)]
|
||||
.into_iter()
|
||||
|
||||
@@ -5,10 +5,10 @@ use std::time::Duration;
|
||||
use niri::animation::ANIMATION_SLOWDOWN;
|
||||
use niri::layout::focus_ring::FocusRing;
|
||||
use niri::render_helpers::border::BorderRenderElement;
|
||||
use niri_config::{Color, CornerRadius};
|
||||
use niri_config::{Color, CornerRadius, FloatOrInt};
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size};
|
||||
use smithay::utils::{Logical, Physical, Point, Rectangle, Size};
|
||||
|
||||
use super::TestCase;
|
||||
|
||||
@@ -22,7 +22,7 @@ impl GradientArea {
|
||||
pub fn new(_size: Size<i32, Logical>) -> Self {
|
||||
let border = FocusRing::new(niri_config::FocusRing {
|
||||
off: false,
|
||||
width: 1,
|
||||
width: FloatOrInt(1.),
|
||||
active_color: Color::new(255, 255, 255, 128),
|
||||
inactive_color: Color::default(),
|
||||
active_gradient: None,
|
||||
@@ -75,13 +75,14 @@ impl TestCase for GradientArea {
|
||||
|
||||
let (a, b) = (size.w / 4, size.h / 4);
|
||||
let rect_size = (size.w - a * 2, size.h - b * 2);
|
||||
let area = Rectangle::from_loc_and_size((a, b), rect_size);
|
||||
let area = Rectangle::from_loc_and_size((a, b), rect_size).to_f64();
|
||||
|
||||
let g_size = Size::from((
|
||||
(size.w as f32 / 8. + size.w as f32 / 8. * 7. * f).round() as i32,
|
||||
(size.h as f32 / 8. + size.h as f32 / 8. * 7. * f).round() as i32,
|
||||
));
|
||||
let g_loc = ((size.w - g_size.w) / 2, (size.h - g_size.h) / 2);
|
||||
let g_loc = Point::from(((size.w - g_size.w) / 2, (size.h - g_size.h) / 2)).to_f64();
|
||||
let g_size = g_size.to_f64();
|
||||
let mut g_area = Rectangle::from_loc_and_size(g_loc, g_size);
|
||||
g_area.loc -= area.loc;
|
||||
|
||||
@@ -91,10 +92,11 @@ impl TestCase for GradientArea {
|
||||
true,
|
||||
Rectangle::default(),
|
||||
CornerRadius::default(),
|
||||
1.,
|
||||
);
|
||||
rv.extend(
|
||||
self.border
|
||||
.render(renderer, Point::from(g_loc), Scale::from(1.))
|
||||
.render(renderer, g_loc)
|
||||
.map(|elem| Box::new(elem) as _),
|
||||
);
|
||||
|
||||
@@ -105,9 +107,10 @@ impl TestCase for GradientArea {
|
||||
[1., 0., 0., 1.],
|
||||
[0., 1., 0., 1.],
|
||||
FRAC_PI_4,
|
||||
Rectangle::from_loc_and_size((0, 0), rect_size),
|
||||
Rectangle::from_loc_and_size((0, 0), rect_size).to_f64(),
|
||||
0.,
|
||||
CornerRadius::default(),
|
||||
1.,
|
||||
)
|
||||
.with_location(area.loc)]
|
||||
.into_iter()
|
||||
|
||||
@@ -5,7 +5,7 @@ use niri::layout::workspace::ColumnWidth;
|
||||
use niri::layout::{LayoutElement as _, Options};
|
||||
use niri::render_helpers::RenderTarget;
|
||||
use niri::utils::get_monotonic_time;
|
||||
use niri_config::Color;
|
||||
use niri_config::{Color, FloatOrInt};
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::desktop::layer_map_for_output;
|
||||
@@ -49,7 +49,7 @@ impl Layout {
|
||||
},
|
||||
border: niri_config::Border {
|
||||
off: false,
|
||||
width: 4,
|
||||
width: FloatOrInt(4.),
|
||||
active_color: Color::new(255, 163, 72, 255),
|
||||
inactive_color: Color::new(50, 50, 50, 255),
|
||||
active_gradient: None,
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::time::Duration;
|
||||
|
||||
use niri::layout::Options;
|
||||
use niri::render_helpers::RenderTarget;
|
||||
use niri_config::Color;
|
||||
use niri_config::{Color, FloatOrInt};
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size};
|
||||
@@ -20,7 +20,7 @@ impl Tile {
|
||||
pub fn freeform(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::freeform(0);
|
||||
let mut rv = Self::with_window(window);
|
||||
rv.tile.request_tile_size(size, false);
|
||||
rv.tile.request_tile_size(size.to_f64(), false);
|
||||
rv.window.communicate();
|
||||
rv
|
||||
}
|
||||
@@ -28,7 +28,7 @@ impl Tile {
|
||||
pub fn fixed_size(size: Size<i32, Logical>) -> Self {
|
||||
let window = TestWindow::fixed_size(0);
|
||||
let mut rv = Self::with_window(window);
|
||||
rv.tile.request_tile_size(size, false);
|
||||
rv.tile.request_tile_size(size.to_f64(), false);
|
||||
rv.window.communicate();
|
||||
rv
|
||||
}
|
||||
@@ -37,7 +37,7 @@ impl Tile {
|
||||
let window = TestWindow::fixed_size(0);
|
||||
window.set_csd_shadow_width(64);
|
||||
let mut rv = Self::with_window(window);
|
||||
rv.tile.request_tile_size(size, false);
|
||||
rv.tile.request_tile_size(size.to_f64(), false);
|
||||
rv.window.communicate();
|
||||
rv
|
||||
}
|
||||
@@ -71,13 +71,13 @@ impl Tile {
|
||||
},
|
||||
border: niri_config::Border {
|
||||
off: false,
|
||||
width: 32,
|
||||
width: FloatOrInt(32.),
|
||||
active_color: Color::new(255, 163, 72, 255),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let tile = niri::layout::tile::Tile::new(window.clone(), Rc::new(options));
|
||||
let tile = niri::layout::tile::Tile::new(window.clone(), 1., Rc::new(options));
|
||||
Self { window, tile }
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ impl Tile {
|
||||
impl TestCase for Tile {
|
||||
fn resize(&mut self, width: i32, height: i32) {
|
||||
self.tile
|
||||
.request_tile_size(Size::from((width, height)), false);
|
||||
.request_tile_size(Size::from((width, height)).to_f64(), false);
|
||||
self.window.communicate();
|
||||
}
|
||||
|
||||
@@ -102,12 +102,13 @@ impl TestCase for Tile {
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
|
||||
let tile_size = self.tile.tile_size().to_physical(1);
|
||||
let location = Point::from(((size.w - tile_size.w) / 2, (size.h - tile_size.h) / 2));
|
||||
let size = size.to_f64();
|
||||
let tile_size = self.tile.tile_size().to_physical(1.);
|
||||
let location = Point::from((size.w - tile_size.w, size.h - tile_size.h)).downscale(2.);
|
||||
|
||||
self.tile.update(
|
||||
true,
|
||||
Rectangle::from_loc_and_size((-location.x, -location.y), size.to_logical(1)),
|
||||
Rectangle::from_loc_and_size((-location.x, -location.y), size.to_logical(1.)),
|
||||
);
|
||||
self.tile
|
||||
.render(
|
||||
|
||||
@@ -47,7 +47,9 @@ impl TestCase for Window {
|
||||
size: Size<i32, Physical>,
|
||||
) -> Vec<Box<dyn RenderElement<GlesRenderer>>> {
|
||||
let win_size = self.window.size().to_physical(1);
|
||||
let location = Point::from(((size.w - win_size.w) / 2, (size.h - win_size.h) / 2));
|
||||
let location = Point::from((size.w - win_size.w, size.h - win_size.h))
|
||||
.to_f64()
|
||||
.downscale(2.);
|
||||
|
||||
self.window
|
||||
.render(
|
||||
|
||||
@@ -157,7 +157,7 @@ mod imp {
|
||||
if let Some(mut damage) = rect.intersection(dst) {
|
||||
damage.loc -= dst.loc;
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[damage])
|
||||
.draw(&mut frame, src, dst, &[damage], &[])
|
||||
.context("error drawing element")?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ use niri::layout::{
|
||||
InteractiveResizeData, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot,
|
||||
};
|
||||
use niri::render_helpers::renderer::NiriRenderer;
|
||||
use niri::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use niri::render_helpers::{RenderTarget, SplitElements};
|
||||
use niri::window::ResolvedWindowRules;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::{Id, Kind};
|
||||
use smithay::output::Output;
|
||||
use smithay::output::{self, Output};
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point, Scale, Serial, Size, Transform};
|
||||
|
||||
@@ -37,7 +37,7 @@ impl TestWindow {
|
||||
let size = Size::from((100, 200));
|
||||
let min_size = Size::from((0, 0));
|
||||
let max_size = Size::from((0, 0));
|
||||
let buffer = SolidColorBuffer::new(size, [0.15, 0.64, 0.41, 1.]);
|
||||
let buffer = SolidColorBuffer::new(size.to_f64(), [0.15, 0.64, 0.41, 1.]);
|
||||
|
||||
Self {
|
||||
id,
|
||||
@@ -49,7 +49,7 @@ impl TestWindow {
|
||||
buffer,
|
||||
pending_fullscreen: false,
|
||||
csd_shadow_width: 0,
|
||||
csd_shadow_buffer: SolidColorBuffer::new((0, 0), [0., 0., 0., 0.3]),
|
||||
csd_shadow_buffer: SolidColorBuffer::new((0., 0.), [0., 0., 0., 0.3]),
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -112,14 +112,14 @@ impl TestWindow {
|
||||
|
||||
if inner.size != new_size {
|
||||
inner.size = new_size;
|
||||
inner.buffer.resize(new_size);
|
||||
inner.buffer.resize(new_size.to_f64());
|
||||
rv = true;
|
||||
}
|
||||
|
||||
let mut csd_shadow_size = new_size;
|
||||
csd_shadow_size.w += inner.csd_shadow_width * 2;
|
||||
csd_shadow_size.h += inner.csd_shadow_width * 2;
|
||||
inner.csd_shadow_buffer.resize(csd_shadow_size);
|
||||
inner.csd_shadow_buffer.resize(csd_shadow_size.to_f64());
|
||||
|
||||
rv
|
||||
}
|
||||
@@ -147,8 +147,8 @@ impl LayoutElement for TestWindow {
|
||||
fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
_renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
location: Point<f64, Logical>,
|
||||
_scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
_target: RenderTarget,
|
||||
) -> SplitElements<LayoutElementRenderElement<R>> {
|
||||
@@ -158,17 +158,15 @@ impl LayoutElement for TestWindow {
|
||||
normal: vec![
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&inner.buffer,
|
||||
location.to_physical_precise_round(scale),
|
||||
scale,
|
||||
location,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&inner.csd_shadow_buffer,
|
||||
(location - Point::from((inner.csd_shadow_width, inner.csd_shadow_width)))
|
||||
.to_physical_precise_round(scale),
|
||||
scale,
|
||||
location
|
||||
- Point::from((inner.csd_shadow_width, inner.csd_shadow_width)).to_f64(),
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
@@ -199,7 +197,7 @@ impl LayoutElement for TestWindow {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_preferred_scale_transform(&self, _scale: i32, _transform: Transform) {}
|
||||
fn set_preferred_scale_transform(&self, _scale: output::Scale, _transform: Transform) {}
|
||||
|
||||
fn has_ssd(&self) -> bool {
|
||||
false
|
||||
|
||||
@@ -21,18 +21,23 @@ input {
|
||||
// Next sections include libinput settings.
|
||||
// Omitting settings disables them, or leaves them at their default values.
|
||||
touchpad {
|
||||
// off
|
||||
tap
|
||||
// dwt
|
||||
// dwtp
|
||||
natural-scroll
|
||||
// accel-speed 0.2
|
||||
// accel-profile "flat"
|
||||
// scroll-method "two-finger"
|
||||
// disabled-on-external-mouse
|
||||
}
|
||||
|
||||
mouse {
|
||||
// off
|
||||
// natural-scroll
|
||||
// accel-speed 0.2
|
||||
// accel-profile "flat"
|
||||
// scroll-method "no-scroll"
|
||||
}
|
||||
|
||||
// Uncomment this to make the mouse warp to the center of newly focused windows.
|
||||
@@ -60,8 +65,8 @@ input {
|
||||
// Run `niri msg outputs` while inside a niri instance to list all outputs and their modes.
|
||||
mode "1920x1080@120.030"
|
||||
|
||||
// Scale is a floating-point number, but at the moment only integer values work.
|
||||
scale 2.0
|
||||
// You can use integer or fractional scale, for example use 1.5 for 150% scale.
|
||||
scale 2
|
||||
|
||||
// Transform allows to rotate the output counter-clockwise, valid values are:
|
||||
// normal, 90, 180, 270, flipped, flipped-90, flipped-180 and flipped-270.
|
||||
@@ -187,6 +192,7 @@ layout {
|
||||
// Add lines like this to spawn processes at startup.
|
||||
// Note that running niri as a session supports xdg-desktop-autostart,
|
||||
// which may be more convenient to use.
|
||||
// See the binds section below for more spawn examples.
|
||||
// spawn-at-startup "alacritty" "-e" "fish"
|
||||
|
||||
// Uncomment this line to ask the clients to omit their client-side decorations if possible.
|
||||
@@ -259,7 +265,8 @@ binds {
|
||||
Mod+D { spawn "fuzzel"; }
|
||||
Super+Alt+L { spawn "swaylock"; }
|
||||
|
||||
// You can also use a shell:
|
||||
// You can also use a shell. Do this if you need pipes, multiple commands, etc.
|
||||
// Note: the entire command goes as a single argument in the end.
|
||||
// Mod+T { spawn "bash" "-c" "notify-send hello && exec alacritty"; }
|
||||
|
||||
// Example volume keys mappings for PipeWire & WirePlumber.
|
||||
|
||||
@@ -34,7 +34,7 @@ fi
|
||||
# Start niri and wait for it to terminate.
|
||||
systemctl --user --wait start niri.service
|
||||
|
||||
# Force stop of grahical-session.target.
|
||||
# Force stop of graphical-session.target.
|
||||
systemctl --user start --job-mode=replace-irreversibly niri-shutdown.target
|
||||
|
||||
# Unset environment that we've set.
|
||||
|
||||
+16
-12
@@ -16,8 +16,9 @@ use bytemuck::cast_slice_mut;
|
||||
use libc::dev_t;
|
||||
use niri_config::Config;
|
||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||
use smithay::backend::allocator::format::FormatSet;
|
||||
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
|
||||
use smithay::backend::allocator::{Format, Fourcc};
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::drm::compositor::{DrmCompositor, PrimaryPlaneElement};
|
||||
use smithay::backend::drm::{
|
||||
DrmDevice, DrmDeviceFd, DrmEvent, DrmEventMetadata, DrmEventTime, DrmNode, NodeType,
|
||||
@@ -516,7 +517,7 @@ impl Tty {
|
||||
niri.layout.update_shaders();
|
||||
|
||||
// Create the dmabuf global.
|
||||
let primary_formats = renderer.dmabuf_formats().collect::<HashSet<_>>();
|
||||
let primary_formats = renderer.dmabuf_formats();
|
||||
let default_feedback =
|
||||
DmabufFeedbackBuilder::new(render_node.dev_id(), primary_formats.clone())
|
||||
.build()
|
||||
@@ -880,11 +881,13 @@ impl Tty {
|
||||
|
||||
// Filter out the CCS modifiers as they have increased bandwidth, causing some monitor
|
||||
// configurations to stop working.
|
||||
let mut render_formats = render_formats.clone();
|
||||
render_formats.retain(|format| {
|
||||
!matches!(
|
||||
format.modifier,
|
||||
Modifier::I915_y_tiled_ccs
|
||||
let render_formats = render_formats
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|format| {
|
||||
!matches!(
|
||||
format.modifier,
|
||||
Modifier::I915_y_tiled_ccs
|
||||
// I915_FORMAT_MOD_Yf_TILED_CCS
|
||||
| Modifier::Unrecognized(0x100000000000005)
|
||||
| Modifier::I915_y_tiled_gen12_rc_ccs
|
||||
@@ -897,8 +900,9 @@ impl Tty {
|
||||
| Modifier::Unrecognized(0x10000000000000b)
|
||||
// I915_FORMAT_MOD_4_TILED_DG2_RC_CCS_CC
|
||||
| Modifier::Unrecognized(0x10000000000000c)
|
||||
)
|
||||
});
|
||||
)
|
||||
})
|
||||
.collect::<FormatSet>();
|
||||
|
||||
// Create the compositor.
|
||||
let mut compositor = DrmCompositor::new(
|
||||
@@ -921,7 +925,7 @@ impl Tty {
|
||||
|
||||
let mut dmabuf_feedback = None;
|
||||
if let Ok(primary_renderer) = self.gpu_manager.single_renderer(&self.primary_render_node) {
|
||||
let primary_formats = primary_renderer.dmabuf_formats().collect::<HashSet<_>>();
|
||||
let primary_formats = primary_renderer.dmabuf_formats();
|
||||
|
||||
match surface_dmabuf_feedback(
|
||||
&compositor,
|
||||
@@ -1945,7 +1949,7 @@ fn primary_node_from_config(config: &Config) -> Option<(DrmNode, DrmNode)> {
|
||||
|
||||
fn surface_dmabuf_feedback(
|
||||
compositor: &GbmDrmCompositor,
|
||||
primary_formats: HashSet<Format>,
|
||||
primary_formats: FormatSet,
|
||||
primary_render_node: DrmNode,
|
||||
surface_render_node: DrmNode,
|
||||
) -> Result<SurfaceDmabufFeedback, io::Error> {
|
||||
@@ -1958,7 +1962,7 @@ fn surface_dmabuf_feedback(
|
||||
.iter()
|
||||
.chain(planes.overlay.iter().flat_map(|p| p.formats.iter()))
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
.collect::<FormatSet>();
|
||||
|
||||
// We limit the scan-out trache to formats we can also render from so that there is always a
|
||||
// fallback render path available in case the supplied buffer can not be scanned out directly.
|
||||
|
||||
@@ -15,7 +15,7 @@ use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
|
||||
use smithay::reexports::calloop::LoopHandle;
|
||||
use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback;
|
||||
use smithay::reexports::winit::dpi::LogicalSize;
|
||||
use smithay::reexports::winit::window::WindowBuilder;
|
||||
use smithay::reexports::winit::window::Window;
|
||||
|
||||
use super::{IpcOutputMap, RenderResult};
|
||||
use crate::niri::{Niri, RedrawState, State};
|
||||
@@ -36,11 +36,11 @@ impl Winit {
|
||||
config: Rc<RefCell<Config>>,
|
||||
event_loop: LoopHandle<State>,
|
||||
) -> Result<Self, winit::Error> {
|
||||
let builder = WindowBuilder::new()
|
||||
let builder = Window::default_attributes()
|
||||
.with_inner_size(LogicalSize::new(1280.0, 800.0))
|
||||
// .with_resizable(false)
|
||||
.with_title("niri");
|
||||
let (backend, winit) = winit::init_from_builder(builder)?;
|
||||
let (backend, winit) = winit::init_from_attributes(builder)?;
|
||||
|
||||
let output = Output::new(
|
||||
"winit".to_string(),
|
||||
|
||||
@@ -13,6 +13,9 @@ use crate::utils::version;
|
||||
#[command(subcommand_help_heading = "Subcommands")]
|
||||
pub struct Cli {
|
||||
/// Path to config file (default: `$XDG_CONFIG_HOME/niri/config.kdl`).
|
||||
///
|
||||
/// This can also be set with the `NIRI_CONFIG` environment variable. If both are set, the
|
||||
/// command line argument takes precedence.
|
||||
#[arg(short, long)]
|
||||
pub config: Option<PathBuf>,
|
||||
/// Import environment globally to systemd and D-Bus, run D-Bus services.
|
||||
@@ -43,6 +46,9 @@ pub enum Sub {
|
||||
/// Validate the config file.
|
||||
Validate {
|
||||
/// Path to config file (default: `$XDG_CONFIG_HOME/niri/config.kdl`).
|
||||
///
|
||||
/// This can also be set with the `NIRI_CONFIG` environment variable. If both are set, the
|
||||
/// command line argument takes precedence.
|
||||
#[arg(short, long)]
|
||||
config: Option<PathBuf>,
|
||||
},
|
||||
@@ -58,6 +64,8 @@ pub enum Msg {
|
||||
Workspaces,
|
||||
/// Print information about the focused window.
|
||||
FocusedWindow,
|
||||
/// Print information about the focused output.
|
||||
FocusedOutput,
|
||||
/// Perform an action.
|
||||
Action {
|
||||
#[command(subcommand)]
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@ impl CursorManager {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Currenly used cursor_image as a cursor provider.
|
||||
/// Currently used cursor_image as a cursor provider.
|
||||
pub fn cursor_image(&self) -> &CursorImageStatus {
|
||||
&self.current_cursor
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use zbus::fdo::{self, RequestNameFlags};
|
||||
use zbus::zvariant::{SerializeDict, Type, Value};
|
||||
use zbus::{dbus_interface, SignalContext};
|
||||
|
||||
use super::Start;
|
||||
|
||||
pub struct Introspect {
|
||||
to_niri: calloop::channel::Sender<IntrospectToNiri>,
|
||||
from_niri: async_channel::Receiver<NiriToIntrospect>,
|
||||
}
|
||||
|
||||
pub enum IntrospectToNiri {
|
||||
GetWindows,
|
||||
}
|
||||
|
||||
pub enum NiriToIntrospect {
|
||||
Windows(HashMap<u64, WindowProperties>),
|
||||
}
|
||||
|
||||
#[derive(Debug, SerializeDict, Type, Value)]
|
||||
#[zvariant(signature = "dict")]
|
||||
pub struct WindowProperties {
|
||||
/// Window title.
|
||||
pub title: String,
|
||||
/// Window app ID.
|
||||
///
|
||||
/// This is actually the name of the .desktop file, and Shell does internal tracking to match
|
||||
/// Wayland app IDs to desktop files. We don't do that yet, which is the reason why
|
||||
/// xdg-desktop-portal-gnome's window list is missing icons.
|
||||
#[zvariant(rename = "app-id")]
|
||||
pub app_id: String,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.gnome.Shell.Introspect")]
|
||||
impl Introspect {
|
||||
async fn get_windows(&self) -> fdo::Result<HashMap<u64, WindowProperties>> {
|
||||
if let Err(err) = self.to_niri.send(IntrospectToNiri::GetWindows) {
|
||||
warn!("error sending message to niri: {err:?}");
|
||||
return Err(fdo::Error::Failed("internal error".to_owned()));
|
||||
}
|
||||
|
||||
match self.from_niri.recv().await {
|
||||
Ok(NiriToIntrospect::Windows(windows)) => Ok(windows),
|
||||
Err(err) => {
|
||||
warn!("error receiving message from niri: {err:?}");
|
||||
Err(fdo::Error::Failed("internal error".to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: call this upon window changes, once more of the infrastructure is there (will be
|
||||
// needed for the event stream IPC anyway).
|
||||
#[dbus_interface(signal)]
|
||||
pub async fn windows_changed(ctxt: &SignalContext<'_>) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl Introspect {
|
||||
pub fn new(
|
||||
to_niri: calloop::channel::Sender<IntrospectToNiri>,
|
||||
from_niri: async_channel::Receiver<NiriToIntrospect>,
|
||||
) -> Self {
|
||||
Self { to_niri, from_niri }
|
||||
}
|
||||
}
|
||||
|
||||
impl Start for Introspect {
|
||||
fn start(self) -> anyhow::Result<zbus::blocking::Connection> {
|
||||
let conn = zbus::blocking::Connection::session()?;
|
||||
let flags = RequestNameFlags::AllowReplacement
|
||||
| RequestNameFlags::ReplaceExisting
|
||||
| RequestNameFlags::DoNotQueue;
|
||||
|
||||
conn.object_server()
|
||||
.at("/org/gnome/Shell/Introspect", self)?;
|
||||
conn.request_name_with_flags("org.gnome.Shell.Introspect", flags)?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
}
|
||||
+17
-4
@@ -4,6 +4,7 @@ use zbus::Interface;
|
||||
use crate::niri::State;
|
||||
|
||||
pub mod freedesktop_screensaver;
|
||||
pub mod gnome_shell_introspect;
|
||||
pub mod gnome_shell_screenshot;
|
||||
pub mod mutter_display_config;
|
||||
pub mod mutter_service_channel;
|
||||
@@ -14,6 +15,7 @@ pub mod mutter_screen_cast;
|
||||
use mutter_screen_cast::ScreenCast;
|
||||
|
||||
use self::freedesktop_screensaver::ScreenSaver;
|
||||
use self::gnome_shell_introspect::Introspect;
|
||||
use self::mutter_display_config::DisplayConfig;
|
||||
use self::mutter_service_channel::ServiceChannel;
|
||||
|
||||
@@ -27,6 +29,7 @@ pub struct DBusServers {
|
||||
pub conn_display_config: Option<Connection>,
|
||||
pub conn_screen_saver: Option<Connection>,
|
||||
pub conn_screen_shot: Option<Connection>,
|
||||
pub conn_introspect: Option<Connection>,
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
pub conn_screen_cast: Option<Connection>,
|
||||
}
|
||||
@@ -66,16 +69,26 @@ impl DBusServers {
|
||||
let screenshot = gnome_shell_screenshot::Screenshot::new(to_niri, from_niri);
|
||||
dbus.conn_screen_shot = try_start(screenshot);
|
||||
|
||||
let (to_niri, from_introspect) = calloop::channel::channel();
|
||||
let (to_introspect, from_niri) = async_channel::unbounded();
|
||||
niri.event_loop
|
||||
.insert_source(from_introspect, move |event, _, state| match event {
|
||||
calloop::channel::Event::Msg(msg) => {
|
||||
state.on_introspect_msg(&to_introspect, msg)
|
||||
}
|
||||
calloop::channel::Event::Closed => (),
|
||||
})
|
||||
.unwrap();
|
||||
let introspect = Introspect::new(to_niri, from_niri);
|
||||
dbus.conn_introspect = try_start(introspect);
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
if niri.pipewire.is_some() {
|
||||
let (to_niri, from_screen_cast) = calloop::channel::channel();
|
||||
niri.event_loop
|
||||
.insert_source(from_screen_cast, {
|
||||
let to_niri = to_niri.clone();
|
||||
move |event, _, state| match event {
|
||||
calloop::channel::Event::Msg(msg) => {
|
||||
state.on_screen_cast_msg(&to_niri, msg)
|
||||
}
|
||||
calloop::channel::Event::Msg(msg) => state.on_screen_cast_msg(msg),
|
||||
calloop::channel::Event::Closed => (),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use serde::Deserialize;
|
||||
use smithay::output::Output;
|
||||
use zbus::fdo::RequestNameFlags;
|
||||
use zbus::zvariant::{DeserializeDict, OwnedObjectPath, SerializeDict, Type, Value};
|
||||
use zbus::{dbus_interface, fdo, InterfaceRef, ObjectServer, SignalContext};
|
||||
@@ -47,15 +46,40 @@ struct RecordMonitorProperties {
|
||||
_is_recording: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, DeserializeDict, Type)]
|
||||
#[zvariant(signature = "dict")]
|
||||
struct RecordWindowProperties {
|
||||
#[zvariant(rename = "window-id")]
|
||||
window_id: u64,
|
||||
#[zvariant(rename = "cursor-mode")]
|
||||
cursor_mode: Option<CursorMode>,
|
||||
#[zvariant(rename = "is-recording")]
|
||||
_is_recording: Option<bool>,
|
||||
}
|
||||
|
||||
static STREAM_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Stream {
|
||||
// FIXME: update on scale changes and whatnot.
|
||||
output: niri_ipc::Output,
|
||||
target: StreamTarget,
|
||||
cursor_mode: CursorMode,
|
||||
was_started: Arc<AtomicBool>,
|
||||
to_niri: calloop::channel::Sender<ScreenCastToNiri>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum StreamTarget {
|
||||
// FIXME: update on scale changes and whatnot.
|
||||
Output(niri_ipc::Output),
|
||||
Window { id: u64 },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StreamTargetId {
|
||||
Output { name: String },
|
||||
Window { id: u64 },
|
||||
}
|
||||
|
||||
#[derive(Debug, SerializeDict, Type, Value)]
|
||||
#[zvariant(signature = "dict")]
|
||||
struct StreamParameters {
|
||||
@@ -68,14 +92,13 @@ struct StreamParameters {
|
||||
pub enum ScreenCastToNiri {
|
||||
StartCast {
|
||||
session_id: usize,
|
||||
output: String,
|
||||
target: StreamTargetId,
|
||||
cursor_mode: CursorMode,
|
||||
signal_ctx: SignalContext<'static>,
|
||||
},
|
||||
StopCast {
|
||||
session_id: usize,
|
||||
},
|
||||
Redraw(Output),
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.gnome.Mutter.ScreenCast")]
|
||||
@@ -176,16 +199,51 @@ impl Session {
|
||||
return Err(fdo::Error::Failed("monitor is disabled".to_owned()));
|
||||
}
|
||||
|
||||
static NUMBER: AtomicUsize = AtomicUsize::new(0);
|
||||
let path = format!(
|
||||
"/org/gnome/Mutter/ScreenCast/Stream/u{}",
|
||||
NUMBER.fetch_add(1, Ordering::SeqCst)
|
||||
STREAM_ID.fetch_add(1, Ordering::SeqCst)
|
||||
);
|
||||
let path = OwnedObjectPath::try_from(path).unwrap();
|
||||
|
||||
let cursor_mode = properties.cursor_mode.unwrap_or_default();
|
||||
|
||||
let stream = Stream::new(output.clone(), cursor_mode, self.to_niri.clone());
|
||||
let target = StreamTarget::Output(output);
|
||||
let stream = Stream::new(target, cursor_mode, self.to_niri.clone());
|
||||
match server.at(&path, stream.clone()).await {
|
||||
Ok(true) => {
|
||||
let iface = server.interface(&path).await.unwrap();
|
||||
self.streams.lock().unwrap().push((stream, iface));
|
||||
}
|
||||
Ok(false) => return Err(fdo::Error::Failed("stream path already exists".to_owned())),
|
||||
Err(err) => {
|
||||
return Err(fdo::Error::Failed(format!(
|
||||
"error creating stream object: {err:?}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
async fn record_window(
|
||||
&mut self,
|
||||
#[zbus(object_server)] server: &ObjectServer,
|
||||
properties: RecordWindowProperties,
|
||||
) -> fdo::Result<OwnedObjectPath> {
|
||||
debug!(?properties, "record_window");
|
||||
|
||||
let path = format!(
|
||||
"/org/gnome/Mutter/ScreenCast/Stream/u{}",
|
||||
STREAM_ID.fetch_add(1, Ordering::SeqCst)
|
||||
);
|
||||
let path = OwnedObjectPath::try_from(path).unwrap();
|
||||
|
||||
let cursor_mode = properties.cursor_mode.unwrap_or_default();
|
||||
|
||||
let target = StreamTarget::Window {
|
||||
id: properties.window_id,
|
||||
};
|
||||
let stream = Stream::new(target, cursor_mode, self.to_niri.clone());
|
||||
match server.at(&path, stream.clone()).await {
|
||||
Ok(true) => {
|
||||
let iface = server.interface(&path).await.unwrap();
|
||||
@@ -214,10 +272,21 @@ impl Stream {
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn parameters(&self) -> StreamParameters {
|
||||
let logical = self.output.logical.as_ref().unwrap();
|
||||
StreamParameters {
|
||||
position: (logical.x, logical.y),
|
||||
size: (logical.width as i32, logical.height as i32),
|
||||
match &self.target {
|
||||
StreamTarget::Output(output) => {
|
||||
let logical = output.logical.as_ref().unwrap();
|
||||
StreamParameters {
|
||||
position: (logical.x, logical.y),
|
||||
size: (logical.width as i32, logical.height as i32),
|
||||
}
|
||||
}
|
||||
StreamTarget::Window { .. } => {
|
||||
// Does any consumer need this?
|
||||
StreamParameters {
|
||||
position: (0, 0),
|
||||
size: (1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,13 +344,13 @@ impl Drop for Session {
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
pub fn new(
|
||||
output: niri_ipc::Output,
|
||||
fn new(
|
||||
target: StreamTarget,
|
||||
cursor_mode: CursorMode,
|
||||
to_niri: calloop::channel::Sender<ScreenCastToNiri>,
|
||||
) -> Self {
|
||||
Self {
|
||||
output,
|
||||
target,
|
||||
cursor_mode,
|
||||
was_started: Arc::new(AtomicBool::new(false)),
|
||||
to_niri,
|
||||
@@ -295,7 +364,7 @@ impl Stream {
|
||||
|
||||
let msg = ScreenCastToNiri::StartCast {
|
||||
session_id,
|
||||
output: self.output.name.clone(),
|
||||
target: self.target.make_id(),
|
||||
cursor_mode: self.cursor_mode,
|
||||
signal_ctx: ctxt,
|
||||
};
|
||||
@@ -305,3 +374,14 @@ impl Stream {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamTarget {
|
||||
fn make_id(&self) -> StreamTargetId {
|
||||
match self {
|
||||
StreamTarget::Output(output) => StreamTargetId::Output {
|
||||
name: output.name.clone(),
|
||||
},
|
||||
StreamTarget::Window { id } => StreamTargetId::Window { id: *id },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::reexports::wayland_server::{Client, Resource};
|
||||
use smithay::wayland::buffer::BufferHandler;
|
||||
use smithay::wayland::compositor::{
|
||||
add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface, send_surface_state,
|
||||
with_states, BufferAssignment, CompositorClientState, CompositorHandler, CompositorState,
|
||||
SurfaceAttributes,
|
||||
add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface, with_states,
|
||||
BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, SurfaceAttributes,
|
||||
};
|
||||
use smithay::wayland::dmabuf::get_dmabuf;
|
||||
use smithay::wayland::shell::xdg::XdgToplevelSurfaceData;
|
||||
@@ -19,6 +18,7 @@ use smithay::{delegate_compositor, delegate_shm};
|
||||
|
||||
use super::xdg_shell::add_mapped_toplevel_pre_commit_hook;
|
||||
use crate::niri::{ClientState, State};
|
||||
use crate::utils::send_scale_transform;
|
||||
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped};
|
||||
|
||||
impl CompositorHandler for State {
|
||||
@@ -37,10 +37,10 @@ impl CompositorHandler for State {
|
||||
}
|
||||
|
||||
if let Some(output) = self.niri.output_for_root(&root) {
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let scale = output.current_scale();
|
||||
let transform = output.current_transform();
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, scale, transform);
|
||||
send_scale_transform(surface, data, scale, transform);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ impl CompositorHandler for State {
|
||||
let output =
|
||||
output.filter(|o| self.niri.layout.monitor_for_output(o).is_some());
|
||||
|
||||
// Chech that the workspace still exists.
|
||||
// Check that the workspace still exists.
|
||||
let workspace_name = workspace_name
|
||||
.filter(|n| self.niri.layout.find_workspace_by_name(n).is_some());
|
||||
|
||||
@@ -209,6 +209,9 @@ impl CompositorHandler for State {
|
||||
let window = mapped.window.clone();
|
||||
let output = output.clone();
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
let id = mapped.id();
|
||||
|
||||
// This is a commit of a previously-mapped toplevel.
|
||||
let is_mapped =
|
||||
with_renderer_surface_state(surface, |state| state.buffer().is_some())
|
||||
@@ -235,6 +238,12 @@ impl CompositorHandler for State {
|
||||
let active_window = self.niri.layout.active_window().map(|(m, _)| &m.window);
|
||||
let was_active = active_window == Some(&window);
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
self.niri
|
||||
.stop_casts_for_target(crate::pw_utils::CastTarget::Window {
|
||||
id: u64::from(id.get()),
|
||||
});
|
||||
|
||||
self.niri.layout.remove_window(&window);
|
||||
|
||||
if was_active {
|
||||
|
||||
@@ -3,7 +3,7 @@ use smithay::desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurf
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::wayland::compositor::{send_surface_state, with_states};
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::wayland::shell::wlr_layer::{
|
||||
Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler,
|
||||
WlrLayerShellState,
|
||||
@@ -11,6 +11,7 @@ use smithay::wayland::shell::wlr_layer::{
|
||||
use smithay::wayland::shell::xdg::PopupSurface;
|
||||
|
||||
use crate::niri::State;
|
||||
use crate::utils::send_scale_transform;
|
||||
|
||||
impl WlrLayerShellHandler for State {
|
||||
fn shell_state(&mut self) -> &mut WlrLayerShellState {
|
||||
@@ -24,11 +25,17 @@ impl WlrLayerShellHandler for State {
|
||||
_layer: Layer,
|
||||
namespace: String,
|
||||
) {
|
||||
let output = wl_output
|
||||
.as_ref()
|
||||
.and_then(Output::from_resource)
|
||||
.or_else(|| self.niri.layout.active_output().cloned())
|
||||
.unwrap();
|
||||
let output = if let Some(wl_output) = &wl_output {
|
||||
Output::from_resource(wl_output)
|
||||
} else {
|
||||
self.niri.layout.active_output().cloned()
|
||||
};
|
||||
let Some(output) = output else {
|
||||
warn!("no output for new layer surface, closing");
|
||||
surface.send_close();
|
||||
return;
|
||||
};
|
||||
|
||||
let mut map = layer_map_for_output(&output);
|
||||
map.map_layer(&LayerSurface::new(surface, namespace))
|
||||
.unwrap();
|
||||
@@ -97,10 +104,10 @@ impl State {
|
||||
.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap();
|
||||
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let scale = output.current_scale();
|
||||
let transform = output.current_transform();
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, scale, transform);
|
||||
send_scale_transform(surface, data, scale, transform);
|
||||
});
|
||||
|
||||
layer.layer_surface().send_configure();
|
||||
|
||||
+59
-12
@@ -22,11 +22,12 @@ use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::reexports::wayland_server::Resource;
|
||||
use smithay::utils::{Logical, Rectangle, Size};
|
||||
use smithay::wayland::compositor::{send_surface_state, with_states};
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
|
||||
use smithay::wayland::drm_lease::{
|
||||
DrmLease, DrmLeaseBuilder, DrmLeaseHandler, DrmLeaseRequest, DrmLeaseState, LeaseRejected,
|
||||
};
|
||||
use smithay::wayland::fractional_scale::FractionalScaleHandler;
|
||||
use smithay::wayland::idle_inhibit::IdleInhibitHandler;
|
||||
use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
|
||||
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
||||
@@ -48,22 +49,27 @@ use smithay::wayland::session_lock::{
|
||||
LockSurface, SessionLockHandler, SessionLockManagerState, SessionLocker,
|
||||
};
|
||||
use smithay::wayland::tablet_manager::TabletSeatHandler;
|
||||
use smithay::wayland::xdg_activation::{
|
||||
XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
|
||||
};
|
||||
use smithay::{
|
||||
delegate_cursor_shape, delegate_data_control, delegate_data_device, delegate_dmabuf,
|
||||
delegate_drm_lease, delegate_idle_inhibit, delegate_idle_notify, delegate_input_method_manager,
|
||||
delegate_output, delegate_pointer_constraints, delegate_pointer_gestures,
|
||||
delegate_presentation, delegate_primary_selection, delegate_relative_pointer, delegate_seat,
|
||||
delegate_security_context, delegate_session_lock, delegate_tablet_manager,
|
||||
delegate_text_input_manager, delegate_viewporter, delegate_virtual_keyboard_manager,
|
||||
delegate_drm_lease, delegate_fractional_scale, delegate_idle_inhibit, delegate_idle_notify,
|
||||
delegate_input_method_manager, delegate_output, delegate_pointer_constraints,
|
||||
delegate_pointer_gestures, delegate_presentation, delegate_primary_selection,
|
||||
delegate_relative_pointer, delegate_seat, delegate_security_context, delegate_session_lock,
|
||||
delegate_tablet_manager, delegate_text_input_manager, delegate_viewporter,
|
||||
delegate_virtual_keyboard_manager, delegate_xdg_activation,
|
||||
};
|
||||
|
||||
pub use crate::handlers::xdg_shell::KdeDecorationsModeState;
|
||||
use crate::niri::{ClientState, State};
|
||||
use crate::protocols::foreign_toplevel::{
|
||||
self, ForeignToplevelHandler, ForeignToplevelManagerState,
|
||||
};
|
||||
use crate::protocols::gamma_control::{GammaControlHandler, GammaControlManagerState};
|
||||
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler};
|
||||
use crate::utils::output_size;
|
||||
use crate::utils::{output_size, send_scale_transform};
|
||||
use crate::{delegate_foreign_toplevel, delegate_gamma_control, delegate_screencopy};
|
||||
|
||||
impl SeatHandler for State {
|
||||
@@ -136,11 +142,11 @@ impl InputMethodHandler for State {
|
||||
fn new_popup(&mut self, surface: PopupSurface) {
|
||||
let popup = PopupKind::InputMethod(surface);
|
||||
if let Some(output) = self.output_for_popup(&popup) {
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let scale = output.current_scale();
|
||||
let transform = output.current_transform();
|
||||
let wl_surface = popup.wl_surface();
|
||||
with_states(wl_surface, |data| {
|
||||
send_surface_state(wl_surface, data, scale, transform);
|
||||
send_scale_transform(wl_surface, data, scale, transform);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -288,7 +294,7 @@ impl SessionLockHandler for State {
|
||||
|
||||
fn new_surface(&mut self, surface: LockSurface, output: WlOutput) {
|
||||
let Some(output) = Output::from_resource(&output) else {
|
||||
error!("no Output matching WlOutput");
|
||||
warn!("no Output matching WlOutput");
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -303,11 +309,11 @@ pub fn configure_lock_surface(surface: &LockSurface, output: &Output) {
|
||||
let size = output_size(output);
|
||||
states.size = Some(Size::from((size.w as u32, size.h as u32)));
|
||||
});
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let scale = output.current_scale();
|
||||
let transform = output.current_transform();
|
||||
let wl_surface = surface.wl_surface();
|
||||
with_states(wl_surface, |data| {
|
||||
send_surface_state(wl_surface, data, scale, transform);
|
||||
send_scale_transform(wl_surface, data, scale, transform);
|
||||
});
|
||||
surface.send_configure();
|
||||
}
|
||||
@@ -498,3 +504,44 @@ impl GammaControlHandler for State {
|
||||
}
|
||||
}
|
||||
delegate_gamma_control!(State);
|
||||
|
||||
impl XdgActivationHandler for State {
|
||||
fn activation_state(&mut self) -> &mut XdgActivationState {
|
||||
&mut self.niri.activation_state
|
||||
}
|
||||
|
||||
fn token_created(&mut self, _token: XdgActivationToken, data: XdgActivationTokenData) -> bool {
|
||||
// Only tokens that were created while the application has keyboard focus are valid.
|
||||
let Some((serial, seat)) = data.serial else {
|
||||
return false;
|
||||
};
|
||||
let Some(seat) = Seat::<State>::from_resource(&seat) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let keyboard = seat.get_keyboard().unwrap();
|
||||
keyboard
|
||||
.last_enter()
|
||||
.map(|last_enter| serial.is_no_older_than(&last_enter))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn request_activation(
|
||||
&mut self,
|
||||
_token: XdgActivationToken,
|
||||
token_data: XdgActivationTokenData,
|
||||
surface: WlSurface,
|
||||
) {
|
||||
if token_data.timestamp.elapsed().as_secs() < 10 {
|
||||
if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&surface) {
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.activate_window(&window);
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delegate_xdg_activation!(State);
|
||||
|
||||
impl FractionalScaleHandler for State {}
|
||||
delegate_fractional_scale!(State);
|
||||
|
||||
+69
-26
@@ -1,3 +1,5 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use smithay::desktop::{
|
||||
find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, utils, LayerSurface,
|
||||
PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy, Window,
|
||||
@@ -8,14 +10,14 @@ use smithay::output::Output;
|
||||
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_positioner::ConstraintAdjustment;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{self};
|
||||
use smithay::reexports::wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration;
|
||||
use smithay::reexports::wayland_server::protocol::wl_output;
|
||||
use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::reexports::wayland_server::Resource;
|
||||
use smithay::reexports::wayland_server::{self, Resource, WEnum};
|
||||
use smithay::utils::{Logical, Rectangle, Serial};
|
||||
use smithay::wayland::compositor::{
|
||||
add_pre_commit_hook, send_surface_state, with_states, BufferAssignment, HookId,
|
||||
SurfaceAttributes,
|
||||
add_pre_commit_hook, with_states, BufferAssignment, HookId, SurfaceAttributes,
|
||||
};
|
||||
use smithay::wayland::input_method::InputMethodSeat;
|
||||
use smithay::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState};
|
||||
@@ -34,7 +36,7 @@ use crate::input::resize_grab::ResizeGrab;
|
||||
use crate::input::DOUBLE_CLICK_TIME;
|
||||
use crate::layout::workspace::ColumnWidth;
|
||||
use crate::niri::{PopupGrabState, State};
|
||||
use crate::utils::{get_monotonic_time, ResizeEdge};
|
||||
use crate::utils::{get_monotonic_time, send_scale_transform, ResizeEdge};
|
||||
use crate::window::{InitialConfigureState, ResolvedWindowRules, Unmapped, WindowRef};
|
||||
|
||||
impl XdgShellHandler for State {
|
||||
@@ -464,6 +466,12 @@ impl XdgShellHandler for State {
|
||||
let window = mapped.window.clone();
|
||||
let output = output.clone();
|
||||
|
||||
#[cfg(feature = "xdp-gnome-screencast")]
|
||||
self.niri
|
||||
.stop_casts_for_target(crate::pw_utils::CastTarget::Window {
|
||||
id: u64::from(mapped.id().get()),
|
||||
});
|
||||
|
||||
self.backend.with_primary_renderer(|renderer| {
|
||||
self.niri.layout.store_unmap_snapshot(renderer, &window);
|
||||
});
|
||||
@@ -545,10 +553,44 @@ impl XdgDecorationHandler for State {
|
||||
}
|
||||
delegate_xdg_decoration!(State);
|
||||
|
||||
/// Whether KDE server decorations are in use.
|
||||
#[derive(Default)]
|
||||
pub struct KdeDecorationsModeState {
|
||||
server: Cell<bool>,
|
||||
}
|
||||
|
||||
impl KdeDecorationsModeState {
|
||||
pub fn is_server(&self) -> bool {
|
||||
self.server.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl KdeDecorationHandler for State {
|
||||
fn kde_decoration_state(&self) -> &KdeDecorationState {
|
||||
&self.niri.kde_decoration_state
|
||||
}
|
||||
|
||||
fn request_mode(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
decoration: &org_kde_kwin_server_decoration::OrgKdeKwinServerDecoration,
|
||||
mode: wayland_server::WEnum<org_kde_kwin_server_decoration::Mode>,
|
||||
) {
|
||||
let WEnum::Value(mode) = mode else {
|
||||
return;
|
||||
};
|
||||
|
||||
decoration.mode(mode);
|
||||
|
||||
with_states(surface, |states| {
|
||||
let state = states
|
||||
.data_map
|
||||
.get_or_insert(KdeDecorationsModeState::default);
|
||||
state
|
||||
.server
|
||||
.set(mode == org_kde_kwin_server_decoration::Mode::Server);
|
||||
});
|
||||
}
|
||||
}
|
||||
delegate_kde_decoration!(State);
|
||||
|
||||
@@ -734,10 +776,10 @@ impl State {
|
||||
if !initial_configure_sent {
|
||||
if let Some(output) = self.output_for_popup(&PopupKind::Xdg(popup.clone()))
|
||||
{
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let scale = output.current_scale();
|
||||
let transform = output.current_transform();
|
||||
with_states(surface, |data| {
|
||||
send_surface_state(surface, data, scale, transform);
|
||||
send_scale_transform(surface, data, scale, transform);
|
||||
});
|
||||
}
|
||||
popup.send_configure().expect("initial configure failed");
|
||||
@@ -789,9 +831,9 @@ impl State {
|
||||
// window can be scrolled to both edges of the screen), but within the whole monitor's
|
||||
// height.
|
||||
let mut target =
|
||||
Rectangle::from_loc_and_size((0, 0), (window_geo.size.w, output_geo.size.h));
|
||||
Rectangle::from_loc_and_size((0, 0), (window_geo.size.w, output_geo.size.h)).to_f64();
|
||||
target.loc -= self.niri.layout.window_loc(window).unwrap();
|
||||
target.loc -= get_popup_toplevel_coords(popup);
|
||||
target.loc -= get_popup_toplevel_coords(popup).to_f64();
|
||||
|
||||
self.position_popup_within_rect(popup, target);
|
||||
}
|
||||
@@ -814,10 +856,10 @@ impl State {
|
||||
target.loc -= layer_geo.loc;
|
||||
target.loc -= get_popup_toplevel_coords(popup);
|
||||
|
||||
self.position_popup_within_rect(popup, target);
|
||||
self.position_popup_within_rect(popup, target.to_f64());
|
||||
}
|
||||
|
||||
fn position_popup_within_rect(&self, popup: &PopupKind, target: Rectangle<i32, Logical>) {
|
||||
fn position_popup_within_rect(&self, popup: &PopupKind, target: Rectangle<f64, Logical>) {
|
||||
match popup {
|
||||
PopupKind::Xdg(popup) => {
|
||||
popup.with_pending_state(|state| {
|
||||
@@ -827,28 +869,29 @@ impl State {
|
||||
PopupKind::InputMethod(popup) => {
|
||||
let text_input_rectangle = popup.text_input_rectangle();
|
||||
let mut bbox =
|
||||
utils::bbox_from_surface_tree(popup.wl_surface(), text_input_rectangle.loc);
|
||||
utils::bbox_from_surface_tree(popup.wl_surface(), text_input_rectangle.loc)
|
||||
.to_f64();
|
||||
|
||||
// Position bbox horizontally first.
|
||||
let overflow_x = (bbox.loc.x + bbox.size.w) - (target.loc.x + target.size.w);
|
||||
if overflow_x > 0 {
|
||||
if overflow_x > 0. {
|
||||
bbox.loc.x -= overflow_x;
|
||||
}
|
||||
|
||||
// Ensure that the popup starts within the window.
|
||||
bbox.loc.x = bbox.loc.x.max(target.loc.x);
|
||||
bbox.loc.x = f64::max(bbox.loc.x, target.loc.x);
|
||||
|
||||
// Try to position IME popup below the text input rectangle.
|
||||
let mut below = bbox;
|
||||
below.loc.y += text_input_rectangle.size.h;
|
||||
below.loc.y += f64::from(text_input_rectangle.size.h);
|
||||
|
||||
let mut above = bbox;
|
||||
above.loc.y -= bbox.size.h;
|
||||
|
||||
if target.loc.y + target.size.h >= below.loc.y + below.size.h {
|
||||
popup.set_location(below.loc);
|
||||
popup.set_location(below.loc.to_i32_round());
|
||||
} else {
|
||||
popup.set_location(above.loc);
|
||||
popup.set_location(above.loc.to_i32_round());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -908,25 +951,25 @@ impl State {
|
||||
|
||||
fn unconstrain_with_padding(
|
||||
positioner: PositionerState,
|
||||
target: Rectangle<i32, Logical>,
|
||||
target: Rectangle<f64, Logical>,
|
||||
) -> Rectangle<i32, Logical> {
|
||||
// Try unconstraining with a small padding first which looks nicer, then if it doesn't fit try
|
||||
// unconstraining without padding.
|
||||
const PADDING: i32 = 8;
|
||||
const PADDING: f64 = 8.;
|
||||
|
||||
let mut padded = target;
|
||||
if PADDING * 2 < padded.size.w {
|
||||
if PADDING * 2. < padded.size.w {
|
||||
padded.loc.x += PADDING;
|
||||
padded.size.w -= PADDING * 2;
|
||||
padded.size.w -= PADDING * 2.;
|
||||
}
|
||||
if PADDING * 2 < padded.size.h {
|
||||
if PADDING * 2. < padded.size.h {
|
||||
padded.loc.y += PADDING;
|
||||
padded.size.h -= PADDING * 2;
|
||||
padded.size.h -= PADDING * 2.;
|
||||
}
|
||||
|
||||
// No padding, so just unconstrain with the original target.
|
||||
if padded == target {
|
||||
return positioner.get_unconstrained_geometry(target);
|
||||
return positioner.get_unconstrained_geometry(target.to_i32_round());
|
||||
}
|
||||
|
||||
// Do not try to resize to fit the padded target rectangle.
|
||||
@@ -938,13 +981,13 @@ fn unconstrain_with_padding(
|
||||
.constraint_adjustment
|
||||
.remove(ConstraintAdjustment::ResizeY);
|
||||
|
||||
let geo = no_resize.get_unconstrained_geometry(padded);
|
||||
if padded.contains_rect(geo) {
|
||||
let geo = no_resize.get_unconstrained_geometry(padded.to_i32_round());
|
||||
if padded.contains_rect(geo.to_f64()) {
|
||||
return geo;
|
||||
}
|
||||
|
||||
// Could not unconstrain into the padded target, so resort to the regular one.
|
||||
positioner.get_unconstrained_geometry(target)
|
||||
positioner.get_unconstrained_geometry(target.to_i32_round())
|
||||
}
|
||||
|
||||
pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId {
|
||||
|
||||
+212
-65
@@ -1,4 +1,5 @@
|
||||
use std::any::Any;
|
||||
use std::cmp::min;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
@@ -23,12 +24,12 @@ use smithay::input::pointer::{
|
||||
GrabStartData as PointerGrabStartData, MotionEvent, RelativeMotionEvent,
|
||||
};
|
||||
use smithay::input::touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent};
|
||||
use smithay::utils::{Logical, Point, SERIAL_COUNTER};
|
||||
use smithay::utils::{Logical, Point, Rectangle, SERIAL_COUNTER};
|
||||
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
|
||||
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
|
||||
|
||||
use self::resize_grab::ResizeGrab;
|
||||
use self::view_offset_grab::ViewOffsetGrab;
|
||||
use self::spatial_movement_grab::SpatialMovementGrab;
|
||||
use crate::niri::State;
|
||||
use crate::ui::screenshot_ui::ScreenshotUi;
|
||||
use crate::utils::spawning::spawn;
|
||||
@@ -36,8 +37,8 @@ use crate::utils::{center, get_monotonic_time, ResizeEdge};
|
||||
|
||||
pub mod resize_grab;
|
||||
pub mod scroll_tracker;
|
||||
pub mod spatial_movement_grab;
|
||||
pub mod swipe_tracker;
|
||||
pub mod view_offset_grab;
|
||||
|
||||
pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400);
|
||||
|
||||
@@ -214,6 +215,19 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the rectangle that covers all outputs in global space.
|
||||
fn global_bounding_rectangle(&self) -> Option<Rectangle<i32, Logical>> {
|
||||
self.niri.global_space.outputs().fold(
|
||||
None,
|
||||
|acc: Option<Rectangle<i32, Logical>>, output| {
|
||||
self.niri
|
||||
.global_space
|
||||
.output_geometry(output)
|
||||
.map(|geo| acc.map(|acc| acc.merge(geo)).unwrap_or(geo))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Computes the cursor position for the tablet event.
|
||||
///
|
||||
/// This function handles the tablet output mapping, as well as coordinate clamping and aspect
|
||||
@@ -225,33 +239,52 @@ impl State {
|
||||
where
|
||||
I::Device: 'static,
|
||||
{
|
||||
let output = self.niri.output_for_tablet()?;
|
||||
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||
let (target_geo, keep_ratio, px) = if let Some(output) = self.niri.output_for_tablet() {
|
||||
(
|
||||
self.niri.global_space.output_geometry(output).unwrap(),
|
||||
true,
|
||||
1. / output.current_scale().fractional_scale(),
|
||||
)
|
||||
} else {
|
||||
let geo = self.global_bounding_rectangle()?;
|
||||
|
||||
let mut pos = event.position_transformed(output_geo.size);
|
||||
pos.x /= output_geo.size.w as f64;
|
||||
pos.y /= output_geo.size.h as 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();
|
||||
|
||||
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 output_aspect_ratio = output_geo.size.w as f64 / output_geo.size.h as f64;
|
||||
let ratio = data.aspect_ratio / output_aspect_ratio;
|
||||
|
||||
if ratio > 1. {
|
||||
pos.x *= ratio;
|
||||
} else {
|
||||
pos.y /= ratio;
|
||||
}
|
||||
}
|
||||
// Do not keep ratio for the unified mode as this is what OpenTabletDriver expects.
|
||||
(geo, false, 1. / scale)
|
||||
};
|
||||
|
||||
pos.x *= output_geo.size.w as f64;
|
||||
pos.y *= output_geo.size.h as f64;
|
||||
pos.x = pos.x.clamp(0.0, output_geo.size.w as f64 - 1.);
|
||||
pos.y = pos.y.clamp(0.0, output_geo.size.h as f64 - 1.);
|
||||
Some(pos + output_geo.loc.to_f64())
|
||||
let mut pos = event.position_transformed(target_geo.size);
|
||||
|
||||
if keep_ratio {
|
||||
pos.x /= target_geo.size.w as f64;
|
||||
pos.y /= target_geo.size.h as f64;
|
||||
|
||||
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 output_aspect_ratio = target_geo.size.w as f64 / target_geo.size.h as f64;
|
||||
let ratio = data.aspect_ratio / output_aspect_ratio;
|
||||
|
||||
if ratio > 1. {
|
||||
pos.x *= ratio;
|
||||
} else {
|
||||
pos.y /= ratio;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pos.x *= target_geo.size.w as f64;
|
||||
pos.y *= target_geo.size.h as f64;
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
fn on_keyboard<I: InputBackend>(&mut self, event: I::KeyboardKeyEvent) {
|
||||
@@ -551,6 +584,52 @@ impl State {
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusColumnRightOrFirst => {
|
||||
self.niri.layout.focus_column_right_or_first();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusColumnLeftOrLast => {
|
||||
self.niri.layout.focus_column_left_or_last();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusColumnOrMonitorLeft => {
|
||||
if let Some(output) = self.niri.output_left() {
|
||||
if self.niri.layout.focus_column_left_or_output(&output)
|
||||
&& !self.maybe_warp_cursor_to_focus_centered()
|
||||
{
|
||||
self.move_cursor_to_output(&output);
|
||||
} else {
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
} else {
|
||||
self.niri.layout.focus_left();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusColumnOrMonitorRight => {
|
||||
if let Some(output) = self.niri.output_right() {
|
||||
if self.niri.layout.focus_column_right_or_output(&output)
|
||||
&& !self.maybe_warp_cursor_to_focus_centered()
|
||||
{
|
||||
self.move_cursor_to_output(&output);
|
||||
} else {
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
} else {
|
||||
self.niri.layout.focus_right();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
}
|
||||
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWindowDown => {
|
||||
self.niri.layout.focus_down();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
@@ -563,6 +642,30 @@ impl State {
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWindowDownOrColumnLeft => {
|
||||
self.niri.layout.focus_down_or_left();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWindowDownOrColumnRight => {
|
||||
self.niri.layout.focus_down_or_right();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWindowUpOrColumnLeft => {
|
||||
self.niri.layout.focus_up_or_left();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWindowUpOrColumnRight => {
|
||||
self.niri.layout.focus_up_or_right();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
// FIXME: granular
|
||||
self.niri.queue_redraw_all();
|
||||
}
|
||||
Action::FocusWindowOrWorkspaceDown => {
|
||||
self.niri.layout.focus_window_or_workspace_down();
|
||||
self.maybe_warp_cursor_to_focus();
|
||||
@@ -884,7 +987,7 @@ impl State {
|
||||
// Check if we have an active pointer constraint.
|
||||
let mut pointer_confined = None;
|
||||
if let Some(focus) = &self.niri.pointer_focus.surface {
|
||||
let pos_within_surface = pos.to_i32_round() - focus.1;
|
||||
let pos_within_surface = pos - focus.1;
|
||||
|
||||
let mut pointer_locked = false;
|
||||
with_pointer_constraint(&focus.0, &pointer, |constraint| {
|
||||
@@ -895,7 +998,7 @@ impl State {
|
||||
|
||||
// Constraint does not apply if not within region.
|
||||
if let Some(region) = constraint.region() {
|
||||
if !region.contains(pos_within_surface) {
|
||||
if !region.contains(pos_within_surface.to_i32_round()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -959,16 +1062,16 @@ 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;
|
||||
point.x = point
|
||||
.x
|
||||
.clamp(geom.loc.x as f64, (geom.loc.x + geom.size.w - 1) as f64);
|
||||
point.y = point
|
||||
.y
|
||||
.clamp(geom.loc.y as f64, (geom.loc.y + geom.size.h - 1) as f64);
|
||||
let point = (point - geom.loc.to_f64())
|
||||
let mut point = (new_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);
|
||||
|
||||
self.niri.screenshot_ui.pointer_motion(point);
|
||||
}
|
||||
|
||||
@@ -985,8 +1088,8 @@ impl State {
|
||||
|
||||
// Prevent the pointer from leaving the confine region, if any.
|
||||
if let Some(region) = region {
|
||||
let new_pos_within_surface = new_pos.to_i32_round() - focus_surface.1;
|
||||
if !region.contains(new_pos_within_surface) {
|
||||
let new_pos_within_surface = new_pos - focus_surface.1;
|
||||
if !region.contains(new_pos_within_surface.to_i32_round()) {
|
||||
prevent = true;
|
||||
}
|
||||
}
|
||||
@@ -1046,12 +1149,10 @@ impl State {
|
||||
&mut self,
|
||||
event: I::PointerMotionAbsoluteEvent,
|
||||
) {
|
||||
let Some(output) = self.niri.global_space.outputs().next() else {
|
||||
let Some(output_geo) = self.global_bounding_rectangle() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||
|
||||
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
@@ -1060,16 +1161,16 @@ 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;
|
||||
point.x = point
|
||||
.x
|
||||
.clamp(geom.loc.x as f64, (geom.loc.x + geom.size.w - 1) as f64);
|
||||
point.y = point
|
||||
.y
|
||||
.clamp(geom.loc.y as f64, (geom.loc.y + geom.size.h - 1) as f64);
|
||||
let point = (point - geom.loc.to_f64())
|
||||
let mut 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);
|
||||
|
||||
self.niri.screenshot_ui.pointer_motion(point);
|
||||
}
|
||||
|
||||
@@ -1205,15 +1306,13 @@ impl State {
|
||||
};
|
||||
if mod_down {
|
||||
if let Some(output) = self.niri.output_under_cursor() {
|
||||
self.niri.layout.view_offset_gesture_begin(&output, false);
|
||||
|
||||
let location = pointer.current_location();
|
||||
let start_data = PointerGrabStartData {
|
||||
focus: None,
|
||||
button: event.button_code(),
|
||||
location,
|
||||
};
|
||||
let grab = ViewOffsetGrab::new(start_data);
|
||||
let grab = SpatialMovementGrab::new(start_data, output);
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
self.niri.pointer_grab_ongoing = true;
|
||||
self.niri
|
||||
@@ -1231,18 +1330,16 @@ impl State {
|
||||
if let Some((output, _)) = self.niri.output_under(pos) {
|
||||
let output = output.clone();
|
||||
let geom = self.niri.global_space.output_geometry(&output).unwrap();
|
||||
let mut point = pos;
|
||||
// Re-clamp as pointer can be within 0.5 from the limit which will round up
|
||||
// to a wrong value.
|
||||
point.x = point
|
||||
.x
|
||||
.clamp(geom.loc.x as f64, (geom.loc.x + geom.size.w - 1) as f64);
|
||||
point.y = point
|
||||
.y
|
||||
.clamp(geom.loc.y as f64, (geom.loc.y + geom.size.h - 1) as f64);
|
||||
let point = (point - geom.loc.to_f64())
|
||||
let mut 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
|
||||
@@ -1654,7 +1751,9 @@ impl State {
|
||||
if cx.abs() > cy.abs() {
|
||||
self.niri.layout.view_offset_gesture_begin(&output, true);
|
||||
} else {
|
||||
self.niri.layout.workspace_switch_gesture_begin(&output);
|
||||
self.niri
|
||||
.layout
|
||||
.workspace_switch_gesture_begin(&output, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1666,7 +1765,7 @@ impl State {
|
||||
let res = self
|
||||
.niri
|
||||
.layout
|
||||
.workspace_switch_gesture_update(delta_y, timestamp);
|
||||
.workspace_switch_gesture_update(delta_y, timestamp, true);
|
||||
if let Some(output) = res {
|
||||
if let Some(output) = output {
|
||||
self.niri.queue_redraw(&output);
|
||||
@@ -1712,7 +1811,7 @@ impl State {
|
||||
let res = self
|
||||
.niri
|
||||
.layout
|
||||
.workspace_switch_gesture_end(event.cancelled());
|
||||
.workspace_switch_gesture_end(event.cancelled(), Some(true));
|
||||
if let Some(output) = res {
|
||||
self.niri.queue_redraw(&output);
|
||||
handled = true;
|
||||
@@ -2195,11 +2294,19 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
||||
let is_touchpad = device.config_tap_finger_count() > 0;
|
||||
if is_touchpad {
|
||||
let c = &config.touchpad;
|
||||
let _ = device.config_send_events_set_mode(if c.off {
|
||||
input::SendEventsMode::DISABLED
|
||||
} else if c.disabled_on_external_mouse {
|
||||
input::SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE
|
||||
} else {
|
||||
input::SendEventsMode::ENABLED
|
||||
});
|
||||
let _ = device.config_tap_set_enabled(c.tap);
|
||||
let _ = device.config_dwt_set_enabled(c.dwt);
|
||||
let _ = device.config_dwtp_set_enabled(c.dwtp);
|
||||
let _ = device.config_scroll_set_natural_scroll_enabled(c.natural_scroll);
|
||||
let _ = device.config_accel_set_speed(c.accel_speed);
|
||||
let _ = device.config_left_handed_set(c.left_handed);
|
||||
|
||||
if let Some(accel_profile) = c.accel_profile {
|
||||
let _ = device.config_accel_set_profile(accel_profile.into());
|
||||
@@ -2207,6 +2314,12 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
||||
let _ = device.config_accel_set_profile(default);
|
||||
}
|
||||
|
||||
if let Some(method) = c.scroll_method {
|
||||
let _ = device.config_scroll_set_method(method.into());
|
||||
} else if let Some(default) = device.config_scroll_default_method() {
|
||||
let _ = device.config_scroll_set_method(default);
|
||||
}
|
||||
|
||||
if let Some(tap_button_map) = c.tap_button_map {
|
||||
let _ = device.config_tap_set_button_map(tap_button_map.into());
|
||||
} else if let Some(default) = device.config_tap_default_button_map() {
|
||||
@@ -2241,18 +2354,35 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
||||
&& !is_trackpoint;
|
||||
if is_mouse {
|
||||
let c = &config.mouse;
|
||||
let _ = device.config_send_events_set_mode(if c.off {
|
||||
input::SendEventsMode::DISABLED
|
||||
} else {
|
||||
input::SendEventsMode::ENABLED
|
||||
});
|
||||
let _ = device.config_scroll_set_natural_scroll_enabled(c.natural_scroll);
|
||||
let _ = device.config_accel_set_speed(c.accel_speed);
|
||||
let _ = device.config_left_handed_set(c.left_handed);
|
||||
|
||||
if let Some(accel_profile) = c.accel_profile {
|
||||
let _ = device.config_accel_set_profile(accel_profile.into());
|
||||
} else if let Some(default) = device.config_accel_default_profile() {
|
||||
let _ = device.config_accel_set_profile(default);
|
||||
}
|
||||
|
||||
if let Some(method) = c.scroll_method {
|
||||
let _ = device.config_scroll_set_method(method.into());
|
||||
} else if let Some(default) = device.config_scroll_default_method() {
|
||||
let _ = device.config_scroll_set_method(default);
|
||||
}
|
||||
}
|
||||
|
||||
if is_trackpoint {
|
||||
let c = &config.trackpoint;
|
||||
let _ = device.config_send_events_set_mode(if c.off {
|
||||
input::SendEventsMode::DISABLED
|
||||
} else {
|
||||
input::SendEventsMode::ENABLED
|
||||
});
|
||||
let _ = device.config_scroll_set_natural_scroll_enabled(c.natural_scroll);
|
||||
let _ = device.config_accel_set_speed(c.accel_speed);
|
||||
|
||||
@@ -2261,6 +2391,23 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
|
||||
} else if let Some(default) = device.config_accel_default_profile() {
|
||||
let _ = device.config_accel_set_profile(default);
|
||||
}
|
||||
|
||||
if let Some(method) = c.scroll_method {
|
||||
let _ = device.config_scroll_set_method(method.into());
|
||||
} else if let Some(default) = device.config_scroll_default_method() {
|
||||
let _ = device.config_scroll_set_method(default);
|
||||
}
|
||||
}
|
||||
|
||||
let is_tablet = device.has_capability(input::DeviceCapability::TabletTool);
|
||||
if is_tablet {
|
||||
let c = &config.tablet;
|
||||
let _ = device.config_send_events_set_mode(if c.off {
|
||||
input::SendEventsMode::DISABLED
|
||||
} else {
|
||||
input::SendEventsMode::ENABLED
|
||||
});
|
||||
let _ = device.config_left_handed_set(c.left_handed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ impl PointerGrab<State> for ResizeGrab {
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus.
|
||||
@@ -60,7 +60,7 @@ impl PointerGrab<State> for ResizeGrab {
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus.
|
||||
|
||||
@@ -7,28 +7,45 @@ use smithay::input::pointer::{
|
||||
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
|
||||
};
|
||||
use smithay::input::SeatHandler;
|
||||
use smithay::output::Output;
|
||||
use smithay::utils::{Logical, Point};
|
||||
|
||||
use crate::niri::State;
|
||||
|
||||
pub struct ViewOffsetGrab {
|
||||
pub struct SpatialMovementGrab {
|
||||
start_data: PointerGrabStartData<State>,
|
||||
last_location: Point<f64, Logical>,
|
||||
output: Output,
|
||||
gesture: GestureState,
|
||||
}
|
||||
|
||||
impl ViewOffsetGrab {
|
||||
pub fn new(start_data: PointerGrabStartData<State>) -> Self {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum GestureState {
|
||||
Recognizing,
|
||||
ViewOffset,
|
||||
WorkspaceSwitch,
|
||||
}
|
||||
|
||||
impl SpatialMovementGrab {
|
||||
pub fn new(start_data: PointerGrabStartData<State>, output: Output) -> Self {
|
||||
Self {
|
||||
last_location: start_data.location,
|
||||
start_data,
|
||||
output,
|
||||
gesture: GestureState::Recognizing,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ungrab(&mut self, state: &mut State) {
|
||||
let res = state
|
||||
.niri
|
||||
.layout
|
||||
.view_offset_gesture_end(false, Some(false));
|
||||
let layout = &mut state.niri.layout;
|
||||
let res = match self.gesture {
|
||||
GestureState::Recognizing => None,
|
||||
GestureState::ViewOffset => layout.view_offset_gesture_end(false, Some(false)),
|
||||
GestureState::WorkspaceSwitch => {
|
||||
layout.workspace_switch_gesture_end(false, Some(false))
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(output) = res {
|
||||
state.niri.queue_redraw(&output);
|
||||
}
|
||||
@@ -41,12 +58,12 @@ impl ViewOffsetGrab {
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerGrab<State> for ViewOffsetGrab {
|
||||
impl PointerGrab<State> for SpatialMovementGrab {
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus.
|
||||
@@ -56,10 +73,34 @@ impl PointerGrab<State> for ViewOffsetGrab {
|
||||
let delta = event.location - self.last_location;
|
||||
self.last_location = event.location;
|
||||
|
||||
let res = data
|
||||
.niri
|
||||
.layout
|
||||
.view_offset_gesture_update(-delta.x, timestamp, false);
|
||||
let layout = &mut data.niri.layout;
|
||||
let res = match self.gesture {
|
||||
GestureState::Recognizing => {
|
||||
let c = event.location - self.start_data.location;
|
||||
|
||||
// Check if the gesture moved far enough to decide. Threshold copied from GTK 4.
|
||||
if c.x * c.x + c.y * c.y >= 8. * 8. {
|
||||
if c.x.abs() > c.y.abs() {
|
||||
self.gesture = GestureState::ViewOffset;
|
||||
layout.view_offset_gesture_begin(&self.output, false);
|
||||
layout.view_offset_gesture_update(-c.x, timestamp, false)
|
||||
} else {
|
||||
self.gesture = GestureState::WorkspaceSwitch;
|
||||
layout.workspace_switch_gesture_begin(&self.output, false);
|
||||
layout.workspace_switch_gesture_update(-c.y, timestamp, false)
|
||||
}
|
||||
} else {
|
||||
Some(None)
|
||||
}
|
||||
}
|
||||
GestureState::ViewOffset => {
|
||||
layout.view_offset_gesture_update(-delta.x, timestamp, false)
|
||||
}
|
||||
GestureState::WorkspaceSwitch => {
|
||||
layout.workspace_switch_gesture_update(-delta.y, timestamp, false)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(output) = res {
|
||||
if let Some(output) = output {
|
||||
data.niri.queue_redraw(&output);
|
||||
@@ -74,7 +115,7 @@ impl PointerGrab<State> for ViewOffsetGrab {
|
||||
&mut self,
|
||||
data: &mut State,
|
||||
handle: &mut PointerInnerHandle<'_, State>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>,
|
||||
_focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus.
|
||||
+113
-90
@@ -12,6 +12,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
||||
Msg::Version => Request::Version,
|
||||
Msg::Outputs => Request::Outputs,
|
||||
Msg::FocusedWindow => Request::FocusedWindow,
|
||||
Msg::FocusedOutput => Request::FocusedOutput,
|
||||
Msg::Action { action } => Request::Action(action.clone()),
|
||||
Msg::Output { output, action } => Request::Output {
|
||||
output: output.clone(),
|
||||
@@ -117,96 +118,7 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
||||
outputs.sort_unstable_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
for (connector, output) in outputs.into_iter() {
|
||||
let Output {
|
||||
name,
|
||||
make,
|
||||
model,
|
||||
physical_size,
|
||||
modes,
|
||||
current_mode,
|
||||
vrr_supported,
|
||||
vrr_enabled,
|
||||
logical,
|
||||
} = output;
|
||||
|
||||
println!(r#"Output "{connector}" ({make} - {model} - {name})"#);
|
||||
|
||||
if let Some(current) = current_mode {
|
||||
let mode = *modes
|
||||
.get(current)
|
||||
.context("invalid response: current mode does not exist")?;
|
||||
let Mode {
|
||||
width,
|
||||
height,
|
||||
refresh_rate,
|
||||
is_preferred,
|
||||
} = mode;
|
||||
let refresh = refresh_rate as f64 / 1000.;
|
||||
let preferred = if is_preferred { " (preferred)" } else { "" };
|
||||
println!(" Current mode: {width}x{height} @ {refresh:.3} Hz{preferred}");
|
||||
} else {
|
||||
println!(" Disabled");
|
||||
}
|
||||
|
||||
if vrr_supported {
|
||||
let enabled = if vrr_enabled { "enabled" } else { "disabled" };
|
||||
println!(" Variable refresh rate: supported, {enabled}");
|
||||
} else {
|
||||
println!(" Variable refresh rate: not supported");
|
||||
}
|
||||
|
||||
if let Some((width, height)) = physical_size {
|
||||
println!(" Physical size: {width}x{height} mm");
|
||||
} else {
|
||||
println!(" Physical size: unknown");
|
||||
}
|
||||
|
||||
if let Some(logical) = logical {
|
||||
let LogicalOutput {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
scale,
|
||||
transform,
|
||||
} = logical;
|
||||
println!(" Logical position: {x}, {y}");
|
||||
println!(" Logical size: {width}x{height}");
|
||||
println!(" Scale: {scale}");
|
||||
|
||||
let transform = match transform {
|
||||
Transform::Normal => "normal",
|
||||
Transform::_90 => "90° counter-clockwise",
|
||||
Transform::_180 => "180°",
|
||||
Transform::_270 => "270° counter-clockwise",
|
||||
Transform::Flipped => "flipped horizontally",
|
||||
Transform::Flipped90 => "90° counter-clockwise, flipped horizontally",
|
||||
Transform::Flipped180 => "flipped vertically",
|
||||
Transform::Flipped270 => "270° counter-clockwise, flipped horizontally",
|
||||
};
|
||||
println!(" Transform: {transform}");
|
||||
}
|
||||
|
||||
println!(" Available modes:");
|
||||
for (idx, mode) in modes.into_iter().enumerate() {
|
||||
let Mode {
|
||||
width,
|
||||
height,
|
||||
refresh_rate,
|
||||
is_preferred,
|
||||
} = mode;
|
||||
let refresh = refresh_rate as f64 / 1000.;
|
||||
|
||||
let is_current = Some(idx) == current_mode;
|
||||
let qualifier = match (is_current, is_preferred) {
|
||||
(true, true) => " (current, preferred)",
|
||||
(true, false) => " (current)",
|
||||
(false, true) => " (preferred)",
|
||||
(false, false) => "",
|
||||
};
|
||||
|
||||
println!(" {width}x{height}@{refresh:.3}{qualifier}");
|
||||
}
|
||||
print_output(connector, output)?;
|
||||
println!();
|
||||
}
|
||||
}
|
||||
@@ -239,6 +151,23 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
||||
println!("No window is focused.");
|
||||
}
|
||||
}
|
||||
Msg::FocusedOutput => {
|
||||
let Response::FocusedOutput(output) = response else {
|
||||
bail!("unexpected response: expected FocusedOutput, got {response:?}");
|
||||
};
|
||||
|
||||
if json {
|
||||
let output = serde_json::to_string(&output).context("error formatting response")?;
|
||||
println!("{output}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(output) = output {
|
||||
print_output(output.name.clone(), output)?;
|
||||
} else {
|
||||
println!("No output is focused.");
|
||||
}
|
||||
}
|
||||
Msg::Action { .. } => {
|
||||
let Response::Handled = response else {
|
||||
bail!("unexpected response: expected Handled, got {response:?}");
|
||||
@@ -313,3 +242,97 @@ pub fn handle_msg(msg: Msg, json: bool) -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_output(connector: String, output: Output) -> anyhow::Result<()> {
|
||||
let Output {
|
||||
name,
|
||||
make,
|
||||
model,
|
||||
physical_size,
|
||||
modes,
|
||||
current_mode,
|
||||
vrr_supported,
|
||||
vrr_enabled,
|
||||
logical,
|
||||
} = output;
|
||||
|
||||
println!(r#"Output "{connector}" ({make} - {model} - {name})"#);
|
||||
|
||||
if let Some(current) = current_mode {
|
||||
let mode = *modes
|
||||
.get(current)
|
||||
.context("invalid response: current mode does not exist")?;
|
||||
let Mode {
|
||||
width,
|
||||
height,
|
||||
refresh_rate,
|
||||
is_preferred,
|
||||
} = mode;
|
||||
let refresh = refresh_rate as f64 / 1000.;
|
||||
let preferred = if is_preferred { " (preferred)" } else { "" };
|
||||
println!(" Current mode: {width}x{height} @ {refresh:.3} Hz{preferred}");
|
||||
} else {
|
||||
println!(" Disabled");
|
||||
}
|
||||
|
||||
if vrr_supported {
|
||||
let enabled = if vrr_enabled { "enabled" } else { "disabled" };
|
||||
println!(" Variable refresh rate: supported, {enabled}");
|
||||
} else {
|
||||
println!(" Variable refresh rate: not supported");
|
||||
}
|
||||
|
||||
if let Some((width, height)) = physical_size {
|
||||
println!(" Physical size: {width}x{height} mm");
|
||||
} else {
|
||||
println!(" Physical size: unknown");
|
||||
}
|
||||
|
||||
if let Some(logical) = logical {
|
||||
let LogicalOutput {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
scale,
|
||||
transform,
|
||||
} = logical;
|
||||
println!(" Logical position: {x}, {y}");
|
||||
println!(" Logical size: {width}x{height}");
|
||||
println!(" Scale: {scale}");
|
||||
|
||||
let transform = match transform {
|
||||
Transform::Normal => "normal",
|
||||
Transform::_90 => "90° counter-clockwise",
|
||||
Transform::_180 => "180°",
|
||||
Transform::_270 => "270° counter-clockwise",
|
||||
Transform::Flipped => "flipped horizontally",
|
||||
Transform::Flipped90 => "90° counter-clockwise, flipped horizontally",
|
||||
Transform::Flipped180 => "flipped vertically",
|
||||
Transform::Flipped270 => "270° counter-clockwise, flipped horizontally",
|
||||
};
|
||||
println!(" Transform: {transform}");
|
||||
}
|
||||
|
||||
println!(" Available modes:");
|
||||
for (idx, mode) in modes.into_iter().enumerate() {
|
||||
let Mode {
|
||||
width,
|
||||
height,
|
||||
refresh_rate,
|
||||
is_preferred,
|
||||
} = mode;
|
||||
let refresh = refresh_rate as f64 / 1000.;
|
||||
|
||||
let is_current = Some(idx) == current_mode;
|
||||
let qualifier = match (is_current, is_preferred) {
|
||||
(true, true) => " (current, preferred)",
|
||||
(true, false) => " (current)",
|
||||
(false, true) => " (preferred)",
|
||||
(false, false) => "",
|
||||
};
|
||||
|
||||
println!(" {width}x{height}@{refresh:.3}{qualifier}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -208,6 +208,31 @@ async fn process(ctx: &ClientCtx, request: Request) -> Reply {
|
||||
let workspaces = result.map_err(|_| String::from("error getting workspace info"))?;
|
||||
Response::Workspaces(workspaces)
|
||||
}
|
||||
Request::FocusedOutput => {
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
ctx.event_loop.insert_idle(move |state| {
|
||||
let active_output = state
|
||||
.niri
|
||||
.layout
|
||||
.active_output()
|
||||
.map(|output| output.name());
|
||||
|
||||
let output = active_output.and_then(|active_output| {
|
||||
state
|
||||
.backend
|
||||
.ipc_outputs()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&active_output)
|
||||
.cloned()
|
||||
});
|
||||
|
||||
let _ = tx.send_blocking(output);
|
||||
});
|
||||
let result = rx.recv().await;
|
||||
let output = result.map_err(|_| String::from("error getting active output info"))?;
|
||||
Response::FocusedOutput(output)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
|
||||
@@ -5,13 +5,12 @@ use anyhow::Context as _;
|
||||
use glam::{Mat3, Vec2};
|
||||
use niri_config::BlockOutFrom;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::texture::TextureRenderElement;
|
||||
use smithay::backend::renderer::element::utils::{
|
||||
Relocate, RelocateRenderElement, RescaleRenderElement,
|
||||
};
|
||||
use smithay::backend::renderer::element::{Id, Kind, RenderElement};
|
||||
use smithay::backend::renderer::element::{Kind, RenderElement};
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture, Uniform};
|
||||
use smithay::backend::renderer::{Renderer as _, Texture};
|
||||
use smithay::backend::renderer::Texture;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use crate::animation::Animation;
|
||||
@@ -20,36 +19,31 @@ use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::shader_element::ShaderRenderElement;
|
||||
use crate::render_helpers::shaders::{mat3_uniform, ProgramType, Shaders};
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::render_helpers::{render_to_encompassing_texture, RenderTarget};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClosingWindow {
|
||||
/// Contents of the window.
|
||||
texture: GlesTexture,
|
||||
buffer: TextureBuffer<GlesTexture>,
|
||||
|
||||
/// Blocked-out contents of the window.
|
||||
blocked_out_texture: GlesTexture,
|
||||
|
||||
/// Scale that the textures was rendered with.
|
||||
texture_scale: Scale<f64>,
|
||||
|
||||
/// ID of the textures' renderer.
|
||||
texture_renderer_id: usize,
|
||||
blocked_out_buffer: TextureBuffer<GlesTexture>,
|
||||
|
||||
/// Where the window should be blocked out from.
|
||||
block_out_from: Option<BlockOutFrom>,
|
||||
|
||||
/// Size of the window geometry.
|
||||
geo_size: Size<i32, Logical>,
|
||||
geo_size: Size<f64, Logical>,
|
||||
|
||||
/// Position in the workspace.
|
||||
pos: Point<i32, Logical>,
|
||||
pos: Point<f64, Logical>,
|
||||
|
||||
/// How much the texture should be offset.
|
||||
texture_offset: Point<f64, Logical>,
|
||||
buffer_offset: Point<f64, Logical>,
|
||||
|
||||
/// How much the blocked-out texture should be offset.
|
||||
blocked_out_texture_offset: Point<f64, Logical>,
|
||||
blocked_out_buffer_offset: Point<f64, Logical>,
|
||||
|
||||
/// The closing animation.
|
||||
anim: Animation,
|
||||
@@ -70,8 +64,8 @@ impl ClosingWindow {
|
||||
renderer: &mut GlesRenderer,
|
||||
snapshot: RenderSnapshot<E, E>,
|
||||
scale: Scale<f64>,
|
||||
geo_size: Size<i32, Logical>,
|
||||
pos: Point<i32, Logical>,
|
||||
geo_size: Size<f64, Logical>,
|
||||
pos: Point<f64, Logical>,
|
||||
anim: Animation,
|
||||
) -> anyhow::Result<Self> {
|
||||
let _span = tracy_client::span!("ClosingWindow::new");
|
||||
@@ -86,27 +80,33 @@ impl ClosingWindow {
|
||||
)
|
||||
.context("error rendering to texture")?;
|
||||
|
||||
let buffer = TextureBuffer::from_texture(
|
||||
renderer,
|
||||
texture,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let offset = geo.loc.to_f64().to_logical(scale);
|
||||
|
||||
Ok((texture, offset))
|
||||
Ok((buffer, offset))
|
||||
};
|
||||
|
||||
let (texture, texture_offset) =
|
||||
let (buffer, buffer_offset) =
|
||||
render_to_texture(snapshot.contents).context("error rendering contents")?;
|
||||
let (blocked_out_texture, blocked_out_texture_offset) =
|
||||
let (blocked_out_buffer, blocked_out_buffer_offset) =
|
||||
render_to_texture(snapshot.blocked_out_contents)
|
||||
.context("error rendering blocked-out contents")?;
|
||||
|
||||
Ok(Self {
|
||||
texture,
|
||||
blocked_out_texture,
|
||||
texture_scale: scale,
|
||||
texture_renderer_id: renderer.id(),
|
||||
buffer,
|
||||
blocked_out_buffer,
|
||||
block_out_from: snapshot.block_out_from,
|
||||
geo_size,
|
||||
pos,
|
||||
texture_offset,
|
||||
blocked_out_texture_offset,
|
||||
buffer_offset,
|
||||
blocked_out_buffer_offset,
|
||||
anim,
|
||||
random_seed: fastrand::f32(),
|
||||
})
|
||||
@@ -123,32 +123,39 @@ impl ClosingWindow {
|
||||
pub fn render(
|
||||
&self,
|
||||
renderer: &mut GlesRenderer,
|
||||
view_rect: Rectangle<i32, Logical>,
|
||||
view_rect: Rectangle<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
target: RenderTarget,
|
||||
) -> ClosingWindowRenderElement {
|
||||
let progress = self.anim.value();
|
||||
let clamped_progress = self.anim.clamped_value().clamp(0., 1.);
|
||||
|
||||
let (texture, offset) = if target.should_block_out(self.block_out_from) {
|
||||
(&self.blocked_out_texture, self.blocked_out_texture_offset)
|
||||
let (buffer, offset) = if target.should_block_out(self.block_out_from) {
|
||||
(&self.blocked_out_buffer, self.blocked_out_buffer_offset)
|
||||
} else {
|
||||
(&self.texture, self.texture_offset)
|
||||
(&self.buffer, self.buffer_offset)
|
||||
};
|
||||
|
||||
if Shaders::get(renderer).program(ProgramType::Close).is_some() {
|
||||
let area_loc = Vec2::new(view_rect.loc.x as f32, view_rect.loc.y as f32);
|
||||
let area_size = Vec2::new(view_rect.size.w as f32, view_rect.size.h as f32);
|
||||
|
||||
let geo_loc = Vec2::new(self.pos.x as f32, self.pos.y as f32);
|
||||
// Round to physical pixels relative to the view position. This is similar to what
|
||||
// happens when rendering normal windows.
|
||||
let relative = self.pos - view_rect.loc;
|
||||
let pos = view_rect.loc + relative.to_physical_precise_round(scale).to_logical(scale);
|
||||
|
||||
let geo_loc = Vec2::new(pos.x as f32, pos.y as f32);
|
||||
let geo_size = Vec2::new(self.geo_size.w as f32, self.geo_size.h as f32);
|
||||
|
||||
let input_to_geo = Mat3::from_scale(area_size / geo_size)
|
||||
* Mat3::from_translation((area_loc - geo_loc) / area_size);
|
||||
|
||||
let tex_scale = Vec2::new(self.texture_scale.x as f32, self.texture_scale.y as f32);
|
||||
let tex_scale = self.buffer.texture_scale();
|
||||
let tex_scale = Vec2::new(tex_scale.x as f32, tex_scale.y as f32);
|
||||
let tex_loc = Vec2::new(offset.x as f32, offset.y as f32);
|
||||
let tex_size = Vec2::new(texture.width() as f32, texture.height() as f32) / tex_scale;
|
||||
let tex_size = self.buffer.texture().size();
|
||||
let tex_size = Vec2::new(tex_size.w as f32, tex_size.h as f32) / tex_scale;
|
||||
|
||||
let geo_to_tex =
|
||||
Mat3::from_translation(-tex_loc / tex_size) * Mat3::from_scale(geo_size / tex_size);
|
||||
@@ -157,6 +164,7 @@ impl ClosingWindow {
|
||||
ProgramType::Close,
|
||||
view_rect.size,
|
||||
None,
|
||||
scale.x as f32,
|
||||
1.,
|
||||
vec![
|
||||
mat3_uniform("niri_input_to_geo", input_to_geo),
|
||||
@@ -166,22 +174,17 @@ impl ClosingWindow {
|
||||
Uniform::new("niri_clamped_progress", clamped_progress as f32),
|
||||
Uniform::new("niri_random_seed", self.random_seed),
|
||||
],
|
||||
HashMap::from([(String::from("niri_tex"), texture.clone())]),
|
||||
HashMap::from([(String::from("niri_tex"), buffer.texture().clone())]),
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.with_location(Point::from((0, 0)))
|
||||
.with_location(Point::from((0., 0.)))
|
||||
.into();
|
||||
}
|
||||
|
||||
let elem = TextureRenderElement::from_static_texture(
|
||||
Id::new(),
|
||||
self.texture_renderer_id,
|
||||
let elem = TextureRenderElement::from_texture_buffer(
|
||||
buffer.clone(),
|
||||
Point::from((0., 0.)),
|
||||
texture.clone(),
|
||||
self.texture_scale.x as i32,
|
||||
Transform::Normal,
|
||||
Some(1. - clamped_progress as f32),
|
||||
None,
|
||||
1. - clamped_progress as f32,
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
@@ -189,15 +192,15 @@ impl ClosingWindow {
|
||||
|
||||
let elem = PrimaryGpuTextureRenderElement(elem);
|
||||
|
||||
let center = self.geo_size.to_point().to_f64().downscale(2.);
|
||||
let center = self.geo_size.to_point().downscale(2.);
|
||||
let elem = RescaleRenderElement::from_element(
|
||||
elem,
|
||||
(center - offset).to_physical_precise_round(scale),
|
||||
((1. - clamped_progress) / 5. + 0.8).max(0.),
|
||||
);
|
||||
|
||||
let mut location = self.pos.to_f64() + offset;
|
||||
location.x -= view_rect.loc.x as f64;
|
||||
let mut location = self.pos + offset;
|
||||
location.x -= view_rect.loc.x;
|
||||
let elem = RelocateRenderElement::from_element(
|
||||
elem,
|
||||
location.to_physical_precise_round(scale),
|
||||
|
||||
+39
-36
@@ -1,23 +1,22 @@
|
||||
use std::cmp::{max, min};
|
||||
use std::iter::zip;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use niri_config::{CornerRadius, Gradient, GradientRelativeTo};
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size};
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size};
|
||||
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::border::BorderRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FocusRing {
|
||||
buffers: [SolidColorBuffer; 8],
|
||||
locations: [Point<i32, Logical>; 8],
|
||||
sizes: [Size<i32, Logical>; 8],
|
||||
locations: [Point<f64, Logical>; 8],
|
||||
sizes: [Size<f64, Logical>; 8],
|
||||
borders: [BorderRenderElement; 8],
|
||||
full_size: Size<i32, Logical>,
|
||||
full_size: Size<f64, Logical>,
|
||||
is_border: bool,
|
||||
use_border_shader: bool,
|
||||
config: niri_config::FocusRing,
|
||||
@@ -56,14 +55,15 @@ impl FocusRing {
|
||||
|
||||
pub fn update_render_elements(
|
||||
&mut self,
|
||||
win_size: Size<i32, Logical>,
|
||||
win_size: Size<f64, Logical>,
|
||||
is_active: bool,
|
||||
is_border: bool,
|
||||
view_rect: Rectangle<i32, Logical>,
|
||||
view_rect: Rectangle<f64, Logical>,
|
||||
radius: CornerRadius,
|
||||
scale: f64,
|
||||
) {
|
||||
let width = i32::from(self.config.width);
|
||||
self.full_size = win_size + Size::from((width * 2, width * 2));
|
||||
let width = self.config.width.0;
|
||||
self.full_size = win_size + Size::from((width, width)).upscale(2.);
|
||||
|
||||
let color = if is_active {
|
||||
self.config.active_color
|
||||
@@ -107,39 +107,48 @@ impl FocusRing {
|
||||
0.
|
||||
};
|
||||
|
||||
let ceil = |logical: f64| (logical * scale).ceil() / scale;
|
||||
|
||||
// All of this stuff should end up aligned to physical pixels because:
|
||||
// * Window size and border width are rounded to physical pixels before being passed to this
|
||||
// function.
|
||||
// * We will ceil the corner radii below.
|
||||
// * We do not divide anything, only add, subtract and multiply by integers.
|
||||
// * At rendering time, tile positions are rounded to physical pixels.
|
||||
|
||||
if is_border {
|
||||
let top_left = max(width, radius.top_left.ceil() as i32);
|
||||
let top_right = min(
|
||||
let top_left = f64::max(width, ceil(f64::from(radius.top_left)));
|
||||
let top_right = f64::min(
|
||||
self.full_size.w - top_left,
|
||||
max(width, radius.top_right.ceil() as i32),
|
||||
f64::max(width, ceil(f64::from(radius.top_right))),
|
||||
);
|
||||
let bottom_left = min(
|
||||
let bottom_left = f64::min(
|
||||
self.full_size.h - top_left,
|
||||
max(width, radius.bottom_left.ceil() as i32),
|
||||
f64::max(width, ceil(f64::from(radius.bottom_left))),
|
||||
);
|
||||
let bottom_right = min(
|
||||
let bottom_right = f64::min(
|
||||
self.full_size.h - top_right,
|
||||
min(
|
||||
f64::min(
|
||||
self.full_size.w - bottom_left,
|
||||
max(width, radius.bottom_right.ceil() as i32),
|
||||
f64::max(width, ceil(f64::from(radius.bottom_right))),
|
||||
),
|
||||
);
|
||||
|
||||
// Top edge.
|
||||
self.sizes[0] = Size::from((win_size.w + width * 2 - top_left - top_right, width));
|
||||
self.sizes[0] = Size::from((win_size.w + width * 2. - top_left - top_right, width));
|
||||
self.locations[0] = Point::from((-width + top_left, -width));
|
||||
|
||||
// Bottom edge.
|
||||
self.sizes[1] =
|
||||
Size::from((win_size.w + width * 2 - bottom_left - bottom_right, width));
|
||||
Size::from((win_size.w + width * 2. - bottom_left - bottom_right, width));
|
||||
self.locations[1] = Point::from((-width + bottom_left, win_size.h));
|
||||
|
||||
// Left edge.
|
||||
self.sizes[2] = Size::from((width, win_size.h + width * 2 - top_left - bottom_left));
|
||||
self.sizes[2] = Size::from((width, win_size.h + width * 2. - top_left - bottom_left));
|
||||
self.locations[2] = Point::from((-width, -width + top_left));
|
||||
|
||||
// Right edge.
|
||||
self.sizes[3] = Size::from((width, win_size.h + width * 2 - top_right - bottom_right));
|
||||
self.sizes[3] = Size::from((width, win_size.h + width * 2. - top_right - bottom_right));
|
||||
self.locations[3] = Point::from((win_size.w, -width + top_right));
|
||||
|
||||
// Top-left corner.
|
||||
@@ -175,6 +184,7 @@ impl FocusRing {
|
||||
Rectangle::from_loc_and_size(full_rect.loc - loc, full_rect.size),
|
||||
rounded_corner_border_width,
|
||||
radius,
|
||||
scale as f32,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -194,6 +204,7 @@ impl FocusRing {
|
||||
Rectangle::from_loc_and_size(full_rect.loc - self.locations[0], full_rect.size),
|
||||
rounded_corner_border_width,
|
||||
radius,
|
||||
scale as f32,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -203,8 +214,7 @@ impl FocusRing {
|
||||
pub fn render(
|
||||
&self,
|
||||
renderer: &mut impl NiriRenderer,
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
location: Point<f64, Logical>,
|
||||
) -> impl Iterator<Item = FocusRingRenderElement> {
|
||||
let mut rv = ArrayVec::<_, 8>::new();
|
||||
|
||||
@@ -215,24 +225,17 @@ impl FocusRing {
|
||||
let border_width = -self.locations[0].y;
|
||||
|
||||
// If drawing as a border with width = 0, then there's nothing to draw.
|
||||
if self.is_border && border_width == 0 {
|
||||
if self.is_border && border_width == 0. {
|
||||
return rv.into_iter();
|
||||
}
|
||||
|
||||
let has_border_shader = BorderRenderElement::has_shader(renderer);
|
||||
|
||||
let mut push = |buffer, border: &BorderRenderElement, location: Point<i32, Logical>| {
|
||||
let mut push = |buffer, border: &BorderRenderElement, location: Point<f64, Logical>| {
|
||||
let elem = if self.use_border_shader && has_border_shader {
|
||||
border.clone().with_location(location).into()
|
||||
} else {
|
||||
SolidColorRenderElement::from_buffer(
|
||||
buffer,
|
||||
location.to_physical_precise_round(scale),
|
||||
scale,
|
||||
1.,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into()
|
||||
SolidColorRenderElement::from_buffer(buffer, location, 1., Kind::Unspecified).into()
|
||||
};
|
||||
rv.push(elem);
|
||||
};
|
||||
@@ -252,8 +255,8 @@ impl FocusRing {
|
||||
rv.into_iter()
|
||||
}
|
||||
|
||||
pub fn width(&self) -> i32 {
|
||||
self.config.width.into()
|
||||
pub fn width(&self) -> f64 {
|
||||
self.config.width.0
|
||||
}
|
||||
|
||||
pub fn is_off(&self) -> bool {
|
||||
|
||||
+430
-61
@@ -34,25 +34,25 @@ use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri_config::{CenterFocusedColumn, Config, Struts, Workspace as WorkspaceConfig};
|
||||
use niri_config::{CenterFocusedColumn, Config, FloatOrInt, Struts, Workspace as WorkspaceConfig};
|
||||
use niri_ipc::SizeChange;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
|
||||
use smithay::backend::renderer::element::texture::TextureBuffer;
|
||||
use smithay::backend::renderer::element::Id;
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::output::Output;
|
||||
use smithay::output::{self, Output};
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point, Scale, Serial, Size, Transform};
|
||||
|
||||
use self::monitor::Monitor;
|
||||
pub use self::monitor::MonitorRenderElement;
|
||||
use self::monitor::{Monitor, WorkspaceSwitch};
|
||||
use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Workspace};
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::texture::TextureBuffer;
|
||||
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
|
||||
use crate::utils::{output_size, ResizeEdge};
|
||||
use crate::utils::{output_size, round_logical_in_physical_max1, ResizeEdge};
|
||||
use crate::window::ResolvedWindowRules;
|
||||
|
||||
pub mod closing_window;
|
||||
@@ -63,7 +63,7 @@ pub mod tile;
|
||||
pub mod workspace;
|
||||
|
||||
/// Size changes up to this many pixels don't animate.
|
||||
pub const RESIZE_ANIMATION_THRESHOLD: i32 = 10;
|
||||
pub const RESIZE_ANIMATION_THRESHOLD: f64 = 10.;
|
||||
|
||||
niri_render_elements! {
|
||||
LayoutElementRenderElement<R> => {
|
||||
@@ -110,7 +110,7 @@ pub trait LayoutElement {
|
||||
fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
target: RenderTarget,
|
||||
@@ -120,7 +120,7 @@ pub trait LayoutElement {
|
||||
fn render_normal<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
target: RenderTarget,
|
||||
@@ -132,7 +132,7 @@ pub trait LayoutElement {
|
||||
fn render_popups<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
target: RenderTarget,
|
||||
@@ -146,7 +146,7 @@ pub trait LayoutElement {
|
||||
fn max_size(&self) -> Size<i32, Logical>;
|
||||
fn is_wl_surface(&self, wl_surface: &WlSurface) -> bool;
|
||||
fn has_ssd(&self) -> bool;
|
||||
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform);
|
||||
fn set_preferred_scale_transform(&self, scale: output::Scale, transform: Transform);
|
||||
fn output_enter(&self, output: &Output);
|
||||
fn output_leave(&self, output: &Output);
|
||||
fn set_offscreen_element_id(&self, id: Option<Id>);
|
||||
@@ -206,10 +206,10 @@ enum MonitorSet<W: LayoutElement> {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Options {
|
||||
/// Padding around windows in logical pixels.
|
||||
pub gaps: i32,
|
||||
pub gaps: f64,
|
||||
/// Extra padding around the working area in logical pixels.
|
||||
pub struts: Struts,
|
||||
pub focus_ring: niri_config::FocusRing,
|
||||
@@ -225,7 +225,7 @@ pub struct Options {
|
||||
impl Default for Options {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gaps: 16,
|
||||
gaps: 16.,
|
||||
struts: Default::default(),
|
||||
focus_ring: Default::default(),
|
||||
border: Default::default(),
|
||||
@@ -265,7 +265,7 @@ impl Options {
|
||||
.unwrap_or(Some(ColumnWidth::Proportion(0.5)));
|
||||
|
||||
Self {
|
||||
gaps: layout.gaps.into(),
|
||||
gaps: layout.gaps.0,
|
||||
struts: layout.struts,
|
||||
focus_ring: layout.focus_ring,
|
||||
border: layout.border,
|
||||
@@ -275,6 +275,16 @@ impl Options {
|
||||
animations: config.animations.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn adjusted_for_scale(mut self, scale: f64) -> Self {
|
||||
let round = |logical: f64| round_logical_in_physical_max1(scale, logical);
|
||||
|
||||
self.gaps = round(self.gaps);
|
||||
self.focus_ring.width = FloatOrInt(round(self.focus_ring.width.0));
|
||||
self.border.width = FloatOrInt(round(self.border.width.0));
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: LayoutElement> Layout<W> {
|
||||
@@ -403,7 +413,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
|
||||
// Get rid of empty workspaces.
|
||||
workspaces.retain(|ws| ws.has_windows());
|
||||
workspaces.retain(|ws| ws.has_windows() || ws.name.is_some());
|
||||
|
||||
if monitors.is_empty() {
|
||||
// Removed the last monitor.
|
||||
@@ -486,12 +496,12 @@ impl<W: LayoutElement> Layout<W> {
|
||||
width: Option<ColumnWidth>,
|
||||
is_full_width: bool,
|
||||
) -> Option<&Output> {
|
||||
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
||||
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w)));
|
||||
if let ColumnWidth::Fixed(w) = &mut width {
|
||||
let rules = window.rules();
|
||||
let border_config = rules.border.resolve_against(self.options.border);
|
||||
if !border_config.off {
|
||||
*w += border_config.width as i32 * 2;
|
||||
*w += border_config.width.0 * 2.;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,12 +585,12 @@ impl<W: LayoutElement> Layout<W> {
|
||||
width: Option<ColumnWidth>,
|
||||
is_full_width: bool,
|
||||
) -> Option<&Output> {
|
||||
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
||||
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w)));
|
||||
if let ColumnWidth::Fixed(w) = &mut width {
|
||||
let rules = window.rules();
|
||||
let border_config = rules.border.resolve_against(self.options.border);
|
||||
if !border_config.off {
|
||||
*w += border_config.width as i32 * 2;
|
||||
*w += border_config.width.0 * 2.;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,12 +643,12 @@ impl<W: LayoutElement> Layout<W> {
|
||||
width: Option<ColumnWidth>,
|
||||
is_full_width: bool,
|
||||
) -> Option<&Output> {
|
||||
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
||||
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w)));
|
||||
if let ColumnWidth::Fixed(w) = &mut width {
|
||||
let rules = window.rules();
|
||||
let border_config = rules.border.resolve_against(self.options.border);
|
||||
if !border_config.off {
|
||||
*w += border_config.width as i32 * 2;
|
||||
*w += border_config.width.0 * 2.;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,12 +681,12 @@ impl<W: LayoutElement> Layout<W> {
|
||||
width: Option<ColumnWidth>,
|
||||
is_full_width: bool,
|
||||
) {
|
||||
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w));
|
||||
let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w)));
|
||||
if let ColumnWidth::Fixed(w) = &mut width {
|
||||
let rules = window.rules();
|
||||
let border_config = rules.border.resolve_against(self.options.border);
|
||||
if !border_config.off {
|
||||
*w += border_config.width as i32 * 2;
|
||||
*w += border_config.width.0 * 2.;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,7 +897,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn window_loc(&self, window: &W::Id) -> Option<Point<i32, Logical>> {
|
||||
pub fn window_loc(&self, window: &W::Id) -> Option<Point<f64, Logical>> {
|
||||
match &self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => {
|
||||
for mon in monitors {
|
||||
@@ -923,12 +933,13 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
for mon in monitors {
|
||||
if &mon.output == output {
|
||||
let scale = output.current_scale();
|
||||
let transform = output.current_transform();
|
||||
let view_size = output_size(output);
|
||||
let working_area = compute_working_area(output, self.options.struts);
|
||||
|
||||
for ws in &mut mon.workspaces {
|
||||
ws.set_view_size(view_size, working_area);
|
||||
ws.update_output_scale_transform();
|
||||
ws.set_view_size(scale, transform, view_size, working_area);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -952,9 +963,13 @@ impl<W: LayoutElement> Layout<W> {
|
||||
*active_monitor_idx = monitor_idx;
|
||||
ws.activate_window(window);
|
||||
|
||||
// Switch to that workspace if not already during a transition.
|
||||
if mon.workspace_switch.is_none() {
|
||||
mon.switch_workspace(workspace_idx);
|
||||
// If currently in the middle of a vertical swipe between the target workspace
|
||||
// and some other, don't switch the workspace.
|
||||
match &mon.workspace_switch {
|
||||
Some(WorkspaceSwitch::Gesture(gesture))
|
||||
if gesture.current_idx.floor() == workspace_idx as f64
|
||||
|| gesture.current_idx.ceil() == workspace_idx as f64 => {}
|
||||
_ => mon.switch_workspace(workspace_idx, true),
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -1235,6 +1250,51 @@ impl<W: LayoutElement> Layout<W> {
|
||||
monitor.focus_column_last();
|
||||
}
|
||||
|
||||
pub fn focus_column_right_or_first(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.focus_column_right_or_first();
|
||||
}
|
||||
|
||||
pub fn focus_column_left_or_last(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.focus_column_left_or_last();
|
||||
}
|
||||
|
||||
pub fn focus_column_left_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
let curr_idx = workspace.active_column_idx;
|
||||
|
||||
if !workspace.columns.is_empty() && curr_idx != 0 {
|
||||
monitor.focus_left();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self.focus_output(output);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn focus_column_right_or_output(&mut self, output: &Output) -> bool {
|
||||
if let Some(monitor) = self.active_monitor() {
|
||||
let workspace = monitor.active_workspace();
|
||||
let curr_idx = workspace.active_column_idx;
|
||||
let columns = &workspace.columns;
|
||||
|
||||
if !workspace.columns.is_empty() && curr_idx != columns.len() - 1 {
|
||||
monitor.focus_right();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self.focus_output(output);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn focus_down(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
@@ -1249,6 +1309,34 @@ impl<W: LayoutElement> Layout<W> {
|
||||
monitor.focus_up();
|
||||
}
|
||||
|
||||
pub fn focus_down_or_left(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.focus_down_or_left();
|
||||
}
|
||||
|
||||
pub fn focus_down_or_right(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.focus_down_or_right();
|
||||
}
|
||||
|
||||
pub fn focus_up_or_left(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.focus_up_or_left();
|
||||
}
|
||||
|
||||
pub fn focus_up_or_right(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.focus_up_or_right();
|
||||
}
|
||||
|
||||
pub fn focus_window_or_workspace_down(&mut self) {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
@@ -1335,7 +1423,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
let Some(monitor) = self.active_monitor() else {
|
||||
return;
|
||||
};
|
||||
monitor.switch_workspace(idx);
|
||||
monitor.switch_workspace(idx, false);
|
||||
}
|
||||
|
||||
pub fn switch_workspace_auto_back_and_forth(&mut self, idx: usize) {
|
||||
@@ -1397,7 +1485,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
&self,
|
||||
output: &Output,
|
||||
pos_within_output: Point<f64, Logical>,
|
||||
) -> Option<(&W, Option<Point<i32, Logical>>)> {
|
||||
) -> Option<(&W, Option<Point<f64, Logical>>)> {
|
||||
let MonitorSet::Normal { monitors, .. } = &self.monitor_set else {
|
||||
return None;
|
||||
};
|
||||
@@ -1442,8 +1530,15 @@ impl<W: LayoutElement> Layout<W> {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
workspace.options, self.options,
|
||||
"workspace options must be synchronized with layout"
|
||||
workspace.base_options, self.options,
|
||||
"workspace base options must be synchronized with layout"
|
||||
);
|
||||
|
||||
let options = Options::clone(&workspace.base_options)
|
||||
.adjusted_for_scale(workspace.scale().fractional_scale());
|
||||
assert_eq!(
|
||||
&*workspace.options, &options,
|
||||
"workspace options must be base options adjusted for workspace scale"
|
||||
);
|
||||
|
||||
assert!(
|
||||
@@ -1546,10 +1641,17 @@ impl<W: LayoutElement> Layout<W> {
|
||||
|
||||
for workspace in &monitor.workspaces {
|
||||
assert_eq!(
|
||||
workspace.options, self.options,
|
||||
workspace.base_options, self.options,
|
||||
"workspace options must be synchronized with layout"
|
||||
);
|
||||
|
||||
let options = Options::clone(&workspace.base_options)
|
||||
.adjusted_for_scale(workspace.scale().fractional_scale());
|
||||
assert_eq!(
|
||||
&*workspace.options, &options,
|
||||
"workspace options must be base options adjusted for workspace scale"
|
||||
);
|
||||
|
||||
assert!(
|
||||
seen_workspace_id.insert(workspace.id()),
|
||||
"workspace id must be unique"
|
||||
@@ -1931,7 +2033,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workspace_switch_gesture_begin(&mut self, output: &Output) {
|
||||
pub fn workspace_switch_gesture_begin(&mut self, output: &Output, is_touchpad: bool) {
|
||||
let monitors = match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => monitors,
|
||||
MonitorSet::NoOutputs { .. } => unreachable!(),
|
||||
@@ -1940,11 +2042,11 @@ impl<W: LayoutElement> Layout<W> {
|
||||
for monitor in monitors {
|
||||
// Cancel the gesture on other outputs.
|
||||
if &monitor.output != output {
|
||||
monitor.workspace_switch_gesture_end(true);
|
||||
monitor.workspace_switch_gesture_end(true, None);
|
||||
continue;
|
||||
}
|
||||
|
||||
monitor.workspace_switch_gesture_begin();
|
||||
monitor.workspace_switch_gesture_begin(is_touchpad);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1952,6 +2054,7 @@ impl<W: LayoutElement> Layout<W> {
|
||||
&mut self,
|
||||
delta_y: f64,
|
||||
timestamp: Duration,
|
||||
is_touchpad: bool,
|
||||
) -> Option<Option<Output>> {
|
||||
let monitors = match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => monitors,
|
||||
@@ -1959,7 +2062,9 @@ impl<W: LayoutElement> Layout<W> {
|
||||
};
|
||||
|
||||
for monitor in monitors {
|
||||
if let Some(refresh) = monitor.workspace_switch_gesture_update(delta_y, timestamp) {
|
||||
if let Some(refresh) =
|
||||
monitor.workspace_switch_gesture_update(delta_y, timestamp, is_touchpad)
|
||||
{
|
||||
if refresh {
|
||||
return Some(Some(monitor.output.clone()));
|
||||
} else {
|
||||
@@ -1971,14 +2076,18 @@ impl<W: LayoutElement> Layout<W> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn workspace_switch_gesture_end(&mut self, cancelled: bool) -> Option<Output> {
|
||||
pub fn workspace_switch_gesture_end(
|
||||
&mut self,
|
||||
cancelled: bool,
|
||||
is_touchpad: Option<bool>,
|
||||
) -> Option<Output> {
|
||||
let monitors = match &mut self.monitor_set {
|
||||
MonitorSet::Normal { monitors, .. } => monitors,
|
||||
MonitorSet::NoOutputs { .. } => return None,
|
||||
};
|
||||
|
||||
for monitor in monitors {
|
||||
if monitor.workspace_switch_gesture_end(cancelled) {
|
||||
if monitor.workspace_switch_gesture_end(cancelled, is_touchpad) {
|
||||
return Some(monitor.output.clone());
|
||||
}
|
||||
}
|
||||
@@ -2325,13 +2434,14 @@ impl<W: LayoutElement> Default for MonitorSet<W> {
|
||||
mod tests {
|
||||
use std::cell::Cell;
|
||||
|
||||
use niri_config::WorkspaceName;
|
||||
use niri_config::{FloatOrInt, WorkspaceName};
|
||||
use proptest::prelude::*;
|
||||
use proptest_derive::Arbitrary;
|
||||
use smithay::output::{Mode, PhysicalProperties, Subpixel};
|
||||
use smithay::utils::Rectangle;
|
||||
|
||||
use super::*;
|
||||
use crate::utils::round_logical_in_physical;
|
||||
|
||||
impl<W: LayoutElement> Default for Layout<W> {
|
||||
fn default() -> Self {
|
||||
@@ -2416,7 +2526,7 @@ mod tests {
|
||||
fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
_renderer: &mut R,
|
||||
_location: Point<i32, Logical>,
|
||||
_location: Point<f64, Logical>,
|
||||
_scale: Scale<f64>,
|
||||
_alpha: f32,
|
||||
_target: RenderTarget,
|
||||
@@ -2445,7 +2555,7 @@ mod tests {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_preferred_scale_transform(&self, _scale: i32, _transform: Transform) {}
|
||||
fn set_preferred_scale_transform(&self, _scale: output::Scale, _transform: Transform) {}
|
||||
|
||||
fn has_ssd(&self) -> bool {
|
||||
false
|
||||
@@ -2552,9 +2662,19 @@ mod tests {
|
||||
]
|
||||
}
|
||||
|
||||
fn arbitrary_scale() -> impl Strategy<Value = f64> {
|
||||
prop_oneof![Just(1.), Just(1.5), Just(2.),]
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Arbitrary)]
|
||||
enum Op {
|
||||
AddOutput(#[proptest(strategy = "1..=5usize")] usize),
|
||||
AddScaledOutput {
|
||||
#[proptest(strategy = "1..=5usize")]
|
||||
id: usize,
|
||||
#[proptest(strategy = "arbitrary_scale()")]
|
||||
scale: f64,
|
||||
},
|
||||
RemoveOutput(#[proptest(strategy = "1..=5usize")] usize),
|
||||
FocusOutput(#[proptest(strategy = "1..=5usize")] usize),
|
||||
AddNamedWorkspace {
|
||||
@@ -2597,12 +2717,25 @@ mod tests {
|
||||
},
|
||||
CloseWindow(#[proptest(strategy = "1..=5usize")] usize),
|
||||
FullscreenWindow(#[proptest(strategy = "1..=5usize")] usize),
|
||||
SetFullscreenWindow {
|
||||
#[proptest(strategy = "1..=5usize")]
|
||||
window: usize,
|
||||
is_fullscreen: bool,
|
||||
},
|
||||
FocusColumnLeft,
|
||||
FocusColumnRight,
|
||||
FocusColumnFirst,
|
||||
FocusColumnLast,
|
||||
FocusColumnRightOrFirst,
|
||||
FocusColumnLeftOrLast,
|
||||
FocusColumnOrMonitorLeft(#[proptest(strategy = "1..=2u8")] u8),
|
||||
FocusColumnOrMonitorRight(#[proptest(strategy = "1..=2u8")] u8),
|
||||
FocusWindowDown,
|
||||
FocusWindowUp,
|
||||
FocusWindowDownOrColumnLeft,
|
||||
FocusWindowDownOrColumnRight,
|
||||
FocusWindowUpOrColumnLeft,
|
||||
FocusWindowUpOrColumnRight,
|
||||
FocusWindowOrWorkspaceDown,
|
||||
FocusWindowOrWorkspaceUp,
|
||||
MoveColumnLeft,
|
||||
@@ -2657,14 +2790,17 @@ mod tests {
|
||||
WorkspaceSwitchGestureBegin {
|
||||
#[proptest(strategy = "1..=5usize")]
|
||||
output_idx: usize,
|
||||
is_touchpad: bool,
|
||||
},
|
||||
WorkspaceSwitchGestureUpdate {
|
||||
#[proptest(strategy = "-400f64..400f64")]
|
||||
delta: f64,
|
||||
timestamp: Duration,
|
||||
is_touchpad: bool,
|
||||
},
|
||||
WorkspaceSwitchGestureEnd {
|
||||
cancelled: bool,
|
||||
is_touchpad: Option<bool>,
|
||||
},
|
||||
InteractiveResizeBegin {
|
||||
#[proptest(strategy = "1..=5usize")]
|
||||
@@ -2715,6 +2851,32 @@ mod tests {
|
||||
);
|
||||
layout.add_output(output.clone());
|
||||
}
|
||||
Op::AddScaledOutput { id, scale } => {
|
||||
let name = format!("output{id}");
|
||||
if layout.outputs().any(|o| o.name() == name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let output = Output::new(
|
||||
name,
|
||||
PhysicalProperties {
|
||||
size: Size::from((1280, 720)),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: String::new(),
|
||||
model: String::new(),
|
||||
},
|
||||
);
|
||||
output.change_current_state(
|
||||
Some(Mode {
|
||||
size: Size::from((1280, 720)),
|
||||
refresh: 60000,
|
||||
}),
|
||||
None,
|
||||
Some(smithay::output::Scale::Fractional(scale)),
|
||||
None,
|
||||
);
|
||||
layout.add_output(output.clone());
|
||||
}
|
||||
Op::RemoveOutput(id) => {
|
||||
let name = format!("output{id}");
|
||||
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
||||
@@ -2881,12 +3043,40 @@ mod tests {
|
||||
Op::FullscreenWindow(id) => {
|
||||
layout.toggle_fullscreen(&id);
|
||||
}
|
||||
Op::SetFullscreenWindow {
|
||||
window,
|
||||
is_fullscreen,
|
||||
} => {
|
||||
layout.set_fullscreen(&window, is_fullscreen);
|
||||
}
|
||||
Op::FocusColumnLeft => layout.focus_left(),
|
||||
Op::FocusColumnRight => layout.focus_right(),
|
||||
Op::FocusColumnFirst => layout.focus_column_first(),
|
||||
Op::FocusColumnLast => layout.focus_column_last(),
|
||||
Op::FocusColumnRightOrFirst => layout.focus_column_right_or_first(),
|
||||
Op::FocusColumnLeftOrLast => layout.focus_column_left_or_last(),
|
||||
Op::FocusColumnOrMonitorLeft(id) => {
|
||||
let name = format!("output{id}");
|
||||
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
layout.focus_column_left_or_output(&output);
|
||||
}
|
||||
Op::FocusColumnOrMonitorRight(id) => {
|
||||
let name = format!("output{id}");
|
||||
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
layout.focus_column_right_or_output(&output);
|
||||
}
|
||||
Op::FocusWindowDown => layout.focus_down(),
|
||||
Op::FocusWindowUp => layout.focus_up(),
|
||||
Op::FocusWindowDownOrColumnLeft => layout.focus_down_or_left(),
|
||||
Op::FocusWindowDownOrColumnRight => layout.focus_down_or_right(),
|
||||
Op::FocusWindowUpOrColumnLeft => layout.focus_up_or_left(),
|
||||
Op::FocusWindowUpOrColumnRight => layout.focus_up_or_right(),
|
||||
Op::FocusWindowOrWorkspaceDown => layout.focus_window_or_workspace_down(),
|
||||
Op::FocusWindowOrWorkspaceUp => layout.focus_window_or_workspace_up(),
|
||||
Op::MoveColumnLeft => layout.move_left(),
|
||||
@@ -3004,19 +3194,29 @@ mod tests {
|
||||
// We don't handle cancels in this gesture.
|
||||
layout.view_offset_gesture_end(false, is_touchpad);
|
||||
}
|
||||
Op::WorkspaceSwitchGestureBegin { output_idx: id } => {
|
||||
Op::WorkspaceSwitchGestureBegin {
|
||||
output_idx: id,
|
||||
is_touchpad,
|
||||
} => {
|
||||
let name = format!("output{id}");
|
||||
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
layout.workspace_switch_gesture_begin(&output);
|
||||
layout.workspace_switch_gesture_begin(&output, is_touchpad);
|
||||
}
|
||||
Op::WorkspaceSwitchGestureUpdate { delta, timestamp } => {
|
||||
layout.workspace_switch_gesture_update(delta, timestamp);
|
||||
Op::WorkspaceSwitchGestureUpdate {
|
||||
delta,
|
||||
timestamp,
|
||||
is_touchpad,
|
||||
} => {
|
||||
layout.workspace_switch_gesture_update(delta, timestamp, is_touchpad);
|
||||
}
|
||||
Op::WorkspaceSwitchGestureEnd { cancelled } => {
|
||||
layout.workspace_switch_gesture_end(cancelled);
|
||||
Op::WorkspaceSwitchGestureEnd {
|
||||
cancelled,
|
||||
is_touchpad,
|
||||
} => {
|
||||
layout.workspace_switch_gesture_end(cancelled, is_touchpad);
|
||||
}
|
||||
Op::InteractiveResizeBegin { window, edges } => {
|
||||
layout.interactive_resize_begin(window, edges);
|
||||
@@ -3097,9 +3297,17 @@ mod tests {
|
||||
Op::FullscreenWindow(3),
|
||||
Op::FocusColumnLeft,
|
||||
Op::FocusColumnRight,
|
||||
Op::FocusColumnRightOrFirst,
|
||||
Op::FocusColumnLeftOrLast,
|
||||
Op::FocusColumnOrMonitorLeft(0),
|
||||
Op::FocusColumnOrMonitorRight(1),
|
||||
Op::FocusWindowUp,
|
||||
Op::FocusWindowUpOrColumnLeft,
|
||||
Op::FocusWindowUpOrColumnRight,
|
||||
Op::FocusWindowOrWorkspaceUp,
|
||||
Op::FocusWindowDown,
|
||||
Op::FocusWindowDownOrColumnLeft,
|
||||
Op::FocusWindowDownOrColumnRight,
|
||||
Op::FocusWindowOrWorkspaceDown,
|
||||
Op::MoveColumnLeft,
|
||||
Op::MoveColumnRight,
|
||||
@@ -3248,11 +3456,35 @@ mod tests {
|
||||
Op::FullscreenWindow(1),
|
||||
Op::FullscreenWindow(2),
|
||||
Op::FullscreenWindow(3),
|
||||
Op::SetFullscreenWindow {
|
||||
window: 1,
|
||||
is_fullscreen: false,
|
||||
},
|
||||
Op::SetFullscreenWindow {
|
||||
window: 1,
|
||||
is_fullscreen: true,
|
||||
},
|
||||
Op::SetFullscreenWindow {
|
||||
window: 2,
|
||||
is_fullscreen: false,
|
||||
},
|
||||
Op::SetFullscreenWindow {
|
||||
window: 2,
|
||||
is_fullscreen: true,
|
||||
},
|
||||
Op::FocusColumnLeft,
|
||||
Op::FocusColumnRight,
|
||||
Op::FocusColumnRightOrFirst,
|
||||
Op::FocusColumnLeftOrLast,
|
||||
Op::FocusColumnOrMonitorLeft(0),
|
||||
Op::FocusColumnOrMonitorRight(1),
|
||||
Op::FocusWindowUp,
|
||||
Op::FocusWindowUpOrColumnLeft,
|
||||
Op::FocusWindowUpOrColumnRight,
|
||||
Op::FocusWindowOrWorkspaceUp,
|
||||
Op::FocusWindowDown,
|
||||
Op::FocusWindowDownOrColumnLeft,
|
||||
Op::FocusWindowDownOrColumnRight,
|
||||
Op::FocusWindowOrWorkspaceDown,
|
||||
Op::MoveColumnLeft,
|
||||
Op::MoveColumnRight,
|
||||
@@ -3466,7 +3698,7 @@ mod tests {
|
||||
|
||||
let mut options = Options::default();
|
||||
options.border.off = false;
|
||||
options.border.width = 1;
|
||||
options.border.width = FloatOrInt(1.);
|
||||
|
||||
check_ops_with_options(options, &ops);
|
||||
}
|
||||
@@ -3484,7 +3716,7 @@ mod tests {
|
||||
|
||||
let mut options = Options::default();
|
||||
options.border.off = false;
|
||||
options.border.width = 1;
|
||||
options.border.width = FloatOrInt(1.);
|
||||
|
||||
check_ops_with_options(options, &ops);
|
||||
}
|
||||
@@ -3618,6 +3850,30 @@ mod tests {
|
||||
check_ops(&ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unfullscreen_window_in_column() {
|
||||
let ops = [
|
||||
Op::AddOutput(1),
|
||||
Op::AddWindow {
|
||||
id: 1,
|
||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
||||
},
|
||||
Op::AddWindow {
|
||||
id: 2,
|
||||
bbox: Rectangle::from_loc_and_size((0, 0), (100, 200)),
|
||||
min_max_size: (Size::from((0, 0)), Size::from((i32::MAX, i32::MAX))),
|
||||
},
|
||||
Op::ConsumeOrExpelWindowLeft,
|
||||
Op::SetFullscreenWindow {
|
||||
window: 2,
|
||||
is_fullscreen: false,
|
||||
},
|
||||
];
|
||||
|
||||
check_ops(&ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_right_of_on_different_workspace() {
|
||||
let ops = [
|
||||
@@ -3767,12 +4023,125 @@ mod tests {
|
||||
check_ops(&ops);
|
||||
}
|
||||
|
||||
fn arbitrary_spacing() -> impl Strategy<Value = u16> {
|
||||
#[test]
|
||||
fn removing_all_outputs_preserves_empty_named_workspaces() {
|
||||
let ops = [
|
||||
Op::AddOutput(1),
|
||||
Op::AddNamedWorkspace {
|
||||
ws_name: 1,
|
||||
output_name: None,
|
||||
},
|
||||
Op::AddNamedWorkspace {
|
||||
ws_name: 2,
|
||||
output_name: None,
|
||||
},
|
||||
Op::RemoveOutput(1),
|
||||
];
|
||||
|
||||
let mut layout = Layout::default();
|
||||
for op in ops {
|
||||
op.apply(&mut layout);
|
||||
}
|
||||
|
||||
let MonitorSet::NoOutputs { workspaces } = layout.monitor_set else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
assert_eq!(workspaces.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_change_updates_cached_sizes() {
|
||||
let mut config = Config::default();
|
||||
config.layout.border.off = false;
|
||||
config.layout.border.width = FloatOrInt(2.);
|
||||
|
||||
let mut layout = Layout::new(&config);
|
||||
|
||||
Op::AddWindow {
|
||||
id: 1,
|
||||
bbox: Rectangle::from_loc_and_size((0, 0), (1280, 200)),
|
||||
min_max_size: Default::default(),
|
||||
}
|
||||
.apply(&mut layout);
|
||||
|
||||
config.layout.border.width = FloatOrInt(4.);
|
||||
layout.update_config(&config);
|
||||
|
||||
layout.verify_invariants();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn working_area_starts_at_physical_pixel() {
|
||||
let struts = Struts {
|
||||
left: FloatOrInt(0.5),
|
||||
right: FloatOrInt(1.),
|
||||
top: FloatOrInt(0.75),
|
||||
bottom: FloatOrInt(1.),
|
||||
};
|
||||
|
||||
let output = Output::new(
|
||||
String::from("output"),
|
||||
PhysicalProperties {
|
||||
size: Size::from((1280, 720)),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: String::new(),
|
||||
model: String::new(),
|
||||
},
|
||||
);
|
||||
output.change_current_state(
|
||||
Some(Mode {
|
||||
size: Size::from((1280, 720)),
|
||||
refresh: 60000,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let area = compute_working_area(&output, struts);
|
||||
|
||||
assert_eq!(round_logical_in_physical(1., area.loc.x), area.loc.x);
|
||||
assert_eq!(round_logical_in_physical(1., area.loc.y), area.loc.y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_fractional_strut() {
|
||||
let struts = Struts {
|
||||
left: FloatOrInt(0.),
|
||||
right: FloatOrInt(0.),
|
||||
top: FloatOrInt(50000.5),
|
||||
bottom: FloatOrInt(0.),
|
||||
};
|
||||
|
||||
let output = Output::new(
|
||||
String::from("output"),
|
||||
PhysicalProperties {
|
||||
size: Size::from((1280, 720)),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: String::new(),
|
||||
model: String::new(),
|
||||
},
|
||||
);
|
||||
output.change_current_state(
|
||||
Some(Mode {
|
||||
size: Size::from((1280, 720)),
|
||||
refresh: 60000,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
compute_working_area(&output, struts);
|
||||
}
|
||||
|
||||
fn arbitrary_spacing() -> impl Strategy<Value = f64> {
|
||||
// Give equal weight to:
|
||||
// - 0: the element is disabled
|
||||
// - 4: some reasonable value
|
||||
// - random value, likely unreasonably big
|
||||
prop_oneof![Just(0), Just(4), (1..=u16::MAX)]
|
||||
prop_oneof![Just(0.), Just(4.), ((1.)..=65535.)]
|
||||
}
|
||||
|
||||
fn arbitrary_struts() -> impl Strategy<Value = Struts> {
|
||||
@@ -3783,10 +4152,10 @@ mod tests {
|
||||
arbitrary_spacing(),
|
||||
)
|
||||
.prop_map(|(left, right, top, bottom)| Struts {
|
||||
left,
|
||||
right,
|
||||
top,
|
||||
bottom,
|
||||
left: FloatOrInt(left),
|
||||
right: FloatOrInt(right),
|
||||
top: FloatOrInt(top),
|
||||
bottom: FloatOrInt(bottom),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3805,7 +4174,7 @@ mod tests {
|
||||
) -> niri_config::FocusRing {
|
||||
niri_config::FocusRing {
|
||||
off,
|
||||
width,
|
||||
width: FloatOrInt(width),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -3818,7 +4187,7 @@ mod tests {
|
||||
) -> niri_config::Border {
|
||||
niri_config::Border {
|
||||
off,
|
||||
width,
|
||||
width: FloatOrInt(width),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -3833,7 +4202,7 @@ mod tests {
|
||||
center_focused_column in arbitrary_center_focused_column(),
|
||||
) -> Options {
|
||||
Options {
|
||||
gaps: gaps.into(),
|
||||
gaps,
|
||||
struts,
|
||||
center_focused_column,
|
||||
focus_ring,
|
||||
|
||||
+137
-45
@@ -7,7 +7,7 @@ use smithay::backend::renderer::element::utils::{
|
||||
CropRenderElement, Relocate, RelocateRenderElement,
|
||||
};
|
||||
use smithay::output::Output;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale};
|
||||
use smithay::utils::{Logical, Point, Rectangle};
|
||||
|
||||
use super::workspace::{
|
||||
compute_working_area, Column, ColumnWidth, OutputId, Workspace, WorkspaceId,
|
||||
@@ -19,7 +19,7 @@ use crate::input::swipe_tracker::SwipeTracker;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::RenderTarget;
|
||||
use crate::rubber_band::RubberBand;
|
||||
use crate::utils::{output_size, ResizeEdge};
|
||||
use crate::utils::{output_size, to_physical_precise_round, ResizeEdge};
|
||||
|
||||
/// Amount of touchpad movement to scroll the height of one workspace.
|
||||
const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.;
|
||||
@@ -54,10 +54,12 @@ pub enum WorkspaceSwitch {
|
||||
#[derive(Debug)]
|
||||
pub struct WorkspaceSwitchGesture {
|
||||
/// Index of the workspace where the gesture was started.
|
||||
pub center_idx: usize,
|
||||
center_idx: usize,
|
||||
/// Current, fractional workspace index.
|
||||
pub current_idx: f64,
|
||||
pub tracker: SwipeTracker,
|
||||
tracker: SwipeTracker,
|
||||
/// Whether the gesture is controlled by the touchpad.
|
||||
is_touchpad: bool,
|
||||
}
|
||||
|
||||
pub type MonitorRenderElement<R> =
|
||||
@@ -320,6 +322,14 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.active_workspace().focus_column_last();
|
||||
}
|
||||
|
||||
pub fn focus_column_right_or_first(&mut self) {
|
||||
self.active_workspace().focus_column_right_or_first();
|
||||
}
|
||||
|
||||
pub fn focus_column_left_or_last(&mut self) {
|
||||
self.active_workspace().focus_column_left_or_last();
|
||||
}
|
||||
|
||||
pub fn focus_down(&mut self) {
|
||||
self.active_workspace().focus_down();
|
||||
}
|
||||
@@ -328,6 +338,62 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.active_workspace().focus_up();
|
||||
}
|
||||
|
||||
pub fn focus_down_or_left(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if !workspace.columns.is_empty() {
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let curr_idx = column.active_tile_idx;
|
||||
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_left();
|
||||
} else {
|
||||
workspace.focus_down();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_down_or_right(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if !workspace.columns.is_empty() {
|
||||
let column = &workspace.columns[workspace.active_column_idx];
|
||||
let curr_idx = column.active_tile_idx;
|
||||
let new_idx = min(column.active_tile_idx + 1, column.tiles.len() - 1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_right();
|
||||
} else {
|
||||
workspace.focus_down();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_up_or_left(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if !workspace.columns.is_empty() {
|
||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||
let new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_left();
|
||||
} else {
|
||||
workspace.focus_up();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_up_or_right(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if workspace.columns.is_empty() {
|
||||
self.switch_workspace_up();
|
||||
} else {
|
||||
let curr_idx = workspace.columns[workspace.active_column_idx].active_tile_idx;
|
||||
let new_idx = curr_idx.saturating_sub(1);
|
||||
if curr_idx == new_idx {
|
||||
self.focus_left();
|
||||
} else {
|
||||
workspace.focus_up();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_window_or_workspace_down(&mut self) {
|
||||
let workspace = self.active_workspace();
|
||||
if workspace.columns.is_empty() {
|
||||
@@ -505,12 +571,13 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
self.workspaces.iter().position(|w| w.id() == id)
|
||||
}
|
||||
|
||||
pub fn switch_workspace(&mut self, idx: usize) {
|
||||
pub fn switch_workspace(&mut self, idx: usize, animate: bool) {
|
||||
self.activate_workspace(min(idx, self.workspaces.len() - 1));
|
||||
// Don't animate this action.
|
||||
self.workspace_switch = None;
|
||||
|
||||
self.clean_up_workspaces();
|
||||
if !animate {
|
||||
self.workspace_switch = None;
|
||||
self.clean_up_workspaces();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn switch_workspace_auto_back_and_forth(&mut self, idx: usize) {
|
||||
@@ -518,16 +585,16 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
|
||||
if idx == self.active_workspace_idx {
|
||||
if let Some(prev_idx) = self.previous_workspace_idx() {
|
||||
self.switch_workspace(prev_idx);
|
||||
self.switch_workspace(prev_idx, false);
|
||||
}
|
||||
} else {
|
||||
self.switch_workspace(idx);
|
||||
self.switch_workspace(idx, false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn switch_workspace_previous(&mut self) {
|
||||
if let Some(idx) = self.previous_workspace_idx() {
|
||||
self.switch_workspace(idx);
|
||||
self.switch_workspace(idx, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,11 +684,13 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
|
||||
if self.options.struts != options.struts {
|
||||
let scale = self.output.current_scale();
|
||||
let transform = self.output.current_transform();
|
||||
let view_size = output_size(&self.output);
|
||||
let working_area = compute_working_area(&self.output, options.struts);
|
||||
|
||||
for ws in &mut self.workspaces {
|
||||
ws.set_view_size(view_size, working_area);
|
||||
ws.set_view_size(scale, transform, view_size, working_area);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,16 +764,16 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
/// Returns the geometry of the active tile relative to and clamped to the output.
|
||||
///
|
||||
/// During animations, assumes the final view position.
|
||||
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<i32, Logical>> {
|
||||
pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
|
||||
let mut rect = self.active_workspace_ref().active_tile_visual_rectangle()?;
|
||||
|
||||
if let Some(switch) = &self.workspace_switch {
|
||||
let size = output_size(&self.output);
|
||||
let size = output_size(&self.output).to_f64();
|
||||
|
||||
let offset = switch.target_idx() - self.active_workspace_idx as f64;
|
||||
let offset = (offset * size.h as f64).round() as i32;
|
||||
let offset = offset * size.h;
|
||||
|
||||
let clip_rect = Rectangle::from_loc_and_size((0, -offset), size);
|
||||
let clip_rect = Rectangle::from_loc_and_size((0., -offset), size);
|
||||
rect = rect.intersection(clip_rect)?;
|
||||
}
|
||||
|
||||
@@ -714,16 +783,16 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
pub fn window_under(
|
||||
&self,
|
||||
pos_within_output: Point<f64, Logical>,
|
||||
) -> Option<(&W, Option<Point<i32, Logical>>)> {
|
||||
) -> Option<(&W, Option<Point<f64, Logical>>)> {
|
||||
match &self.workspace_switch {
|
||||
Some(switch) => {
|
||||
let size = output_size(&self.output);
|
||||
let size = output_size(&self.output).to_f64();
|
||||
|
||||
let render_idx = switch.current_idx();
|
||||
let before_idx = render_idx.floor();
|
||||
let after_idx = render_idx.ceil();
|
||||
|
||||
let offset = ((render_idx - before_idx) * size.h as f64).round() as i32;
|
||||
let offset = (render_idx - before_idx) * size.h;
|
||||
|
||||
if after_idx < 0. || before_idx as usize >= self.workspaces.len() {
|
||||
return None;
|
||||
@@ -731,22 +800,22 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
|
||||
let after_idx = after_idx as usize;
|
||||
|
||||
let (idx, ws_offset) = if pos_within_output.y < (size.h - offset) as f64 {
|
||||
let (idx, ws_offset) = if pos_within_output.y < size.h - offset {
|
||||
if before_idx < 0. {
|
||||
return None;
|
||||
}
|
||||
|
||||
(before_idx as usize, Point::from((0, offset)))
|
||||
(before_idx as usize, Point::from((0., offset)))
|
||||
} else {
|
||||
if after_idx >= self.workspaces.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
(after_idx, Point::from((0, -size.h + offset)))
|
||||
(after_idx, Point::from((0., -size.h + offset)))
|
||||
};
|
||||
|
||||
let ws = &self.workspaces[idx];
|
||||
let (win, win_pos) = ws.window_under(pos_within_output + ws_offset.to_f64())?;
|
||||
let (win, win_pos) = ws.window_under(pos_within_output + ws_offset)?;
|
||||
Some((win, win_pos.map(|p| p - ws_offset)))
|
||||
}
|
||||
None => {
|
||||
@@ -765,7 +834,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
let before_idx = render_idx.floor();
|
||||
let after_idx = render_idx.ceil();
|
||||
|
||||
let offset = ((render_idx - before_idx) * size.h as f64).round() as i32;
|
||||
let offset = (render_idx - before_idx) * size.h;
|
||||
|
||||
if after_idx < 0. || before_idx as usize >= self.workspaces.len() {
|
||||
return None;
|
||||
@@ -773,22 +842,22 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
|
||||
let after_idx = after_idx as usize;
|
||||
|
||||
let (idx, ws_offset) = if pos_within_output.y < (size.h - offset) as f64 {
|
||||
let (idx, ws_offset) = if pos_within_output.y < size.h - offset {
|
||||
if before_idx < 0. {
|
||||
return None;
|
||||
}
|
||||
|
||||
(before_idx as usize, Point::from((0, offset)))
|
||||
(before_idx as usize, Point::from((0., offset)))
|
||||
} else {
|
||||
if after_idx >= self.workspaces.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
(after_idx, Point::from((0, -size.h + offset)))
|
||||
(after_idx, Point::from((0., -size.h + offset)))
|
||||
};
|
||||
|
||||
let ws = &self.workspaces[idx];
|
||||
ws.resize_edges_under(pos_within_output + ws_offset.to_f64())
|
||||
ws.resize_edges_under(pos_within_output + ws_offset)
|
||||
}
|
||||
None => {
|
||||
let ws = &self.workspaces[self.active_workspace_idx];
|
||||
@@ -814,10 +883,8 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
) -> Vec<MonitorRenderElement<R>> {
|
||||
let _span = tracy_client::span!("Monitor::render_elements");
|
||||
|
||||
let output_scale = Scale::from(self.output.current_scale().fractional_scale());
|
||||
let output_transform = self.output.current_transform();
|
||||
let output_mode = self.output.current_mode().unwrap();
|
||||
let size = output_transform.transform_size(output_mode.size);
|
||||
let scale = self.output.current_scale().fractional_scale();
|
||||
let size = output_size(&self.output);
|
||||
|
||||
match &self.workspace_switch {
|
||||
Some(switch) => {
|
||||
@@ -825,7 +892,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
let before_idx = render_idx.floor();
|
||||
let after_idx = render_idx.ceil();
|
||||
|
||||
let offset = ((render_idx - before_idx) * size.h as f64).round() as i32;
|
||||
let offset = (render_idx - before_idx) * size.h;
|
||||
|
||||
if after_idx < 0. || before_idx as usize >= self.workspaces.len() {
|
||||
return vec![];
|
||||
@@ -838,7 +905,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
Some(RelocateRenderElement::from_element(
|
||||
CropRenderElement::from_element(
|
||||
elem,
|
||||
output_scale,
|
||||
scale,
|
||||
// HACK: crop to infinite bounds for all sides except the side
|
||||
// where the workspaces join,
|
||||
// otherwise it will cut pixel shaders and mess up
|
||||
@@ -848,7 +915,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
(i32::MAX / 2, i32::MAX / 2),
|
||||
),
|
||||
)?,
|
||||
(0, -offset + size.h),
|
||||
Point::from((0., -offset + size.h)).to_physical_precise_round(scale),
|
||||
Relocate::Relative,
|
||||
))
|
||||
});
|
||||
@@ -868,13 +935,13 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
Some(RelocateRenderElement::from_element(
|
||||
CropRenderElement::from_element(
|
||||
elem,
|
||||
output_scale,
|
||||
scale,
|
||||
Rectangle::from_extemities(
|
||||
(-i32::MAX / 2, -i32::MAX / 2),
|
||||
(i32::MAX / 2, size.h),
|
||||
(i32::MAX / 2, to_physical_precise_round(scale, size.h)),
|
||||
),
|
||||
)?,
|
||||
(0, -offset),
|
||||
Point::from((0., -offset)).to_physical_precise_round(scale),
|
||||
Relocate::Relative,
|
||||
))
|
||||
});
|
||||
@@ -889,7 +956,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
Some(RelocateRenderElement::from_element(
|
||||
CropRenderElement::from_element(
|
||||
elem,
|
||||
output_scale,
|
||||
scale,
|
||||
// HACK: set infinite crop bounds due to a damage tracking bug
|
||||
// which causes glitched rendering for maximized GTK windows.
|
||||
// FIXME: use proper bounds after fixing the Crop element.
|
||||
@@ -908,7 +975,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workspace_switch_gesture_begin(&mut self) {
|
||||
pub fn workspace_switch_gesture_begin(&mut self, is_touchpad: bool) {
|
||||
let center_idx = self.active_workspace_idx;
|
||||
let current_idx = self
|
||||
.workspace_switch
|
||||
@@ -920,6 +987,7 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
center_idx,
|
||||
current_idx,
|
||||
tracker: SwipeTracker::new(),
|
||||
is_touchpad,
|
||||
};
|
||||
self.workspace_switch = Some(WorkspaceSwitch::Gesture(gesture));
|
||||
}
|
||||
@@ -928,14 +996,24 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
&mut self,
|
||||
delta_y: f64,
|
||||
timestamp: Duration,
|
||||
is_touchpad: bool,
|
||||
) -> Option<bool> {
|
||||
let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if gesture.is_touchpad != is_touchpad {
|
||||
return None;
|
||||
}
|
||||
|
||||
gesture.tracker.push(delta_y, timestamp);
|
||||
|
||||
let pos = gesture.tracker.pos() / WORKSPACE_GESTURE_MOVEMENT;
|
||||
let total_height = if gesture.is_touchpad {
|
||||
WORKSPACE_GESTURE_MOVEMENT
|
||||
} else {
|
||||
self.workspaces[0].view_size().h
|
||||
};
|
||||
let pos = gesture.tracker.pos() / total_height;
|
||||
|
||||
let min = gesture.center_idx.saturating_sub(1) as f64;
|
||||
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
|
||||
@@ -950,20 +1028,34 @@ impl<W: LayoutElement> Monitor<W> {
|
||||
Some(true)
|
||||
}
|
||||
|
||||
pub fn workspace_switch_gesture_end(&mut self, cancelled: bool) -> bool {
|
||||
pub fn workspace_switch_gesture_end(
|
||||
&mut self,
|
||||
cancelled: bool,
|
||||
is_touchpad: Option<bool>,
|
||||
) -> bool {
|
||||
let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if is_touchpad.map_or(false, |x| gesture.is_touchpad != x) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if cancelled {
|
||||
self.workspace_switch = None;
|
||||
self.clean_up_workspaces();
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut velocity = gesture.tracker.velocity() / WORKSPACE_GESTURE_MOVEMENT;
|
||||
let current_pos = gesture.tracker.pos() / WORKSPACE_GESTURE_MOVEMENT;
|
||||
let pos = gesture.tracker.projected_end_pos() / WORKSPACE_GESTURE_MOVEMENT;
|
||||
let total_height = if gesture.is_touchpad {
|
||||
WORKSPACE_GESTURE_MOVEMENT
|
||||
} else {
|
||||
self.workspaces[0].view_size().h
|
||||
};
|
||||
|
||||
let mut velocity = gesture.tracker.velocity() / total_height;
|
||||
let current_pos = gesture.tracker.pos() / total_height;
|
||||
let pos = gesture.tracker.projected_end_pos() / total_height;
|
||||
|
||||
let min = gesture.center_idx.saturating_sub(1) as f64;
|
||||
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
|
||||
|
||||
@@ -4,13 +4,12 @@ use std::time::Duration;
|
||||
use anyhow::Context as _;
|
||||
use glam::{Mat3, Vec2};
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::texture::TextureRenderElement;
|
||||
use smithay::backend::renderer::element::utils::{
|
||||
Relocate, RelocateRenderElement, RescaleRenderElement,
|
||||
};
|
||||
use smithay::backend::renderer::element::{Id, Kind, RenderElement};
|
||||
use smithay::backend::renderer::element::{Kind, RenderElement};
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, Uniform};
|
||||
use smithay::backend::renderer::{Renderer as _, Texture};
|
||||
use smithay::backend::renderer::Texture;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use crate::animation::Animation;
|
||||
@@ -19,6 +18,7 @@ use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::render_to_encompassing_texture;
|
||||
use crate::render_helpers::shader_element::ShaderRenderElement;
|
||||
use crate::render_helpers::shaders::{mat3_uniform, ProgramType, Shaders};
|
||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OpenAnimation {
|
||||
@@ -55,8 +55,8 @@ impl OpenAnimation {
|
||||
&self,
|
||||
renderer: &mut GlesRenderer,
|
||||
elements: &[impl RenderElement<GlesRenderer>],
|
||||
geo_size: Size<i32, Logical>,
|
||||
location: Point<i32, Logical>,
|
||||
geo_size: Size<f64, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
) -> anyhow::Result<OpeningWindowRenderElement> {
|
||||
let progress = self.anim.value();
|
||||
@@ -75,17 +75,17 @@ impl OpenAnimation {
|
||||
let texture_size = geo.size.to_f64().to_logical(scale);
|
||||
|
||||
if Shaders::get(renderer).program(ProgramType::Open).is_some() {
|
||||
let mut area = Rectangle::from_loc_and_size(location.to_f64() + offset, texture_size);
|
||||
let mut area = Rectangle::from_loc_and_size(location + offset, texture_size);
|
||||
|
||||
// Expand the area a bit to allow for more varied effects.
|
||||
let mut target_size = area.size.upscale(1.5);
|
||||
target_size.w = f64::max(area.size.w + 1000., target_size.w);
|
||||
target_size.h = f64::max(area.size.h + 1000., target_size.h);
|
||||
let diff = target_size.to_point() - area.size.to_point();
|
||||
area.loc -= diff.downscale(2.);
|
||||
area.size += diff.to_size();
|
||||
let diff = (target_size.to_point() - area.size.to_point()).downscale(2.);
|
||||
let diff = diff.to_physical_precise_round(scale).to_logical(scale);
|
||||
area.loc -= diff;
|
||||
area.size += diff.upscale(2.).to_size();
|
||||
|
||||
let area = area.to_i32_up();
|
||||
let area_loc = Vec2::new(area.loc.x as f32, area.loc.y as f32);
|
||||
let area_size = Vec2::new(area.size.w as f32, area.size.h as f32);
|
||||
|
||||
@@ -106,6 +106,7 @@ impl OpenAnimation {
|
||||
ProgramType::Open,
|
||||
area.size,
|
||||
None,
|
||||
scale.x as f32,
|
||||
1.,
|
||||
vec![
|
||||
mat3_uniform("niri_input_to_geo", input_to_geo),
|
||||
@@ -122,15 +123,12 @@ impl OpenAnimation {
|
||||
.into());
|
||||
}
|
||||
|
||||
let elem = TextureRenderElement::from_static_texture(
|
||||
Id::new(),
|
||||
renderer.id(),
|
||||
let buffer =
|
||||
TextureBuffer::from_texture(renderer, texture, scale, Transform::Normal, Vec::new());
|
||||
let elem = TextureRenderElement::from_texture_buffer(
|
||||
buffer,
|
||||
Point::from((0., 0.)),
|
||||
texture.clone(),
|
||||
scale.x as i32,
|
||||
Transform::Normal,
|
||||
Some(clamped_progress as f32),
|
||||
None,
|
||||
clamped_progress as f32,
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
@@ -138,7 +136,7 @@ impl OpenAnimation {
|
||||
|
||||
let elem = PrimaryGpuTextureRenderElement(elem);
|
||||
|
||||
let center = geo_size.to_point().to_f64().downscale(2.);
|
||||
let center = geo_size.to_point().downscale(2.);
|
||||
let elem = RescaleRenderElement::from_element(
|
||||
elem,
|
||||
(center - offset).to_physical_precise_round(scale),
|
||||
@@ -147,7 +145,7 @@ impl OpenAnimation {
|
||||
|
||||
let elem = RelocateRenderElement::from_element(
|
||||
elem,
|
||||
(location.to_f64() + offset).to_physical_precise_round(scale),
|
||||
(location + offset).to_physical_precise_round(scale),
|
||||
Relocate::Relative,
|
||||
);
|
||||
|
||||
|
||||
+124
-98
@@ -1,10 +1,8 @@
|
||||
use std::cmp::max;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri_config::CornerRadius;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::{Element, Kind};
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||
@@ -23,6 +21,7 @@ use crate::render_helpers::damage::ExtraDamage;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::resize::ResizeRenderElement;
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::{render_to_encompassing_texture, RenderTarget};
|
||||
|
||||
/// Toplevel window with decorations.
|
||||
@@ -50,7 +49,7 @@ pub struct Tile<W: LayoutElement> {
|
||||
fullscreen_backdrop: SolidColorBuffer,
|
||||
|
||||
/// The size we were requested to fullscreen into.
|
||||
fullscreen_size: Size<i32, Logical>,
|
||||
fullscreen_size: Size<f64, Logical>,
|
||||
|
||||
/// The animation upon opening a window.
|
||||
open_animation: Option<OpenAnimation>,
|
||||
@@ -70,6 +69,9 @@ pub struct Tile<W: LayoutElement> {
|
||||
/// Extra damage for clipped surface corner radius changes.
|
||||
rounded_corner_damage: RoundedCornerDamage,
|
||||
|
||||
/// Scale of the output the tile is on (and rounds its sizes to).
|
||||
scale: f64,
|
||||
|
||||
/// Configurable properties of the layout.
|
||||
pub options: Rc<Options>,
|
||||
}
|
||||
@@ -93,18 +95,18 @@ type TileRenderSnapshot =
|
||||
#[derive(Debug)]
|
||||
struct ResizeAnimation {
|
||||
anim: Animation,
|
||||
size_from: Size<i32, Logical>,
|
||||
size_from: Size<f64, Logical>,
|
||||
snapshot: LayoutElementRenderSnapshot,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MoveAnimation {
|
||||
anim: Animation,
|
||||
from: i32,
|
||||
from: f64,
|
||||
}
|
||||
|
||||
impl<W: LayoutElement> Tile<W> {
|
||||
pub fn new(window: W, options: Rc<Options>) -> Self {
|
||||
pub fn new(window: W, scale: f64, options: Rc<Options>) -> Self {
|
||||
let rules = window.rules();
|
||||
let border_config = rules.border.resolve_against(options.border);
|
||||
let focus_ring_config = rules.focus_ring.resolve_against(options.focus_ring.into());
|
||||
@@ -114,7 +116,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
border: FocusRing::new(border_config.into()),
|
||||
focus_ring: FocusRing::new(focus_ring_config.into()),
|
||||
is_fullscreen: false, // FIXME: up-to-date fullscreen right away, but we need size.
|
||||
fullscreen_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]),
|
||||
fullscreen_backdrop: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]),
|
||||
fullscreen_size: Default::default(),
|
||||
open_animation: None,
|
||||
resize_animation: None,
|
||||
@@ -122,11 +124,13 @@ impl<W: LayoutElement> Tile<W> {
|
||||
move_y_animation: None,
|
||||
unmap_snapshot: None,
|
||||
rounded_corner_damage: Default::default(),
|
||||
scale,
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_config(&mut self, options: Rc<Options>) {
|
||||
pub fn update_config(&mut self, scale: f64, options: Rc<Options>) {
|
||||
self.scale = scale;
|
||||
self.options = options;
|
||||
|
||||
let rules = self.window.rules();
|
||||
@@ -147,7 +151,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
|
||||
pub fn update_window(&mut self) {
|
||||
// FIXME: remove when we can get a fullscreen size right away.
|
||||
if self.fullscreen_size != Size::from((0, 0)) {
|
||||
if self.fullscreen_size != Size::from((0., 0.)) {
|
||||
self.is_fullscreen = self.window.is_fullscreen();
|
||||
}
|
||||
|
||||
@@ -160,16 +164,16 @@ impl<W: LayoutElement> Tile<W> {
|
||||
let val = resize.anim.value();
|
||||
let size_from = resize.size_from;
|
||||
|
||||
size.w = (size_from.w as f64 + (size.w - size_from.w) as f64 * val).round() as i32;
|
||||
size.h = (size_from.h as f64 + (size.h - size_from.h) as f64 * val).round() as i32;
|
||||
size.w = size_from.w + (size.w - size_from.w) * val;
|
||||
size.h = size_from.h + (size.h - size_from.h) * val;
|
||||
|
||||
size
|
||||
} else {
|
||||
animate_from.size
|
||||
};
|
||||
|
||||
let change = self.window.size().to_point() - size_from.to_point();
|
||||
let change = max(change.x.abs(), change.y.abs());
|
||||
let change = self.window.size().to_f64().to_point() - size_from.to_point();
|
||||
let change = f64::max(change.x.abs(), change.y.abs());
|
||||
if change > RESIZE_ANIMATION_THRESHOLD {
|
||||
let anim = Animation::new(0., 1., 0., self.options.animations.window_resize.anim);
|
||||
self.resize_animation = Some(ResizeAnimation {
|
||||
@@ -235,13 +239,13 @@ impl<W: LayoutElement> Tile<W> {
|
||||
|| self.move_y_animation.is_some()
|
||||
}
|
||||
|
||||
pub fn update(&mut self, is_active: bool, view_rect: Rectangle<i32, Logical>) {
|
||||
pub fn update(&mut self, is_active: bool, view_rect: Rectangle<f64, Logical>) {
|
||||
let rules = self.window.rules();
|
||||
|
||||
let draw_border_with_background = rules
|
||||
.draw_border_with_background
|
||||
.unwrap_or_else(|| !self.window.has_ssd());
|
||||
let border_width = self.effective_border_width().unwrap_or(0);
|
||||
let border_width = self.effective_border_width().unwrap_or(0.);
|
||||
let radius = if self.is_fullscreen {
|
||||
CornerRadius::default()
|
||||
} else {
|
||||
@@ -260,6 +264,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
view_rect.size,
|
||||
),
|
||||
radius,
|
||||
self.scale,
|
||||
);
|
||||
|
||||
let draw_focus_ring_with_background = if self.effective_border_width().is_some() {
|
||||
@@ -281,20 +286,25 @@ impl<W: LayoutElement> Tile<W> {
|
||||
!draw_focus_ring_with_background,
|
||||
view_rect,
|
||||
radius,
|
||||
self.scale,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn render_offset(&self) -> Point<i32, Logical> {
|
||||
pub fn scale(&self) -> f64 {
|
||||
self.scale
|
||||
}
|
||||
|
||||
pub fn render_offset(&self) -> Point<f64, Logical> {
|
||||
let mut offset = Point::from((0., 0.));
|
||||
|
||||
if let Some(move_) = &self.move_x_animation {
|
||||
offset.x += f64::from(move_.from) * move_.anim.value();
|
||||
offset.x += move_.from * move_.anim.value();
|
||||
}
|
||||
if let Some(move_) = &self.move_y_animation {
|
||||
offset.y += f64::from(move_.from) * move_.anim.value();
|
||||
offset.y += move_.from * move_.anim.value();
|
||||
}
|
||||
|
||||
offset.to_i32_round()
|
||||
offset
|
||||
}
|
||||
|
||||
pub fn start_open_animation(&mut self) {
|
||||
@@ -310,16 +320,16 @@ impl<W: LayoutElement> Tile<W> {
|
||||
self.resize_animation.as_ref().map(|resize| &resize.anim)
|
||||
}
|
||||
|
||||
pub fn animate_move_from(&mut self, from: Point<i32, Logical>) {
|
||||
pub fn animate_move_from(&mut self, from: Point<f64, Logical>) {
|
||||
self.animate_move_x_from(from.x);
|
||||
self.animate_move_y_from(from.y);
|
||||
}
|
||||
|
||||
pub fn animate_move_x_from(&mut self, from: i32) {
|
||||
pub fn animate_move_x_from(&mut self, from: f64) {
|
||||
self.animate_move_x_from_with_config(from, self.options.animations.window_movement.0);
|
||||
}
|
||||
|
||||
pub fn animate_move_x_from_with_config(&mut self, from: i32, config: niri_config::Animation) {
|
||||
pub fn animate_move_x_from_with_config(&mut self, from: f64, config: niri_config::Animation) {
|
||||
let current_offset = self.render_offset().x;
|
||||
|
||||
// Preserve the previous config if ongoing.
|
||||
@@ -334,11 +344,11 @@ impl<W: LayoutElement> Tile<W> {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn animate_move_y_from(&mut self, from: i32) {
|
||||
pub fn animate_move_y_from(&mut self, from: f64) {
|
||||
self.animate_move_y_from_with_config(from, self.options.animations.window_movement.0);
|
||||
}
|
||||
|
||||
pub fn animate_move_y_from_with_config(&mut self, from: i32, config: niri_config::Animation) {
|
||||
pub fn animate_move_y_from_with_config(&mut self, from: f64, config: niri_config::Animation) {
|
||||
let current_offset = self.render_offset().y;
|
||||
|
||||
// Preserve the previous config if ongoing.
|
||||
@@ -370,7 +380,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
|
||||
/// Returns `None` if the border is hidden and `Some(width)` if it should be shown.
|
||||
fn effective_border_width(&self) -> Option<i32> {
|
||||
fn effective_border_width(&self) -> Option<f64> {
|
||||
if self.is_fullscreen {
|
||||
return None;
|
||||
}
|
||||
@@ -383,22 +393,27 @@ impl<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
|
||||
/// Returns the location of the window's visual geometry within this Tile.
|
||||
pub fn window_loc(&self) -> Point<i32, Logical> {
|
||||
let mut loc = Point::from((0, 0));
|
||||
pub fn window_loc(&self) -> Point<f64, Logical> {
|
||||
let mut loc = Point::from((0., 0.));
|
||||
|
||||
// In fullscreen, center the window in the given size.
|
||||
if self.is_fullscreen {
|
||||
let window_size = self.window.size();
|
||||
let window_size = self.window_size();
|
||||
let target_size = self.fullscreen_size;
|
||||
|
||||
// Windows aren't supposed to be larger than the fullscreen size, but in case we get
|
||||
// one, leave it at the top-left as usual.
|
||||
if window_size.w < target_size.w {
|
||||
loc.x += (target_size.w - window_size.w) / 2;
|
||||
loc.x += (target_size.w - window_size.w) / 2.;
|
||||
}
|
||||
if window_size.h < target_size.h {
|
||||
loc.y += (target_size.h - window_size.h) / 2;
|
||||
loc.y += (target_size.h - window_size.h) / 2.;
|
||||
}
|
||||
|
||||
// Round to physical pixels.
|
||||
loc = loc
|
||||
.to_physical_precise_round(self.scale)
|
||||
.to_logical(self.scale);
|
||||
}
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
@@ -408,68 +423,73 @@ impl<W: LayoutElement> Tile<W> {
|
||||
loc
|
||||
}
|
||||
|
||||
pub fn tile_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.window.size();
|
||||
pub fn tile_size(&self) -> Size<f64, Logical> {
|
||||
let mut size = self.window_size();
|
||||
|
||||
if self.is_fullscreen {
|
||||
// Normally we'd just return the fullscreen size here, but this makes things a bit
|
||||
// nicer if a fullscreen window is bigger than the fullscreen size for some reason.
|
||||
size.w = max(size.w, self.fullscreen_size.w);
|
||||
size.h = max(size.h, self.fullscreen_size.h);
|
||||
size.w = f64::max(size.w, self.fullscreen_size.w);
|
||||
size.h = f64::max(size.h, self.fullscreen_size.h);
|
||||
return size;
|
||||
}
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
size.w = size.w.saturating_add(width * 2);
|
||||
size.h = size.h.saturating_add(width * 2);
|
||||
size.w += width * 2.;
|
||||
size.h += width * 2.;
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
pub fn window_size(&self) -> Size<i32, Logical> {
|
||||
self.window.size()
|
||||
pub fn window_size(&self) -> Size<f64, Logical> {
|
||||
let mut size = self.window.size().to_f64();
|
||||
size = size
|
||||
.to_physical_precise_round(self.scale)
|
||||
.to_logical(self.scale);
|
||||
size
|
||||
}
|
||||
|
||||
fn animated_window_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.window.size();
|
||||
fn animated_window_size(&self) -> Size<f64, Logical> {
|
||||
let mut size = self.window_size();
|
||||
|
||||
if let Some(resize) = &self.resize_animation {
|
||||
let val = resize.anim.value();
|
||||
let size_from = resize.size_from;
|
||||
let size_from = resize.size_from.to_f64();
|
||||
|
||||
size.w = (size_from.w as f64 + (size.w - size_from.w) as f64 * val).round() as i32;
|
||||
size.w = max(1, size.w);
|
||||
size.h = (size_from.h as f64 + (size.h - size_from.h) as f64 * val).round() as i32;
|
||||
size.h = max(1, size.h);
|
||||
size.w = f64::max(1., size_from.w + (size.w - size_from.w) * val);
|
||||
size.h = f64::max(1., size_from.h + (size.h - size_from.h) * val);
|
||||
size = size
|
||||
.to_physical_precise_round(self.scale)
|
||||
.to_logical(self.scale);
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
fn animated_tile_size(&self) -> Size<i32, Logical> {
|
||||
fn animated_tile_size(&self) -> Size<f64, Logical> {
|
||||
let mut size = self.animated_window_size();
|
||||
|
||||
if self.is_fullscreen {
|
||||
// Normally we'd just return the fullscreen size here, but this makes things a bit
|
||||
// nicer if a fullscreen window is bigger than the fullscreen size for some reason.
|
||||
size.w = max(size.w, self.fullscreen_size.w);
|
||||
size.h = max(size.h, self.fullscreen_size.h);
|
||||
size.w = f64::max(size.w, self.fullscreen_size.w);
|
||||
size.h = f64::max(size.h, self.fullscreen_size.h);
|
||||
return size;
|
||||
}
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
size.w = size.w.saturating_add(width * 2);
|
||||
size.h = size.h.saturating_add(width * 2);
|
||||
size.w += width * 2.;
|
||||
size.h += width * 2.;
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
pub fn buf_loc(&self) -> Point<i32, Logical> {
|
||||
let mut loc = Point::from((0, 0));
|
||||
pub fn buf_loc(&self) -> Point<f64, Logical> {
|
||||
let mut loc = Point::from((0., 0.));
|
||||
loc += self.window_loc();
|
||||
loc += self.window.buf_loc();
|
||||
loc += self.window.buf_loc().to_f64();
|
||||
loc
|
||||
}
|
||||
|
||||
@@ -479,74 +499,85 @@ impl<W: LayoutElement> Tile<W> {
|
||||
}
|
||||
|
||||
pub fn is_in_activation_region(&self, point: Point<f64, Logical>) -> bool {
|
||||
let activation_region = Rectangle::from_loc_and_size((0, 0), self.tile_size());
|
||||
activation_region.to_f64().contains(point)
|
||||
let activation_region = Rectangle::from_loc_and_size((0., 0.), self.tile_size());
|
||||
activation_region.contains(point)
|
||||
}
|
||||
|
||||
pub fn request_tile_size(&mut self, mut size: Size<i32, Logical>, animate: bool) {
|
||||
pub fn request_tile_size(&mut self, mut size: Size<f64, Logical>, animate: bool) {
|
||||
// Can't go through effective_border_width() because we might be fullscreen.
|
||||
if !self.border.is_off() {
|
||||
let width = self.border.width();
|
||||
size.w = max(1, size.w - width * 2);
|
||||
size.h = max(1, size.h - width * 2);
|
||||
size.w = f64::max(1., size.w - width * 2.);
|
||||
size.h = f64::max(1., size.h - width * 2.);
|
||||
}
|
||||
|
||||
self.window.request_size(size, animate);
|
||||
// The size request has to be i32 unfortunately, due to Wayland. We floor here instead of
|
||||
// round to avoid situations where proportionally-sized columns don't fit on the screen
|
||||
// exactly.
|
||||
self.window.request_size(size.to_i32_floor(), animate);
|
||||
}
|
||||
|
||||
pub fn tile_width_for_window_width(&self, size: i32) -> i32 {
|
||||
pub fn tile_width_for_window_width(&self, size: f64) -> f64 {
|
||||
if self.border.is_off() {
|
||||
size
|
||||
} else {
|
||||
size.saturating_add(self.border.width() * 2)
|
||||
size + self.border.width() * 2.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tile_height_for_window_height(&self, size: i32) -> i32 {
|
||||
pub fn tile_height_for_window_height(&self, size: f64) -> f64 {
|
||||
if self.border.is_off() {
|
||||
size
|
||||
} else {
|
||||
size.saturating_add(self.border.width() * 2)
|
||||
size + self.border.width() * 2.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_height_for_tile_height(&self, size: i32) -> i32 {
|
||||
pub fn window_width_for_tile_width(&self, size: f64) -> f64 {
|
||||
if self.border.is_off() {
|
||||
size
|
||||
} else {
|
||||
size.saturating_sub(self.border.width() * 2)
|
||||
size - self.border.width() * 2.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&mut self, size: Size<i32, Logical>) {
|
||||
pub fn window_height_for_tile_height(&self, size: f64) -> f64 {
|
||||
if self.border.is_off() {
|
||||
size
|
||||
} else {
|
||||
size - self.border.width() * 2.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_fullscreen(&mut self, size: Size<f64, Logical>) {
|
||||
self.fullscreen_backdrop.resize(size);
|
||||
self.fullscreen_size = size;
|
||||
self.window.request_fullscreen(size);
|
||||
self.window.request_fullscreen(size.to_i32_round());
|
||||
}
|
||||
|
||||
pub fn min_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.window.min_size();
|
||||
pub fn min_size(&self) -> Size<f64, Logical> {
|
||||
let mut size = self.window.min_size().to_f64();
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
size.w = max(1, size.w);
|
||||
size.h = max(1, size.h);
|
||||
size.w = f64::max(1., size.w);
|
||||
size.h = f64::max(1., size.h);
|
||||
|
||||
size.w = size.w.saturating_add(width * 2);
|
||||
size.h = size.h.saturating_add(width * 2);
|
||||
size.w += width * 2.;
|
||||
size.h += width * 2.;
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
pub fn max_size(&self) -> Size<i32, Logical> {
|
||||
let mut size = self.window.max_size();
|
||||
pub fn max_size(&self) -> Size<f64, Logical> {
|
||||
let mut size = self.window.max_size().to_f64();
|
||||
|
||||
if let Some(width) = self.effective_border_width() {
|
||||
if size.w > 0 {
|
||||
size.w = size.w.saturating_add(width * 2);
|
||||
if size.w > 0. {
|
||||
size.w += width * 2.;
|
||||
}
|
||||
if size.h > 0 {
|
||||
size.h = size.h.saturating_add(width * 2);
|
||||
if size.h > 0. {
|
||||
size.h += width * 2.;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +598,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
fn render_inner<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
focus_ring: bool,
|
||||
target: RenderTarget,
|
||||
@@ -581,7 +612,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
};
|
||||
|
||||
let window_loc = self.window_loc();
|
||||
let window_size = self.window_size();
|
||||
let window_size = self.window_size().to_f64();
|
||||
let animated_window_size = self.animated_window_size();
|
||||
let window_render_loc = location + window_loc;
|
||||
let area = Rectangle::from_loc_and_size(window_render_loc, animated_window_size);
|
||||
@@ -609,7 +640,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
if let Some(texture_from) = resize.snapshot.texture(gles_renderer, scale, target) {
|
||||
let window_elements = self.window.render_normal(
|
||||
gles_renderer,
|
||||
Point::from((0, 0)),
|
||||
Point::from((0., 0.)),
|
||||
scale,
|
||||
1.,
|
||||
target,
|
||||
@@ -664,8 +695,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
resize_fallback = Some(
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&fallback_buffer,
|
||||
area.loc.to_physical_precise_round(scale),
|
||||
scale,
|
||||
area.loc,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
@@ -726,13 +756,14 @@ impl<W: LayoutElement> Tile<W> {
|
||||
if radius != CornerRadius::default() && has_border_shader {
|
||||
return BorderRenderElement::new(
|
||||
geo.size,
|
||||
Rectangle::from_loc_and_size((0, 0), geo.size),
|
||||
Rectangle::from_loc_and_size((0., 0.), geo.size),
|
||||
elem.color(),
|
||||
elem.color(),
|
||||
0.,
|
||||
Rectangle::from_loc_and_size((0, 0), geo.size),
|
||||
Rectangle::from_loc_and_size((0., 0.), geo.size),
|
||||
0.,
|
||||
radius,
|
||||
scale.x as f32,
|
||||
)
|
||||
.with_location(geo.loc)
|
||||
.into();
|
||||
@@ -758,8 +789,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
let elem = self.is_fullscreen.then(|| {
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&self.fullscreen_backdrop,
|
||||
location.to_physical_precise_round(scale),
|
||||
scale,
|
||||
location,
|
||||
1.,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
@@ -769,23 +799,19 @@ impl<W: LayoutElement> Tile<W> {
|
||||
|
||||
let elem = self.effective_border_width().map(|width| {
|
||||
self.border
|
||||
.render(renderer, location + Point::from((width, width)), scale)
|
||||
.render(renderer, location + Point::from((width, width)))
|
||||
.map(Into::into)
|
||||
});
|
||||
let rv = rv.chain(elem.into_iter().flatten());
|
||||
|
||||
let elem = focus_ring.then(|| {
|
||||
self.focus_ring
|
||||
.render(renderer, location, scale)
|
||||
.map(Into::into)
|
||||
});
|
||||
let elem = focus_ring.then(|| self.focus_ring.render(renderer, location).map(Into::into));
|
||||
rv.chain(elem.into_iter().flatten())
|
||||
}
|
||||
|
||||
pub fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
focus_ring: bool,
|
||||
target: RenderTarget,
|
||||
@@ -798,7 +824,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
if let Some(open) = &self.open_animation {
|
||||
let renderer = renderer.as_gles_renderer();
|
||||
let elements =
|
||||
self.render_inner(renderer, Point::from((0, 0)), scale, focus_ring, target);
|
||||
self.render_inner(renderer, Point::from((0., 0.)), scale, focus_ring, target);
|
||||
let elements = elements.collect::<Vec<TileRenderElement<_>>>();
|
||||
match open.render(renderer, &elements, self.tile_size(), location, scale) {
|
||||
Ok(elem) => {
|
||||
@@ -843,7 +869,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
|
||||
let contents = self.render(
|
||||
renderer,
|
||||
Point::from((0, 0)),
|
||||
Point::from((0., 0.)),
|
||||
scale,
|
||||
false,
|
||||
RenderTarget::Output,
|
||||
@@ -852,7 +878,7 @@ impl<W: LayoutElement> Tile<W> {
|
||||
// A bit of a hack to render blocked out as for screencast, but I think it's fine here.
|
||||
let blocked_out_contents = self.render(
|
||||
renderer,
|
||||
Point::from((0, 0)),
|
||||
Point::from((0., 0.)),
|
||||
scale,
|
||||
false,
|
||||
RenderTarget::Screencast,
|
||||
|
||||
+423
-298
File diff suppressed because it is too large
Load Diff
+14
-2
@@ -18,7 +18,8 @@ use niri::dbus;
|
||||
use niri::ipc::client::handle_msg;
|
||||
use niri::niri::State;
|
||||
use niri::utils::spawning::{
|
||||
spawn, CHILD_ENV, REMOVE_ENV_RUST_BACKTRACE, REMOVE_ENV_RUST_LIB_BACKTRACE,
|
||||
spawn, store_and_increase_nofile_rlimit, CHILD_ENV, REMOVE_ENV_RUST_BACKTRACE,
|
||||
REMOVE_ENV_RUST_LIB_BACKTRACE,
|
||||
};
|
||||
use niri::utils::watcher::Watcher;
|
||||
use niri::utils::{cause_panic, version, IS_SYSTEMD_SERVICE};
|
||||
@@ -90,6 +91,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracy_client::Client::start();
|
||||
|
||||
let path = config
|
||||
.or_else(env_config_path)
|
||||
.or_else(default_config_path)
|
||||
.expect("error getting config path");
|
||||
Config::load(&path)?;
|
||||
@@ -112,7 +114,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// Load the config.
|
||||
let mut config_created = false;
|
||||
let path = cli.config.or_else(|| {
|
||||
let path = cli.config.or_else(env_config_path);
|
||||
env::remove_var("NIRI_CONFIG");
|
||||
let path = path.or_else(|| {
|
||||
let default_path = default_config_path()?;
|
||||
let default_parent = default_path.parent().unwrap();
|
||||
|
||||
@@ -173,6 +177,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let spawn_at_startup = mem::take(&mut config.spawn_at_startup);
|
||||
*CHILD_ENV.write().unwrap() = mem::take(&mut config.environment);
|
||||
|
||||
store_and_increase_nofile_rlimit();
|
||||
|
||||
// Create the compositor.
|
||||
let mut event_loop = EventLoop::try_new().unwrap();
|
||||
let display = Display::new().unwrap();
|
||||
@@ -312,6 +318,12 @@ fn import_environment() {
|
||||
}
|
||||
}
|
||||
|
||||
fn env_config_path() -> Option<PathBuf> {
|
||||
env::var_os("NIRI_CONFIG")
|
||||
.filter(|x| !x.is_empty())
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
|
||||
fn default_config_path() -> Option<PathBuf> {
|
||||
let Some(dirs) = ProjectDirs::from("", "", "niri") else {
|
||||
warn!("error retrieving home directory");
|
||||
|
||||
+467
-136
File diff suppressed because it is too large
Load Diff
@@ -94,7 +94,13 @@ where
|
||||
overlay_cursor,
|
||||
output,
|
||||
} => {
|
||||
let output = Output::from_resource(&output).unwrap();
|
||||
let Some(output) = Output::from_resource(&output) else {
|
||||
trace!("screencopy client requested non-existent output");
|
||||
let frame = data_init.init(frame, ScreencopyFrameState::Failed);
|
||||
frame.failed();
|
||||
return;
|
||||
};
|
||||
|
||||
let buffer_size = output.current_mode().unwrap().size;
|
||||
let region_loc = Point::from((0, 0));
|
||||
|
||||
@@ -116,7 +122,13 @@ where
|
||||
return;
|
||||
}
|
||||
|
||||
let output = Output::from_resource(&output).unwrap();
|
||||
let Some(output) = Output::from_resource(&output) else {
|
||||
trace!("screencopy client requested non-existent output");
|
||||
let frame = data_init.init(frame, ScreencopyFrameState::Failed);
|
||||
frame.failed();
|
||||
return;
|
||||
};
|
||||
|
||||
let output_transform = output.current_transform();
|
||||
let output_physical_size =
|
||||
output_transform.transform_size(output.current_mode().unwrap().size);
|
||||
|
||||
+333
-67
@@ -27,19 +27,28 @@ use smithay::backend::allocator::dmabuf::{AsDmabuf, Dmabuf};
|
||||
use smithay::backend::allocator::gbm::{GbmBuffer, GbmBufferFlags, GbmDevice};
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::drm::DrmDeviceFd;
|
||||
use smithay::output::Output;
|
||||
use smithay::backend::renderer::element::RenderElement;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::output::WeakOutput;
|
||||
use smithay::reexports::calloop::generic::Generic;
|
||||
use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction};
|
||||
use smithay::reexports::gbm::Modifier;
|
||||
use smithay::utils::{Physical, Size};
|
||||
use smithay::utils::{Physical, Scale, Size, Transform};
|
||||
use zbus::SignalContext;
|
||||
|
||||
use crate::dbus::mutter_screen_cast::{self, CursorMode, ScreenCastToNiri};
|
||||
use crate::dbus::mutter_screen_cast::{self, CursorMode};
|
||||
use crate::niri::State;
|
||||
use crate::render_helpers::render_to_dmabuf;
|
||||
|
||||
pub struct PipeWire {
|
||||
_context: Context,
|
||||
pub core: Core,
|
||||
to_niri: calloop::channel::Sender<PwToNiri>,
|
||||
}
|
||||
|
||||
pub enum PwToNiri {
|
||||
StopCast { session_id: usize },
|
||||
Redraw(CastTarget),
|
||||
}
|
||||
|
||||
pub struct Cast {
|
||||
@@ -47,14 +56,59 @@ pub struct Cast {
|
||||
pub stream: Stream,
|
||||
_listener: StreamListener<()>,
|
||||
pub is_active: Rc<Cell<bool>>,
|
||||
pub output: Output,
|
||||
pub size: Size<i32, Physical>,
|
||||
pub target: CastTarget,
|
||||
pub size: Rc<Cell<CastSize>>,
|
||||
pub refresh: u32,
|
||||
offer_alpha: bool,
|
||||
pub cursor_mode: CursorMode,
|
||||
pub last_frame_time: Duration,
|
||||
pub min_time_between_frames: Rc<Cell<Duration>>,
|
||||
pub dmabufs: Rc<RefCell<HashMap<i32, Dmabuf>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CastSize {
|
||||
InitialPending(Size<i32, Physical>),
|
||||
Ready(Size<i32, Physical>),
|
||||
ChangePending {
|
||||
last_negotiated: Size<i32, Physical>,
|
||||
pending: Size<i32, Physical>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum CastSizeChange {
|
||||
Ready,
|
||||
Pending,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum CastTarget {
|
||||
Output(WeakOutput),
|
||||
Window { id: u64 },
|
||||
}
|
||||
|
||||
macro_rules! make_params {
|
||||
($params:ident, $size:expr, $refresh:expr, $alpha:expr) => {
|
||||
let mut b1 = Vec::new();
|
||||
let mut b2 = Vec::new();
|
||||
|
||||
let o1 = make_video_params($size, $refresh, false);
|
||||
let pod1 = make_pod(&mut b1, o1);
|
||||
|
||||
let mut p1;
|
||||
let mut p2;
|
||||
$params = if $alpha {
|
||||
let o2 = make_video_params($size, $refresh, true);
|
||||
p2 = [pod1, make_pod(&mut b2, o2)];
|
||||
&mut p2[..]
|
||||
} else {
|
||||
p1 = [pod1];
|
||||
&mut p1[..]
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
impl PipeWire {
|
||||
pub fn new(event_loop: &LoopHandle<'static, State>) -> anyhow::Result<Self> {
|
||||
let main_loop = MainLoop::new(None).context("error creating MainLoop")?;
|
||||
@@ -84,43 +138,49 @@ impl PipeWire {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let (to_niri, from_pipewire) = calloop::channel::channel();
|
||||
event_loop
|
||||
.insert_source(from_pipewire, move |event, _, state| match event {
|
||||
calloop::channel::Event::Msg(msg) => state.on_pw_msg(msg),
|
||||
calloop::channel::Event::Closed => (),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(Self {
|
||||
_context: context,
|
||||
core,
|
||||
to_niri,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn start_cast(
|
||||
&self,
|
||||
to_niri: calloop::channel::Sender<ScreenCastToNiri>,
|
||||
gbm: GbmDevice<DrmDeviceFd>,
|
||||
session_id: usize,
|
||||
output: Output,
|
||||
target: CastTarget,
|
||||
size: Size<i32, Physical>,
|
||||
refresh: u32,
|
||||
alpha: bool,
|
||||
cursor_mode: CursorMode,
|
||||
signal_ctx: SignalContext<'static>,
|
||||
) -> anyhow::Result<Cast> {
|
||||
let _span = tracy_client::span!("PipeWire::start_cast");
|
||||
|
||||
let to_niri_ = to_niri.clone();
|
||||
let to_niri_ = self.to_niri.clone();
|
||||
let stop_cast = move || {
|
||||
if let Err(err) = to_niri_.send(ScreenCastToNiri::StopCast { session_id }) {
|
||||
if let Err(err) = to_niri_.send(PwToNiri::StopCast { session_id }) {
|
||||
warn!("error sending StopCast to niri: {err:?}");
|
||||
}
|
||||
};
|
||||
let weak = output.downgrade();
|
||||
let target_ = target.clone();
|
||||
let to_niri_ = self.to_niri.clone();
|
||||
let redraw = move || {
|
||||
if let Some(output) = weak.upgrade() {
|
||||
if let Err(err) = to_niri.send(ScreenCastToNiri::Redraw(output)) {
|
||||
warn!("error sending Redraw to niri: {err:?}");
|
||||
}
|
||||
if let Err(err) = to_niri_.send(PwToNiri::Redraw(target_.clone())) {
|
||||
warn!("error sending Redraw to niri: {err:?}");
|
||||
}
|
||||
};
|
||||
|
||||
let mode = output.current_mode().unwrap();
|
||||
let size = mode.size;
|
||||
let transform = output.current_transform();
|
||||
let size = transform.transform_size(size);
|
||||
let refresh = mode.refresh;
|
||||
let redraw_ = redraw.clone();
|
||||
|
||||
let stream = Stream::new(&self.core, "niri-screen-cast-src", Properties::new())
|
||||
.context("error creating Stream")?;
|
||||
@@ -130,6 +190,10 @@ impl PipeWire {
|
||||
let is_active = Rc::new(Cell::new(false));
|
||||
let min_time_between_frames = Rc::new(Cell::new(Duration::ZERO));
|
||||
let dmabufs = Rc::new(RefCell::new(HashMap::new()));
|
||||
let negotiated_alpha = Rc::new(Cell::new(false));
|
||||
|
||||
let pending_size = size;
|
||||
let size = Rc::new(Cell::new(CastSize::InitialPending(size)));
|
||||
|
||||
let listener = stream
|
||||
.add_local_listener_with_user_data(())
|
||||
@@ -180,6 +244,8 @@ impl PipeWire {
|
||||
})
|
||||
.param_changed({
|
||||
let min_time_between_frames = min_time_between_frames.clone();
|
||||
let size = size.clone();
|
||||
let negotiated_alpha = negotiated_alpha.clone();
|
||||
move |stream, (), id, pod| {
|
||||
let id = ParamType::from_raw(id);
|
||||
trace!(?id, "pw stream: param_changed");
|
||||
@@ -206,6 +272,21 @@ impl PipeWire {
|
||||
format.parse(pod).unwrap();
|
||||
trace!("pw stream: got format = {format:?}");
|
||||
|
||||
let expected_size = size.get().expected_format_size();
|
||||
let format_size =
|
||||
Size::from((format.size().width as i32, format.size().height as i32));
|
||||
|
||||
if format_size == expected_size {
|
||||
size.set(CastSize::Ready(expected_size));
|
||||
} else {
|
||||
size.set(CastSize::ChangePending {
|
||||
last_negotiated: format_size,
|
||||
pending: expected_size,
|
||||
});
|
||||
}
|
||||
|
||||
negotiated_alpha.set(format.format() == VideoFormat::BGRA);
|
||||
|
||||
let max_frame_rate = format.max_framerate();
|
||||
// Subtract 0.5 ms to improve edge cases when equal to refresh rate.
|
||||
let min_frame_time = Duration::from_secs_f64(
|
||||
@@ -270,8 +351,13 @@ impl PipeWire {
|
||||
.add_buffer({
|
||||
let dmabufs = dmabufs.clone();
|
||||
let stop_cast = stop_cast.clone();
|
||||
move |_stream, (), buffer| {
|
||||
trace!("pw stream: add_buffer");
|
||||
let size = size.clone();
|
||||
let negotiated_alpha = negotiated_alpha.clone();
|
||||
move |stream, (), buffer| {
|
||||
let size = size.get().negotiated_size();
|
||||
let alpha = negotiated_alpha.get();
|
||||
trace!("pw stream: add_buffer, size={:?}, alpha={alpha}", size);
|
||||
let size = size.expect("size must be negotiated to allocate buffers");
|
||||
|
||||
unsafe {
|
||||
let spa_buffer = (*buffer).buffer;
|
||||
@@ -279,10 +365,16 @@ impl PipeWire {
|
||||
assert!((*spa_buffer).n_datas > 0);
|
||||
assert!((*spa_data).type_ & (1 << DataType::DmaBuf.as_raw()) > 0);
|
||||
|
||||
let fourcc = if alpha {
|
||||
Fourcc::Argb8888
|
||||
} else {
|
||||
Fourcc::Xrgb8888
|
||||
};
|
||||
|
||||
let bo = match gbm.create_buffer_object::<()>(
|
||||
size.w as u32,
|
||||
size.h as u32,
|
||||
Fourcc::Xrgb8888,
|
||||
fourcc,
|
||||
GbmBufferFlags::RENDERING | GbmBufferFlags::LINEAR,
|
||||
) {
|
||||
Ok(bo) => bo,
|
||||
@@ -311,6 +403,12 @@ impl PipeWire {
|
||||
|
||||
assert!(dmabufs.borrow_mut().insert(fd, dmabuf).is_none());
|
||||
}
|
||||
|
||||
// During size re-negotiation, the stream sometimes just keeps running, in
|
||||
// which case we may need to force a redraw once we got a newly sized buffer.
|
||||
if dmabufs.borrow().len() == 1 && stream.state() == StreamState::Streaming {
|
||||
redraw_();
|
||||
}
|
||||
}
|
||||
})
|
||||
.remove_buffer({
|
||||
@@ -331,55 +429,16 @@ impl PipeWire {
|
||||
.register()
|
||||
.unwrap();
|
||||
|
||||
let object = pod::object!(
|
||||
SpaTypes::ObjectParamFormat,
|
||||
ParamType::EnumFormat,
|
||||
pod::property!(FormatProperties::MediaType, Id, MediaType::Video),
|
||||
pod::property!(FormatProperties::MediaSubtype, Id, MediaSubtype::Raw),
|
||||
pod::property!(FormatProperties::VideoFormat, Id, VideoFormat::BGRx),
|
||||
Property {
|
||||
key: FormatProperties::VideoModifier.as_raw(),
|
||||
value: pod::Value::Long(u64::from(Modifier::Invalid) as i64),
|
||||
flags: PropertyFlags::MANDATORY,
|
||||
},
|
||||
pod::property!(
|
||||
FormatProperties::VideoSize,
|
||||
Rectangle,
|
||||
Rectangle {
|
||||
width: size.w as u32,
|
||||
height: size.h as u32,
|
||||
}
|
||||
),
|
||||
pod::property!(
|
||||
FormatProperties::VideoFramerate,
|
||||
Fraction,
|
||||
Fraction { num: 0, denom: 1 }
|
||||
),
|
||||
pod::property!(
|
||||
FormatProperties::VideoMaxFramerate,
|
||||
Choice,
|
||||
Range,
|
||||
Fraction,
|
||||
Fraction {
|
||||
num: refresh as u32,
|
||||
denom: 1000
|
||||
},
|
||||
Fraction { num: 1, denom: 1 },
|
||||
Fraction {
|
||||
num: refresh as u32,
|
||||
denom: 1000
|
||||
}
|
||||
),
|
||||
);
|
||||
trace!("starting pw stream with size={pending_size:?}, refresh={refresh}");
|
||||
|
||||
let mut buffer = vec![];
|
||||
let mut params = [make_pod(&mut buffer, object)];
|
||||
let params;
|
||||
make_params!(params, pending_size, refresh, alpha);
|
||||
stream
|
||||
.connect(
|
||||
Direction::Output,
|
||||
None,
|
||||
StreamFlags::DRIVER | StreamFlags::ALLOC_BUFFERS,
|
||||
&mut params,
|
||||
params,
|
||||
)
|
||||
.context("error connecting stream")?;
|
||||
|
||||
@@ -388,8 +447,10 @@ impl PipeWire {
|
||||
stream,
|
||||
_listener: listener,
|
||||
is_active,
|
||||
output,
|
||||
target,
|
||||
size,
|
||||
refresh,
|
||||
offer_alpha: alpha,
|
||||
cursor_mode,
|
||||
last_frame_time: Duration::ZERO,
|
||||
min_time_between_frames,
|
||||
@@ -399,6 +460,211 @@ impl PipeWire {
|
||||
}
|
||||
}
|
||||
|
||||
impl Cast {
|
||||
pub fn ensure_size(&self, size: Size<i32, Physical>) -> anyhow::Result<CastSizeChange> {
|
||||
let current_size = self.size.get();
|
||||
if current_size == CastSize::Ready(size) {
|
||||
return Ok(CastSizeChange::Ready);
|
||||
}
|
||||
|
||||
if current_size.pending_size() == Some(size) {
|
||||
debug!("stream size still hasn't changed, skipping frame");
|
||||
return Ok(CastSizeChange::Pending);
|
||||
}
|
||||
|
||||
let _span = tracy_client::span!("Cast::ensure_size");
|
||||
debug!("cast size changed, updating stream size");
|
||||
|
||||
self.size.set(current_size.with_pending(size));
|
||||
|
||||
let params;
|
||||
make_params!(params, size, self.refresh, self.offer_alpha);
|
||||
self.stream
|
||||
.update_params(params)
|
||||
.context("error updating stream params")?;
|
||||
|
||||
Ok(CastSizeChange::Pending)
|
||||
}
|
||||
|
||||
pub fn set_refresh(&mut self, refresh: u32) -> anyhow::Result<()> {
|
||||
if self.refresh == refresh {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _span = tracy_client::span!("Cast::set_refresh");
|
||||
debug!("cast FPS changed, updating stream FPS");
|
||||
self.refresh = refresh;
|
||||
|
||||
let size = self.size.get().expected_format_size();
|
||||
let params;
|
||||
make_params!(params, size, self.refresh, self.offer_alpha);
|
||||
self.stream
|
||||
.update_params(params)
|
||||
.context("error updating stream params")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn should_skip_frame(&self, target_frame_time: Duration) -> bool {
|
||||
let last = self.last_frame_time;
|
||||
let min = self.min_time_between_frames.get();
|
||||
|
||||
if last.is_zero() {
|
||||
trace!(?target_frame_time, ?last, "last is zero, recording");
|
||||
return false;
|
||||
}
|
||||
|
||||
if target_frame_time < last {
|
||||
// Record frame with a warning; in case it was an overflow this will fix it.
|
||||
warn!(
|
||||
?target_frame_time,
|
||||
?last,
|
||||
"target frame time is below last, did it overflow or did we mispredict?"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
let diff = target_frame_time - last;
|
||||
if diff < min {
|
||||
trace!(
|
||||
?target_frame_time,
|
||||
?last,
|
||||
"skipping frame because it is too soon: diff={diff:?} < min={min:?}",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn dequeue_buffer_and_render(
|
||||
&mut self,
|
||||
renderer: &mut GlesRenderer,
|
||||
elements: impl Iterator<Item = impl RenderElement<GlesRenderer>>,
|
||||
size: Size<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
) -> bool {
|
||||
let mut buffer = match self.stream.dequeue_buffer() {
|
||||
Some(buffer) => buffer,
|
||||
None => {
|
||||
warn!("no available buffer in pw stream, skipping frame");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let data = &mut buffer.datas_mut()[0];
|
||||
let fd = data.as_raw().fd as i32;
|
||||
let dmabuf = self.dmabufs.borrow()[&fd].clone();
|
||||
|
||||
if let Err(err) =
|
||||
render_to_dmabuf(renderer, dmabuf, size, scale, Transform::Normal, elements)
|
||||
{
|
||||
warn!("error rendering to dmabuf: {err:?}");
|
||||
return false;
|
||||
}
|
||||
|
||||
let maxsize = data.as_raw().maxsize;
|
||||
let chunk = data.chunk_mut();
|
||||
*chunk.size_mut() = maxsize;
|
||||
*chunk.stride_mut() = maxsize as i32 / size.h;
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl CastSize {
|
||||
fn pending_size(self) -> Option<Size<i32, Physical>> {
|
||||
match self {
|
||||
CastSize::InitialPending(pending) => Some(pending),
|
||||
CastSize::Ready(_) => None,
|
||||
CastSize::ChangePending { pending, .. } => Some(pending),
|
||||
}
|
||||
}
|
||||
|
||||
fn negotiated_size(self) -> Option<Size<i32, Physical>> {
|
||||
match self {
|
||||
CastSize::InitialPending(_) => None,
|
||||
CastSize::Ready(size) => Some(size),
|
||||
CastSize::ChangePending {
|
||||
last_negotiated, ..
|
||||
} => Some(last_negotiated),
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_format_size(self) -> Size<i32, Physical> {
|
||||
match self {
|
||||
CastSize::InitialPending(pending) => pending,
|
||||
CastSize::Ready(size) => size,
|
||||
CastSize::ChangePending { pending, .. } => pending,
|
||||
}
|
||||
}
|
||||
|
||||
fn with_pending(self, pending: Size<i32, Physical>) -> Self {
|
||||
match self {
|
||||
CastSize::InitialPending(_) => CastSize::InitialPending(pending),
|
||||
CastSize::Ready(size) => CastSize::ChangePending {
|
||||
last_negotiated: size,
|
||||
pending,
|
||||
},
|
||||
CastSize::ChangePending {
|
||||
last_negotiated, ..
|
||||
} => CastSize::ChangePending {
|
||||
last_negotiated,
|
||||
pending,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_video_params(size: Size<i32, Physical>, refresh: u32, alpha: bool) -> pod::Object {
|
||||
let format = if alpha {
|
||||
VideoFormat::BGRA
|
||||
} else {
|
||||
VideoFormat::BGRx
|
||||
};
|
||||
|
||||
pod::object!(
|
||||
SpaTypes::ObjectParamFormat,
|
||||
ParamType::EnumFormat,
|
||||
pod::property!(FormatProperties::MediaType, Id, MediaType::Video),
|
||||
pod::property!(FormatProperties::MediaSubtype, Id, MediaSubtype::Raw),
|
||||
pod::property!(FormatProperties::VideoFormat, Id, format),
|
||||
Property {
|
||||
key: FormatProperties::VideoModifier.as_raw(),
|
||||
value: pod::Value::Long(u64::from(Modifier::Invalid) as i64),
|
||||
flags: PropertyFlags::MANDATORY,
|
||||
},
|
||||
pod::property!(
|
||||
FormatProperties::VideoSize,
|
||||
Rectangle,
|
||||
Rectangle {
|
||||
width: size.w as u32,
|
||||
height: size.h as u32,
|
||||
}
|
||||
),
|
||||
pod::property!(
|
||||
FormatProperties::VideoFramerate,
|
||||
Fraction,
|
||||
Fraction { num: 0, denom: 1 }
|
||||
),
|
||||
pod::property!(
|
||||
FormatProperties::VideoMaxFramerate,
|
||||
Choice,
|
||||
Range,
|
||||
Fraction,
|
||||
Fraction {
|
||||
num: refresh,
|
||||
denom: 1000
|
||||
},
|
||||
Fraction { num: 1, denom: 1 },
|
||||
Fraction {
|
||||
num: refresh,
|
||||
denom: 1000
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn make_pod(buffer: &mut Vec<u8>, object: pod::Object) -> &Pod {
|
||||
PodSerializer::serialize(Cursor::new(&mut *buffer), &pod::Value::Object(object)).unwrap();
|
||||
Pod::from_bytes(buffer).unwrap()
|
||||
|
||||
@@ -26,27 +26,30 @@ pub struct BorderRenderElement {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
struct Parameters {
|
||||
size: Size<i32, Logical>,
|
||||
gradient_area: Rectangle<i32, Logical>,
|
||||
size: Size<f64, Logical>,
|
||||
gradient_area: Rectangle<f64, Logical>,
|
||||
color_from: [f32; 4],
|
||||
color_to: [f32; 4],
|
||||
angle: f32,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
border_width: f32,
|
||||
corner_radius: CornerRadius,
|
||||
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
impl BorderRenderElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
size: Size<i32, Logical>,
|
||||
gradient_area: Rectangle<i32, Logical>,
|
||||
size: Size<f64, Logical>,
|
||||
gradient_area: Rectangle<f64, Logical>,
|
||||
color_from: [f32; 4],
|
||||
color_to: [f32; 4],
|
||||
angle: f32,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
border_width: f32,
|
||||
corner_radius: CornerRadius,
|
||||
scale: f32,
|
||||
) -> Self {
|
||||
let inner = ShaderRenderElement::empty(ProgramType::Border, Kind::Unspecified);
|
||||
let mut rv = Self {
|
||||
@@ -60,6 +63,7 @@ impl BorderRenderElement {
|
||||
geometry,
|
||||
border_width,
|
||||
corner_radius,
|
||||
scale,
|
||||
},
|
||||
};
|
||||
rv.update_inner();
|
||||
@@ -79,6 +83,7 @@ impl BorderRenderElement {
|
||||
geometry: Default::default(),
|
||||
border_width: 0.,
|
||||
corner_radius: Default::default(),
|
||||
scale: 1.,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -90,14 +95,15 @@ impl BorderRenderElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
size: Size<i32, Logical>,
|
||||
gradient_area: Rectangle<i32, Logical>,
|
||||
size: Size<f64, Logical>,
|
||||
gradient_area: Rectangle<f64, Logical>,
|
||||
color_from: [f32; 4],
|
||||
color_to: [f32; 4],
|
||||
angle: f32,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
border_width: f32,
|
||||
corner_radius: CornerRadius,
|
||||
scale: f32,
|
||||
) {
|
||||
let params = Parameters {
|
||||
size,
|
||||
@@ -108,6 +114,7 @@ impl BorderRenderElement {
|
||||
geometry,
|
||||
border_width,
|
||||
corner_radius,
|
||||
scale,
|
||||
};
|
||||
if self.params == params {
|
||||
return;
|
||||
@@ -127,6 +134,7 @@ impl BorderRenderElement {
|
||||
geometry,
|
||||
border_width,
|
||||
corner_radius,
|
||||
scale,
|
||||
} = self.params;
|
||||
|
||||
let grad_offset = geometry.loc - gradient_area.loc;
|
||||
@@ -157,6 +165,7 @@ impl BorderRenderElement {
|
||||
self.inner.update(
|
||||
size,
|
||||
None,
|
||||
scale,
|
||||
vec![
|
||||
Uniform::new("color_from", color_from),
|
||||
Uniform::new("color_to", color_to),
|
||||
@@ -172,7 +181,7 @@ impl BorderRenderElement {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn with_location(mut self, location: Point<i32, Logical>) -> Self {
|
||||
pub fn with_location(mut self, location: Point<f64, Logical>) -> Self {
|
||||
self.inner = self.inner.with_location(location);
|
||||
self
|
||||
}
|
||||
@@ -239,8 +248,9 @@ impl RenderElement<GlesRenderer> for BorderRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage)
|
||||
RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage, opaque_regions)
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, renderer: &mut GlesRenderer) -> Option<UnderlyingStorage> {
|
||||
@@ -255,8 +265,9 @@ impl<'render> RenderElement<TtyRenderer<'render>> for BorderRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
RenderElement::<TtyRenderer<'_>>::draw(&self.inner, frame, src, dst, damage)
|
||||
RenderElement::<TtyRenderer<'_>>::draw(&self.inner, frame, src, dst, damage, opaque_regions)
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, renderer: &mut TtyRenderer<'render>) -> Option<UnderlyingStorage> {
|
||||
|
||||
@@ -18,8 +18,10 @@ pub struct ClippedSurfaceRenderElement<R: NiriRenderer> {
|
||||
inner: WaylandSurfaceRenderElement<R>,
|
||||
program: GlesTexProgram,
|
||||
corner_radius: CornerRadius,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
input_to_geo: Mat3,
|
||||
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
@@ -32,7 +34,7 @@ impl<R: NiriRenderer> ClippedSurfaceRenderElement<R> {
|
||||
pub fn new(
|
||||
elem: WaylandSurfaceRenderElement<R>,
|
||||
scale: Scale<f64>,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
program: GlesTexProgram,
|
||||
corner_radius: CornerRadius,
|
||||
) -> Self {
|
||||
@@ -76,6 +78,7 @@ impl<R: NiriRenderer> ClippedSurfaceRenderElement<R> {
|
||||
corner_radius,
|
||||
geometry,
|
||||
input_to_geo,
|
||||
scale: scale.x as f32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +89,7 @@ impl<R: NiriRenderer> ClippedSurfaceRenderElement<R> {
|
||||
pub fn will_clip(
|
||||
elem: &WaylandSurfaceRenderElement<R>,
|
||||
scale: Scale<f64>,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
corner_radius: CornerRadius,
|
||||
) -> bool {
|
||||
let elem_geo = elem.geometry(scale);
|
||||
@@ -95,10 +98,10 @@ impl<R: NiriRenderer> ClippedSurfaceRenderElement<R> {
|
||||
if corner_radius == CornerRadius::default() {
|
||||
!geo.contains_rect(elem_geo)
|
||||
} else {
|
||||
let corners = Self::rounded_corners(geometry.to_f64(), corner_radius);
|
||||
let corners = Self::rounded_corners(geometry, corner_radius);
|
||||
let corners = corners
|
||||
.into_iter()
|
||||
.map(|rect| rect.to_physical_precise_round(scale));
|
||||
.map(|rect| rect.to_physical_precise_up(scale));
|
||||
let geo = Rectangle::subtract_rects_many([geo], corners);
|
||||
!Rectangle::subtract_rects_many([elem_geo], geo).is_empty()
|
||||
}
|
||||
@@ -186,11 +189,11 @@ impl<R: NiriRenderer> Element for ClippedSurfaceRenderElement<R> {
|
||||
if self.corner_radius == CornerRadius::default() {
|
||||
regions.collect()
|
||||
} else {
|
||||
let corners = Self::rounded_corners(self.geometry.to_f64(), self.corner_radius);
|
||||
let corners = Self::rounded_corners(self.geometry, self.corner_radius);
|
||||
|
||||
let elem_loc = self.geometry(scale).loc;
|
||||
let corners = corners.into_iter().map(|rect| {
|
||||
let mut rect = rect.to_physical_precise_round(scale);
|
||||
let mut rect = rect.to_physical_precise_up(scale);
|
||||
rect.loc -= elem_loc;
|
||||
rect
|
||||
});
|
||||
@@ -215,10 +218,12 @@ impl RenderElement<GlesRenderer> for ClippedSurfaceRenderElement<GlesRenderer> {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
frame.override_default_tex_program(
|
||||
self.program.clone(),
|
||||
vec![
|
||||
Uniform::new("niri_scale", self.scale),
|
||||
Uniform::new(
|
||||
"geo_size",
|
||||
(self.geometry.size.w as f32, self.geometry.size.h as f32),
|
||||
@@ -227,7 +232,7 @@ impl RenderElement<GlesRenderer> for ClippedSurfaceRenderElement<GlesRenderer> {
|
||||
mat3_uniform("input_to_geo", self.input_to_geo),
|
||||
],
|
||||
);
|
||||
RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(&self.inner, frame, src, dst, damage, opaque_regions)?;
|
||||
frame.clear_tex_program_override();
|
||||
Ok(())
|
||||
}
|
||||
@@ -248,6 +253,7 @@ impl<'render> RenderElement<TtyRenderer<'render>>
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
frame.as_gles_frame().override_default_tex_program(
|
||||
self.program.clone(),
|
||||
@@ -260,7 +266,7 @@ impl<'render> RenderElement<TtyRenderer<'render>>
|
||||
mat3_uniform("input_to_geo", self.input_to_geo),
|
||||
],
|
||||
);
|
||||
RenderElement::draw(&self.inner, frame, src, dst, damage)?;
|
||||
RenderElement::draw(&self.inner, frame, src, dst, damage, opaque_regions)?;
|
||||
frame.as_gles_frame().clear_tex_program_override();
|
||||
Ok(())
|
||||
}
|
||||
@@ -276,7 +282,7 @@ impl<'render> RenderElement<TtyRenderer<'render>>
|
||||
}
|
||||
|
||||
impl RoundedCornerDamage {
|
||||
pub fn set_size(&mut self, size: Size<i32, Logical>) {
|
||||
pub fn set_size(&mut self, size: Size<f64, Logical>) {
|
||||
self.damage.set_size(size);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size};
|
||||
pub struct ExtraDamage {
|
||||
id: Id,
|
||||
commit: CommitCounter,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
}
|
||||
|
||||
impl ExtraDamage {
|
||||
@@ -19,7 +19,7 @@ impl ExtraDamage {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_size(&mut self, size: Size<i32, Logical>) {
|
||||
pub fn set_size(&mut self, size: Size<f64, Logical>) {
|
||||
if self.geometry.size == size {
|
||||
return;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ impl ExtraDamage {
|
||||
self.commit.increment();
|
||||
}
|
||||
|
||||
pub fn with_location(mut self, location: Point<i32, Logical>) -> Self {
|
||||
pub fn with_location(mut self, location: Point<f64, Logical>) -> Self {
|
||||
self.geometry.loc = location;
|
||||
self
|
||||
}
|
||||
@@ -58,7 +58,7 @@ impl Element for ExtraDamage {
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.geometry.to_physical_precise_round(scale)
|
||||
self.geometry.to_physical_precise_up(scale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ impl<R: Renderer> RenderElement<R> for ExtraDamage {
|
||||
_src: Rectangle<f64, Buffer>,
|
||||
_dst: Rectangle<i32, Physical>,
|
||||
_damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), R::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use smithay::backend::allocator::format::get_bpp;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::utils::{Buffer, Logical, Scale, Size, Transform};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MemoryBuffer {
|
||||
data: Arc<[u8]>,
|
||||
format: Fourcc,
|
||||
size: Size<i32, Buffer>,
|
||||
scale: Scale<f64>,
|
||||
transform: Transform,
|
||||
}
|
||||
|
||||
impl MemoryBuffer {
|
||||
pub fn new(
|
||||
data: impl Into<Arc<[u8]>>,
|
||||
format: Fourcc,
|
||||
size: impl Into<Size<i32, Buffer>>,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
transform: Transform,
|
||||
) -> Self {
|
||||
let data = data.into();
|
||||
|
||||
let size = size.into();
|
||||
let stride =
|
||||
size.w * (get_bpp(format).expect("Format with unknown bits per pixel") / 8) as i32;
|
||||
assert!(data.len() >= (stride * size.h) as usize);
|
||||
|
||||
Self {
|
||||
data,
|
||||
format,
|
||||
size,
|
||||
scale: scale.into(),
|
||||
transform,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn format(&self) -> Fourcc {
|
||||
self.format
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Size<i32, Buffer> {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn scale(&self) -> Scale<f64> {
|
||||
self.scale
|
||||
}
|
||||
|
||||
pub fn transform(&self) -> Transform {
|
||||
self.transform
|
||||
}
|
||||
|
||||
pub fn logical_size(&self) -> Size<f64, Logical> {
|
||||
self.size.to_f64().to_logical(self.scale, self.transform)
|
||||
}
|
||||
}
|
||||
+17
-22
@@ -3,8 +3,6 @@ use std::ptr;
|
||||
use anyhow::{ensure, Context};
|
||||
use niri_config::BlockOutFrom;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderElement};
|
||||
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
|
||||
use smithay::backend::renderer::element::{Kind, RenderElement};
|
||||
use smithay::backend::renderer::gles::{GlesMapping, GlesRenderer, GlesTexture};
|
||||
@@ -14,13 +12,16 @@ use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
|
||||
use smithay::reexports::wayland_server::protocol::wl_shm;
|
||||
use smithay::utils::{Logical, Physical, Point, Rectangle, Scale, Size, Transform};
|
||||
use smithay::wayland::shm;
|
||||
use solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
|
||||
use self::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use self::texture::{TextureBuffer, TextureRenderElement};
|
||||
|
||||
pub mod border;
|
||||
pub mod clipped_surface;
|
||||
pub mod damage;
|
||||
pub mod debug;
|
||||
pub mod memory;
|
||||
pub mod offscreen;
|
||||
pub mod primary_gpu_texture;
|
||||
pub mod render_elements;
|
||||
@@ -30,7 +31,9 @@ pub mod resources;
|
||||
pub mod shader_element;
|
||||
pub mod shaders;
|
||||
pub mod snapshot;
|
||||
pub mod solid_color;
|
||||
pub mod surface;
|
||||
pub mod texture;
|
||||
|
||||
/// What we're rendering for.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -47,7 +50,7 @@ pub enum RenderTarget {
|
||||
#[derive(Debug)]
|
||||
pub struct BakedBuffer<B> {
|
||||
pub buffer: B,
|
||||
pub location: Point<i32, Logical>,
|
||||
pub location: Point<f64, Logical>,
|
||||
pub src: Option<Rectangle<f64, Logical>>,
|
||||
pub dst: Option<Size<i32, Logical>>,
|
||||
}
|
||||
@@ -64,7 +67,7 @@ pub trait ToRenderElement {
|
||||
|
||||
fn to_render_element(
|
||||
&self,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
kind: Kind,
|
||||
@@ -116,17 +119,17 @@ impl ToRenderElement for BakedBuffer<TextureBuffer<GlesTexture>> {
|
||||
|
||||
fn to_render_element(
|
||||
&self,
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
location: Point<f64, Logical>,
|
||||
_scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
kind: Kind,
|
||||
) -> Self::RenderElement {
|
||||
let elem = TextureRenderElement::from_texture_buffer(
|
||||
(location + self.location).to_physical_precise_round(scale),
|
||||
&self.buffer,
|
||||
Some(alpha),
|
||||
self.buffer.clone(),
|
||||
location + self.location,
|
||||
alpha,
|
||||
self.src,
|
||||
self.dst,
|
||||
self.dst.map(|dst| dst.to_f64()),
|
||||
kind,
|
||||
);
|
||||
PrimaryGpuTextureRenderElement(elem)
|
||||
@@ -138,20 +141,12 @@ impl ToRenderElement for BakedBuffer<SolidColorBuffer> {
|
||||
|
||||
fn to_render_element(
|
||||
&self,
|
||||
location: Point<i32, Logical>,
|
||||
scale: Scale<f64>,
|
||||
location: Point<f64, Logical>,
|
||||
_scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
kind: Kind,
|
||||
) -> Self::RenderElement {
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&self.buffer,
|
||||
(location + self.location)
|
||||
.to_physical_precise_round(scale)
|
||||
.to_i32_round(),
|
||||
scale,
|
||||
alpha,
|
||||
kind,
|
||||
)
|
||||
SolidColorRenderElement::from_buffer(&self.buffer, location + self.location, alpha, kind)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +312,7 @@ fn render_elements(
|
||||
if let Some(mut damage) = output_rect.intersection(dst) {
|
||||
damage.loc -= dst.loc;
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[damage])
|
||||
.draw(&mut frame, src, dst, &[damage], &[])
|
||||
.context("error drawing element")?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderElement};
|
||||
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
|
||||
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer};
|
||||
@@ -10,6 +9,7 @@ use smithay::utils::{Buffer, Physical, Rectangle, Scale, Transform};
|
||||
use super::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use super::render_to_texture;
|
||||
use super::renderer::AsGlesFrame;
|
||||
use super::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
|
||||
/// Renders elements into an off-screen buffer.
|
||||
@@ -59,12 +59,17 @@ impl OffscreenRenderElement {
|
||||
elements,
|
||||
) {
|
||||
Ok((texture, _sync_point)) => {
|
||||
let buffer =
|
||||
TextureBuffer::from_texture(renderer, texture, scale, Transform::Normal, None);
|
||||
let buffer = TextureBuffer::from_texture(
|
||||
renderer,
|
||||
texture,
|
||||
scale as f64,
|
||||
Transform::Normal,
|
||||
Vec::new(),
|
||||
);
|
||||
let element = TextureRenderElement::from_texture_buffer(
|
||||
geo.loc.to_f64(),
|
||||
&buffer,
|
||||
Some(result_alpha),
|
||||
buffer,
|
||||
geo.loc.to_f64().to_logical(scale as f64),
|
||||
result_alpha,
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
@@ -170,12 +175,27 @@ impl RenderElement<GlesRenderer> for OffscreenRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
if let Some(texture) = &self.texture {
|
||||
RenderElement::<GlesRenderer>::draw(texture, gles_frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(
|
||||
texture,
|
||||
gles_frame,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
)?;
|
||||
} else {
|
||||
RenderElement::<GlesRenderer>::draw(&self.fallback, gles_frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(
|
||||
&self.fallback,
|
||||
gles_frame,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -196,12 +216,27 @@ impl<'render> RenderElement<TtyRenderer<'render>> for OffscreenRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
if let Some(texture) = &self.texture {
|
||||
RenderElement::<GlesRenderer>::draw(texture, gles_frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(
|
||||
texture,
|
||||
gles_frame,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
)?;
|
||||
} else {
|
||||
RenderElement::<GlesRenderer>::draw(&self.fallback, gles_frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(
|
||||
&self.fallback,
|
||||
gles_frame,
|
||||
src,
|
||||
dst,
|
||||
damage,
|
||||
opaque_regions,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use smithay::backend::renderer::element::texture::TextureRenderElement;
|
||||
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, DamageSet, OpaqueRegions};
|
||||
use smithay::utils::{Buffer, Physical, Rectangle, Scale, Transform};
|
||||
|
||||
use super::renderer::AsGlesFrame;
|
||||
use super::texture::TextureRenderElement;
|
||||
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
|
||||
|
||||
/// Wrapper for a texture from the primary GPU for rendering with the primary GPU.
|
||||
@@ -60,9 +60,10 @@ impl RenderElement<GlesRenderer> for PrimaryGpuTextureRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage, opaque_regions)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -80,9 +81,10 @@ impl<'render> RenderElement<TtyRenderer<'render>> for PrimaryGpuTextureRenderEle
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage, opaque_regions)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -103,10 +103,11 @@ macro_rules! niri_render_elements {
|
||||
src: smithay::utils::Rectangle<f64, smithay::utils::Buffer>,
|
||||
dst: smithay::utils::Rectangle<i32, smithay::utils::Physical>,
|
||||
damage: &[smithay::utils::Rectangle<i32, smithay::utils::Physical>],
|
||||
opaque_regions: &[smithay::utils::Rectangle<i32, smithay::utils::Physical>],
|
||||
) -> Result<(), smithay::backend::renderer::gles::GlesError> {
|
||||
match self {
|
||||
$($name::$variant(elem) => {
|
||||
smithay::backend::renderer::element::RenderElement::<smithay::backend::renderer::gles::GlesRenderer>::draw(elem, frame, src, dst, damage)
|
||||
smithay::backend::renderer::element::RenderElement::<smithay::backend::renderer::gles::GlesRenderer>::draw(elem, frame, src, dst, damage, opaque_regions)
|
||||
})+
|
||||
}
|
||||
}
|
||||
@@ -127,10 +128,11 @@ macro_rules! niri_render_elements {
|
||||
src: smithay::utils::Rectangle<f64, smithay::utils::Buffer>,
|
||||
dst: smithay::utils::Rectangle<i32, smithay::utils::Physical>,
|
||||
damage: &[smithay::utils::Rectangle<i32, smithay::utils::Physical>],
|
||||
opaque_regions: &[smithay::utils::Rectangle<i32, smithay::utils::Physical>],
|
||||
) -> Result<(), $crate::backend::tty::TtyRendererError<'render>> {
|
||||
match self {
|
||||
$($name::$variant(elem) => {
|
||||
smithay::backend::renderer::element::RenderElement::<$crate::backend::tty::TtyRenderer<'render>>::draw(elem, frame, src, dst, damage)
|
||||
smithay::backend::renderer::element::RenderElement::<$crate::backend::tty::TtyRenderer<'render>>::draw(elem, frame, src, dst, damage, opaque_regions)
|
||||
})+
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ pub struct ResizeRenderElement(ShaderRenderElement);
|
||||
impl ResizeRenderElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
area: Rectangle<i32, Logical>,
|
||||
area: Rectangle<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
texture_prev: (GlesTexture, Rectangle<i32, Physical>),
|
||||
size_prev: Size<i32, Logical>,
|
||||
size_prev: Size<f64, Logical>,
|
||||
texture_next: (GlesTexture, Rectangle<i32, Physical>),
|
||||
size_next: Size<i32, Logical>,
|
||||
size_next: Size<f64, Logical>,
|
||||
progress: f32,
|
||||
clamped_progress: f32,
|
||||
corner_radius: CornerRadius,
|
||||
@@ -35,17 +35,17 @@ impl ResizeRenderElement {
|
||||
let (texture_prev, tex_prev_geo) = texture_prev;
|
||||
let (texture_next, tex_next_geo) = texture_next;
|
||||
|
||||
let scale_prev = area.size.to_f64() / size_prev.to_f64();
|
||||
let scale_next = area.size.to_f64() / size_next.to_f64();
|
||||
let scale_prev = area.size / size_prev;
|
||||
let scale_next = area.size / size_next;
|
||||
|
||||
// Compute the area necessary to fit a crossfade.
|
||||
let tex_prev_geo_scaled = tex_prev_geo.to_f64().upscale(scale_prev);
|
||||
let tex_next_geo_scaled = tex_next_geo.to_f64().upscale(scale_next);
|
||||
let combined_geo = tex_prev_geo_scaled.merge(tex_next_geo_scaled);
|
||||
let combined_geo = tex_prev_geo_scaled.merge(tex_next_geo_scaled).to_i32_up();
|
||||
|
||||
let area = Rectangle::from_loc_and_size(
|
||||
area.loc + combined_geo.loc.to_logical(scale).to_i32_round(),
|
||||
combined_geo.size.to_logical(scale).to_i32_round(),
|
||||
area.loc + combined_geo.loc.to_logical(scale),
|
||||
combined_geo.size.to_logical(scale),
|
||||
);
|
||||
|
||||
// Convert Smithay types into glam types.
|
||||
@@ -87,6 +87,7 @@ impl ResizeRenderElement {
|
||||
ProgramType::Resize,
|
||||
area.size,
|
||||
None,
|
||||
scale.x,
|
||||
result_alpha,
|
||||
vec![
|
||||
mat3_uniform("niri_input_to_curr_geo", input_to_curr_geo),
|
||||
@@ -166,8 +167,9 @@ impl RenderElement<GlesRenderer> for ResizeRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, frame, src, dst, damage, opaque_regions)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -183,9 +185,10 @@ impl<'render> RenderElement<TtyRenderer<'render>> for ResizeRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let gles_frame = frame.as_gles_frame();
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(&self.0, gles_frame, src, dst, damage, opaque_regions)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ pub struct ShaderRenderElement {
|
||||
program: ProgramType,
|
||||
id: Id,
|
||||
commit_counter: CommitCounter,
|
||||
area: Rectangle<i32, Logical>,
|
||||
opaque_regions: Vec<Rectangle<i32, Logical>>,
|
||||
area: Rectangle<f64, Logical>,
|
||||
opaque_regions: Vec<Rectangle<f64, Logical>>,
|
||||
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
|
||||
scale: f32,
|
||||
alpha: f32,
|
||||
additional_uniforms: Vec<Uniform<'static>>,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
@@ -47,6 +49,7 @@ struct ShaderProgramInternal {
|
||||
uniform_tex_matrix: ffi::types::GLint,
|
||||
uniform_matrix: ffi::types::GLint,
|
||||
uniform_size: ffi::types::GLint,
|
||||
uniform_scale: ffi::types::GLint,
|
||||
uniform_alpha: ffi::types::GLint,
|
||||
attrib_vert: ffi::types::GLint,
|
||||
attrib_vert_position: ffi::types::GLint,
|
||||
@@ -78,6 +81,7 @@ unsafe fn compile_program(
|
||||
let matrix = CStr::from_bytes_with_nul(b"matrix\0").expect("NULL terminated");
|
||||
let tex_matrix = CStr::from_bytes_with_nul(b"tex_matrix\0").expect("NULL terminated");
|
||||
let size = CStr::from_bytes_with_nul(b"niri_size\0").expect("NULL terminated");
|
||||
let scale = CStr::from_bytes_with_nul(b"niri_scale\0").expect("NULL terminated");
|
||||
let alpha = CStr::from_bytes_with_nul(b"niri_alpha\0").expect("NULL terminated");
|
||||
let tint = CStr::from_bytes_with_nul(b"niri_tint\0").expect("NULL terminated");
|
||||
|
||||
@@ -90,6 +94,8 @@ unsafe fn compile_program(
|
||||
.GetUniformLocation(program, tex_matrix.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_size: gl
|
||||
.GetUniformLocation(program, size.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_scale: gl
|
||||
.GetUniformLocation(program, scale.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_alpha: gl
|
||||
.GetUniformLocation(program, alpha.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert: gl.GetAttribLocation(program, vert.as_ptr() as *const ffi::types::GLchar),
|
||||
@@ -131,6 +137,8 @@ unsafe fn compile_program(
|
||||
),
|
||||
uniform_size: gl
|
||||
.GetUniformLocation(debug_program, size.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_scale: gl
|
||||
.GetUniformLocation(debug_program, scale.as_ptr() as *const ffi::types::GLchar),
|
||||
uniform_alpha: gl
|
||||
.GetUniformLocation(debug_program, alpha.as_ptr() as *const ffi::types::GLchar),
|
||||
attrib_vert: gl
|
||||
@@ -198,8 +206,10 @@ impl ShaderRenderElement {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
program: ProgramType,
|
||||
size: Size<i32, Logical>,
|
||||
opaque_regions: Option<Vec<Rectangle<i32, Logical>>>,
|
||||
size: Size<f64, Logical>,
|
||||
opaque_regions: Option<Vec<Rectangle<f64, Logical>>>,
|
||||
// Should only be used for visual improvements, i.e. corner radius anti-aliasing.
|
||||
scale: f32,
|
||||
alpha: f32,
|
||||
uniforms: Vec<Uniform<'_>>,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
@@ -209,8 +219,9 @@ impl ShaderRenderElement {
|
||||
program,
|
||||
id: Id::new(),
|
||||
commit_counter: CommitCounter::default(),
|
||||
area: Rectangle::from_loc_and_size((0, 0), size),
|
||||
area: Rectangle::from_loc_and_size((0., 0.), size),
|
||||
opaque_regions: opaque_regions.unwrap_or_default(),
|
||||
scale,
|
||||
alpha,
|
||||
additional_uniforms: uniforms.into_iter().map(|u| u.into_owned()).collect(),
|
||||
textures,
|
||||
@@ -225,6 +236,7 @@ impl ShaderRenderElement {
|
||||
commit_counter: CommitCounter::default(),
|
||||
area: Rectangle::default(),
|
||||
opaque_regions: vec![],
|
||||
scale: 1.,
|
||||
alpha: 1.,
|
||||
additional_uniforms: vec![],
|
||||
textures: HashMap::new(),
|
||||
@@ -238,20 +250,22 @@ impl ShaderRenderElement {
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
size: Size<i32, Logical>,
|
||||
opaque_regions: Option<Vec<Rectangle<i32, Logical>>>,
|
||||
size: Size<f64, Logical>,
|
||||
opaque_regions: Option<Vec<Rectangle<f64, Logical>>>,
|
||||
scale: f32,
|
||||
uniforms: Vec<Uniform<'_>>,
|
||||
textures: HashMap<String, GlesTexture>,
|
||||
) {
|
||||
self.area.size = size;
|
||||
self.opaque_regions = opaque_regions.unwrap_or_default();
|
||||
self.scale = scale;
|
||||
self.additional_uniforms = uniforms.into_iter().map(|u| u.into_owned()).collect();
|
||||
self.textures = textures;
|
||||
|
||||
self.commit_counter.increment();
|
||||
}
|
||||
|
||||
pub fn with_location(mut self, location: Point<i32, Logical>) -> Self {
|
||||
pub fn with_location(mut self, location: Point<f64, Logical>) -> Self {
|
||||
self.area.loc = location;
|
||||
self
|
||||
}
|
||||
@@ -277,7 +291,7 @@ impl Element for ShaderRenderElement {
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
self.opaque_regions
|
||||
.iter()
|
||||
.map(|region| region.to_physical_precise_round(scale))
|
||||
.map(|region| region.to_physical_precise_down(scale))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -297,6 +311,7 @@ impl RenderElement<GlesRenderer> for ShaderRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dest: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), GlesError> {
|
||||
let frame = frame.as_gles_frame();
|
||||
|
||||
@@ -421,6 +436,7 @@ impl RenderElement<GlesRenderer> for ShaderRenderElement {
|
||||
tex_matrix.as_ref().as_ptr(),
|
||||
);
|
||||
gl.Uniform2f(program.uniform_size, dest.size.w as f32, dest.size.h as f32);
|
||||
gl.Uniform1f(program.uniform_scale, self.scale);
|
||||
gl.Uniform1f(program.uniform_alpha, self.alpha);
|
||||
|
||||
let tint = if has_tint { 1.0f32 } else { 0.0f32 };
|
||||
@@ -526,10 +542,11 @@ impl<'render> RenderElement<TtyRenderer<'render>> for ShaderRenderElement {
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), TtyRendererError<'render>> {
|
||||
let frame = frame.as_gles_frame();
|
||||
|
||||
RenderElement::<GlesRenderer>::draw(self, frame, src, dst, damage)?;
|
||||
RenderElement::<GlesRenderer>::draw(self, frame, src, dst, damage, opaque_regions)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ uniform float niri_tint;
|
||||
#endif
|
||||
|
||||
uniform float niri_alpha;
|
||||
uniform float niri_scale;
|
||||
|
||||
uniform vec2 niri_size;
|
||||
varying vec2 niri_v_coords;
|
||||
@@ -56,7 +57,8 @@ float rounding_alpha(vec2 coords, vec2 size, vec4 corner_radius) {
|
||||
}
|
||||
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
||||
float half_px = 0.5 / niri_scale;
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -20,6 +20,8 @@ varying vec2 v_coords;
|
||||
uniform float tint;
|
||||
#endif
|
||||
|
||||
uniform float niri_scale;
|
||||
|
||||
uniform vec2 geo_size;
|
||||
uniform vec4 corner_radius;
|
||||
uniform mat3 input_to_geo;
|
||||
@@ -45,7 +47,8 @@ float rounding_alpha(vec2 coords, vec2 size) {
|
||||
}
|
||||
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
||||
float half_px = 0.5 / niri_scale;
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -18,4 +18,5 @@ uniform float niri_clamped_progress;
|
||||
uniform float niri_random_seed;
|
||||
|
||||
uniform float niri_alpha;
|
||||
uniform float niri_scale;
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ impl Shaders {
|
||||
.compile_custom_texture_shader(
|
||||
include_str!("clipped_surface.frag"),
|
||||
&[
|
||||
UniformName::new("niri_scale", UniformType::_1f),
|
||||
UniformName::new("geo_size", UniformType::_2f),
|
||||
UniformName::new("corner_radius", UniformType::_4f),
|
||||
UniformName::new("input_to_geo", UniformType::Matrix3x3),
|
||||
|
||||
@@ -18,4 +18,5 @@ uniform float niri_clamped_progress;
|
||||
uniform float niri_random_seed;
|
||||
|
||||
uniform float niri_alpha;
|
||||
uniform float niri_scale;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ uniform vec4 niri_corner_radius;
|
||||
uniform float niri_clip_to_geometry;
|
||||
|
||||
uniform float niri_alpha;
|
||||
uniform float niri_scale;
|
||||
|
||||
float niri_rounding_alpha(vec2 coords, vec2 size) {
|
||||
vec2 center;
|
||||
@@ -47,5 +48,6 @@ float niri_rounding_alpha(vec2 coords, vec2 size) {
|
||||
}
|
||||
|
||||
float dist = distance(coords, center);
|
||||
return 1.0 - smoothstep(radius - 0.5, radius + 0.5, dist);
|
||||
float half_px = 0.5 / niri_scale;
|
||||
return 1.0 - smoothstep(radius - half_px, radius + half_px, dist);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub struct RenderSnapshot<C, B> {
|
||||
pub block_out_from: Option<BlockOutFrom>,
|
||||
|
||||
/// Visual size of the element at the point of the snapshot.
|
||||
pub size: Size<i32, Logical>,
|
||||
pub size: Size<f64, Logical>,
|
||||
|
||||
/// Contents rendered into a texture (lazily).
|
||||
pub texture: OnceCell<Option<(GlesTexture, Rectangle<i32, Physical>)>>,
|
||||
@@ -55,7 +55,7 @@ where
|
||||
.blocked_out_contents
|
||||
.iter()
|
||||
.map(|baked| {
|
||||
baked.to_render_element(Point::from((0, 0)), scale, 1., Kind::Unspecified)
|
||||
baked.to_render_element(Point::from((0., 0.)), scale, 1., Kind::Unspecified)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -81,7 +81,7 @@ where
|
||||
.contents
|
||||
.iter()
|
||||
.map(|baked| {
|
||||
baked.to_render_element(Point::from((0, 0)), scale, 1., Kind::Unspecified)
|
||||
baked.to_render_element(Point::from((0., 0.)), scale, 1., Kind::Unspecified)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, OpaqueRegions};
|
||||
use smithay::backend::renderer::{Frame as _, Renderer};
|
||||
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size};
|
||||
|
||||
/// Smithay's solid color buffer, but with fractional scale.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SolidColorBuffer {
|
||||
id: Id,
|
||||
size: Size<f64, Logical>,
|
||||
commit: CommitCounter,
|
||||
color: [f32; 4],
|
||||
}
|
||||
|
||||
/// Render element for a [`SolidColorBuffer`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SolidColorRenderElement {
|
||||
id: Id,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
commit: CommitCounter,
|
||||
color: [f32; 4],
|
||||
kind: Kind,
|
||||
}
|
||||
|
||||
impl Default for SolidColorBuffer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: Id::new(),
|
||||
size: Default::default(),
|
||||
commit: Default::default(),
|
||||
color: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SolidColorBuffer {
|
||||
pub fn new(size: impl Into<Size<f64, Logical>>, color: [f32; 4]) -> Self {
|
||||
SolidColorBuffer {
|
||||
id: Id::new(),
|
||||
color,
|
||||
commit: CommitCounter::default(),
|
||||
size: size.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: impl Into<Size<f64, Logical>>) {
|
||||
let size = size.into();
|
||||
if size != self.size {
|
||||
self.size = size;
|
||||
self.commit.increment();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, color: [f32; 4]) {
|
||||
if color != self.color {
|
||||
self.color = color;
|
||||
self.commit.increment();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, size: impl Into<Size<f64, Logical>>, color: [f32; 4]) {
|
||||
let size = size.into();
|
||||
if size != self.size || color != self.color {
|
||||
self.size = size;
|
||||
self.color = color;
|
||||
self.commit.increment();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(&self) -> [f32; 4] {
|
||||
self.color
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Size<f64, Logical> {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl SolidColorRenderElement {
|
||||
pub fn from_buffer(
|
||||
buffer: &SolidColorBuffer,
|
||||
location: impl Into<Point<f64, Logical>>,
|
||||
alpha: f32,
|
||||
kind: Kind,
|
||||
) -> Self {
|
||||
let geo = Rectangle::from_loc_and_size(location, buffer.size());
|
||||
let color = [
|
||||
buffer.color[0] * alpha,
|
||||
buffer.color[1] * alpha,
|
||||
buffer.color[2] * alpha,
|
||||
buffer.color[3] * alpha,
|
||||
];
|
||||
Self::new(buffer.id.clone(), geo, buffer.commit, color, kind)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
id: Id,
|
||||
geometry: Rectangle<f64, Logical>,
|
||||
commit: CommitCounter,
|
||||
color: [f32; 4],
|
||||
kind: Kind,
|
||||
) -> Self {
|
||||
SolidColorRenderElement {
|
||||
id,
|
||||
geometry,
|
||||
commit,
|
||||
color,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(&self) -> [f32; 4] {
|
||||
self.color
|
||||
}
|
||||
|
||||
pub fn geo(&self) -> Rectangle<f64, Logical> {
|
||||
self.geometry
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for SolidColorRenderElement {
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.commit
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
Rectangle::from_loc_and_size((0., 0.), (1., 1.))
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.geometry.to_physical_precise_round(scale)
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
if self.color[3] == 1f32 {
|
||||
let rect = Rectangle::from_loc_and_size((0., 0.), self.geometry.size)
|
||||
.to_physical_precise_down(scale);
|
||||
OpaqueRegions::from_slice(&[rect])
|
||||
} else {
|
||||
OpaqueRegions::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn alpha(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
fn kind(&self) -> Kind {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Renderer> RenderElement<R> for SolidColorRenderElement {
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut <R as Renderer>::Frame<'_>,
|
||||
_src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), <R as Renderer>::Error> {
|
||||
frame.draw_solid(dst, damage, self.color)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn underlying_storage(&self, _renderer: &mut R) -> Option<UnderlyingStorage<'_>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use smithay::backend::renderer::element::texture::TextureBuffer;
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::utils::{import_surface, RendererSurfaceStateUserData};
|
||||
use smithay::backend::renderer::Renderer as _;
|
||||
@@ -6,13 +5,14 @@ use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point};
|
||||
use smithay::wayland::compositor::{with_surface_tree_downward, TraversalAction};
|
||||
|
||||
use super::texture::TextureBuffer;
|
||||
use super::BakedBuffer;
|
||||
|
||||
/// Renders elements from a surface tree as textures into `storage`.
|
||||
pub fn render_snapshot_from_surface_tree(
|
||||
renderer: &mut GlesRenderer,
|
||||
surface: &WlSurface,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
storage: &mut Vec<BakedBuffer<TextureBuffer<GlesTexture>>>,
|
||||
) {
|
||||
let _span = tracy_client::span!("render_snapshot_from_surface_tree");
|
||||
@@ -28,7 +28,7 @@ pub fn render_snapshot_from_surface_tree(
|
||||
let data = &*data.borrow();
|
||||
|
||||
if let Some(view) = data.view() {
|
||||
location += view.offset;
|
||||
location += view.offset.to_f64();
|
||||
TraversalAction::DoChildren(location)
|
||||
} else {
|
||||
TraversalAction::SkipChildren
|
||||
@@ -43,7 +43,7 @@ pub fn render_snapshot_from_surface_tree(
|
||||
|
||||
if let Some(data) = data {
|
||||
if let Some(view) = data.borrow().view() {
|
||||
location += view.offset;
|
||||
location += view.offset.to_f64();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -62,9 +62,9 @@ pub fn render_snapshot_from_surface_tree(
|
||||
let buffer = TextureBuffer::from_texture(
|
||||
renderer,
|
||||
texture.clone(),
|
||||
data.buffer_scale(),
|
||||
f64::from(data.buffer_scale()),
|
||||
data.buffer_transform(),
|
||||
None,
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let baked = BakedBuffer {
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
|
||||
use smithay::backend::renderer::utils::{CommitCounter, OpaqueRegions};
|
||||
use smithay::backend::renderer::{Frame as _, ImportMem, Renderer, Texture};
|
||||
use smithay::utils::{Buffer, Logical, Physical, Point, Rectangle, Scale, Size, Transform};
|
||||
|
||||
use super::memory::MemoryBuffer;
|
||||
|
||||
/// Smithay's texture buffer, but with fractional scale.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextureBuffer<T> {
|
||||
id: Id,
|
||||
commit_counter: CommitCounter,
|
||||
renderer_id: usize,
|
||||
texture: T,
|
||||
scale: Scale<f64>,
|
||||
transform: Transform,
|
||||
opaque_regions: Vec<Rectangle<i32, Buffer>>,
|
||||
}
|
||||
|
||||
/// Render element for a [`TextureBuffer`].
|
||||
#[derive(Debug)]
|
||||
pub struct TextureRenderElement<T> {
|
||||
buffer: TextureBuffer<T>,
|
||||
location: Point<f64, Logical>,
|
||||
alpha: f32,
|
||||
src: Option<Rectangle<f64, Logical>>,
|
||||
size: Option<Size<f64, Logical>>,
|
||||
kind: Kind,
|
||||
}
|
||||
|
||||
impl<T> TextureBuffer<T> {
|
||||
pub fn from_texture<R: Renderer<TextureId = T>>(
|
||||
renderer: &R,
|
||||
texture: T,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
transform: Transform,
|
||||
opaque_regions: Vec<Rectangle<i32, Buffer>>,
|
||||
) -> Self {
|
||||
TextureBuffer {
|
||||
id: Id::new(),
|
||||
commit_counter: CommitCounter::default(),
|
||||
renderer_id: renderer.id(),
|
||||
texture,
|
||||
scale: scale.into(),
|
||||
transform,
|
||||
opaque_regions,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn from_memory<R: Renderer<TextureId = T> + ImportMem>(
|
||||
renderer: &mut R,
|
||||
data: &[u8],
|
||||
format: Fourcc,
|
||||
size: impl Into<Size<i32, Buffer>>,
|
||||
flipped: bool,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
transform: Transform,
|
||||
opaque_regions: Vec<Rectangle<i32, Buffer>>,
|
||||
) -> Result<Self, <R as Renderer>::Error> {
|
||||
let texture = renderer.import_memory(data, format, size.into(), flipped)?;
|
||||
Ok(TextureBuffer::from_texture(
|
||||
renderer,
|
||||
texture,
|
||||
scale,
|
||||
transform,
|
||||
opaque_regions,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn from_memory_buffer<R: Renderer<TextureId = T> + ImportMem>(
|
||||
renderer: &mut R,
|
||||
buffer: &MemoryBuffer,
|
||||
) -> Result<Self, <R as Renderer>::Error> {
|
||||
Self::from_memory(
|
||||
renderer,
|
||||
buffer.data(),
|
||||
buffer.format(),
|
||||
buffer.size(),
|
||||
false,
|
||||
buffer.scale(),
|
||||
buffer.transform(),
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn texture(&self) -> &T {
|
||||
&self.texture
|
||||
}
|
||||
|
||||
pub fn texture_scale(&self) -> Scale<f64> {
|
||||
self.scale
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture> TextureBuffer<T> {
|
||||
pub fn logical_size(&self) -> Size<f64, Logical> {
|
||||
self.texture
|
||||
.size()
|
||||
.to_f64()
|
||||
.to_logical(self.scale, self.transform)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TextureRenderElement<T> {
|
||||
pub fn from_texture_buffer(
|
||||
buffer: TextureBuffer<T>,
|
||||
location: impl Into<Point<f64, Logical>>,
|
||||
alpha: f32,
|
||||
src: Option<Rectangle<f64, Logical>>,
|
||||
size: Option<Size<f64, Logical>>,
|
||||
kind: Kind,
|
||||
) -> Self {
|
||||
TextureRenderElement {
|
||||
buffer,
|
||||
location: location.into(),
|
||||
alpha,
|
||||
src,
|
||||
size,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture> TextureRenderElement<T> {
|
||||
pub fn logical_size(&self) -> Size<f64, Logical> {
|
||||
self.size
|
||||
.or_else(|| self.src.map(|src| src.size))
|
||||
.unwrap_or_else(|| self.buffer.logical_size())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture> Element for TextureRenderElement<T> {
|
||||
fn id(&self) -> &Id {
|
||||
&self.buffer.id
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.buffer.commit_counter
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
let logical_geo = Rectangle::from_loc_and_size(self.location, self.logical_size());
|
||||
logical_geo.to_physical_precise_round(scale)
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
self.buffer.transform
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
self.src
|
||||
.map(|src| {
|
||||
src.to_buffer(
|
||||
self.buffer.scale,
|
||||
self.buffer.transform,
|
||||
&self.buffer.logical_size(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
Rectangle::from_loc_and_size((0, 0), self.buffer.texture.size()).to_f64()
|
||||
})
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
let texture_size = self.buffer.texture.size().to_f64();
|
||||
let src = self.src();
|
||||
|
||||
self.buffer
|
||||
.opaque_regions
|
||||
.iter()
|
||||
.filter_map(|region| {
|
||||
let mut region = region.to_f64().intersection(src)?;
|
||||
|
||||
region.loc -= src.loc;
|
||||
region.upscale(texture_size / src.size);
|
||||
|
||||
let logical =
|
||||
region.to_logical(self.buffer.scale, self.buffer.transform, &src.size);
|
||||
Some(logical.to_physical_precise_down(scale))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn alpha(&self) -> f32 {
|
||||
self.alpha
|
||||
}
|
||||
|
||||
fn kind(&self) -> Kind {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, T> RenderElement<R> for TextureRenderElement<T>
|
||||
where
|
||||
R: Renderer<TextureId = T>,
|
||||
T: Texture,
|
||||
{
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut <R as Renderer>::Frame<'_>,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dest: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
opaque_regions: &[Rectangle<i32, Physical>],
|
||||
) -> Result<(), <R as Renderer>::Error> {
|
||||
if frame.id() != self.buffer.renderer_id {
|
||||
warn!("trying to render texture from different renderer");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
frame.render_texture_from_to(
|
||||
&self.buffer.texture,
|
||||
src,
|
||||
dest,
|
||||
damage,
|
||||
opaque_regions,
|
||||
self.buffer.transform,
|
||||
self.alpha,
|
||||
)
|
||||
}
|
||||
|
||||
fn underlying_storage(&self, _renderer: &mut R) -> Option<UnderlyingStorage<'_>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,20 @@ use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use niri_config::Config;
|
||||
use ordered_float::NotNan;
|
||||
use pangocairo::cairo::{self, ImageSurface};
|
||||
use pangocairo::pango::FontDescription;
|
||||
use smithay::backend::renderer::element::memory::{
|
||||
MemoryRenderBuffer, MemoryRenderBufferRenderElement,
|
||||
};
|
||||
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
|
||||
use smithay::backend::renderer::element::{Element, Kind};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::gbm::Format as Fourcc;
|
||||
use smithay::utils::Transform;
|
||||
use smithay::utils::{Point, Transform};
|
||||
|
||||
use crate::animation::Animation;
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::utils::{output_size, to_physical_precise_round};
|
||||
|
||||
const TEXT: &str = "Failed to parse the config file. \
|
||||
Please run <span face='monospace' bgcolor='#000000'>niri validate</span> \
|
||||
@@ -28,7 +29,7 @@ const BORDER: i32 = 4;
|
||||
|
||||
pub struct ConfigErrorNotification {
|
||||
state: State,
|
||||
buffers: RefCell<HashMap<i32, Option<MemoryRenderBuffer>>>,
|
||||
buffers: RefCell<HashMap<NotNan<f64>, Option<TextureBuffer<GlesTexture>>>>,
|
||||
|
||||
// If set, this is a "Created config at {path}" notification. If unset, this is a config error
|
||||
// notification.
|
||||
@@ -44,9 +45,6 @@ enum State {
|
||||
Hiding(Animation),
|
||||
}
|
||||
|
||||
pub type ConfigErrorNotificationRenderElement<R> =
|
||||
RelocateRenderElement<MemoryRenderBufferRenderElement<R>>;
|
||||
|
||||
impl ConfigErrorNotification {
|
||||
pub fn new(config: Rc<RefCell<Config>>) -> Self {
|
||||
Self {
|
||||
@@ -128,59 +126,54 @@ impl ConfigErrorNotification {
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
output: &Output,
|
||||
) -> Option<ConfigErrorNotificationRenderElement<R>> {
|
||||
) -> Option<PrimaryGpuTextureRenderElement> {
|
||||
if matches!(self.state, State::Hidden) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
let output_size = output_size(output);
|
||||
let path = self.created_path.as_deref();
|
||||
|
||||
let mut buffers = self.buffers.borrow_mut();
|
||||
let buffer = buffers
|
||||
.entry(scale)
|
||||
.or_insert_with_key(move |&scale| render(scale, path).ok());
|
||||
let buffer = buffer.as_ref()?;
|
||||
.entry(NotNan::new(scale).unwrap())
|
||||
.or_insert_with(move || render(renderer.as_gles_renderer(), scale, path).ok());
|
||||
let buffer = buffer.clone()?;
|
||||
|
||||
let elem = MemoryRenderBufferRenderElement::from_buffer(
|
||||
renderer,
|
||||
(0., 0.),
|
||||
let size = buffer.logical_size();
|
||||
let y_range = size.h + f64::from(PADDING) * 2.;
|
||||
|
||||
let x = (output_size.w - size.w).max(0.) / 2.;
|
||||
let y = match &self.state {
|
||||
State::Hidden => unreachable!(),
|
||||
State::Showing(anim) | State::Hiding(anim) => -size.h + anim.value() * y_range,
|
||||
State::Shown(_) => f64::from(PADDING) * 2.,
|
||||
};
|
||||
|
||||
let location = Point::from((x, y));
|
||||
let location = location.to_physical_precise_round(scale).to_logical(scale);
|
||||
|
||||
let elem = TextureRenderElement::from_texture_buffer(
|
||||
buffer,
|
||||
Some(0.9),
|
||||
location,
|
||||
1.,
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
let output_transform = output.current_transform();
|
||||
let output_mode = output.current_mode().unwrap();
|
||||
let output_size = output_transform.transform_size(output_mode.size);
|
||||
|
||||
let buffer_size = elem
|
||||
.geometry(output.current_scale().fractional_scale().into())
|
||||
.size;
|
||||
|
||||
let y_range = buffer_size.h + PADDING * 2 * scale;
|
||||
|
||||
let x = (output_size.w / 2 - buffer_size.w / 2).max(0);
|
||||
let y = match &self.state {
|
||||
State::Hidden => unreachable!(),
|
||||
State::Showing(anim) | State::Hiding(anim) => {
|
||||
(-buffer_size.h as f64 + anim.value() * y_range as f64).round() as i32
|
||||
}
|
||||
State::Shown(_) => PADDING * 2 * scale,
|
||||
};
|
||||
let elem = RelocateRenderElement::from_element(elem, (x, y), Relocate::Absolute);
|
||||
|
||||
Some(elem)
|
||||
);
|
||||
Some(PrimaryGpuTextureRenderElement(elem))
|
||||
}
|
||||
}
|
||||
|
||||
fn render(scale: i32, created_path: Option<&Path>) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
fn render(
|
||||
renderer: &mut GlesRenderer,
|
||||
scale: f64,
|
||||
created_path: Option<&Path>,
|
||||
) -> anyhow::Result<TextureBuffer<GlesTexture>> {
|
||||
let _span = tracy_client::span!("config_error_notification::render");
|
||||
|
||||
let padding = PADDING * scale;
|
||||
let padding: i32 = to_physical_precise_round(scale, PADDING);
|
||||
|
||||
let mut text = String::from(TEXT);
|
||||
let mut border_color = (1., 0.3, 0.3);
|
||||
@@ -194,7 +187,7 @@ fn render(scale: i32, created_path: Option<&Path>) -> anyhow::Result<MemoryRende
|
||||
};
|
||||
|
||||
let mut font = FontDescription::from_string(FONT);
|
||||
font.set_absolute_size((font.size() * scale).into());
|
||||
font.set_absolute_size(to_physical_precise_round(scale, font.size()));
|
||||
|
||||
let surface = ImageSurface::create(cairo::Format::ARgb32, 0, 0)?;
|
||||
let cr = cairo::Context::new(&surface)?;
|
||||
@@ -206,10 +199,6 @@ fn render(scale: i32, created_path: Option<&Path>) -> anyhow::Result<MemoryRende
|
||||
width += padding * 2;
|
||||
height += padding * 2;
|
||||
|
||||
// FIXME: fix bug in Smithay that rounds pixel sizes down to scale.
|
||||
width = (width + scale - 1) / scale * scale;
|
||||
height = (height + scale - 1) / scale * scale;
|
||||
|
||||
let surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?;
|
||||
let cr = cairo::Context::new(&surface)?;
|
||||
cr.set_source_rgb(0.1, 0.1, 0.1);
|
||||
@@ -229,19 +218,22 @@ fn render(scale: i32, created_path: Option<&Path>) -> anyhow::Result<MemoryRende
|
||||
cr.line_to(0., height.into());
|
||||
cr.line_to(0., 0.);
|
||||
cr.set_source_rgb(border_color.0, border_color.1, border_color.2);
|
||||
cr.set_line_width((BORDER * scale).into());
|
||||
// Keep the border width even to avoid blurry edges.
|
||||
cr.set_line_width((f64::from(BORDER) / 2. * scale).round() * 2.);
|
||||
cr.stroke()?;
|
||||
drop(cr);
|
||||
|
||||
let data = surface.take_data().unwrap();
|
||||
let buffer = MemoryRenderBuffer::from_slice(
|
||||
let buffer = TextureBuffer::from_memory(
|
||||
renderer,
|
||||
&data,
|
||||
Fourcc::Argb8888,
|
||||
(width, height),
|
||||
false,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
None,
|
||||
);
|
||||
Vec::new(),
|
||||
)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use ordered_float::NotNan;
|
||||
use pangocairo::cairo::{self, ImageSurface};
|
||||
use pangocairo::pango::{Alignment, FontDescription};
|
||||
use smithay::backend::renderer::element::memory::{
|
||||
MemoryRenderBuffer, MemoryRenderBufferRenderElement,
|
||||
};
|
||||
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
|
||||
use smithay::backend::renderer::element::{Element, Kind};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::output::Output;
|
||||
use smithay::reexports::gbm::Format as Fourcc;
|
||||
use smithay::utils::Transform;
|
||||
|
||||
use crate::render_helpers::memory::MemoryBuffer;
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::utils::{output_size, to_physical_precise_round};
|
||||
|
||||
const TEXT: &str = "Are you sure you want to exit niri?\n\n\
|
||||
Press <span face='mono' bgcolor='#2C2C2C'> Enter </span> to confirm.";
|
||||
@@ -22,17 +23,17 @@ const BORDER: i32 = 8;
|
||||
|
||||
pub struct ExitConfirmDialog {
|
||||
is_open: bool,
|
||||
buffers: RefCell<HashMap<i32, Option<MemoryRenderBuffer>>>,
|
||||
buffers: RefCell<HashMap<NotNan<f64>, Option<MemoryBuffer>>>,
|
||||
}
|
||||
|
||||
pub type ExitConfirmDialogRenderElement<R> =
|
||||
RelocateRenderElement<MemoryRenderBufferRenderElement<R>>;
|
||||
|
||||
impl ExitConfirmDialog {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
is_open: false,
|
||||
buffers: RefCell::new(HashMap::from([(1, Some(render(1)?))])),
|
||||
buffers: RefCell::new(HashMap::from([(
|
||||
NotNan::new(1.).unwrap(),
|
||||
Some(render(1.)?),
|
||||
)])),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,52 +63,48 @@ impl ExitConfirmDialog {
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
output: &Output,
|
||||
) -> Option<ExitConfirmDialogRenderElement<R>> {
|
||||
) -> Option<PrimaryGpuTextureRenderElement> {
|
||||
if !self.is_open {
|
||||
return None;
|
||||
}
|
||||
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
let output_size = output_size(output);
|
||||
|
||||
let mut buffers = self.buffers.borrow_mut();
|
||||
let fallback = buffers[&1].clone().unwrap();
|
||||
let buffer = buffers.entry(scale).or_insert_with(|| render(scale).ok());
|
||||
let fallback = buffers[&NotNan::new(1.).unwrap()].clone().unwrap();
|
||||
let buffer = buffers
|
||||
.entry(NotNan::new(scale).unwrap())
|
||||
.or_insert_with(|| render(scale).ok());
|
||||
let buffer = buffer.as_ref().unwrap_or(&fallback);
|
||||
|
||||
let elem = MemoryRenderBufferRenderElement::from_buffer(
|
||||
renderer,
|
||||
(0., 0.),
|
||||
let size = buffer.logical_size();
|
||||
let buffer = TextureBuffer::from_memory_buffer(renderer.as_gles_renderer(), buffer).ok()?;
|
||||
|
||||
let location = (output_size.to_f64().to_point() - size.to_point()).downscale(2.);
|
||||
let mut location = location.to_physical_precise_round(scale).to_logical(scale);
|
||||
location.x = f64::max(0., location.x);
|
||||
location.y = f64::max(0., location.y);
|
||||
|
||||
let elem = TextureRenderElement::from_texture_buffer(
|
||||
buffer,
|
||||
None,
|
||||
location,
|
||||
1.,
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
let output_transform = output.current_transform();
|
||||
let output_mode = output.current_mode().unwrap();
|
||||
let output_size = output_transform.transform_size(output_mode.size);
|
||||
|
||||
let buffer_size = elem
|
||||
.geometry(output.current_scale().fractional_scale().into())
|
||||
.size;
|
||||
|
||||
let x = (output_size.w / 2 - buffer_size.w / 2).max(0);
|
||||
let y = (output_size.h / 2 - buffer_size.h / 2).max(0);
|
||||
let elem = RelocateRenderElement::from_element(elem, (x, y), Relocate::Absolute);
|
||||
|
||||
Some(elem)
|
||||
);
|
||||
Some(PrimaryGpuTextureRenderElement(elem))
|
||||
}
|
||||
}
|
||||
|
||||
fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
fn render(scale: f64) -> anyhow::Result<MemoryBuffer> {
|
||||
let _span = tracy_client::span!("exit_confirm_dialog::render");
|
||||
|
||||
let padding = PADDING * scale;
|
||||
let padding: i32 = to_physical_precise_round(scale, PADDING);
|
||||
|
||||
let mut font = FontDescription::from_string(FONT);
|
||||
font.set_absolute_size((font.size() * scale).into());
|
||||
font.set_absolute_size(to_physical_precise_round(scale, font.size()));
|
||||
|
||||
let surface = ImageSurface::create(cairo::Format::ARgb32, 0, 0)?;
|
||||
let cr = cairo::Context::new(&surface)?;
|
||||
@@ -120,10 +117,6 @@ fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
width += padding * 2;
|
||||
height += padding * 2;
|
||||
|
||||
// FIXME: fix bug in Smithay that rounds pixel sizes down to scale.
|
||||
width = (width + scale - 1) / scale * scale;
|
||||
height = (height + scale - 1) / scale * scale;
|
||||
|
||||
let surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?;
|
||||
let cr = cairo::Context::new(&surface)?;
|
||||
cr.set_source_rgb(0.1, 0.1, 0.1);
|
||||
@@ -144,18 +137,18 @@ fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> {
|
||||
cr.line_to(0., height.into());
|
||||
cr.line_to(0., 0.);
|
||||
cr.set_source_rgb(1., 0.3, 0.3);
|
||||
cr.set_line_width((BORDER * scale).into());
|
||||
// Keep the border width even to avoid blurry edges.
|
||||
cr.set_line_width((f64::from(BORDER) / 2. * scale).round() * 2.);
|
||||
cr.stroke()?;
|
||||
drop(cr);
|
||||
|
||||
let data = surface.take_data().unwrap();
|
||||
let buffer = MemoryRenderBuffer::from_slice(
|
||||
&data,
|
||||
let buffer = MemoryBuffer::new(
|
||||
data.to_vec(),
|
||||
Fourcc::Argb8888,
|
||||
(width, height),
|
||||
scale,
|
||||
Transform::Normal,
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(buffer)
|
||||
|
||||
+48
-57
@@ -7,21 +7,21 @@ use std::rc::Rc;
|
||||
use niri_config::{Action, Config, Key, Modifiers, Trigger};
|
||||
use pangocairo::cairo::{self, ImageSurface};
|
||||
use pangocairo::pango::{AttrColor, AttrInt, AttrList, AttrString, FontDescription, Weight};
|
||||
use smithay::backend::renderer::element::memory::{
|
||||
MemoryRenderBuffer, MemoryRenderBufferRenderElement,
|
||||
};
|
||||
use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::input::keyboard::xkb::keysym_get_name;
|
||||
use smithay::output::{Output, WeakOutput};
|
||||
use smithay::reexports::gbm::Format as Fourcc;
|
||||
use smithay::utils::{Physical, Size, Transform};
|
||||
use smithay::utils::{Scale, Transform};
|
||||
|
||||
use crate::input::CompositorMod;
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::utils::{output_size, to_physical_precise_round};
|
||||
|
||||
const PADDING: i32 = 8;
|
||||
const MARGIN: i32 = PADDING * 2;
|
||||
// const MARGIN: i32 = PADDING * 2;
|
||||
const FONT: &str = "sans 14px";
|
||||
const BORDER: i32 = 4;
|
||||
const LINE_INTERVAL: i32 = 2;
|
||||
@@ -35,13 +35,9 @@ pub struct HotkeyOverlay {
|
||||
}
|
||||
|
||||
pub struct RenderedOverlay {
|
||||
buffer: Option<MemoryRenderBuffer>,
|
||||
size: Size<i32, Physical>,
|
||||
scale: i32,
|
||||
buffer: Option<TextureBuffer<GlesTexture>>,
|
||||
}
|
||||
|
||||
pub type HotkeyOverlayRenderElement<R> = RelocateRenderElement<MemoryRenderBufferRenderElement<R>>;
|
||||
|
||||
impl HotkeyOverlay {
|
||||
pub fn new(config: Rc<RefCell<Config>>, comp_mod: CompositorMod) -> Self {
|
||||
Self {
|
||||
@@ -82,17 +78,13 @@ impl HotkeyOverlay {
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
output: &Output,
|
||||
) -> Option<HotkeyOverlayRenderElement<R>> {
|
||||
) -> Option<PrimaryGpuTextureRenderElement> {
|
||||
if !self.is_open {
|
||||
return None;
|
||||
}
|
||||
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let margin = MARGIN * scale;
|
||||
|
||||
let output_transform = output.current_transform();
|
||||
let output_mode = output.current_mode().unwrap();
|
||||
let output_size = output_transform.transform_size(output_mode.size);
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
let output_size = output_size(output);
|
||||
|
||||
let mut buffers = self.buffers.borrow_mut();
|
||||
buffers.retain(|output, _| output.upgrade().is_some());
|
||||
@@ -100,51 +92,50 @@ impl HotkeyOverlay {
|
||||
// FIXME: should probably use the working area rather than view size.
|
||||
let weak = output.downgrade();
|
||||
if let Some(rendered) = buffers.get(&weak) {
|
||||
if rendered.scale != scale {
|
||||
buffers.remove(&weak);
|
||||
if let Some(buffer) = &rendered.buffer {
|
||||
if buffer.texture_scale() != Scale::from(scale) {
|
||||
buffers.remove(&weak);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rendered = buffers.entry(weak).or_insert_with(|| {
|
||||
render(&self.config.borrow(), self.comp_mod, scale).unwrap_or_else(|_| {
|
||||
// This can go negative but whatever, as long as there's no rerender loop.
|
||||
let mut size = output_size;
|
||||
size.w -= margin * 2;
|
||||
size.h -= margin * 2;
|
||||
RenderedOverlay {
|
||||
buffer: None,
|
||||
size,
|
||||
scale,
|
||||
}
|
||||
})
|
||||
let renderer = renderer.as_gles_renderer();
|
||||
render(renderer, &self.config.borrow(), self.comp_mod, scale)
|
||||
.unwrap_or_else(|_| RenderedOverlay { buffer: None })
|
||||
});
|
||||
let buffer = rendered.buffer.as_ref()?;
|
||||
|
||||
let elem = MemoryRenderBufferRenderElement::from_buffer(
|
||||
renderer,
|
||||
(0., 0.),
|
||||
buffer,
|
||||
Some(0.9),
|
||||
let size = buffer.logical_size();
|
||||
let location = (output_size.to_f64().to_point() - size.to_point()).downscale(2.);
|
||||
let mut location = location.to_physical_precise_round(scale).to_logical(scale);
|
||||
location.x = f64::max(0., location.x);
|
||||
location.y = f64::max(0., location.y);
|
||||
|
||||
let elem = TextureRenderElement::from_texture_buffer(
|
||||
buffer.clone(),
|
||||
location,
|
||||
0.9,
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.ok()?;
|
||||
);
|
||||
|
||||
let x = (output_size.w / 2 - rendered.size.w / 2).max(0);
|
||||
let y = (output_size.h / 2 - rendered.size.h / 2).max(0);
|
||||
let elem = RelocateRenderElement::from_element(elem, (x, y), Relocate::Absolute);
|
||||
|
||||
Some(elem)
|
||||
Some(PrimaryGpuTextureRenderElement(elem))
|
||||
}
|
||||
}
|
||||
|
||||
fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Result<RenderedOverlay> {
|
||||
fn render(
|
||||
renderer: &mut GlesRenderer,
|
||||
config: &Config,
|
||||
comp_mod: CompositorMod,
|
||||
scale: f64,
|
||||
) -> anyhow::Result<RenderedOverlay> {
|
||||
let _span = tracy_client::span!("hotkey_overlay::render");
|
||||
|
||||
// let margin = MARGIN * scale;
|
||||
let padding = PADDING * scale;
|
||||
let line_interval = LINE_INTERVAL * scale;
|
||||
let padding: i32 = to_physical_precise_round(scale, PADDING);
|
||||
let line_interval: i32 = to_physical_precise_round(scale, LINE_INTERVAL);
|
||||
|
||||
// FIXME: if it doesn't fit, try splitting in two columns or something.
|
||||
// let mut target_size = output_size;
|
||||
@@ -254,7 +245,7 @@ fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Resul
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut font = FontDescription::from_string(FONT);
|
||||
font.set_absolute_size((font.size() * scale).into());
|
||||
font.set_absolute_size(to_physical_precise_round(scale, font.size()));
|
||||
|
||||
let surface = ImageSurface::create(cairo::Format::ARgb32, 0, 0)?;
|
||||
let cr = cairo::Context::new(&surface)?;
|
||||
@@ -303,10 +294,6 @@ fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Resul
|
||||
width += padding * 2;
|
||||
height += padding * 2;
|
||||
|
||||
// FIXME: fix bug in Smithay that rounds pixel sizes down to scale.
|
||||
width = (width + scale - 1) / scale * scale;
|
||||
height = (height + scale - 1) / scale * scale;
|
||||
|
||||
let surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?;
|
||||
let cr = cairo::Context::new(&surface)?;
|
||||
cr.set_source_rgb(0.1, 0.1, 0.1);
|
||||
@@ -348,24 +335,25 @@ fn render(config: &Config, comp_mod: CompositorMod, scale: i32) -> anyhow::Resul
|
||||
cr.line_to(0., height.into());
|
||||
cr.line_to(0., 0.);
|
||||
cr.set_source_rgb(0.5, 0.8, 1.0);
|
||||
cr.set_line_width((BORDER * scale).into());
|
||||
// Keep the border width even to avoid blurry edges.
|
||||
cr.set_line_width((f64::from(BORDER) / 2. * scale).round() * 2.);
|
||||
cr.stroke()?;
|
||||
drop(cr);
|
||||
|
||||
let data = surface.take_data().unwrap();
|
||||
let buffer = MemoryRenderBuffer::from_slice(
|
||||
let buffer = TextureBuffer::from_memory(
|
||||
renderer,
|
||||
&data,
|
||||
Fourcc::Argb8888,
|
||||
(width, height),
|
||||
false,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
None,
|
||||
);
|
||||
Vec::new(),
|
||||
)?;
|
||||
|
||||
Ok(RenderedOverlay {
|
||||
buffer: Some(buffer),
|
||||
size: Size::from((width, height)),
|
||||
scale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -410,6 +398,9 @@ fn key_name(comp_mod: CompositorMod, key: &Key) -> String {
|
||||
if key.modifiers.contains(Modifiers::ALT) || (has_comp_mod && comp_mod == CompositorMod::Alt) {
|
||||
name.push_str("Alt + ");
|
||||
}
|
||||
if key.modifiers.contains(Modifiers::ISO_LEVEL3_SHIFT) {
|
||||
name.push_str("ISO_Level3_Shift + ");
|
||||
}
|
||||
if key.modifiers.contains(Modifiers::SHIFT) {
|
||||
name.push_str("Shift + ");
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderElement};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::backend::renderer::gles::GlesTexture;
|
||||
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::render_helpers::RenderTarget;
|
||||
|
||||
pub const DELAY: Duration = Duration::from_millis(250);
|
||||
@@ -51,9 +51,9 @@ impl ScreenTransition {
|
||||
};
|
||||
|
||||
PrimaryGpuTextureRenderElement(TextureRenderElement::from_texture_buffer(
|
||||
self.from_texture[idx].clone(),
|
||||
(0., 0.),
|
||||
&self.from_texture[idx],
|
||||
Some(self.alpha),
|
||||
self.alpha,
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
|
||||
+61
-51
@@ -8,8 +8,6 @@ use arrayvec::ArrayVec;
|
||||
use niri_config::Action;
|
||||
use smithay::backend::allocator::Fourcc;
|
||||
use smithay::backend::input::{ButtonState, MouseButton};
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use smithay::backend::renderer::element::texture::{TextureBuffer, TextureRenderElement};
|
||||
use smithay::backend::renderer::element::Kind;
|
||||
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
||||
use smithay::backend::renderer::ExportMem;
|
||||
@@ -19,7 +17,10 @@ use smithay::utils::{Physical, Point, Rectangle, Size, Transform};
|
||||
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::texture::{TextureBuffer, TextureRenderElement};
|
||||
use crate::render_helpers::RenderTarget;
|
||||
use crate::utils::to_physical_precise_round;
|
||||
|
||||
const BORDER: i32 = 2;
|
||||
|
||||
@@ -41,7 +42,7 @@ pub enum ScreenshotUi {
|
||||
|
||||
pub struct OutputData {
|
||||
size: Size<i32, Physical>,
|
||||
scale: i32,
|
||||
scale: f64,
|
||||
transform: Transform,
|
||||
// Output, screencast, screen capture.
|
||||
texture: [GlesTexture; 3],
|
||||
@@ -112,19 +113,25 @@ impl ScreenshotUi {
|
||||
let transform = output.current_transform();
|
||||
let output_mode = output.current_mode().unwrap();
|
||||
let size = transform.transform_size(output_mode.size);
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let scale = output.current_scale().fractional_scale();
|
||||
let texture_buffer = texture.clone().map(|texture| {
|
||||
TextureBuffer::from_texture(renderer, texture, scale, Transform::Normal, None)
|
||||
TextureBuffer::from_texture(
|
||||
renderer,
|
||||
texture,
|
||||
scale,
|
||||
Transform::Normal,
|
||||
Vec::new(),
|
||||
)
|
||||
});
|
||||
let buffers = [
|
||||
SolidColorBuffer::new((0, 0), [1., 1., 1., 1.]),
|
||||
SolidColorBuffer::new((0, 0), [1., 1., 1., 1.]),
|
||||
SolidColorBuffer::new((0, 0), [1., 1., 1., 1.]),
|
||||
SolidColorBuffer::new((0, 0), [1., 1., 1., 1.]),
|
||||
SolidColorBuffer::new((0, 0), [0., 0., 0., 0.5]),
|
||||
SolidColorBuffer::new((0, 0), [0., 0., 0., 0.5]),
|
||||
SolidColorBuffer::new((0, 0), [0., 0., 0., 0.5]),
|
||||
SolidColorBuffer::new((0, 0), [0., 0., 0., 0.5]),
|
||||
SolidColorBuffer::new((0., 0.), [1., 1., 1., 1.]),
|
||||
SolidColorBuffer::new((0., 0.), [1., 1., 1., 1.]),
|
||||
SolidColorBuffer::new((0., 0.), [1., 1., 1., 1.]),
|
||||
SolidColorBuffer::new((0., 0.), [1., 1., 1., 1.]),
|
||||
SolidColorBuffer::new((0., 0.), [0., 0., 0., 0.5]),
|
||||
SolidColorBuffer::new((0., 0.), [0., 0., 0., 0.5]),
|
||||
SolidColorBuffer::new((0., 0.), [0., 0., 0., 0.5]),
|
||||
SolidColorBuffer::new((0., 0.), [0., 0., 0., 0.5]),
|
||||
];
|
||||
let locations = [Default::default(); 8];
|
||||
let data = OutputData {
|
||||
@@ -161,10 +168,9 @@ impl ScreenshotUi {
|
||||
}
|
||||
};
|
||||
|
||||
let scale = selection.0.current_scale().integer_scale();
|
||||
let last_selection = Some((
|
||||
selection.0.downgrade(),
|
||||
rect_from_corner_points(selection.1, selection.2, scale),
|
||||
rect_from_corner_points(selection.1, selection.2),
|
||||
));
|
||||
|
||||
*self = Self::Closed { last_selection };
|
||||
@@ -187,17 +193,15 @@ impl ScreenshotUi {
|
||||
};
|
||||
|
||||
let (selection_output, a, b) = selection;
|
||||
let scale = selection_output.current_scale().integer_scale();
|
||||
let mut rect = rect_from_corner_points(*a, *b, scale);
|
||||
let mut rect = rect_from_corner_points(*a, *b);
|
||||
|
||||
for (output, data) in output_data {
|
||||
let buffers = &mut data.buffers;
|
||||
let locations = &mut data.locations;
|
||||
let size = data.size;
|
||||
let scale = data.scale;
|
||||
|
||||
if output == selection_output {
|
||||
let scale = output.current_scale().integer_scale();
|
||||
|
||||
// Check if the selection is still valid. If not, reset it back to default.
|
||||
if !Rectangle::from_loc_and_size((0, 0), size).contains_rect(rect) {
|
||||
rect = Rectangle::from_loc_and_size(
|
||||
@@ -205,20 +209,29 @@ impl ScreenshotUi {
|
||||
(size.w / 2, size.h / 2),
|
||||
);
|
||||
*a = rect.loc;
|
||||
*b = rect.loc + rect.size - Size::from((scale, scale));
|
||||
*b = rect.loc + rect.size - Size::from((1, 1));
|
||||
}
|
||||
|
||||
let border = BORDER * scale;
|
||||
let border = to_physical_precise_round(scale, BORDER);
|
||||
|
||||
buffers[0].resize((rect.size.w + border * 2, border));
|
||||
buffers[1].resize((rect.size.w + border * 2, border));
|
||||
buffers[2].resize((border, rect.size.h));
|
||||
buffers[3].resize((border, rect.size.h));
|
||||
let resize = move |buffer: &mut SolidColorBuffer, w: i32, h: i32| {
|
||||
let size = Size::<_, Physical>::from((w, h));
|
||||
buffer.resize(size.to_f64().to_logical(scale));
|
||||
};
|
||||
|
||||
buffers[4].resize((size.w, rect.loc.y));
|
||||
buffers[5].resize((size.w, size.h - rect.loc.y - rect.size.h));
|
||||
buffers[6].resize((rect.loc.x, rect.size.h));
|
||||
buffers[7].resize((size.w - rect.loc.x - rect.size.w, rect.size.h));
|
||||
resize(&mut buffers[0], rect.size.w + border * 2, border);
|
||||
resize(&mut buffers[1], rect.size.w + border * 2, border);
|
||||
resize(&mut buffers[2], border, rect.size.h);
|
||||
resize(&mut buffers[3], border, rect.size.h);
|
||||
|
||||
resize(&mut buffers[4], size.w, rect.loc.y);
|
||||
resize(&mut buffers[5], size.w, size.h - rect.loc.y - rect.size.h);
|
||||
resize(&mut buffers[6], rect.loc.x, rect.size.h);
|
||||
resize(
|
||||
&mut buffers[7],
|
||||
size.w - rect.loc.x - rect.size.w,
|
||||
rect.size.h,
|
||||
);
|
||||
|
||||
locations[0] = Point::from((rect.loc.x - border, rect.loc.y - border));
|
||||
locations[1] = Point::from((rect.loc.x - border, rect.loc.y + rect.size.h));
|
||||
@@ -229,15 +242,15 @@ impl ScreenshotUi {
|
||||
locations[6] = Point::from((0, rect.loc.y));
|
||||
locations[7] = Point::from((rect.loc.x + rect.size.w, rect.loc.y));
|
||||
} else {
|
||||
buffers[0].resize((0, 0));
|
||||
buffers[1].resize((0, 0));
|
||||
buffers[2].resize((0, 0));
|
||||
buffers[3].resize((0, 0));
|
||||
buffers[0].resize((0., 0.));
|
||||
buffers[1].resize((0., 0.));
|
||||
buffers[2].resize((0., 0.));
|
||||
buffers[3].resize((0., 0.));
|
||||
|
||||
buffers[4].resize(size.to_logical(1));
|
||||
buffers[5].resize((0, 0));
|
||||
buffers[6].resize((0, 0));
|
||||
buffers[7].resize((0, 0));
|
||||
buffers[4].resize(size.to_f64().to_logical(data.scale));
|
||||
buffers[5].resize((0., 0.));
|
||||
buffers[6].resize((0., 0.));
|
||||
buffers[7].resize((0., 0.));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,8 +276,7 @@ impl ScreenshotUi {
|
||||
elements.extend(buf_loc.map(|(buffer, loc)| {
|
||||
SolidColorRenderElement::from_buffer(
|
||||
buffer,
|
||||
*loc,
|
||||
1., // We treat these as physical coordinates.
|
||||
loc.to_f64().to_logical(output_data.scale),
|
||||
1.,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
@@ -279,9 +291,9 @@ impl ScreenshotUi {
|
||||
};
|
||||
elements.push(
|
||||
PrimaryGpuTextureRenderElement(TextureRenderElement::from_texture_buffer(
|
||||
output_data.texture_buffer[index].clone(),
|
||||
(0., 0.),
|
||||
&output_data.texture_buffer[index],
|
||||
None,
|
||||
1.,
|
||||
None,
|
||||
None,
|
||||
Kind::Unspecified,
|
||||
@@ -308,8 +320,7 @@ impl ScreenshotUi {
|
||||
};
|
||||
|
||||
let data = &output_data[&selection.0];
|
||||
let scale = selection.0.current_scale().integer_scale();
|
||||
let rect = rect_from_corner_points(selection.1, selection.2, scale);
|
||||
let rect = rect_from_corner_points(selection.1, selection.2);
|
||||
let buf_rect = rect
|
||||
.to_logical(1)
|
||||
.to_buffer(1, Transform::Normal, &data.size.to_logical(1));
|
||||
@@ -344,7 +355,7 @@ impl ScreenshotUi {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_size(&self, output: &Output) -> Option<(Size<i32, Physical>, i32, Transform)> {
|
||||
pub fn output_size(&self, output: &Output) -> Option<(Size<i32, Physical>, f64, Transform)> {
|
||||
if let Self::Open { output_data, .. } = self {
|
||||
let data = output_data.get(output)?;
|
||||
Some((data.size, data.scale, data.transform))
|
||||
@@ -405,16 +416,14 @@ impl ScreenshotUi {
|
||||
// Check if the resulting selection is zero-sized, and try to come up with a small
|
||||
// default rectangle.
|
||||
let (output, a, b) = selection;
|
||||
let scale = output.current_scale().integer_scale();
|
||||
let mut rect = rect_from_corner_points(*a, *b, scale);
|
||||
if rect.size.is_empty() || rect.size == Size::from((scale, scale)) {
|
||||
let mut rect = rect_from_corner_points(*a, *b);
|
||||
if rect.size.is_empty() || rect.size == Size::from((1, 1)) {
|
||||
let data = &output_data[output];
|
||||
rect = Rectangle::from_loc_and_size((rect.loc.x - 16, rect.loc.y - 16), (32, 32))
|
||||
.intersection(Rectangle::from_loc_and_size((0, 0), data.size))
|
||||
.unwrap_or_default();
|
||||
let scale = output.current_scale().integer_scale();
|
||||
*a = rect.loc;
|
||||
*b = rect.loc + rect.size - Size::from((scale, scale));
|
||||
*b = rect.loc + rect.size - Size::from((1, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,11 +460,12 @@ fn action(raw: Keysym, mods: ModifiersState) -> Option<Action> {
|
||||
pub fn rect_from_corner_points(
|
||||
a: Point<i32, Physical>,
|
||||
b: Point<i32, Physical>,
|
||||
scale: i32,
|
||||
) -> Rectangle<i32, Physical> {
|
||||
let x1 = min(a.x, b.x);
|
||||
let y1 = min(a.y, b.y);
|
||||
let x2 = max(a.x, b.x);
|
||||
let y2 = max(a.y, b.y);
|
||||
Rectangle::from_extemities((x1, y1), (x2 + scale, y2 + scale))
|
||||
// We're adding + 1 because the pointer is clamped to output size - 1, so to get the full
|
||||
// screen worth of selection we must add back that + 1.
|
||||
Rectangle::from_extemities((x1, y1), (x2 + 1, y2 + 1))
|
||||
}
|
||||
|
||||
+3
-1
@@ -11,7 +11,9 @@ pub struct IdCounter {
|
||||
impl IdCounter {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
value: AtomicU32::new(0),
|
||||
// Start from 1 to reduce the possibility that some other code that uses these IDs will
|
||||
// get confused.
|
||||
value: AtomicU32::new(1),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+38
-8
@@ -12,10 +12,13 @@ use directories::UserDirs;
|
||||
use git_version::git_version;
|
||||
use niri_config::Config;
|
||||
use smithay::input::pointer::CursorIcon;
|
||||
use smithay::output::Output;
|
||||
use smithay::output::{self, Output};
|
||||
use smithay::reexports::rustix::time::{clock_gettime, ClockId};
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Size, Transform};
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Coordinate, Logical, Point, Rectangle, Size, Transform};
|
||||
use smithay::wayland::compositor::{send_surface_state, SurfaceData};
|
||||
use smithay::wayland::fractional_scale::with_fractional_scale;
|
||||
|
||||
pub mod id;
|
||||
pub mod scale;
|
||||
@@ -87,14 +90,29 @@ pub fn center_f64(rect: Rectangle<f64, Logical>) -> Point<f64, Logical> {
|
||||
rect.loc + rect.size.downscale(2.0).to_point()
|
||||
}
|
||||
|
||||
pub fn output_size(output: &Output) -> Size<i32, Logical> {
|
||||
let output_scale = output.current_scale().integer_scale();
|
||||
/// Convert logical pixels to physical, rounding to physical pixels.
|
||||
pub fn to_physical_precise_round<N: Coordinate>(scale: f64, logical: impl Coordinate) -> N {
|
||||
N::from_f64((logical.to_f64() * scale).round())
|
||||
}
|
||||
|
||||
pub fn round_logical_in_physical(scale: f64, logical: f64) -> f64 {
|
||||
(logical * scale).round() / scale
|
||||
}
|
||||
|
||||
pub fn round_logical_in_physical_max1(scale: f64, logical: f64) -> f64 {
|
||||
if logical == 0. {
|
||||
return 0.;
|
||||
}
|
||||
|
||||
(logical * scale).max(1.).round() / scale
|
||||
}
|
||||
|
||||
pub fn output_size(output: &Output) -> Size<f64, Logical> {
|
||||
let output_scale = output.current_scale().fractional_scale();
|
||||
let output_transform = output.current_transform();
|
||||
let output_mode = output.current_mode().unwrap();
|
||||
|
||||
output_transform
|
||||
.transform_size(output_mode.size)
|
||||
.to_logical(output_scale)
|
||||
let logical_size = output_mode.size.to_f64().to_logical(output_scale);
|
||||
output_transform.transform_size(logical_size)
|
||||
}
|
||||
|
||||
pub fn logical_output(output: &Output) -> niri_ipc::LogicalOutput {
|
||||
@@ -133,6 +151,18 @@ pub fn ipc_transform_to_smithay(transform: niri_ipc::Transform) -> Transform {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_scale_transform(
|
||||
surface: &WlSurface,
|
||||
data: &SurfaceData,
|
||||
scale: output::Scale,
|
||||
transform: Transform,
|
||||
) {
|
||||
send_surface_state(surface, data, scale.integer_scale(), transform);
|
||||
with_fractional_scale(data, |fractional| {
|
||||
fractional.set_preferred_scale(scale.fractional_scale());
|
||||
});
|
||||
}
|
||||
|
||||
pub fn expand_home(path: &Path) -> anyhow::Result<Option<PathBuf>> {
|
||||
if let Ok(rest) = path.strip_prefix("~") {
|
||||
let dirs = UserDirs::new().context("error retrieving home directory")?;
|
||||
|
||||
+35
-20
@@ -7,6 +7,7 @@ use smithay::utils::{Physical, Raw, Size};
|
||||
|
||||
const MIN_SCALE: i32 = 1;
|
||||
const MAX_SCALE: i32 = 4;
|
||||
const STEPS: i32 = 4;
|
||||
const MIN_LOGICAL_AREA: i32 = 800 * 480;
|
||||
|
||||
const MOBILE_TARGET_DPI: f64 = 135.;
|
||||
@@ -31,9 +32,6 @@ pub fn guess_monitor_scale(size_mm: Size<i32, Raw>, resolution: Size<i32, Physic
|
||||
f64::from(resolution.w * resolution.w + resolution.h * resolution.h).sqrt() / diag_inches;
|
||||
let perfect_scale = physical_dpi / target_dpi;
|
||||
|
||||
// For integer scaling factors (we currently only do integer), bias the perfect scale down.
|
||||
let perfect_scale = perfect_scale - 0.15;
|
||||
|
||||
supported_scales(resolution)
|
||||
.map(|scale| (scale, (scale - perfect_scale).abs()))
|
||||
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
|
||||
@@ -41,16 +39,23 @@ pub fn guess_monitor_scale(size_mm: Size<i32, Raw>, resolution: Size<i32, Physic
|
||||
}
|
||||
|
||||
fn supported_scales(resolution: Size<i32, Physical>) -> impl Iterator<Item = f64> {
|
||||
(MIN_SCALE..=MAX_SCALE)
|
||||
(MIN_SCALE * STEPS..=MAX_SCALE * STEPS)
|
||||
.map(|x| f64::from(x) / f64::from(STEPS))
|
||||
.filter(move |scale| is_valid_for_resolution(resolution, *scale))
|
||||
.map(f64::from)
|
||||
}
|
||||
|
||||
fn is_valid_for_resolution(resolution: Size<i32, Physical>, scale: i32) -> bool {
|
||||
let logical = resolution.to_logical(scale);
|
||||
fn is_valid_for_resolution(resolution: Size<i32, Physical>, scale: f64) -> bool {
|
||||
let logical = resolution.to_f64().to_logical(scale).to_i32_round::<i32>();
|
||||
logical.w * logical.h >= MIN_LOGICAL_AREA
|
||||
}
|
||||
|
||||
/// Adjusts the scale to the closest exactly-representable value.
|
||||
pub fn closest_representable_scale(scale: f64) -> f64 {
|
||||
// Current fractional-scale Wayland protocol can only represent N / 120 scales.
|
||||
const FRACTIONAL_SCALE_DENOM: f64 = 120.;
|
||||
|
||||
(scale * FRACTIONAL_SCALE_DENOM).round() / FRACTIONAL_SCALE_DENOM
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use k9::snapshot;
|
||||
@@ -64,33 +69,33 @@ mod tests {
|
||||
#[test]
|
||||
fn test_guess_monitor_scale() {
|
||||
// Librem 5; not enough logical area when scaled
|
||||
snapshot!(check((65, 129), (720, 1440)), "1.0");
|
||||
snapshot!(check((65, 129), (720, 1440)), "1.5");
|
||||
// OnePlus 6
|
||||
snapshot!(check((68, 144), (1080, 2280)), "2.0");
|
||||
snapshot!(check((68, 144), (1080, 2280)), "2.5");
|
||||
// Google Pixel 6a
|
||||
snapshot!(check((64, 142), (1080, 2400)), "2.0");
|
||||
snapshot!(check((64, 142), (1080, 2400)), "2.5");
|
||||
// 13" MacBook Retina
|
||||
snapshot!(check((286, 179), (2560, 1600)), "2.0");
|
||||
snapshot!(check((286, 179), (2560, 1600)), "1.75");
|
||||
// Surface Laptop Studio
|
||||
snapshot!(check((303, 202), (2400, 1600)), "1.0");
|
||||
snapshot!(check((303, 202), (2400, 1600)), "1.5");
|
||||
// Dell XPS 9320
|
||||
snapshot!(check((290, 180), (3840, 2400)), "2.0");
|
||||
snapshot!(check((290, 180), (3840, 2400)), "2.5");
|
||||
// Lenovo ThinkPad X1 Yoga Gen 6
|
||||
snapshot!(check((300, 190), (3840, 2400)), "2.0");
|
||||
snapshot!(check((300, 190), (3840, 2400)), "2.5");
|
||||
// Generic 23" 1080p
|
||||
snapshot!(check((509, 286), (1920, 1080)), "1.0");
|
||||
// Generic 23" 4K
|
||||
snapshot!(check((509, 286), (3840, 2160)), "2.0");
|
||||
snapshot!(check((509, 286), (3840, 2160)), "1.75");
|
||||
// Generic 27" 4K
|
||||
snapshot!(check((598, 336), (3840, 2160)), "1.0");
|
||||
snapshot!(check((598, 336), (3840, 2160)), "1.5");
|
||||
// Generic 32" 4K
|
||||
snapshot!(check((708, 398), (3840, 2160)), "1.0");
|
||||
snapshot!(check((708, 398), (3840, 2160)), "1.25");
|
||||
// Generic 25" 4K; ideal scale is 1.60, should round to 1.5 and 1.0
|
||||
snapshot!(check((554, 312), (3840, 2160)), "1.0");
|
||||
snapshot!(check((554, 312), (3840, 2160)), "1.5");
|
||||
// Generic 23.5" 4K; ideal scale is 1.70, should round to 1.75 and 2.0
|
||||
snapshot!(check((522, 294), (3840, 2160)), "2.0");
|
||||
snapshot!(check((522, 294), (3840, 2160)), "1.75");
|
||||
// Lenovo Legion 7 Gen 7 AMD 16"
|
||||
snapshot!(check((340, 210), (2560, 1600)), "1.0");
|
||||
snapshot!(check((340, 210), (2560, 1600)), "1.5");
|
||||
// Acer Nitro XV320QU LV 31.5"
|
||||
snapshot!(check((700, 390), (2560, 1440)), "1.0");
|
||||
// Surface Pro 6
|
||||
@@ -101,4 +106,14 @@ mod tests {
|
||||
fn guess_monitor_scale_unknown_size() {
|
||||
assert_eq!(check((0, 0), (1920, 1080)), 1.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_round_scale() {
|
||||
snapshot!(closest_representable_scale(1.3), "1.3");
|
||||
snapshot!(closest_representable_scale(1.31), "1.3083333333333333");
|
||||
snapshot!(closest_representable_scale(1.32), "1.3166666666666667");
|
||||
snapshot!(closest_representable_scale(1.33), "1.3333333333333333");
|
||||
snapshot!(closest_representable_scale(1.34), "1.3416666666666666");
|
||||
snapshot!(closest_representable_scale(1.35), "1.35");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::RwLock;
|
||||
use std::{io, thread};
|
||||
|
||||
use atomic::Atomic;
|
||||
use libc::{getrlimit, rlim_t, rlimit, setrlimit, RLIMIT_NOFILE};
|
||||
use niri_config::Environment;
|
||||
|
||||
use crate::utils::expand_home;
|
||||
@@ -14,6 +16,50 @@ pub static REMOVE_ENV_RUST_BACKTRACE: AtomicBool = AtomicBool::new(false);
|
||||
pub static REMOVE_ENV_RUST_LIB_BACKTRACE: AtomicBool = AtomicBool::new(false);
|
||||
pub static CHILD_ENV: RwLock<Environment> = RwLock::new(Environment(Vec::new()));
|
||||
|
||||
static ORIGINAL_NOFILE_RLIMIT_CUR: Atomic<rlim_t> = Atomic::new(0);
|
||||
static ORIGINAL_NOFILE_RLIMIT_MAX: Atomic<rlim_t> = Atomic::new(0);
|
||||
|
||||
/// Increases the nofile rlimit to the maximum and stores the original value.
|
||||
pub fn store_and_increase_nofile_rlimit() {
|
||||
let mut rlim = rlimit {
|
||||
rlim_cur: 0,
|
||||
rlim_max: 0,
|
||||
};
|
||||
if unsafe { getrlimit(RLIMIT_NOFILE, &mut rlim) } != 0 {
|
||||
let err = io::Error::last_os_error();
|
||||
warn!("error getting nofile rlimit: {err:?}");
|
||||
return;
|
||||
}
|
||||
|
||||
ORIGINAL_NOFILE_RLIMIT_CUR.store(rlim.rlim_cur, Ordering::SeqCst);
|
||||
ORIGINAL_NOFILE_RLIMIT_MAX.store(rlim.rlim_max, Ordering::SeqCst);
|
||||
|
||||
trace!(
|
||||
"changing nofile rlimit from {} to {}",
|
||||
rlim.rlim_cur,
|
||||
rlim.rlim_max
|
||||
);
|
||||
rlim.rlim_cur = rlim.rlim_max;
|
||||
|
||||
if unsafe { setrlimit(RLIMIT_NOFILE, &rlim) } != 0 {
|
||||
let err = io::Error::last_os_error();
|
||||
warn!("error setting nofile rlimit: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Restores the original nofile rlimit.
|
||||
pub fn restore_nofile_rlimit() {
|
||||
let rlim_cur = ORIGINAL_NOFILE_RLIMIT_CUR.load(Ordering::SeqCst);
|
||||
let rlim_max = ORIGINAL_NOFILE_RLIMIT_MAX.load(Ordering::SeqCst);
|
||||
|
||||
if rlim_cur == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let rlim = rlimit { rlim_cur, rlim_max };
|
||||
unsafe { setrlimit(RLIMIT_NOFILE, &rlim) };
|
||||
}
|
||||
|
||||
/// Spawns the command to run independently of the compositor.
|
||||
pub fn spawn<T: AsRef<OsStr> + Send + 'static>(command: Vec<T>) {
|
||||
let _span = tracy_client::span!();
|
||||
@@ -103,6 +149,8 @@ fn do_spawn(command: &OsStr, mut process: Command) -> Option<Child> {
|
||||
_ => libc::_exit(0),
|
||||
}
|
||||
|
||||
restore_nofile_rlimit();
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
@@ -214,6 +262,8 @@ mod systemd {
|
||||
}
|
||||
}
|
||||
|
||||
restore_nofile_rlimit();
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
+130
-41
@@ -2,38 +2,43 @@ use std::cell::{Cell, RefCell};
|
||||
use std::cmp::{max, min};
|
||||
use std::time::Duration;
|
||||
|
||||
use niri_config::WindowRule;
|
||||
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use niri_config::{CornerRadius, WindowRule};
|
||||
use smithay::backend::renderer::element::surface::render_elements_from_surface_tree;
|
||||
use smithay::backend::renderer::element::{Id, Kind};
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::desktop::space::SpaceElement as _;
|
||||
use smithay::desktop::{PopupManager, Window};
|
||||
use smithay::output::Output;
|
||||
use smithay::output::{self, Output};
|
||||
use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1;
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform};
|
||||
use smithay::wayland::compositor::{
|
||||
remove_pre_commit_hook, send_surface_state, with_states, HookId,
|
||||
};
|
||||
use smithay::wayland::compositor::{remove_pre_commit_hook, with_states, HookId};
|
||||
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface};
|
||||
|
||||
use super::{ResolvedWindowRules, WindowRef};
|
||||
use crate::handlers::KdeDecorationsModeState;
|
||||
use crate::layout::{
|
||||
InteractiveResizeData, LayoutElement, LayoutElementRenderElement, LayoutElementRenderSnapshot,
|
||||
};
|
||||
use crate::niri::WindowOffscreenId;
|
||||
use crate::niri_render_elements;
|
||||
use crate::render_helpers::border::BorderRenderElement;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
use crate::render_helpers::snapshot::RenderSnapshot;
|
||||
use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement};
|
||||
use crate::render_helpers::surface::render_snapshot_from_surface_tree;
|
||||
use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements};
|
||||
use crate::utils::ResizeEdge;
|
||||
use crate::utils::id::IdCounter;
|
||||
use crate::utils::{send_scale_transform, ResizeEdge};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mapped {
|
||||
pub window: Window,
|
||||
|
||||
/// Unique ID of this `Mapped`.
|
||||
id: MappedId,
|
||||
|
||||
/// Pre-commit hook that we have on all mapped toplevel surfaces.
|
||||
pre_commit_hook: HookId,
|
||||
|
||||
@@ -50,7 +55,7 @@ pub struct Mapped {
|
||||
is_focused: bool,
|
||||
|
||||
/// Whether this window is the active window in its column.
|
||||
pub is_active_in_column: bool,
|
||||
is_active_in_column: bool,
|
||||
|
||||
/// Buffer to draw instead of the window when it should be blocked out.
|
||||
block_out_buffer: RefCell<SolidColorBuffer>,
|
||||
@@ -73,6 +78,29 @@ pub struct Mapped {
|
||||
last_interactive_resize_start: Cell<Option<(Duration, ResizeEdge)>>,
|
||||
}
|
||||
|
||||
niri_render_elements! {
|
||||
WindowCastRenderElements<R> => {
|
||||
Layout = LayoutElementRenderElement<R>,
|
||||
// Blocked-out window with rounded corners.
|
||||
Border = BorderRenderElement,
|
||||
}
|
||||
}
|
||||
|
||||
static MAPPED_ID_COUNTER: IdCounter = IdCounter::new();
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct MappedId(u32);
|
||||
|
||||
impl MappedId {
|
||||
fn next() -> MappedId {
|
||||
MappedId(MAPPED_ID_COUNTER.next())
|
||||
}
|
||||
|
||||
pub fn get(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Interactive resize state.
|
||||
#[derive(Debug)]
|
||||
enum InteractiveResize {
|
||||
@@ -101,12 +129,13 @@ impl Mapped {
|
||||
pub fn new(window: Window, rules: ResolvedWindowRules, hook: HookId) -> Self {
|
||||
Self {
|
||||
window,
|
||||
id: MappedId::next(),
|
||||
pre_commit_hook: hook,
|
||||
rules,
|
||||
need_to_recompute_rules: false,
|
||||
is_focused: false,
|
||||
is_active_in_column: false,
|
||||
block_out_buffer: RefCell::new(SolidColorBuffer::new((0, 0), [0., 0., 0., 1.])),
|
||||
block_out_buffer: RefCell::new(SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.])),
|
||||
animate_next_configure: false,
|
||||
animate_serials: Vec::new(),
|
||||
animation_snapshot: None,
|
||||
@@ -144,10 +173,18 @@ impl Mapped {
|
||||
self.recompute_window_rules(rules, is_at_startup)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> MappedId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn is_focused(&self) -> bool {
|
||||
self.is_focused
|
||||
}
|
||||
|
||||
pub fn is_active_in_column(&self) -> bool {
|
||||
self.is_active_in_column
|
||||
}
|
||||
|
||||
pub fn set_is_focused(&mut self, is_focused: bool) {
|
||||
if self.is_focused == is_focused {
|
||||
return;
|
||||
@@ -160,18 +197,18 @@ impl Mapped {
|
||||
fn render_snapshot(&self, renderer: &mut GlesRenderer) -> LayoutElementRenderSnapshot {
|
||||
let _span = tracy_client::span!("Mapped::render_snapshot");
|
||||
|
||||
let size = self.size();
|
||||
let size = self.size().to_f64();
|
||||
|
||||
let mut buffer = self.block_out_buffer.borrow_mut();
|
||||
buffer.resize(size);
|
||||
let blocked_out_contents = vec![BakedBuffer {
|
||||
buffer: buffer.clone(),
|
||||
location: Point::from((0, 0)),
|
||||
location: Point::from((0., 0.)),
|
||||
src: None,
|
||||
dst: None,
|
||||
}];
|
||||
|
||||
let buf_pos = self.window.geometry().loc.upscale(-1);
|
||||
let buf_pos = self.window.geometry().loc.upscale(-1).to_f64();
|
||||
|
||||
let mut contents = vec![];
|
||||
|
||||
@@ -182,7 +219,7 @@ impl Mapped {
|
||||
render_snapshot_from_surface_tree(
|
||||
renderer,
|
||||
popup.wl_surface(),
|
||||
buf_pos + offset,
|
||||
buf_pos + offset.to_f64(),
|
||||
&mut contents,
|
||||
);
|
||||
}
|
||||
@@ -219,6 +256,55 @@ impl Mapped {
|
||||
pub fn last_interactive_resize_start(&self) -> &Cell<Option<(Duration, ResizeEdge)>> {
|
||||
&self.last_interactive_resize_start
|
||||
}
|
||||
|
||||
pub fn render_for_screen_cast<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
scale: Scale<f64>,
|
||||
) -> impl DoubleEndedIterator<Item = WindowCastRenderElements<R>> {
|
||||
let bbox = self.window.bbox_with_popups().to_physical_precise_up(scale);
|
||||
|
||||
let has_border_shader = BorderRenderElement::has_shader(renderer);
|
||||
let rules = self.rules();
|
||||
let radius = rules.geometry_corner_radius.unwrap_or_default();
|
||||
let window_size = self
|
||||
.size()
|
||||
.to_f64()
|
||||
.to_physical_precise_round(scale)
|
||||
.to_logical(scale);
|
||||
let radius = radius.fit_to(window_size.w as f32, window_size.h as f32);
|
||||
|
||||
let location = self.window.geometry().loc.to_f64() - bbox.loc.to_logical(scale);
|
||||
let elements = self.render(renderer, location, scale, 1., RenderTarget::Screencast);
|
||||
|
||||
elements.into_iter().map(move |elem| {
|
||||
if let LayoutElementRenderElement::SolidColor(elem) = &elem {
|
||||
// In this branch we're rendering a blocked-out window with a solid color. We need
|
||||
// to render it with a rounded corner shader even if clip_to_geometry is false,
|
||||
// because in this case we're assuming that the unclipped window CSD already has
|
||||
// corners rounded to the user-provided radius, so our blocked-out rendering should
|
||||
// match that radius.
|
||||
if radius != CornerRadius::default() && has_border_shader {
|
||||
let geo = elem.geo();
|
||||
return BorderRenderElement::new(
|
||||
geo.size,
|
||||
Rectangle::from_loc_and_size((0., 0.), geo.size),
|
||||
elem.color(),
|
||||
elem.color(),
|
||||
0.,
|
||||
Rectangle::from_loc_and_size((0., 0.), geo.size),
|
||||
0.,
|
||||
radius,
|
||||
scale.x as f32,
|
||||
)
|
||||
.with_location(geo.loc)
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
WindowCastRenderElements::from(elem)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Mapped {
|
||||
@@ -250,7 +336,7 @@ impl LayoutElement for Mapped {
|
||||
fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
target: RenderTarget,
|
||||
@@ -259,17 +345,12 @@ impl LayoutElement for Mapped {
|
||||
|
||||
if target.should_block_out(self.rules.block_out_from) {
|
||||
let mut buffer = self.block_out_buffer.borrow_mut();
|
||||
buffer.resize(self.window.geometry().size);
|
||||
let elem = SolidColorRenderElement::from_buffer(
|
||||
&buffer,
|
||||
location.to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
);
|
||||
buffer.resize(self.window.geometry().size.to_f64());
|
||||
let elem =
|
||||
SolidColorRenderElement::from_buffer(&buffer, location, alpha, Kind::Unspecified);
|
||||
rv.normal.push(elem.into());
|
||||
} else {
|
||||
let buf_pos = location - self.window.geometry().loc;
|
||||
let buf_pos = location - self.window.geometry().loc.to_f64();
|
||||
|
||||
let surface = self.toplevel().wl_surface();
|
||||
for (popup, popup_offset) in PopupManager::popups_for_surface(surface) {
|
||||
@@ -278,7 +359,7 @@ impl LayoutElement for Mapped {
|
||||
rv.popups.extend(render_elements_from_surface_tree(
|
||||
renderer,
|
||||
popup.wl_surface(),
|
||||
(buf_pos + offset).to_physical_precise_round(scale),
|
||||
(buf_pos + offset.to_f64()).to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
@@ -301,24 +382,19 @@ impl LayoutElement for Mapped {
|
||||
fn render_normal<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
target: RenderTarget,
|
||||
) -> Vec<LayoutElementRenderElement<R>> {
|
||||
if target.should_block_out(self.rules.block_out_from) {
|
||||
let mut buffer = self.block_out_buffer.borrow_mut();
|
||||
buffer.resize(self.window.geometry().size);
|
||||
let elem = SolidColorRenderElement::from_buffer(
|
||||
&buffer,
|
||||
location.to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
);
|
||||
buffer.resize(self.window.geometry().size.to_f64());
|
||||
let elem =
|
||||
SolidColorRenderElement::from_buffer(&buffer, location, alpha, Kind::Unspecified);
|
||||
vec![elem.into()]
|
||||
} else {
|
||||
let buf_pos = location - self.window.geometry().loc;
|
||||
let buf_pos = location - self.window.geometry().loc.to_f64();
|
||||
let surface = self.toplevel().wl_surface();
|
||||
render_elements_from_surface_tree(
|
||||
renderer,
|
||||
@@ -334,7 +410,7 @@ impl LayoutElement for Mapped {
|
||||
fn render_popups<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Logical>,
|
||||
location: Point<f64, Logical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
target: RenderTarget,
|
||||
@@ -344,7 +420,7 @@ impl LayoutElement for Mapped {
|
||||
} else {
|
||||
let mut rv = vec![];
|
||||
|
||||
let buf_pos = location - self.window.geometry().loc;
|
||||
let buf_pos = location - self.window.geometry().loc.to_f64();
|
||||
let surface = self.toplevel().wl_surface();
|
||||
for (popup, popup_offset) in PopupManager::popups_for_surface(surface) {
|
||||
let offset = self.window.geometry().loc + popup_offset - popup.geometry().loc;
|
||||
@@ -352,7 +428,7 @@ impl LayoutElement for Mapped {
|
||||
rv.extend(render_elements_from_surface_tree(
|
||||
renderer,
|
||||
popup.wl_surface(),
|
||||
(buf_pos + offset).to_physical_precise_round(scale),
|
||||
(buf_pos + offset.to_f64()).to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
@@ -427,15 +503,28 @@ impl LayoutElement for Mapped {
|
||||
self.toplevel().wl_surface() == wl_surface
|
||||
}
|
||||
|
||||
fn set_preferred_scale_transform(&self, scale: i32, transform: Transform) {
|
||||
fn set_preferred_scale_transform(&self, scale: output::Scale, transform: Transform) {
|
||||
self.window.with_surfaces(|surface, data| {
|
||||
send_surface_state(surface, data, scale, transform);
|
||||
send_scale_transform(surface, data, scale, transform);
|
||||
});
|
||||
}
|
||||
|
||||
fn has_ssd(&self) -> bool {
|
||||
self.toplevel().current_state().decoration_mode
|
||||
== Some(zxdg_toplevel_decoration_v1::Mode::ServerSide)
|
||||
let toplevel = self.toplevel();
|
||||
let mode = toplevel.current_state().decoration_mode;
|
||||
|
||||
match mode {
|
||||
Some(zxdg_toplevel_decoration_v1::Mode::ServerSide) => true,
|
||||
// Check KDE decorations when XDG are not in use.
|
||||
None => with_states(toplevel.wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<KdeDecorationsModeState>()
|
||||
.map(KdeDecorationsModeState::is_server)
|
||||
== Some(true)
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn output_enter(&self, output: &Output) {
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ impl<'a> WindowRef<'a> {
|
||||
pub fn is_active_in_column(self) -> bool {
|
||||
match self {
|
||||
WindowRef::Unmapped(_) => false,
|
||||
WindowRef::Mapped(mapped) => mapped.is_active_in_column,
|
||||
WindowRef::Mapped(mapped) => mapped.is_active_in_column(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[default.extend-words]
|
||||
datas = "datas"
|
||||
@@ -25,30 +25,41 @@ input {
|
||||
}
|
||||
|
||||
touchpad {
|
||||
// off
|
||||
tap
|
||||
// dwt
|
||||
// dwtp
|
||||
natural-scroll
|
||||
// accel-speed 0.2
|
||||
// accel-profile "flat"
|
||||
// scroll-method "two-finger"
|
||||
// tap-button-map "left-middle-right"
|
||||
// click-method "clickfinger"
|
||||
// left-handed
|
||||
// disabled-on-external-mouse
|
||||
}
|
||||
|
||||
mouse {
|
||||
// off
|
||||
// natural-scroll
|
||||
// accel-speed 0.2
|
||||
// accel-profile "flat"
|
||||
// scroll-method "no-scroll"
|
||||
// left-handed
|
||||
}
|
||||
|
||||
trackpoint {
|
||||
// off
|
||||
// natural-scroll
|
||||
// accel-speed 0.2
|
||||
// accel-profile "flat"
|
||||
// scroll-method "on-button-down"
|
||||
}
|
||||
|
||||
tablet {
|
||||
// off
|
||||
map-to-output "eDP-1"
|
||||
// left-handed
|
||||
}
|
||||
|
||||
touch {
|
||||
@@ -116,11 +127,17 @@ Most settings for the pointing devices are passed directly to libinput.
|
||||
Other Wayland compositors also use libinput, so it's likely you will find the same settings there.
|
||||
For flags like `tap`, omit them or comment them out to disable the setting.
|
||||
|
||||
A few settings are common between input devices:
|
||||
|
||||
- `off`: if set, no events will be sent from this device.
|
||||
|
||||
A few settings are common between `touchpad`, `mouse` and `trackpoint`:
|
||||
|
||||
- `natural-scroll`: if set, inverts the scrolling direction.
|
||||
- `accel-speed`: pointer acceleration speed, valid values are from `-1.0` to `1.0` where the default is `0.0`.
|
||||
- `accel-profile`: can be `adaptive` (the default) or `flat` (disables pointer acceleration).
|
||||
- `scroll-method`: when to generate scroll events instead of pointer motion events, can be `no-scroll`, `two-finger`, `edge`, or `on-button-down`.
|
||||
The default and supported methods vary depending on the device type.
|
||||
|
||||
Settings specific to `touchpad`s:
|
||||
|
||||
@@ -129,6 +146,11 @@ Settings specific to `touchpad`s:
|
||||
- `dwtp`: disable-when-trackpointing.
|
||||
- `tap-button-map`: can be `left-right-middle` or `left-middle-right`, controls which button corresponds to a two-finger tap and a three-finger tap.
|
||||
- `click-method`: can be `button-areas` or `clickfinger`, changes the [click method](https://wayland.freedesktop.org/libinput/doc/latest/clickpad-softbuttons.html).
|
||||
- `disabled-on-external-mouse`: do not send events while external pointer device is plugged in.
|
||||
|
||||
Settings specific to `touchpad`, `mouse` and `tablet`:
|
||||
|
||||
- `left-handed`: if set, changes the device to left-handed mode.
|
||||
|
||||
Tablets and touchscreens are absolute pointing devices that can be mapped to a specific output like so:
|
||||
|
||||
@@ -146,6 +168,8 @@ input {
|
||||
|
||||
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.
|
||||
|
||||
### General Settings
|
||||
|
||||
These settings are not specific to a particular input device.
|
||||
|
||||
@@ -48,6 +48,10 @@ layout {
|
||||
|
||||
Set gaps around (inside and outside) windows in logical pixels.
|
||||
|
||||
<sup>Since: 0.1.7</sup> You can use fractional values.
|
||||
The value will be rounded to physical pixels according to the scale factor of every output.
|
||||
For example, `gaps 0.5` on an output with `scale 2` will result in one physical-pixel wide gaps.
|
||||
|
||||
```
|
||||
layout {
|
||||
gaps 16
|
||||
@@ -170,6 +174,22 @@ layout {
|
||||
}
|
||||
```
|
||||
|
||||
#### Width
|
||||
|
||||
Set the thickness of the border in logical pixels.
|
||||
|
||||
<sup>Since: 0.1.7</sup> You can use fractional values.
|
||||
The value will be rounded to physical pixels according to the scale factor of every output.
|
||||
For example, `width 0.5` on an output with `scale 2` will result in one physical-pixel thick borders.
|
||||
|
||||
```
|
||||
layout {
|
||||
border {
|
||||
width 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Colors
|
||||
|
||||
Colors can be set in a variety of ways:
|
||||
@@ -227,6 +247,10 @@ They are set in logical pixels.
|
||||
Left and right struts will cause the next window to the side to always peek out slightly.
|
||||
Top and bottom struts will simply add outer gaps in addition to the area occupied by layer-shell panels and regular gaps.
|
||||
|
||||
<sup>Since: 0.1.7</sup> You can use fractional values.
|
||||
The value will be rounded to physical pixels according to the scale factor of every output.
|
||||
For example, `top 0.5` on an output with `scale 2` will result in one physical-pixel wide top strut.
|
||||
|
||||
```
|
||||
layout {
|
||||
struts {
|
||||
|
||||
@@ -68,10 +68,14 @@ output "eDP-1" {
|
||||
|
||||
Set the scale of the monitor.
|
||||
|
||||
This is a floating-point number to enable fractional scaling in the future, but at the moment only integer scale values will work.
|
||||
|
||||
<sup>Since: 0.1.6</sup> If scale is unset, niri will guess an appropriate scale based on the physical dimensions and the resolution of the monitor.
|
||||
|
||||
<sup>Since: 0.1.7</sup> You can use fractional scale values, for example `scale 1.5` for 150% scale.
|
||||
|
||||
<sup>Since: 0.1.7</sup> Dot is no longer needed for integer scale, for example you can write `scale 2` instead of `scale 2.0`.
|
||||
|
||||
<sup>Since: 0.1.7</sup> Scale below 0 and above 10 will now fail during config parsing. Scale was previously clamped to these values anyway.
|
||||
|
||||
```
|
||||
output "eDP-1" {
|
||||
scale 2.0
|
||||
|
||||
@@ -13,7 +13,7 @@ You can find documentation for various sections of the config on these wiki page
|
||||
|
||||
### Loading
|
||||
|
||||
Niri will load configuration from `$XDG_CONFIG_HOME/.config/niri/config.kdl` or `~/.config/niri/config.kdl`.
|
||||
Niri will load configuration from `$XDG_CONFIG_HOME/niri/config.kdl` or `~/.config/niri/config.kdl`.
|
||||
If that file is missing, niri will create it with the contents of [the default configuration file](https://github.com/YaLTeR/niri/blob/main/resources/default-config.kdl).
|
||||
Please use the default configuration file as the starting point for your custom configuration.
|
||||
|
||||
@@ -25,6 +25,11 @@ You can run `niri validate` to parse the config and see any errors.
|
||||
|
||||
To use a different config file path, pass it in the `--config` or `-c` argument to `niri`.
|
||||
|
||||
You can also set `$NIRI_CONFIG` to the path of the config file.
|
||||
`--config` always takes precedence.
|
||||
If `--config` or `$NIRI_CONFIG` doesn't point to a real file, the config will not be loaded.
|
||||
If `$NIRI_CONFIG` is set to an empty string, it is ignored and the default config location is used instead.
|
||||
|
||||
### Syntax
|
||||
|
||||
The config is written in [KDL].
|
||||
|
||||
@@ -119,6 +119,7 @@ Let's look at the matchers in more detail.
|
||||
#### `title` and `app-id`
|
||||
|
||||
These are regular expressions that should match anywhere in the window title and app ID respectively.
|
||||
You can read about the supported regular expression syntax [here](https://docs.rs/regex/latest/regex/#syntax).
|
||||
|
||||
```
|
||||
// Match windows with title containing "Mozilla Firefox",
|
||||
|
||||
+22
-1
@@ -1,3 +1,11 @@
|
||||
### How to disable client-side decorations/make windows rectangular?
|
||||
|
||||
Uncomment the `prefer-no-csd` setting at the [top level](./Configuration:-Miscellaneous.md) of the config.
|
||||
Then niri will ask windows to omit client-side decorations, and also inform them that they are being tiled (which makes some windows rectangular, even if they cannot omit the decorations).
|
||||
|
||||
Note that currently this will prevent edge window resize handles from showing up.
|
||||
You can still resize windows by holding <kbd>Mod</kbd> and the right mouse button.
|
||||
|
||||
### Why is the border/focus ring showing up through semitransparent windows?
|
||||
|
||||
Uncomment the `prefer-no-csd` setting at the [top level](./Configuration:-Miscellaneous.md) of the config.
|
||||
@@ -7,7 +15,7 @@ By default, focus ring and border are rendered as a solid background rectangle b
|
||||
That is, they will show up through semitransparent windows.
|
||||
This is because windows using client-side decorations can have an arbitrary shape.
|
||||
|
||||
You can also override this behavior with the `draw-border-with-background` [window rule](https://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules).
|
||||
You can also override this behavior with the `draw-border-with-background` [window rule](./Configuration:-Window-Rules.md).
|
||||
|
||||
### Why is the Waybar pop-up menu showing behind windows?
|
||||
|
||||
@@ -15,3 +23,16 @@ Set `"layer": "top"` in your Waybar config.
|
||||
|
||||
Niri currently draws pop-up menus on the same layer as their parent surface.
|
||||
By default, Waybar is on the `bottom` layer, which is behind windows, so Waybar pop-up menus also show behind windows.
|
||||
|
||||
### How to enable rounded corners for all windows?
|
||||
|
||||
Put this window rule in your config:
|
||||
|
||||
```
|
||||
window-rule {
|
||||
geometry-corner-radius 12
|
||||
clip-to-geometry true
|
||||
}
|
||||
```
|
||||
|
||||
For more information, check [this wiki section](https://github.com/YaLTeR/niri/wiki/Configuration:-Window-Rules#geometry-corner-radius).
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
There are two main coordinate spaces in niri: physical (pixels of every individual output) and logical (shared among all outputs, takes into account the scale of every output).
|
||||
Wayland clients mostly work in the logical space, and it's the most convenient space to do all the layout in, since it bakes in the output scaling factor.
|
||||
|
||||
However, many things need to be sized or positioned at integer physical coordinates.
|
||||
For example, Wayland toplevel buffers are assumed to be placed at an integer physical pixel on an output (and `WaylandSurfaceRenderElement` will do that for you).
|
||||
Borders and focus rings should also have a width equal to an integer number of physical pixels to stay crisp (not to mention that `SolidColorRenderElement` does not anti-alias lines at fractional pixel positions).
|
||||
|
||||
Integer physical coordinates do not necessarily correspond to integer logical coordinates though.
|
||||
Even with an integer scale = 2, a physical pixel at (1, 1) will be at the logical position of (0.5, 0.5).
|
||||
This problem becomes much worse with fractional scale factors where most integer logical coordinates will fall on fractional physical coordinates.
|
||||
|
||||
Thus, niri uses fractional logical coordinates for most of its layout.
|
||||
However, one needs to be very careful to keep things aligned to the physical grid to avoid artifacts like:
|
||||
|
||||
* Border width alternating 1 px thicker/thinner
|
||||
* Border showing 1 px off from the window at certain positions
|
||||
* 1 px gaps around rounded corners
|
||||
* Slightly blurry window contents during resizes
|
||||
* And so on...
|
||||
|
||||
The way it's handled in niri is:
|
||||
|
||||
1. All relevant sizes on a workspace are rounded to an integer physical coordinate according to the current output scale. Things like struts, gaps, border widths, working area location.
|
||||
|
||||
It's important to understand that they remain fractional numbers in the logical space, but these numbers correspond to an integer number of pixels in the physical space.
|
||||
The rounding looks something like: `(logical_size * scale).round() / scale`.
|
||||
Whenever a workspace moves to an output with a different scale (or the output scale changes), all sizes are re-rounded from their original configured values to align with the new physical space.
|
||||
2. The view offset and individual column/tile render offsets are *not* rounded to physical pixels, but:
|
||||
3. `tiles_with_render_positions()` rounds tile positions to physical pixels as it returns them,
|
||||
4. Custom shaders like opening, closing and resizing windows, are also careful to keep positions and sizes rounded to the physical pixels.
|
||||
|
||||
The idea is that every tile can assume that it is rendered at an integer physical coordinate, therefore when shifting the position by, say, border width (also rounded to integer physical coordinates), the new position will stay rounded to integer physical coordinates.
|
||||
The same logic works for the rest of the layout thanks to gaps, struts and working area being similarly rounded.
|
||||
This way, the entire layout is always aligned, as long as it is positioned at an integer physical coordinate (which rounding the tile positions effectively achieves).
|
||||
+7
-1
@@ -30,7 +30,13 @@ This works with both window-initiated resizes (when using client-side decoration
|
||||
|
||||
<sup>Since: 0.1.6</sup>
|
||||
|
||||
Move the view horizontally by holding <kbd>Mod</kbd> and the middle mouse button (or the wheel).
|
||||
Move the view horizontally by holding <kbd>Mod</kbd> and the middle mouse button (or the wheel) and dragging the mouse horizontally.
|
||||
|
||||
#### Workspace Switch
|
||||
|
||||
<sup>Since: 0.1.7</sup>
|
||||
|
||||
Switch workspaces by holding <kbd>Mod</kbd> and the middle mouse button (or the wheel) and dragging the mouse vertically.
|
||||
|
||||
### Touchpad
|
||||
|
||||
|
||||
@@ -29,6 +29,14 @@ You can try the following:
|
||||
1. Update NVIDIA drivers. You need a GPU and drivers recent enough to support GBM.
|
||||
2. Make sure kernel modesetting is enabled. This usually involves adding `nvidia-drm.modeset=1` to the kernel command line. Find and follow a guide for your distribution. Guides from other Wayland compositors can help.
|
||||
|
||||
If niri runs but the screen flickers, try adding this into your niri config:
|
||||
|
||||
```
|
||||
debug {
|
||||
wait-for-frame-completion-before-queueing
|
||||
}
|
||||
```
|
||||
|
||||
### Asahi, ARM, and other kmsro devices
|
||||
|
||||
On some of these systems, niri fails to correctly detect the primary render device.
|
||||
|
||||
@@ -8,13 +8,13 @@ Many apps need one. For example, [mako](https://github.com/emersion/mako) works
|
||||
|
||||
These provide a cross-desktop API for apps to use for various things like file pickers or UI settings. Flatpak apps in particular require working portals.
|
||||
|
||||
Portals **require** [running niri as a session](https://github.com/YaLTeR/niri#session), which means through the `niri-session` script or from a display manager. You will want the following portals installed:
|
||||
Portals **require** [running niri as a session](./Getting-Started.md), which means through the `niri-session` script or from a display manager. You will want the following portals installed:
|
||||
|
||||
* `xdg-desktop-portal-gtk`: implements most of the basic functionality, this is the "default fallback portal".
|
||||
* `xdg-desktop-portal-gnome`: required for screencasting support.
|
||||
* `gnome-keyring`: implements the Secret portal, required for certain apps to work.
|
||||
|
||||
Then systemd should start them on-demand automatically. These particular portals are configured in `niri-portals.conf` which [must be installed](https://github.com/YaLTeR/niri#installation) in the correct location.
|
||||
Then systemd should start them on-demand automatically. These particular portals are configured in `niri-portals.conf` which [must be installed](https://github.com/YaLTeR/niri/wiki/Getting-Started#installation) in the correct location.
|
||||
|
||||
Since we're using `xdg-desktop-portal-gnome`, Flatpak apps will read the GNOME UI settings. For example, to enable the dark style, run:
|
||||
|
||||
|
||||
@@ -37,6 +37,24 @@ binds {
|
||||
}
|
||||
```
|
||||
|
||||
## Using xwayland-run to run Xwayland
|
||||
|
||||
[xwayland-run] is a helper utility to run an X11 client within a dedicated Xwayland rootful server.
|
||||
It takes care of starting Xwayland, setting the X11 DISPLAY environment variable, setting up xauth and running the specified X11 client using the newly started Xwayland instance.
|
||||
When the X11 client terminates, xwayland-run will automatically close the dedicated Xwayland server.
|
||||
|
||||
It works like this:
|
||||
|
||||
```
|
||||
xwayland-run <Xwayland arguments> -- your-x11-app <X11 app arguments>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
xwayland-run -geometry 800x600 -fullscreen -- wine wingame.exe
|
||||
```
|
||||
|
||||
## Using the Cage Wayland compositor
|
||||
|
||||
It is also possible to run the X11 application in [Cage](https://github.com/cage-kiosk/cage), which runs a nested Wayland session which also supports Xwayland, where the X11 application can run in.
|
||||
@@ -81,3 +99,19 @@ gamescope -W 2560 -H 1440 -w 2560 -h 1440 -f -- flatpak run com.valvesoftware.S
|
||||
> [!NOTE]
|
||||
> If Steam terminates abnormally while running in gamescope, it seems that subsequent gamescope invocations will sometimes fail to start it properly.
|
||||
> If this happens, run Steam inside a rootful Xwayland as described above, then exit it normally, and then you will be able to use gamescope again.
|
||||
|
||||
## Using xwayland-satellite
|
||||
|
||||
[xwayland-satellite] is an experimental new project that essentially implements rootless Xwayland in a separate application, without the host compositor's involvement.
|
||||
|
||||
Build it according to instructions from its README, then run the `xwayland-satellite` binary.
|
||||
Now you can start X11 applications on the X11 DISPLAY that it provides:
|
||||
|
||||
```
|
||||
env DISPLAY=:0 flatpak run com.valvesoftware.Steam
|
||||
```
|
||||
|
||||
They will appear as normal windows.
|
||||
|
||||
[xwayland-run]: https://gitlab.freedesktop.org/ofourdan/xwayland-run
|
||||
[xwayland-satellite]: https://github.com/Supreeeme/xwayland-satellite
|
||||
|
||||
@@ -24,3 +24,4 @@
|
||||
## Development
|
||||
* [Design Principles](./Design-Principles.md)
|
||||
* [Developing niri](./Developing-niri.md)
|
||||
* [Fractional Layout](./Fractional-Layout.md)
|
||||
|
||||
@@ -105,7 +105,7 @@ vec4 default_close(vec3 coords_geo, vec3 size_geo) {
|
||||
return color;
|
||||
}
|
||||
|
||||
// Example: make the window "fall down" with slight rotation.
|
||||
// Example: make the window 'fall down' with slight rotation.
|
||||
vec4 fall_and_rotate(vec3 coords_geo, vec3 size_geo) {
|
||||
// For this shader, set animation curve to linear for best results.
|
||||
|
||||
@@ -142,6 +142,6 @@ vec4 fall_and_rotate(vec3 coords_geo, vec3 size_geo) {
|
||||
// This is the function that you must define.
|
||||
vec4 close_color(vec3 coords_geo, vec3 size_geo) {
|
||||
// You can pick one of the example functions or write your own.
|
||||
return rotate_and_fall_down(coords_geo, size_geo);
|
||||
return fall_and_rotate(coords_geo, size_geo);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ vec4 resize_color(vec3 coords_curr_geo, vec3 size_curr_geo) {
|
||||
//
|
||||
// The shader runs over an area of unspecified size and location, so you must
|
||||
// expect and handle coordinates outside the [0, 1] range. The area will be
|
||||
// large enough to accomodate a crossfade effect.
|
||||
// large enough to accommodate a crossfade effect.
|
||||
//
|
||||
// * size_curr_geo: size of the current window geometry in logical pixels.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user