diff --git a/build/vulkan.rs b/build/vulkan.rs index 450c55c5..8203c108 100644 --- a/build/vulkan.rs +++ b/build/vulkan.rs @@ -1,17 +1,24 @@ mod hash; use { - crate::vulkan::hash::{ROOT, unchanged}, + crate::vulkan::hash::{TREES, Tree, unchanged}, anyhow::bail, std::process::Command, }; pub fn main() -> anyhow::Result<()> { - println!("cargo:rerun-if-changed={}", ROOT); + for tree in TREES { + main_(tree)?; + } + Ok(()) +} + +fn main_(tree: &Tree) -> anyhow::Result<()> { + println!("cargo:rerun-if-changed={}", tree.root); if !std::fs::exists("compile-shaders")? { return Ok(()); } - if unchanged() { + if unchanged(tree) { return Ok(()); } let code = Command::new("cargo") diff --git a/build/vulkan/hash.rs b/build/vulkan/hash.rs index dabd45fc..de6e0692 100644 --- a/build/vulkan/hash.rs +++ b/build/vulkan/hash.rs @@ -1,10 +1,33 @@ use {std::fmt::Write, walkdir::WalkDir}; -pub const ROOT: &str = "src/gfx_apis/vulkan/shaders"; -pub const HASH: &str = "src/gfx_apis/vulkan/shaders_hash.txt"; +#[allow(dead_code)] +pub struct Tree { + pub root: &'static str, + pub hash: &'static str, + pub bin: &'static str, + pub shaders: &'static [&'static str], +} -fn calculate_hash() -> anyhow::Result { - let dir = WalkDir::new(ROOT); +pub const TREES: &[Tree] = &[Tree { + root: "src/gfx_apis/vulkan/shaders", + hash: "src/gfx_apis/vulkan/shaders_hash.txt", + bin: "src/gfx_apis/vulkan/shaders_bin", + shaders: &[ + "fill.frag", + "fill.vert", + "tex.vert", + "tex.frag", + "out.vert", + "out.frag", + "legacy/fill.frag", + "legacy/fill.vert", + "legacy/tex.vert", + "legacy/tex.frag", + ], +}]; + +fn calculate_hash(tree: &Tree) -> anyhow::Result { + let dir = WalkDir::new(tree.root); let mut files = vec![]; for file in dir { let file = file?; @@ -21,11 +44,11 @@ fn calculate_hash() -> anyhow::Result { Ok(out) } -pub fn unchanged() -> bool { - let Ok(actual) = std::fs::read_to_string(HASH) else { +pub fn unchanged(tree: &Tree) -> bool { + let Ok(actual) = std::fs::read_to_string(tree.hash) else { return false; }; - let Ok(expected) = calculate_hash() else { + let Ok(expected) = calculate_hash(tree) else { return false; }; actual == expected diff --git a/compile-shaders/compile/src/main.rs b/compile-shaders/compile/src/main.rs index d780da28..84325ddd 100644 --- a/compile-shaders/compile/src/main.rs +++ b/compile-shaders/compile/src/main.rs @@ -1,32 +1,27 @@ use { anyhow::{Context, anyhow, bail}, - compile_shaders_core::{BIN, ROOT, update_hash}, + compile_shaders_core::{TREES, Tree, update_hash}, shaderc::{CompileOptions, ResolvedInclude}, std::{fs::File, io::Write, path::Path}, }; fn main() -> anyhow::Result<()> { - compile("fill.frag")?; - compile("fill.vert")?; - compile("tex.vert")?; - compile("tex.frag")?; - compile("out.vert")?; - compile("out.frag")?; - compile("legacy/fill.frag")?; - compile("legacy/fill.vert")?; - compile("legacy/tex.vert")?; - compile("legacy/tex.frag")?; - update_hash()?; + for tree in TREES { + for shader in tree.shaders { + compile(tree, shader)?; + } + update_hash(tree)?; + } Ok(()) } -fn compile(name: &str) -> anyhow::Result<()> { +fn compile(tree: &Tree, name: &str) -> anyhow::Result<()> { let out = format!("{name}.spv").replace("/", "_"); - compile_shader(name, &out).with_context(|| name.to_string()) + compile_shader(tree, name, &out).with_context(|| name.to_string()) } -fn compile_shader(name: &str, out: &str) -> anyhow::Result<()> { - let root = Path::new(ROOT).join(Path::new(name).parent().unwrap()); +fn compile_shader(tree: &Tree, name: &str, out: &str) -> anyhow::Result<()> { + let root = Path::new(tree.root).join(Path::new(name).parent().unwrap()); let read = |path: &str| std::fs::read_to_string(root.join(path)); let mut options = CompileOptions::new()?; options.set_include_callback(|name, _, _, _| { @@ -44,10 +39,10 @@ fn compile_shader(name: &str, out: &str) -> anyhow::Result<()> { "vert" => shaderc::ShaderKind::Vertex, n => bail!("Unknown shader stage {}", n), }; - let src = std::fs::read_to_string(format!("{}/{}", ROOT, name))?; + let src = std::fs::read_to_string(format!("{}/{}", tree.root, name))?; let compiler = shaderc::Compiler::new()?; let binary = compiler.compile_into_spirv(&src, stage, name, "main", Some(&options))?; - let mut file = File::create(Path::new(BIN).join(out))?; + let mut file = File::create(Path::new(tree.bin).join(out))?; file.write_all(binary.as_binary_u8())?; file.flush()?; Ok(()) diff --git a/compile-shaders/core/src/lib.rs b/compile-shaders/core/src/lib.rs index 53647204..1752eeec 100644 --- a/compile-shaders/core/src/lib.rs +++ b/compile-shaders/core/src/lib.rs @@ -1,8 +1,6 @@ include!("../../../build/vulkan/hash.rs"); -pub const BIN: &str = "src/gfx_apis/vulkan/shaders_bin"; - -pub fn update_hash() -> anyhow::Result<()> { - std::fs::write(HASH, calculate_hash()?)?; +pub fn update_hash(tree: &Tree) -> anyhow::Result<()> { + std::fs::write(tree.hash, calculate_hash(tree)?)?; Ok(()) } diff --git a/compile-shaders/core/src/main.rs b/compile-shaders/core/src/main.rs index 1e4ac922..ef54899b 100644 --- a/compile-shaders/core/src/main.rs +++ b/compile-shaders/core/src/main.rs @@ -1,5 +1,8 @@ -use compile_shaders_core::update_hash; +use compile_shaders_core::{update_hash, TREES}; fn main() -> anyhow::Result<()> { - update_hash() + for tree in TREES { + update_hash(tree)?; + } + Ok(()) } diff --git a/src/backend.rs b/src/backend.rs index 531e858a..678b7346 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -22,6 +22,7 @@ use { }, }, libinput::consts::DeviceCapability, + utils::static_text::StaticText, video::drm::{ ConnectorType, DRM_MODE_COLORIMETRY_BT2020_RGB, DRM_MODE_COLORIMETRY_DEFAULT, DrmConnector, DrmError, DrmVersion, HDMI_EOTF_SMPTE_ST2084, @@ -267,7 +268,7 @@ pub trait InputDevice { } } -#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Linearize)] pub enum InputDeviceCapability { Keyboard, Pointer, @@ -278,6 +279,20 @@ pub enum InputDeviceCapability { Switch, } +impl StaticText for InputDeviceCapability { + fn text(&self) -> &'static str { + match self { + InputDeviceCapability::Keyboard => "keyboard", + InputDeviceCapability::Pointer => "pointer", + InputDeviceCapability::Touch => "touch", + InputDeviceCapability::TabletTool => "tablet tool", + InputDeviceCapability::TabletPad => "tablet pad", + InputDeviceCapability::Gesture => "gesture", + InputDeviceCapability::Switch => "switch", + } + } +} + impl InputDeviceCapability { pub fn to_libinput(self) -> DeviceCapability { use crate::libinput::consts::*; @@ -293,19 +308,38 @@ impl InputDeviceCapability { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Linearize)] pub enum InputDeviceAccelProfile { Flat, Adaptive, } -#[derive(Debug, Copy, Clone)] +impl StaticText for InputDeviceAccelProfile { + fn text(&self) -> &'static str { + match self { + InputDeviceAccelProfile::Flat => "Flat", + InputDeviceAccelProfile::Adaptive => "Adaptive", + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Linearize)] pub enum InputDeviceClickMethod { None, ButtonAreas, Clickfinger, } +impl StaticText for InputDeviceClickMethod { + fn text(&self) -> &'static str { + match self { + InputDeviceClickMethod::None => "none", + InputDeviceClickMethod::ButtonAreas => "button-areas", + InputDeviceClickMethod::Clickfinger => "clickfinger", + } + } +} + pub enum BackendEvent { NewDrmDevice(Rc), NewConnector(Rc), @@ -540,6 +574,9 @@ pub trait BackendDrmDevice { fn version(&self) -> Result; fn set_direct_scanout_enabled(&self, enabled: bool); fn is_render_device(&self) -> bool; + fn direct_scanout_enabled(&self) -> bool { + false + } fn create_lease( self: Rc, lessee: Rc, @@ -551,6 +588,10 @@ pub trait BackendDrmDevice { fn set_flip_margin(&self, margin: u64) { let _ = margin; } + #[expect(dead_code)] + fn flip_margin(&self) -> Option { + None + } } pub trait BackendDrmLease { diff --git a/src/backends/metal/present.rs b/src/backends/metal/present.rs index bf211e29..5f1a8940 100644 --- a/src/backends/metal/present.rs +++ b/src/backends/metal/present.rs @@ -1,6 +1,6 @@ use { crate::{ - backend::Connector, + backend::{BackendDrmDevice, Connector}, backends::metal::{ MetalError, allocator::{RenderBuffer, RenderBufferCopy}, @@ -814,13 +814,6 @@ impl MetalConnector { data } - fn direct_scanout_enabled(&self) -> bool { - self.dev - .direct_scanout_enabled - .get() - .unwrap_or(self.state.direct_scanout_enabled.get()) - } - fn prepare_present_fb( &self, cd: &Rc, @@ -832,7 +825,7 @@ impl MetalConnector { ) -> Result { self.trim_scanout_cache(); let try_direct_scanout = try_direct_scanout - && self.direct_scanout_enabled() + && self.dev.direct_scanout_enabled() // at least on AMD, using a FB on a different device for rendering will fail // and destroy the render context. it's possible to work around this by waiting // until the FB is no longer being scanned out, but if a notification pops up diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index a15681e0..3a7e043c 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -186,6 +186,12 @@ impl BackendDrmDevice for MetalDrmDevice { Some(self.id) == self.backend.ctx.get().map(|c| c.dev_id) } + fn direct_scanout_enabled(&self) -> bool { + self.direct_scanout_enabled + .get() + .unwrap_or(self.backend.state.direct_scanout_enabled.get()) + } + fn create_lease( self: Rc, lessee: Rc, @@ -309,11 +315,15 @@ impl BackendDrmDevice for MetalDrmDevice { c.post_commit_margin.set(margin); c.post_commit_margin_decay.reset(margin); if let Some(output) = self.backend.state.root.outputs.get(&c.connector_id) { - output.flip_margin_ns.set(Some(margin)); + output.set_flip_margin(margin); } } } } + + fn flip_margin(&self) -> Option { + Some(self.min_post_commit_margin.get()) + } } pub struct HandleEvents { @@ -2457,7 +2467,7 @@ impl MetalBackend { }; connector.post_commit_margin.set(new_margin); if let Some(global) = &global { - global.flip_margin_ns.set(Some(new_margin)); + global.set_flip_margin(new_margin); } } diff --git a/src/cli.rs b/src/cli.rs index c741b944..3e6f0651 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -27,12 +27,11 @@ use { damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs, reexec::ReexecArgs, run_tagged::RunTaggedArgs, tree::TreeArgs, xwayland::XwaylandArgs, }, - compositor::start_compositor, + compositor::{LogLevel, start_compositor}, format::{Format, ref_formats}, portal, pr_caps::drop_all_pr_caps, }, - ::log::Level, clap::{Args, Parser, Subcommand, ValueEnum, ValueHint, builder::PossibleValue}, clap_complete::Shell, }; @@ -50,7 +49,7 @@ struct Jay { pub struct GlobalArgs { /// The log level. #[clap(value_enum, long, default_value_t)] - pub log_level: CliLogLevel, + pub log_level: LogLevel, } #[derive(Subcommand, Debug)] @@ -176,7 +175,7 @@ pub struct LogArgs { pub struct SetLogArgs { /// The new log level. #[clap(value_enum)] - level: CliLogLevel, + level: LogLevel, } #[derive(Args, Debug)] @@ -194,28 +193,6 @@ pub enum CliBackend { Metal, } -#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default)] -pub enum CliLogLevel { - Trace, - Debug, - #[default] - Info, - Warn, - Error, -} - -impl Into for CliLogLevel { - fn into(self) -> Level { - match self { - CliLogLevel::Trace => Level::Trace, - CliLogLevel::Debug => Level::Debug, - CliLogLevel::Info => Level::Info, - CliLogLevel::Warn => Level::Warn, - CliLogLevel::Error => Level::Error, - } - } -} - #[derive(Args, Debug)] pub struct GenerateArgs { /// The shell to generate completions for diff --git a/src/cli/clients.rs b/src/cli/clients.rs index 64e1c8b3..c045245b 100644 --- a/src/cli/clients.rs +++ b/src/cli/clients.rs @@ -67,7 +67,7 @@ struct KillIdArgs { } pub fn main(global: GlobalArgs, clients_args: ClientsArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let clients = Rc::new(Clients { tc: tc.clone() }); clients.run(clients_args).await; }); diff --git a/src/cli/color_management.rs b/src/cli/color_management.rs index edae33ca..d23235cb 100644 --- a/src/cli/color_management.rs +++ b/src/cli/color_management.rs @@ -26,7 +26,7 @@ pub enum ColorManagementCmd { } pub fn main(global: GlobalArgs, args: ColorManagementArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let cm = ColorManagement { tc: tc.clone() }; cm.run(args).await; }); diff --git a/src/cli/damage_tracking.rs b/src/cli/damage_tracking.rs index 70b8d432..68a27851 100644 --- a/src/cli/damage_tracking.rs +++ b/src/cli/damage_tracking.rs @@ -55,7 +55,7 @@ pub struct DecayArgs { } pub fn main(global: GlobalArgs, damage_tracking_args: DamageTrackingArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let damage_tracking = Rc::new(DamageTracking { tc: tc.clone() }); damage_tracking.run(damage_tracking_args).await; }); diff --git a/src/cli/idle.rs b/src/cli/idle.rs index 9bc190ce..d2c001e4 100644 --- a/src/cli/idle.rs +++ b/src/cli/idle.rs @@ -51,7 +51,7 @@ pub struct IdleSetGracePeriodArgs { } pub fn main(global: GlobalArgs, args: IdleArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let idle = Idle { tc: tc.clone() }; idle.run(args).await; }); diff --git a/src/cli/input.rs b/src/cli/input.rs index 62e76487..2df25758 100644 --- a/src/cli/input.rs +++ b/src/cli/input.rs @@ -9,7 +9,7 @@ use { LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, LIBINPUT_CONFIG_CLICK_METHOD_NONE, }, tools::tool_client::{Handle, ToolClient, with_tool_client}, - utils::{errorfmt::ErrorFmt, string_ext::StringExt}, + utils::{errorfmt::ErrorFmt, static_text::StaticText, string_ext::StringExt}, wire::{JayInputId, jay_compositor, jay_input}, }, clap::{Args, Subcommand, ValueEnum, ValueHint}, @@ -324,7 +324,7 @@ pub struct UseHardwareCursorArgs { } pub fn main(global: GlobalArgs, args: InputArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let idle = Rc::new(Input { tc: tc.clone() }); idle.run(args).await; }); @@ -854,21 +854,11 @@ impl Input { print!("{prefix} capabilities:"); let mut first = true; for cap in &device.capabilities { - use InputDeviceCapability::*; print!(" "); if !mem::take(&mut first) { print!("| "); } - let name = match cap { - Keyboard => "keyboard", - Pointer => "pointer", - Touch => "touch", - TabletTool => "tablet tool", - TabletPad => "tablet pad", - Gesture => "gesture", - Switch => "switch", - }; - print!("{}", name); + print!("{}", cap.text()); } println!(); if let Some(v) = &device.accel_profile { diff --git a/src/cli/log.rs b/src/cli/log.rs index 2023daf6..ece70ae6 100644 --- a/src/cli/log.rs +++ b/src/cli/log.rs @@ -18,7 +18,7 @@ use { }; pub fn main(global: GlobalArgs, args: LogArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let logger = Rc::new(Log { tc: tc.clone(), path: RefCell::new(None), diff --git a/src/cli/quit.rs b/src/cli/quit.rs index 5cb03d2d..13722ece 100644 --- a/src/cli/quit.rs +++ b/src/cli/quit.rs @@ -8,7 +8,7 @@ use { }; pub fn main(global: GlobalArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { run(tc).await; }); } diff --git a/src/cli/randr.rs b/src/cli/randr.rs index 9ec490b5..d6041d39 100644 --- a/src/cli/randr.rs +++ b/src/cli/randr.rs @@ -466,7 +466,7 @@ fn blend_space_possible_values() -> Vec { } pub fn main(global: GlobalArgs, args: RandrArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let idle = Rc::new(Randr { tc: tc.clone() }); idle.run(args).await; }); diff --git a/src/cli/reexec.rs b/src/cli/reexec.rs index c4049e83..0889fa3b 100644 --- a/src/cli/reexec.rs +++ b/src/cli/reexec.rs @@ -20,7 +20,7 @@ pub struct ReexecArgs { } pub fn main(global: GlobalArgs, reexec_args: ReexecArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let rexec = Rc::new(Reexec { tc: tc.clone() }); rexec.run(reexec_args).await; }); diff --git a/src/cli/run_privileged.rs b/src/cli/run_privileged.rs index 11a78355..16c9b7fc 100644 --- a/src/cli/run_privileged.rs +++ b/src/cli/run_privileged.rs @@ -10,7 +10,7 @@ use { }; pub fn main(global: GlobalArgs, args: RunPrivilegedArgs) { - Logger::install_stderr(global.log_level.into()); + Logger::install_stderr(global.log_level); if let Some(xrd) = xrd() { let mut wd = match std::env::var(WAYLAND_DISPLAY) { Ok(v) => v, diff --git a/src/cli/run_tagged.rs b/src/cli/run_tagged.rs index d2baa230..9e985bf3 100644 --- a/src/cli/run_tagged.rs +++ b/src/cli/run_tagged.rs @@ -21,7 +21,7 @@ pub struct RunTaggedArgs { } pub fn main(global: GlobalArgs, run_tagged_args: RunTaggedArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let run_tagged = Rc::new(RunTagged { tc: tc.clone() }); run_tagged.run(run_tagged_args).await; }); diff --git a/src/cli/screenshot.rs b/src/cli/screenshot.rs index 8e8894e6..7ffd5f63 100644 --- a/src/cli/screenshot.rs +++ b/src/cli/screenshot.rs @@ -30,7 +30,7 @@ use { }; pub fn main(global: GlobalArgs, args: ScreenshotArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let screenshot = Rc::new(Screenshot { tc: tc.clone(), args, diff --git a/src/cli/seat_test.rs b/src/cli/seat_test.rs index ce8404d0..69a9ba98 100644 --- a/src/cli/seat_test.rs +++ b/src/cli/seat_test.rs @@ -27,7 +27,7 @@ use { }; pub fn main(global: GlobalArgs, args: SeatTestArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let screenshot = Rc::new(SeatTest { tc: tc.clone(), args, diff --git a/src/cli/set_log_level.rs b/src/cli/set_log_level.rs index 97a00880..9587cc0f 100644 --- a/src/cli/set_log_level.rs +++ b/src/cli/set_log_level.rs @@ -4,11 +4,12 @@ use { tools::tool_client::{ToolClient, with_tool_client}, wire::jay_compositor::SetLogLevel, }, + linearize::Linearize, std::rc::Rc, }; pub fn main(global: GlobalArgs, args: SetLogArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let logger = Rc::new(Log { tc: tc.clone(), args, @@ -27,7 +28,7 @@ async fn run(log: Rc) { let comp = tc.jay_compositor().await; tc.send(SetLogLevel { self_id: comp, - level: log.args.level as u32, + level: log.args.level.linearize() as u32, }); tc.round_trip().await; } diff --git a/src/cli/tree.rs b/src/cli/tree.rs index 1ee94d93..670267e3 100644 --- a/src/cli/tree.rs +++ b/src/cli/tree.rs @@ -62,7 +62,7 @@ struct QueryWorkspaceNameArgs { } pub fn main(global: GlobalArgs, tree_args: TreeArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let comp = tc.jay_compositor().await; let tree = Rc::new(Tree { tc: tc.clone(), diff --git a/src/cli/unlock.rs b/src/cli/unlock.rs index d097f584..9e739399 100644 --- a/src/cli/unlock.rs +++ b/src/cli/unlock.rs @@ -8,7 +8,7 @@ use { }; pub fn main(global: GlobalArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let logger = Rc::new(Unlocker { tc: tc.clone() }); run(logger).await; }); diff --git a/src/cli/xwayland.rs b/src/cli/xwayland.rs index bdd0f16e..6b188072 100644 --- a/src/cli/xwayland.rs +++ b/src/cli/xwayland.rs @@ -39,7 +39,7 @@ pub enum CliScalingMode { } pub fn main(global: GlobalArgs, args: XwaylandArgs) { - with_tool_client(global.log_level.into(), |tc| async move { + with_tool_client(global.log_level, |tc| async move { let xwayland = Xwayland { tc: tc.clone() }; xwayland.run(args).await; }); diff --git a/src/client.rs b/src/client.rs index 0dc2634b..a1fa33cb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -25,6 +25,7 @@ use { pending_serial::PendingSerial, pid_info::{PidInfo, get_pid_info, get_socket_creds}, pidfd_send_signal::pidfd_send_signal, + static_text::StaticText, }, wire::WlRegistryId, }, @@ -68,6 +69,28 @@ bitflags! { CAP_GAMMA_CONTROL_MANAGER = 1 << 14, } +impl StaticText for ClientCapsEnum { + fn text(&self) -> &'static str { + match self { + ClientCapsEnum::CAP_DATA_CONTROL_MANAGER => "data-control", + ClientCapsEnum::CAP_VIRTUAL_KEYBOARD_MANAGER => "virtual-keyboard", + ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_LIST => "foreign-toplevel-list", + ClientCapsEnum::CAP_IDLE_NOTIFIER => "idle-notifier", + ClientCapsEnum::CAP_SESSION_LOCK_MANAGER => "session-lock", + ClientCapsEnum::CAP_JAY_COMPOSITOR => "jay-compositor", + ClientCapsEnum::CAP_LAYER_SHELL => "layer-shell", + ClientCapsEnum::CAP_SCREENCOPY_MANAGER => "screencopy", + ClientCapsEnum::CAP_SEAT_MANAGER => "seat-manager", + ClientCapsEnum::CAP_DRM_LEASE => "drm-lease", + ClientCapsEnum::CAP_INPUT_METHOD => "input-method", + ClientCapsEnum::CAP_WORKSPACE => "workspace-manager", + ClientCapsEnum::CAP_FOREIGN_TOPLEVEL_MANAGER => "foreign-toplevel-manager", + ClientCapsEnum::CAP_HEAD_MANAGER => "head-manager", + ClientCapsEnum::CAP_GAMMA_CONTROL_MANAGER => "gamma-control-manager", + } + } +} + pub const CAPS_DEFAULT: ClientCaps = ClientCaps(CAP_LAYER_SHELL.0 | CAP_DRM_LEASE.0); pub const CAPS_DEFAULT_SANDBOXED: ClientCaps = ClientCaps(CAP_DRM_LEASE.0); diff --git a/src/compositor.rs b/src/compositor.rs index fa6e9835..27a96be2 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -36,7 +36,7 @@ use { HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done, }, jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts}, - wl_output::{OutputId, PersistentOutputState, WlOutputGlobal}, + wl_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal}, wl_seat::handle_position_hint_requests, wl_surface::{ NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events, @@ -68,6 +68,7 @@ use { clone3::ensure_reaper, clonecell::CloneCell, errorfmt::ErrorFmt, + event_listener::handle_lazy_event_sources, fdcloser::FdCloser, nice::{did_elevate_scheduler, elevate_scheduler}, numcell::NumCell, @@ -76,6 +77,7 @@ use { rc_eq::RcEq, refcounted::RefCounted, run_toplevel::RunToplevel, + static_text::StaticText, tri::Try, }, version::VERSION, @@ -83,8 +85,11 @@ use { wheel::{Wheel, WheelError}, }, ahash::AHashSet, + clap::ValueEnum, forker::ForkerProxy, - jay_config::_private::DEFAULT_SEAT_NAME, + jay_config::{_private::DEFAULT_SEAT_NAME, logging::LogLevel as ConfigLogLevel}, + linearize::Linearize, + log::LevelFilter, std::{ cell::{Cell, RefCell}, env, @@ -100,6 +105,9 @@ use { pub const MAX_EXTENTS: i32 = (1 << 22) - 1; +pub const MIN_SCALE: Scale = Scale::from_wl(60); +pub const MAX_SCALE: Scale = Scale::from_int(16); + pub fn start_compositor(global: GlobalArgs, args: RunArgs) { sighand::reset_all(); let reaper_pid = ensure_reaper(); @@ -112,9 +120,9 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) { None }; let forker = create_forker(reaper_pid); - let portal = portal::run_from_compositor(global.log_level.into()); + let portal = portal::run_from_compositor(global.log_level); enable_profiler(); - let logger = Logger::install_compositor(global.log_level.into()); + let logger = Logger::install_compositor(global.log_level); let portal = match portal { Ok(p) => Some(p), Err(e) => { @@ -191,7 +199,8 @@ fn start_compositor2( test_future: Option, caps_thread: Option, ) -> Result<(), CompositorError> { - log::info!("pid = {}", uapi::getpid()); + let pid = uapi::getpid(); + log::info!("pid = {pid}"); log::info!("version = {VERSION}"); if did_elevate_scheduler() { log::info!("Running with elevated scheduler: SCHED_RR"); @@ -216,6 +225,7 @@ fn start_compositor2( let crit_ids = Rc::new(CritMatcherIds::default()); let eventfd_cache = EventfdCache::new(&ring, &engine); let state = Rc::new(State { + pid, kb_ctx, backend: CloneCell::new(Rc::new(DummyBackend)), forker: Default::default(), @@ -286,6 +296,8 @@ fn start_compositor2( use_wire_scale: Default::default(), wire_scale: Default::default(), windows: Default::default(), + client: Default::default(), + display: Default::default(), }, acceptor: Default::default(), serial: Default::default(), @@ -377,6 +389,7 @@ fn start_compositor2( copy_device_registry: Rc::new(CopyDeviceRegistry::new(&ring, &engine, &eventfd_cache)), supports_presentation_feedback: Default::default(), eventfd_cache, + lazy_event_sources: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); @@ -577,6 +590,10 @@ fn start_global_event_handlers(state: &Rc) -> Vec> { Phase::PostLayout, handle_xdg_surface_configure_events(state.clone()), ), + eng.spawn( + "lazy event sources", + handle_lazy_event_sources(state.clone()), + ), ] } @@ -695,6 +712,9 @@ fn create_dummy_output(state: &Rc) { eotf: backend_state.eotf, supported_formats: Default::default(), brightness: None, + blend_space: BlendSpace::Srgb, + use_native_gamut: false, + vrr_cursor_hz: None, }; let connector_data = Rc::new(ConnectorData { id, @@ -798,3 +818,65 @@ pub fn config_dir() -> Option { None } } + +#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, Eq, PartialEq, Linearize)] +pub enum LogLevel { + Trace, + Debug, + #[default] + Info, + Warn, + Error, + Off, +} + +impl Into for LogLevel { + fn into(self) -> LevelFilter { + match self { + LogLevel::Trace => LevelFilter::Trace, + LogLevel::Debug => LevelFilter::Debug, + LogLevel::Info => LevelFilter::Info, + LogLevel::Warn => LevelFilter::Warn, + LogLevel::Error => LevelFilter::Error, + LogLevel::Off => LevelFilter::Off, + } + } +} + +impl From for LogLevel { + fn from(value: LevelFilter) -> Self { + match value { + LevelFilter::Trace => LogLevel::Trace, + LevelFilter::Debug => LogLevel::Debug, + LevelFilter::Info => LogLevel::Info, + LevelFilter::Warn => LogLevel::Warn, + LevelFilter::Error => LogLevel::Error, + LevelFilter::Off => LogLevel::Off, + } + } +} + +impl StaticText for LogLevel { + fn text(&self) -> &'static str { + match self { + LogLevel::Off => "Off", + LogLevel::Error => "Error", + LogLevel::Warn => "Warn", + LogLevel::Info => "Info", + LogLevel::Debug => "Debug", + LogLevel::Trace => "Trace", + } + } +} + +impl From for LogLevel { + fn from(value: ConfigLogLevel) -> Self { + match value { + ConfigLogLevel::Trace => LogLevel::Trace, + ConfigLogLevel::Debug => LogLevel::Debug, + ConfigLogLevel::Info => LogLevel::Info, + ConfigLogLevel::Warn => LogLevel::Warn, + ConfigLogLevel::Error => LogLevel::Error, + } + } +} diff --git a/src/config/handler.rs b/src/config/handler.rs index 4ff91fe0..75a1c336 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -9,7 +9,6 @@ use { client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientId}, cmm::cmm_eotf::Eotf, compositor::{MAX_EXTENTS, WAYLAND_DISPLAY}, - config::ConfigProxy, criteria::{ CritLiteralOrRegex, CritMgrExt, CritTarget, CritUpstreamNode, clm::ClmLeafMatcher, @@ -27,12 +26,11 @@ use { scale::Scale, state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State}, tagged_acceptor::TaggedAcceptorError, - theme::{Color, ThemeSized}, + theme::{ThemeColor, ThemeSized}, tree::{ - ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode, - TearingMode, TileState, ToplevelData, ToplevelNode, VrrMode, WorkspaceNode, - toplevel_create_split, toplevel_parent_container, toplevel_set_floating, - toplevel_set_workspace, + ContainerSplit, OutputNode, TearingMode, TileState, ToplevelData, ToplevelNode, + VrrMode, WorkspaceNode, toplevel_create_split, toplevel_parent_container, + toplevel_set_floating, toplevel_set_workspace, }, utils::{ asyncevent::AsyncEvent, @@ -66,7 +64,7 @@ use { }, }, keyboard::{Group, Keymap, mods::Modifiers, syms::KeySym}, - logging::LogLevel, + logging::LogLevel as ConfigLogLevel, theme::{BarPosition, colors::Colorable, sized::Resizable}, timer::Timer as JayTimer, video::{ @@ -88,7 +86,6 @@ use { hash::Hash, ops::Deref, rc::{Rc, Weak}, - sync::Arc, time::Duration, }, thiserror::Error, @@ -263,17 +260,17 @@ impl ConfigProxyHandler { fn handle_log_request( &self, - level: LogLevel, + level: ConfigLogLevel, msg: &str, file: Option<&str>, line: Option, ) { let level = match level { - LogLevel::Error => Level::Error, - LogLevel::Warn => Level::Warn, - LogLevel::Info => Level::Info, - LogLevel::Debug => Level::Debug, - LogLevel::Trace => Level::Trace, + ConfigLogLevel::Error => Level::Error, + ConfigLogLevel::Warn => Level::Warn, + ConfigLogLevel::Info => Level::Info, + ConfigLogLevel::Debug => Level::Debug, + ConfigLogLevel::Trace => Level::Trace, }; let debug = fmt::from_fn(|fmt| { if let Some(file) = file { @@ -424,22 +421,7 @@ impl ConfigProxyHandler { } fn handle_reload(&self) { - log::info!("Reloading config"); - let config = match ConfigProxy::from_config_dir(&self.state) { - Ok(c) => c, - Err(e) => { - log::error!("Cannot reload config: {}", ErrorFmt(e)); - return; - } - }; - if let Some(config) = self.state.config.take() { - config.destroy(); - for seat in self.state.globals.seats.lock().values() { - seat.clear_shortcuts(); - } - } - config.configure(true); - self.state.config.set(Some(Rc::new(config))); + self.state.reload_config(); } fn handle_get_seat_fullscreen(&self, seat: Seat) -> Result<(), CphError> { @@ -816,7 +798,7 @@ impl ConfigProxyHandler { left_handed: bool, ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_left_handed(left_handed); + dev.set_left_handed(left_handed); Ok(()) } @@ -831,31 +813,31 @@ impl ConfigProxyHandler { ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, _ => return Err(CphError::UnknownAccelProfile(accel_profile)), }; - dev.device.set_accel_profile(profile); + dev.set_accel_profile(profile); Ok(()) } fn handle_set_accel_speed(&self, device: InputDevice, speed: f64) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_accel_speed(speed); + dev.set_accel_speed(speed); Ok(()) } fn handle_set_px_per_wheel_scroll(&self, device: InputDevice, px: f64) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.px_per_scroll_wheel.set(px); + dev.set_px_per_scroll_wheel(px); Ok(()) } fn handle_set_tap_enabled(&self, device: InputDevice, enabled: bool) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_tap_enabled(enabled); + dev.set_tap_enabled(enabled); Ok(()) } fn handle_set_drag_enabled(&self, device: InputDevice, enabled: bool) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_drag_enabled(enabled); + dev.set_drag_enabled(enabled); Ok(()) } @@ -865,7 +847,7 @@ impl ConfigProxyHandler { enabled: bool, ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_natural_scrolling_enabled(enabled); + dev.set_natural_scrolling_enabled(enabled); Ok(()) } @@ -875,7 +857,7 @@ impl ConfigProxyHandler { enabled: bool, ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_drag_lock_enabled(enabled); + dev.set_drag_lock_enabled(enabled); Ok(()) } @@ -885,7 +867,7 @@ impl ConfigProxyHandler { matrix: [[f64; 2]; 2], ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_transform_matrix(matrix); + dev.set_transform_matrix(matrix); Ok(()) } @@ -895,7 +877,7 @@ impl ConfigProxyHandler { matrix: [[f32; 3]; 2], ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_calibration_matrix(matrix); + dev.set_calibration_matrix(matrix); Ok(()) } @@ -911,7 +893,7 @@ impl ConfigProxyHandler { CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, _ => return Err(CphError::UnknownClickMethod(click_method)), }; - dev.device.set_click_method(method); + dev.set_click_method(method); Ok(()) } @@ -921,13 +903,12 @@ impl ConfigProxyHandler { enabled: bool, ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.device.set_middle_button_emulation_enabled(enabled); + dev.set_middle_button_emulation_enabled(enabled); Ok(()) } fn handle_set_ei_socket_enabled(&self, enabled: bool) { - self.state.enable_ei_acceptor.set(enabled); - self.state.update_ei_acceptor(); + self.state.set_ei_socket_enabled(enabled); } fn handle_get_workspace(&self, name: &str) { @@ -971,7 +952,6 @@ impl ConfigProxyHandler { fn handle_set_flip_margin(&self, device: DrmDevice, margin: Duration) -> Result<(), CphError> { self.get_drm_device(device)? - .dev .set_flip_margin(margin.as_nanos().try_into().unwrap_or(u64::MAX)); Ok(()) } @@ -982,8 +962,7 @@ impl ConfigProxyHandler { XScalingMode::DOWNSCALED => true, _ => return Err(CphError::UnknownXScalingMode(mode)), }; - self.state.xwayland.use_wire_scale.set(use_wire_scale); - self.state.update_xwayland_wire_scale(); + self.state.set_xwayland_use_wire_scale(use_wire_scale); Ok(()) } @@ -993,13 +972,11 @@ impl ConfigProxyHandler { } fn handle_set_ui_drag_enabled(&self, enabled: bool) { - self.state.ui_drag_enabled.set(enabled); + self.state.set_ui_drag_enabled(enabled); } fn handle_set_ui_drag_threshold(&self, threshold: i32) { - let threshold = threshold.max(1); - let squared = threshold.saturating_mul(threshold); - self.state.ui_drag_threshold_squared.set(squared); + self.state.set_ui_drag_threshold(threshold.max(1)); } fn handle_set_direct_scanout_enabled( @@ -1010,7 +987,6 @@ impl ConfigProxyHandler { match device { Some(dev) => self .get_drm_device(dev)? - .dev .set_direct_scanout_enabled(enabled), _ => self.state.direct_scanout_enabled.set(enabled), } @@ -1403,12 +1379,7 @@ impl ConfigProxyHandler { } fn handle_set_float_above_fullscreen(&self, above: bool) { - self.state.float_above_fullscreen.set(above); - for seat in self.state.globals.seats.lock().values() { - seat.emulate_cursor_moved(); - seat.trigger_tree_changed(false); - } - self.state.root.update_visible(&self.state); + self.state.set_float_above_fullscreen(above); } fn handle_get_float_above_fullscreen(&self) { @@ -1418,10 +1389,7 @@ impl ConfigProxyHandler { } fn handle_set_show_bar(&self, show: bool) { - self.state.show_bar.set(show); - for output in self.state.root.outputs.lock().values() { - output.on_spaces_changed(); - } + self.state.set_show_bar(show); } fn handle_get_show_bar(&self) { @@ -1431,8 +1399,7 @@ impl ConfigProxyHandler { } fn handle_set_show_titles(&self, show: bool) { - self.state.theme.show_titles.set(show); - self.spaces_change(); + self.state.set_show_titles(show); } fn handle_get_show_titles(&self) { @@ -1445,8 +1412,7 @@ impl ConfigProxyHandler { let Ok(position) = position.try_into() else { return Err(CphError::UnknownBarPosition(position)); }; - self.state.theme.bar_position.set(position); - self.spaces_change(); + self.state.set_bar_position(position); Ok(()) } @@ -1457,19 +1423,11 @@ impl ConfigProxyHandler { } fn handle_set_show_float_pin_icon(&self, show: bool) { - self.state.show_pin_icon.set(show); - for stacked in self.state.root.stacked.iter() { - if let Some(float) = stacked.deref().clone().node_into_float() { - float.schedule_render_titles(); - } - } + self.state.set_show_pin_icon(show); } fn handle_set_workspace_display_order(&self, order: WorkspaceDisplayOrder) { - self.state.workspace_display_order.set(order.into()); - for output in self.state.root.outputs.lock().values() { - output.handle_workspace_display_order_update(); - } + self.state.set_workspace_display_order(order.into()); } fn handle_get_seat_float_pinned(&self, seat: Seat) -> Result<(), CphError> { @@ -1849,17 +1807,8 @@ impl ConfigProxyHandler { Ok(()) } - fn handle_set_log_level(&self, level: LogLevel) { - let level = match level { - LogLevel::Error => Level::Error, - LogLevel::Warn => Level::Warn, - LogLevel::Info => Level::Info, - LogLevel::Debug => Level::Debug, - LogLevel::Trace => Level::Trace, - }; - if let Some(logger) = &self.state.logger { - logger.set_level(level); - } + fn handle_set_log_level(&self, level: ConfigLogLevel) { + self.state.set_log_level(level.into()); } fn handle_grab(&self, kb: InputDevice, grab: bool) -> Result<(), CphError> { @@ -2421,44 +2370,6 @@ impl ConfigProxyHandler { Ok(()) } - fn spaces_change(&self) { - struct V; - impl NodeVisitorBase for V { - fn visit_output(&mut self, node: &Rc) { - node.on_spaces_changed(); - node.node_visit_children(self); - } - fn visit_container(&mut self, node: &Rc) { - node.on_spaces_changed(); - node.node_visit_children(self); - } - fn visit_float(&mut self, node: &Rc) { - node.on_spaces_changed(); - node.node_visit_children(self); - } - } - self.state.root.clone().node_visit(&mut V); - self.state.damage(self.state.root.extents.get()); - self.state.icons.update_sizes(&self.state); - } - - fn colors_changed(&self) { - struct V; - impl NodeVisitorBase for V { - fn visit_container(&mut self, node: &Rc) { - node.on_colors_changed(); - node.node_visit_children(self); - } - fn visit_float(&mut self, node: &Rc) { - node.on_colors_changed(); - node.node_visit_children(self); - } - } - self.state.root.clone().node_visit(&mut V); - self.state.damage(self.state.root.extents.get()); - self.state.icons.clear(); - } - fn get_sized(&self, sized: Resizable) -> Result { use jay_config::theme::sized::*; let sized = match sized { @@ -2486,46 +2397,32 @@ impl ConfigProxyHandler { if size > sized.max() { return Err(CphError::InvalidSize(size, sized)); } - let field = sized.field(&self.state.theme); - field.val.set(size); - field.set.set(true); - self.spaces_change(); + self.state.set_size(sized, size); Ok(()) } fn handle_reset_colors(&self) { - self.state.theme.colors.reset(); - self.colors_changed(); + self.state.reset_colors(); } fn handle_reset_sizes(&self) { - self.state.theme.sizes.reset(); - self.spaces_change(); + self.state.reset_sizes(); } fn handle_reset_font(&self) { - let theme = &self.state.theme; - theme.font.set(self.state.theme.default_font.clone()); - theme.bar_font.set(None); - theme.title_font.set(None); + self.state.reset_fonts(); } fn handle_set_font(&self, font: &str) { - self.state.theme.font.set(Arc::new(font.to_string())); + self.state.set_font(font); } fn handle_set_bar_font(&self, font: &str) { - self.state - .theme - .bar_font - .set(Some(Arc::new(font.to_string()))); + self.state.set_bar_font(Some(font)); } fn handle_set_title_font(&self, font: &str) { - self.state - .theme - .title_font - .set(Some(Arc::new(font.to_string()))); + self.state.set_title_font(Some(font)); } fn handle_get_font(&self) { @@ -2533,34 +2430,37 @@ impl ConfigProxyHandler { self.respond(Response::GetFont { font }); } - fn get_color(&self, colorable: Colorable) -> Result<&Cell, CphError> { - let colors = &self.state.theme.colors; + fn get_color(&self, colorable: Colorable) -> Result { use jay_config::theme::colors::*; let colorable = match colorable { - UNFOCUSED_TITLE_BACKGROUND_COLOR => &colors.unfocused_title_background, - FOCUSED_TITLE_BACKGROUND_COLOR => &colors.focused_title_background, + UNFOCUSED_TITLE_BACKGROUND_COLOR => ThemeColor::unfocused_title_background, + FOCUSED_TITLE_BACKGROUND_COLOR => ThemeColor::focused_title_background, CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR => { - &colors.captured_unfocused_title_background + ThemeColor::captured_unfocused_title_background } - CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR => &colors.captured_focused_title_background, - FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR => &colors.focused_inactive_title_background, - BACKGROUND_COLOR => &colors.background, - BAR_BACKGROUND_COLOR => &colors.bar_background, - SEPARATOR_COLOR => &colors.separator, - BORDER_COLOR => &colors.border, - UNFOCUSED_TITLE_TEXT_COLOR => &colors.unfocused_title_text, - FOCUSED_TITLE_TEXT_COLOR => &colors.focused_title_text, - FOCUSED_INACTIVE_TITLE_TEXT_COLOR => &colors.focused_inactive_title_text, - BAR_STATUS_TEXT_COLOR => &colors.bar_text, - ATTENTION_REQUESTED_BACKGROUND_COLOR => &colors.attention_requested_background, - HIGHLIGHT_COLOR => &colors.highlight, + CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR => { + ThemeColor::captured_focused_title_background + } + FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR => { + ThemeColor::focused_inactive_title_background + } + BACKGROUND_COLOR => ThemeColor::background, + BAR_BACKGROUND_COLOR => ThemeColor::bar_background, + SEPARATOR_COLOR => ThemeColor::separator, + BORDER_COLOR => ThemeColor::border, + UNFOCUSED_TITLE_TEXT_COLOR => ThemeColor::unfocused_title_text, + FOCUSED_TITLE_TEXT_COLOR => ThemeColor::focused_title_text, + FOCUSED_INACTIVE_TITLE_TEXT_COLOR => ThemeColor::focused_inactive_title_text, + BAR_STATUS_TEXT_COLOR => ThemeColor::bar_text, + ATTENTION_REQUESTED_BACKGROUND_COLOR => ThemeColor::attention_requested_background, + HIGHLIGHT_COLOR => ThemeColor::highlight, _ => return Err(CphError::UnknownColor(colorable.0)), }; Ok(colorable) } fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> { - let color = self.get_color(colorable)?.get(); + let color = self.get_color(colorable)?.field(&self.state.theme).get(); let [r, g, b, a] = color.to_array(Eotf::Gamma22); let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a); self.respond(Response::GetColor { color }); @@ -2572,8 +2472,8 @@ impl ConfigProxyHandler { colorable: Colorable, color: jay_config::theme::Color, ) -> Result<(), CphError> { - self.get_color(colorable)?.set(color.into()); - self.colors_changed(); + self.state + .set_color(self.get_color(colorable)?, color.into()); Ok(()) } diff --git a/src/criteria/clm.rs b/src/criteria/clm.rs index be88c270..f9e1c595 100644 --- a/src/criteria/clm.rs +++ b/src/criteria/clm.rs @@ -7,6 +7,7 @@ use { CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt, CritUpstreamNode, FixedRootMatcher, RootMatcherMap, clm::clm_matchers::{ + clmm_id::ClmMatchId, clmm_is_xwayland::ClmMatchIsXwayland, clmm_pid::ClmMatchPid, clmm_sandboxed::ClmMatchSandboxed, @@ -62,6 +63,7 @@ pub struct RootMatchers { comm: ClmRootMatcherMap, exe: ClmRootMatcherMap, tag: ClmRootMatcherMap, + id: ClmRootMatcherMap, } impl RootMatchers { @@ -74,6 +76,7 @@ impl RootMatchers { self.comm.clear(); self.exe.clear(); self.tag.clear(); + self.id.clear(); } } @@ -184,6 +187,7 @@ impl ClMatcherManager { unconditional!(comm); unconditional!(exe); unconditional!(tag); + unconditional!(id); fixed!(sandboxed); fixed!(is_xwayland); self.constant[true].handle(data); @@ -229,6 +233,11 @@ impl ClMatcherManager { pub fn tag(&self, string: CritLiteralOrRegex) -> Rc { self.root(ClmMatchTag::new(string)) } + + #[expect(dead_code)] + pub fn id(&self, id: ClientId) -> Rc { + self.root(ClmMatchId(id)) + } } impl CritTarget for Rc { diff --git a/src/criteria/clm/clm_matchers.rs b/src/criteria/clm/clm_matchers.rs index bd661aa4..f39837fc 100644 --- a/src/criteria/clm/clm_matchers.rs +++ b/src/criteria/clm/clm_matchers.rs @@ -17,6 +17,7 @@ macro_rules! fixed_root_criterion { }; } +pub mod clmm_id; pub mod clmm_is_xwayland; pub mod clmm_pid; pub mod clmm_sandboxed; diff --git a/src/criteria/clm/clm_matchers/clmm_id.rs b/src/criteria/clm/clm_matchers/clmm_id.rs new file mode 100644 index 00000000..1cb347af --- /dev/null +++ b/src/criteria/clm/clm_matchers/clmm_id.rs @@ -0,0 +1,19 @@ +use { + crate::{ + client::{Client, ClientId}, + criteria::{RootMatcherMap, clm::RootMatchers, crit_graph::CritRootCriterion}, + }, + std::rc::Rc, +}; + +pub struct ClmMatchId(pub ClientId); + +impl CritRootCriterion> for ClmMatchId { + fn matches(&self, data: &Rc) -> bool { + data.id == self.0 + } + + fn nodes(roots: &RootMatchers) -> Option<&RootMatcherMap, Self>> { + Some(&roots.id) + } +} diff --git a/src/cursor_user.rs b/src/cursor_user.rs index 6d911048..f7ee78b3 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -198,6 +198,11 @@ impl CursorUserGroup { } } + #[expect(dead_code)] + pub fn cursor_size(&self) -> u32 { + self.size.get() + } + fn output_center(&self, output: &Rc) -> (Fixed, Fixed) { let pos = output.global.pos.get(); let x = Fixed::from_int((pos.x1() + pos.x2()) / 2); diff --git a/src/gfx_api.rs b/src/gfx_api.rs index f178b753..2900f5a8 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -15,7 +15,7 @@ use { state::State, theme::Color, tree::{Node, OutputNode, Transform}, - utils::{clonecell::UnsafeCellCloneSafe, errorfmt::ErrorFmt}, + utils::{clonecell::UnsafeCellCloneSafe, errorfmt::ErrorFmt, static_text::StaticText}, video::{ Modifier, dmabuf::DmaBuf, @@ -47,6 +47,12 @@ pub enum GfxApi { Vulkan, } +impl StaticText for GfxApi { + fn text(&self) -> &'static str { + self.to_str() + } +} + impl TryFrom for GfxApi { type Error = (); diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 78a1f396..47e1af43 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -10,7 +10,6 @@ mod device; mod dmabuf_buffer; mod eotfs; mod format; -mod gpu_alloc_ash; mod image; mod instance; mod pipeline; diff --git a/src/gfx_apis/vulkan/allocator.rs b/src/gfx_apis/vulkan/allocator.rs index 0b6d994c..c7fd5c1a 100644 --- a/src/gfx_apis/vulkan/allocator.rs +++ b/src/gfx_apis/vulkan/allocator.rs @@ -1,13 +1,9 @@ use { crate::{ cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker}, - gfx_apis::vulkan::{ - VulkanError, - device::VulkanDevice, - gpu_alloc_ash::{self, AshMemoryDevice}, - renderer::VulkanRenderer, - }, + gfx_apis::vulkan::{VulkanError, device::VulkanDevice, renderer::VulkanRenderer}, utils::{numcell::NumCell, page_size::page_size, ptr_ext::MutPtrExt}, + vulkan_core::gpu_alloc_ash::{self, AshMemoryDevice}, }, ash::{ Device, diff --git a/src/ifs/head_management.rs b/src/ifs/head_management.rs index 0821b065..8b1bf6bc 100644 --- a/src/ifs/head_management.rs +++ b/src/ifs/head_management.rs @@ -7,9 +7,12 @@ use { client::ClientId, format::Format, globals::GlobalName, - ifs::head_management::{ - head_management_macros::HeadExts, jay_head_manager_session_v1::JayHeadManagerSessionV1, - jay_head_v1::JayHeadV1, + ifs::{ + head_management::{ + head_management_macros::HeadExts, + jay_head_manager_session_v1::JayHeadManagerSessionV1, jay_head_v1::JayHeadV1, + }, + wl_output::BlendSpace, }, scale::Scale, state::OutputData, @@ -18,7 +21,7 @@ use { wire::JayHeadManagerSessionV1Id, }, std::{ - cell::{Cell, RefCell}, + cell::{Cell, Ref, RefCell}, rc::Rc, }, thiserror::Error, @@ -92,6 +95,20 @@ pub struct HeadState { pub eotf: BackendEotfs, pub supported_formats: RcEq>, pub brightness: Option, + pub blend_space: BlendSpace, + pub use_native_gamut: bool, + pub vrr_cursor_hz: Option, +} + +pub struct ReadOnlyHeadState { + state: Rc>, +} + +impl ReadOnlyHeadState { + #[expect(dead_code)] + pub fn borrow(&self) -> Ref<'_, HeadState> { + self.state.borrow() + } } impl HeadState { @@ -218,6 +235,13 @@ impl HeadManagers { } } + #[expect(dead_code)] + pub fn state(&self) -> ReadOnlyHeadState { + ReadOnlyHeadState { + state: self.state.clone(), + } + } + pub fn handle_removed(&self) { for head in self.managers.lock().drain_values() { skip_in_transaction!(head); @@ -240,6 +264,10 @@ impl HeadManagers { state.transform = n.global.persistent.transform.get(); state.vrr_mode = n.global.persistent.vrr_mode.get(); state.tearing_mode = n.global.persistent.tearing_mode.get(); + state.brightness = n.global.persistent.brightness.get(); + state.blend_space = n.global.persistent.blend_space.get(); + state.use_native_gamut = n.global.persistent.use_native_gamut.get(); + state.vrr_cursor_hz = n.global.persistent.vrr_cursor_hz.get(); } for head in self.managers.lock().values() { skip_in_transaction!(head); @@ -530,4 +558,19 @@ impl HeadManagers { } } } + + pub fn handle_blend_space_change(&self, blend_space: BlendSpace) { + let state = &mut *self.state.borrow_mut(); + state.blend_space = blend_space; + } + + pub fn handle_use_native_gamut_change(&self, use_native_gamut: bool) { + let state = &mut *self.state.borrow_mut(); + state.use_native_gamut = use_native_gamut; + } + + pub fn handle_cursor_hz_change(&self, cursor_hz: Option) { + let state = &mut *self.state.borrow_mut(); + state.vrr_cursor_hz = cursor_hz; + } } diff --git a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs index 6ac99aa9..099c04ad 100644 --- a/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs +++ b/src/ifs/head_management/jay_head_ext/jay_head_ext_compositor_space_scaler_v1.rs @@ -1,5 +1,6 @@ use { crate::{ + compositor::{MAX_SCALE, MIN_SCALE}, ifs::head_management::{HeadOp, HeadState}, scale::Scale, wire::{ @@ -17,9 +18,6 @@ impl_compositor_space_scaler_v1! { after_announce = after_announce, } -const MIN_SCALE: Scale = Scale::from_wl(60); -const MAX_SCALE: Scale = Scale::from_int(16); - impl HeadName { fn after_announce(&self, _shared: &HeadState) { self.send_range(MIN_SCALE, MAX_SCALE); diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 218d7c29..be54b9c8 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -1,7 +1,7 @@ use { crate::{ - cli::CliLogLevel, client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError, ClientId}, + compositor::LogLevel, globals::{Global, GlobalName}, ifs::{ jay_acceptor_request::JayAcceptorRequest, @@ -35,7 +35,7 @@ use { }, }, bstr::ByteSlice, - log::Level, + linearize::LinearizeExt, std::{cell::Cell, ops::Deref, rc::Rc, str::FromStr}, thiserror::Error, }; @@ -179,28 +179,15 @@ impl JayCompositorRequestHandler for JayCompositor { } fn quit(&self, _req: Quit, _slf: &Rc) -> Result<(), Self::Error> { - log::info!("Quitting"); - self.client.state.ring.stop(); + self.client.state.quit(); Ok(()) } fn set_log_level(&self, req: SetLogLevel, _slf: &Rc) -> Result<(), Self::Error> { - const ERROR: u32 = CliLogLevel::Error as u32; - const WARN: u32 = CliLogLevel::Warn as u32; - const INFO: u32 = CliLogLevel::Info as u32; - const DEBUG: u32 = CliLogLevel::Debug as u32; - const TRACE: u32 = CliLogLevel::Trace as u32; - let level = match req.level { - ERROR => Level::Error, - WARN => Level::Warn, - INFO => Level::Info, - DEBUG => Level::Debug, - TRACE => Level::Trace, - _ => return Err(JayCompositorError::UnknownLogLevel(req.level)), + let Some(level) = LogLevel::from_linear(req.level as usize) else { + return Err(JayCompositorError::UnknownLogLevel(req.level)); }; - if let Some(logger) = &self.client.state.logger { - logger.set_level(level); - } + self.client.state.set_log_level(level); Ok(()) } diff --git a/src/ifs/jay_idle.rs b/src/ifs/jay_idle.rs index 07e04b0c..a91a8b20 100644 --- a/src/ifs/jay_idle.rs +++ b/src/ifs/jay_idle.rs @@ -67,13 +67,15 @@ impl JayIdleRequestHandler for JayIdle { fn set_interval(&self, req: SetInterval, _slf: &Rc) -> Result<(), Self::Error> { let interval = Duration::from_secs(req.interval); - self.client.state.idle.set_timeout(interval); + let state = &self.client.state; + state.idle.set_timeout(interval); Ok(()) } fn set_grace_period(&self, req: SetGracePeriod, _slf: &Rc) -> Result<(), Self::Error> { let period = Duration::from_secs(req.period); - self.client.state.idle.set_grace_period(period); + let state = &self.client.state; + state.idle.set_grace_period(period); Ok(()) } } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index cfac6e6c..9fdbac8a 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -1,6 +1,8 @@ use { crate::{ - backend::{self, InputDeviceAccelProfile, InputDeviceClickMethod, InputDeviceId}, + backend::{ + InputDeviceAccelProfile, InputDeviceCapability, InputDeviceClickMethod, InputDeviceId, + }, client::{Client, ClientError}, clientmem::{ClientMem, ClientMemError}, ifs::wl_seat::WlSeatGlobal, @@ -16,7 +18,8 @@ use { utils::errorfmt::ErrorFmt, wire::{JayInputId, jay_input::*}, }, - kbvm::xkb::rmlvo::Group, + arrayvec::ArrayVec, + linearize::{Linearize, LinearizeExt}, std::rc::Rc, thiserror::Error, uapi::OwnedFd, @@ -85,11 +88,8 @@ impl JayInput { } fn send_input_device(&self, data: &InputDeviceData) { - use backend::InputDeviceCapability::*; - let mut caps = vec![]; - for cap in [ - Keyboard, Pointer, Touch, TabletTool, TabletPad, Gesture, Switch, - ] { + let mut caps = ArrayVec::<_, { InputDeviceCapability::LENGTH }>::new(); + for cap in InputDeviceCapability::variants() { if data.data.device.has_capability(cap) { caps.push(cap.to_libinput().raw()); } @@ -228,26 +228,11 @@ impl JayInput { F: FnOnce(&Rc) -> Result<(), JayInputError>, { self.or_error(|| { - let mut groups = None::>; - if layout.is_some() || variant.is_some() { - groups = Some( - Group::from_layouts_and_variants( - layout.unwrap_or_default(), - variant.unwrap_or_default(), - ) - .collect(), - ); - } - let mut options_vec = None::>; - if let Some(options) = options { - options_vec = Some(options.split(",").collect()); - } - let keymap = self.client.state.kb_ctx.keymap_from_names( - rules, - model, - groups.as_deref(), - options_vec.as_deref(), - )?; + let keymap = self + .client + .state + .kb_ctx + .keymap_from_rmlvo(rules, model, layout, variant, options)?; f(&keymap)?; Ok(()) }) @@ -324,7 +309,7 @@ impl JayInputRequestHandler for JayInput { LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, _ => return Err(JayInputError::UnknownAccelerationProfile(req.profile)), }; - dev.device.set_accel_profile(profile); + dev.set_accel_profile(profile); Ok(()) }) } @@ -332,7 +317,7 @@ impl JayInputRequestHandler for JayInput { fn set_accel_speed(&self, req: SetAccelSpeed, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device.set_accel_speed(req.speed); + dev.set_accel_speed(req.speed); Ok(()) }) } @@ -340,7 +325,7 @@ impl JayInputRequestHandler for JayInput { fn set_tap_enabled(&self, req: SetTapEnabled, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device.set_tap_enabled(req.enabled != 0); + dev.set_tap_enabled(req.enabled != 0); Ok(()) }) } @@ -352,7 +337,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device.set_drag_enabled(req.enabled != 0); + dev.set_drag_enabled(req.enabled != 0); Ok(()) }) } @@ -364,7 +349,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device.set_drag_lock_enabled(req.enabled != 0); + dev.set_drag_lock_enabled(req.enabled != 0); Ok(()) }) } @@ -372,7 +357,7 @@ impl JayInputRequestHandler for JayInput { fn set_left_handed(&self, req: SetLeftHanded, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device.set_left_handed(req.enabled != 0); + dev.set_left_handed(req.enabled != 0); Ok(()) }) } @@ -384,7 +369,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device.set_natural_scrolling_enabled(req.enabled != 0); + dev.set_natural_scrolling_enabled(req.enabled != 0); Ok(()) }) } @@ -396,7 +381,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.px_per_scroll_wheel.set(req.px); + dev.set_px_per_scroll_wheel(req.px); Ok(()) }) } @@ -408,8 +393,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device - .set_transform_matrix([[req.m11, req.m12], [req.m21, req.m22]]); + dev.set_transform_matrix([[req.m11, req.m12], [req.m21, req.m22]]); Ok(()) }) } @@ -523,8 +507,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device - .set_calibration_matrix([[req.m00, req.m01, req.m02], [req.m10, req.m11, req.m12]]); + dev.set_calibration_matrix([[req.m00, req.m01, req.m02], [req.m10, req.m11, req.m12]]); Ok(()) }) } @@ -538,7 +521,7 @@ impl JayInputRequestHandler for JayInput { LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, _ => return Err(JayInputError::UnknownClickMethod(req.method)), }; - dev.device.set_click_method(method); + dev.set_click_method(method); Ok(()) }) } @@ -550,8 +533,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.device - .set_middle_button_emulation_enabled(req.enabled != 0); + dev.set_middle_button_emulation_enabled(req.enabled != 0); Ok(()) }) } diff --git a/src/ifs/jay_randr.rs b/src/ifs/jay_randr.rs index 2a462f46..310b1716 100644 --- a/src/ifs/jay_randr.rs +++ b/src/ifs/jay_randr.rs @@ -350,7 +350,7 @@ impl JayRandrRequestHandler for JayRandr { let Some(dev) = self.get_device(req.dev) else { return Ok(()); }; - dev.dev.set_direct_scanout_enabled(req.enabled != 0); + dev.set_direct_scanout_enabled(req.enabled != 0); Ok(()) } @@ -493,7 +493,7 @@ impl JayRandrRequestHandler for JayRandr { let Some(dev) = self.get_device(req.dev) else { return Ok(()); }; - dev.dev.set_flip_margin(req.margin_ns); + dev.set_flip_margin(req.margin_ns); Ok(()) } diff --git a/src/ifs/jay_xwayland.rs b/src/ifs/jay_xwayland.rs index 05399fcf..1483a5da 100644 --- a/src/ifs/jay_xwayland.rs +++ b/src/ifs/jay_xwayland.rs @@ -57,10 +57,7 @@ impl JayXwaylandRequestHandler for JayXwayland { }; self.client .state - .xwayland - .use_wire_scale - .set(use_wire_scale); - self.client.state.update_xwayland_wire_scale(); + .set_xwayland_use_wire_scale(use_wire_scale); Ok(()) } } diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 9d165ba9..f90f5645 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -94,6 +94,12 @@ pub struct WlOutputGlobal { CopyHashMap<(ClientId, WpColorManagementOutputV1Id), Rc>, } +impl PartialEq for WlOutputGlobal { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + #[derive(Default)] pub struct OutputGlobalOpt { pub global: CloneCell>>, diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index e3f930e0..6765f02b 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -24,7 +24,9 @@ pub mod zwp_virtual_keyboard_v1; use { crate::{ async_engine::SpawnedFuture, - backend::{ButtonState, Leds}, + backend::{ + ButtonState, InputDeviceAccelProfile, InputDeviceClickMethod, Leds, TransformMatrix, + }, client::{Client, ClientError, ClientId}, cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, ei::ei_ifs::ei_seat::EiSeat, @@ -253,6 +255,12 @@ pub struct WlSeatGlobal { simple_im_enabled: Cell, } +impl PartialEq for WlSeatGlobal { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + #[derive(Copy, Clone)] enum MarkMode { Mark, @@ -954,10 +962,20 @@ impl WlSeatGlobal { self.focus_history_visible_only.set(visible); } + #[expect(dead_code)] + pub fn focus_history_visible(&self) -> bool { + self.focus_history_visible_only.get() + } + pub fn focus_history_set_same_workspace(&self, same_workspace: bool) { self.focus_history_same_workspace.set(same_workspace); } + #[expect(dead_code)] + pub fn focus_history_same_workspace(&self) -> bool { + self.focus_history_same_workspace.get() + } + fn focus_layer_rel( self: &Rc, next_layer: impl Fn(NodeLayer) -> NodeLayer, @@ -1460,10 +1478,20 @@ impl WlSeatGlobal { self.focus_follows_mouse.set(focus_follows_mouse); } + #[expect(dead_code)] + pub fn focus_follows_mouse(&self) -> bool { + self.focus_follows_mouse.get() + } + pub fn set_fallback_output_mode(&self, fallback_output_mode: FallbackOutputMode) { self.fallback_output_mode.set(fallback_output_mode); } + #[expect(dead_code)] + pub fn fallback_output_mode(&self) -> FallbackOutputMode { + self.fallback_output_mode.get() + } + pub fn set_window_management_enabled(self: &Rc, enabled: bool) { self.pointer_owner .set_window_management_enabled(self, enabled); @@ -1580,6 +1608,11 @@ impl WlSeatGlobal { pub fn set_pointer_revert_key(&self, key: KeySym) { self.revert_key.set(key); } + + #[expect(dead_code)] + pub fn pointer_revert_key(&self) -> KeySym { + self.revert_key.get() + } } impl CursorUserOwner for WlSeatGlobal { @@ -1860,6 +1893,54 @@ impl DeviceHandlerData { } state.root.extents.get() } + + pub fn set_accel_profile(&self, v: InputDeviceAccelProfile) { + self.device.set_accel_profile(v); + } + + pub fn set_accel_speed(&self, v: f64) { + self.device.set_accel_speed(v); + } + + pub fn set_tap_enabled(&self, v: bool) { + self.device.set_tap_enabled(v); + } + + pub fn set_drag_enabled(&self, v: bool) { + self.device.set_drag_enabled(v); + } + + pub fn set_drag_lock_enabled(&self, v: bool) { + self.device.set_drag_lock_enabled(v); + } + + pub fn set_left_handed(&self, v: bool) { + self.device.set_left_handed(v); + } + + pub fn set_natural_scrolling_enabled(&self, v: bool) { + self.device.set_natural_scrolling_enabled(v); + } + + pub fn set_px_per_scroll_wheel(&self, v: f64) { + self.px_per_scroll_wheel.set(v); + } + + pub fn set_transform_matrix(&self, v: TransformMatrix) { + self.device.set_transform_matrix(v); + } + + pub fn set_calibration_matrix(&self, v: [[f32; 3]; 2]) { + self.device.set_calibration_matrix(v); + } + + pub fn set_click_method(&self, v: InputDeviceClickMethod) { + self.device.set_click_method(v); + } + + pub fn set_middle_button_emulation_enabled(&self, v: bool) { + self.device.set_middle_button_emulation_enabled(v); + } } impl LedsListener for DeviceHandlerData { diff --git a/src/ifs/wl_surface/zwp_idle_inhibitor_v1.rs b/src/ifs/wl_surface/zwp_idle_inhibitor_v1.rs index 3c945c5c..34d26581 100644 --- a/src/ifs/wl_surface/zwp_idle_inhibitor_v1.rs +++ b/src/ifs/wl_surface/zwp_idle_inhibitor_v1.rs @@ -43,11 +43,13 @@ impl ZwpIdleInhibitorV1 { } pub fn activate(self: &Rc) { - self.client.state.idle.add_inhibitor(self); + let state = &self.client.state; + state.idle.add_inhibitor(self); } pub fn deactivate(&self) { - self.client.state.idle.remove_inhibitor(self); + let state = &self.client.state; + state.idle.remove_inhibitor(self); } } diff --git a/src/ifs/wp_content_type_v1.rs b/src/ifs/wp_content_type_v1.rs index 16f3727d..2118fbb2 100644 --- a/src/ifs/wp_content_type_v1.rs +++ b/src/ifs/wp_content_type_v1.rs @@ -4,6 +4,7 @@ use { ifs::wl_surface::WlSurface, leaks::Tracker, object::{Object, Version}, + utils::static_text::StaticText, wire::{WpContentTypeV1Id, wp_content_type_v1::*}, }, jay_config::window::{ @@ -26,6 +27,16 @@ pub enum ContentType { Game, } +impl StaticText for ContentType { + fn text(&self) -> &'static str { + match self { + Self::Photo => "Photo", + Self::Video => "Video", + Self::Game => "Game", + } + } +} + pub trait ContentTypeExt { fn to_config(&self) -> ConfigContentType; } diff --git a/src/ifs/wp_cursor_shape_device_v1.rs b/src/ifs/wp_cursor_shape_device_v1.rs index f1d4e307..b4fdec17 100644 --- a/src/ifs/wp_cursor_shape_device_v1.rs +++ b/src/ifs/wp_cursor_shape_device_v1.rs @@ -72,7 +72,33 @@ impl WpCursorShapeDeviceV1RequestHandler for WpCursorShapeDeviceV1 { } fn set_shape(&self, req: SetShape, _slf: &Rc) -> Result<(), Self::Error> { - let cursor = match req.shape { + let cursor = KnownCursor::from_shape(req.shape, self.version) + .ok_or(WpCursorShapeDeviceV1Error::UnknownShape(req.shape))?; + let tablet_tool; + let (node_client_id, user) = match &self.cursor_user { + CursorShapeCursorUser::Seat(s) => match s.pointer_node() { + Some(n) => (n.node_client_id(), s.pointer_cursor()), + _ => return Ok(()), + }, + CursorShapeCursorUser::TabletTool(t) => match t.get() { + Some(t) => { + tablet_tool = t; + (tablet_tool.node().node_client_id(), tablet_tool.cursor()) + } + _ => return Ok(()), + }, + }; + if node_client_id != Some(self.client.id) { + return Ok(()); + } + user.set_known(cursor); + Ok(()) + } +} + +impl KnownCursor { + pub fn from_shape(shape: u32, version: Version) -> Option { + let cursor = match shape { DEFAULT => KnownCursor::Default, CONTEXT_MENU => KnownCursor::ContextMenu, HELP => KnownCursor::Help, @@ -107,29 +133,52 @@ impl WpCursorShapeDeviceV1RequestHandler for WpCursorShapeDeviceV1 { ALL_SCROLL => KnownCursor::AllScroll, ZOOM_IN => KnownCursor::ZoomIn, ZOOM_OUT => KnownCursor::ZoomOut, - DND_ASK if self.version >= V2 => KnownCursor::DndAsk, - ALL_RESIZE if self.version >= V2 => KnownCursor::AllResize, - _ => return Err(WpCursorShapeDeviceV1Error::UnknownShape(req.shape)), + DND_ASK if version >= V2 => KnownCursor::DndAsk, + ALL_RESIZE if version >= V2 => KnownCursor::AllResize, + _ => return None, }; - let tablet_tool; - let (node_client_id, user) = match &self.cursor_user { - CursorShapeCursorUser::Seat(s) => match s.pointer_node() { - Some(n) => (n.node_client_id(), s.pointer_cursor()), - _ => return Ok(()), - }, - CursorShapeCursorUser::TabletTool(t) => match t.get() { - Some(t) => { - tablet_tool = t; - (tablet_tool.node().node_client_id(), tablet_tool.cursor()) - } - _ => return Ok(()), - }, - }; - if node_client_id != Some(self.client.id) { - return Ok(()); + Some(cursor) + } + + pub fn to_shape(self) -> u32 { + match self { + KnownCursor::Default => DEFAULT, + KnownCursor::ContextMenu => CONTEXT_MENU, + KnownCursor::Help => HELP, + KnownCursor::Pointer => POINTER, + KnownCursor::Progress => PROGRESS, + KnownCursor::Wait => WAIT, + KnownCursor::Cell => CELL, + KnownCursor::Crosshair => CROSSHAIR, + KnownCursor::Text => TEXT, + KnownCursor::VerticalText => VERTICAL_TEXT, + KnownCursor::Alias => ALIAS, + KnownCursor::Copy => COPY, + KnownCursor::Move => MOVE, + KnownCursor::NoDrop => NO_DROP, + KnownCursor::NotAllowed => NOT_ALLOWED, + KnownCursor::Grab => GRAB, + KnownCursor::Grabbing => GRABBING, + KnownCursor::EResize => E_RESIZE, + KnownCursor::NResize => N_RESIZE, + KnownCursor::NeResize => NE_RESIZE, + KnownCursor::NwResize => NW_RESIZE, + KnownCursor::SResize => S_RESIZE, + KnownCursor::SeResize => SE_RESIZE, + KnownCursor::SwResize => SW_RESIZE, + KnownCursor::WResize => W_RESIZE, + KnownCursor::EwResize => EW_RESIZE, + KnownCursor::NsResize => NS_RESIZE, + KnownCursor::NeswResize => NESW_RESIZE, + KnownCursor::NwseResize => NWSE_RESIZE, + KnownCursor::ColResize => COL_RESIZE, + KnownCursor::RowResize => ROW_RESIZE, + KnownCursor::AllScroll => ALL_SCROLL, + KnownCursor::ZoomIn => ZOOM_IN, + KnownCursor::ZoomOut => ZOOM_OUT, + KnownCursor::DndAsk => DND_ASK, + KnownCursor::AllResize => ALL_RESIZE, } - user.set_known(cursor); - Ok(()) } } diff --git a/src/kbvm.rs b/src/kbvm.rs index 1b751711..b7026ae6 100644 --- a/src/kbvm.rs +++ b/src/kbvm.rs @@ -13,6 +13,7 @@ use { self, Keymap, diagnostic::{Diagnostic, WriteToLog}, keymap::{Indicator, IndicatorMatcher}, + rmlvo::Group, }, }, std::{ @@ -51,6 +52,8 @@ pub struct KbvmMap { pub id: KbvmMapId, pub state_machine: StateMachine, pub lookup_table: LookupTable, + #[expect(dead_code)] + pub map_text: String, pub map: KeymapFd, pub xwayland_map: KeymapFd, pub has_indicators: bool, @@ -96,11 +99,36 @@ impl KbvmContext { self.create_keymap(id, map) } + pub fn keymap_from_rmlvo( + &self, + rules: Option<&str>, + model: Option<&str>, + layout: Option<&str>, + variant: Option<&str>, + options: Option<&str>, + ) -> Result, KbvmError> { + let mut groups = None::>; + if layout.is_some() || variant.is_some() { + groups = Some( + Group::from_layouts_and_variants( + layout.unwrap_or_default(), + variant.unwrap_or_default(), + ) + .collect(), + ); + } + let mut options_vec = None::>; + if let Some(options) = options { + options_vec = Some(options.split(",").collect()); + } + self.keymap_from_names(rules, model, groups.as_deref(), options_vec.as_deref()) + } + pub fn keymap_from_names( &self, rules: Option<&str>, model: Option<&str>, - groups: Option<&[xkb::rmlvo::Group<'_>]>, + groups: Option<&[Group<'_>]>, options: Option<&[&str]>, ) -> Result, KbvmError> { let map = self @@ -129,11 +157,14 @@ impl KbvmContext { has_indicators = true; } let builder = map.to_builder(); + let (_, xwayland_map) = create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?; + let (map_text, map) = create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?; Ok(Rc::new(KbvmMap { id, state_machine: builder.build_state_machine(), - map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?, - xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?, + map, + map_text, + xwayland_map, lookup_table: builder.build_lookup_table(), has_indicators, num_lock, @@ -145,7 +176,7 @@ impl KbvmContext { } } -fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result { +fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result<(String, KeymapFd), OsError> { let mut format = map.format(); if xwayland { format = format.lookup_only(true).rename_long_keys(true); @@ -159,10 +190,11 @@ fn create_keymap_memfd(map: &Keymap, xwayland: bool) -> Result, + filter: AtomicU32, path: Mutex>, _file: Mutex, file_fd: AtomicI32, } impl Logger { - pub fn install_stderr(level: Level) -> Arc { + pub fn install_stderr(level: LogLevel) -> Arc { let file = match uapi::fcntl_dupfd_cloexec(2, 0) { Ok(fd) => fd, Err(e) => { @@ -42,18 +46,20 @@ impl Logger { Self::install(level, b"STDERR", file) } - pub fn install_compositor(level: Level) -> Arc { + pub fn install_compositor(level: LogLevel) -> Arc { let (path, file) = open_log_file("jay"); Self::install(level, path.as_bytes(), file) } - pub fn install_pipe(file: OwnedFd, level: Level) -> Arc { + pub fn install_pipe(file: OwnedFd, level: LogLevel) -> Arc { Self::install(level, b"PIPE", file) } - fn install(level: Level, path: &[u8], file: OwnedFd) -> Arc { + fn install(level: LogLevel, path: &[u8], file: OwnedFd) -> Arc { + let filter: LevelFilter = level.into(); let slf = Arc::new(Self { - level: AtomicU32::new(level as _), + level: AtomicEnum::new(level), + filter: AtomicU32::new(filter as _), path: Mutex::new(Arc::new(path.to_vec().into())), file_fd: AtomicI32::new(file.raw()), _file: Mutex::new(file), @@ -62,14 +68,21 @@ impl Logger { logger: slf.clone(), })) .unwrap(); - log::set_max_level(level.to_level_filter()); + log::set_max_level(filter); set_panic_hook(); slf } - pub fn set_level(&self, level: Level) { - self.level.store(level as _, Relaxed); - log::set_max_level(level.to_level_filter()); + pub fn set_level(&self, level: LogLevel) { + let filter: LevelFilter = level.into(); + self.level.store(level, Relaxed); + self.filter.store(filter as _, Relaxed); + log::set_max_level(filter); + } + + #[expect(dead_code)] + pub fn level(&self) -> LogLevel { + self.level.load(Relaxed) } pub fn path(&self) -> Arc { @@ -166,11 +179,11 @@ struct LogWrapper { impl Log for LogWrapper { fn enabled(&self, metadata: &Metadata) -> bool { - metadata.level() as u32 <= self.logger.level.load(Relaxed) + metadata.level() as u32 <= self.logger.filter.load(Relaxed) } fn log(&self, record: &Record) { - if record.level() as u32 > self.logger.level.load(Relaxed) { + if record.level() as u32 > self.logger.filter.load(Relaxed) { return; } let mut buffer = BUFFER.get(); diff --git a/src/output_schedule.rs b/src/output_schedule.rs index 8164922e..c9420c21 100644 --- a/src/output_schedule.rs +++ b/src/output_schedule.rs @@ -127,6 +127,7 @@ impl OutputSchedule { Some(v) => v, }; self.persistent.vrr_cursor_hz.set(hz); + self.connector.head_managers.handle_cursor_hz_change(hz); self.cursor_delta_nsec.set(delta); self.trigger(); } diff --git a/src/portal.rs b/src/portal.rs index b2b9f62e..0dedaf3d 100644 --- a/src/portal.rs +++ b/src/portal.rs @@ -11,6 +11,7 @@ use { async_engine::AsyncEngine, cli::GlobalArgs, cmm::cmm_manager::ColorManager, + compositor::LogLevel, dbus::{ BUS_DEST, BUS_PATH, DBUS_NAME_FLAG_DO_NOT_QUEUE, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER, Dbus, DbusSocket, @@ -44,7 +45,6 @@ use { wheel::Wheel, wire_dbus::org, }, - log::Level, std::{ ffi::OsStr, io::{BufReader, BufWriter}, @@ -64,7 +64,7 @@ const PORTAL_CANCELLED: u32 = 1; const PORTAL_ENDED: u32 = 2; pub fn run_freestanding(global: GlobalArgs) { - let logger = Logger::install_stderr(global.log_level.into()); + let logger = Logger::install_stderr(global.log_level); run(logger, true); } @@ -131,7 +131,7 @@ impl PortalStartup { } } -pub fn run_from_compositor(level: Level) -> Result { +pub fn run_from_compositor(level: LogLevel) -> Result { let Pipe { read, write } = match pipe() { Ok(p) => p, Err(e) => return Err(PortalError::CreatePipe(e)), diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index a0223a40..d85bc4ae 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -2,6 +2,7 @@ use { crate::{ gfx_api::{GfxApi, GfxFormat, cross_intersect_formats}, gfx_apis::create_gfx_context, + globals::GlobalName, ifs::wl_seat::POINTER, object::Version, portal::{ @@ -60,7 +61,7 @@ struct PortalDisplayPrelude { con: Rc, state: Rc, registry: Rc, - globals: RefCell>>, + globals: RefCell>>, } shared_ids!(PortalDisplayId); @@ -80,8 +81,8 @@ pub struct PortalDisplay { pub vp: Rc, pub render_ctx: CloneCell>>, - pub outputs: CopyHashMap>, - pub seats: CopyHashMap>, + pub outputs: CopyHashMap>, + pub seats: CopyHashMap>, pub workspaces: CopyHashMap>, pub windows: CopyHashMap>, @@ -89,14 +90,14 @@ pub struct PortalDisplay { } pub struct PortalOutput { - pub global_id: u32, + pub global_id: GlobalName, pub dpy: Rc, pub wl: Rc, pub jay: Rc, } pub struct PortalSeat { - pub global_id: u32, + pub global_id: GlobalName, pub dpy: Rc, pub wl: Rc, pub jay_pointer: Rc, @@ -128,32 +129,32 @@ impl UsrWlSeatOwner for PortalSeat { } impl UsrWlPointerOwner for PortalSeat { - fn enter(&self, ev: &wl_pointer::Enter) { + fn enter(self: Rc, ev: &wl_pointer::Enter) { if let Some(window) = self.dpy.windows.get(&ev.surface) { self.pointer_focus.set(Some(window.clone())); - window.motion(self, ev.surface_x, ev.surface_y, true); + window.motion(&self, ev.surface_x, ev.surface_y, true); } } - fn leave(&self, _ev: &wl_pointer::Leave) { + fn leave(self: Rc, _ev: &wl_pointer::Leave) { self.pointer_focus.take(); } - fn motion(&self, ev: &wl_pointer::Motion) { + fn motion(self: Rc, ev: &wl_pointer::Motion) { if let Some(window) = self.pointer_focus.get() { - window.motion(self, ev.surface_x, ev.surface_y, false); + window.motion(&self, ev.surface_x, ev.surface_y, false); } } - fn button(&self, ev: &wl_pointer::Button) { + fn button(self: Rc, ev: &wl_pointer::Button) { if let Some(window) = self.pointer_focus.get() { - window.button(self, ev.button, ev.state); + window.button(&self, ev.button, ev.state); } } } impl UsrWlRegistryOwner for PortalDisplayPrelude { - fn global(self: Rc, name: u32, interface: &str, version: u32) { + fn global(self: Rc, name: GlobalName, interface: &str, version: u32) { self.globals .borrow_mut() .entry(interface.to_string()) @@ -237,7 +238,7 @@ impl UsrConOwner for PortalDisplay { } impl UsrWlRegistryOwner for PortalDisplay { - fn global(self: Rc, name: u32, interface: &str, version: u32) { + fn global(self: Rc, name: GlobalName, interface: &str, version: u32) { if interface == WlOutput.name() { add_output(&self, name, version); } else if interface == WlSeat.name() { @@ -250,7 +251,7 @@ impl UsrWlRegistryOwner for PortalDisplay { version: Version(version.min(5)), }); self.con.add_object(ls.clone()); - self.registry.request_bind(name, ls.version.0, ls.deref()); + self.registry.bind(name, ls.deref()); self.dmabuf.set(Some(ls)); } } @@ -353,7 +354,7 @@ fn finish_display_connect(dpy: Rc) { version: Version(version.min(12)), }); dpy.con.add_object(jc.clone()); - dpy.registry.request_bind(name, jc.version.0, jc.deref()); + dpy.registry.bind(name, jc.deref()); jc_opt = Some(jc); } else if interface == WpFractionalScaleManagerV1.name() { let ls = Rc::new(UsrWpFractionalScaleManager { @@ -362,7 +363,7 @@ fn finish_display_connect(dpy: Rc) { version: Version(version.min(1)), }); dpy.con.add_object(ls.clone()); - dpy.registry.request_bind(name, ls.version.0, ls.deref()); + dpy.registry.bind(name, ls.deref()); fsm_opt = Some(ls); } else if interface == ZwlrLayerShellV1.name() { let ls = Rc::new(UsrWlrLayerShell { @@ -371,7 +372,7 @@ fn finish_display_connect(dpy: Rc) { version: Version(version.min(5)), }); dpy.con.add_object(ls.clone()); - dpy.registry.request_bind(name, ls.version.0, ls.deref()); + dpy.registry.bind(name, ls.deref()); ls_opt = Some(ls); } else if interface == WpViewporter.name() { let ls = Rc::new(UsrWpViewporter { @@ -380,7 +381,7 @@ fn finish_display_connect(dpy: Rc) { version: Version(version.min(1)), }); dpy.con.add_object(ls.clone()); - dpy.registry.request_bind(name, ls.version.0, ls.deref()); + dpy.registry.bind(name, ls.deref()); vp_opt = Some(ls); } else if interface == WlCompositor.name() { let ls = Rc::new(UsrWlCompositor { @@ -389,7 +390,7 @@ fn finish_display_connect(dpy: Rc) { version: Version(version.min(6)), }); dpy.con.add_object(ls.clone()); - dpy.registry.request_bind(name, ls.version.0, ls.deref()); + dpy.registry.bind(name, ls.deref()); comp_opt = Some(ls); } else if interface == ZwpLinuxDmabufV1.name() { let ls = Rc::new(UsrLinuxDmabuf { @@ -399,7 +400,7 @@ fn finish_display_connect(dpy: Rc) { version: Version(version.min(5)), }); dpy.con.add_object(ls.clone()); - dpy.registry.request_bind(name, ls.version.0, ls.deref()); + dpy.registry.bind(name, ls.deref()); dmabuf_opt = Some(ls); } else if interface == WlOutput.name() { outputs.push((name, version)); @@ -465,7 +466,7 @@ fn finish_display_connect(dpy: Rc) { log::info!("Display {} initialized", dpy.id); } -fn add_seat(dpy: &Rc, name: u32, version: u32) { +fn add_seat(dpy: &Rc, name: GlobalName, version: u32) { let wl = Rc::new(UsrWlSeat { id: dpy.con.id(), con: dpy.con.clone(), @@ -473,7 +474,7 @@ fn add_seat(dpy: &Rc, name: u32, version: u32) { version: Version(version.min(9)), }); dpy.con.add_object(wl.clone()); - dpy.registry.request_bind(name, wl.version.0, wl.deref()); + dpy.registry.bind(name, wl.deref()); let jay_pointer = dpy.jc.get_pointer(&wl); let js = Rc::new(PortalSeat { global_id: name, @@ -489,7 +490,7 @@ fn add_seat(dpy: &Rc, name: u32, version: u32) { dpy.seats.set(name, js); } -fn add_output(dpy: &Rc, name: u32, version: u32) { +fn add_output(dpy: &Rc, name: GlobalName, version: u32) { let wl = Rc::new(UsrWlOutput { id: dpy.con.id(), con: dpy.con.clone(), @@ -498,7 +499,7 @@ fn add_output(dpy: &Rc, name: u32, version: u32) { name: Default::default(), }); dpy.con.add_object(wl.clone()); - dpy.registry.request_bind(name, wl.version.0, wl.deref()); + dpy.registry.bind(name, wl.deref()); let jo = dpy.jc.get_output(&wl); let po = Rc::new(PortalOutput { global_id: name, diff --git a/src/portal/ptl_remote_desktop/remote_desktop_gui.rs b/src/portal/ptl_remote_desktop/remote_desktop_gui.rs index 0da1db0e..f9b52189 100644 --- a/src/portal/ptl_remote_desktop/remote_desktop_gui.rs +++ b/src/portal/ptl_remote_desktop/remote_desktop_gui.rs @@ -1,5 +1,6 @@ use { crate::{ + globals::GlobalName, ifs::wl_seat::{BTN_LEFT, wl_pointer::PRESSED}, portal::{ ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, @@ -21,7 +22,7 @@ const V_MARGIN: f32 = 20.0; pub struct SelectionGui { remote_desktop_session: Rc, dpy: Rc, - surfaces: CopyHashMap>, + surfaces: CopyHashMap>, } pub struct SelectionGuiSurface { diff --git a/src/portal/ptl_screencast/screencast_gui.rs b/src/portal/ptl_screencast/screencast_gui.rs index 20ae0dff..d792f233 100644 --- a/src/portal/ptl_screencast/screencast_gui.rs +++ b/src/portal/ptl_screencast/screencast_gui.rs @@ -1,5 +1,6 @@ use { crate::{ + globals::GlobalName, ifs::wl_seat::{BTN_LEFT, wl_pointer::PRESSED}, portal::{ ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, @@ -29,7 +30,7 @@ const V_MARGIN: f32 = 20.0; pub struct SelectionGui { screencast_session: Rc, dpy: Rc, - surfaces: CopyHashMap>, + surfaces: CopyHashMap>, } pub struct SelectionGuiSurface { @@ -254,7 +255,7 @@ impl UsrJaySelectToplevelOwner for SelectingWindowScreencast { } impl UsrJaySelectWorkspaceOwner for SelectingWorkspaceScreencast { - fn done(&self, output: u32, ws: Option>) { + fn done(&self, output: GlobalName, ws: Option>) { let Some(ws) = ws else { log::info!("User has aborted the selection"); self.core.session.kill(); diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 7f01fc4e..4962bca8 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -10,6 +10,7 @@ use { AcquireSync, AlphaMode, GfxContext, GfxFramebuffer, GfxTexture, ReleaseSync, needs_render_usage, }, + globals::GlobalName, ifs::zwlr_layer_shell_v1::OVERLAY, portal::{ ptl_display::{PortalDisplay, PortalOutput, PortalSeat}, @@ -29,6 +30,7 @@ use { wl_usr::usr_ifs::{ usr_linux_buffer_params::{UsrLinuxBufferParams, UsrLinuxBufferParamsOwner}, usr_wl_buffer::{UsrWlBuffer, UsrWlBufferOwner}, + usr_wl_callback::UsrWlCallbackOwner, usr_wl_surface::UsrWlSurface, usr_wlr_layer_surface::{UsrWlrLayerSurface, UsrWlrLayerSurfaceOwner}, usr_wp_fractional_scale::{UsrWpFractionalScale, UsrWpFractionalScaleOwner}, @@ -116,7 +118,7 @@ pub struct Button { pub data: GuiElementData, pub tex_off_x: Cell, pub tex_off_y: Cell, - pub hover: RefCell>, + pub hover: RefCell>, pub padding: Cell, pub border: Cell, pub border_color: Cell, @@ -504,7 +506,7 @@ pub struct WindowData { pub width: Cell, pub height: Cell, pub owner: CloneCell>>, - pub seats: CopyHashMap>, + pub seats: CopyHashMap>, } #[derive(Default)] @@ -666,15 +668,7 @@ impl WindowData { self.frame_missed.set(false); - self.surface.frame({ - let slf = self.clone(); - move || { - slf.have_frame.set(true); - if slf.frame_missed.get() { - slf.schedule_render(); - } - } - }); + self.surface.frame().owner.set(Some(self.clone())); self.have_frame.set(false); buf.free.set(false); @@ -901,6 +895,15 @@ impl UsrWpFractionalScaleOwner for WindowData { } } +impl UsrWlCallbackOwner for WindowData { + fn done(self: Rc) { + self.have_frame.set(true); + if self.frame_missed.get() { + self.schedule_render(); + } + } +} + impl UsrWlrLayerSurfaceOwner for OverlayWindow { fn configure(&self, _ev: &Configure) { self.data.schedule_render(); diff --git a/src/scale.rs b/src/scale.rs index 2e93dd96..178bba08 100644 --- a/src/scale.rs +++ b/src/scale.rs @@ -1,8 +1,8 @@ use std::fmt::{Debug, Display, Formatter}; -const BASE: u32 = 120; -const BASE64: i64 = BASE as i64; -const BASEF: f64 = BASE as f64; +pub const SCALE_BASE: u32 = 120; +const BASE64: i64 = SCALE_BASE as i64; +pub const SCALE_BASEF: f64 = SCALE_BASE as f64; #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] @@ -16,23 +16,23 @@ impl Default for Scale { impl Scale { pub const fn from_int(f: u32) -> Self { - Self(f.saturating_mul(BASE)) + Self(f.saturating_mul(SCALE_BASE)) } pub fn from_f64(f: f64) -> Self { - Self((f * BASEF).round() as u32) + Self((f * SCALE_BASEF).round() as u32) } pub fn from_f64_as_float(f: f64) -> Self { - Self(((f * (BASEF / 15.0)).round() as u32).saturating_mul(15)) + Self(((f * (SCALE_BASEF / 15.0)).round() as u32).saturating_mul(15)) } pub fn to_f64(self) -> f64 { - self.0 as f64 / BASEF + self.0 as f64 / SCALE_BASEF } pub fn round_up(self) -> u32 { - self.0.saturating_add(BASE - 1) / BASE + self.0.saturating_add(SCALE_BASE - 1) / SCALE_BASE } pub const fn from_wl(wl: u32) -> Self { @@ -55,7 +55,7 @@ impl Scale { impl PartialEq for Scale { fn eq(&self, other: &u32) -> bool { - self.0 == other * BASE + self.0 == other * SCALE_BASE } } diff --git a/src/state.rs b/src/state.rs index 4eee9bf7..2d624d5b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -13,7 +13,7 @@ use { client::{Client, ClientCaps, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange}, clientmem::ClientMemOffset, cmm::{cmm_description::ColorDescription, cmm_manager::ColorManager}, - compositor::LIBEI_SOCKET, + compositor::{LIBEI_SOCKET, LogLevel}, config::ConfigProxy, copy_device::CopyDeviceRegistry, cpu_worker::CpuWorker, @@ -93,7 +93,7 @@ use { scale::Scale, security_context_acceptor::SecurityContextAcceptors, tagged_acceptor::TaggedAcceptors, - theme::{Color, Theme}, + theme::{BarPosition, Color, Theme, ThemeColor, ThemeSized}, time::Time, tree::{ ContainerNode, ContainerSplit, Direction, DisplayNode, FindTreeUsecase, FloatNode, @@ -110,7 +110,7 @@ use { clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - event_listener::{EventListener, EventSource}, + event_listener::{EventListener, EventSource, LazyEventSources}, fdcloser::FdCloser, hash_map_ext::HashMapExt, linkedlist::LinkedList, @@ -145,10 +145,12 @@ use { time::Duration, }, thiserror::Error, - uapi::OwnedFd, + uapi::{OwnedFd, c}, }; pub struct State { + #[expect(dead_code)] + pub pid: c::pid_t, pub kb_ctx: KbvmContext, pub backend: CloneCell>, pub forker: CloneCell>>, @@ -290,6 +292,7 @@ pub struct State { pub copy_device_registry: Rc, pub supports_presentation_feedback: Cell, pub eventfd_cache: Rc, + pub lazy_event_sources: Rc, } // impl Drop for State { @@ -319,6 +322,8 @@ pub struct XWaylandState { pub use_wire_scale: Cell, pub wire_scale: Cell>, pub windows: CopyHashMap>, + pub client: CloneCell>>, + pub display: CloneCell>>, } pub struct IdleState { @@ -338,31 +343,37 @@ pub struct IdleState { impl IdleState { pub fn set_timeout(&self, timeout: Duration) { self.timeout.set(timeout); - self.timeout_changed.set(true); - self.change.trigger(); + self.timeout_changed(); } pub fn set_grace_period(&self, grace_period: Duration) { self.grace_period.set(grace_period); + self.timeout_changed(); + } + + fn timeout_changed(&self) { self.timeout_changed.set(true); self.change.trigger(); } pub fn add_inhibitor(&self, inhibitor: &Rc) { self.inhibitors.set(inhibitor.inhibit_id, inhibitor.clone()); - self.inhibitors_changed.set(true); - self.change.trigger(); + self.inhibitors_changed(); } pub fn remove_inhibitor(&self, inhibitor: &ZwpIdleInhibitorV1) { self.inhibitors.remove(&inhibitor.inhibit_id); - self.inhibitors_changed.set(true); - self.change.trigger(); + self.inhibitors_changed(); if self.inhibitors.is_empty() { self.resume_inhibited_notifications(); } } + fn inhibitors_changed(&self) { + self.inhibitors_changed.set(true); + self.change.trigger(); + } + fn resume_inhibited_notifications(&self) { for notification in self.inhibited_idle_notifications.lock().drain_values() { notification.resume.trigger(); @@ -517,6 +528,14 @@ impl DrmDevData { ); self.dev.clone().make_render_device(); } + + pub fn set_direct_scanout_enabled(&self, enabled: bool) { + self.dev.set_direct_scanout_enabled(enabled); + } + + pub fn set_flip_margin(&self, margin: u64) { + self.dev.set_flip_margin(margin); + } } struct UpdateTextTexturesVisitor; @@ -984,6 +1003,13 @@ impl State { } } + pub fn set_xwayland_use_wire_scale(&self, use_wire_scale: bool) { + if self.xwayland.use_wire_scale.replace(use_wire_scale) == use_wire_scale { + return; + } + self.update_xwayland_wire_scale(); + } + pub fn next_serial(&self, client: Option<&Client>) -> u64 { let serial = self.serial.fetch_add(1); if let Some(client) = client { @@ -1128,6 +1154,7 @@ impl State { self.cpu_worker.clear(); self.wait_for_syncobj.clear(); self.xdg_surface_configure_events.clear(); + self.lazy_event_sources.clear(); } pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) { @@ -1688,6 +1715,200 @@ impl State { self.explicit_sync_enabled.set(enabled); self.expose_new_singletons(); } + + pub fn set_log_level(&self, level: LogLevel) { + if let Some(logger) = &self.logger { + logger.set_level(level); + } + } + + fn colors_changed(&self) { + struct V; + impl NodeVisitorBase for V { + fn visit_container(&mut self, node: &Rc) { + node.on_colors_changed(); + node.node_visit_children(self); + } + + fn visit_output(&mut self, node: &Rc) { + node.on_colors_changed(); + node.node_visit_children(self); + } + + fn visit_float(&mut self, node: &Rc) { + node.on_colors_changed(); + node.node_visit_children(self); + } + } + self.root.clone().node_visit(&mut V); + self.damage(self.root.extents.get()); + self.icons.clear(); + } + + pub fn reset_colors(&self) { + self.theme.colors.reset(); + self.colors_changed(); + } + + pub fn quit(&self) { + log::info!("Quitting"); + self.ring.stop(); + } + + pub fn reload_config(self: &Rc) { + log::info!("Reloading config"); + let config = match ConfigProxy::from_config_dir(self) { + Ok(c) => c, + Err(e) => { + log::error!("Cannot reload config: {}", ErrorFmt(e)); + return; + } + }; + if let Some(config) = self.config.take() { + config.destroy(); + for seat in self.globals.seats.lock().values() { + seat.clear_shortcuts(); + } + } + config.configure(true); + self.config.set(Some(Rc::new(config))); + } + + pub fn set_ei_socket_enabled(self: &Rc, enabled: bool) { + self.enable_ei_acceptor.set(enabled); + self.update_ei_acceptor(); + } + + pub fn set_workspace_display_order(&self, order: WorkspaceDisplayOrder) { + self.workspace_display_order.set(order); + for output in self.root.outputs.lock().values() { + output.handle_workspace_display_order_update(); + } + } + + fn spaces_changed(&self) { + struct V; + impl NodeVisitorBase for V { + fn visit_container(&mut self, node: &Rc) { + node.on_spaces_changed(); + node.node_visit_children(self); + } + fn visit_output(&mut self, node: &Rc) { + node.on_spaces_changed(); + node.node_visit_children(self); + } + fn visit_float(&mut self, node: &Rc) { + node.on_spaces_changed(); + node.node_visit_children(self); + } + } + self.root.clone().node_visit(&mut V); + self.damage(self.root.extents.get()); + self.icons.update_sizes(self); + } + + pub fn set_show_bar(&self, show: bool) { + self.show_bar.set(show); + self.spaces_changed(); + } + + pub fn set_show_titles(&self, show: bool) { + self.theme.show_titles.set(show); + self.spaces_changed(); + } + + pub fn set_ui_drag_enabled(&self, enabled: bool) { + self.ui_drag_enabled.set(enabled); + } + + pub fn set_ui_drag_threshold(&self, threshold: i32) { + self.ui_drag_threshold_squared + .set(threshold.saturating_mul(threshold)); + } + + pub fn set_show_pin_icon(&self, show: bool) { + self.show_pin_icon.set(show); + for stacked in self.root.stacked.iter() { + if let Some(float) = stacked.deref().clone().node_into_float() { + float.schedule_render_titles(); + } + } + } + + pub fn set_float_above_fullscreen(&self, v: bool) { + self.float_above_fullscreen.set(v); + for seat in self.globals.seats.lock().values() { + seat.emulate_cursor_moved(); + seat.trigger_tree_changed(false); + } + self.root.update_visible(self); + } + + pub fn reset_sizes(&self) { + self.theme.sizes.reset(); + self.spaces_changed(); + } + + fn fonts_changed(&self) { + struct V; + impl NodeVisitorBase for V { + fn visit_container(&mut self, node: &Rc) { + node.schedule_render_titles(); + node.node_visit_children(self); + } + fn visit_output(&mut self, node: &Rc) { + node.schedule_update_render_data(); + node.node_visit_children(self); + } + fn visit_float(&mut self, node: &Rc) { + node.schedule_render_titles(); + node.node_visit_children(self); + } + } + self.root.clone().node_visit(&mut V); + } + + pub fn reset_fonts(&self) { + let theme = &self.theme; + theme.font.set(self.theme.default_font.clone()); + theme.bar_font.set(None); + theme.title_font.set(None); + self.fonts_changed(); + } + + pub fn set_font(&self, font: &str) { + self.theme.font.set(Arc::new(font.to_string())); + self.fonts_changed(); + } + + pub fn set_bar_font(&self, font: Option<&str>) { + let font = font.map(|font| Arc::new(font.to_string())); + self.theme.bar_font.set(font); + self.fonts_changed(); + } + + pub fn set_title_font(&self, font: Option<&str>) { + let font = font.map(|font| Arc::new(font.to_string())); + self.theme.title_font.set(font); + self.fonts_changed(); + } + + pub fn set_bar_position(&self, p: BarPosition) { + self.theme.bar_position.set(p); + self.spaces_changed(); + } + + pub fn set_size(&self, sized: ThemeSized, size: i32) { + let field = sized.field(&self.theme); + field.val.set(size); + field.set.set(true); + self.spaces_changed(); + } + + pub fn set_color(&self, colored: ThemeColor, v: Color) { + colored.field(&self.theme).set(v); + self.colors_changed(); + } } #[derive(Debug, Error)] diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index bd4afc00..aee14494 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -74,6 +74,9 @@ pub fn handle(state: &Rc, connector: &Rc) { eotf: backend_state.eotf, supported_formats: Default::default(), brightness: None, + blend_space: BlendSpace::Srgb, + use_native_gamut: false, + vrr_cursor_hz: None, }; let data = Rc::new(ConnectorData { id, diff --git a/src/theme.rs b/src/theme.rs index f470993e..303ed28a 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -4,12 +4,17 @@ use { crate::{ cmm::cmm_eotf::{Eotf, bt1886_eotf_args, bt1886_inv_eotf_args}, gfx_api::AlphaMode, - utils::clonecell::CloneCell, + utils::{clonecell::CloneCell, static_text::StaticText}, }, jay_config::theme::BarPosition as ConfigBarPosition, linearize::Linearize, num_traits::Float, - std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc}, + std::{ + cell::Cell, + cmp::Ordering, + ops::{Add, Div, Mul}, + sync::Arc, + }, }; #[derive(Copy, Clone, Debug, PartialEq)] @@ -337,6 +342,32 @@ impl Color { a: self.a * (1.0 - other.a) + other.a, } } + + pub fn srgb_to_oklab(self) -> Oklab { + if self.a == 0.0 { + return Oklab { + l: 0.0, + a: 0.0, + b: 0.0, + }; + } + + let [r, g, b, _] = self.to_array2(Eotf::Linear, Some(1.0 / self.a)); + + let l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b; + let m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b; + let s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b; + + let l_ = l.cbrt(); + let m_ = m.cbrt(); + let s_ = s.cbrt(); + + let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_; + let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_; + let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_; + + Oklab { l, a, b } + } } impl From for Color { @@ -361,6 +392,25 @@ macro_rules! colors { )* } + #[derive(Copy, Clone, Debug, Linearize)] + #[expect(non_camel_case_types)] + pub enum ThemeColor { + $( + $name, + )* + } + + impl ThemeColor { + pub fn field(self, theme: &Theme) -> &Cell { + let colors = &theme.colors; + match self { + $( + Self::$name => &colors.$name, + )* + } + } + } + impl ThemeColors { pub fn reset(&self) { let default = Self::default(); @@ -406,6 +456,30 @@ colors! { highlight = (0x9d, 0x28, 0xc6, 0x7f), } +impl StaticText for ThemeColor { + fn text(&self) -> &'static str { + match self { + ThemeColor::background => "Background", + ThemeColor::unfocused_title_background => "Title Background (unfocused)", + ThemeColor::focused_title_background => "Title Background (focused)", + ThemeColor::captured_unfocused_title_background => { + "Title Background (unfocused, captured)" + } + ThemeColor::captured_focused_title_background => "Title Background (focused, captured)", + ThemeColor::focused_inactive_title_background => "Title Background (focused, inactive)", + ThemeColor::unfocused_title_text => "Title Text (unfocused)", + ThemeColor::focused_title_text => "Title Text (focused)", + ThemeColor::focused_inactive_title_text => "Title Text (focused, inactive)", + ThemeColor::separator => "Separator", + ThemeColor::border => "Border", + ThemeColor::bar_background => "Bar Background", + ThemeColor::bar_text => "Bar Text", + ThemeColor::attention_requested_background => "Attention Requested", + ThemeColor::highlight => "Highlight", + } + } +} + pub struct ThemeSize { pub val: Cell, pub set: Cell, @@ -425,7 +499,7 @@ macro_rules! sizes { )* } - #[derive(Copy, Clone, Debug)] + #[derive(Copy, Clone, Debug, Linearize)] #[expect(non_camel_case_types)] pub enum ThemeSized { $( @@ -514,6 +588,17 @@ sizes! { bar_separator_width = (0, 1000, 1), } +impl StaticText for ThemeSized { + fn text(&self) -> &'static str { + match self { + ThemeSized::title_height => "Title Height", + ThemeSized::bar_height => "Bar Height", + ThemeSized::border_width => "Border Width", + ThemeSized::bar_separator_width => "Bar Separator Width", + } + } +} + pub const DEFAULT_FONT: &str = "monospace 8"; #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)] @@ -523,6 +608,15 @@ pub enum BarPosition { Bottom, } +impl StaticText for BarPosition { + fn text(&self) -> &'static str { + match self { + BarPosition::Top => "Top", + BarPosition::Bottom => "Bottom", + } + } +} + impl TryFrom for BarPosition { type Error = (); @@ -601,3 +695,94 @@ impl Theme { } } } + +#[derive(Copy, Clone, Debug)] +pub struct Oklch { + pub l: f32, + pub c: f32, + pub h: f32, +} + +#[derive(Copy, Clone, Debug)] +pub struct Oklab { + pub l: f32, + pub a: f32, + pub b: f32, +} + +impl Oklab { + pub fn to_srgb(self) -> Color { + let l_ = self.l + 0.3963377774 * self.a + 0.2158037573 * self.b; + let m_ = self.l - 0.1055613458 * self.a - 0.0638541728 * self.b; + let s_ = self.l - 0.0894841775 * self.a - 1.2914855480 * self.b; + + let l = l_ * l_ * l_; + let m = m_ * m_ * m_; + let s = s_ * s_ * s_; + + let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s; + let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s; + let b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s; + + Color::new( + Eotf::Linear, + AlphaMode::PremultipliedElectrical, + r, + g, + b, + 1.0, + ) + } + + pub fn to_oklch(self) -> Oklch { + let c = (self.a * self.a + self.b * self.b).sqrt(); + let h = self.b.atan2(self.a); + + Oklch { l: self.l, c, h } + } +} + +impl Oklch { + pub fn to_oklab(self) -> Oklab { + let a = self.c * self.h.cos(); + let b = self.c * self.h.sin(); + + Oklab { l: self.l, a, b } + } +} + +impl Add for Oklab { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + l: self.l + rhs.l, + a: self.a + rhs.a, + b: self.b + rhs.b, + } + } +} + +impl Mul for Oklab { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + Self { + l: self.l * rhs, + a: self.a * rhs, + b: self.b * rhs, + } + } +} + +impl Div for Oklab { + type Output = Self; + + fn div(self, rhs: f32) -> Self::Output { + Self { + l: self.l / rhs, + a: self.a / rhs, + b: self.b / rhs, + } + } +} diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 0b720d5c..a4242a89 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -2,7 +2,7 @@ use { crate::{ async_engine::{AsyncEngine, SpawnedFuture}, client::{EventFormatter, RequestParser}, - compositor::WAYLAND_DISPLAY, + compositor::{LogLevel, WAYLAND_DISPLAY}, io_uring::{IoUring, IoUringError}, logger::Logger, object::{ObjectId, WL_DISPLAY_ID}, @@ -30,7 +30,6 @@ use { }, }, ahash::AHashMap, - log::Level, std::{ cell::{Cell, RefCell}, collections::VecDeque, @@ -97,7 +96,7 @@ pub struct ToolClient { jay_damage_tracking: Cell>>, } -pub fn with_tool_client(level: Level, f: F) +pub fn with_tool_client(level: LogLevel, f: F) where F: FnOnce(Rc) -> T + 'static, T: Future + 'static, @@ -111,7 +110,7 @@ fn handle_error(e: ToolClientError) -> ! { fatal!("Could not create a tool client: {}", ErrorFmt(e)); } -fn with_tool_client_(level: Level, f: F) -> Result<(), ToolClientError> +fn with_tool_client_(level: LogLevel, f: F) -> Result<(), ToolClientError> where F: FnOnce(Rc) -> T + 'static, T: Future + 'static, diff --git a/src/tree.rs b/src/tree.rs index 50a9624b..6495f1b7 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -25,7 +25,7 @@ use { keyboard::KeyboardState, rect::Rect, renderer::Renderer, - utils::{linkedlist::NodeRef, numcell::NumCell}, + utils::{linkedlist::NodeRef, numcell::NumCell, static_text::StaticText}, }, jay_config::{ Direction as JayDirection, video::Transform as ConfigTransform, @@ -79,6 +79,15 @@ impl Into for WorkspaceDisplayOrder { } } +impl StaticText for WorkspaceDisplayOrder { + fn text(&self) -> &'static str { + match self { + WorkspaceDisplayOrder::Manual => "Manual", + WorkspaceDisplayOrder::Sorted => "Sorted", + } + } +} + #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default, Linearize)] pub enum Transform { #[default] diff --git a/src/tree/output.rs b/src/tree/output.rs index fd232355..bf047663 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -478,6 +478,10 @@ impl OutputNode { } } + pub fn on_colors_changed(self: &Rc) { + self.schedule_update_render_data(); + } + pub fn set_preferred_scale(self: &Rc, scale: Scale) { let old_scale = self.global.persistent.scale.replace(scale); if scale == old_scale { @@ -996,6 +1000,10 @@ impl OutputNode { .replace(use_native_gamut); if old != use_native_gamut { self.update_color_description(); + self.global + .connector + .head_managers + .handle_use_native_gamut_change(use_native_gamut); } } @@ -1003,6 +1011,10 @@ impl OutputNode { let old = self.global.persistent.blend_space.replace(blend_space); if old != blend_space { self.state.damage(self.global.position()); + self.global + .connector + .head_managers + .handle_blend_space_change(blend_space); } } fn find_stacked_at( @@ -1528,6 +1540,10 @@ impl OutputNode { log::error!("Could not set gamma_lut: {}", ErrorFmt(e)); }) } + + pub fn set_flip_margin(&self, margin_ns: u64) { + self.flip_margin_ns.set(Some(margin_ns)); + } } pub struct OutputTitle { @@ -1904,14 +1920,24 @@ pub enum VrrMode { #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct VrrSurfaceRequirements { - content_type: Option, + pub content_type: Option, } -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct VrrContentTypeRequirements { - photo: bool, - video: bool, - game: bool, + pub photo: bool, + pub video: bool, + pub game: bool, +} + +impl Default for VrrContentTypeRequirements { + fn default() -> Self { + Self { + photo: true, + video: true, + game: true, + } + } } impl VrrMode { @@ -1970,7 +1996,15 @@ pub enum TearingMode { #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct TearingSurfaceRequirements { - tearing_requested: bool, + pub tearing_requested: bool, +} + +impl Default for TearingSurfaceRequirements { + fn default() -> Self { + Self { + tearing_requested: true, + } + } } impl TearingMode { diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 8ec1e105..fdf4f1b0 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -34,6 +34,7 @@ use { array_to_tuple::ArrayToTuple, clonecell::CloneCell, copyhashmap::CopyHashMap, + event_listener::LazyEventSource, hash_map_ext::HashMapExt, numcell::NumCell, rc_eq::rc_eq, @@ -48,7 +49,7 @@ use { jay_config::{window, window::WindowType}, std::{ borrow::Borrow, - cell::{Cell, RefCell}, + cell::{Cell, OnceCell, RefCell}, ops::Deref, rc::{Rc, Weak}, }, @@ -404,6 +405,7 @@ pub struct ToplevelData { pub just_mapped_scheduled: Cell, pub seat_foci: CopyHashMap, pub content_type: Cell>, + pub property_changed_source: OnceCell>, } impl ToplevelData { @@ -457,6 +459,7 @@ impl ToplevelData { just_mapped_scheduled: Cell::new(false), seat_foci: Default::default(), content_type: Default::default(), + property_changed_source: Default::default(), } } @@ -497,7 +500,14 @@ impl ToplevelData { (width, height) } + fn trigger_property_source(&self) { + if let Some(source) = self.property_changed_source.get() { + source.trigger(); + } + } + pub fn property_changed(&self, change: TlMatcherChange) { + self.trigger_property_source(); let mgr = &self.state.tl_matcher_manager; let props = self.changed_properties.get(); if props.is_none() && mgr.has_no_interest(self, change) { @@ -925,6 +935,12 @@ impl ToplevelData { }; parent.node_is_workspace() } + + #[expect(dead_code)] + pub fn property_changed_source(&self) -> &Rc { + self.property_changed_source + .get_or_init(|| self.state.lazy_event_sources.create_source()) + } } impl Drop for ToplevelData { diff --git a/src/utils.rs b/src/utils.rs index 109bb822..7c956c82 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,7 @@ pub mod activation_token; pub mod array; pub mod array_to_tuple; pub mod asyncevent; +pub mod atomic_enum; pub mod binary_search_map; pub mod bindings; pub mod bitfield; @@ -53,6 +54,7 @@ pub mod run_toplevel; pub mod scroller; pub mod smallmap; pub mod stack; +pub mod static_text; pub mod string_ext; pub mod syncqueue; pub mod threshold_counter; diff --git a/src/utils/atomic_enum.rs b/src/utils/atomic_enum.rs new file mode 100644 index 00000000..45e50416 --- /dev/null +++ b/src/utils/atomic_enum.rs @@ -0,0 +1,41 @@ +use { + linearize::Linearize, + std::{ + marker::PhantomData, + sync::atomic::{AtomicUsize, Ordering}, + }, +}; + +pub struct AtomicEnum { + v: AtomicUsize, + _phantom: PhantomData, +} + +impl Default for AtomicEnum +where + T: Default + Linearize + Copy, +{ + fn default() -> Self { + Self::new(T::default()) + } +} + +impl AtomicEnum +where + T: Linearize + Copy, +{ + pub fn new(t: T) -> Self { + Self { + v: AtomicUsize::new(t.linearize()), + _phantom: Default::default(), + } + } + + pub fn load(&self, ordering: Ordering) -> T { + unsafe { T::from_linear_unchecked(self.v.load(ordering)) } + } + + pub fn store(&self, t: T, ordering: Ordering) { + self.v.store(t.linearize(), ordering); + } +} diff --git a/src/utils/event_listener.rs b/src/utils/event_listener.rs index 7e5e5964..63c66012 100644 --- a/src/utils/event_listener.rs +++ b/src/utils/event_listener.rs @@ -1,11 +1,28 @@ use { - crate::utils::linkedlist::{LinkedList, LinkedListIter, LinkedNode}, + crate::{ + state::State, + utils::{ + linkedlist::{LinkedList, LinkedListIter, LinkedNode}, + queue::AsyncQueue, + }, + }, std::{ cell::Cell, + ops::Deref, rc::{Rc, Weak}, }, }; +pub async fn handle_lazy_event_sources(state: Rc) { + loop { + let source = state.lazy_event_sources.queue.pop().await; + source.queued.set(false); + for listener in source.listeners.iter() { + listener.triggered(); + } + } +} + pub struct EventSource { listeners: LinkedList>, on_attach: Cell>>, @@ -15,6 +32,21 @@ pub struct EventListener { link: LinkedNode>, } +#[derive(Default)] +pub struct LazyEventSources { + queue: AsyncQueue>, +} + +pub trait LazyEventSourceListener { + fn triggered(self: Rc); +} + +pub struct LazyEventSource { + sources: Rc, + queued: Cell, + listeners: EventSource, +} + impl Default for EventSource { fn default() -> Self { Self { @@ -39,6 +71,10 @@ impl EventSource { self.listeners.is_not_empty() } + pub fn is_empty(&self) -> bool { + self.listeners.is_empty() + } + pub fn on_attach(&self, f: Box) { self.on_attach.set(Some(f)); } @@ -83,3 +119,37 @@ impl EventListener { self.link.upgrade() } } + +impl Deref for LazyEventSource { + type Target = EventSource; + + fn deref(&self) -> &Self::Target { + &self.listeners + } +} + +impl LazyEventSource { + pub fn trigger(self: &Rc) { + if self.listeners.is_empty() { + return; + } + if self.queued.replace(true) { + return; + } + self.sources.queue.push(self.clone()); + } +} + +impl LazyEventSources { + pub fn create_source(self: &Rc) -> Rc { + Rc::new(LazyEventSource { + sources: self.clone(), + queued: Default::default(), + listeners: Default::default(), + }) + } + + pub fn clear(&self) { + self.queue.clear(); + } +} diff --git a/src/utils/static_text.rs b/src/utils/static_text.rs new file mode 100644 index 00000000..eb8d03c1 --- /dev/null +++ b/src/utils/static_text.rs @@ -0,0 +1,3 @@ +pub trait StaticText { + fn text(&self) -> &'static str; +} diff --git a/src/vulkan_core.rs b/src/vulkan_core.rs index 397a5356..00dac433 100644 --- a/src/vulkan_core.rs +++ b/src/vulkan_core.rs @@ -32,6 +32,7 @@ use { pub mod device; pub mod fence; +pub mod gpu_alloc_ash; pub mod sync; pub mod timeline_semaphore; diff --git a/src/gfx_apis/vulkan/gpu_alloc_ash.rs b/src/vulkan_core/gpu_alloc_ash.rs similarity index 100% rename from src/gfx_apis/vulkan/gpu_alloc_ash.rs rename to src/vulkan_core/gpu_alloc_ash.rs diff --git a/src/wl_usr.rs b/src/wl_usr.rs index 35f76b93..dcaa79bf 100644 --- a/src/wl_usr.rs +++ b/src/wl_usr.rs @@ -40,7 +40,7 @@ use { rc::Rc, }, thiserror::Error, - uapi::c, + uapi::{OwnedFd, c}, }; #[derive(Debug, Error)] @@ -119,6 +119,24 @@ impl UsrCon { if let Err(e) = ring.connect(&socket, &addr).await { return Err(UsrConError::Connect(e)); } + Ok(Self::from_socket( + ring, + wheel, + eng, + dma_buf_ids, + &socket, + server_id, + )) + } + + pub fn from_socket( + ring: &Rc, + wheel: &Rc, + eng: &Rc, + dma_buf_ids: &Rc, + socket: &Rc, + server_id: u32, + ) -> Rc { let mut obj_ids = Bitfield::default(); obj_ids.take(0); obj_ids.take(1); @@ -150,7 +168,7 @@ impl UsrCon { "wl_usr incoming", Incoming { con: slf.clone(), - buf: BufFdIn::new(&socket, &slf.ring), + buf: BufFdIn::new(socket, &slf.ring), data: vec![], } .run(), @@ -161,13 +179,13 @@ impl UsrCon { "wl_usr outgoing", Outgoing { con: slf.clone(), - buf: BufFdOut::new(&socket, &slf.ring), + buf: BufFdOut::new(socket, &slf.ring), buffers: Default::default(), } .run(), ), )); - Ok(slf) + slf } pub fn kill(&self) { @@ -224,7 +242,8 @@ impl UsrCon { where F: FnOnce() + 'static, { - let callback = Rc::new(UsrWlCallback::new(self, handler)); + let callback = Rc::new(UsrWlCallback::new(self)); + callback.owner.set(Some(Rc::new(Cell::new(Some(handler))))); self.request(wl_display::Sync { self_id: WL_DISPLAY_ID, callback: callback.id, diff --git a/src/wl_usr/usr_ifs.rs b/src/wl_usr/usr_ifs.rs index 8f31fc04..c2272c7c 100644 --- a/src/wl_usr/usr_ifs.rs +++ b/src/wl_usr/usr_ifs.rs @@ -15,7 +15,12 @@ pub mod usr_linux_dmabuf; pub mod usr_wl_buffer; pub mod usr_wl_callback; pub mod usr_wl_compositor; +pub mod usr_wl_data_device; +pub mod usr_wl_data_device_manager; +pub mod usr_wl_data_offer; +pub mod usr_wl_data_source; pub mod usr_wl_display; +pub mod usr_wl_keyboard; pub mod usr_wl_output; pub mod usr_wl_pointer; pub mod usr_wl_registry; @@ -25,9 +30,17 @@ pub mod usr_wl_shm_pool; pub mod usr_wl_surface; pub mod usr_wlr_layer_shell; pub mod usr_wlr_layer_surface; +pub mod usr_wp_cursor_shape_device_v1; +pub mod usr_wp_cursor_shape_manager_v1; pub mod usr_wp_fractional_scale; pub mod usr_wp_fractional_scale_manager; pub mod usr_wp_viewport; pub mod usr_wp_viewporter; +pub mod usr_xdg_surface; +pub mod usr_xdg_toplevel; +pub mod usr_xdg_wm_base; pub mod usr_zwlr_screencopy_frame; pub mod usr_zwlr_screencopy_manager; +pub mod usr_zwp_linux_buffer_params_v1; +pub mod usr_zwp_linux_dmabuf_v1; +pub mod usr_zwp_primary_selection_device_manager; diff --git a/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs b/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs index f7ea6fa8..03592c4c 100644 --- a/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs +++ b/src/wl_usr/usr_ifs/usr_jay_select_workspace.rs @@ -1,5 +1,6 @@ use { crate::{ + globals::GlobalName, object::Version, utils::clonecell::CloneCell, wire::{JaySelectWorkspaceId, jay_select_workspace::*}, @@ -9,7 +10,7 @@ use { usr_object::UsrObject, }, }, - std::{convert::Infallible, rc::Rc}, + std::{cell::Cell, convert::Infallible, rc::Rc}, }; pub struct UsrJaySelectWorkspace { @@ -20,7 +21,7 @@ pub struct UsrJaySelectWorkspace { } pub trait UsrJaySelectWorkspaceOwner { - fn done(&self, output: u32, ws: Option>); + fn done(&self, output: GlobalName, ws: Option>); } impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace { @@ -28,7 +29,7 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace { fn cancelled(&self, _ev: Cancelled, _slf: &Rc) -> Result<(), Self::Error> { if let Some(owner) = self.owner.get() { - owner.done(0, None); + owner.done(GlobalName::from_raw(0), None); } self.con.remove_obj(self); Ok(()) @@ -41,7 +42,7 @@ impl JaySelectWorkspaceEventHandler for UsrJaySelectWorkspace { owner: Default::default(), version: self.version, linear_id: Default::default(), - output: Default::default(), + output: Cell::new(GlobalName::from_raw(0)), name: Default::default(), }); self.con.add_object(tl.clone()); diff --git a/src/wl_usr/usr_ifs/usr_jay_workspace.rs b/src/wl_usr/usr_ifs/usr_jay_workspace.rs index d1f3ca7b..dfaf0562 100644 --- a/src/wl_usr/usr_ifs/usr_jay_workspace.rs +++ b/src/wl_usr/usr_ifs/usr_jay_workspace.rs @@ -1,5 +1,6 @@ use { crate::{ + globals::GlobalName, object::Version, utils::clonecell::CloneCell, wire::{JayWorkspaceId, jay_workspace::*}, @@ -18,7 +19,7 @@ pub struct UsrJayWorkspace { pub owner: CloneCell>>, pub version: Version, pub linear_id: Cell, - pub output: Cell, + pub output: Cell, pub name: RefCell>, } @@ -68,7 +69,7 @@ impl JayWorkspaceEventHandler for UsrJayWorkspace { } fn output(&self, ev: Output, _slf: &Rc) -> Result<(), Self::Error> { - self.output.set(ev.global_name); + self.output.set(GlobalName::from_raw(ev.global_name)); if let Some(owner) = self.owner.get() { owner.output(&ev); } diff --git a/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs b/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs index 186a4579..62036874 100644 --- a/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs +++ b/src/wl_usr/usr_ifs/usr_jay_workspace_watcher.rs @@ -1,11 +1,12 @@ use { crate::{ + globals::GlobalName, object::Version, utils::clonecell::CloneCell, wire::{JayWorkspaceWatcherId, jay_workspace_watcher::*}, wl_usr::{UsrCon, usr_ifs::usr_jay_workspace::UsrJayWorkspace, usr_object::UsrObject}, }, - std::{convert::Infallible, ops::Deref, rc::Rc}, + std::{cell::Cell, convert::Infallible, ops::Deref, rc::Rc}, }; pub struct UsrJayWorkspaceWatcher { @@ -32,7 +33,7 @@ impl JayWorkspaceWatcherEventHandler for UsrJayWorkspaceWatcher { owner: Default::default(), version: self.version, linear_id: Default::default(), - output: Default::default(), + output: Cell::new(GlobalName::from_raw(0)), name: Default::default(), }); self.con.add_object(jw.clone()); diff --git a/src/wl_usr/usr_ifs/usr_wl_callback.rs b/src/wl_usr/usr_ifs/usr_wl_callback.rs index eaa4e5fd..2afba91b 100644 --- a/src/wl_usr/usr_ifs/usr_wl_callback.rs +++ b/src/wl_usr/usr_ifs/usr_wl_callback.rs @@ -10,19 +10,31 @@ use { pub struct UsrWlCallback { pub id: WlCallbackId, pub con: Rc, - pub handler: Cell>>, + pub owner: Cell>>, pub version: Version, } +pub trait UsrWlCallbackOwner { + fn done(self: Rc); +} + +impl UsrWlCallbackOwner for Cell> +where + T: FnOnce() + 'static, +{ + fn done(self: Rc) { + if let Some(slf) = self.take() { + slf(); + } + } +} + impl UsrWlCallback { - pub fn new(con: &Rc, handler: F) -> Self - where - F: FnOnce() + 'static, - { + pub fn new(con: &Rc) -> Self { Self { id: con.id(), con: con.clone(), - handler: Cell::new(Some(Box::new(handler))), + owner: Default::default(), version: Version(1), } } @@ -32,8 +44,8 @@ impl WlCallbackEventHandler for UsrWlCallback { type Error = Infallible; fn done(&self, _ev: Done, _slf: &Rc) -> Result<(), Self::Error> { - if let Some(handler) = self.handler.take() { - handler(); + if let Some(handler) = self.owner.take() { + handler.done(); } self.con.remove_obj(self); Ok(()) @@ -51,6 +63,6 @@ impl UsrObject for UsrWlCallback { } fn break_loops(&self) { - self.handler.take(); + self.owner.take(); } } diff --git a/src/wl_usr/usr_ifs/usr_wl_data_device.rs b/src/wl_usr/usr_ifs/usr_wl_data_device.rs new file mode 100644 index 00000000..7b3cb57e --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_wl_data_device.rs @@ -0,0 +1,99 @@ +use { + crate::{ + object::Version, + utils::clonecell::CloneCell, + wire::{WlDataDeviceId, wl_data_device::*}, + wl_usr::{ + UsrCon, + usr_ifs::{usr_wl_data_offer::UsrWlDataOffer, usr_wl_data_source::UsrWlDataSource}, + usr_object::UsrObject, + }, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrWlDataDevice { + pub id: WlDataDeviceId, + pub con: Rc, + pub version: Version, + pub offer: CloneCell>>, + pub selection: CloneCell>>, +} + +impl UsrWlDataDevice { + #[expect(dead_code)] + pub fn set_selection(&self, serial: u32, source: &UsrWlDataSource) { + self.con.request(SetSelection { + self_id: self.id, + source: source.id, + serial, + }); + } +} + +impl WlDataDeviceEventHandler for UsrWlDataDevice { + type Error = Infallible; + + fn data_offer(&self, ev: DataOffer, _slf: &Rc) -> Result<(), Self::Error> { + let obj = Rc::new(UsrWlDataOffer { + id: ev.id, + con: self.con.clone(), + version: self.version, + mime_types: Default::default(), + }); + self.con.add_object(obj.clone()); + if let Some(offer) = self.offer.set(Some(obj)) { + self.con.remove_obj(&*offer); + } + Ok(()) + } + + fn enter(&self, ev: Enter, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } + + fn leave(&self, ev: Leave, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } + + fn motion(&self, ev: Motion, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } + + fn drop_(&self, ev: Drop, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } + + fn selection(&self, ev: Selection, _slf: &Rc) -> Result<(), Self::Error> { + self.selection.take(); + if let Some(offer) = self.offer.get() + && offer.id == ev.id + { + self.selection.set(Some(offer)); + } + Ok(()) + } +} + +usr_object_base! { + self = UsrWlDataDevice = WlDataDevice; + version = self.version; +} + +impl UsrObject for UsrWlDataDevice { + fn destroy(&self) { + if let Some(offer) = self.offer.take() { + self.con.remove_obj(&*offer); + } + self.con.request(Release { self_id: self.id }); + } + + fn break_loops(&self) { + self.selection.take(); + self.offer.take(); + } +} diff --git a/src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs b/src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs new file mode 100644 index 00000000..0957a93a --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_wl_data_device_manager.rs @@ -0,0 +1,72 @@ +use { + crate::{ + object::Version, + wire::{WlDataDeviceManagerId, wl_data_device_manager::*}, + wl_usr::{ + UsrCon, + usr_ifs::{ + usr_wl_data_device::UsrWlDataDevice, usr_wl_data_source::UsrWlDataSource, + usr_wl_seat::UsrWlSeat, + }, + usr_object::UsrObject, + }, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrWlDataDeviceManager { + pub id: WlDataDeviceManagerId, + pub con: Rc, + pub version: Version, +} + +impl UsrWlDataDeviceManager { + #[expect(dead_code)] + pub fn create_data_source(&self) -> Rc { + let obj = Rc::new(UsrWlDataSource { + id: self.con.id(), + con: self.con.clone(), + owner: Default::default(), + version: self.version, + }); + self.con.request(CreateDataSource { + self_id: self.id, + id: obj.id, + }); + self.con.add_object(obj.clone()); + obj + } + + #[expect(dead_code)] + pub fn get_data_device(&self, seat: &UsrWlSeat) -> Rc { + let obj = Rc::new(UsrWlDataDevice { + id: self.con.id(), + con: self.con.clone(), + version: self.version, + offer: Default::default(), + selection: Default::default(), + }); + self.con.request(GetDataDevice { + self_id: self.id, + id: obj.id, + seat: seat.id, + }); + self.con.add_object(obj.clone()); + obj + } +} + +impl WlDataDeviceManagerEventHandler for UsrWlDataDeviceManager { + type Error = Infallible; +} + +usr_object_base! { + self = UsrWlDataDeviceManager = WlDataDeviceManager; + version = self.version; +} + +impl UsrObject for UsrWlDataDeviceManager { + fn destroy(&self) { + self.con.request(Release { self_id: self.id }); + } +} diff --git a/src/wl_usr/usr_ifs/usr_wl_data_offer.rs b/src/wl_usr/usr_ifs/usr_wl_data_offer.rs new file mode 100644 index 00000000..4c500e36 --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_wl_data_offer.rs @@ -0,0 +1,58 @@ +use { + crate::{ + object::Version, + wire::{WlDataOfferId, wl_data_offer::*}, + wl_usr::{UsrCon, usr_object::UsrObject}, + }, + ahash::AHashSet, + std::{cell::RefCell, convert::Infallible, rc::Rc}, + uapi::OwnedFd, +}; + +pub struct UsrWlDataOffer { + pub id: WlDataOfferId, + pub con: Rc, + pub version: Version, + pub mime_types: RefCell>, +} + +impl UsrWlDataOffer { + #[expect(dead_code)] + pub fn receive(&self, mime_type: &str, fd: &Rc) { + self.con.request(Receive { + self_id: self.id, + mime_type, + fd: fd.clone(), + }); + } +} + +impl WlDataOfferEventHandler for UsrWlDataOffer { + type Error = Infallible; + + fn offer(&self, ev: Offer<'_>, _slf: &Rc) -> Result<(), Self::Error> { + self.mime_types + .borrow_mut() + .insert(ev.mime_type.to_string()); + Ok(()) + } + + fn source_actions(&self, _ev: SourceActions, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn action(&self, _ev: Action, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } +} + +usr_object_base! { + self = UsrWlDataOffer = WlDataOffer; + version = self.version; +} + +impl UsrObject for UsrWlDataOffer { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }); + } +} diff --git a/src/wl_usr/usr_ifs/usr_wl_data_source.rs b/src/wl_usr/usr_ifs/usr_wl_data_source.rs new file mode 100644 index 00000000..e7109346 --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_wl_data_source.rs @@ -0,0 +1,82 @@ +use { + crate::{ + object::Version, + utils::clonecell::CloneCell, + wire::{WlDataSourceId, wl_data_source::*}, + wl_usr::{UsrCon, usr_object::UsrObject}, + }, + std::{convert::Infallible, rc::Rc}, + uapi::OwnedFd, +}; + +pub struct UsrWlDataSource { + pub id: WlDataSourceId, + pub con: Rc, + pub owner: CloneCell>>, + pub version: Version, +} + +pub trait UsrWlDataSourceOwner { + fn send(&self, mime_type: &str, fd: Rc); +} + +impl UsrWlDataSource { + #[expect(dead_code)] + pub fn offer(&self, mime_type: &str) { + self.con.request(Offer { + self_id: self.id, + mime_type, + }); + } +} + +impl WlDataSourceEventHandler for UsrWlDataSource { + type Error = Infallible; + + fn target(&self, ev: Target<'_>, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } + + fn send(&self, ev: Send<'_>, _slf: &Rc) -> Result<(), Self::Error> { + if let Some(owner) = self.owner.get() { + owner.send(ev.mime_type, ev.fd); + } + Ok(()) + } + + fn cancelled(&self, ev: Cancelled, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } + + fn dnd_drop_performed(&self, ev: DndDropPerformed, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } + + fn dnd_finished(&self, ev: DndFinished, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } + + fn action(&self, ev: Action, _slf: &Rc) -> Result<(), Self::Error> { + let _ = ev; + Ok(()) + } +} + +usr_object_base! { + self = UsrWlDataSource = WlDataSource; + version = self.version; +} + +impl UsrObject for UsrWlDataSource { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }); + } + + fn break_loops(&self) { + self.owner.take(); + } +} diff --git a/src/wl_usr/usr_ifs/usr_wl_keyboard.rs b/src/wl_usr/usr_ifs/usr_wl_keyboard.rs new file mode 100644 index 00000000..05e7734d --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_wl_keyboard.rs @@ -0,0 +1,148 @@ +use { + crate::{ + ifs::wl_seat::wl_keyboard, + object::Version, + utils::{clonecell::CloneCell, mmap::mmap, oserror::OsError}, + wire::{WlKeyboardId, WlSurfaceId, wl_keyboard::*}, + wl_usr::{UsrCon, usr_object::UsrObject}, + }, + kbvm::{ + Components, Keycode, ModifierMask, + lookup::{Lookup, LookupTable}, + xkb::{ + Context, + diagnostic::{Diagnostic, WriteToLog}, + }, + }, + std::{cell::RefCell, rc::Rc}, + thiserror::Error, + uapi::c, +}; + +pub struct UsrWlKeyboard { + pub id: WlKeyboardId, + pub con: Rc, + pub keyboard: RefCell>, + pub owner: CloneCell>>, + pub version: Version, +} + +pub struct Keyboard { + lookup: LookupTable, + components: Components, +} + +pub trait UsrWlKeyboardOwner { + fn focus(self: Rc, surface: WlSurfaceId, serial: u32); + fn unfocus(self: Rc); + fn modifiers(self: Rc, mods: ModifierMask); + fn down(self: Rc, lookup: Lookup<'_>, serial: u32); + fn repeat(self: Rc, lookup: Lookup<'_>, serial: u32); + fn up(self: Rc, lookup: Lookup<'_>, serial: u32); +} + +impl WlKeyboardEventHandler for UsrWlKeyboard { + type Error = UsrWlKeyboardError; + + fn keymap(&self, ev: Keymap, _slf: &Rc) -> Result<(), Self::Error> { + let map = mmap(ev.size as _, c::PROT_READ, c::MAP_PRIVATE, ev.fd.raw(), 0) + .map_err(UsrWlKeyboardError::MapKeymap)?; + let mut builder = Context::builder(); + builder.enable_default_includes(false); + builder.enable_environment(false); + let keymap = builder + .build() + .keymap_from_bytes(WriteToLog, None, unsafe { &*map.ptr }) + .map_err(UsrWlKeyboardError::ParseKeymap)?; + let lookup = keymap.to_builder().build_lookup_table(); + let keyboard = Keyboard { + lookup, + components: Default::default(), + }; + self.keyboard.replace(Some(keyboard)); + Ok(()) + } + + fn enter(&self, ev: Enter<'_>, _slf: &Rc) -> Result<(), Self::Error> { + let Some(owner) = self.owner.get() else { + return Ok(()); + }; + owner.focus(ev.surface, ev.serial); + Ok(()) + } + + fn leave(&self, _ev: Leave, _slf: &Rc) -> Result<(), Self::Error> { + let Some(owner) = self.owner.get() else { + return Ok(()); + }; + owner.unfocus(); + Ok(()) + } + + fn key(&self, ev: Key, _slf: &Rc) -> Result<(), Self::Error> { + let Some(kb) = &*self.keyboard.borrow() else { + return Ok(()); + }; + let Some(owner) = self.owner.get() else { + return Ok(()); + }; + let kc = Keycode::from_evdev(ev.key); + let lookup = kb + .lookup + .lookup(kb.components.group, kb.components.mods, kc); + if ev.state == wl_keyboard::PRESSED { + owner.down(lookup, ev.serial); + } else if ev.state == wl_keyboard::REPEATED { + owner.repeat(lookup, ev.serial); + } else if ev.state == wl_keyboard::RELEASED { + owner.up(lookup, ev.serial); + } + Ok(()) + } + + fn modifiers(&self, ev: Modifiers, _slf: &Rc) -> Result<(), Self::Error> { + let Some(kb) = &mut *self.keyboard.borrow_mut() else { + return Ok(()); + }; + kb.components.mods_pressed.0 = ev.mods_depressed; + kb.components.mods_latched.0 = ev.mods_latched; + kb.components.mods_locked.0 = ev.mods_locked; + kb.components.group_locked.0 = ev.group; + let old = kb.components.mods; + kb.components.update_effective(); + let new = kb.components.mods; + if old != new + && let Some(owner) = self.owner.get() + { + owner.modifiers(new); + } + Ok(()) + } + + fn repeat_info(&self, _ev: RepeatInfo, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } +} + +usr_object_base! { + self = UsrWlKeyboard = WlKeyboard; + version = self.version; +} + +impl UsrObject for UsrWlKeyboard { + fn destroy(&self) { + self.con.request(Release { self_id: self.id }); + } + + fn break_loops(&self) { + self.owner.take(); + } +} + +#[derive(Debug, Error)] +pub enum UsrWlKeyboardError { + #[error("Could not map the keymap")] + MapKeymap(#[source] OsError), + #[error("Could not parse the keymap")] + ParseKeymap(#[source] Diagnostic), +} diff --git a/src/wl_usr/usr_ifs/usr_wl_pointer.rs b/src/wl_usr/usr_ifs/usr_wl_pointer.rs index d4220d97..c8dcf486 100644 --- a/src/wl_usr/usr_ifs/usr_wl_pointer.rs +++ b/src/wl_usr/usr_ifs/usr_wl_pointer.rs @@ -3,7 +3,7 @@ use { ifs::wl_seat::wl_pointer::PendingScroll, object::Version, utils::clonecell::CloneCell, - wire::{WlPointerId, wl_pointer::*}, + wire::{WlPointerId, WlSurfaceId, wl_pointer::*}, wl_usr::{UsrCon, usr_ifs::usr_wl_surface::UsrWlSurface, usr_object::UsrObject}, }, std::{cell::Cell, convert::Infallible, rc::Rc}, @@ -19,34 +19,34 @@ pub struct UsrWlPointer { } pub trait UsrWlPointerOwner { - fn enter(&self, ev: &Enter) { + fn enter(self: Rc, ev: &Enter) { let _ = ev; } - fn leave(&self, ev: &Leave) { + fn leave(self: Rc, ev: &Leave) { let _ = ev; } - fn motion(&self, ev: &Motion) { + fn motion(self: Rc, ev: &Motion) { let _ = ev; } - fn button(&self, ev: &Button) { + fn button(self: Rc, ev: &Button) { let _ = ev; } - fn scroll(&self, ps: &PendingScroll) { + fn scroll(self: Rc, ps: &PendingScroll) { let _ = ps; } } impl UsrWlPointer { #[expect(dead_code)] - pub fn set_cursor(&self, serial: u32, cursor: &UsrWlSurface, hot_x: i32, hot_y: i32) { + pub fn set_cursor(&self, serial: u32, cursor: Option<&UsrWlSurface>, hot_x: i32, hot_y: i32) { self.con.request(SetCursor { self_id: self.id, serial, - surface: cursor.id, + surface: cursor.map(|c| c.id).unwrap_or(WlSurfaceId::NONE), hotspot_x: hot_x, hotspot_y: hot_y, }); diff --git a/src/wl_usr/usr_ifs/usr_wl_registry.rs b/src/wl_usr/usr_ifs/usr_wl_registry.rs index 223800c6..b7d9585b 100644 --- a/src/wl_usr/usr_ifs/usr_wl_registry.rs +++ b/src/wl_usr/usr_ifs/usr_wl_registry.rs @@ -1,5 +1,6 @@ use { crate::{ + globals::GlobalName, object::Version, utils::clonecell::CloneCell, wire::{WlRegistryId, wl_registry::*}, @@ -16,24 +17,24 @@ pub struct UsrWlRegistry { } pub trait UsrWlRegistryOwner { - fn global(self: Rc, name: u32, interface: &str, version: u32) { + fn global(self: Rc, name: GlobalName, interface: &str, version: u32) { let _ = name; let _ = interface; let _ = version; } - fn global_remove(&self, name: u32) { + fn global_remove(&self, name: GlobalName) { let _ = name; } } impl UsrWlRegistry { - pub fn request_bind(&self, name: u32, version: u32, obj: &dyn UsrObject) { + pub fn bind(&self, name: GlobalName, obj: &dyn UsrObject) { self.con.request(Bind { self_id: self.id, - name, + name: name.raw(), interface: obj.interface().name(), - version, + version: obj.version().0, id: obj.id(), }); } @@ -44,14 +45,14 @@ impl WlRegistryEventHandler for UsrWlRegistry { fn global(&self, ev: Global<'_>, _slf: &Rc) -> Result<(), Self::Error> { if let Some(owner) = self.owner.get() { - owner.global(ev.name, ev.interface, ev.version); + owner.global(GlobalName::from_raw(ev.name), ev.interface, ev.version); } Ok(()) } fn global_remove(&self, ev: GlobalRemove, _slf: &Rc) -> Result<(), Self::Error> { if let Some(owner) = self.owner.get() { - owner.global_remove(ev.name); + owner.global_remove(GlobalName::from_raw(ev.name)); } Ok(()) } diff --git a/src/wl_usr/usr_ifs/usr_wl_seat.rs b/src/wl_usr/usr_ifs/usr_wl_seat.rs index 82db0a05..d1f4ba6f 100644 --- a/src/wl_usr/usr_ifs/usr_wl_seat.rs +++ b/src/wl_usr/usr_ifs/usr_wl_seat.rs @@ -3,7 +3,11 @@ use { object::Version, utils::clonecell::CloneCell, wire::{WlSeatId, wl_seat::*}, - wl_usr::{UsrCon, usr_ifs::usr_wl_pointer::UsrWlPointer, usr_object::UsrObject}, + wl_usr::{ + UsrCon, + usr_ifs::{usr_wl_keyboard::UsrWlKeyboard, usr_wl_pointer::UsrWlPointer}, + usr_object::UsrObject, + }, }, std::{cell::Cell, convert::Infallible, rc::Rc}, }; @@ -42,6 +46,23 @@ impl UsrWlSeat { }); ptr } + + #[expect(dead_code)] + pub fn get_keyboard(&self) -> Rc { + let kb = Rc::new(UsrWlKeyboard { + id: self.con.id(), + con: self.con.clone(), + keyboard: Default::default(), + owner: Default::default(), + version: self.version, + }); + self.con.add_object(kb.clone()); + self.con.request(GetKeyboard { + self_id: self.id, + id: kb.id, + }); + kb + } } impl WlSeatEventHandler for UsrWlSeat { diff --git a/src/wl_usr/usr_ifs/usr_wl_surface.rs b/src/wl_usr/usr_ifs/usr_wl_surface.rs index 070bc298..397c0cd7 100644 --- a/src/wl_usr/usr_ifs/usr_wl_surface.rs +++ b/src/wl_usr/usr_ifs/usr_wl_surface.rs @@ -37,16 +37,14 @@ impl UsrWlSurface { }); } - pub fn frame(&self, f: F) - where - F: FnOnce() + 'static, - { - let cb = Rc::new(UsrWlCallback::new(&self.con, f)); + pub fn frame(&self) -> Rc { + let cb = Rc::new(UsrWlCallback::new(&self.con)); self.con.request(Frame { self_id: self.id, callback: cb.id, }); - self.con.add_object(cb); + self.con.add_object(cb.clone()); + cb } pub fn commit(&self) { diff --git a/src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs b/src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs new file mode 100644 index 00000000..49e8c816 --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_wp_cursor_shape_device_v1.rs @@ -0,0 +1,41 @@ +use { + crate::{ + cursor::KnownCursor, + object::Version, + wire::{WpCursorShapeDeviceV1Id, wp_cursor_shape_device_v1::*}, + wl_usr::{UsrCon, usr_object::UsrObject}, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrWpCursorShapeDeviceV1 { + pub id: WpCursorShapeDeviceV1Id, + pub con: Rc, + pub version: Version, +} + +impl UsrWpCursorShapeDeviceV1 { + #[expect(dead_code)] + pub fn set_shape(&self, serial: u32, cursor: KnownCursor) { + self.con.request(SetShape { + self_id: self.id, + serial, + shape: cursor.to_shape(), + }); + } +} + +impl WpCursorShapeDeviceV1EventHandler for UsrWpCursorShapeDeviceV1 { + type Error = Infallible; +} + +usr_object_base! { + self = UsrWpCursorShapeDeviceV1 = WpCursorShapeDeviceV1; + version = self.version; +} + +impl UsrObject for UsrWpCursorShapeDeviceV1 { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }) + } +} diff --git a/src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs b/src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs new file mode 100644 index 00000000..69c9cd55 --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_wp_cursor_shape_manager_v1.rs @@ -0,0 +1,54 @@ +use { + crate::{ + object::Version, + wire::{WpCursorShapeManagerV1Id, wp_cursor_shape_manager_v1::*}, + wl_usr::{ + UsrCon, + usr_ifs::{ + usr_wl_pointer::UsrWlPointer, + usr_wp_cursor_shape_device_v1::UsrWpCursorShapeDeviceV1, + }, + usr_object::UsrObject, + }, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrWpCursorShapeManagerV1 { + pub id: WpCursorShapeManagerV1Id, + pub con: Rc, + pub version: Version, +} + +impl UsrWpCursorShapeManagerV1 { + #[expect(dead_code)] + pub fn get_pointer(&self, pointer: &UsrWlPointer) -> Rc { + let obj = Rc::new(UsrWpCursorShapeDeviceV1 { + id: self.con.id(), + con: self.con.clone(), + version: self.version, + }); + self.con.request(GetPointer { + self_id: self.id, + cursor_shape_device: obj.id, + pointer: pointer.id, + }); + self.con.add_object(obj.clone()); + obj + } +} + +impl WpCursorShapeManagerV1EventHandler for UsrWpCursorShapeManagerV1 { + type Error = Infallible; +} + +usr_object_base! { + self = UsrWpCursorShapeManagerV1 = WpCursorShapeManagerV1; + version = self.version; +} + +impl UsrObject for UsrWpCursorShapeManagerV1 { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }) + } +} diff --git a/src/wl_usr/usr_ifs/usr_xdg_surface.rs b/src/wl_usr/usr_ifs/usr_xdg_surface.rs new file mode 100644 index 00000000..e706c5c2 --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_xdg_surface.rs @@ -0,0 +1,70 @@ +use { + crate::{ + object::Version, + utils::clonecell::CloneCell, + wire::{XdgSurfaceId, xdg_surface::*}, + wl_usr::{UsrCon, usr_ifs::usr_xdg_toplevel::UsrXdgToplevel, usr_object::UsrObject}, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrXdgSurface { + pub id: XdgSurfaceId, + pub con: Rc, + pub owner: CloneCell>>, + pub version: Version, +} + +pub trait UsrXdgSurfaceOwner { + fn configure(&self) { + // nothing + } +} + +impl UsrXdgSurface { + #[expect(dead_code)] + pub fn get_toplevel(&self) -> Rc { + let obj = Rc::new(UsrXdgToplevel { + id: self.con.id(), + con: self.con.clone(), + owner: Default::default(), + version: self.version, + }); + self.con.request(GetToplevel { + self_id: self.id, + id: obj.id, + }); + self.con.add_object(obj.clone()); + obj + } +} + +impl XdgSurfaceEventHandler for UsrXdgSurface { + type Error = Infallible; + + fn configure(&self, ev: Configure, _slf: &Rc) -> Result<(), Self::Error> { + self.con.request(AckConfigure { + self_id: self.id, + serial: ev.serial, + }); + if let Some(owner) = self.owner.get() { + owner.configure(); + } + Ok(()) + } +} + +usr_object_base! { + self = UsrXdgSurface = XdgSurface; + version = self.version; +} + +impl UsrObject for UsrXdgSurface { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }) + } + + fn break_loops(&self) { + self.owner.take(); + } +} diff --git a/src/wl_usr/usr_ifs/usr_xdg_toplevel.rs b/src/wl_usr/usr_ifs/usr_xdg_toplevel.rs new file mode 100644 index 00000000..4e4d7491 --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_xdg_toplevel.rs @@ -0,0 +1,95 @@ +use { + crate::{ + object::Version, + utils::clonecell::CloneCell, + wire::{WlOutputId, XdgToplevelId, xdg_toplevel::*}, + wl_usr::{UsrCon, usr_object::UsrObject}, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrXdgToplevel { + pub id: XdgToplevelId, + pub con: Rc, + pub owner: CloneCell>>, + pub version: Version, +} + +impl UsrXdgToplevel { + #[expect(dead_code)] + pub fn set_title(&self, title: &str) { + self.con.request(SetTitle { + self_id: self.id, + title, + }); + } + + #[expect(dead_code)] + pub fn set_fullscreen(&self, fullscreen: bool) { + match fullscreen { + true => { + self.con.request(SetFullscreen { + self_id: self.id, + output: WlOutputId::NONE, + }); + } + false => { + self.con.request(UnsetFullscreen { self_id: self.id }); + } + } + } +} + +pub trait UsrXdgToplevelOwner { + fn configure(&self, width: i32, height: i32) { + let _ = width; + let _ = height; + } + + fn close(&self) { + // nothing + } +} + +impl UsrXdgToplevel {} + +impl XdgToplevelEventHandler for UsrXdgToplevel { + type Error = Infallible; + + fn configure(&self, ev: Configure<'_>, _slf: &Rc) -> Result<(), Self::Error> { + if let Some(owner) = self.owner.get() { + owner.configure(ev.width, ev.height); + } + Ok(()) + } + + fn close(&self, _ev: Close, _slf: &Rc) -> Result<(), Self::Error> { + if let Some(owner) = self.owner.get() { + owner.close(); + } + Ok(()) + } + + fn configure_bounds(&self, _ev: ConfigureBounds, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn wm_capabilities(&self, _ev: WmCapabilities<'_>, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } +} + +usr_object_base! { + self = UsrXdgToplevel = XdgToplevel; + version = self.version; +} + +impl UsrObject for UsrXdgToplevel { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }) + } + + fn break_loops(&self) { + self.owner.take(); + } +} diff --git a/src/wl_usr/usr_ifs/usr_xdg_wm_base.rs b/src/wl_usr/usr_ifs/usr_xdg_wm_base.rs new file mode 100644 index 00000000..b9d7faf0 --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_xdg_wm_base.rs @@ -0,0 +1,60 @@ +use { + crate::{ + object::Version, + wire::{XdgWmBaseId, xdg_wm_base::*}, + wl_usr::{ + UsrCon, + usr_ifs::{usr_wl_surface::UsrWlSurface, usr_xdg_surface::UsrXdgSurface}, + usr_object::UsrObject, + }, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrXdgWmBase { + pub id: XdgWmBaseId, + pub con: Rc, + pub version: Version, +} + +impl UsrXdgWmBase { + #[expect(dead_code)] + pub fn get_xdg_surface(&self, surface: &UsrWlSurface) -> Rc { + let obj = Rc::new(UsrXdgSurface { + id: self.con.id(), + con: self.con.clone(), + owner: Default::default(), + version: self.version, + }); + self.con.request(GetXdgSurface { + self_id: self.id, + id: obj.id, + surface: surface.id, + }); + self.con.add_object(obj.clone()); + obj + } +} + +impl XdgWmBaseEventHandler for UsrXdgWmBase { + type Error = Infallible; + + fn ping(&self, ev: Ping, _slf: &Rc) -> Result<(), Self::Error> { + self.con.request(Pong { + self_id: self.id, + serial: ev.serial, + }); + Ok(()) + } +} + +usr_object_base! { + self = UsrXdgWmBase = XdgWmBase; + version = self.version; +} + +impl UsrObject for UsrXdgWmBase { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }) + } +} diff --git a/src/wl_usr/usr_ifs/usr_zwp_linux_buffer_params_v1.rs b/src/wl_usr/usr_ifs/usr_zwp_linux_buffer_params_v1.rs new file mode 100644 index 00000000..f06a383b --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_zwp_linux_buffer_params_v1.rs @@ -0,0 +1,79 @@ +use { + crate::{ + format::Format, + object::Version, + video::Modifier, + wire::{ZwpLinuxBufferParamsV1Id, zwp_linux_buffer_params_v1::*}, + wl_usr::{UsrCon, usr_ifs::usr_wl_buffer::UsrWlBuffer, usr_object::UsrObject}, + }, + std::{convert::Infallible, rc::Rc}, + uapi::OwnedFd, +}; + +pub struct UsrZwpLinuxBufferParamsV1 { + pub id: ZwpLinuxBufferParamsV1Id, + pub con: Rc, + pub version: Version, +} + +impl UsrZwpLinuxBufferParamsV1 { + pub fn add( + &self, + fd: &Rc, + plane_idx: usize, + offset: u32, + stride: u32, + modifier: Modifier, + ) { + self.con.request(Add { + self_id: self.id, + fd: fd.clone(), + plane_idx: plane_idx as u32, + offset, + stride, + modifier, + }); + } + + pub fn create_immed(&self, width: i32, height: i32, format: &Format) -> Rc { + let obj = Rc::new(UsrWlBuffer { + id: self.con.id(), + con: self.con.clone(), + owner: Default::default(), + version: self.version, + }); + self.con.request(CreateImmed { + self_id: self.id, + buffer_id: obj.id, + width, + height, + format: format.drm, + flags: 0, + }); + self.con.add_object(obj.clone()); + obj + } +} + +impl ZwpLinuxBufferParamsV1EventHandler for UsrZwpLinuxBufferParamsV1 { + type Error = Infallible; + + fn created(&self, _ev: Created, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn failed(&self, _ev: Failed, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } +} + +usr_object_base! { + self = UsrZwpLinuxBufferParamsV1 = ZwpLinuxBufferParamsV1; + version = self.version; +} + +impl UsrObject for UsrZwpLinuxBufferParamsV1 { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }) + } +} diff --git a/src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs b/src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs new file mode 100644 index 00000000..f2017a51 --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_zwp_linux_dmabuf_v1.rs @@ -0,0 +1,67 @@ +use { + crate::{ + object::Version, + video::dmabuf::DmaBuf, + wire::{ZwpLinuxDmabufV1Id, zwp_linux_dmabuf_v1::*}, + wl_usr::{ + UsrCon, + usr_ifs::{ + usr_wl_buffer::UsrWlBuffer, + usr_zwp_linux_buffer_params_v1::UsrZwpLinuxBufferParamsV1, + }, + usr_object::UsrObject, + }, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrZwpLinuxDmabufV1 { + pub id: ZwpLinuxDmabufV1Id, + pub con: Rc, + pub version: Version, +} + +impl UsrZwpLinuxDmabufV1 { + #[expect(dead_code)] + pub fn create_buffer(&self, buffer: &DmaBuf) -> Rc { + let params = Rc::new(UsrZwpLinuxBufferParamsV1 { + id: self.con.id(), + con: self.con.clone(), + version: self.version, + }); + self.con.request(CreateParams { + self_id: self.id, + params_id: params.id, + }); + self.con.add_object(params.clone()); + for (idx, plane) in buffer.planes.iter().enumerate() { + params.add(&plane.fd, idx, plane.offset, plane.stride, buffer.modifier); + } + let obj = params.create_immed(buffer.width, buffer.height, &buffer.format); + self.con.remove_obj(&*params); + obj + } +} + +impl ZwpLinuxDmabufV1EventHandler for UsrZwpLinuxDmabufV1 { + type Error = Infallible; + + fn format(&self, _ev: Format, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } + + fn modifier(&self, _ev: Modifier, _slf: &Rc) -> Result<(), Self::Error> { + Ok(()) + } +} + +usr_object_base! { + self = UsrZwpLinuxDmabufV1 = ZwpLinuxDmabufV1; + version = self.version; +} + +impl UsrObject for UsrZwpLinuxDmabufV1 { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }) + } +} diff --git a/src/wl_usr/usr_ifs/usr_zwp_primary_selection_device_manager.rs b/src/wl_usr/usr_ifs/usr_zwp_primary_selection_device_manager.rs new file mode 100644 index 00000000..b978ba9d --- /dev/null +++ b/src/wl_usr/usr_ifs/usr_zwp_primary_selection_device_manager.rs @@ -0,0 +1,31 @@ +use { + crate::{ + object::Version, + wire::{ZwpPrimarySelectionDeviceManagerV1Id, zwp_primary_selection_device_manager_v1::*}, + wl_usr::{UsrCon, usr_object::UsrObject}, + }, + std::{convert::Infallible, rc::Rc}, +}; + +pub struct UsrZwpPrimarySelectionDeviceManagerV1 { + pub id: ZwpPrimarySelectionDeviceManagerV1Id, + pub con: Rc, + pub version: Version, +} + +impl UsrZwpPrimarySelectionDeviceManagerV1 {} + +impl ZwpPrimarySelectionDeviceManagerV1EventHandler for UsrZwpPrimarySelectionDeviceManagerV1 { + type Error = Infallible; +} + +usr_object_base! { + self = UsrZwpPrimarySelectionDeviceManagerV1 = ZwpPrimarySelectionDeviceManagerV1; + version = self.version; +} + +impl UsrObject for UsrZwpPrimarySelectionDeviceManagerV1 { + fn destroy(&self) { + self.con.request(Destroy { self_id: self.id }) + } +} diff --git a/src/xwayland.rs b/src/xwayland.rs index fe96464a..a5deb612 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -114,9 +114,13 @@ pub async fn manage(state: Rc) { log::error!("Could not listen on the Xwayland socket: {}", ErrorFmt(e)); return; } - let display = format!(":{}", xsocket.id); + let display = Rc::new(format!(":{}", xsocket.id)); forker.setenv(DISPLAY.as_bytes(), display.as_bytes()); - let _unsetenv = on_drop(|| forker.unsetenv(DISPLAY.as_bytes())); + state.xwayland.display.set(Some(display.clone())); + let _unsetenv = on_drop(|| { + forker.unsetenv(DISPLAY.as_bytes()); + state.xwayland.display.take(); + }); log::info!("Allocated display :{} for Xwayland", xsocket.id); log::info!("Waiting for connection attempt"); if state.backend.get().import_environment() { @@ -207,8 +211,10 @@ async fn run( state.ring.readable(&Rc::new(dfdread)).await?; state.xwayland.queue.clear(); state.xwayland.pidfd.set(Some(pidfd.clone())); + state.xwayland.client.set(Some(client.clone())); let _remove_pidfd = on_drop(|| { state.xwayland.pidfd.take(); + state.xwayland.client.take(); }); { let shared = Rc::new(XwmShared::default());