diff --git a/src/lib.rs b/src/lib.rs index ba71686b..287b5286 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,5 +35,6 @@ mod style; mod submission; pub mod tests; pub mod util; +mod ui_manager; mod vkeyboard; mod xdg; diff --git a/src/outputs.h b/src/outputs.h index 38a207f6..e018d037 100644 --- a/src/outputs.h +++ b/src/outputs.h @@ -4,10 +4,14 @@ #include "wayland-client-protocol.h" struct squeek_outputs; +struct squeek_output_handle { + struct wl_output *output; + struct squeek_outputs *outputs; +}; struct squeek_outputs *squeek_outputs_new(); void squeek_outputs_free(struct squeek_outputs*); void squeek_outputs_register(struct squeek_outputs*, struct wl_output *output); -struct wl_output *squeek_outputs_get_current(struct squeek_outputs*); +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 08ac590d..29f4e78c 100644 --- a/src/outputs.rs +++ b/src/outputs.rs @@ -17,7 +17,7 @@ pub mod c { // Defined in C #[repr(transparent)] - #[derive(Clone, PartialEq)] + #[derive(Clone, PartialEq, Copy)] pub struct WlOutput(*const c_void); #[repr(C)] @@ -105,6 +105,24 @@ pub mod c { type COutputs = ::util::c::Wrapped; + /// A stable reference to an output. + #[derive(Clone)] + #[repr(C)] + pub struct OutputHandle { + wl_output: WlOutput, + outputs: COutputs, + } + + impl OutputHandle { + // Cannot return an Output reference + // because COutputs is too deeply wrapped + 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()) + } + } + // Defined in Rust extern fn outputs_handle_geometry( @@ -240,46 +258,15 @@ pub mod c { #[no_mangle] pub extern "C" - fn squeek_outputs_get_current(raw_collection: COutputs) -> WlOutput { + fn squeek_outputs_get_current(raw_collection: COutputs) -> OutputHandle { let collection = raw_collection.clone_ref(); let collection = collection.borrow(); - collection.outputs[0].output.clone() - } - - #[no_mangle] - pub extern "C" - fn squeek_outputs_get_perceptual_width( - raw_collection: COutputs, - wl_output: WlOutput, - ) -> i32 { - let collection = raw_collection.clone_ref(); - let collection = collection.borrow(); - - let output_state = find_output(&collection, wl_output) - .map(|o| &o.current); - match output_state { - Some(OutputState { - current_mode: Some(super::Mode { width, height } ), - transform: Some(transform), - scale, - }) => { - match transform { - Transform::Normal - | Transform::Rotated180 - | Transform::Flipped - | Transform::FlippedRotated180 => width / scale, - _ => height / scale, - } - }, - _ => { - log_print!( - logging::Level::Surprise, - "Not enough info received on output", - ); - 0 - }, + OutputHandle { + wl_output: collection.outputs[0].output.clone(), + outputs: raw_collection.clone(), } } + // TODO: handle unregistration fn find_output( @@ -305,6 +292,14 @@ pub mod c { } } +/// Generic size +#[derive(Clone)] +pub struct Size { + pub width: u32, + pub height: u32, +} + +/// wl_output mode #[derive(Clone)] struct Mode { width: i32, @@ -315,10 +310,16 @@ struct Mode { pub struct OutputState { current_mode: Option, transform: Option, - scale: i32, + pub scale: i32, } impl OutputState { + // More properly, this would have been a builder kind of struct, + // with wl_output gradually adding properties to it + // before it reached a fully initialized state, + // when it would transform into a struct without all (some?) of the Options. + // However, it's not clear which state is fully initialized, + // and whether it would make things easier at all anyway. fn uninitialized() -> OutputState { OutputState { current_mode: None, @@ -326,6 +327,32 @@ impl OutputState { scale: 1, } } + + pub fn get_pixel_size(&self) -> Option { + use self::c::Transform; + match self { + OutputState { + current_mode: Some(Mode { width, height } ), + transform: Some(transform), + scale: _, + } => Some( + match transform { + Transform::Normal + | Transform::Rotated180 + | Transform::Flipped + | Transform::FlippedRotated180 => Size { + width: *width as u32, + height: *height as u32, + }, + _ => Size { + width: *height as u32, + height: *width as u32, + }, + } + ), + _ => None, + } + } } pub struct Output { diff --git a/src/server-context-service.c b/src/server-context-service.c index 3e1e7f11..46811ce4 100644 --- a/src/server-context-service.c +++ b/src/server-context-service.c @@ -43,6 +43,7 @@ struct _ServerContextService { /// Needed for instantiating the widget struct submission *submission; // unowned struct squeek_layout_state *layout; + struct ui_manager *manager; // unowned gboolean visible; PhoshLayerSurface *window; @@ -86,18 +87,6 @@ on_notify_unmap (GObject *object, g_object_set (context, "visible", FALSE, NULL); } -static uint32_t -calculate_height(int32_t width) -{ - uint32_t height = 180; - if (width < 360 && width > 0) { - height = ((unsigned)width * 7 / 12); // to match 360×210 - } else if (width < 540) { - height = 180 + (540 - (unsigned)width) * 30 / 180; // smooth transition - } - return height; -} - static void on_surface_configure(PhoshLayerSurface *surface, ServerContextService *context) { @@ -108,7 +97,7 @@ on_surface_configure(PhoshLayerSurface *surface, ServerContextService *context) "configured-height", &height, NULL); - guint desired_height = calculate_height(width); + guint desired_height = squeek_uiman_get_perceptual_height(context->manager); guint configured_height = (guint)height; // if height was already requested once but a different one was given // (for the same set of surrounding properties), @@ -131,14 +120,14 @@ make_window (ServerContextService *context) if (context->window) g_error("Window already present"); - struct wl_output *output = squeek_outputs_get_current(squeek_wayland->outputs); - int32_t width = squeek_outputs_get_perceptual_width(squeek_wayland->outputs, output); - uint32_t height = calculate_height(width); + struct squeek_output_handle output = squeek_outputs_get_current(squeek_wayland->outputs); + squeek_uiman_set_output(context->manager, output); + uint32_t height = squeek_uiman_get_perceptual_height(context->manager); context->window = g_object_new ( PHOSH_TYPE_LAYER_SURFACE, "layer-shell", squeek_wayland->layer_shell, - "wl-output", output, + "wl-output", output.output, "height", height, "anchor", ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT @@ -322,11 +311,12 @@ server_context_service_init (ServerContextService *state) { } ServerContextService * -server_context_service_new (EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout) +server_context_service_new (EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman) { ServerContextService *ui = g_object_new (SERVER_TYPE_CONTEXT_SERVICE, NULL); ui->submission = submission; ui->state = state; ui->layout = layout; + ui->manager = uiman; return ui; } diff --git a/src/server-context-service.h b/src/server-context-service.h index 2c88a8bc..a69f85ae 100644 --- a/src/server-context-service.h +++ b/src/server-context-service.h @@ -20,6 +20,7 @@ #include "src/layout.h" #include "src/submission.h" +#include "ui_manager.h" G_BEGIN_DECLS @@ -36,7 +37,7 @@ typedef struct _ServerContextService ServerContextService; GType server_context_service_get_type (void) G_GNUC_CONST; -ServerContextService *server_context_service_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout); +ServerContextService *server_context_service_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman); enum squeek_arrangement_kind server_context_service_get_layout_type(ServerContextService *); void server_context_service_show_keyboard (ServerContextService *context); void server_context_service_hide_keyboard (ServerContextService *context); diff --git a/src/server-main.c b/src/server-main.c index 513def37..0fae0245 100644 --- a/src/server-main.c +++ b/src/server-main.c @@ -32,6 +32,7 @@ #include "outputs.h" #include "submission.h" #include "server-context-service.h" +#include "ui_manager.h" #include "wayland.h" #include @@ -45,6 +46,7 @@ struct squeekboard { ServerContextService *ui_context; // mess, includes the entire UI struct submission *submission; // Wayland text input handling. struct squeek_layout_state layout_choice; // Currently wanted layout. + struct ui_manager *ui_manager; // UI shape tracker/chooser. TODO: merge with layuot choice }; @@ -201,6 +203,8 @@ main (int argc, char **argv) g_warning("Wayland input method interface not available"); } + instance.ui_manager = squeek_uiman_new(); + instance.settings_context = eekboard_context_service_new(&instance.layout_choice); // set up dbus @@ -279,7 +283,8 @@ main (int argc, char **argv) ServerContextService *ui_context = server_context_service_new( instance.settings_context, instance.submission, - &instance.layout_choice); + &instance.layout_choice, + instance.ui_manager); if (!ui_context) { g_error("Could not initialize GUI"); exit(1); diff --git a/src/ui_manager.h b/src/ui_manager.h new file mode 100644 index 00000000..57d3cc70 --- /dev/null +++ b/src/ui_manager.h @@ -0,0 +1,14 @@ +#ifndef UI_MANAGER__ +#define UI_MANAGER__ + +#include + +#include "outputs.h" + +struct ui_manager; + +struct ui_manager *squeek_uiman_new(); +void squeek_uiman_set_output(struct ui_manager *uiman, struct squeek_output_handle output); +uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman); + +#endif diff --git a/src/ui_manager.rs b/src/ui_manager.rs new file mode 100644 index 00000000..c7af6521 --- /dev/null +++ b/src/ui_manager.rs @@ -0,0 +1,81 @@ +/* Copyright (C) 2020 Purism SPC + * SPDX-License-Identifier: GPL-3.0+ + */ + +/*! Centrally manages the shape of the UI widgets, and the choice of layout. + * + * Coordinates this based on information collated from all possible sources. + */ + +use std::cmp::min; +use ::outputs::c::OutputHandle; + +mod c { + use super::*; + use ::util::c::Wrapped; + + #[no_mangle] + pub extern "C" + fn squeek_uiman_new() -> Wrapped { + Wrapped::new(Manager { output: None }) + } + + /// Used to size the layer surface containing all the OSK widgets. + #[no_mangle] + pub extern "C" + fn squeek_uiman_get_perceptual_height( + uiman: Wrapped, + ) -> u32 { + let uiman = uiman.clone_ref(); + let uiman = uiman.borrow(); + // TODO: what to do when there's no output? + uiman.get_perceptual_height().unwrap_or(0) + } + + #[no_mangle] + pub extern "C" + fn squeek_uiman_set_output( + uiman: Wrapped, + output: OutputHandle, + ) { + let uiman = uiman.clone_ref(); + let mut uiman = uiman.borrow_mut(); + uiman.output = Some(output); + } +} + +/// Stores current state of all things influencing what the UI should look like. +pub struct Manager { + /// Shared output handle, current state updated whenever it's needed. + // TODO: Stop assuming that the output never changes. + // (There's no way for the output manager to update the ui manager.) + // FIXME: Turn into an OutputState and apply relevant connections elsewhere. + // Otherwise testability and predictablity is low. + output: Option, + //// Pixel size of the surface. Needs explicit updating. + //surface_size: Option, +} + +impl Manager { + fn get_perceptual_height(&self) -> Option { + let output_info = (&self.output).as_ref() + .and_then(|o| o.get_state()) + .map(|os| (os.scale as u32, os.get_pixel_size())); + match output_info { + Some((scale, Some(px_size))) => Some({ + let height = if (px_size.width < 720) & (px_size.width > 0) { + px_size.width * 7 / 12 // to match 360×210 + } else if px_size.width < 1080 { + 360 + (1080 - px_size.width) * 60 / 360 // smooth transition + } else { + 360 + }; + + // Don't exceed half the display size + min(height, px_size.height / 2) / scale + }), + Some((scale, None)) => Some(360 / scale), + None => None, + } + } +} diff --git a/src/util.rs b/src/util.rs index d58a3e77..e87ffea6 100644 --- a/src/util.rs +++ b/src/util.rs @@ -98,7 +98,8 @@ pub mod c { Rc::from_raw(self.0) } - /// Creates a new Rc reference to the same data + /// Creates a new Rc reference to the same data. + /// Use for accessing the underlying data as a reference. pub fn clone_ref(&self) -> Rc> { // A bit dangerous: the Rc may be in use elsewhere let used_rc = unsafe { Rc::from_raw(self.0) }; @@ -130,6 +131,7 @@ pub mod c { impl COpaquePtr for Wrapped {} } +/// Clones the underlying data structure, like ToOwned. pub trait CloneOwned { type Owned; fn clone_owned(&self) -> Self::Owned;