Compare commits

...

14 Commits
mib ... sizing

Author SHA1 Message Date
9ce2cf254b layout_state: Don't always operate on the global instance 2020-03-12 11:34:20 +00:00
6d7360a230 layout_holder: Rename functions used from C 2020-03-12 11:34:20 +00:00
33039e65cb layout_holder: Remove unused functions 2020-03-12 11:34:20 +00:00
4007754de9 eekboard_context: Rename to LayoutHolder 2020-03-12 11:34:20 +00:00
6abaa36db8 eekboard_context: Rename 2020-03-12 11:34:20 +00:00
710509a671 eekboard context: Remove some unused code 2020-03-12 11:34:20 +00:00
16d7fcae7c eekboard context: Remove unused struct 2020-03-12 11:34:20 +00:00
e504154571 managers: Turn gsettings management into a separate piece. 2020-03-12 11:34:20 +00:00
b19938da01 ui: Fix old Rust borrowing 2020-03-12 11:26:49 +00:00
b409df15bb ui: Update UI state based on output events 2020-03-12 11:26:49 +00:00
7dd2866b17 ui manager: Update state and calculate new size on ouptut change 2020-03-12 11:26:49 +00:00
f6fc6c83dc outputs: Pass output updates
Introduce a callback in `outputs::Outputs` that calls on every `wl_output.done`, and a dummy consumer in `ui_manager`.

This is sufficient to detect display height changes.
2020-03-12 11:26:49 +00:00
fa5c7c63d9 ui_manager: Calculate max_height in a purer fashion 2020-03-12 11:26:49 +00:00
1093e32325 sizing: Use physical dimensions of the display to determine optimal keyboard height.
Parameters fudged appropriately to preserve dimensions from the original design targting Librem5:

- 720×1440 results in 420px height, via max finger size
- 1440×720 results in 360px height, via not exceeding half the display

In absence of physical dimensions, a pixel is assumed to measure half the size as on the Librem5, and then shrunk by the current display scale factor.This gives the ability to test in nested phoc at selected scale factors like before (2x being most accurate), and keeps the right size in QEMU.
2020-03-12 11:26:49 +00:00
17 changed files with 483 additions and 260 deletions

View File

