From ff3f7228b5f212315e1f64d973972a5554fdd82a Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sat, 22 Jan 2022 14:14:50 +0000 Subject: [PATCH 1/7] cleanup: Remove unused header lines --- src/server-context-service.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/server-context-service.h b/src/server-context-service.h index 2cecd9b6..588aeb5d 100644 --- a/src/server-context-service.h +++ b/src/server-context-service.h @@ -31,8 +31,6 @@ G_DECLARE_FINAL_TYPE (ServerContextService, server_context_service, SERVER, CONT ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman, struct squeek_state_manager *state_manager); enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *); -void server_context_service_force_show_keyboard (ServerContextService *self); -void server_context_service_hide_keyboard (ServerContextService *self); G_END_DECLS #endif /* SERVER_CONTEXT_SERVICE_H */ From 1b72cbdfaaeaaeb44f9f9ffcf361a659b4023cf4 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sat, 22 Jan 2022 14:15:11 +0000 Subject: [PATCH 2/7] docstrings: Clarify the purpose of Receiver --- src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.rs b/src/main.rs index 0a089bf1..5148e55c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,8 @@ mod c { /// Holds the Rust structures that are interesting from C. #[repr(C)] pub struct RsObjects { + /// The handle to which Commands should be sent + /// for processing in the main loop. receiver: Wrapped>, state_manager: Wrapped, submission: Wrapped, From f4f44a49aec2004d9853706c344242a34ded8c40 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 23 Jan 2022 17:12:33 +0000 Subject: [PATCH 3/7] wayland: Move initialization to the Rust side This will help make the init procedure safer, by limiting the number of Rust objects that need to be carried to the C side and may be mangled on the way there. The second benefit is that it allows outputs to become part of new state management. --- src/event_loop/driver.rs | 2 + src/main.h | 3 +- src/main.rs | 54 +++++++++++++++--- src/outputs.rs | 28 +++++++--- src/server-main.c | 118 +++++++++++++++++++-------------------- src/state.rs | 4 +- src/vkeyboard.rs | 7 +++ src/wayland.c | 6 ++ src/wayland.h | 17 ++---- 9 files changed, 148 insertions(+), 91 deletions(-) diff --git a/src/event_loop/driver.rs b/src/event_loop/driver.rs index 62ab185f..c60a14bc 100644 --- a/src/event_loop/driver.rs +++ b/src/event_loop/driver.rs @@ -29,7 +29,9 @@ use std::time::Instant; use crate::logging::Warn; +/// Type of the sender that waits for external events type Sender = mpsc::Sender; +/// Type of the sender that waits for internal state changes type UISender = glib::Sender; /// This loop driver spawns a new thread which updates the state in a loop, diff --git a/src/main.h b/src/main.h index c2c62a77..b6ea1cbf 100644 --- a/src/main.h +++ b/src/main.h @@ -21,11 +21,12 @@ struct rsobjects { struct receiver *receiver; struct squeek_state_manager *state_manager; struct submission *submission; + struct squeek_wayland *wayland; }; void register_ui_loop_handler(struct receiver *receiver, ServerContextService *ui, DBusHandler *dbus_handler); -struct rsobjects squeek_rsobjects_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk); +struct rsobjects squeek_init(void); void squeek_state_send_force_visible(struct squeek_state_manager *state); void squeek_state_send_force_hidden(struct squeek_state_manager *state); diff --git a/src/main.rs b/src/main.rs index 5148e55c..0e9c0538 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Purism SPC +/* Copyright (C) 2020,2022 Purism SPC * SPDX-License-Identifier: GPL-3.0+ */ @@ -11,12 +11,14 @@ use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver}; mod c { use super::*; use std::os::raw::c_void; + use std::ptr; use std::rc::Rc; use std::time::Instant; use crate::event_loop::driver; use crate::imservice::IMService; use crate::imservice::c::InputMethod; + use crate::outputs::Outputs; use crate::state; use crate::submission::Submission; use crate::util::c::Wrapped; @@ -38,9 +40,40 @@ mod c { receiver: Wrapped>, state_manager: Wrapped, submission: Wrapped, + /// Not wrapped, because C needs to access this. + wayland: *mut Wayland, + } + + /// Corresponds to wayland.h::squeek_wayland. + /// Fields unused by Rust are marked as generic data types. + #[repr(C)] + pub struct Wayland { + layer_shell: *const c_void, + virtual_keyboard_manager: *const c_void, + input_method_manager: *const c_void, + outputs: Wrapped, + seat: *const c_void, + input_method: *mut InputMethod, + virtual_keyboard: ZwpVirtualKeyboardV1, + } + + impl Wayland { + fn new(outputs_manager: Outputs) -> Self { + Wayland { + layer_shell: ptr::null(), + virtual_keyboard_manager: ptr::null(), + input_method_manager: ptr::null(), + outputs: Wrapped::new(outputs_manager), + seat: ptr::null(), + input_method: ptr::null_mut(), + virtual_keyboard: ZwpVirtualKeyboardV1::null(), + } + } } extern "C" { + #[allow(improper_ctypes)] + fn init_wayland(wayland: *mut Wayland); fn server_context_service_real_show_keyboard(service: *const UIManager); fn server_context_service_real_hide_keyboard(service: *const UIManager); fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32); @@ -54,19 +87,23 @@ mod c { /// and that leads to suffering. #[no_mangle] pub extern "C" - fn squeek_rsobjects_new( - im: *mut InputMethod, - vk: ZwpVirtualKeyboardV1, - ) -> RsObjects { + fn squeek_init() -> RsObjects { + // Set up channels let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT); - let now = Instant::now(); let state_manager = driver::Threaded::new(sender, state::Application::new(now)); - let imservice = if im.is_null() { + let outputs = Outputs::new(state_manager.clone()); + let mut wayland = Box::new(Wayland::new(outputs)); + let wayland_raw = &mut *wayland as *mut _; + unsafe { init_wayland(wayland_raw); } + + let vk = wayland.virtual_keyboard; + + let imservice = if wayland.input_method.is_null() { None } else { - Some(IMService::new(im, state_manager.clone())) + Some(IMService::new(wayland.input_method, state_manager.clone())) }; let submission = Submission::new(vk, imservice); @@ -74,6 +111,7 @@ mod c { submission: Wrapped::new(submission), state_manager: Wrapped::new(state_manager), receiver: Wrapped::new(receiver), + wayland: Box::into_raw(wayland), } } diff --git a/src/outputs.rs b/src/outputs.rs index 29f4e78c..78297ec9 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -1,6 +1,11 @@ +/* Copyright (C) 2019-2022 Purism SPC + * SPDX-License-Identifier: GPL-3.0+ + */ + /*! Managing Wayland outputs */ use std::vec::Vec; +use crate::event_loop; use ::logging; // traits @@ -12,7 +17,7 @@ pub mod c { use std::os::raw::{ c_char, c_void }; - use ::util::c::COpaquePtr; + use ::util::c::{COpaquePtr, Wrapped}; // Defined in C @@ -103,7 +108,8 @@ pub mod c { ) -> i32; } - type COutputs = ::util::c::Wrapped; + /// Wrapping Outputs is required for calling its methods from C + type COutputs = Wrapped; /// A stable reference to an output. #[derive(Clone)] @@ -125,6 +131,8 @@ pub mod c { // Defined in Rust + // Callbacks from the output listener follow + extern fn outputs_handle_geometry( outputs: COutputs, wl_output: WlOutput, @@ -221,11 +229,7 @@ pub mod c { }; } - #[no_mangle] - pub extern "C" - fn squeek_outputs_new() -> COutputs { - COutputs::new(Outputs { outputs: Vec::new() }) - } + // End callbacks #[no_mangle] pub extern "C" @@ -363,4 +367,14 @@ pub struct Output { pub struct Outputs { outputs: Vec, + sender: event_loop::driver::Threaded, +} + +impl Outputs { + pub fn new(sender: event_loop::driver::Threaded) -> Outputs { + Outputs { + outputs: Vec::new(), + sender, + } + } } diff --git a/src/server-main.c b/src/server-main.c index 1799ccfa..b2cacdc6 100644 --- a/src/server-main.c +++ b/src/server-main.c @@ -112,34 +112,33 @@ registry_handle_global (void *data, // Even when lower version would be served, it would not be supported, // causing a hard exit (void)version; - struct squeekboard *instance = data; + struct squeek_wayland *wayland = data; if (!strcmp (interface, zwlr_layer_shell_v1_interface.name)) { - instance->wayland.layer_shell = wl_registry_bind (registry, name, + wayland->layer_shell = wl_registry_bind (registry, name, &zwlr_layer_shell_v1_interface, 1); } else if (!strcmp (interface, zwp_virtual_keyboard_manager_v1_interface.name)) { - instance->wayland.virtual_keyboard_manager = wl_registry_bind(registry, name, + wayland->virtual_keyboard_manager = wl_registry_bind(registry, name, &zwp_virtual_keyboard_manager_v1_interface, 1); } else if (!strcmp (interface, zwp_input_method_manager_v2_interface.name)) { - instance->wayland.input_method_manager = wl_registry_bind(registry, name, + wayland->input_method_manager = wl_registry_bind(registry, name, &zwp_input_method_manager_v2_interface, 1); } else if (!strcmp (interface, "wl_output")) { struct wl_output *output = wl_registry_bind (registry, name, &wl_output_interface, 2); - squeek_outputs_register(instance->wayland.outputs, output); + squeek_outputs_register(wayland->outputs, output); } else if (!strcmp(interface, "wl_seat")) { - instance->wayland.seat = wl_registry_bind(registry, name, + wayland->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); } } - static void registry_handle_global_remove (void *data, struct wl_registry *registry, uint32_t name) { - // TODO + // TODO: outputs } static const struct wl_registry_listener registry_listener = { @@ -147,6 +146,54 @@ static const struct wl_registry_listener registry_listener = { registry_handle_global_remove }; + +void init_wayland(struct squeek_wayland *wayland) { + // Set up Wayland + gdk_set_allowed_backends ("wayland"); + GdkDisplay *gdk_display = gdk_display_get_default (); + struct wl_display *display = gdk_wayland_display_get_wl_display (gdk_display); + + if (display == NULL) { + g_error ("Failed to get display: %m\n"); + exit(1); + } + + struct wl_registry *registry = wl_display_get_registry (display); + wl_registry_add_listener (registry, ®istry_listener, wayland); + wl_display_roundtrip(display); // wait until the registry is actually populated + + if (!wayland->seat) { + g_error("No seat Wayland global available."); + exit(1); + } + if (!wayland->virtual_keyboard_manager) { + g_error("No virtual keyboard manager Wayland global available."); + exit(1); + } + if (!wayland->layer_shell) { + g_error("No layer shell global available."); + exit(1); + } + + if (!wayland->input_method_manager) { + g_warning("Wayland input method interface not available"); + } + + if (wayland->input_method_manager) { + wayland->input_method = zwp_input_method_manager_v2_get_input_method( + wayland->input_method_manager, + wayland->seat); + } + if (wayland->virtual_keyboard_manager) { + wayland->virtual_keyboard = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard( + wayland->virtual_keyboard_manager, + wayland->seat); + } + + // initialize global + squeek_wayland = wayland; +} + #define SESSION_NAME "sm.puri.OSK0" GDBusProxy *_proxy = NULL; @@ -284,22 +331,6 @@ phosh_theme_init (void) g_object_set (G_OBJECT (gtk_settings), "gtk-application-prefer-dark-theme", TRUE, NULL); } -/// Create Rust objects in one go, -/// to avoid crossing the language barrier and losing type information -static struct rsobjects create_rsobjects(struct zwp_input_method_manager_v2 *immanager, - struct zwp_virtual_keyboard_manager_v1 *vkmanager, - struct wl_seat *seat) { - struct zwp_input_method_v2 *im = NULL; - if (immanager) { - im = zwp_input_method_manager_v2_get_input_method(immanager, seat); - } - struct zwp_virtual_keyboard_v1 *vk = NULL; - if (vkmanager) { - vk = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(vkmanager, seat); - } - return squeek_rsobjects_new(im, vk); -} - static GDebugKey debug_keys[] = { { .key = "force-show", @@ -359,44 +390,10 @@ main (int argc, char **argv) phosh_theme_init (); - // Set up Wayland - gdk_set_allowed_backends ("wayland"); - GdkDisplay *gdk_display = gdk_display_get_default (); - struct wl_display *display = gdk_wayland_display_get_wl_display (gdk_display); - - if (display == NULL) { - g_error ("Failed to get display: %m\n"); - exit(1); - } - - struct squeekboard instance = {0}; - squeek_wayland_init (&instance.wayland); - struct wl_registry *registry = wl_display_get_registry (display); - wl_registry_add_listener (registry, ®istry_listener, &instance); - wl_display_roundtrip(display); // wait until the registry is actually populated - squeek_wayland_set_global(&instance.wayland); - if (!instance.wayland.seat) { - g_error("No seat Wayland global available."); - exit(1); - } - if (!instance.wayland.virtual_keyboard_manager) { - g_error("No virtual keyboard manager Wayland global available."); - exit(1); - } - if (!instance.wayland.layer_shell) { - g_error("No layer shell global available."); - exit(1); - } - - if (!instance.wayland.input_method_manager) { - g_warning("Wayland input method interface not available"); - } - - struct rsobjects rsobjects = create_rsobjects(instance.wayland.input_method_manager, - instance.wayland.virtual_keyboard_manager, - instance.wayland.seat); + // Also initializes wayland + struct rsobjects rsobjects = squeek_init(); instance.ui_manager = squeek_uiman_new(); @@ -477,6 +474,5 @@ main (int argc, char **argv) } g_main_loop_unref (loop); - squeek_wayland_deinit (&instance.wayland); return 0; } diff --git a/src/state.rs b/src/state.rs index 24b89d94..f3db005d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -29,12 +29,14 @@ pub enum InputMethod { InactiveSince(Instant), } -/// Incoming events +/// Incoming events. +/// This contains events that cause a change to the internal state. #[derive(Clone)] pub enum Event { InputMethod(InputMethod), Visibility(visibility::Event), PhysicalKeyboard(Presence), + /// Event triggered because a moment in time passed. /// Use to animate state transitions. /// The value is the ideal arrival time. diff --git a/src/vkeyboard.rs b/src/vkeyboard.rs index aff822a7..4e8fb1a4 100644 --- a/src/vkeyboard.rs +++ b/src/vkeyboard.rs @@ -10,11 +10,18 @@ type KeyCode = u32; pub mod c { use std::ffi::CStr; use std::os::raw::{ c_char, c_void }; + use std::ptr; #[repr(transparent)] #[derive(Clone, Copy)] pub struct ZwpVirtualKeyboardV1(*const c_void); + impl ZwpVirtualKeyboardV1 { + pub fn null() -> Self { + Self(ptr::null()) + } + } + #[repr(C)] pub struct KeyMap { fd: u32, diff --git a/src/wayland.c b/src/wayland.c index 3c9fce93..6e569def 100644 --- a/src/wayland.c +++ b/src/wayland.c @@ -4,6 +4,12 @@ struct squeek_wayland *squeek_wayland = NULL; +void squeek_wayland_init_global(struct squeek_outputs *outputs) { + struct squeek_wayland *wayland = {0}; + wayland->outputs = outputs; + squeek_wayland = wayland; +} + // The following functions only exist // to create linkable symbols out of inline functions, // because those are not directly callable from Rust diff --git a/src/wayland.h b/src/wayland.h index a411188d..08277211 100644 --- a/src/wayland.h +++ b/src/wayland.h @@ -10,27 +10,18 @@ #include "outputs.h" struct squeek_wayland { + // globals struct zwlr_layer_shell_v1 *layer_shell; struct zwp_virtual_keyboard_manager_v1 *virtual_keyboard_manager; struct zwp_input_method_manager_v2 *input_method_manager; struct squeek_outputs *outputs; struct wl_seat *seat; + // objects + struct zwp_input_method_v2 *input_method; + struct zwp_virtual_keyboard_v1 *virtual_keyboard; }; extern struct squeek_wayland *squeek_wayland; - -static inline void squeek_wayland_init(struct squeek_wayland *wayland) { - wayland->outputs = squeek_outputs_new(); -} - -static inline void squeek_wayland_set_global(struct squeek_wayland *wayland) { - squeek_wayland = wayland; -} - -static inline void squeek_wayland_deinit(struct squeek_wayland *wayland) { - squeek_outputs_free(wayland->outputs); -} - #endif // WAYLAND_H From 236f7d4daf649e280c0ca715fc7bafb9b48c72b1 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 23 Jan 2022 18:06:26 +0000 Subject: [PATCH 4/7] ffi: Remove unnecessary pointers to InputMethod InputMethod is already a pointer. --- src/imservice.rs | 41 ++++++++++++++++++++++++++--------------- src/main.rs | 4 ++-- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/imservice.rs b/src/imservice.rs index 1ad7621c..da0f6749 100644 --- a/src/imservice.rs +++ b/src/imservice.rs @@ -26,21 +26,32 @@ pub mod c { use super::*; use std::os::raw::{c_char, c_void}; + use std::ptr; // The following defined in C /// struct zwp_input_method_v2* #[repr(transparent)] + #[derive(PartialEq, Clone, Copy)] pub struct InputMethod(*const c_void); + impl InputMethod { + pub fn is_null(&self) -> bool { + self.0.is_null() + } + pub fn null() -> Self { + Self(ptr::null()) + } + } + extern "C" { - fn imservice_destroy_im(im: *mut c::InputMethod); + fn imservice_destroy_im(im: InputMethod); #[allow(improper_ctypes)] // IMService will never be dereferenced in C - pub fn imservice_connect_listeners(im: *mut InputMethod, imservice: *const IMService); - pub fn eek_input_method_commit_string(im: *mut InputMethod, text: *const c_char); - pub fn eek_input_method_delete_surrounding_text(im: *mut InputMethod, before: u32, after: u32); - pub fn eek_input_method_commit(im: *mut InputMethod, serial: u32); + pub fn imservice_connect_listeners(im: InputMethod, imservice: *const IMService); + pub fn eek_input_method_commit_string(im: InputMethod, text: *const c_char); + pub fn eek_input_method_delete_surrounding_text(im: InputMethod, before: u32, after: u32); + pub fn eek_input_method_commit(im: InputMethod, serial: u32); } // The following defined in Rust. TODO: wrap naked pointers to Rust data inside RefCells to prevent multiple writers @@ -49,7 +60,7 @@ pub mod c { #[no_mangle] pub extern "C" fn imservice_handle_input_method_activate(imservice: *mut IMService, - im: *const InputMethod) + im: InputMethod) { let imservice = check_imservice(imservice, im).unwrap(); imservice.preedit_string = String::new(); @@ -62,7 +73,7 @@ pub mod c { #[no_mangle] pub extern "C" fn imservice_handle_input_method_deactivate(imservice: *mut IMService, - im: *const InputMethod) + im: InputMethod) { let imservice = check_imservice(imservice, im).unwrap(); imservice.pending = IMProtocolState { @@ -74,7 +85,7 @@ pub mod c { #[no_mangle] pub extern "C" fn imservice_handle_surrounding_text(imservice: *mut IMService, - im: *const InputMethod, + im: InputMethod, text: *const c_char, cursor: u32, _anchor: u32) { let imservice = check_imservice(imservice, im).unwrap(); @@ -90,7 +101,7 @@ pub mod c { #[no_mangle] pub extern "C" fn imservice_handle_content_type(imservice: *mut IMService, - im: *const InputMethod, + im: InputMethod, hint: u32, purpose: u32) { let imservice = check_imservice(imservice, im).unwrap(); @@ -118,7 +129,7 @@ pub mod c { #[no_mangle] pub extern "C" fn imservice_handle_text_change_cause(imservice: *mut IMService, - im: *const InputMethod, + im: InputMethod, cause: u32) { let imservice = check_imservice(imservice, im).unwrap(); @@ -138,7 +149,7 @@ pub mod c { #[no_mangle] pub extern "C" fn imservice_handle_done(imservice: *mut IMService, - im: *const InputMethod) + im: InputMethod) { let imservice = check_imservice(imservice, im).unwrap(); @@ -156,7 +167,7 @@ pub mod c { #[no_mangle] pub extern "C" fn imservice_handle_unavailable(imservice: *mut IMService, - im: *mut InputMethod) + im: InputMethod) { let imservice = check_imservice(imservice, im).unwrap(); unsafe { imservice_destroy_im(im); } @@ -181,7 +192,7 @@ pub mod c { /// Care must be take /// not to exceed the lifetime of the pointer with the reference, /// especially not to store it. - fn check_imservice(imservice: *mut IMService, im: *const InputMethod) + fn check_imservice(imservice: *mut IMService, im: InputMethod) -> Result<&'static mut IMService, &'static str> { if imservice.is_null() { @@ -315,7 +326,7 @@ impl Default for IMProtocolState { pub struct IMService { /// Owned reference (still created and destroyed in C) - pub im: *mut c::InputMethod, + pub im: c::InputMethod, sender: driver::Threaded, pending: IMProtocolState, @@ -331,7 +342,7 @@ pub enum SubmitError { impl IMService { pub fn new( - im: *mut c::InputMethod, + im: c::InputMethod, sender: driver::Threaded, ) -> Box { // IMService will be referenced to by C, diff --git a/src/main.rs b/src/main.rs index 0e9c0538..afef8288 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,7 @@ mod c { input_method_manager: *const c_void, outputs: Wrapped, seat: *const c_void, - input_method: *mut InputMethod, + input_method: InputMethod, virtual_keyboard: ZwpVirtualKeyboardV1, } @@ -65,7 +65,7 @@ mod c { input_method_manager: ptr::null(), outputs: Wrapped::new(outputs_manager), seat: ptr::null(), - input_method: ptr::null_mut(), + input_method: InputMethod::null(), virtual_keyboard: ZwpVirtualKeyboardV1::null(), } } From 14a485debad00bc6fc40b99d53e62e2b6a720e73 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 23 Jan 2022 18:14:26 +0000 Subject: [PATCH 5/7] outputs: Clean up for more Rust usage --- src/outputs.rs | 62 +++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/outputs.rs b/src/outputs.rs index 78297ec9..e4466457 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -125,7 +125,9 @@ pub mod c { pub fn get_state(&self) -> Option { let outputs = self.outputs.clone_ref(); let outputs = outputs.borrow(); - find_output(&outputs, self.wl_output.clone()).map(|o| o.current.clone()) + outputs + .find_output(self.wl_output.clone()) + .map(|o| o.current.clone()) } } @@ -151,7 +153,8 @@ pub mod c { let outputs = outputs.clone_ref(); let mut collection = outputs.borrow_mut(); let output_state: Option<&mut OutputState> - = find_output_mut(&mut collection, wl_output) + = collection + .find_output_mut(wl_output) .map(|o| &mut o.pending); match output_state { Some(state) => { state.transform = Some(transform) }, @@ -179,7 +182,8 @@ pub mod c { let outputs = outputs.clone_ref(); let mut collection = outputs.borrow_mut(); let output_state: Option<&mut OutputState> - = find_output_mut(&mut collection, wl_output) + = collection + .find_output_mut(wl_output) .map(|o| &mut o.pending); match output_state { Some(state) => { @@ -200,7 +204,8 @@ pub mod c { ) { let outputs = outputs.clone_ref(); let mut collection = outputs.borrow_mut(); - let output = find_output_mut(&mut collection, wl_output); + let output = collection + .find_output_mut(wl_output); match output { Some(output) => { output.current = output.pending.clone(); } None => log_print!( @@ -218,7 +223,8 @@ pub mod c { let outputs = outputs.clone_ref(); let mut collection = outputs.borrow_mut(); let output_state: Option<&mut OutputState> - = find_output_mut(&mut collection, wl_output) + = collection + .find_output_mut(wl_output) .map(|o| &mut o.pending); match output_state { Some(state) => { state.scale = factor; } @@ -272,28 +278,6 @@ pub mod c { } // TODO: handle unregistration - - fn find_output( - collection: &Outputs, - wl_output: WlOutput, - ) -> Option<&Output> { - collection.outputs - .iter() - .find_map(|o| - if o.output == wl_output { Some(o) } else { None } - ) - } - - fn find_output_mut( - collection: &mut Outputs, - wl_output: WlOutput, - ) -> Option<&mut Output> { - collection.outputs - .iter_mut() - .find_map(|o| - if o.output == wl_output { Some(o) } else { None } - ) - } } /// Generic size @@ -359,7 +343,11 @@ impl OutputState { } } -pub struct Output { +/// Not guaranteed to exist, +/// but can be used to look up state. +pub type OutputId = c::WlOutput; + +struct Output { output: c::WlOutput, pending: OutputState, current: OutputState, @@ -377,4 +365,22 @@ impl Outputs { sender, } } + + fn find_output(&self, wl_output: c::WlOutput) -> Option<&Output> { + self.outputs + .iter() + .find_map(|o| + if o.output == wl_output { Some(o) } else { None } + ) + } + + fn find_output_mut(&mut self, wl_output: c::WlOutput) + -> Option<&mut Output> + { + self.outputs + .iter_mut() + .find_map(|o| + if o.output == wl_output { Some(o) } else { None } + ) + } } From d3eb68ed5a67bab9710d2bf9adef4f0a57c60330 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 23 Jan 2022 18:57:27 +0000 Subject: [PATCH 6/7] outputs: Notify the state manager about changes --- src/outputs.rs | 57 ++++++++++++++++++++++++++++++++++++++++---------- src/state.rs | 14 ++++++++++++- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/outputs.rs b/src/outputs.rs index e4466457..a5cd517b 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -22,7 +22,7 @@ pub mod c { // Defined in C #[repr(transparent)] - #[derive(Clone, PartialEq, Copy)] + #[derive(Clone, PartialEq, Copy, Debug)] pub struct WlOutput(*const c_void); #[repr(C)] @@ -68,7 +68,7 @@ pub mod c { } /// Map to `wl_output.transform` values - #[derive(Clone)] + #[derive(Clone, Copy, Debug)] pub enum Transform { Normal = 0, Rotated90 = 1, @@ -206,13 +206,25 @@ pub mod c { let mut collection = outputs.borrow_mut(); let output = collection .find_output_mut(wl_output); - match output { - Some(output) => { output.current = output.pending.clone(); } - None => log_print!( - logging::Level::Warning, - "Got done on unknown output", - ), + let event = match output { + Some(output) => { + output.current = output.pending.clone(); + Some(Event { + output: OutputId(wl_output), + change: ChangeType::Altered(output.current), + }) + }, + None => { + log_print!( + logging::Level::Warning, + "Got done on unknown output", + ); + None + } }; + if let Some(event) = event { + collection.send_event(event); + } } extern fn outputs_handle_scale( @@ -288,13 +300,13 @@ pub struct Size { } /// wl_output mode -#[derive(Clone)] +#[derive(Clone, Copy, Debug)] struct Mode { width: i32, height: i32, } -#[derive(Clone)] +#[derive(Clone, Copy, Debug)] pub struct OutputState { current_mode: Option, transform: Option, @@ -345,7 +357,13 @@ impl OutputState { /// Not guaranteed to exist, /// but can be used to look up state. -pub type OutputId = c::WlOutput; +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct OutputId(c::WlOutput); + +// WlOutput is a pointer, +// but in the public interface, +// we're only using it as a lookup key. +unsafe impl Send for OutputId {} struct Output { output: c::WlOutput, @@ -366,6 +384,10 @@ impl Outputs { } } + fn send_event(&self, event: Event) { + self.sender.send(event.into()).unwrap() + } + fn find_output(&self, wl_output: c::WlOutput) -> Option<&Output> { self.outputs .iter() @@ -384,3 +406,16 @@ impl Outputs { ) } } + +#[derive(Clone, Copy, Debug)] +pub enum ChangeType { + /// Added or changed + Altered(OutputState), + Removed, +} + +#[derive(Clone, Copy, Debug)] +pub struct Event { + output: OutputId, + change: ChangeType, +} diff --git a/src/state.rs b/src/state.rs index f3db005d..9009f1f4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,6 +8,7 @@ use crate::animation; use crate::imservice::{ ContentHint, ContentPurpose }; use crate::main::{ Commands, PanelCommand }; +use crate::outputs; use std::time::Instant; @@ -36,7 +37,7 @@ pub enum Event { InputMethod(InputMethod), Visibility(visibility::Event), PhysicalKeyboard(Presence), - + Output(outputs::Event), /// Event triggered because a moment in time passed. /// Use to animate state transitions. /// The value is the ideal arrival time. @@ -49,6 +50,12 @@ impl From for Event { } } +impl From for Event { + fn from(ev: outputs::Event) -> Self { + Self::Output(ev) + } +} + pub mod visibility { #[derive(Clone)] pub enum Event { @@ -166,6 +173,11 @@ impl Application { ..self }, + Event::Output(output) => { + println!("Stub: output event {:?}", output); + self + }, + Event::InputMethod(new_im) => match (self.im.clone(), new_im) { (InputMethod::Active(_old), InputMethod::Active(new_im)) => Self { From f15f97d4c94a6ba00615558175e1d9a96dc201c2 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Mon, 24 Jan 2022 19:04:18 +0000 Subject: [PATCH 7/7] outputs: Handle removal Currrently, Squeekboard doesn't do anything with this information. It still expects one output to be present, or it will crash. --- src/outputs.h | 3 ++- src/outputs.rs | 69 +++++++++++++++++++++++++++++++++++++++-------- src/server-main.c | 9 +++++-- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/outputs.h b/src/outputs.h index f6466a70..c81026e5 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -11,7 +11,8 @@ struct squeek_output_handle { struct squeek_outputs *squeek_outputs_new(void); void squeek_outputs_free(struct squeek_outputs*); -void squeek_outputs_register(struct squeek_outputs*, struct wl_output *output); +void squeek_outputs_register(struct squeek_outputs*, struct wl_output *output, uint32_t id); +struct wl_output *squeek_outputs_try_unregister(struct squeek_outputs*, uint32_t id); struct squeek_output_handle squeek_outputs_get_current(struct squeek_outputs*); int32_t squeek_outputs_get_perceptual_width(struct squeek_outputs*, struct wl_output *output); #endif diff --git a/src/outputs.rs b/src/outputs.rs index a5cd517b..e82995bc 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -16,6 +16,7 @@ pub mod c { use super::*; use std::os::raw::{ c_char, c_void }; + use std::ptr; use ::util::c::{COpaquePtr, Wrapped}; @@ -25,6 +26,12 @@ pub mod c { #[derive(Clone, PartialEq, Copy, Debug)] pub struct WlOutput(*const c_void); + impl WlOutput { + fn null() -> Self { + Self(ptr::null()) + } + } + #[repr(C)] struct WlOutputListener { geometry: extern fn( @@ -257,14 +264,17 @@ pub mod c { #[no_mangle] pub extern "C" - fn squeek_outputs_register(raw_collection: COutputs, output: WlOutput) { + fn squeek_outputs_register(raw_collection: COutputs, output: WlOutput, id: u32) { let collection = raw_collection.clone_ref(); let mut collection = collection.borrow_mut(); - collection.outputs.push(Output { - output: output.clone(), - pending: OutputState::uninitialized(), - current: OutputState::uninitialized(), - }); + collection.outputs.push(( + Output { + output: output.clone(), + pending: OutputState::uninitialized(), + current: OutputState::uninitialized(), + }, + id, + )); unsafe { squeek_output_add_listener( output, @@ -277,14 +287,29 @@ pub mod c { raw_collection, )}; } - + + /// This will try to unregister the output, if the id matches a registered one. + #[no_mangle] + pub extern "C" + fn squeek_outputs_try_unregister(raw_collection: COutputs, id: u32) -> WlOutput { + let collection = raw_collection.clone_ref(); + let mut collection = collection.borrow_mut(); + collection.remove_output_by_global(id) + .map_err(|e| log_print!( + logging::Level::Debug, + "Tried to remove global {:x} but it is not registered as an output: {:?}", + id, e, + )) + .unwrap_or(WlOutput::null()) + } + #[no_mangle] pub extern "C" fn squeek_outputs_get_current(raw_collection: COutputs) -> OutputHandle { let collection = raw_collection.clone_ref(); let collection = collection.borrow(); OutputHandle { - wl_output: collection.outputs[0].output.clone(), + wl_output: collection.outputs[0].0.output.clone(), outputs: raw_collection.clone(), } } @@ -371,8 +396,13 @@ struct Output { current: OutputState, } +#[derive(Debug)] +struct NotFound; + +type GlobalId = u32; + pub struct Outputs { - outputs: Vec, + outputs: Vec<(Output, GlobalId)>, sender: event_loop::driver::Threaded, } @@ -388,10 +418,27 @@ impl Outputs { self.sender.send(event.into()).unwrap() } + fn remove_output_by_global(&mut self, id: GlobalId) + -> Result + { + let index = self.outputs.iter() + .position(|(_o, global_id)| *global_id == id); + if let Some(index) = index { + let (output, _id) = self.outputs.remove(index); + self.send_event(Event { + change: ChangeType::Removed, + output: OutputId(output.output), + }); + Ok(output.output) + } else { + Err(NotFound) + } + } + fn find_output(&self, wl_output: c::WlOutput) -> Option<&Output> { self.outputs .iter() - .find_map(|o| + .find_map(|(o, _global)| if o.output == wl_output { Some(o) } else { None } ) } @@ -401,7 +448,7 @@ impl Outputs { { self.outputs .iter_mut() - .find_map(|o| + .find_map(|(o, _global)| if o.output == wl_output { Some(o) } else { None } ) } diff --git a/src/server-main.c b/src/server-main.c index b2cacdc6..ec72aecc 100644 --- a/src/server-main.c +++ b/src/server-main.c @@ -126,7 +126,7 @@ registry_handle_global (void *data, } else if (!strcmp (interface, "wl_output")) { struct wl_output *output = wl_registry_bind (registry, name, &wl_output_interface, 2); - squeek_outputs_register(wayland->outputs, output); + squeek_outputs_register(wayland->outputs, output, name); } else if (!strcmp(interface, "wl_seat")) { wayland->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1); @@ -138,7 +138,12 @@ registry_handle_global_remove (void *data, struct wl_registry *registry, uint32_t name) { - // TODO: outputs + (void)registry; + struct squeek_wayland *wayland = data; + struct wl_output *output = squeek_outputs_try_unregister(wayland->outputs, name); + if (output) { + wl_output_destroy(output); + } } static const struct wl_registry_listener registry_listener = {