diff --git a/src/animation.h b/src/animation.h new file mode 100644 index 00000000..31c4c98d --- /dev/null +++ b/src/animation.h @@ -0,0 +1,13 @@ +#pragma once +#include + +// from main.h +struct sender; + +// from animations.rs +struct squeek_animation_visibility_manager; + +struct squeek_animation_visibility_manager *squeek_animation_visibility_manager_new(struct sender *ui_sender); + +void squeek_animation_visibility_manager_send_claim_visible(struct squeek_animation_visibility_manager *animman); +void squeek_animation_visibility_manager_send_force_hide(struct squeek_animation_visibility_manager *animman); diff --git a/src/dbus.c b/src/dbus.c index 24bab66f..14cc32d0 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -44,11 +44,6 @@ dbus_handler_destroy(DBusHandler *service) service->introspection_data = NULL; } - if (service->context) { - g_signal_handlers_disconnect_by_data (service->context, service); - service->context = NULL; - } - free(service); } @@ -57,38 +52,25 @@ handle_set_visible(SmPuriOSK0 *object, GDBusMethodInvocation *invocation, gboolean arg_visible, gpointer user_data) { DBusHandler *service = user_data; - if (service->context) { - if (arg_visible) { - server_context_service_force_show_keyboard (service->context); - } else { - server_context_service_hide_keyboard (service->context); - } + if (arg_visible) { + squeek_animation_visibility_manager_send_claim_visible (service->animman); + } else { + squeek_animation_visibility_manager_send_force_hide (service->animman); } + sm_puri_osk0_complete_set_visible(object, invocation); return TRUE; } -static void on_visible(DBusHandler *service, - GParamSpec *pspec, - ServerContextService *context) -{ - (void)pspec; - gboolean visible; - - g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (context)); - - g_object_get (context, "visible", &visible, NULL); - - sm_puri_osk0_set_visible(service->dbus_interface, visible); -} - DBusHandler * dbus_handler_new (GDBusConnection *connection, - const gchar *object_path) + const gchar *object_path, + struct squeek_animation_visibility_manager *animman) { DBusHandler *self = calloc(1, sizeof(DBusHandler)); self->object_path = g_strdup(object_path); self->connection = connection; + self->animman = animman; self->dbus_interface = sm_puri_osk0_skeleton_new(); g_signal_connect(self->dbus_interface, "handle-set-visible", @@ -109,16 +91,9 @@ dbus_handler_new (GDBusConnection *connection, return self; } -void -dbus_handler_set_ui_context(DBusHandler *service, - ServerContextService *context) +// Exported to Rust +void dbus_handler_set_visible(DBusHandler *service, + uint8_t visible) { - g_return_if_fail (!service->context); - - service->context = context; - - g_signal_connect_swapped (service->context, - "notify::visible", - G_CALLBACK(on_visible), - service); + sm_puri_osk0_set_visible(service->dbus_interface, visible); } diff --git a/src/dbus.h b/src/dbus.h index f719e274..49e2e46d 100644 --- a/src/dbus.h +++ b/src/dbus.h @@ -19,7 +19,7 @@ #ifndef DBUS_H_ #define DBUS_H_ 1 -#include "server-context-service.h" +#include "animation.h" #include "sm.puri.OSK0.h" @@ -28,6 +28,10 @@ G_BEGIN_DECLS #define DBUS_SERVICE_PATH "/sm/puri/OSK0" #define DBUS_SERVICE_INTERFACE "sm.puri.OSK0" +/// Two jobs: accept events, forwarding them to the visibility manager, +/// and get updated from inside to show internal state. +/// Updates are handled in the same loop as the UI. +/// See main.rs typedef struct _DBusHandler { GDBusConnection *connection; @@ -36,13 +40,14 @@ typedef struct _DBusHandler guint registration_id; char *object_path; - ServerContextService *context; // unowned reference + /// Forward incoming events there + struct squeek_animation_visibility_manager *animman; // shared reference } DBusHandler; DBusHandler * dbus_handler_new (GDBusConnection *connection, - const gchar *object_path); -void dbus_handler_set_ui_context(DBusHandler *service, - ServerContextService *context); + const gchar *object_path, + struct squeek_animation_visibility_manager *animman); + void dbus_handler_destroy(DBusHandler*); G_END_DECLS #endif /* DBUS_H_ */ diff --git a/src/imservice.rs b/src/imservice.rs index 5c30da6c..f7974a9a 100644 --- a/src/imservice.rs +++ b/src/imservice.rs @@ -23,7 +23,6 @@ pub mod c { use std::os::raw::{c_char, c_void}; - pub use ::ui_manager::c::UIManager; pub use ::submission::c::StateManager; // The following defined in C @@ -153,14 +152,14 @@ pub mod c { if active_changed { (imservice.active_callback)(imservice.current.active); - } - if imservice.current.active { - unsafe { - eekboard_context_service_set_hint_purpose( - imservice.state_manager, - imservice.current.content_hint.bits(), - imservice.current.content_purpose.clone() as u32, - ); + if imservice.current.active { + unsafe { + eekboard_context_service_set_hint_purpose( + imservice.state_manager, + imservice.current.content_hint.bits(), + imservice.current.content_purpose.clone() as u32, + ); + } } } } diff --git a/src/lib.rs b/src/lib.rs index 0673e4a0..1478ccbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub mod imservice; mod keyboard; mod layout; mod locale; +mod main; mod manager; mod outputs; mod popover; diff --git a/src/main.h b/src/main.h new file mode 100644 index 00000000..ea0d5a03 --- /dev/null +++ b/src/main.h @@ -0,0 +1,17 @@ +#pragma once +/// This all wraps https://gtk-rs.org/gtk-rs-core/stable/latest/docs/glib/struct.MainContext.html#method.channel + +#include "eek/eek-types.h" +#include "dbus.h" + +struct receiver; +struct sender; + +struct channel { + struct sender *sender; + struct receiver *receiver; +}; + +/// Creates a channel with one end inside the glib main loop +struct channel main_loop_channel_new(void); +void register_ui_loop_handler(struct receiver *receiver, ServerContextService *ui, DBusHandler *dbus_handler); diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..daf1b5b2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,119 @@ +/* Copyright (C) 2020 Purism SPC + * SPDX-License-Identifier: GPL-3.0+ + */ + +/*! Glue for the main loop. */ + +use crate::animation::Outcome as Message; +use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver, Sender}; +use std::thread; +use std::time::Duration; + +mod c { + use super::*; + use std::os::raw::c_void; + use std::rc::Rc; + + use ::util::c::{ ArcWrapped, Wrapped }; + + /// ServerContextService* + #[repr(transparent)] + pub struct UIManager(*const c_void); + + /// DbusHandler* + #[repr(transparent)] + pub struct DBusHandler(*const c_void); + + /// Corresponds to main.c::channel + #[repr(C)] + pub struct Channel { + sender: ArcWrapped>, + receiver: Wrapped>, + } + + extern "C" { + pub fn server_context_service_real_show_keyboard(imservice: *const UIManager); + pub fn server_context_service_real_hide_keyboard(imservice: *const UIManager); + // This should probably only get called from the gtk main loop, + // given that dbus handler is using glib. + pub fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8); + } + + #[no_mangle] + pub extern "C" + fn main_loop_channel_new() -> Channel { + let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT); + let sender = ArcWrapped::new(sender); + let receiver = Wrapped::new(receiver); + let channel = Channel { + sender, + receiver, + }; + + //start_work(channel.sender.clone()); + + channel + } + + /// testing only + fn start_work(sender: ArcWrapped>) { + let sender = sender.clone_ref(); + thread::spawn(move || { + let sender = sender.lock().unwrap(); + thread::sleep(Duration::from_secs(3)); + sender.send(Message::Visible).unwrap(); + thread::sleep(Duration::from_secs(3)); + sender.send(Message::Hidden).unwrap(); + thread::sleep(Duration::from_secs(3)); + sender.send(Message::Visible).unwrap(); + }); + } + + /// Places the UI loop callback in the glib main loop. + #[no_mangle] + pub extern "C" + fn register_ui_loop_handler( + receiver: Wrapped>, + ui_manager: *const UIManager, + dbus_handler: *const DBusHandler, + ) { + let receiver = unsafe { receiver.unwrap() }; + let receiver = Rc::try_unwrap(receiver).expect("References still present"); + let receiver = receiver.into_inner(); + let ctx = MainContext::default(); + ctx.acquire(); + receiver.attach( + Some(&ctx), + move |msg| { + main_loop_handle_message(msg, ui_manager, dbus_handler); + Continue(true) + }, + ); + ctx.release(); + } + + /// A single iteration of the UI loop. + /// Applies state outcomes to external portions of the program. + /// This is the outest layer of the imperative shell, + /// and doesn't lend itself to testing other than integration. + fn main_loop_handle_message( + msg: Message, + ui_manager: *const UIManager, + dbus_handler: *const DBusHandler, + ) { + match msg { + Message::Visible => unsafe { + // FIXME: reset layout to default if no IM field is active + // Ideally: anim state stores the current IM hints, + // Message::Visible(hints) is received here + // and applied to layout + server_context_service_real_show_keyboard(ui_manager); + dbus_handler_set_visible(dbus_handler, 1); + }, + Message::Hidden => unsafe { + server_context_service_real_hide_keyboard(ui_manager); + dbus_handler_set_visible(dbus_handler, 0); + }, + }; + } +} diff --git a/src/server-context-service.c b/src/server-context-service.c index 573dfd32..77cb66dc 100644 --- a/src/server-context-service.c +++ b/src/server-context-service.c @@ -30,7 +30,6 @@ enum { PROP_0, - PROP_VISIBLE, PROP_ENABLED, PROP_LAST }; @@ -45,10 +44,8 @@ struct _ServerContextService { struct ui_manager *manager; // unowned struct vis_manager *vis_manager; // owned - gboolean visible; PhoshLayerSurface *window; GtkWidget *widget; // nullable - guint hiding; guint last_requested_height; }; @@ -67,23 +64,6 @@ on_destroy (ServerContextService *self, GtkWidget *widget) //eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context)); } -static void -on_notify_map (ServerContextService *self, GtkWidget *widget) -{ - g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self)); - - g_object_set (self, "visible", TRUE, NULL); -} - - -static void -on_notify_unmap (ServerContextService *self, GtkWidget *widget) -{ - g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self)); - - g_object_set (self, "visible", FALSE, NULL); -} - static uint32_t calculate_height(int32_t width, GdkRectangle *geometry) { @@ -187,8 +167,6 @@ make_window (ServerContextService *self) g_object_connect (self->window, "swapped-signal::destroy", G_CALLBACK(on_destroy), self, - "swapped-signal::map", G_CALLBACK(on_notify_map), self, - "swapped-signal::unmap", G_CALLBACK(on_notify_unmap), self, "swapped-signal::configured", G_CALLBACK(on_surface_configure), self, NULL); @@ -225,7 +203,7 @@ make_widget (ServerContextService *self) gtk_widget_show_all(self->widget); } -static void +void server_context_service_real_show_keyboard (ServerContextService *self) { if (!self->window) { @@ -234,92 +212,13 @@ server_context_service_real_show_keyboard (ServerContextService *self) if (!self->widget) { make_widget (self); } - self->visible = TRUE; gtk_widget_show (GTK_WIDGET(self->window)); } -static gboolean -show_keyboard_source_func(ServerContextService *context) -{ - server_context_service_real_show_keyboard(context); - return G_SOURCE_REMOVE; -} - -static void +void server_context_service_real_hide_keyboard (ServerContextService *self) { gtk_widget_hide (GTK_WIDGET(self->window)); - self->visible = FALSE; -} - -static gboolean -hide_keyboard_source_func(ServerContextService *context) -{ - server_context_service_real_hide_keyboard(context); - return G_SOURCE_REMOVE; -} - -static gboolean -on_hide (ServerContextService *self) -{ - server_context_service_real_hide_keyboard(self); - self->hiding = 0; - - return G_SOURCE_REMOVE; -} - -static void -server_context_service_show_keyboard (ServerContextService *self) -{ - g_return_if_fail (SERVER_IS_CONTEXT_SERVICE(self)); - - if (self->hiding) { - g_source_remove (self->hiding); - self->hiding = 0; - } - - if (!self->visible) { - g_idle_add((GSourceFunc)show_keyboard_source_func, self); - } -} - -void -server_context_service_force_show_keyboard (ServerContextService *self) -{ - if (!submission_hint_available(self->submission)) { - eekboard_context_service_set_hint_purpose( - self->state, - ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, - ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NORMAL - ); - } - server_context_service_show_keyboard(self); -} - -void -server_context_service_hide_keyboard (ServerContextService *self) -{ - g_return_if_fail (SERVER_IS_CONTEXT_SERVICE(self)); - - if (self->visible) { - g_idle_add((GSourceFunc)hide_keyboard_source_func, self); - } -} - -/// Meant for use by the input-method handler: -/// the visible keyboard is no longer needed. -/// The implementation will delay it slightly, -/// because the release may be due to switching from one text field to another. -/// In this case, the user doesn't really need the keyboard surface -/// to disappear completely. -void -server_context_service_release_visibility (ServerContextService *self) -{ - g_return_if_fail (SERVER_IS_CONTEXT_SERVICE(self)); - - if (!self->hiding && self->visible) { - self->hiding = g_timeout_add (200, (GSourceFunc) on_hide, self); - } } static void @@ -338,9 +237,6 @@ server_context_service_set_property (GObject *object, ServerContextService *self = SERVER_CONTEXT_SERVICE(object); switch (prop_id) { - case PROP_VISIBLE: - self->visible = g_value_get_boolean (value); - break; case PROP_ENABLED: server_context_service_set_physical_keyboard_present (self, !g_value_get_boolean (value)); break; @@ -356,11 +252,7 @@ server_context_service_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - ServerContextService *self = SERVER_CONTEXT_SERVICE(object); switch (prop_id) { - case PROP_VISIBLE: - g_value_set_boolean (value, self->visible); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -388,18 +280,6 @@ server_context_service_class_init (ServerContextServiceClass *klass) gobject_class->get_property = server_context_service_get_property; gobject_class->dispose = server_context_service_dispose; - /** - * Flag to indicate if keyboard is visible or not. - */ - pspec = g_param_spec_boolean ("visible", - "Visible", - "Visible", - FALSE, - G_PARAM_READWRITE); - g_object_class_install_property (gobject_class, - PROP_VISIBLE, - pspec); - /** * ServerContextServie:keyboard: * @@ -452,13 +332,3 @@ server_context_service_new (EekboardContextService *self, struct submission *sub init(ui); return ui; } - -void -server_context_service_update_visible (ServerContextService *self, gboolean visible) { - if (visible) { - server_context_service_show_keyboard(self); - } else { - server_context_service_hide_keyboard(self); - } -} - diff --git a/src/server-main.c b/src/server-main.c index 32923e8a..18c016a2 100644 --- a/src/server-main.c +++ b/src/server-main.c @@ -25,10 +25,12 @@ #include "config.h" +#include "animation.h" #include "eek/eek.h" #include "eekboard/eekboard-context-service.h" #include "dbus.h" #include "layout.h" +#include "main.h" #include "outputs.h" #include "submission.h" #include "server-context-service.h" @@ -375,6 +377,11 @@ main (int argc, char **argv) g_warning("Wayland input method interface not available"); } + + struct channel ui_channel = main_loop_channel_new(); + + struct squeek_animation_visibility_manager *animman = squeek_animation_visibility_manager_new(ui_channel.sender); + instance.ui_manager = squeek_uiman_new(); instance.settings_context = eekboard_context_service_new(&instance.layout_choice); @@ -394,7 +401,7 @@ main (int argc, char **argv) guint owner_id = 0; DBusHandler *service = NULL; if (connection) { - service = dbus_handler_new(connection, DBUS_SERVICE_PATH); + service = dbus_handler_new(connection, DBUS_SERVICE_PATH, animman); if (service == NULL) { g_printerr ("Can't create dbus server\n"); @@ -415,7 +422,7 @@ main (int argc, char **argv) } } - struct vis_manager *vis_manager = squeek_visman_new(); + struct vis_manager *vis_manager = squeek_visman_new(animman); instance.submission = get_submission(instance.wayland.input_method_manager, instance.wayland.virtual_keyboard_manager, @@ -435,13 +442,9 @@ main (int argc, char **argv) g_error("Could not initialize GUI"); exit(1); } - instance.ui_context = ui_context; - squeek_visman_set_ui(vis_manager, instance.ui_context); - if (instance.dbus_handler) { - dbus_handler_set_ui_context(instance.dbus_handler, instance.ui_context); - } - eekboard_context_service_set_ui(instance.settings_context, instance.ui_context); + instance.ui_context = ui_context; + register_ui_loop_handler(ui_channel.receiver, instance.ui_context, instance.dbus_handler); session_register(); diff --git a/src/ui_manager.h b/src/ui_manager.h index 0f95e2e4..ff3bab3a 100644 --- a/src/ui_manager.h +++ b/src/ui_manager.h @@ -3,6 +3,7 @@ #include +#include "animation.h" #include "eek/eek-types.h" #include "outputs.h" @@ -14,7 +15,6 @@ uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman); struct vis_manager; -struct vis_manager *squeek_visman_new(void); -void squeek_visman_set_ui(struct vis_manager *visman, ServerContextService *ui_context); +struct vis_manager *squeek_visman_new(struct squeek_animation_visibility_manager *animman); void squeek_visman_set_keyboard_present(struct vis_manager *visman, uint32_t keyboard_present); #endif diff --git a/src/ui_manager.rs b/src/ui_manager.rs index 3aa3f0b1..a4c30386 100644 --- a/src/ui_manager.rs +++ b/src/ui_manager.rs @@ -5,45 +5,41 @@ /*! Centrally manages the shape of the UI widgets, and the choice of layout. * * Coordinates this based on information collated from all possible sources. + * + * Somewhat obsoleted by the `animation` module + * (except keyboard presence calculation), + * and could be folded into that tracker loop as another piece of state. */ +use crate::animation::{ + ThreadLoopDriver as Receiver, + Event as ReceiverMessage, +}; +use crate::logging; use std::cmp::min; use ::outputs::c::OutputHandle; + +use crate::logging::Warn; + + pub mod c { use super::*; - use std::os::raw::c_void; use ::util::c::Wrapped; + + use ::util::CloneOwned; - /// ServerContextService* - #[repr(transparent)] - pub struct UIManager(*const c_void); - - extern "C" { - pub fn server_context_service_update_visible(imservice: *const UIManager, active: u32); - pub fn server_context_service_release_visibility(imservice: *const UIManager); - } - #[no_mangle] pub extern "C" - fn squeek_visman_new() -> Wrapped { + fn squeek_visman_new(receiver: Wrapped) -> Wrapped { Wrapped::new(VisibilityManager { - ui_manager: None, + receiver: receiver.clone_owned(), visibility_state: VisibilityFactors { im_active: false, physical_keyboard_present: false, } }) } - - /// Use to initialize the UI reference - #[no_mangle] - pub extern "C" - fn squeek_visman_set_ui(visman: Wrapped, ui_manager: *const UIManager) { - let visman = visman.clone_ref(); - let mut visman = visman.borrow_mut(); - visman.set_ui_manager(Some(ui_manager)) - } #[no_mangle] pub extern "C" @@ -169,48 +165,24 @@ impl VisibilityFactors { } } -// Temporary struct for migration. Should be integrated with Manager eventually. pub struct VisibilityManager { - /// Owned reference. Be careful, it's shared with C at large - ui_manager: Option<*const c::UIManager>, + /// Forward changes there. + receiver: Receiver, visibility_state: VisibilityFactors, } impl VisibilityManager { - fn set_ui_manager(&mut self, ui_manager: Option<*const c::UIManager>) { - let new = VisibilityManager { - ui_manager, - ..unsafe { self.clone() } - }; - self.apply_changes(new); - } - fn apply_changes(&mut self, new: Self) { - if let Some(ui) = &new.ui_manager { - if self.ui_manager.is_none() { - // Previous state was never applied, so effectively undefined. - // Just apply the new one. - let new_state = new.visibility_state.desired(); - unsafe { - c::server_context_service_update_visible( - *ui, - (new_state == Visibility::Visible) as u32, - ); - } - } else { - match self.visibility_state.transition_to(&new.visibility_state) { - VisibilityTransition::Hide => unsafe { - c::server_context_service_update_visible(*ui, 0); - }, - VisibilityTransition::Show => unsafe { - c::server_context_service_update_visible(*ui, 1); - }, - VisibilityTransition::Release => unsafe { - c::server_context_service_release_visibility(*ui); - }, - VisibilityTransition::NoTransition => {} - } - } + let request = match self.visibility_state.transition_to(&new.visibility_state) { + VisibilityTransition::Hide => Some(ReceiverMessage::ForceHide), + VisibilityTransition::Show => Some(ReceiverMessage::ClaimVisible), + VisibilityTransition::Release => Some(ReceiverMessage::ReleaseVisible), + VisibilityTransition::NoTransition => None, + }; + + if let Some(request) = request { + new.receiver.send(request) + .or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to animation manager"); } *self = new; } @@ -237,11 +209,10 @@ impl VisibilityManager { self.apply_changes(new); } - /// The struct is not really safe to clone due to the ui_manager reference. /// This is only a helper for getting desired visibility. unsafe fn clone(&self) -> Self { VisibilityManager { - ui_manager: self.ui_manager.clone(), + receiver: self.receiver.clone(), visibility_state: self.visibility_state.clone(), } }