@ -45,7 +45,7 @@
typedef struct _EekGtkKeyboardPrivate
{
EekRenderer *renderer; // owned, nullable
EekboardContextService *eekboard_context; // unowned reference
LayoutHolder *eekboard_context; // unowned reference
struct submission *submission; // unowned reference
struct squeek_layout_state *layout; // unowned
@ -124,9 +124,10 @@ eek_gtk_keyboard_real_size_allocate (GtkWidget *self,
(uint32_t)(allocation->width - allocation->x) * scale,
(uint32_t)(allocation->height - allocation->y) * scale);
if (priv->layout->arrangement != new_type) {
priv->layout->arrangement = new_type;
struct squeek_layout_state layout = *priv->layout;
layout.arrangement = new_type;
eekboard_context_service_use_layout(priv->eekboard_context, priv->layout);
eek_layout_holder_use_layout(priv->eekboard_context, &layout);
}
if (priv->renderer)
@ -158,7 +159,7 @@ static void drag(EekGtkKeyboard *self,
if (!priv->keyboard) {
return;
}
squeek_layout_drag(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout,
squeek_layout_drag(eek_layout_holder_get_keyboard(priv->eekboard_context)->layout,
priv->submission,
x, y, eek_renderer_get_transformation(priv->renderer), time,
priv->eekboard_context, self);
@ -170,7 +171,7 @@ static void release(EekGtkKeyboard *self, guint32 time)
if (!priv->keyboard) {
return;
}
squeek_layout_release(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout,
squeek_layout_release(eek_layout_holder_get_keyboard(priv->eekboard_context)->layout,
priv->submission,
eek_renderer_get_transformation(priv->renderer), time,
priv->eekboard_context, self);
@ -343,7 +344,7 @@ on_notify_keyboard (GObject *object,
EekGtkKeyboard *self) {
(void)spec;
EekGtkKeyboardPrivate *priv = (EekGtkKeyboardPrivate*)eek_gtk_keyboard_get_instance_private (self);
priv->keyboard = eekboard_context_service_get_keyboard(EEKBOARD_CONTEXT_SERVICE(object));
priv->keyboard = eek_layout_holder_get_keyboard(LAYOUT_HOLDER(object));
if (priv->renderer) {
eek_renderer_free(priv->renderer);
}
@ -356,7 +357,7 @@ on_notify_keyboard (GObject *object,
* Returns: a #GtkWidget
*/
GtkWidget *
eek_gtk_keyboard_new (EekboardContextService *eekservice,
eek_gtk_keyboard_new (LayoutHolder *eekservice,
struct submission *submission,
struct squeek_layout_state *layout)
{

View File

@ -48,7 +48,7 @@ struct _EekGtkKeyboardClass
};
GType eek_gtk_keyboard_get_type (void) G_GNUC_CONST;
GtkWidget *eek_gtk_keyboard_new (EekboardContextService *eekservice, struct submission *submission, struct squeek_layout_state *layout);
GtkWidget *eek_gtk_keyboard_new (LayoutHolder *eekservice, struct submission *submission, struct squeek_layout_state *layout);
G_END_DECLS
#endif /* EEK_GTK_KEYBOARD_H */

View File

@ -37,7 +37,7 @@ G_BEGIN_DECLS
typedef struct _EekBounds EekBounds;
typedef struct _EekboardContextService EekboardContextService;
typedef struct _LayoutHolder LayoutHolder;
typedef struct _ServerContextService ServerContextService;
typedef struct _LevelKeyboard LevelKeyboard;

View File

@ -35,28 +35,17 @@ enum {
PROP_LAST
};
enum {
DESTROYED,
LAST_SIGNAL
};
#define LAYOUT_HOLDER_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEKBOARD_TYPE_LAYOUT_HOLDER, LayoutHolderPrivate))
static guint signals[LAST_SIGNAL] = { 0, };
#define EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServicePrivate))
struct _EekboardContextServicePrivate {
struct _LayoutHolderPrivate {
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
struct submission *submission; // unowned
};
G_DEFINE_TYPE_WITH_PRIVATE (EekboardContextService, eekboard_context_service, G_TYPE_OBJECT);
G_DEFINE_TYPE_WITH_PRIVATE (LayoutHolder, layout_holder, G_TYPE_OBJECT);
static void
eekboard_context_service_set_property (GObject *object,
@ -73,12 +62,12 @@ eekboard_context_service_set_property (GObject *object,
}
static void
eekboard_context_service_get_property (GObject *object,
layout_holder_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
EekboardContextService *context = EEKBOARD_CONTEXT_SERVICE(object);
LayoutHolder *context = LAYOUT_HOLDER(object);
switch (prop_id) {
case PROP_KEYBOARD:
@ -90,13 +79,6 @@ eekboard_context_service_get_property (GObject *object,
}
}
static void
eekboard_context_service_dispose (GObject *object)
{
G_OBJECT_CLASS (eekboard_context_service_parent_class)->
dispose (object);
}
static void
settings_get_layout(GSettings *settings, char **type, char **layout)
{
@ -116,7 +98,8 @@ settings_get_layout(GSettings *settings, char **type, char **layout)
}
void
eekboard_context_service_use_layout(EekboardContextService *context, struct squeek_layout_state *state) {
eek_layout_holder_use_layout(LayoutHolder *context, struct squeek_layout_state *state) {
*context->layout = *state;
gchar *layout_name = state->overlay_name;
if (layout_name == NULL) {
@ -160,73 +143,22 @@ eekboard_context_service_use_layout(EekboardContextService *context, struct sque
}
}
static void eekboard_context_service_update_settings_layout(EekboardContextService *context) {
g_autofree gchar *keyboard_layout = NULL;
g_autofree gchar *keyboard_type = NULL;
settings_get_layout(context->priv->settings,
&keyboard_type, &keyboard_layout);
if (g_strcmp0(context->layout->layout_name, keyboard_layout) != 0 || context->layout->overlay_name) {
g_free(context->layout->overlay_name);
context->layout->overlay_name = NULL;
if (keyboard_layout) {
g_free(context->layout->layout_name);
context->layout->layout_name = g_strdup(keyboard_layout);
}
// This must actually update the UI.
eekboard_context_service_use_layout(context, context->layout);
}
}
static gboolean
settings_handle_layout_changed(GSettings *s,
gpointer keys, gint n_keys,
gpointer user_data) {
(void)s;
(void)keys;
(void)n_keys;
EekboardContextService *context = user_data;
eekboard_context_service_update_settings_layout(context);
return TRUE;
static void
layout_holder_init (LayoutHolder *self) {
self->priv = LAYOUT_HOLDER_GET_PRIVATE(self);
}
static void
eekboard_context_service_constructed (GObject *object)
{
(void)object;
}
static void
eekboard_context_service_class_init (EekboardContextServiceClass *klass)
layout_holder_class_init (LayoutHolderClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
gobject_class->constructed = eekboard_context_service_constructed;
gobject_class->set_property = eekboard_context_service_set_property;
gobject_class->get_property = eekboard_context_service_get_property;
gobject_class->dispose = eekboard_context_service_dispose;
/**
* EekboardContextService::destroyed:
* @context: an #EekboardContextService
*
* Emitted when @context is destroyed.
*/
signals[DESTROYED] =
g_signal_new (I_("destroyed"),
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(EekboardContextServiceClass, destroyed),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
gobject_class->get_property = layout_holder_get_property;
/**
* EekboardContextService:keyboard:
*
* An #EekKeyboard currently active in this context.
* An #LevelKeyboard currently active in this context.
*/
pspec = g_param_spec_pointer("keyboard",
"Keyboard",
@ -237,10 +169,91 @@ eekboard_context_service_class_init (EekboardContextServiceClass *klass)
pspec);
}
static void
eekboard_context_service_init (EekboardContextService *self)
/**
* Get keyboard currently active in @context.
* Returns: (transfer none): a LevelKeyboard
*/
LevelKeyboard *
eek_layout_holder_get_keyboard (LayoutHolder *context)
{
self->priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(self);
return context->priv->keyboard;
}
void eekboard_context_service_set_hint_purpose(LayoutHolder *context,
uint32_t hint, uint32_t purpose)
{
if (context->layout->hint != hint || context->layout->purpose != purpose) {
context->layout->hint = hint;
context->layout->purpose = purpose;
eek_layout_holder_use_layout(context, context->layout);
}
}
void
eekboard_context_service_set_overlay(LayoutHolder *context, const char* name) {
if (g_strcmp0(context->layout->overlay_name, name)) {
g_free(context->layout->overlay_name);
context->layout->overlay_name = g_strdup(name);
eek_layout_holder_use_layout(context, context->layout);
}
}
const char*
eekboard_context_service_get_overlay(LayoutHolder *context) {
return context->layout->overlay_name;
}
LayoutHolder *eek_layout_holder_new(struct squeek_layout_state *state)
{
LayoutHolder *context = g_object_new (EEKBOARD_TYPE_LAYOUT_HOLDER, NULL);
context->layout = state;
eek_layout_holder_use_layout(context, context->layout);
return context;
}
void eek_layout_holder_set_submission(LayoutHolder *context, struct submission *submission) {
context->priv->submission = submission;
if (context->priv->submission) {
submission_set_keyboard(context->priv->submission, context->priv->keyboard);
}
}
static void settings_update_layout(struct gsettings_tracker *self) {
// The layout in the param must be the same layout as held by context.
g_autofree gchar *keyboard_layout = NULL;
g_autofree gchar *keyboard_type = NULL;
settings_get_layout(self->gsettings,
&keyboard_type, &keyboard_layout);
if (g_strcmp0(self->layout->layout_name, keyboard_layout) != 0 || self->layout->overlay_name) {
g_free(self->layout->overlay_name);
self->layout->overlay_name = NULL;
if (keyboard_layout) {
g_free(self->layout->layout_name);
self->layout->layout_name = g_strdup(keyboard_layout);
}
// This must actually update the UI.
eek_layout_holder_use_layout(self->context, self->layout);
}
}
static gboolean
handle_layout_changed(GSettings *s,
gpointer keys, gint n_keys,
gpointer user_data) {
(void)s;
(void)keys;
(void)n_keys;
struct gsettings_tracker *self = user_data;
settings_update_layout(self);
return TRUE;
}
void eek_gsettings_tracker_init(struct gsettings_tracker *tracker, LayoutHolder *context, struct squeek_layout_state *layout)
{
tracker->layout = layout;
tracker->context = context;
const char *schema_name = "org.gnome.desktop.input-sources";
GSettingsSchemaSource *ssrc = g_settings_schema_source_get_default();
if (ssrc) {
@ -250,10 +263,10 @@ eekboard_context_service_init (EekboardContextService *self)
if (schema) {
// Not referencing the found schema directly,
// because it's not clear how...
self->priv->settings = g_settings_new (schema_name);
gulong conn_id = g_signal_connect(self->priv->settings, "change-event",
G_CALLBACK(settings_handle_layout_changed),
self);
tracker->gsettings = g_settings_new (schema_name);
gulong conn_id = g_signal_connect(tracker->gsettings, "change-event",
G_CALLBACK(handle_layout_changed),
tracker);
if (conn_id == 0) {
g_warning ("Could not connect to gsettings updates, "
"automatic layout changing unavailable");
@ -265,74 +278,6 @@ eekboard_context_service_init (EekboardContextService *self)
} else {
g_warning("No gsettings schemas installed. Layout switching unavailable.");
}
}
/**
* eekboard_context_service_destroy:
* @context: an #EekboardContextService
*
* Destroy @context.
*/
void
eekboard_context_service_destroy (EekboardContextService *context)
{
g_return_if_fail (EEKBOARD_IS_CONTEXT_SERVICE(context));
g_signal_emit (context, signals[DESTROYED], 0);
}
/**
* eekboard_context_service_get_keyboard:
* @context: an #EekboardContextService
*
* Get keyboard currently active in @context.
* Returns: (transfer none): an #EekKeyboard
*/
LevelKeyboard *
eekboard_context_service_get_keyboard (EekboardContextService *context)
{
return context->priv->keyboard;
}
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
uint32_t hint, uint32_t purpose)
{
if (context->layout->hint != hint || context->layout->purpose != purpose) {
context->layout->hint = hint;
context->layout->purpose = purpose;
eekboard_context_service_use_layout(context, context->layout);
}
}
void
eekboard_context_service_set_overlay(EekboardContextService *context, const char* name) {
if (g_strcmp0(context->layout->overlay_name, name)) {
g_free(context->layout->overlay_name);
context->layout->overlay_name = g_strdup(name);
eekboard_context_service_use_layout(context, context->layout);
}
}
const char*
eekboard_context_service_get_overlay(EekboardContextService *context) {
return context->layout->overlay_name;
}
EekboardContextService *eekboard_context_service_new(struct squeek_layout_state *state)
{
EekboardContextService *context = g_object_new (EEKBOARD_TYPE_CONTEXT_SERVICE, NULL);
context->layout = state;
eekboard_context_service_update_settings_layout(context);
eekboard_context_service_use_layout(context, context->layout);
return context;
}
void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission) {
context->priv->submission = submission;
if (context->priv->submission) {
submission_set_keyboard(context->priv->submission, context->priv->keyboard);
}
}
void eekboard_context_service_set_ui(EekboardContextService *context, ServerContextService *ui) {
context->priv->ui = ui;
settings_update_layout(tracker);
}

View File

@ -30,71 +30,60 @@
G_BEGIN_DECLS
#define EEKBOARD_CONTEXT_SERVICE_PATH "/org/fedorahosted/Eekboard/Context_%d"
#define EEKBOARD_CONTEXT_SERVICE_INTERFACE "org.fedorahosted.Eekboard.Context"
#define EEKBOARD_TYPE_CONTEXT_SERVICE (eekboard_context_service_get_type())
#define EEKBOARD_CONTEXT_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextService))
#define EEKBOARD_TYPE_LAYOUT_HOLDER (layout_holder_get_type())
#define LAYOUT_HOLDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEKBOARD_TYPE_LAYOUT_HOLDER, LayoutHolder))
#define EEKBOARD_CONTEXT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServiceClass))
#define EEKBOARD_IS_CONTEXT_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE))
#define EEKBOARD_IS_CONTEXT_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEKBOARD_TYPE_CONTEXT_SERVICE))
#define EEKBOARD_CONTEXT_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEKBOARD_TYPE_CONTEXT_SERVICE, EekboardContextServiceClass))
typedef struct _EekboardContextServiceClass EekboardContextServiceClass;
typedef struct _EekboardContextServicePrivate EekboardContextServicePrivate;
typedef struct _LayoutHolderClass LayoutHolderClass;
typedef struct _LayoutHolderPrivate LayoutHolderPrivate;
/**
* EekboardContextService:
*
* Handles layout state, gsettings, and virtual-keyboard.
* Handles layout state, and virtual-keyboard.
*
* TODO: Restrict to managing keyboard layouts, and maybe button repeats,
* and the virtual keyboard protocol.
*
* The #EekboardContextService structure contains only private data
* and should only be accessed using the provided API.
*/
struct _EekboardContextService {
struct _LayoutHolder {
GObject parent;
EekboardContextServicePrivate *priv;
LayoutHolderPrivate *priv;
struct squeek_layout_state *layout; // Unowned
};
/**
* EekboardContextServiceClass:
* @create_keyboard: virtual function for create a keyboard from string
* @enabled: class handler for #EekboardContextService::enabled signal
* @disabled: class handler for #EekboardContextService::disabled signal
*/
struct _EekboardContextServiceClass {
struct _LayoutHolderClass {
/*< private >*/
GObjectClass parent_class;
/*< public >*/
/* signals */
void (*destroyed) (EekboardContextService *self);
/*< private >*/
/* padding */
gpointer pdummy[24];
};
GType eekboard_context_service_get_type
(void) G_GNUC_CONST;
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);
GType layout_holder_get_type(void) G_GNUC_CONST;
void eekboard_context_service_set_keymap(EekboardContextService *context,
/// Handles gsettings os-level keyboard layout switches.
struct gsettings_tracker {
GSettings *gsettings; // Owned reference
LayoutHolder *context; // Unowned
struct squeek_layout_state *layout; // Unowned
};
void eek_gsettings_tracker_init(struct gsettings_tracker* tracker, LayoutHolder *context, struct squeek_layout_state *layout);
LayoutHolder *eek_layout_holder_new(struct squeek_layout_state *state);
void eek_layout_holder_set_submission(LayoutHolder *context, struct submission *submission);
LevelKeyboard *eek_layout_holder_get_keyboard(LayoutHolder *context);
void eekboard_context_service_set_keymap(LayoutHolder *context,
const LevelKeyboard *keyboard);
void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
void eekboard_context_service_set_hint_purpose(LayoutHolder *context,
uint32_t hint,
uint32_t purpose);
void
eekboard_context_service_use_layout(EekboardContextService *context, struct squeek_layout_state *layout);
eek_layout_holder_use_layout(LayoutHolder *context, struct squeek_layout_state *layout);
G_END_DECLS
#endif /* EEKBOARD_CONTEXT_SERVICE_H */

View File

@ -26,7 +26,7 @@ static const struct zwp_input_method_v2_listener input_method_listener = {
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager,
struct wl_seat *seat,
EekboardContextService *state) {
LayoutHolder *state) {
struct zwp_input_method_v2 *im = NULL;
if (immanager) {
im = zwp_input_method_manager_v2_get_input_method(immanager, seat);

View File

@ -47,7 +47,7 @@ void squeek_layout_release(struct squeek_layout *layout,
struct submission *submission,
struct transformation widget_to_layout,
uint32_t timestamp,
EekboardContextService *manager,
LayoutHolder *manager,
EekGtkKeyboard *ui_keyboard);
void squeek_layout_release_all_only(struct squeek_layout *layout,
struct submission *submission,
@ -61,7 +61,7 @@ void squeek_layout_drag(struct squeek_layout *layout,
struct submission *submission,
double x_widget, double y_widget,
struct transformation widget_to_layout,
uint32_t timestamp, EekboardContextService *manager,
uint32_t timestamp, LayoutHolder *manager,
EekGtkKeyboard *ui_keyboard);
void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr, struct submission *submission);
void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);

View File

@ -15,6 +15,7 @@ sources = [
'dbus.c',
'imservice.c',
'server-context-service.c',
'ui_manager.c',
'wayland.c',
'../eek/eek.c',
'../eek/eek-element.c',

View File

@ -1,5 +1,10 @@
/* Copyright (C) 2019-2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Managing Wayland outputs */
use std::cell::RefCell;
use std::vec::Vec;
use ::logging;
@ -17,7 +22,7 @@ pub mod c {
// Defined in C
#[repr(transparent)]
#[derive(Clone, PartialEq, Copy)]
#[derive(Clone, PartialEq, Copy, Hash)]
pub struct WlOutput(*const c_void);
#[repr(C)]
@ -63,7 +68,7 @@ pub mod c {
}
/// Map to `wl_output.transform` values
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub enum Transform {
Normal = 0,
Rotated90 = 1,
@ -103,7 +108,7 @@ pub mod c {
) -> i32;
}
type COutputs = ::util::c::Wrapped<Outputs>;
pub type COutputs = ::util::c::Wrapped<Outputs>;
/// A stable reference to an output.
#[derive(Clone)]
@ -121,6 +126,10 @@ pub mod c {
let outputs = outputs.borrow();
find_output(&outputs, self.wl_output.clone()).map(|o| o.current.clone())
}
pub fn get_id(&self) -> OutputId {
OutputId { wl_output: self.wl_output }
}
}
// Defined in Rust
@ -129,7 +138,7 @@ pub mod c {
outputs: COutputs,
wl_output: WlOutput,
_x: i32, _y: i32,
_phys_width: i32, _phys_height: i32,
phys_width: i32, phys_height: i32,
_subpixel: i32,
_make: *const c_char, _model: *const c_char,
transform: i32,
@ -145,8 +154,23 @@ pub mod c {
let output_state: Option<&mut OutputState>
= find_output_mut(&mut collection, wl_output)
.map(|o| &mut o.pending);
match output_state {
Some(state) => { state.transform = Some(transform) },
Some(state) => {
state.transform = Some(transform);
state.phys_size = {
if (phys_width > 0) & (phys_height > 0) {
Some(SizeMM { width: phys_width, height: phys_height })
} else {
log_print!(
logging::Level::Surprise,
"Impossible physical dimensions: {}mm × {}mm",
phys_width, phys_height,
);
None
}
}
},
None => log_print!(
logging::Level::Warning,
"Got geometry on unknown output",
@ -187,19 +211,27 @@ pub mod c {
}
extern fn outputs_handle_done(
outputs: COutputs,
outputs_raw: COutputs,
wl_output: WlOutput,
) {
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output = find_output_mut(&mut collection, wl_output);
match output {
Some(output) => { output.current = output.pending.clone(); }
None => log_print!(
logging::Level::Warning,
"Got done on unknown output",
),
};
let outputs = outputs_raw.clone_ref();
{
let mut collection = RefCell::borrow_mut(&outputs);
let output = find_output_mut(&mut collection, wl_output);
match output {
Some(output) => { output.current = output.pending.clone(); }
None => log_print!(
logging::Level::Warning,
"Got done on unknown output",
),
};
}
let collection = RefCell::borrow(&outputs);
if let Some(ref cb) = &collection.update_cb {
let mut cb = RefCell::borrow_mut(cb);
let cb = Box::as_mut(&mut cb);
cb(OutputHandle { wl_output, outputs: outputs_raw });
}
}
extern fn outputs_handle_scale(
@ -224,7 +256,10 @@ pub mod c {
#[no_mangle]
pub extern "C"
fn squeek_outputs_new() -> COutputs {
COutputs::new(Outputs { outputs: Vec::new() })
COutputs::new(Outputs {
outputs: Vec::new(),
update_cb: None,
})
}
#[no_mangle]
@ -293,22 +328,29 @@ pub mod c {
}
/// Generic size
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct Size {
pub width: u32,
pub height: u32,
}
#[derive(Clone, PartialEq)]
pub struct SizeMM {
pub width: i32,
pub height: i32,
}
/// wl_output mode
#[derive(Clone)]
#[derive(Clone, PartialEq)]
struct Mode {
width: i32,
height: i32,
}
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct OutputState {
current_mode: Option<Mode>,
phys_size: Option<SizeMM>,
transform: Option<c::Transform>,
pub scale: i32,
}
@ -323,6 +365,7 @@ impl OutputState {
fn uninitialized() -> OutputState {
OutputState {
current_mode: None,
phys_size: None,
transform: None,
scale: 1,
}
@ -334,6 +377,35 @@ impl OutputState {
OutputState {
current_mode: Some(Mode { width, height } ),
transform: Some(transform),
phys_size: _,
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,
}
}
/// Returns transformed dimensions
pub fn get_phys_size(&self) -> Option<Size> {
use self::c::Transform;
match self {
OutputState {
current_mode: _,
transform: Some(transform),
phys_size: Some(SizeMM { width, height }),
scale: _,
} => Some(
match transform {
@ -355,12 +427,52 @@ impl OutputState {
}
}
/// A comparable ID of an output
#[derive(Clone, PartialEq, Hash)]
pub struct OutputId {
// WlOutput is a unique pointer, so it will not repeat
// even if there are multiple output managers.
wl_output: c::WlOutput,
}
pub struct Output {
output: c::WlOutput,
pending: OutputState,
current: OutputState,
}
/// The manager of all outputs.
// This is the target of several callbacks,
// so it should only be used with a stable place in memory, like `Rc<RefCell>`.
// It should not be instantiated externally or copied,
// or it will not receive those callbacks and be somewhat of an empty shell.
// It should be safe to use as long as the fields are not `pub`,
// and there's no `Clone`, and this module's API only ever gives out
// references wrapped in `Rc<RefCell>`.
// For perfectness, it would only ever give out immutable opaque references,
// but that's not practical at the moment.
// `mem::swap` could replace the value inside,
// but as long as the swap is atomic,
// that should not cause an inconsistent state.
pub struct Outputs {
outputs: Vec<Output>,
// The RefCell is here to let the function be called
// while holding only a read-reference to `Outputs`.
// Otherwise anything trying to get useful data from OutputHandle
// will fail to acquire reference to Outputs.
// TODO: Maybe pass only current state along with Outputs and Output hash.
// The only reason a full OutputHandle is here
// is to be able to track the right Output.
update_cb: Option<RefCell<Box<dyn FnMut(c::OutputHandle)>>>,
}
impl Outputs {
/// The function will get called whenever
/// any output changes or is removed or created.
/// If output handle doesn't return state, the output just went down.
/// It cannot modify anything in Outputs.
// FIXME: handle output destruction
pub fn set_update_cb(&mut self, callback: Box<dyn FnMut(c::OutputHandle)>) {
self.update_cb = Some(RefCell::new(callback));
}
}

View File

@ -39,7 +39,7 @@ typedef struct _ServerContextServiceClass ServerContextServiceClass;
struct _ServerContextService {
GObject parent;
EekboardContextService *state; // unowned
LayoutHolder *state; // unowned
/// Needed for instantiating the widget
struct submission *submission; // unowned
struct squeek_layout_state *layout;
@ -139,6 +139,7 @@ make_window (ServerContextService *context)
NULL
);
squeek_uiman_set_surface(context->manager, context->window);
g_object_connect (context->window,
"signal::destroy", G_CALLBACK(on_destroy), context,
"signal::map", G_CALLBACK(on_notify_map), context,
@ -311,7 +312,7 @@ server_context_service_init (ServerContextService *state) {
}
ServerContextService *
server_context_service_new (EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout, struct ui_manager *uiman)
server_context_service_new (LayoutHolder *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;

View File

@ -37,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, struct ui_manager *uiman);
ServerContextService *server_context_service_new(LayoutHolder *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);

View File

@ -42,11 +42,12 @@
struct squeekboard {
struct squeek_wayland wayland; // Just hooks.
DBusHandler *dbus_handler; // Controls visibility of the OSK.
EekboardContextService *settings_context; // Gsettings hooks.
LayoutHolder *layout_holder; // Currently used layout & keyboard.
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
struct gsettings_tracker gsettings_tracker; // Gsettings handling.
};
@ -203,10 +204,10 @@ 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);
instance.ui_manager = squeek_uiman_new(instance.wayland.outputs);
instance.layout_holder = eek_layout_holder_new(&instance.layout_choice);
eek_gsettings_tracker_init(&instance.gsettings_tracker, instance.layout_holder, &instance.layout_choice);
// set up dbus
// TODO: make dbus errors non-always-fatal
@ -279,12 +280,12 @@ main (int argc, char **argv)
instance.submission = get_submission(instance.wayland.input_method_manager,
instance.wayland.virtual_keyboard_manager,
instance.wayland.seat,
instance.settings_context);
instance.layout_holder);
eekboard_context_service_set_submission(instance.settings_context, instance.submission);
eek_layout_holder_set_submission(instance.layout_holder, instance.submission);
ServerContextService *ui_context = server_context_service_new(
instance.settings_context,
instance.layout_holder,
instance.submission,
&instance.layout_choice,
instance.ui_manager);
@ -299,7 +300,6 @@ main (int argc, char **argv)
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);
session_register();

View File

@ -10,10 +10,10 @@ struct submission;
struct submission* get_submission(struct zwp_input_method_manager_v2 *immanager,
struct zwp_virtual_keyboard_manager_v1 *vkmanager,
struct wl_seat *seat,
EekboardContextService *state);
LayoutHolder *state);
// Defined in Rust
struct submission* submission_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk, EekboardContextService *state);
struct submission* submission_new(struct zwp_input_method_v2 *im, struct zwp_virtual_keyboard_v1 *vk, LayoutHolder *state);
void submission_set_ui(struct submission *self, ServerContextService *ui_context);
void submission_set_keyboard(struct submission *self, LevelKeyboard *keyboard);
#endif

8
src/ui_manager.c Normal file
View File

@ -0,0 +1,8 @@
#include "eek/layersurface.h"
void squeek_manager_set_surface_height(PhoshLayerSurface *surface, uint32_t desired_height) {
phosh_layer_surface_set_size(surface, 0,
(gint)desired_height);
phosh_layer_surface_set_exclusive_zone(surface, (gint)desired_height);
phosh_layer_surface_wl_surface_commit (surface);
}

View File

@ -2,13 +2,14 @@
#define UI_MANAGER__
#include <inttypes.h>
#include "eek/layersurface.h"
#include "outputs.h"
struct ui_manager;
struct ui_manager *squeek_uiman_new();
struct ui_manager *squeek_uiman_new(struct squeek_outputs *outputs);
void squeek_uiman_set_output(struct ui_manager *uiman, struct squeek_output_handle output);
void squeek_uiman_set_surface(struct ui_manager *uiman, PhoshLayerSurface *surface);
uint32_t squeek_uiman_get_perceptual_height(struct ui_manager *uiman);
#endif

View File

@ -7,17 +7,48 @@
* Coordinates this based on information collated from all possible sources.
*/
use std::cell::RefCell;
use std::cmp::min;
use std::rc::Rc;
use ::logging;
use ::outputs::{ OutputId, Outputs, OutputState};
use ::outputs::c::OutputHandle;
// Traits
use ::logging::Warn;
mod c {
use super::*;
use std::os::raw::c_void;
use ::outputs::c::COutputs;
use ::util::c::Wrapped;
#[derive(Clone, Copy)]
#[repr(C)]
pub struct PhoshLayerSurface(*const c_void);
extern "C" {
// Rustc wrongly assumes
// that COutputs allows C direct access to the underlying RefCell.
#[allow(improper_ctypes)]
pub fn squeek_manager_set_surface_height(
surface: PhoshLayerSurface,
height: u32,
);
}
#[no_mangle]
pub extern "C"
fn squeek_uiman_new() -> Wrapped<Manager> {
Wrapped::new(Manager { output: None })
fn squeek_uiman_new(outputs: COutputs) -> Wrapped<Manager> {
let uiman_raw = Wrapped::new(Manager::new());
if !outputs.is_null() {
let uiman = uiman_raw.clone_ref();
let outputs = outputs.clone_ref();
let mut outputs = outputs.borrow_mut();
register_output_man(uiman, &mut outputs);
}
uiman_raw
}
/// Used to size the layer surface containing all the OSK widgets.
@ -29,7 +60,7 @@ mod c {
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)
uiman.state.get_perceptual_height().unwrap_or(0)
}
#[no_mangle]
@ -40,29 +71,75 @@ mod c {
) {
let uiman = uiman.clone_ref();
let mut uiman = uiman.borrow_mut();
uiman.output = Some(output);
uiman.set_output(output)
}
#[no_mangle]
pub extern "C"
fn squeek_uiman_set_surface(
uiman: Wrapped<Manager>,
surface: PhoshLayerSurface,
) {
let uiman = uiman.clone_ref();
let mut uiman = uiman.borrow_mut();
// Surface is not state, so doesn't need to propagate updates.
uiman.surface = Some(surface);
}
}
/// 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<OutputHandle>,
#[derive(Clone, PartialEq)]
pub struct ManagerState {
current_output: Option<(OutputId, OutputState)>,
//// Pixel size of the surface. Needs explicit updating.
//surface_size: Option<Size>,
}
impl Manager {
impl ManagerState {
/// The largest ideal heigth for the keyboard as a whole
/// judged by the ease of hitting targets within.
/// Ideally related to finger size, the crammedness of the layout,
/// distance from display, and motor skills of the user.
// FIXME: Start by making this aware of display's dpi,
// then layout number of rows.
fn get_max_target_height(output: &OutputState) -> u32 {
let layout_rows = 4; // FIXME: use number from layout.
let px_size = output.get_pixel_size();
let phys_size = output.get_phys_size();
let finger_height_px = match (px_size, phys_size) {
(Some(px_size), Some(phys_size)) => {
// Fudged to result in 420px from the original design.
// That gives about 9.5mm per finger height.
// Maybe floats are not the best choice here,
// but it gets rounded ASAP. Consider rationals.
let keyboard_fraction_of_display: f64 = 420. / 1440.;
let keyboard_mm = keyboard_fraction_of_display * 130.;
let finger_height_mm = keyboard_mm / 4.;
// TODO: Take into account target shape/area, not just height.
finger_height_mm * px_size.height as f64 / phys_size.height as f64
},
(_, None) => output.scale as f64 * 52.5, // match total 420px at scale 2 from original design
(None, Some(_)) => {
log_print!(
logging::Level::Surprise,
"Output has physical size data but no pixel info",
);
output.scale as f64 * 52.5
},
};
(layout_rows as f64 * finger_height_px) as u32
}
fn get_perceptual_height(&self) -> Option<u32> {
let output_info = (&self.output).as_ref()
.and_then(|o| o.get_state())
.map(|os| (os.scale as u32, os.get_pixel_size()));
let output_info = (&self.current_output).as_ref()
.map(|(_id, os)| (
os.scale as u32,
os.get_pixel_size(),
ManagerState::get_max_target_height(&os),
));
match output_info {
Some((scale, Some(px_size))) => Some({
Some((scale, Some(px_size), target_height)) => 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 {
@ -71,11 +148,94 @@ impl Manager {
360
};
// Don't exceed half the display size
min(height, px_size.height / 2) / scale
// Don't exceed half the display size.
let height = min(height, px_size.height / 2);
// Don't waste screen space by exceeding best target height.
let height = min(height, target_height);
height / scale
}),
Some((scale, None)) => Some(360 / scale),
Some((scale, None, _)) => Some(360 / scale),
None => None,
}
}
}
pub struct Manager {
state: ManagerState,
surface: Option<c::PhoshLayerSurface>,
}
impl Manager {
fn new() -> Manager {
Manager {
state: ManagerState { current_output: None, },
surface: None,
}
}
fn set_output(&mut self, output: OutputHandle) {
let output_state = output.get_state()
.or_warn(
&mut logging::Print,
logging::Problem::Bug,
// This is really bad. It only happens when the layer surface
// is placed, and it happens once.
// The layer surface is on an output that can't be tracked.
"Tried to set output that's not known to exist. Ignoring.",
);
self.state.current_output = output_state.map(
|state| (output.get_id(), state)
);
// TODO: At the time of writing, this function is only used once,
// before the layer surface is initialized.
// Therefore it doesn't update anything. Maybe it should in the future,
// if it sees more use.
}
fn handle_output_change(&mut self, output: OutputHandle) {
let (id, output_state) = match &self.state.current_output {
Some((id, state)) => {
if *id != output.get_id() { return } // Not the current output.
else { (id.clone(), state.clone()) }
},
None => return, // Keyboard isn't on any output.
};
if let Some(new_output_state) = output.get_state() {
if new_output_state != output_state {
let new_state = ManagerState {
current_output: Some((id.clone(), new_output_state)),
..self.state.clone()
};
if let Some(surface) = &self.surface {
let new_height = new_state.get_perceptual_height();
if new_height != self.state.get_perceptual_height() {
// TODO: here hard-size the keyboard and suggestion box too.
match new_height {
Some(new_height) => unsafe {
c::squeek_manager_set_surface_height(
*surface,
new_height,
)
}
None => log_print!(
logging::Level::Bug,
"Can't calculate new size",
),
}
}
}
self.state = new_state;
}
};
}
}
fn register_output_man(
ui_man: Rc<RefCell<Manager>>,
output_man: &mut Outputs,
) {
let ui_man = ui_man.clone();
output_man.set_update_cb(Box::new(move |output: OutputHandle| {
let mut ui_man = ui_man.borrow_mut();
ui_man.handle_output_change(output)
}))
}

View File

@ -94,6 +94,7 @@ pub mod c {
}
/// Extracts the reference to the data.
/// It may cause problems if attempted in more than one place
// FIXME: check for null
pub unsafe fn unwrap(self) -> Rc<RefCell<T>> {
Rc::from_raw(self.0)
}
@ -107,6 +108,10 @@ pub mod c {
Rc::into_raw(used_rc); // prevent dropping the original reference
rc
}
pub fn is_null(&self) -> bool {
self.0.is_null()
}
}
impl<T> Clone for Wrapped<T> {