diff --git a/Cargo.lock b/Cargo.lock index af1b517..19427e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,6 +373,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.153" @@ -432,12 +438,28 @@ dependencies = [ "libc", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pango" version = "0.18.3" @@ -616,6 +638,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "slab" version = "0.4.9" @@ -709,6 +740,16 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "toml" version = "0.8.2" @@ -743,6 +784,63 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -755,6 +853,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version-compare" version = "0.2.0" @@ -857,6 +961,8 @@ version = "0.2.2" dependencies = [ "clap", "libwaysip", + "tracing", + "tracing-subscriber", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e77d3c7..bcee70d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ readme = "README.md" [workspace.dependencies] libwaysip = { version = "0.2.2", path = "./libwaysip" } +tracing = "0.1" diff --git a/libwaysip/src/dispatch.rs b/libwaysip/src/dispatch.rs new file mode 100644 index 0000000..33909c1 --- /dev/null +++ b/libwaysip/src/dispatch.rs @@ -0,0 +1,326 @@ +use crate::render::*; +use crate::state::{self, LayerSurfaceInfo, SecondState}; +use wayland_client::protocol::wl_output; +use wayland_client::{ + delegate_noop, + globals::{registry_queue_init, GlobalListContents}, + protocol::{ + wl_buffer::WlBuffer, + wl_compositor::WlCompositor, + wl_keyboard, wl_pointer, wl_registry, + wl_seat::{self, WlSeat}, + wl_shm::{self, WlShm}, + wl_shm_pool::WlShmPool, + wl_surface::WlSurface, + }, + Connection, Dispatch, Proxy, WEnum, +}; + +use wayland_protocols::xdg::shell::client::{xdg_toplevel::XdgToplevel, xdg_wm_base}; + +use wayland_protocols::xdg::xdg_output::zv1::client::{ + zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1, +}; + +use wayland_protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1}, + zwlr_layer_surface_v1::{self, Anchor, ZwlrLayerSurfaceV1}, +}; + +use wayland_protocols::wp::cursor_shape::v1::client::{ + wp_cursor_shape_device_v1::{self, WpCursorShapeDeviceV1}, + wp_cursor_shape_manager_v1::WpCursorShapeManagerV1, +}; + +use wayland_cursor::CursorImageBuffer; +use wayland_cursor::CursorTheme; + +impl Dispatch for SecondState { + fn event( + state: &mut Self, + surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event { + surface.ack_configure(serial); + let Some(LayerSurfaceInfo { + wl_surface, buffer, .. + }) = state.wl_surfaces.iter().find(|info| info.layer == *surface) + else { + return; + }; + wl_surface.attach(Some(buffer), 0, 0); + wl_surface.commit(); + } + } +} + +impl Dispatch for SecondState { + fn event( + state: &mut Self, + proxy: &zxdg_output_v1::ZxdgOutputV1, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + let Some(info) = state + .zxdgoutputs + .iter_mut() + .find(|info| info.zxdgoutput == *proxy) + else { + return; + }; + match event { + zxdg_output_v1::Event::LogicalSize { width, height } => { + info.height = height; + info.width = width; + } + zxdg_output_v1::Event::LogicalPosition { x, y } => { + info.start_x = x; + info.start_y = y; + } + zxdg_output_v1::Event::Name { name } => info.name = name, + zxdg_output_v1::Event::Description { description } => info.description = description, + _ => {} + } + } +} + +// so interesting, it is just need to invoke once, it just used to get the globals +impl Dispatch for state::BaseState { + fn event( + _state: &mut Self, + _proxy: &wl_registry::WlRegistry, + _event: ::Event, + _data: &GlobalListContents, + _conn: &Connection, + _qh: &wayland_client::QueueHandle, + ) { + } +} + +impl Dispatch for state::SecondState { + fn event( + state: &mut Self, + proxy: &wl_registry::WlRegistry, + event: ::Event, + _data: &(), + _conn: &Connection, + qh: &wayland_client::QueueHandle, + ) { + let wl_registry::Event::Global { + name, + interface, + version, + } = event + else { + return; + }; + + if interface == wl_output::WlOutput::interface().name { + let output = proxy.bind::(name, version, qh, ()); + state.outputs.push(state::WlOutputInfo::new(output)); + } + } +} + +impl Dispatch for state::SecondState { + fn event( + state: &mut Self, + wl_output: &wl_output::WlOutput, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + let output = state + .outputs + .iter_mut() + .find(|x| x.get_output() == wl_output) + .unwrap(); + + match event { + wl_output::Event::Name { name } => { + output.name = name; + } + wl_output::Event::Description { description } => { + output.description = description; + } + wl_output::Event::Mode { width, height, .. } => { + output.size = (width, height); + } + + _ => (), + } + } +} + +impl Dispatch for state::SecondState { + fn event( + _state: &mut Self, + wm_base: &xdg_wm_base::XdgWmBase, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + if let xdg_wm_base::Event::Ping { serial } = event { + wm_base.pong(serial); + } + } +} + +impl Dispatch for state::SecondState { + fn event( + _state: &mut Self, + seat: &wl_seat::WlSeat, + event: ::Event, + _data: &(), + _conn: &Connection, + qh: &wayland_client::QueueHandle, + ) { + if let wl_seat::Event::Capabilities { + capabilities: WEnum::Value(capabilities), + } = event + { + if capabilities.contains(wl_seat::Capability::Keyboard) { + seat.get_keyboard(qh, ()); + } + if capabilities.contains(wl_seat::Capability::Pointer) { + seat.get_pointer(qh, ()); + } + } + } +} + +impl Dispatch for state::SecondState { + fn event( + state: &mut Self, + _proxy: &wl_keyboard::WlKeyboard, + event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &wayland_client::QueueHandle, + ) { + if let wl_keyboard::Event::Key { key, .. } = event { + if key == 1 { + state.running = false; + } + } + } +} + +impl Dispatch for state::SecondState { + fn event( + dispatch_state: &mut Self, + pointer: &wl_pointer::WlPointer, + event: ::Event, + _data: &(), + _conn: &Connection, + qh: &wayland_client::QueueHandle, + ) { + match event { + wl_pointer::Event::Button { state, .. } => { + match state { + WEnum::Value(wl_pointer::ButtonState::Pressed) => { + dispatch_state.start_pos = Some(dispatch_state.current_pos); + if !dispatch_state.is_area() { + dispatch_state.end_pos = Some(dispatch_state.current_pos); + dispatch_state.running = false; + } + } + WEnum::Value(wl_pointer::ButtonState::Released) => { + dispatch_state.end_pos = Some(dispatch_state.current_pos); + // if released, this time select is end + dispatch_state.running = false; + } + _ => {} + } + dispatch_state.redraw(); + } + wl_pointer::Event::Enter { + serial, + surface, + surface_x, + surface_y, + } => { + let Some(LayerSurfaceInfo { + cursor_suface, + cursor_buffer, + .. + }) = dispatch_state + .wl_surfaces + .iter() + .find(|info| info.wl_surface == surface) + else { + return; + }; + let current_screen = dispatch_state + .wl_surfaces + .iter() + .position(|info| info.wl_surface == surface) + .unwrap(); + dispatch_state.current_screen = current_screen; + let start_x = dispatch_state.zxdgoutputs[dispatch_state.current_screen].start_x; + let start_y = dispatch_state.zxdgoutputs[dispatch_state.current_screen].start_y; + dispatch_state.current_pos = + (surface_x + start_x as f64, surface_y + start_y as f64); + + if let Some(ref cursor_manager) = dispatch_state.cursor_manager { + let device = cursor_manager.get_pointer(pointer, qh, ()); + device.set_shape(serial, wp_cursor_shape_device_v1::Shape::Crosshair); + device.destroy(); + } else { + let cursor_buffer = cursor_buffer.as_ref().unwrap(); + cursor_suface.attach(Some(cursor_buffer), 0, 0); + let (hotspot_x, hotspot_y) = cursor_buffer.hotspot(); + pointer.set_cursor( + serial, + Some(cursor_suface), + hotspot_x as i32, + hotspot_y as i32, + ); + cursor_suface.commit(); + } + if dispatch_state.is_screen() { + dispatch_state.redraw_screen(); + } else { + dispatch_state.redraw(); + } + } + wl_pointer::Event::Motion { + surface_x, + surface_y, + .. + } => { + let start_x = dispatch_state.zxdgoutputs[dispatch_state.current_screen].start_x; + let start_y = dispatch_state.zxdgoutputs[dispatch_state.current_screen].start_y; + dispatch_state.current_pos = + (surface_x + start_x as f64, surface_y + start_y as f64); + if dispatch_state.is_area() { + dispatch_state.redraw(); + } + } + _ => {} + } + } +} + +delegate_noop!(SecondState: ignore WlCompositor); // WlCompositor is need to create a surface +delegate_noop!(SecondState: ignore WlSurface); // surface is the base needed to show buffer + // +delegate_noop!(SecondState: ignore WlShm); // shm is used to create buffer pool +delegate_noop!(SecondState: ignore XdgToplevel); // so it is the same with layer_shell, private a + // place for surface +delegate_noop!(SecondState: ignore WlShmPool); // so it is pool, created by wl_shm +delegate_noop!(SecondState: ignore WlBuffer); // buffer show the picture +delegate_noop!(SecondState: ignore ZwlrLayerShellV1); // it is simillar with xdg_toplevel, also the + // ext-session-shell +delegate_noop!(SecondState: ignore ZxdgOutputManagerV1); + +delegate_noop!(SecondState: ignore WpCursorShapeManagerV1); +delegate_noop!(SecondState: ignore WpCursorShapeDeviceV1); diff --git a/libwaysip/src/error.rs b/libwaysip/src/error.rs index 269408f..f5a66a1 100644 --- a/libwaysip/src/error.rs +++ b/libwaysip/src/error.rs @@ -8,12 +8,12 @@ use wayland_client::{globals::BindError, DispatchError}; /// 4. when not get the cursor #[derive(Error, Debug)] pub enum WaySipError { - #[error("Init Failed")] + #[error("Failed to initialize app state")] InitFailed(String), - #[error("Error during queue")] + #[error("Wayland dispatch failed!")] DispatchError(DispatchError), - #[error("Not supported protocol")] + #[error("Protocol not supported")] NotSupportedProtocol(BindError), #[error("Cannot get cursor")] - NotGetCursorTheme + CursorThemeFetchFailed, } diff --git a/libwaysip/src/lib.rs b/libwaysip/src/lib.rs index 6194658..9a375b7 100644 --- a/libwaysip/src/lib.rs +++ b/libwaysip/src/lib.rs @@ -1,18 +1,20 @@ +mod dispatch; mod error; mod render; +pub mod state; + pub use error::WaySipError; use std::os::unix::prelude::AsFd; +use wayland_client::protocol::wl_output; use wayland_client::{ delegate_noop, globals::{registry_queue_init, GlobalListContents}, protocol::{ wl_buffer::WlBuffer, wl_compositor::WlCompositor, - wl_keyboard, - wl_output::{self, WlOutput}, - wl_pointer, wl_registry, + wl_keyboard, wl_pointer, wl_registry, wl_seat::{self, WlSeat}, wl_shm::{self, WlShm}, wl_shm_pool::WlShmPool, @@ -39,587 +41,6 @@ use wayland_protocols::wp::cursor_shape::v1::client::{ use wayland_cursor::CursorImageBuffer; use wayland_cursor::CursorTheme; - -#[derive(Debug)] -struct BaseState; - -// so interesting, it is just need to invoke once, it just used to get the globals -impl Dispatch for BaseState { - fn event( - _state: &mut Self, - _proxy: &wl_registry::WlRegistry, - _event: ::Event, - _data: &GlobalListContents, - _conn: &Connection, - _qh: &wayland_client::QueueHandle, - ) { - } -} - -#[derive(Debug)] -struct ZXdgOutputInfo { - zxdgoutput: zxdg_output_v1::ZxdgOutputV1, - width: i32, - height: i32, - start_x: i32, - start_y: i32, - name: String, - description: String, -} - -#[derive(Debug, Clone)] -pub struct WlOutputInfo { - output: WlOutput, - description: String, - name: String, - size: (i32, i32), -} - -impl WlOutputInfo { - fn new(output: WlOutput) -> Self { - Self { - output, - description: "".to_string(), - name: "".to_string(), - size: (0, 0), - } - } - - fn get_output(&self) -> &WlOutput { - &self.output - } - - pub fn get_size(&self) -> (i32, i32) { - self.size - } - - pub fn get_name(&self) -> &str { - &self.name - } - - pub fn get_description(&self) -> &str { - &self.description - } -} - -impl ZXdgOutputInfo { - fn new(zxdgoutput: zxdg_output_v1::ZxdgOutputV1) -> Self { - Self { - zxdgoutput, - width: 0, - height: 0, - start_x: 0, - start_y: 0, - name: "".to_string(), - description: "".to_string(), - } - } - fn get_screen_info(&self, output_info: WlOutputInfo) -> ScreenInfo { - ScreenInfo { - output_info, - start_x: self.start_x, - start_y: self.start_y, - width: self.width, - height: self.height, - name: self.name.clone(), - description: self.description.clone(), - } - } -} - -/// tell the screen info, include description, size and the name. and include the current wloutput -/// binded by the screen -#[derive(Debug)] -pub struct ScreenInfo { - output_info: WlOutputInfo, - start_x: i32, - start_y: i32, - width: i32, - height: i32, - name: String, - description: String, -} - -impl ScreenInfo { - /// get the binding output - pub fn get_outputinfo(&self) -> &WlOutputInfo { - &self.output_info - } - - /// get the logical size of the screen - pub fn get_size(&self) -> (i32, i32) { - (self.width, self.height) - } - - /// get the name of the screen - pub fn get_name(&self) -> &str { - &self.name - } - - /// get the description of the screen - pub fn get_description(&self) -> &str { - &self.description - } - - /// get the logical positon of the screen - pub fn get_position(&self) -> (i32, i32) { - (self.start_x, self.start_y) - } -} - -#[derive(Debug)] -struct LayerSurfaceInfo { - layer: ZwlrLayerSurfaceV1, - wl_surface: WlSurface, - cursor_suface: WlSurface, - buffer: WlBuffer, - cursor_buffer: Option, - cairo_t: cairo::Context, -} - -/// You are allow to choose three actions of waysip, include area selection, point selection, and -/// select sreen -#[derive(Debug, Clone, Copy, Default)] -pub enum WaySipKind { - #[default] - Area, - Point, - Screen, -} - -#[derive(Debug)] -struct SecondState { - outputs: Vec, - zxdgoutputs: Vec, - running: bool, - waysipkind: WaySipKind, - wl_surfaces: Vec, - current_pos: (f64, f64), - start_pos: Option<(f64, f64)>, - end_pos: Option<(f64, f64)>, - current_screen: usize, - cursor_manager: Option, -} - -impl SecondState { - fn new(waysipkind: WaySipKind) -> Self { - SecondState { - outputs: Vec::new(), - zxdgoutputs: Vec::new(), - running: true, - waysipkind, - wl_surfaces: Vec::new(), - current_pos: (0., 0.), - start_pos: None, - end_pos: None, - current_screen: 0, - cursor_manager: None, - } - } - - fn is_area(&self) -> bool { - matches!(self.waysipkind, WaySipKind::Area) - } - - fn is_screen(&self) -> bool { - matches!(self.waysipkind, WaySipKind::Screen) - } -} - -/// describe the information of the area -#[derive(Debug)] -pub struct AreaInfo { - pub start_x: f64, - pub start_y: f64, - pub end_x: f64, - pub end_y: f64, - - pub screen_info: ScreenInfo, -} - -impl AreaInfo { - /// provide the width of the area as f64 - pub fn width_f64(&self) -> f64 { - (self.end_x - self.start_x).abs() - } - - /// provide the width of the area as i32 - pub fn width(&self) -> i32 { - self.width_f64() as i32 - } - - /// provide the height of the area as f64 - pub fn height_f64(&self) -> f64 { - (self.end_y - self.start_y).abs() - } - - /// provide the width of the area as i32 - pub fn height(&self) -> i32 { - self.height_f64() as i32 - } - - /// caculate the real start position - pub fn left_top_point(&self) -> (i32, i32) { - ( - self.start_x.min(self.end_x) as i32, - (self.start_y.min(self.end_y)) as i32, - ) - } - - /// you can get the info of the choosed screen - pub fn selected_screen_info(&self) -> &ScreenInfo { - &self.screen_info - } -} - -impl SecondState { - fn redraw_screen(&self) { - for ( - (index, info), - ZXdgOutputInfo { - width, - height, - start_x, - start_y, - name, - description, - .. - }, - ) in self - .wl_surfaces - .iter() - .enumerate() - .zip(self.zxdgoutputs.iter()) - { - info.redraw_select_screen( - self.current_screen == index, - (*width, *height), - (*start_x, *start_y), - name, - description, - ); - } - } - fn redraw(&self) { - if self.start_pos.is_none() { - return; - } - let (pos_x, pos_y) = self.start_pos.unwrap(); - for ( - ZXdgOutputInfo { - width, - height, - start_x, - start_y, - .. - }, - layershell_info, - ) in self.zxdgoutputs.iter().zip(self.wl_surfaces.iter()) - { - layershell_info.redraw( - (pos_x, pos_y), - self.current_pos, - (*start_x, *start_y), - (*width, *height), - ); - } - } - - fn area_info(&self) -> Option { - if self.start_pos.is_none() || self.end_pos.is_none() { - return None; - } - let (start_x, start_y) = self.start_pos.unwrap(); - let (end_x, end_y) = self.end_pos.unwrap(); - let output = self.outputs[self.current_screen].clone(); - let info = &self.zxdgoutputs[self.current_screen]; - Some(AreaInfo { - start_x, - start_y, - end_x, - end_y, - screen_info: info.get_screen_info(output), - }) - } -} - -impl Dispatch for SecondState { - fn event( - state: &mut Self, - proxy: &wl_registry::WlRegistry, - event: ::Event, - _data: &(), - _conn: &Connection, - qh: &wayland_client::QueueHandle, - ) { - let wl_registry::Event::Global { - name, - interface, - version, - } = event - else { - return; - }; - - if interface == wl_output::WlOutput::interface().name { - let output = proxy.bind::(name, version, qh, ()); - state.outputs.push(WlOutputInfo::new(output)); - } - } -} - -impl Dispatch for SecondState { - fn event( - state: &mut Self, - wl_output: &wl_output::WlOutput, - event: ::Event, - _data: &(), - _conn: &Connection, - _qhandle: &wayland_client::QueueHandle, - ) { - let output = state - .outputs - .iter_mut() - .find(|x| x.get_output() == wl_output) - .unwrap(); - - match event { - wl_output::Event::Name { name } => { - output.name = name; - } - wl_output::Event::Description { description } => { - output.description = description; - } - wl_output::Event::Mode { width, height, .. } => { - output.size = (width, height); - } - - _ => (), - } - } -} - -impl Dispatch for SecondState { - fn event( - _state: &mut Self, - wm_base: &xdg_wm_base::XdgWmBase, - event: ::Event, - _data: &(), - _conn: &Connection, - _qhandle: &wayland_client::QueueHandle, - ) { - if let xdg_wm_base::Event::Ping { serial } = event { - wm_base.pong(serial); - } - } -} - -impl Dispatch for SecondState { - fn event( - _state: &mut Self, - seat: &wl_seat::WlSeat, - event: ::Event, - _data: &(), - _conn: &Connection, - qh: &wayland_client::QueueHandle, - ) { - if let wl_seat::Event::Capabilities { - capabilities: WEnum::Value(capabilities), - } = event - { - if capabilities.contains(wl_seat::Capability::Keyboard) { - seat.get_keyboard(qh, ()); - } - if capabilities.contains(wl_seat::Capability::Pointer) { - seat.get_pointer(qh, ()); - } - } - } -} - -impl Dispatch for SecondState { - fn event( - state: &mut Self, - _proxy: &wl_keyboard::WlKeyboard, - event: ::Event, - _data: &(), - _conn: &Connection, - _qhandle: &wayland_client::QueueHandle, - ) { - if let wl_keyboard::Event::Key { key, .. } = event { - if key == 1 { - state.running = false; - } - } - } -} - -impl Dispatch for SecondState { - fn event( - dispatch_state: &mut Self, - pointer: &wl_pointer::WlPointer, - event: ::Event, - _data: &(), - _conn: &Connection, - qh: &wayland_client::QueueHandle, - ) { - match event { - wl_pointer::Event::Button { state, .. } => { - match state { - WEnum::Value(wl_pointer::ButtonState::Pressed) => { - dispatch_state.start_pos = Some(dispatch_state.current_pos); - if !dispatch_state.is_area() { - dispatch_state.end_pos = Some(dispatch_state.current_pos); - dispatch_state.running = false; - } - } - WEnum::Value(wl_pointer::ButtonState::Released) => { - dispatch_state.end_pos = Some(dispatch_state.current_pos); - // if released, this time select is end - dispatch_state.running = false; - } - _ => {} - } - dispatch_state.redraw(); - } - wl_pointer::Event::Enter { - serial, - surface, - surface_x, - surface_y, - } => { - let Some(LayerSurfaceInfo { - cursor_suface, - cursor_buffer, - .. - }) = dispatch_state - .wl_surfaces - .iter() - .find(|info| info.wl_surface == surface) - else { - return; - }; - let current_screen = dispatch_state - .wl_surfaces - .iter() - .position(|info| info.wl_surface == surface) - .unwrap(); - dispatch_state.current_screen = current_screen; - let start_x = dispatch_state.zxdgoutputs[dispatch_state.current_screen].start_x; - let start_y = dispatch_state.zxdgoutputs[dispatch_state.current_screen].start_y; - dispatch_state.current_pos = - (surface_x + start_x as f64, surface_y + start_y as f64); - - if let Some(ref cursor_manager) = dispatch_state.cursor_manager { - let device = cursor_manager.get_pointer(pointer, qh, ()); - device.set_shape(serial, wp_cursor_shape_device_v1::Shape::Crosshair); - device.destroy(); - } else { - let cursor_buffer = cursor_buffer.as_ref().unwrap(); - cursor_suface.attach(Some(cursor_buffer), 0, 0); - let (hotspot_x, hotspot_y) = cursor_buffer.hotspot(); - pointer.set_cursor( - serial, - Some(cursor_suface), - hotspot_x as i32, - hotspot_y as i32, - ); - cursor_suface.commit(); - } - if dispatch_state.is_screen() { - dispatch_state.redraw_screen(); - } else { - dispatch_state.redraw(); - } - } - wl_pointer::Event::Motion { - surface_x, - surface_y, - .. - } => { - let start_x = dispatch_state.zxdgoutputs[dispatch_state.current_screen].start_x; - let start_y = dispatch_state.zxdgoutputs[dispatch_state.current_screen].start_y; - dispatch_state.current_pos = - (surface_x + start_x as f64, surface_y + start_y as f64); - if dispatch_state.is_area() { - dispatch_state.redraw(); - } - } - _ => {} - } - } -} - -impl Dispatch for SecondState { - fn event( - state: &mut Self, - surface: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, - event: ::Event, - _data: &(), - _conn: &Connection, - _qhandle: &wayland_client::QueueHandle, - ) { - if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event { - surface.ack_configure(serial); - let Some(LayerSurfaceInfo { - wl_surface, buffer, .. - }) = state.wl_surfaces.iter().find(|info| info.layer == *surface) - else { - return; - }; - wl_surface.attach(Some(buffer), 0, 0); - wl_surface.commit(); - } - } -} - -impl Dispatch for SecondState { - fn event( - state: &mut Self, - proxy: &zxdg_output_v1::ZxdgOutputV1, - event: ::Event, - _data: &(), - _conn: &Connection, - _qhandle: &wayland_client::QueueHandle, - ) { - let Some(info) = state - .zxdgoutputs - .iter_mut() - .find(|info| info.zxdgoutput == *proxy) - else { - return; - }; - match event { - zxdg_output_v1::Event::LogicalSize { width, height } => { - info.height = height; - info.width = width; - } - zxdg_output_v1::Event::LogicalPosition { x, y } => { - info.start_x = x; - info.start_y = y; - } - zxdg_output_v1::Event::Name { name } => info.name = name, - zxdg_output_v1::Event::Description { description } => info.description = description, - _ => {} - } - } -} - -delegate_noop!(SecondState: ignore WlCompositor); // WlCompositor is need to create a surface -delegate_noop!(SecondState: ignore WlSurface); // surface is the base needed to show buffer - // -delegate_noop!(SecondState: ignore WlShm); // shm is used to create buffer pool -delegate_noop!(SecondState: ignore XdgToplevel); // so it is the same with layer_shell, private a - // place for surface -delegate_noop!(SecondState: ignore WlShmPool); // so it is pool, created by wl_shm -delegate_noop!(SecondState: ignore WlBuffer); // buffer show the picture -delegate_noop!(SecondState: ignore ZwlrLayerShellV1); // it is simillar with xdg_toplevel, also the - // ext-session-shell -delegate_noop!(SecondState: ignore ZxdgOutputManagerV1); - -delegate_noop!(SecondState: ignore WpCursorShapeManagerV1); -delegate_noop!(SecondState: ignore WpCursorShapeDeviceV1); - fn get_cursor_buffer(connection: &Connection, shm: &WlShm) -> Option { let mut cursor_theme = CursorTheme::load(connection, shm.clone(), 23).ok()?; let mut cursor = cursor_theme.get_cursor("crosshair"); @@ -630,21 +51,21 @@ fn get_cursor_buffer(connection: &Connection, shm: &WlShm) -> Option Result, WaySipError> { +pub fn get_area(kind: state::WaySipKind) -> Result, WaySipError> { let connection = Connection::connect_to_env().map_err(|e| WaySipError::InitFailed(e.to_string()))?; - let (globals, _) = registry_queue_init::(&connection) + let (globals, _) = registry_queue_init::(&connection) .map_err(|e| WaySipError::InitFailed(e.to_string()))?; // We just need the // global, the // event_queue is // not needed, we // do not need - // BaseState after + // state::BaseState after // this anymore - let mut state = SecondState::new(kind); + let mut state = state::SecondState::new(kind); - let mut event_queue = connection.new_event_queue::(); + let mut event_queue = connection.new_event_queue::(); let qh = event_queue.handle(); let wmcompositer = globals @@ -664,7 +85,7 @@ pub fn get_area(kind: WaySipKind) -> Result, WaySipError> { let cursor_buffer = get_cursor_buffer(&connection, &shm); if cursor_manager.is_none() && cursor_buffer.is_none() { - return Err(WaySipError::NotGetCursorTheme); + return Err(WaySipError::CursorThemeFetchFailed); } state.cursor_manager = cursor_manager; @@ -686,7 +107,9 @@ pub fn get_area(kind: WaySipKind) -> Result, WaySipError> { for wloutput in state.outputs.iter() { let zwloutput = xdg_output_manager.get_xdg_output(wloutput.get_output(), &qh, ()); - state.zxdgoutputs.push(ZXdgOutputInfo::new(zwloutput)); + state + .zxdgoutputs + .push(state::ZXdgOutputInfo::new(zwloutput)); } event_queue @@ -747,7 +170,7 @@ pub fn get_area(kind: WaySipKind) -> Result, WaySipError> { ); let cursor_suface = wmcompositer.create_surface(&qh, ()); // and create a surface. if two or more, - state.wl_surfaces.push(LayerSurfaceInfo { + state.wl_surfaces.push(state::LayerSurfaceInfo { layer, wl_surface, cursor_suface, diff --git a/libwaysip/src/render.rs b/libwaysip/src/render.rs index cbcd0ea..38a08f6 100644 --- a/libwaysip/src/render.rs +++ b/libwaysip/src/render.rs @@ -2,7 +2,7 @@ use cairo::{Context, Format}; use memmap2::MmapMut; use std::fs::File; -use super::LayerSurfaceInfo; +use super::state::LayerSurfaceInfo; const FONT_FAMILY: &str = "Sans"; const FONT_SIZE: i32 = 20; diff --git a/libwaysip/src/state.rs b/libwaysip/src/state.rs new file mode 100644 index 0000000..3c3883e --- /dev/null +++ b/libwaysip/src/state.rs @@ -0,0 +1,325 @@ +use crate::render::*; +use wayland_client::protocol::wl_output; +use wayland_client::{ + delegate_noop, + globals::{registry_queue_init, GlobalListContents}, + protocol::{ + wl_buffer::WlBuffer, + wl_compositor::WlCompositor, + wl_keyboard, wl_pointer, wl_registry, + wl_seat::{self, WlSeat}, + wl_shm::{self, WlShm}, + wl_shm_pool::WlShmPool, + wl_surface::WlSurface, + }, + Connection, Dispatch, Proxy, WEnum, +}; + +use wayland_protocols::xdg::shell::client::{xdg_toplevel::XdgToplevel, xdg_wm_base}; + +use wayland_protocols::xdg::xdg_output::zv1::client::{ + zxdg_output_manager_v1::ZxdgOutputManagerV1, zxdg_output_v1, +}; + +use wayland_protocols_wlr::layer_shell::v1::client::{ + zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1}, + zwlr_layer_surface_v1::{self, Anchor, ZwlrLayerSurfaceV1}, +}; + +use wayland_protocols::wp::cursor_shape::v1::client::{ + wp_cursor_shape_device_v1::{self, WpCursorShapeDeviceV1}, + wp_cursor_shape_manager_v1::WpCursorShapeManagerV1, +}; + +use wayland_cursor::CursorImageBuffer; +use wayland_cursor::CursorTheme; + +use wayland_client::protocol::wl_output::WlOutput; + +/// You are allow to choose three actions of waysip, include area selection, point selection, and +/// select sreen +#[derive(Debug, Clone, Copy, Default)] +pub enum WaySipKind { + #[default] + Area, + Point, + Screen, +} + +#[derive(Debug)] +pub struct BaseState; + +#[derive(Debug)] +pub struct ZXdgOutputInfo { + pub zxdgoutput: zxdg_output_v1::ZxdgOutputV1, + pub width: i32, + pub height: i32, + pub start_x: i32, + pub start_y: i32, + pub name: String, + pub description: String, +} + +impl ZXdgOutputInfo { + pub fn new(zxdgoutput: zxdg_output_v1::ZxdgOutputV1) -> Self { + Self { + zxdgoutput, + width: 0, + height: 0, + start_x: 0, + start_y: 0, + name: "".to_string(), + description: "".to_string(), + } + } + pub fn get_screen_info(&self, output_info: WlOutputInfo) -> ScreenInfo { + ScreenInfo { + output_info, + start_x: self.start_x, + start_y: self.start_y, + width: self.width, + height: self.height, + name: self.name.clone(), + description: self.description.clone(), + } + } +} + +#[derive(Debug, Clone)] +pub struct WlOutputInfo { + pub output: WlOutput, + pub description: String, + pub name: String, + pub size: (i32, i32), +} + +impl WlOutputInfo { + pub fn new(output: WlOutput) -> Self { + Self { + output, + description: "".to_string(), + name: "".to_string(), + size: (0, 0), + } + } + + pub fn get_output(&self) -> &WlOutput { + &self.output + } + + pub fn get_size(&self) -> (i32, i32) { + self.size + } + + pub fn get_name(&self) -> &str { + &self.name + } + + pub fn get_description(&self) -> &str { + &self.description + } +} + +/// tell the screen info, include description, size and the name. and include the current wloutput +/// binded by the screen +#[derive(Debug)] +pub struct ScreenInfo { + pub output_info: WlOutputInfo, + pub start_x: i32, + pub start_y: i32, + pub width: i32, + pub height: i32, + pub name: String, + pub description: String, +} + +impl ScreenInfo { + /// get the binding output + pub fn get_outputinfo(&self) -> &WlOutputInfo { + &self.output_info + } + + /// get the logical size of the screen + pub fn get_size(&self) -> (i32, i32) { + (self.width, self.height) + } + + /// get the name of the screen + pub fn get_name(&self) -> &str { + &self.name + } + + /// get the description of the screen + pub fn get_description(&self) -> &str { + &self.description + } + + /// get the logical positon of the screen + pub fn get_position(&self) -> (i32, i32) { + (self.start_x, self.start_y) + } +} + +#[derive(Debug)] +pub struct SecondState { + pub outputs: Vec, + pub zxdgoutputs: Vec, + pub running: bool, + pub waysipkind: WaySipKind, + pub wl_surfaces: Vec, + pub current_pos: (f64, f64), + pub start_pos: Option<(f64, f64)>, + pub end_pos: Option<(f64, f64)>, + pub current_screen: usize, + pub cursor_manager: Option, +} + +impl SecondState { + pub fn new(waysipkind: WaySipKind) -> Self { + SecondState { + outputs: Vec::new(), + zxdgoutputs: Vec::new(), + running: true, + waysipkind, + wl_surfaces: Vec::new(), + current_pos: (0., 0.), + start_pos: None, + end_pos: None, + current_screen: 0, + cursor_manager: None, + } + } + + pub fn is_area(&self) -> bool { + matches!(self.waysipkind, WaySipKind::Area) + } + + pub fn is_screen(&self) -> bool { + matches!(self.waysipkind, WaySipKind::Screen) + } + + pub fn redraw_screen(&self) { + for ( + (index, info), + ZXdgOutputInfo { + width, + height, + start_x, + start_y, + name, + description, + .. + }, + ) in self + .wl_surfaces + .iter() + .enumerate() + .zip(self.zxdgoutputs.iter()) + { + info.redraw_select_screen( + self.current_screen == index, + (*width, *height), + (*start_x, *start_y), + name, + description, + ); + } + } + pub fn redraw(&self) { + if self.start_pos.is_none() { + return; + } + let (pos_x, pos_y) = self.start_pos.unwrap(); + for ( + ZXdgOutputInfo { + width, + height, + start_x, + start_y, + .. + }, + layershell_info, + ) in self.zxdgoutputs.iter().zip(self.wl_surfaces.iter()) + { + layershell_info.redraw( + (pos_x, pos_y), + self.current_pos, + (*start_x, *start_y), + (*width, *height), + ); + } + } + + pub fn area_info(&self) -> Option { + if self.start_pos.is_none() || self.end_pos.is_none() { + return None; + } + let (start_x, start_y) = self.start_pos.unwrap(); + let (end_x, end_y) = self.end_pos.unwrap(); + let output = self.outputs[self.current_screen].clone(); + let info = &self.zxdgoutputs[self.current_screen]; + Some(AreaInfo { + start_x, + start_y, + end_x, + end_y, + screen_info: info.get_screen_info(output), + }) + } +} + +#[derive(Debug)] +pub struct LayerSurfaceInfo { + pub layer: ZwlrLayerSurfaceV1, + pub wl_surface: WlSurface, + pub cursor_suface: WlSurface, + pub buffer: WlBuffer, + pub cursor_buffer: Option, + pub cairo_t: cairo::Context, +} + +/// describe the information of the area +#[derive(Debug)] +pub struct AreaInfo { + pub start_x: f64, + pub start_y: f64, + pub end_x: f64, + pub end_y: f64, + + pub screen_info: ScreenInfo, +} + +impl AreaInfo { + /// provide the width of the area as f64 + pub fn width_f64(&self) -> f64 { + (self.end_x - self.start_x).abs() + } + + /// provide the width of the area as i32 + pub fn width(&self) -> i32 { + self.width_f64() as i32 + } + + /// provide the height of the area as f64 + pub fn height_f64(&self) -> f64 { + (self.end_y - self.start_y).abs() + } + + /// provide the width of the area as i32 + pub fn height(&self) -> i32 { + self.height_f64() as i32 + } + + /// caculate the real start position + pub fn left_top_point(&self) -> (i32, i32) { + ( + self.start_x.min(self.end_x) as i32, + (self.start_y.min(self.end_y)) as i32, + ) + } + + /// you can get the info of the choosed screen + pub fn selected_screen_info(&self) -> &ScreenInfo { + &self.screen_info + } +} diff --git a/waysip/Cargo.toml b/waysip/Cargo.toml index ddd0d5e..b62905a 100644 --- a/waysip/Cargo.toml +++ b/waysip/Cargo.toml @@ -14,3 +14,5 @@ readme.workspace = true [dependencies] clap = { version = "4.4.10", features = ["derive"] } libwaysip.workspace = true +tracing.workspace = true +tracing-subscriber = "0.3" diff --git a/waysip/src/main.rs b/waysip/src/main.rs index 1ccf0a7..a1959ea 100644 --- a/waysip/src/main.rs +++ b/waysip/src/main.rs @@ -1,9 +1,10 @@ use clap::Parser; -use libwaysip::{get_area, WaySipKind}; +use libwaysip::{get_area, state::WaySipKind}; +use std::str::FromStr; #[derive(Debug, Parser)] #[command(name = "waysip")] -#[command(about="a tool like slurp, but in rust", long_about = None)] +#[command(about="Wayland native area picker", long_about = None)] enum Cli { #[command(short_flag = 'p')] Point, @@ -15,20 +16,28 @@ enum Cli { Output, } -fn main() { +fn main() -> Result<(), Box> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::from_str("trace")?) + .with_writer(std::io::stderr) + .init(); + let args = Cli::parse(); + // TODO: Make errors go through the cli into output + macro_rules! get_info { ($x: expr) => { match get_area($x) { Ok(Some(info)) => info, Ok(None) => { eprintln!("Get None, you cancel it"); - return; + // TODO: Have proper error types + return Ok(()); } Err(e) => { eprintln!("Error,{e}"); - return; + return Ok(()); } } }; @@ -67,4 +76,6 @@ fn main() { println!("{x},{y} {width}x{height}",); } } + + Ok(()) }