From 0f72c41b42d2cb6fcb92de955e223200c2621300 Mon Sep 17 00:00:00 2001 From: stephen Date: Sat, 4 Jan 2025 15:44:06 +0000 Subject: [PATCH] listen to output events from the wlr protocol --- Cargo.lock | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + src/lib.rs | 6 ++ src/listen.rs | 261 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 32 +++--- 5 files changed, 553 insertions(+), 13 deletions(-) create mode 100644 src/listen.rs diff --git a/Cargo.lock b/Cargo.lock index 8d03b43..3a4ed07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,52 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cc" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.14" @@ -32,6 +72,34 @@ dependencies = [ "winnow", ] +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" @@ -134,6 +202,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "proc-macro2" version = "1.0.92" @@ -143,6 +217,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.37" @@ -152,12 +235,31 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "serde" version = "1.0.216" @@ -190,6 +292,18 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "swayout" version = "1.2.2" @@ -197,6 +311,9 @@ dependencies = [ "kdl", "serde", "serde_json", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", "xdg", ] @@ -243,6 +360,152 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "wayland-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +dependencies = [ + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +dependencies = [ + "bitflags", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.6.20" diff --git a/Cargo.toml b/Cargo.toml index 353b746..cd25472 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,7 @@ serde_json = "1.0.133" kdl = "6.1.0" xdg = "2.5.2" + +wayland-client = "0.31.7" +wayland-protocols = { version = "0.32.5" , features = ["client", "unstable"] } +wayland-protocols-wlr = { version = "0.3.5", features = ["client"] } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index b1f1778..62b18d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ use crate::lid::is_lid_closed; mod sway; use crate::sway::{apply_outputs, get_outputs, Output}; +mod listen; + /// Determine the available layout names. /// Return one layout for each layout defined in the configuration file /// for which all outputs are available (and lids are not closed), @@ -240,3 +242,7 @@ pub fn apply_automatic() -> Result { Err("No automatic layouts available.".to_string()) } } + +pub fn listen() { + listen::listen() +} \ No newline at end of file diff --git a/src/listen.rs b/src/listen.rs new file mode 100644 index 0000000..27d4a16 --- /dev/null +++ b/src/listen.rs @@ -0,0 +1,261 @@ +use wayland_client::{event_created_child, protocol::wl_registry, Connection, Dispatch, QueueHandle}; +use wayland_client::protocol::wl_output::Transform; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::{AdaptiveSyncState, ZwlrOutputHeadV1}; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_manager_v1::ZwlrOutputManagerV1; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_head_v1::Event as HeadEvent; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::Event as ModeEvent; +use wayland_protocols_wlr::output_management::v1::client::zwlr_output_mode_v1::ZwlrOutputModeV1; + +// The state of this app. +struct AppState { + output_names: Vec, +} +impl AppState { + fn reset(&mut self) { + self.output_names.clear() + } + fn add_output(&mut self, output_name:String) { + self.output_names.push(output_name) + } +} + +// When we get the global registry event, use the registry to bind the ZwlrOutputManagerV1 to get output events +impl Dispatch for AppState { + fn event( + _state: &mut Self, + registry: &wl_registry::WlRegistry, + event: wl_registry::Event, + _data: &(), + _conn: &Connection, + qh: &QueueHandle, + ) { + // When receiving events from the wl_registry, we are only interested in the + // `global` event, which signals a new available global. + // When receiving this event, we just print its characteristics in this example. + if let wl_registry::Event::Global { name, interface, version } = event { + //println!("[{}] {} (v{})", name, interface, version); + match interface.as_str() { + "zwlr_output_manager_v1" => { + println!("found output manager"); + registry.bind::(name, version, qh, ()); + } + _ => {} + } + } + } +} + +// Handle ZwlrOutputManagerV1 events +impl Dispatch for AppState { + event_created_child!(AppState, ZwlrOutputManagerV1, [ + 0 => (ZwlrOutputHeadV1, ()) // How did I know that 0 was for ZwlrOutputHeadV1? + ]); + fn event( + state: &mut Self, + _: &ZwlrOutputManagerV1, + event: zwlr_output_manager_v1::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + zwlr_output_manager_v1::Event::Head { head } => { + println!("-=-= Head") + } + zwlr_output_manager_v1::Event::Finished => { + println!("-=-= Finished") + } + zwlr_output_manager_v1::Event::Done { serial } => { + print!("-=-= Done. Output names({}): ", &state.output_names.len()); + for output_name in &state.output_names { + print!("{},", output_name); + } + println!("."); + } + _ => {} + } + } +} +impl Dispatch for AppState { + event_created_child!(AppState, ZwlrOutputManagerV1, [ + 3 => (ZwlrOutputModeV1, ()) // How did I know that 3 is ZwlrOutputModeV1? + ]); + fn event( + state: &mut Self, + _: &ZwlrOutputHeadV1, + event: HeadEvent, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + //println!("headv1 op:{}", event.opcode()); + match event { + HeadEvent::Name { name } => { + println!("name: {}", &name); + state.add_output(name); + } + HeadEvent::Description { description} => { + // HeadEvent.Description is " ()" + println!("description: {}", description) + } + HeadEvent::PhysicalSize { width, height } => { + println!("size: {}x{}", width, height) + } + HeadEvent::Mode { mode } => { + //println!("mode") + } + HeadEvent::Enabled { enabled} => { + println!("enabled: {}", enabled) + } + HeadEvent::CurrentMode { mode } => { + println!("mode..."); + } + HeadEvent::Position { x, y } => { + println!("position: {},{}", x, y) + } + HeadEvent::Transform { transform } => { + let mut s = String::new(); + match transform.into_result() { + Ok(t) => { + s.push_str( + match t { + Transform::Normal => { "normal" } + Transform::_90 => { "90" } + Transform::_180 => { "180" } + Transform::_270 => { "270" } + Transform::Flipped => { "flipped" } + Transform::Flipped90 => { "flipped90" } + Transform::Flipped180 => { "flipped180" } + Transform::Flipped270 => { "flipped270" } + _ => { "wat?" } + } + ) + } + Err(e) => { + s.push_str(e.to_string().as_str()) + } + }; + println!("transform: {}", s) + } + HeadEvent::Scale { scale } => { + println!("scale: {}", scale) + } + HeadEvent::Finished => { + // This happens when something is disconnected + println!("finished") + } + // I never get Make + HeadEvent::Make { make } => { + println!("make: {}", make) + } + // I never get Model + HeadEvent::Model { model } => { + println!("model: {}", model) + } + // I never get SerialNumber + HeadEvent::SerialNumber { serial_number } => { + println!("serial: {}", serial_number) + } + HeadEvent::AdaptiveSync { state } => { + let mut s = String::new(); + match state.into_result() { + Ok(st) => { + s.push_str( + match st { + AdaptiveSyncState::Disabled => { "disabled" } + AdaptiveSyncState::Enabled => { "enabled" } + _ => { "wat?" } + } + ) + } + Err(e) => { + s.push_str(e.to_string().as_str()) + } + }; + println!("adaptive sync: {}", s) + } + _ => { + println!("head unhandled {}", event.opcode()) + + } + } + } +} +impl Dispatch for AppState { + fn event( + _state: &mut Self, + _: &ZwlrOutputModeV1, + event: ModeEvent, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + ModeEvent::Size { width, height } => { + //println!("mode size: {}x{}", width, height) + } + ModeEvent::Refresh { refresh } => { + //println!("mode refresh: {}", refresh) + } + ModeEvent::Preferred => { + //println!("mode preferred") + } + ModeEvent::Finished => { + //println!("mode finished") + } + _ => { + println!("mode unhandled {}", event.opcode()) + } + } + + } +} + +// The main function of our program +pub fn listen() { + // Create a Wayland connection by connecting to the server through the + // environment-provided configuration. + let conn = Connection::connect_to_env().unwrap(); + + // Retrieve the WlDisplay Wayland object from the connection. This object is + // the starting point of any Wayland program, from which all other objects will + // be created. + let display = conn.display(); + + + // Create an event queue for our event processing + let mut event_queue = conn.new_event_queue(); + // And get its handle to associate new objects to it + let qh = event_queue.handle(); + + // Create a wl_registry object by sending the wl_display.get_registry request. + // This method takes two arguments: a handle to the queue that the newly created + // wl_registry will be assigned to, and the user-data that should be associated + // with this registry (here it is () as we don't need user-data). + let _registry = display.get_registry(&qh, ()); + + // At this point everything is ready, and we just need to wait to receive the events + // from the wl_registry. Our callback will print the advertised globals. + println!("Advertised globals:"); + + // To actually receive the events, we invoke the `roundtrip` method. This method + // is special and you will generally only invoke it during the setup of your program: + // it will block until the server has received and processed all the messages you've + // sent up to now. + // + // In our case, that means it'll block until the server has received our + // wl_display.get_registry request, and as a reaction has sent us a batch of + // wl_registry.global events. + // + // `roundtrip` will then empty the internal buffer of the queue it has been invoked + // on, and thus invoke our `Dispatch` implementation that prints the list of advertised + // globals. + let mut state = AppState { output_names : Vec::new() }; + + event_queue.roundtrip(&mut state).unwrap(); + + loop { + event_queue.blocking_dispatch(&mut state).unwrap(); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c0c2133..514df3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,21 +6,27 @@ fn main() { // first arg is executable swayout::print_layout_names(); } else if args.len() == 2 { - let arg = &args[1]; - if arg == "--automatic" { - match swayout::apply_automatic() { - Ok(layout_name) => { - println!("{}", layout_name); - } - Err(e) => { - eprintln!("{}", e); - process::exit(2) + let arg = args[1].as_str(); + match arg { + "--automatic" => { + match swayout::apply_automatic() { + Ok(layout_name) => { + println!("{}", layout_name); + } + Err(e) => { + eprintln!("{}", e); + process::exit(2) + } } + }, + "--listen" => { + swayout::listen() } - } else { - if let Err(e) = swayout::apply_layout(&args[1]) { - eprintln!("{}", e); - process::exit(3) + _ => { + if let Err(e) = swayout::apply_layout(&args[1]) { + eprintln!("{}", e); + process::exit(3) + } } } } else {