diff --git a/eek/eek-gtk-keyboard.c b/eek/eek-gtk-keyboard.c index 463ae2a8..19b0837d 100644 --- a/eek/eek-gtk-keyboard.c +++ b/eek/eek-gtk-keyboard.c @@ -55,6 +55,8 @@ typedef struct _EekGtkKeyboardPrivate GdkEventSequence *sequence; // unowned reference LfbEvent *event; + + gulong kb_signal; } EekGtkKeyboardPrivate; G_DEFINE_TYPE_WITH_PRIVATE (EekGtkKeyboard, eek_gtk_keyboard, GTK_TYPE_DRAWING_AREA) @@ -307,12 +309,19 @@ eek_gtk_keyboard_set_property (GObject *object, } } +// This may actually get called multiple times in a row +// if both a parent object and its parent get destroyed static void eek_gtk_keyboard_dispose (GObject *object) { EekGtkKeyboard *self = EEK_GTK_KEYBOARD (object); EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); + if (priv->kb_signal != 0) { + g_signal_handler_disconnect(priv->eekboard_context, priv->kb_signal); + priv->kb_signal = 0; + } + if (priv->renderer) { eek_renderer_free(priv->renderer); priv->renderer = NULL; @@ -424,7 +433,7 @@ eek_gtk_keyboard_new (EekboardContextService *eekservice, }; priv->render_geometry = initial_geometry; - g_signal_connect (eekservice, + priv->kb_signal = g_signal_connect (eekservice, "notify::keyboard", G_CALLBACK(on_notify_keyboard), ret); diff --git a/eekboard/eekboard-context-service.c b/eekboard/eekboard-context-service.c index fa3e00e7..ec1ac005 100644 --- a/eekboard/eekboard-context-service.c +++ b/eekboard/eekboard-context-service.c @@ -60,10 +60,10 @@ struct _EekboardContextService { LevelKeyboard *keyboard; // currently used keyboard GSettings *settings; // Owned reference - // Maybe TODO: it's used only for fetching layout type. - // Maybe let UI push the type to this structure? - ServerContextService *ui; // unowned reference - /// Needed for keymap changes after keyboard updates + /// Needed for keymap changes after keyboard updates. + // TODO: can the main loop access submission to change the key maps instead? + // This should probably land together with passing buttons through state, + // to avoid race conditions between setting buttons and key maps. struct submission *submission; // unowned }; @@ -297,6 +297,8 @@ eekboard_context_service_get_keyboard (EekboardContextService *context) return context->keyboard; } +// Used from Rust. +// TODO: move hint management to Rust entirely void eekboard_context_service_set_hint_purpose(EekboardContextService *context, uint32_t hint, uint32_t purpose) { @@ -340,7 +342,3 @@ void eekboard_context_service_set_submission(EekboardContextService *context, st submission_use_layout(context->submission, context->keyboard->layout, time); } } - -void eekboard_context_service_set_ui(EekboardContextService *context, ServerContextService *ui) { - context->ui = ui; -} diff --git a/eekboard/eekboard-context-service.h b/eekboard/eekboard-context-service.h index 345c246e..4b2e706a 100644 --- a/eekboard/eekboard-context-service.h +++ b/eekboard/eekboard-context-service.h @@ -39,16 +39,12 @@ G_DECLARE_FINAL_TYPE(EekboardContextService, eekboard_context_service, EEKBOARD, EekboardContextService *eekboard_context_service_new(struct squeek_layout_state *state); void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission); -void eekboard_context_service_set_ui(EekboardContextService *context, ServerContextService *ui); void eekboard_context_service_destroy (EekboardContextService *context); LevelKeyboard *eekboard_context_service_get_keyboard(EekboardContextService *context); void eekboard_context_service_set_keymap(EekboardContextService *context, const LevelKeyboard *keyboard); -void eekboard_context_service_set_hint_purpose(EekboardContextService *context, - uint32_t hint, - uint32_t purpose); void eekboard_context_service_use_layout(EekboardContextService *context, struct squeek_layout_state *layout, uint32_t timestamp); G_END_DECLS diff --git a/src/animation.rs b/src/animation.rs index b13ad37b..2ca9b06f 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -6,8 +6,8 @@ use std::time::Duration; -use crate::main::PixelSize; use crate::outputs::OutputId; +use crate::panel::PixelSize; /// The keyboard should hide after this has elapsed to prevent flickering. pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200); diff --git a/src/event_loop/mod.rs b/src/event_loop/mod.rs index 118bb082..7a494aca 100644 --- a/src/event_loop/mod.rs +++ b/src/event_loop/mod.rs @@ -151,7 +151,7 @@ mod test { use super::*; use crate::animation; use crate::imservice::{ ContentHint, ContentPurpose }; - use crate::main::PanelCommand; + use crate::panel; use crate::state::{ Application, InputMethod, InputMethodDetails, Presence, visibility }; use crate::state::test::application_with_fake_output; @@ -176,13 +176,13 @@ mod test { let l = State::new(state, now); let (l, commands) = handle_event(l, InputMethod::InactiveSince(now).into(), now); - assert_matches!(commands.panel_visibility, Some(PanelCommand::Show{..})); + assert_matches!(commands.panel_visibility, Some(panel::Command::Show{..})); assert_eq!(l.scheduled_wakeup, Some(now + animation::HIDING_TIMEOUT)); now += animation::HIDING_TIMEOUT; let (l, commands) = handle_event(l, Event::TimeoutReached(now), now); - assert_eq!(commands.panel_visibility, Some(PanelCommand::Hide)); + assert_eq!(commands.panel_visibility, Some(panel::Command::Hide)); assert_eq!(l.scheduled_wakeup, None); } } diff --git a/src/imservice.c b/src/imservice.c index 3797941e..bca54308 100644 --- a/src/imservice.c +++ b/src/imservice.c @@ -1,6 +1,7 @@ -#include "submission.h" +#include "input-method-unstable-v2-client-protocol.h" +#include "virtual-keyboard-unstable-v1-client-protocol.h" -#include +#include "submission.h" struct imservice; diff --git a/src/lib.rs b/src/lib.rs index 618f5a06..38b7944c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,6 +36,7 @@ mod locale; mod main; mod manager; mod outputs; +mod panel; mod popover; mod resources; mod state; diff --git a/src/main.h b/src/main.h index b6ea1cbf..9b1c14d1 100644 --- a/src/main.h +++ b/src/main.h @@ -8,6 +8,7 @@ #include "eek/eek-types.h" #include "dbus.h" +#include "panel.h" struct receiver; @@ -24,7 +25,7 @@ struct rsobjects { struct squeek_wayland *wayland; }; -void register_ui_loop_handler(struct receiver *receiver, ServerContextService *ui, DBusHandler *dbus_handler); +void register_ui_loop_handler(struct receiver *receiver, struct panel_manager *panel, EekboardContextService *hint_manager, DBusHandler *dbus_handler); struct rsobjects squeek_init(void); diff --git a/src/main.rs b/src/main.rs index d78ff767..47ac7982 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ */ /*! Glue for the main loop. */ -use crate::outputs::OutputId; +use crate::panel; use crate::debug; use crate::state; use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver}; @@ -20,19 +20,21 @@ mod c { use crate::imservice::IMService; use crate::imservice::c::InputMethod; use crate::outputs::Outputs; - use crate::outputs::c::WlOutput; use crate::state; use crate::submission::Submission; use crate::util::c::Wrapped; use crate::vkeyboard::c::ZwpVirtualKeyboardV1; - /// ServerContextService* - #[repr(transparent)] - pub struct UIManager(*const c_void); - /// DbusHandler* #[repr(transparent)] pub struct DBusHandler(*const c_void); + + /// EekboardContextService* in the role of a hint receiver + // The clone/copy is a concession to C style of programming. + // It would be hard to get rid of it. + #[repr(transparent)] + #[derive(Clone, Copy)] + pub struct HintManager(*const c_void); /// Holds the Rust structures that are interesting from C. #[repr(C)] @@ -76,9 +78,7 @@ mod c { extern "C" { #[allow(improper_ctypes)] fn init_wayland(wayland: *mut Wayland); - fn server_context_service_update_keyboard(service: *const UIManager, output: WlOutput, scaled_height: u32); - fn server_context_service_real_hide_keyboard(service: *const UIManager); - fn server_context_service_set_hint_purpose(service: *const UIManager, hint: u32, purpose: u32); + fn eekboard_context_service_set_hint_purpose(service: HintManager, hint: u32, purpose: u32); // This should probably only get called from the gtk main loop, // given that dbus handler is using glib. fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8); @@ -124,18 +124,20 @@ mod c { pub extern "C" fn register_ui_loop_handler( receiver: Wrapped>, - ui_manager: *const UIManager, + panel_manager: panel::c::PanelManager, + hint_manager: HintManager, 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 panel_manager = Wrapped::new(panel::Manager::new(panel_manager)); let ctx = MainContext::default(); let _acqu = ctx.acquire(); receiver.attach( Some(&ctx), move |msg| { - main_loop_handle_message(msg, ui_manager, dbus_handler); + main_loop_handle_message(msg, panel_manager.clone(), hint_manager, dbus_handler); Continue(true) }, ); @@ -149,18 +151,13 @@ mod c { /// and doesn't lend itself to testing other than integration. fn main_loop_handle_message( msg: Commands, - ui_manager: *const UIManager, + panel_manager: Wrapped, + hint_manager: HintManager, dbus_handler: *const DBusHandler, ) { - match msg.panel_visibility { - Some(PanelCommand::Show { output, height }) => unsafe { - server_context_service_update_keyboard(ui_manager, output.0, height.as_scaled_ceiling()); - }, - Some(PanelCommand::Hide) => unsafe { - server_context_service_real_hide_keyboard(ui_manager); - }, - None => {}, - }; + if let Some(visibility) = msg.panel_visibility { + panel::Manager::update(panel_manager, visibility); + } if let Some(visible) = msg.dbus_visible_set { if dbus_handler != std::ptr::null() { @@ -170,8 +167,8 @@ mod c { if let Some(hints) = msg.layout_hint_set { unsafe { - server_context_service_set_hint_purpose( - ui_manager, + eekboard_context_service_set_hint_purpose( + hint_manager, hints.hint.bits(), hints.purpose.clone() as u32, ) @@ -180,42 +177,11 @@ mod c { } } -/// Size in pixels that is aware of scaling -#[derive(Clone, Copy, PartialEq, Debug)] -pub struct PixelSize { - pub pixels: u32, - pub scale_factor: u32, -} - -fn div_ceil(a: u32, b: u32) -> u32 { - // Given that it's for pixels on a screen, an overflow is unlikely. - (a + b - 1) / b -} - -impl PixelSize { - pub fn as_scaled_floor(&self) -> u32 { - self.pixels / self.scale_factor - } - - pub fn as_scaled_ceiling(&self) -> u32 { - div_ceil(self.pixels, self.scale_factor) - } -} - -#[derive(Clone, PartialEq, Debug)] -pub enum PanelCommand { - Show { - output: OutputId, - height: PixelSize, - }, - Hide, -} - /// The commands consumed by the main loop, /// to be sent out to external components. #[derive(Clone)] pub struct Commands { - pub panel_visibility: Option, + pub panel_visibility: Option, pub layout_hint_set: Option, pub dbus_visible_set: Option, } diff --git a/src/meson.build b/src/meson.build index d85be960..7b2226e7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,6 +14,7 @@ sources = [ config_h, 'dbus.c', 'imservice.c', + 'panel.c', 'popover.c', 'server-context-service.c', 'wayland.c', diff --git a/src/panel.c b/src/panel.c new file mode 100644 index 00000000..6dfb69c9 --- /dev/null +++ b/src/panel.c @@ -0,0 +1,130 @@ +#include "eekboard/eekboard-context-service.h" +#include "wayland.h" +#include "panel.h" + + +// Called from rust +/// Destroys the widget +void +panel_manager_hide(struct panel_manager *self) +{ + if (self->window) { + gtk_widget_destroy (GTK_WIDGET (self->window)); + } + if (self->widget) { + gtk_widget_destroy (GTK_WIDGET (self->widget)); + } + self->window = NULL; + self->widget = NULL; +} + +static void +on_destroy (struct panel_manager *self, GtkWidget *widget) +{ + g_assert (widget == GTK_WIDGET(self->window)); + panel_manager_hide(self); +} + + +/// panel::Manager. Only needed for this callback +struct squeek_panel_manager; + +/// Calls back into Rust +void squeek_panel_manager_configured(struct squeek_panel_manager *mgr, uint32_t width, uint32_t height); + +static void +on_surface_configure(struct squeek_panel_manager *self, PhoshLayerSurface *surface) +{ + gint width; + gint height; + g_return_if_fail (PHOSH_IS_LAYER_SURFACE (surface)); + + g_object_get(G_OBJECT(surface), + "configured-width", &width, + "configured-height", &height, + NULL); + squeek_panel_manager_configured(self, width, height); +} + +static void +make_widget (struct panel_manager *self) +{ + if (self->widget) { + g_error("Widget already present"); + } + self->widget = eek_gtk_keyboard_new (self->state, self->submission, self->layout); + + gtk_widget_set_has_tooltip (self->widget, TRUE); + gtk_container_add (GTK_CONTAINER(self->window), self->widget); + gtk_widget_show_all(self->widget); +} + + +// Called from rust +/// Creates a new panel widget +void +panel_manager_request_widget (struct panel_manager *self, struct wl_output *output, uint32_t height, struct squeek_panel_manager *mgr) +{ + if (self->window) { + g_error("Window already present"); + } + + self->window = g_object_new ( + PHOSH_TYPE_LAYER_SURFACE, + "layer-shell", squeek_wayland->layer_shell, + "wl-output", output, + "height", height, + "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM + | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, + "layer", ZWLR_LAYER_SHELL_V1_LAYER_TOP, + "kbd-interactivity", FALSE, + "exclusive-zone", height, + "namespace", "osk", + NULL + ); + + g_object_connect (self->window, + "swapped-signal::destroy", G_CALLBACK(on_destroy), self, + "swapped-signal::configured", G_CALLBACK(on_surface_configure), mgr, + NULL); + + // The properties below are just to make hacking easier. + // The way we use layer-shell overrides some, + // and there's no space in the protocol for others. + // Those may still be useful in the future, + // or for hacks with regular windows. + gtk_widget_set_can_focus (GTK_WIDGET(self->window), FALSE); + g_object_set (G_OBJECT(self->window), "accept_focus", FALSE, NULL); + gtk_window_set_title (GTK_WINDOW(self->window), "Squeekboard"); + gtk_window_set_icon_name (GTK_WINDOW(self->window), "squeekboard"); + gtk_window_set_keep_above (GTK_WINDOW(self->window), TRUE); + + make_widget(self); + + gtk_widget_show (GTK_WIDGET(self->window)); +} + +// Called from rust +/// Updates the size +void +panel_manager_resize (struct panel_manager *self, uint32_t height) +{ + phosh_layer_surface_set_size(self->window, 0, height); + phosh_layer_surface_set_exclusive_zone(self->window, height); + phosh_layer_surface_wl_surface_commit(self->window); +} + + +struct panel_manager panel_manager_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout) +{ + struct panel_manager mgr = { + .state = state, + .submission = submission, + .layout = layout, + .window = NULL, + .widget = NULL, + .current_output = NULL, + }; + return mgr; +} diff --git a/src/panel.h b/src/panel.h new file mode 100644 index 00000000..5d0ae615 --- /dev/null +++ b/src/panel.h @@ -0,0 +1,21 @@ +#pragma once + +#include "eek/layersurface.h" +#include "src/layout.h" +#include "src/submission.h" + +// Stores the objects that the panel and its widget will refer to +struct panel_manager { + EekboardContextService *state; // unowned + /// Needed for instantiating the widget + struct submission *submission; // unowned + struct squeek_layout_state *layout; + + PhoshLayerSurface *window; + GtkWidget *widget; // nullable + + // Those should be held in Rust + struct wl_output *current_output; +}; + +struct panel_manager panel_manager_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout); diff --git a/src/panel.rs b/src/panel.rs new file mode 100644 index 00000000..53d95594 --- /dev/null +++ b/src/panel.rs @@ -0,0 +1,248 @@ +/* Copyright (C) 2022 Purism SPC + * SPDX-License-Identifier: GPL-3.0+ + */ + +/*! Panel state management. + * + * This is effectively a mirror of the previous C code, + * with an explicit state machine managing the panel size. + * + * It still relies on a callback from Wayland to accept the panel size, + * which makes this code somewhat prone to mistakes. + * + * An alternative to the callback would be + * to send a message all the way to `state::State` + * every time the allocated size changes. + * That would allow for a more holistic view + * of interactions of different pieces of state. + * + * However, `state::State` already has the potential to become a ball of mud, + * tightly coupling different functionality and making it difficult to see independent units. + * + * For this reason, I'm taking a light touch approach with the panel manager, + * and moving it just a bit closer to `state::State`. + * Hopefully ths still allows us to expose assumptions that were not stated yet + * (e.g. can the output disappear between size request andallocation?). + * + * Tight coupling, e.g. a future one between presented hints and layout size, + * will have to be taken into account later. + */ + +use crate::logging; +use crate::outputs::OutputId; +use crate::util::c::Wrapped; + + +pub mod c { + use super::*; + use glib; + use gtk::Continue; + use std::os::raw::c_void; + + use crate::outputs::c::WlOutput; + + /// struct panel_manager* + #[repr(transparent)] + #[derive(Clone, Copy)] + pub struct PanelManager(*const c_void); + + extern "C" { + #[allow(improper_ctypes)] + pub fn panel_manager_request_widget( + service: PanelManager, + output: WlOutput, + height: u32, + // for callbacks + panel: Wrapped, + ); + pub fn panel_manager_resize(service: PanelManager, height: u32); + pub fn panel_manager_hide(service: PanelManager); + } + + #[no_mangle] + pub extern "C" + fn squeek_panel_manager_configured(panel: Wrapped, width: u32, height: u32) { + // This is why this needs to be moved into state::State: + // it's getting too coupled to glib. + glib::idle_add_local(move || { + let panel = panel.clone_ref(); + panel.borrow_mut().set_configured(Size{width, height}); + Continue(false) + }); + } +} + + +/// Size in pixels that is aware of scaling +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct PixelSize { + pub pixels: u32, + pub scale_factor: u32, +} + +fn div_ceil(a: u32, b: u32) -> u32 { + // Given that it's for pixels on a screen, an overflow is unlikely. + (a + b - 1) / b +} + +impl PixelSize { + pub fn as_scaled_floor(&self) -> u32 { + self.pixels / self.scale_factor + } + + pub fn as_scaled_ceiling(&self) -> u32 { + div_ceil(self.pixels, self.scale_factor) + } +} + +#[derive(Clone, Debug)] +struct Size { + width: u32, + height: u32, +} + +/// This state requests the Wayland layer shell protocol synchronization: +/// the application asks for some size, +/// and then receives a size that the compositor thought appropriate. +/// Stores raw values passed to Wayland, i.e. scaled dimensions. +#[derive(Clone, Debug)] +enum State { + Hidden, + SizeRequested { + output: OutputId, + height: u32, + //width: u32, + }, + SizeAllocated { + output: OutputId, + wanted_height: u32, + allocated: Size, + }, +} + +#[derive(Clone, PartialEq, Debug)] +pub enum Command { + Show { + output: OutputId, + height: PixelSize, + }, + Hide, +} + +/// Tries to contain all the panel sizing duties. +pub struct Manager { + panel: c::PanelManager, + state: State, +} + +impl Manager { + pub fn new(panel: c::PanelManager) -> Self { + Self { + panel, + state: State::Hidden, + } + } + // TODO: mabe send the allocated size back to state::State, + // to perform layout adjustments + fn set_configured(&mut self, size: Size) { + self.state = match self.state.clone() { + State::Hidden => { + // This may happen if a hide is scheduled immediately after a show. + log_print!( + logging::Level::Surprise, + "Panel has been configured, but no request is pending. Ignoring", + ); + State::Hidden + }, + State::SizeAllocated{output, wanted_height, ..} => { + log_print!( + logging::Level::Surprise, + "Panel received new configuration without asking", + ); + State::SizeAllocated{output, wanted_height, allocated: size} + }, + State::SizeRequested{output, height} => State::SizeAllocated { + output, + wanted_height: height, + allocated: size, + }, + }; + } + + pub fn update(mgr: Wrapped, cmd: Command) { + let copied = mgr.clone(); + + let mgr = mgr.clone_ref(); + let mut mgr = mgr.borrow_mut(); + + (*mgr).state = match (cmd, mgr.state.clone()) { + (Command::Hide, State::Hidden) => State::Hidden, + (Command::Hide, State::SizeAllocated{..}) => { + unsafe { c::panel_manager_hide(mgr.panel); } + State::Hidden + }, + (Command::Hide, State::SizeRequested{..}) => { + unsafe { c::panel_manager_hide(mgr.panel); } + State::Hidden + }, + (Command::Show{output, height}, State::Hidden) => { + let height = height.as_scaled_ceiling(); + unsafe { c::panel_manager_request_widget(mgr.panel, output.0, height, copied); } + State::SizeRequested{output, height} + }, + ( + Command::Show{output, height}, + State::SizeRequested{output: req_output, height: req_height}, + ) => { + let height = height.as_scaled_ceiling(); + if output == req_output && height == req_height { + State::SizeRequested{output: req_output, height: req_height} + } else if output == req_output { + // I'm not sure about that. + // This could cause a busy loop, + // when two requests are being processed at the same time: + // one message in the compositor to allocate size A, + // causing the state to update to height A' + // the other from the state wanting height B', + // causing the compositor to change size to B. + // So better cut this short here, despite artifacts. + // Out of simplicty, just ignore the new request. + // If that causes problems, the request in flight could be stored + // for the purpose of handling it better somehow. + State::SizeRequested{output: req_output, height: req_height} + } else { + // This looks weird, but should be safe. + // The stack seems to handle + // configure events on a dead surface. + unsafe { + c::panel_manager_hide(mgr.panel); + c::panel_manager_request_widget(mgr.panel, output.0, height, copied); + } + State::SizeRequested{output, height} + } + }, + ( + Command::Show{output, height}, + State::SizeAllocated{output: alloc_output, allocated, wanted_height}, + ) => { + let height = height.as_scaled_ceiling(); + if output == alloc_output && height == wanted_height { + State::SizeAllocated{output: alloc_output, wanted_height, allocated} + } else if output == alloc_output && height == allocated.height { + State::SizeAllocated{output: alloc_output, wanted_height: height, allocated} + } else if output == alloc_output { + // Should *all* other heights cause a resize? + // What about those between wanted and allocated? + unsafe { c::panel_manager_resize(mgr.panel, height); } + State::SizeRequested{output, height} + } else { + unsafe { + c::panel_manager_hide(mgr.panel); + c::panel_manager_request_widget(mgr.panel, output.0, height, copied); + } + State::SizeRequested{output, height} + } + }, + } + } +} diff --git a/src/server-context-service.c b/src/server-context-service.c index 55e7f78d..debcb79b 100644 --- a/src/server-context-service.c +++ b/src/server-context-service.c @@ -20,14 +20,7 @@ #include #include -#include "eek/eek.h" -#include "eek/eek-gtk-keyboard.h" -#include "eek/layersurface.h" -#include "eekboard/eekboard-context-service.h" -#include "submission.h" -#include "wayland.h" #include "server-context-service.h" -#include "wayland-client-protocol.h" enum { PROP_0, @@ -37,149 +30,13 @@ enum { struct _ServerContextService { GObject parent; - - EekboardContextService *state; // unowned - /// Needed for instantiating the widget - struct submission *submission; // unowned - struct squeek_layout_state *layout; struct squeek_state_manager *state_manager; // shared reference - - PhoshLayerSurface *window; - GtkWidget *widget; // nullable - - struct wl_output *current_output; - guint last_requested_height; }; G_DEFINE_TYPE(ServerContextService, server_context_service, G_TYPE_OBJECT); static void -on_destroy (ServerContextService *self, GtkWidget *widget) -{ - g_return_if_fail (SERVER_IS_CONTEXT_SERVICE (self)); - - g_assert (widget == GTK_WIDGET(self->window)); - - self->window = NULL; - self->widget = NULL; - - //eekboard_context_service_destroy (EEKBOARD_CONTEXT_SERVICE (context)); -} - -static void -make_window (ServerContextService *self, struct wl_output *output, uint32_t height) -{ - if (self->window) { - g_error("Window already present"); - } - - self->window = g_object_new ( - PHOSH_TYPE_LAYER_SURFACE, - "layer-shell", squeek_wayland->layer_shell, - "wl-output", output, - "height", height, - "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM - | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT - | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, - "layer", ZWLR_LAYER_SHELL_V1_LAYER_TOP, - "kbd-interactivity", FALSE, - "exclusive-zone", height, - "namespace", "osk", - NULL - ); - - g_object_connect (self->window, - "swapped-signal::destroy", G_CALLBACK(on_destroy), self, - //"swapped-signal::configured", G_CALLBACK(on_surface_configure), self, - NULL); - - // The properties below are just to make hacking easier. - // The way we use layer-shell overrides some, - // and there's no space in the protocol for others. - // Those may still be useful in the future, - // or for hacks with regular windows. - gtk_widget_set_can_focus (GTK_WIDGET(self->window), FALSE); - g_object_set (G_OBJECT(self->window), "accept_focus", FALSE, NULL); - gtk_window_set_title (GTK_WINDOW(self->window), "Squeekboard"); - gtk_window_set_icon_name (GTK_WINDOW(self->window), "squeekboard"); - gtk_window_set_keep_above (GTK_WINDOW(self->window), TRUE); -} - -static void -destroy_window (ServerContextService *self) -{ - gtk_widget_destroy (GTK_WIDGET (self->window)); - self->window = NULL; -} - -static void -make_widget (ServerContextService *self) -{ - if (self->widget) { - gtk_widget_destroy(self->widget); - self->widget = NULL; - } - self->widget = eek_gtk_keyboard_new (self->state, self->submission, self->layout); - - gtk_widget_set_has_tooltip (self->widget, TRUE); - gtk_container_add (GTK_CONTAINER(self->window), self->widget); - gtk_widget_show_all(self->widget); -} - -// Called from rust -/// Updates the type of hiddenness -void -server_context_service_real_hide_keyboard (ServerContextService *self) -{ - //self->desired_height = 0; - self->current_output = NULL; - if (self->window) { - gtk_widget_hide (GTK_WIDGET(self->window)); - } -} - -// Called from rust -/// Updates the type of visibility. /// Height is in scaled units. -void -server_context_service_update_keyboard (ServerContextService *self, struct wl_output *output, uint32_t scaled_height) -{ - if (output != self->current_output) { - // Recreate on a new output - server_context_service_real_hide_keyboard(self); - } else { - gint h; - PhoshLayerSurface *surface = self->window; - g_object_get(G_OBJECT(surface), - "configured-height", &h, - NULL); - - if ((uint32_t)h != scaled_height) { - - //TODO: make sure that redrawing happens in the correct place (it doesn't now). - phosh_layer_surface_set_size(self->window, 0, scaled_height); - phosh_layer_surface_set_exclusive_zone(self->window, scaled_height); - phosh_layer_surface_wl_surface_commit(self->window); - - self->current_output = output; - - return; - } - } - - self->current_output = output; - - if (!self->window) { - make_window (self, output, scaled_height); - } - if (!self->widget) { - make_widget (self); - } - gtk_widget_show (GTK_WIDGET(self->window)); -} - - -static void server_context_service_set_property (GObject *object, guint prop_id, const GValue *value, @@ -210,17 +67,6 @@ server_context_service_get_property (GObject *object, } } -static void -server_context_service_dispose (GObject *object) -{ - ServerContextService *self = SERVER_CONTEXT_SERVICE(object); - - destroy_window (self); - self->widget = NULL; - - G_OBJECT_CLASS (server_context_service_parent_class)->dispose (object); -} - static void server_context_service_class_init (ServerContextServiceClass *klass) { @@ -229,7 +75,6 @@ server_context_service_class_init (ServerContextServiceClass *klass) gobject_class->set_property = server_context_service_set_property; gobject_class->get_property = server_context_service_get_property; - gobject_class->dispose = server_context_service_dispose; /** * ServerContextServie:keyboard: @@ -250,41 +95,29 @@ server_context_service_class_init (ServerContextServiceClass *klass) static void server_context_service_init (ServerContextService *self) {} -static void -init (ServerContextService *self) { + +ServerContextService * +server_context_service_new (struct squeek_state_manager *state_manager) +{ + ServerContextService *holder = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL); + holder->state_manager = state_manager; + const char *schema_name = "org.gnome.desktop.a11y.applications"; GSettingsSchemaSource *ssrc = g_settings_schema_source_get_default(); g_autoptr(GSettingsSchema) schema = NULL; if (!ssrc) { g_warning("No gsettings schemas installed."); - return; + return NULL; } schema = g_settings_schema_source_lookup(ssrc, schema_name, TRUE); if (schema) { g_autoptr(GSettings) settings = g_settings_new (schema_name); g_settings_bind (settings, "screen-keyboard-enabled", - self, "enabled", G_SETTINGS_BIND_GET); + holder, "enabled", G_SETTINGS_BIND_GET); } else { g_warning("Gsettings schema %s is not installed on the system. " "Enabling by default.", schema_name); } -} - -ServerContextService * -server_context_service_new (EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager) -{ - ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL); - ui->submission = submission; - ui->state = self; - ui->layout = layout; - ui->state_manager = state_manager; - init(ui); - return ui; -} - -// Used from Rust -void server_context_service_set_hint_purpose(ServerContextService *self, uint32_t hint, - uint32_t purpose) { - eekboard_context_service_set_hint_purpose(self->state, hint, purpose); + return holder; } diff --git a/src/server-context-service.h b/src/server-context-service.h index 599da818..c47b8c91 100644 --- a/src/server-context-service.h +++ b/src/server-context-service.h @@ -17,9 +17,9 @@ */ #ifndef SERVER_CONTEXT_SERVICE_H #define SERVER_CONTEXT_SERVICE_H 1 +#include -#include "src/layout.h" -#include "src/submission.h" +#include "main.h" G_BEGIN_DECLS @@ -28,8 +28,8 @@ G_BEGIN_DECLS /** Manages the lifecycle of the window displaying layouts. */ G_DECLARE_FINAL_TYPE (ServerContextService, server_context_service, SERVER, CONTEXT_SERVICE, GObject) -ServerContextService *server_context_service_new(EekboardContextService *self, struct submission *submission, struct squeek_layout_state *layout, struct squeek_state_manager *state_manager); -enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *); +ServerContextService *server_context_service_new(struct squeek_state_manager *state_manager); G_END_DECLS + #endif /* SERVER_CONTEXT_SERVICE_H */ diff --git a/src/server-main.c b/src/server-main.c index 57381ccf..4807053c 100644 --- a/src/server-main.c +++ b/src/server-main.c @@ -31,6 +31,7 @@ #include "layout.h" #include "main.h" #include "outputs.h" +#include "panel.h" #include "submission.h" #include "server-context-service.h" #include "wayland.h" @@ -51,8 +52,10 @@ typedef enum _SqueekboardDebugFlags { struct squeekboard { struct squeek_wayland wayland; // Just hooks. DBusHandler *dbus_handler; // Controls visibility of the OSK. - EekboardContextService *settings_context; // Gsettings hooks. - ServerContextService *ui_context; // mess, includes the entire UI + EekboardContextService *settings_context; // Gsettings hooks for layouts. + /// Gsettings hook for visibility. TODO: this does not belong in gsettings. + ServerContextService *settings_handler; + struct panel_manager panel_manager; // Controls the shape of the panel. /// Currently wanted layout. TODO: merge into state::Application struct squeek_layout_state layout_choice; }; @@ -435,20 +438,21 @@ main (int argc, char **argv) } } - eekboard_context_service_set_submission(instance.settings_context, rsobjects.submission); - - ServerContextService *ui_context = server_context_service_new( - instance.settings_context, - rsobjects.submission, - &instance.layout_choice, + ServerContextService *setting_listener = server_context_service_new( rsobjects.state_manager); - if (!ui_context) { - g_error("Could not initialize GUI"); - exit(1); + if (!setting_listener) { + g_warning ("could not connect to gsettings"); } - instance.ui_context = ui_context; - register_ui_loop_handler(rsobjects.receiver, instance.ui_context, instance.dbus_handler); + instance.settings_handler = setting_listener; + + eekboard_context_service_set_submission(instance.settings_context, rsobjects.submission); + + instance.panel_manager = panel_manager_new(instance.settings_context, + rsobjects.submission, + &instance.layout_choice); + + register_ui_loop_handler(rsobjects.receiver, &instance.panel_manager, instance.settings_context, instance.dbus_handler); session_register(); diff --git a/src/state.rs b/src/state.rs index 96ee3ff9..a9b7cca4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,9 +8,11 @@ use crate::animation; use crate::debug; use crate::imservice::{ ContentHint, ContentPurpose }; -use crate::main::{ Commands, PanelCommand, PixelSize }; +use crate::main::Commands; use crate::outputs; use crate::outputs::{Millimeter, OutputId, OutputState}; +use crate::panel; +use crate::panel::PixelSize; use crate::util::Rational; use std::cmp; use std::collections::HashMap; @@ -118,8 +120,8 @@ impl Outcome { // FIXME: handle switching outputs let (dbus_visible_set, panel_visibility) = match new_state.visibility { animation::Outcome::Visible{output, height} - => (Some(true), Some(PanelCommand::Show{output, height})), - animation::Outcome::Hidden => (Some(false), Some(PanelCommand::Hide)), + => (Some(true), Some(panel::Command::Show{output, height})), + animation::Outcome::Hidden => (Some(false), Some(panel::Command::Hide)), }; Commands { diff --git a/src/submission.h b/src/submission.h index 5ee2f727..184a5431 100644 --- a/src/submission.h +++ b/src/submission.h @@ -1,12 +1,12 @@ #ifndef __SUBMISSION_H #define __SUBMISSION_H -#include "input-method-unstable-v2-client-protocol.h" -#include "virtual-keyboard-unstable-v1-client-protocol.h" +#include "inttypes.h" + #include "eek/eek-types.h" -#include "main.h" struct squeek_layout; +struct submission; // Defined in Rust uint8_t submission_hint_available(struct submission *self);