Merge branch 'layouts' into 'master'

state: Select the layout

See merge request World/Phosh/squeekboard!553
This commit is contained in:
dcz
2022-06-14 15:23:14 +00:00
24 changed files with 475 additions and 345 deletions

View File

@ -35,6 +35,7 @@
#include "eekboard/eekboard-context-service.h" #include "eekboard/eekboard-context-service.h"
#include "src/layout.h" #include "src/layout.h"
#include "src/popover.h"
#include "src/submission.h" #include "src/submission.h"
#define LIBFEEDBACK_USE_UNSTABLE_API #define LIBFEEDBACK_USE_UNSTABLE_API
@ -48,6 +49,8 @@ typedef struct _EekGtkKeyboardPrivate
struct render_geometry render_geometry; // mutable struct render_geometry render_geometry; // mutable
EekboardContextService *eekboard_context; // unowned reference EekboardContextService *eekboard_context; // unowned reference
struct squeek_popover *popover; // shared reference
struct squeek_state_manager *state_manager; // shared reference
struct submission *submission; // unowned reference struct submission *submission; // unowned reference
struct squeek_layout_state *layout; // unowned struct squeek_layout_state *layout; // unowned
@ -118,15 +121,6 @@ eek_gtk_keyboard_real_draw (GtkWidget *self,
return FALSE; return FALSE;
} }
// Units of virtual pixels size
static enum squeek_arrangement_kind get_type(uint32_t width, uint32_t height) {
(void)height;
if (width < 540) {
return ARRANGEMENT_KIND_BASE;
}
return ARRANGEMENT_KIND_WIDE;
}
static void static void
eek_gtk_keyboard_real_size_allocate (GtkWidget *self, eek_gtk_keyboard_real_size_allocate (GtkWidget *self,
GtkAllocation *allocation) GtkAllocation *allocation)
@ -134,15 +128,6 @@ eek_gtk_keyboard_real_size_allocate (GtkWidget *self,
EekGtkKeyboard *keyboard = EEK_GTK_KEYBOARD (self); EekGtkKeyboard *keyboard = EEK_GTK_KEYBOARD (self);
EekGtkKeyboardPrivate *priv = EekGtkKeyboardPrivate *priv =
eek_gtk_keyboard_get_instance_private (keyboard); eek_gtk_keyboard_get_instance_private (keyboard);
// check if the change would switch types
enum squeek_arrangement_kind new_type = get_type(
(uint32_t)(allocation->width - allocation->x),
(uint32_t)(allocation->height - allocation->y));
if (priv->layout->arrangement != new_type) {
priv->layout->arrangement = new_type;
uint32_t time = gdk_event_get_time(NULL);
eekboard_context_service_use_layout(priv->eekboard_context, priv->layout, time);
}
if (priv->renderer) { if (priv->renderer) {
set_allocation_size (keyboard, priv->keyboard->layout, set_allocation_size (keyboard, priv->keyboard->layout,
@ -158,6 +143,7 @@ on_event_triggered (LfbEvent *event,
GAsyncResult *res, GAsyncResult *res,
gpointer unused) gpointer unused)
{ {
(void)unused;
g_autoptr (GError) err = NULL; g_autoptr (GError) err = NULL;
if (!lfb_event_trigger_feedback_finish (event, res, &err)) { if (!lfb_event_trigger_feedback_finish (event, res, &err)) {
@ -188,7 +174,7 @@ static void drag(EekGtkKeyboard *self,
squeek_layout_drag(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout, squeek_layout_drag(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout,
priv->submission, priv->submission,
x, y, priv->render_geometry.widget_to_layout, time, x, y, priv->render_geometry.widget_to_layout, time,
priv->eekboard_context, self); priv->popover, priv->state_manager, self);
} }
static void release(EekGtkKeyboard *self, guint32 time) static void release(EekGtkKeyboard *self, guint32 time)
@ -199,7 +185,7 @@ static void release(EekGtkKeyboard *self, guint32 time)
} }
squeek_layout_release(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout, squeek_layout_release(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout,
priv->submission, priv->render_geometry.widget_to_layout, time, priv->submission, priv->render_geometry.widget_to_layout, time,
priv->eekboard_context, self); priv->popover, priv->state_manager, self);
} }
static gboolean static gboolean
@ -406,13 +392,15 @@ on_notify_keyboard (GObject *object,
GtkWidget * GtkWidget *
eek_gtk_keyboard_new (EekboardContextService *eekservice, eek_gtk_keyboard_new (EekboardContextService *eekservice,
struct submission *submission, struct submission *submission,
struct squeek_layout_state *layout) struct squeek_state_manager *state_manager,
struct squeek_popover *popover)
{ {
EekGtkKeyboard *ret = EEK_GTK_KEYBOARD(g_object_new (EEK_TYPE_GTK_KEYBOARD, NULL)); EekGtkKeyboard *ret = EEK_GTK_KEYBOARD(g_object_new (EEK_TYPE_GTK_KEYBOARD, NULL));
EekGtkKeyboardPrivate *priv = (EekGtkKeyboardPrivate*)eek_gtk_keyboard_get_instance_private (ret); EekGtkKeyboardPrivate *priv = (EekGtkKeyboardPrivate*)eek_gtk_keyboard_get_instance_private (ret);
priv->popover = popover;
priv->eekboard_context = eekservice; priv->eekboard_context = eekservice;
priv->submission = submission; priv->submission = submission;
priv->layout = layout; priv->state_manager = state_manager;
priv->renderer = NULL; priv->renderer = NULL;
// This should really be done on initialization. // This should really be done on initialization.
// Before the widget is allocated, // Before the widget is allocated,

View File

@ -30,6 +30,8 @@
#include "eek/eek-renderer.h" #include "eek/eek-renderer.h"
#include "eek/eek-types.h" #include "eek/eek-types.h"
#include "src/main.h"
#include "src/popover.h"
struct submission; struct submission;
struct squeek_layout_state; struct squeek_layout_state;
@ -48,7 +50,7 @@ struct _EekGtkKeyboardClass
gpointer pdummy[24]; gpointer pdummy[24];
}; };
GtkWidget *eek_gtk_keyboard_new (EekboardContextService *eekservice, struct submission *submission, struct squeek_layout_state *layout); GtkWidget *eek_gtk_keyboard_new (EekboardContextService *eekservice, struct submission *submission, struct squeek_state_manager *state_manager, struct squeek_popover *popover);
void eek_gtk_keyboard_emit_feedback (EekGtkKeyboard *self); void eek_gtk_keyboard_emit_feedback (EekGtkKeyboard *self);
G_END_DECLS G_END_DECLS

View File

@ -55,7 +55,7 @@ static guint signals[LAST_SIGNAL] = { 0, };
*/ */
struct _EekboardContextService { struct _EekboardContextService {
GObject parent; GObject parent;
struct squeek_layout_state *layout; // Unowned struct squeek_state_manager *state_manager; // shared reference
LevelKeyboard *keyboard; // currently used keyboard LevelKeyboard *keyboard; // currently used keyboard
GSettings *settings; // Owned reference GSettings *settings; // Owned reference
@ -126,24 +126,7 @@ settings_get_layout(GSettings *settings, char **type, char **layout)
g_variant_unref(inputs); g_variant_unref(inputs);
} }
void void eekboard_context_service_set_layout(EekboardContextService *context, struct squeek_layout *layout, uint32_t timestamp) {
eekboard_context_service_use_layout(EekboardContextService *context, struct squeek_layout_state *state, uint32_t timestamp) {
gchar *layout_name = state->layout_name;
gchar *overlay_name = state->overlay_name;
// try to get the best keyboard layout
if (layout_name == NULL) {
layout_name = "us";
}
// overlay is "Normal" for most layouts, we will only look for "terminal" in rust code.
// for now just avoid passing a null pointer
if (overlay_name == NULL) {
overlay_name = ""; // fallback to Normal
}
// generic part follows
struct squeek_layout *layout = squeek_load_layout(layout_name, state->arrangement, state->purpose, overlay_name);
LevelKeyboard *keyboard = level_keyboard_new(layout); LevelKeyboard *keyboard = level_keyboard_new(layout);
// set as current // set as current
LevelKeyboard *previous_keyboard = context->keyboard; LevelKeyboard *previous_keyboard = context->keyboard;
@ -169,17 +152,7 @@ static void eekboard_context_service_update_settings_layout(EekboardContextServi
settings_get_layout(context->settings, settings_get_layout(context->settings,
&keyboard_type, &keyboard_layout); &keyboard_type, &keyboard_layout);
if (g_strcmp0(context->layout->layout_name, keyboard_layout) != 0 || context->layout->overlay_name) { squeek_state_send_layout_set(context->state_manager, keyboard_layout, keyboard_type, gdk_event_get_time(NULL));
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.
uint32_t time = gdk_event_get_time(NULL);
eekboard_context_service_use_layout(context, context->layout, time);
}
} }
static gboolean static gboolean
@ -297,47 +270,17 @@ eekboard_context_service_get_keyboard (EekboardContextService *context)
return context->keyboard; return context->keyboard;
} }
// Used from Rust. EekboardContextService *eekboard_context_service_new(struct squeek_state_manager *state_manager)
// TODO: move hint management to Rust entirely
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;
uint32_t time = gdk_event_get_time(NULL);
eekboard_context_service_use_layout(context, context->layout, time);
}
}
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);
uint32_t time = gdk_event_get_time(NULL);
eekboard_context_service_use_layout(context, context->layout, time);
}
}
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); EekboardContextService *context = g_object_new (EEKBOARD_TYPE_CONTEXT_SERVICE, NULL);
context->layout = state; context->state_manager = state_manager;
eekboard_context_service_update_settings_layout(context); eekboard_context_service_update_settings_layout(context);
uint32_t time = gdk_event_get_time(NULL);
eekboard_context_service_use_layout(context, context->layout, time);
return context; return context;
} }
void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission) { void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission) {
context->submission = submission; context->submission = submission;
if (context->submission) { if (context->submission && context->keyboard) {
uint32_t time = gdk_event_get_time(NULL); uint32_t time = gdk_event_get_time(NULL);
submission_use_layout(context->submission, context->keyboard->layout, time); submission_use_layout(context->submission, context->keyboard->layout, time);
} }

View File

@ -24,6 +24,7 @@
#include "src/submission.h" #include "src/submission.h"
#include "src/layout.h" #include "src/layout.h"
#include "src/main.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h" #include "virtual-keyboard-unstable-v1-client-protocol.h"
#include "text-input-unstable-v3-client-protocol.h" #include "text-input-unstable-v3-client-protocol.h"
@ -37,7 +38,7 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(EekboardContextService, eekboard_context_service, EEKBOARD, CONTEXT_SERVICE, GObject) G_DECLARE_FINAL_TYPE(EekboardContextService, eekboard_context_service, EEKBOARD, CONTEXT_SERVICE, GObject)
EekboardContextService *eekboard_context_service_new(struct squeek_layout_state *state); EekboardContextService *eekboard_context_service_new(struct squeek_state_manager *state_manager);
void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission); void eekboard_context_service_set_submission(EekboardContextService *context, struct submission *submission);
void eekboard_context_service_destroy (EekboardContextService *context); void eekboard_context_service_destroy (EekboardContextService *context);
LevelKeyboard *eekboard_context_service_get_keyboard(EekboardContextService *context); LevelKeyboard *eekboard_context_service_get_keyboard(EekboardContextService *context);
@ -45,7 +46,5 @@ LevelKeyboard *eekboard_context_service_get_keyboard(EekboardContextService *con
void eekboard_context_service_set_keymap(EekboardContextService *context, void eekboard_context_service_set_keymap(EekboardContextService *context,
const LevelKeyboard *keyboard); const LevelKeyboard *keyboard);
void
eekboard_context_service_use_layout(EekboardContextService *context, struct squeek_layout_state *layout, uint32_t timestamp);
G_END_DECLS G_END_DECLS
#endif /* EEKBOARD_CONTEXT_SERVICE_H */ #endif /* EEKBOARD_CONTEXT_SERVICE_H */

23
src/actors/mod.rs Normal file
View File

@ -0,0 +1,23 @@
/* Copyright (C) 2022 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Actors are parts of Squeekboard containing state independent from the main application state.
Because main application state is meant to be immutable,
it cannot be referenced directly by pieces of logic
interacting with the environment.
Such impure logic is split away (actor's logic)
and combined with relevant pieces of state (actor state),
thus preserving the purity (and sometimes simplicity) of the main state.
Actors can communicate with the main state by sending it messages,
and by receiving updates from it.
*/
// TODO: move crate::panel into crate::actors::panel.
// Panel contains state and logic to protect the main state from getting flooded
// with low-level wayland and gtk sizing events.
pub mod popover;

40
src/actors/popover.rs Normal file
View File

@ -0,0 +1,40 @@
/* Copyright (C) 2022 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! The popover is opened directly by the GTK surface,
without bouncing click events off the main state.
Then it must accurately show which layout has been selected.
It can get the system layout directly from gsettings on open,
but it cannot get the user-selected overlay, because it's stored in state.
To solve this, overlay will be cached in the popover actor,
and updated by main state every time it changes.
*/
pub mod c {
use super::*;
use crate::util::c::Wrapped;
/// The mutable instance of state
pub type Actor = Wrapped<State>;
}
#[derive(Clone)]
pub struct State {
pub overlay: Option<String>,
}
impl State {
pub fn new() -> Self {
Self { overlay: None }
}
}
pub fn set_overlay(
actor: &c::Actor,
overlay: Option<String>,
) {
let actor = actor.clone_ref();
let mut actor = actor.borrow_mut();
actor.overlay = overlay;
}

View File

@ -6,18 +6,30 @@
use std::time::Duration; use std::time::Duration;
use crate::imservice::ContentPurpose;
use crate::layout::ArrangementKind;
use crate::outputs::OutputId; use crate::outputs::OutputId;
use crate::panel::PixelSize; use crate::panel::PixelSize;
/// The keyboard should hide after this has elapsed to prevent flickering. /// The keyboard should hide after this has elapsed to prevent flickering.
pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200); pub const HIDING_TIMEOUT: Duration = Duration::from_millis(200);
/// Description of parameters which influence panel contents
#[derive(PartialEq, Clone, Debug)]
pub struct Contents {
pub name: String,
pub kind: ArrangementKind,
pub overlay_name: Option<String>,
pub purpose: ContentPurpose,
}
/// The outwardly visible state of visibility /// The outwardly visible state of visibility
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub enum Outcome { pub enum Outcome {
Visible { Visible {
output: OutputId, output: OutputId,
height: PixelSize, height: PixelSize,
contents: Contents,
}, },
Hidden, Hidden,
} }

View File

@ -7,64 +7,16 @@
use std::env; use std::env;
use std::fmt; use std::fmt;
use std::path::PathBuf; use std::path::PathBuf;
use std::convert::TryFrom;
use super::{ Error, LoadError }; use super::{ Error, LoadError };
use super::parsing; use super::parsing;
use ::layout::ArrangementKind; use crate::layout;
use ::logging; use crate::layout::ArrangementKind;
use ::util::c::as_str; use crate::logging;
use ::xdg; use crate::xdg;
use ::imservice::ContentPurpose; use crate::imservice::ContentPurpose;
// traits, derives
use ::logging::Warn;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use std::os::raw::c_char;
#[no_mangle]
pub extern "C"
fn squeek_load_layout(
name: *const c_char, // name of the keyboard
type_: u32, // type like Wide
variant: u32, // purpose variant like numeric, terminal...
overlay: *const c_char, // the overlay (looking for "terminal")
) -> *mut ::layout::Layout {
let type_ = match type_ {
0 => ArrangementKind::Base,
1 => ArrangementKind::Wide,
_ => panic!("Bad enum value"),
};
let name = as_str(&name)
.expect("Bad layout name")
.expect("Empty layout name");
let variant = ContentPurpose::try_from(variant)
.or_print(
logging::Problem::Warning,
"Received invalid purpose value",
)
.unwrap_or(ContentPurpose::Normal);
let overlay_str = as_str(&overlay)
.expect("Bad overlay name")
.expect("Empty overlay name");
let overlay_str = match overlay_str {
"" => None,
other => Some(other),
};
let (kind, layout) = load_layout_data_with_fallback(&name, type_, variant, overlay_str);
let layout = ::layout::Layout::new(layout, kind, variant);
Box::into_raw(Box::new(layout))
}
}
const FALLBACK_LAYOUT_NAME: &str = "us"; const FALLBACK_LAYOUT_NAME: &str = "us";
@ -265,7 +217,7 @@ fn load_layout_data_with_fallback(
kind: ArrangementKind, kind: ArrangementKind,
purpose: ContentPurpose, purpose: ContentPurpose,
overlay: Option<&str>, overlay: Option<&str>,
) -> (ArrangementKind, ::layout::LayoutData) { ) -> (ArrangementKind, layout::LayoutData) {
// Build the path to the right keyboard layout subdirectory // Build the path to the right keyboard layout subdirectory
let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR") let path = env::var_os("SQUEEKBOARD_KEYBOARDSDIR")
@ -300,6 +252,17 @@ fn load_layout_data_with_fallback(
panic!("No useful layout found!"); panic!("No useful layout found!");
} }
pub fn load_layout(
name: String,
kind: ArrangementKind,
variant: ContentPurpose,
overlay: Option<String>,
) -> layout::Layout {
let overlay = overlay.as_ref().map(String::as_str);
let (found_kind, layout)
= load_layout_data_with_fallback(&name, kind, variant, overlay);
layout::Layout::new(layout, found_kind, variant)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -4,7 +4,7 @@
/*! Combined module for dealing with layout files */ /*! Combined module for dealing with layout files */
mod loading; pub mod loading;
pub mod parsing; pub mod parsing;
use std::io; use std::io;

View File

@ -39,7 +39,9 @@ type UISender = glib::Sender<Commands>;
/// It sends outcomes to the glib main loop using a channel. /// It sends outcomes to the glib main loop using a channel.
/// The outcomes are applied by the UI end of the channel in the `main` module. /// The outcomes are applied by the UI end of the channel in the `main` module.
// This could still be reasonably tested, // This could still be reasonably tested,
// by creating a glib::Sender and checking what messages it receives. /// by creating a glib::Sender and checking what messages it receives.
// This can/should be abstracted over Event and Commands,
// so that the C call-ins can be thrown away from here and defined near events.
#[derive(Clone)] #[derive(Clone)]
pub struct Threaded { pub struct Threaded {
thread: Sender, thread: Sender,
@ -108,8 +110,11 @@ mod c {
use super::*; use super::*;
use crate::state::Presence; use crate::state::Presence;
use crate::state::LayoutChoice;
use crate::state::visibility; use crate::state::visibility;
use crate::util;
use crate::util::c::Wrapped; use crate::util::c::Wrapped;
use std::os::raw::c_char;
#[no_mangle] #[no_mangle]
pub extern "C" pub extern "C"
@ -140,4 +145,28 @@ mod c {
sender.send(Event::PhysicalKeyboard(state)) sender.send(Event::PhysicalKeyboard(state))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager"); .or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
} }
#[no_mangle]
pub extern "C"
fn squeek_state_send_layout_set(
sender: Wrapped<Threaded>,
name: *const c_char,
source: *const c_char,
// TODO: use when synthetic events are needed
_timestamp: u32,
) {
let sender = sender.clone_ref();
let sender = sender.borrow();
let string_or_empty = |v| String::from(
util::c::as_str(v)
.unwrap_or(Some(""))
.unwrap_or("")
);
sender
.send(Event::LayoutChoice(LayoutChoice {
name: string_or_empty(&name),
source: string_or_empty(&source).into(),
}))
.or_warn(&mut logging::Print, logging::Problem::Warning, "Can't send to state manager");
}
} }

View File

@ -226,7 +226,7 @@ bitflags!{
/// use rs::imservice::ContentPurpose; /// use rs::imservice::ContentPurpose;
/// assert_eq!(ContentPurpose::Alpha as u32, 1); /// assert_eq!(ContentPurpose::Alpha as u32, 1);
/// ``` /// ```
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum ContentPurpose { pub enum ContentPurpose {
Normal = 0, Normal = 0,
Alpha = 1, Alpha = 1,

View File

@ -7,6 +7,8 @@
#include "eek/eek-gtk-keyboard.h" #include "eek/eek-gtk-keyboard.h"
#include "eek/eek-renderer.h" #include "eek/eek-renderer.h"
#include "eek/eek-types.h" #include "eek/eek-types.h"
#include "src/main.h"
#include "src/popover.h"
#include "src/submission.h" #include "src/submission.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h" #include "virtual-keyboard-unstable-v1-client-protocol.h"
#include "text-input-unstable-v3-client-protocol.h" #include "text-input-unstable-v3-client-protocol.h"
@ -40,7 +42,8 @@ void squeek_layout_release(struct squeek_layout *layout,
struct submission *submission, struct submission *submission,
struct transformation widget_to_layout, struct transformation widget_to_layout,
uint32_t timestamp, uint32_t timestamp,
EekboardContextService *manager, struct squeek_popover *popover,
struct squeek_state_manager *state,
EekGtkKeyboard *ui_keyboard); EekGtkKeyboard *ui_keyboard);
void squeek_layout_release_all_only(struct squeek_layout *layout, void squeek_layout_release_all_only(struct squeek_layout *layout,
struct submission *submission, struct submission *submission,
@ -54,7 +57,8 @@ void squeek_layout_drag(struct squeek_layout *layout,
struct submission *submission, struct submission *submission,
double x_widget, double y_widget, double x_widget, double y_widget,
struct transformation widget_to_layout, struct transformation widget_to_layout,
uint32_t timestamp, EekboardContextService *manager, uint32_t timestamp, struct squeek_popover *popover,
struct squeek_state_manager *state,
EekGtkKeyboard *ui_keyboard); EekGtkKeyboard *ui_keyboard);
void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr, struct submission *submission); 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); void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);

View File

@ -25,30 +25,35 @@ use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use std::vec::Vec; use std::vec::Vec;
use ::action::Action; use crate::action::Action;
use ::drawing; use crate::actors;
use ::float_ord::FloatOrd; use crate::drawing;
use ::keyboard::KeyState; use crate::float_ord::FloatOrd;
use ::logging; use crate::keyboard::KeyState;
use ::manager; use crate::logging;
use ::submission::{ Submission, SubmitData, Timestamp }; use crate::popover;
use ::util::find_max_double; use crate::receiver;
use crate::submission::{ Submission, SubmitData, Timestamp };
use crate::util::find_max_double;
use ::imservice::ContentPurpose; use crate::imservice::ContentPurpose;
// Traits // Traits
use std::borrow::Borrow; use std::borrow::Borrow;
use ::logging::Warn; use crate::logging::Warn;
/// Gathers stuff defined in C or called by C /// Gathers stuff defined in C or called by C
pub mod c { pub mod c {
use super::*; use super::*;
use gtk_sys; use crate::receiver;
use std::os::raw::c_void;
use crate::submission::c::Submission as CSubmission; use crate::submission::c::Submission as CSubmission;
use gtk_sys;
use std::ops::{ Add, Sub }; use std::ops::{ Add, Sub };
use std::os::raw::c_void;
use crate::util::CloneOwned;
// The following defined in C // The following defined in C
#[repr(transparent)] #[repr(transparent)]
@ -215,13 +220,17 @@ pub mod c {
submission: CSubmission, submission: CSubmission,
widget_to_layout: Transformation, widget_to_layout: Transformation,
time: u32, time: u32,
manager: manager::c::Manager, popover: actors::popover::c::Actor,
app_state: receiver::c::State,
ui_keyboard: EekGtkKeyboard, ui_keyboard: EekGtkKeyboard,
) { ) {
let time = Timestamp(time); let time = Timestamp(time);
let layout = unsafe { &mut *layout }; let layout = unsafe { &mut *layout };
let submission = submission.clone_ref(); let submission = submission.clone_ref();
let mut submission = submission.borrow_mut(); let mut submission = submission.borrow_mut();
let app_state = app_state.clone_owned();
let popover_state = popover.clone_owned();
let ui_backend = UIBackend { let ui_backend = UIBackend {
widget_to_layout, widget_to_layout,
keyboard: ui_keyboard, keyboard: ui_keyboard,
@ -236,7 +245,7 @@ pub mod c {
&mut submission, &mut submission,
Some(&ui_backend), Some(&ui_backend),
time, time,
Some(manager), Some((&popover_state, app_state.clone())),
key, key,
); );
} }
@ -315,13 +324,18 @@ pub mod c {
x_widget: f64, y_widget: f64, x_widget: f64, y_widget: f64,
widget_to_layout: Transformation, widget_to_layout: Transformation,
time: u32, time: u32,
manager: manager::c::Manager, popover: actors::popover::c::Actor,
app_state: receiver::c::State,
ui_keyboard: EekGtkKeyboard, ui_keyboard: EekGtkKeyboard,
) { ) {
let time = Timestamp(time); let time = Timestamp(time);
let layout = unsafe { &mut *layout }; let layout = unsafe { &mut *layout };
let submission = submission.clone_ref(); let submission = submission.clone_ref();
let mut submission = submission.borrow_mut(); let mut submission = submission.borrow_mut();
// We only need to query state here, not update.
// A copy is enough.
let popover_state = popover.clone_owned();
let app_state = app_state.clone_owned();
let ui_backend = UIBackend { let ui_backend = UIBackend {
widget_to_layout, widget_to_layout,
keyboard: ui_keyboard, keyboard: ui_keyboard,
@ -352,7 +366,7 @@ pub mod c {
&mut submission, &mut submission,
Some(&ui_backend), Some(&ui_backend),
time, time,
Some(manager), Some((&popover_state, app_state.clone())),
key, key,
); );
} }
@ -377,7 +391,7 @@ pub mod c {
&mut submission, &mut submission,
Some(&ui_backend), Some(&ui_backend),
time, time,
Some(manager), Some((&popover_state, app_state.clone())),
key, key,
); );
} }
@ -1035,7 +1049,11 @@ mod seat {
submission: &mut Submission, submission: &mut Submission,
ui: Option<&UIBackend>, ui: Option<&UIBackend>,
time: Timestamp, time: Timestamp,
manager: Option<manager::c::Manager>, // TODO: intermediate measure:
// passing state conditionally because it's only used for popover.
// Eventually, it should be used for sumitting button events,
// and passed always.
manager: Option<(&actors::popover::State, receiver::State)>,
rckey: &Rc<RefCell<KeyState>>, rckey: &Rc<RefCell<KeyState>>,
) { ) {
let key: KeyState = { let key: KeyState = {
@ -1070,7 +1088,7 @@ mod seat {
// only show when UI is present // only show when UI is present
Action::ShowPreferences => if let Some(ui) = &ui { Action::ShowPreferences => if let Some(ui) = &ui {
// only show when layout manager is available // only show when layout manager is available
if let Some(manager) = manager { if let Some((manager, app_state)) = manager {
let view = layout.get_current_view(); let view = layout.get_current_view();
let places = ::layout::procedures::find_key_places( let places = ::layout::procedures::find_key_places(
view, &rckey, view, &rckey,
@ -1085,10 +1103,11 @@ mod seat {
width: button.size.width, width: button.size.width,
height: button.size.height, height: button.size.height,
}; };
::popover::show( popover::show(
ui.keyboard, ui.keyboard,
ui.widget_to_layout.reverse_bounds(bounds), ui.widget_to_layout.reverse_bounds(bounds),
manager, manager,
app_state,
); );
} }
} }

View File

@ -23,6 +23,7 @@ mod assert_matches;
mod logging; mod logging;
mod action; mod action;
mod actors;
mod animation; mod animation;
pub mod data; pub mod data;
mod debug; mod debug;
@ -34,10 +35,10 @@ mod keyboard;
mod layout; mod layout;
mod locale; mod locale;
mod main; mod main;
mod manager;
mod outputs; mod outputs;
mod panel; mod panel;
mod popover; mod popover;
mod receiver;
mod resources; mod resources;
mod state; mod state;
mod style; mod style;

View File

@ -9,6 +9,7 @@
#include "eek/eek-types.h" #include "eek/eek-types.h"
#include "dbus.h" #include "dbus.h"
#include "panel.h" #include "panel.h"
#include "src/popover.h"
struct receiver; struct receiver;
@ -23,9 +24,10 @@ struct rsobjects {
struct squeek_state_manager *state_manager; struct squeek_state_manager *state_manager;
struct submission *submission; struct submission *submission;
struct squeek_wayland *wayland; struct squeek_wayland *wayland;
struct squeek_popover *popover;
}; };
void register_ui_loop_handler(struct receiver *receiver, struct panel_manager *panel, EekboardContextService *hint_manager, DBusHandler *dbus_handler); void register_ui_loop_handler(struct receiver *receiver, struct panel_manager *panel, struct squeek_popover *popover, EekboardContextService *hint_manager, DBusHandler *dbus_handler);
struct rsobjects squeek_init(void); struct rsobjects squeek_init(void);
@ -33,3 +35,4 @@ void squeek_state_send_force_visible(struct squeek_state_manager *state);
void squeek_state_send_force_hidden(struct squeek_state_manager *state); void squeek_state_send_force_hidden(struct squeek_state_manager *state);
void squeek_state_send_keyboard_present(struct squeek_state_manager *state, uint32_t keyboard_present); void squeek_state_send_keyboard_present(struct squeek_state_manager *state, uint32_t keyboard_present);
void squeek_state_send_layout_set(struct squeek_state_manager *state, char *name, char *layout, uint32_t timestamp);

View File

@ -3,9 +3,11 @@
*/ */
/*! Glue for the main loop. */ /*! Glue for the main loop. */
use crate::panel; use crate::actors;
use crate::animation;
use crate::debug; use crate::debug;
use crate::state; use crate::data::loading;
use crate::panel;
use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver}; use glib::{Continue, MainContext, PRIORITY_DEFAULT, Receiver};
@ -19,6 +21,7 @@ mod c {
use crate::event_loop::driver; use crate::event_loop::driver;
use crate::imservice::IMService; use crate::imservice::IMService;
use crate::imservice::c::InputMethod; use crate::imservice::c::InputMethod;
use crate::layout;
use crate::outputs::Outputs; use crate::outputs::Outputs;
use crate::state; use crate::state;
use crate::submission::Submission; use crate::submission::Submission;
@ -46,6 +49,7 @@ mod c {
submission: Wrapped<Submission>, submission: Wrapped<Submission>,
/// Not wrapped, because C needs to access this. /// Not wrapped, because C needs to access this.
wayland: *mut Wayland, wayland: *mut Wayland,
popover: actors::popover::c::Actor,
} }
/// Corresponds to wayland.h::squeek_wayland. /// Corresponds to wayland.h::squeek_wayland.
@ -78,7 +82,8 @@ mod c {
extern "C" { extern "C" {
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
fn init_wayland(wayland: *mut Wayland); fn init_wayland(wayland: *mut Wayland);
fn eekboard_context_service_set_hint_purpose(service: HintManager, hint: u32, purpose: u32); #[allow(improper_ctypes)]
fn eekboard_context_service_set_layout(service: HintManager, layout: *const layout::Layout, timestamp: u32);
// This should probably only get called from the gtk main loop, // This should probably only get called from the gtk main loop,
// given that dbus handler is using glib. // given that dbus handler is using glib.
fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8); fn dbus_handler_set_visible(dbus: *const DBusHandler, visible: u8);
@ -116,6 +121,7 @@ mod c {
state_manager: Wrapped::new(state_manager), state_manager: Wrapped::new(state_manager),
receiver: Wrapped::new(receiver), receiver: Wrapped::new(receiver),
wayland: Box::into_raw(wayland), wayland: Box::into_raw(wayland),
popover: Wrapped::new(actors::popover::State::new()),
} }
} }
@ -125,6 +131,7 @@ mod c {
fn register_ui_loop_handler( fn register_ui_loop_handler(
receiver: Wrapped<Receiver<Commands>>, receiver: Wrapped<Receiver<Commands>>,
panel_manager: panel::c::PanelManager, panel_manager: panel::c::PanelManager,
popover: actors::popover::c::Actor,
hint_manager: HintManager, hint_manager: HintManager,
dbus_handler: *const DBusHandler, dbus_handler: *const DBusHandler,
) { ) {
@ -137,7 +144,13 @@ mod c {
receiver.attach( receiver.attach(
Some(&ctx), Some(&ctx),
move |msg| { move |msg| {
main_loop_handle_message(msg, panel_manager.clone(), hint_manager, dbus_handler); main_loop_handle_message(
msg,
panel_manager.clone(),
&popover,
hint_manager,
dbus_handler,
);
Continue(true) Continue(true)
}, },
); );
@ -152,6 +165,7 @@ mod c {
fn main_loop_handle_message( fn main_loop_handle_message(
msg: Commands, msg: Commands,
panel_manager: Wrapped<panel::Manager>, panel_manager: Wrapped<panel::Manager>,
popover: &actors::popover::c::Actor,
hint_manager: HintManager, hint_manager: HintManager,
dbus_handler: *const DBusHandler, dbus_handler: *const DBusHandler,
) { ) {
@ -165,16 +179,29 @@ mod c {
} }
} }
if let Some(hints) = msg.layout_hint_set { if let Some(commands::SetLayout { description }) = msg.layout_selection {
let animation::Contents {
name,
kind,
overlay_name,
purpose,
} = description;
actors::popover::set_overlay(popover, overlay_name.clone());
let layout = loading::load_layout(name, kind, purpose, overlay_name);
let layout = Box::into_raw(Box::new(layout));
unsafe { unsafe {
eekboard_context_service_set_hint_purpose( eekboard_context_service_set_layout(hint_manager, layout, 0);
hint_manager,
hints.hint.bits(),
hints.purpose.clone() as u32,
)
};
} }
} }
}
}
pub mod commands {
use crate::animation;
#[derive(Clone, Debug)]
pub struct SetLayout {
pub description: animation::Contents,
}
} }
/// The commands consumed by the main loop, /// The commands consumed by the main loop,
@ -182,6 +209,6 @@ mod c {
#[derive(Clone)] #[derive(Clone)]
pub struct Commands { pub struct Commands {
pub panel_visibility: Option<panel::Command>, pub panel_visibility: Option<panel::Command>,
pub layout_hint_set: Option<state::InputMethodDetails>,
pub dbus_visible_set: Option<bool>, pub dbus_visible_set: Option<bool>,
pub layout_selection: Option<commands::SetLayout>,
} }

View File

@ -1,33 +0,0 @@
/*! Procedures relating to the management of the switching of layouts */
use ::util;
pub mod c {
use std::os::raw::{c_char, c_void};
/// EekboardContextService*
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct Manager(*const c_void);
extern "C" {
pub fn eekboard_context_service_set_overlay(
manager: Manager,
name: *const c_char,
);
pub fn eekboard_context_service_get_overlay(
manager: Manager,
) -> *const c_char;
}
}
/// Returns the overlay name.
/// The result lifetime is "as long as the C copy lives"
pub fn get_overlay(manager: c::Manager) -> Option<String> {
let raw_str = unsafe {
c::eekboard_context_service_get_overlay(manager)
};
// this string is generated from Rust, should never be invalid
util::c::as_str(&raw_str).unwrap()
.map(String::from)
}

View File

@ -52,7 +52,7 @@ make_widget (struct panel_manager *self)
if (self->widget) { if (self->widget) {
g_error("Widget already present"); g_error("Widget already present");
} }
self->widget = eek_gtk_keyboard_new (self->state, self->submission, self->layout); self->widget = eek_gtk_keyboard_new (self->state, self->submission, self->state_manager, self->popover);
gtk_widget_set_has_tooltip (self->widget, TRUE); gtk_widget_set_has_tooltip (self->widget, TRUE);
gtk_container_add (GTK_CONTAINER(self->window), self->widget); gtk_container_add (GTK_CONTAINER(self->window), self->widget);
@ -116,15 +116,16 @@ panel_manager_resize (struct panel_manager *self, uint32_t height)
} }
struct panel_manager panel_manager_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout) struct panel_manager panel_manager_new(EekboardContextService *state, struct submission *submission, struct squeek_state_manager *state_manager, struct squeek_popover *popover)
{ {
struct panel_manager mgr = { struct panel_manager mgr = {
.state = state, .state = state,
.submission = submission, .submission = submission,
.layout = layout,
.window = NULL, .window = NULL,
.widget = NULL, .widget = NULL,
.current_output = NULL, .current_output = NULL,
.state_manager = state_manager,
.popover = popover,
}; };
return mgr; return mgr;
} }

View File

@ -2,14 +2,16 @@
#include "eek/layersurface.h" #include "eek/layersurface.h"
#include "src/layout.h" #include "src/layout.h"
#include "src/main.h"
#include "src/submission.h" #include "src/submission.h"
// Stores the objects that the panel and its widget will refer to // Stores the objects that the panel and its widget will refer to
struct panel_manager { struct panel_manager {
EekboardContextService *state; // unowned EekboardContextService *state; // unowned
/// Needed for instantiating the widget /// Needed for instantiating the widget
struct squeek_state_manager *state_manager; // shared reference
struct squeek_popover *popover; // shared reference
struct submission *submission; // unowned struct submission *submission; // unowned
struct squeek_layout_state *layout;
PhoshLayerSurface *window; PhoshLayerSurface *window;
GtkWidget *widget; // nullable GtkWidget *widget; // nullable
@ -18,4 +20,4 @@ struct panel_manager {
struct wl_output *current_output; struct wl_output *current_output;
}; };
struct panel_manager panel_manager_new(EekboardContextService *state, struct submission *submission, struct squeek_layout_state *layout); struct panel_manager panel_manager_new(EekboardContextService *state, struct submission *submission, struct squeek_state_manager *state_manager, struct squeek_popover *popover);

5
src/popover.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
/// Popover state.
/// Wrapped<actors::popover::State>
struct squeek_popover;

View File

@ -4,11 +4,13 @@ use gio;
use gtk; use gtk;
use std::ffi::CString; use std::ffi::CString;
use std::cmp::Ordering; use std::cmp::Ordering;
use ::layout::c::{ Bounds, EekGtkKeyboard }; use crate::actors;
use ::locale::{ OwnedTranslation, compare_current_locale }; use crate::layout::c::{ Bounds, EekGtkKeyboard };
use ::logging; use crate::locale::{ OwnedTranslation, compare_current_locale };
use ::manager; use crate::logging;
use ::resources; use crate::receiver;
use crate::resources;
use crate::state;
// Traits // Traits
use gio::prelude::ActionMapExt; use gio::prelude::ActionMapExt;
@ -16,7 +18,7 @@ use gio::prelude::SettingsExt;
use glib::translate::FromGlibPtrNone; use glib::translate::FromGlibPtrNone;
use glib::variant::ToVariant; use glib::variant::ToVariant;
use gtk::prelude::*; use gtk::prelude::*;
use ::logging::Warn; use crate::logging::Warn;
mod c { mod c {
use std::os::raw::c_char; use std::os::raw::c_char;
@ -127,9 +129,11 @@ fn get_settings(schema_name: &str) -> Option<gio::Settings> {
.map(|_sschema| gio::Settings::new(schema_name)) .map(|_sschema| gio::Settings::new(schema_name))
} }
fn set_layout(kind: String, name: String) { fn set_layout(kind: &str, name: &str) {
let settings = get_settings("org.gnome.desktop.input-sources"); let settings = get_settings("org.gnome.desktop.input-sources");
if let Some(settings) = settings { if let Some(settings) = settings {
let kind = String::from(kind);
let name = String::from(name);
#[cfg(feature = "glib_v0_14")] #[cfg(feature = "glib_v0_14")]
let inputs = settings.value("sources"); let inputs = settings.value("sources");
#[cfg(not(feature = "glib_v0_14"))] #[cfg(not(feature = "glib_v0_14"))]
@ -150,7 +154,7 @@ fn set_layout(kind: String, name: String) {
/// A reference to what the user wants to see /// A reference to what the user wants to see
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
enum LayoutId { pub enum LayoutId {
/// Affects the layout in system settings /// Affects the layout in system settings
System { System {
kind: String, kind: String,
@ -170,40 +174,23 @@ impl LayoutId {
} }
fn set_visible_layout( fn set_visible_layout(
manager: manager::c::Manager, layout_id: &LayoutId,
layout_id: LayoutId,
) { ) {
match layout_id { match layout_id {
LayoutId::System { kind, name } => { LayoutId::System { kind, name } => {
unsafe {
use std::ptr;
manager::c::eekboard_context_service_set_overlay(
manager,
ptr::null(),
);
}
set_layout(kind, name); set_layout(kind, name);
}
LayoutId::Local(name) => {
let name = CString::new(name.as_str()).unwrap();
let name_ptr = name.as_ptr();
unsafe {
manager::c::eekboard_context_service_set_overlay(
manager,
name_ptr,
)
}
}, },
_ => {},
} }
} }
/// Takes into account first any overlays, then system layouts from the list /// Takes into account first any overlays, then system layouts from the list
fn get_current_layout( fn get_current_layout(
manager: manager::c::Manager, popover: &actors::popover::State,
system_layouts: &Vec<LayoutId>, system_layouts: &Vec<LayoutId>,
) -> Option<LayoutId> { ) -> Option<LayoutId> {
match manager::get_overlay(manager) { match &popover.overlay {
Some(name) => Some(LayoutId::Local(name)), Some(name) => Some(LayoutId::Local(name.into())),
None => system_layouts.get(0).map(LayoutId::clone), None => system_layouts.get(0).map(LayoutId::clone),
} }
} }
@ -247,7 +234,8 @@ fn translate_layout_names(layouts: &Vec<LayoutId>) -> Vec<OwnedTranslation> {
pub fn show( pub fn show(
window: EekGtkKeyboard, window: EekGtkKeyboard,
position: Bounds, position: Bounds,
manager: manager::c::Manager, popover: &actors::popover::State,
app_state: receiver::State,
) { ) {
unsafe { gtk::set_initialized() }; unsafe { gtk::set_initialized() };
let window = unsafe { gtk::Widget::from_glib_none(window.0) }; let window = unsafe { gtk::Widget::from_glib_none(window.0) };
@ -327,7 +315,7 @@ pub fn show(
let action_group = gio::SimpleActionGroup::new(); let action_group = gio::SimpleActionGroup::new();
if let Some(current_layout) = get_current_layout(manager, &system_layouts) { if let Some(current_layout) = get_current_layout(popover, &system_layouts) {
let current_layout_name = all_layouts.iter() let current_layout_name = all_layouts.iter()
.find( .find(
|l| l.get_name() == current_layout.get_name() |l| l.get_name() == current_layout.get_name()
@ -356,10 +344,13 @@ pub fn show(
.find( .find(
|choices| state == choices.get_name() |choices| state == choices.get_name()
).unwrap(); ).unwrap();
set_visible_layout( app_state
manager, .send(state::Event::OverlayChanged(layout.clone()))
layout.clone(), .or_print(
) logging::Problem::Bug,
&format!("Can't send to state"),
);
set_visible_layout(layout)
}); });
}, },
None => log_print!( None => log_print!(

15
src/receiver.rs Normal file
View File

@ -0,0 +1,15 @@
/*! Defines the application-wide message bus for updating state.*/
use crate::event_loop::driver::Threaded;
pub mod c {
use super::*;
use crate::util::c::Wrapped;
pub type State = Wrapped<Threaded>;
}
// The state receiver is an endpoint of a channel, so it's safely cloneable.
// There's no need to keep it in a Rc.
// The C version uses Wrapped with an underlying Rc,
// because Wrapped is well-tested already.
pub type State = Threaded;

View File

@ -56,8 +56,6 @@ struct squeekboard {
/// Gsettings hook for visibility. TODO: this does not belong in gsettings. /// Gsettings hook for visibility. TODO: this does not belong in gsettings.
ServerContextService *settings_handler; ServerContextService *settings_handler;
struct panel_manager panel_manager; // Controls the shape of the panel. 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;
}; };
@ -400,7 +398,7 @@ main (int argc, char **argv)
// Also initializes wayland // Also initializes wayland
struct rsobjects rsobjects = squeek_init(); struct rsobjects rsobjects = squeek_init();
instance.settings_context = eekboard_context_service_new(&instance.layout_choice); instance.settings_context = eekboard_context_service_new(rsobjects.state_manager);
// set up dbus // set up dbus
@ -450,9 +448,10 @@ main (int argc, char **argv)
instance.panel_manager = panel_manager_new(instance.settings_context, instance.panel_manager = panel_manager_new(instance.settings_context,
rsobjects.submission, rsobjects.submission,
&instance.layout_choice); rsobjects.state_manager,
rsobjects.popover);
register_ui_loop_handler(rsobjects.receiver, &instance.panel_manager, instance.settings_context, instance.dbus_handler); register_ui_loop_handler(rsobjects.receiver, &instance.panel_manager, rsobjects.popover, instance.settings_context, instance.dbus_handler);
session_register(); session_register();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Purism SPC /* Copyright (C) 2021,2022 Purism SPC
* SPDX-License-Identifier: GPL-3.0+ * SPDX-License-Identifier: GPL-3.0+
*/ */
@ -8,11 +8,14 @@
use crate::animation; use crate::animation;
use crate::debug; use crate::debug;
use crate::imservice::{ ContentHint, ContentPurpose }; use crate::imservice::{ ContentHint, ContentPurpose };
use crate::layout::ArrangementKind;
use crate::main;
use crate::main::Commands; use crate::main::Commands;
use crate::outputs; use crate::outputs;
use crate::outputs::{Millimeter, OutputId, OutputState}; use crate::outputs::{Millimeter, OutputId, OutputState};
use crate::panel; use crate::panel;
use crate::panel::PixelSize; use crate::panel::PixelSize;
use crate::popover;
use crate::util::Rational; use crate::util::Rational;
use std::cmp; use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
@ -37,6 +40,29 @@ pub enum InputMethod {
InactiveSince(Instant), InactiveSince(Instant),
} }
#[derive(Clone, Debug)]
pub enum LayoutSource {
Xkb,
Other(String),
}
impl From<String> for LayoutSource {
fn from(v: String) -> Self {
if v.as_str() == "xkb" {
LayoutSource::Xkb
} else {
LayoutSource::Other(v)
}
}
}
/// The user's preferred system layout
#[derive(Clone, Debug)]
pub struct LayoutChoice {
pub name: String,
pub source: LayoutSource,
}
/// Incoming events. /// Incoming events.
/// This contains events that cause a change to the internal state. /// This contains events that cause a change to the internal state.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -45,6 +71,8 @@ pub enum Event {
Visibility(visibility::Event), Visibility(visibility::Event),
PhysicalKeyboard(Presence), PhysicalKeyboard(Presence),
Output(outputs::Event), Output(outputs::Event),
LayoutChoice(LayoutChoice),
OverlayChanged(popover::LayoutId),
Debug(debug::Event), Debug(debug::Event),
/// Event triggered because a moment in time passed. /// Event triggered because a moment in time passed.
/// Use to animate state transitions. /// Use to animate state transitions.
@ -87,7 +115,7 @@ pub mod visibility {
/// The outwardly visible state. /// The outwardly visible state.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Outcome { pub struct Outcome {
pub visibility: animation::Outcome, pub panel: animation::Outcome,
pub im: InputMethod, pub im: InputMethod,
} }
@ -98,36 +126,40 @@ impl Outcome {
/// The receivers of the commands bear the burden /// The receivers of the commands bear the burden
/// of checking if the commands end up being no-ops. /// of checking if the commands end up being no-ops.
pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands { pub fn get_commands_to_reach(&self, new_state: &Self) -> Commands {
let layout_hint_set = match new_state {
Outcome {
visibility: animation::Outcome::Visible{..},
im: InputMethod::Active(hints),
} => Some(hints.clone()),
Outcome {
visibility: animation::Outcome::Visible{..},
im: InputMethod::InactiveSince(_),
} => Some(InputMethodDetails {
hint: ContentHint::NONE,
purpose: ContentPurpose::Normal,
}),
Outcome {
visibility: animation::Outcome::Hidden,
..
} => None,
};
// FIXME: handle switching outputs // FIXME: handle switching outputs
let (dbus_visible_set, panel_visibility) = match new_state.visibility { let (dbus_visible_set, panel_visibility) = match new_state.panel {
animation::Outcome::Visible{output, height} animation::Outcome::Visible{output, height, ..}
=> (Some(true), Some(panel::Command::Show{output, height})), => (Some(true), Some(panel::Command::Show{output, height})),
animation::Outcome::Hidden => (Some(false), Some(panel::Command::Hide)), animation::Outcome::Hidden => (Some(false), Some(panel::Command::Hide)),
}; };
// Compare the old and new states as not to flood with updates,
// which may look up in the file system.
use animation::Outcome::*;
let layout_selection = match &new_state.panel {
Visible{ contents: new_contents, ..} => {
let same
= if let Visible { contents, .. } = &self.panel {
contents == new_contents
} else {
false
};
if !same {
Some(main::commands::SetLayout {
description: new_contents.clone()
})
} else {
None
}
},
animation::Outcome::Hidden => None,
};
Commands { Commands {
panel_visibility, panel_visibility,
layout_hint_set,
dbus_visible_set, dbus_visible_set,
layout_selection,
} }
} }
} }
@ -156,6 +188,13 @@ pub struct Application {
/// but not sure about being allowed on non-touch displays. /// but not sure about being allowed on non-touch displays.
pub preferred_output: Option<OutputId>, pub preferred_output: Option<OutputId>,
pub outputs: HashMap<OutputId, OutputState>, pub outputs: HashMap<OutputId, OutputState>,
/// We presume that the system always has some preference,
/// even though we receive the preference after init,
/// and we might not receive one at all (gsettings missing).
/// Then a default is used.
pub layout_choice: LayoutChoice,
/// Manual override of the system layout
pub overlay_layout: Option<popover::LayoutId>,
} }
impl Application { impl Application {
@ -173,6 +212,11 @@ impl Application {
debug_mode_enabled: false, debug_mode_enabled: false,
preferred_output: None, preferred_output: None,
outputs: Default::default(), outputs: Default::default(),
layout_choice: LayoutChoice {
name: String::from("us"),
source: LayoutSource::Xkb,
},
overlay_layout: None,
} }
} }
@ -257,7 +301,18 @@ impl Application {
im: InputMethod::InactiveSince(old), im: InputMethod::InactiveSince(old),
..self ..self
}, },
} },
Event::LayoutChoice(layout_choice) => Self {
layout_choice,
overlay_layout: None,
..self
},
Event::OverlayChanged(overlay_layout) => Self {
overlay_layout: Some(overlay_layout),
..self
},
}; };
if state.debug_mode_enabled { if state.debug_mode_enabled {
@ -273,7 +328,9 @@ Outcome:
state state
} }
fn get_preferred_height(output: &OutputState) -> Option<PixelSize> { fn get_preferred_height_and_arrangement(output: &OutputState)
-> Option<(PixelSize, ArrangementKind)>
{
output.get_pixel_size() output.get_pixel_size()
.map(|px_size| { .map(|px_size| {
// Assume isotropy. // Assume isotropy.
@ -301,7 +358,6 @@ Outcome:
// TODO: calculate based on selected layout // TODO: calculate based on selected layout
const ROW_COUNT: u32 = 4; const ROW_COUNT: u32 = 4;
let height = {
let ideal_height = IDEAL_TARGET_SIZE * ROW_COUNT as i32; let ideal_height = IDEAL_TARGET_SIZE * ROW_COUNT as i32;
let ideal_height_px = (ideal_height * density).ceil().0 as u32; let ideal_height_px = (ideal_height * density).ceil().0 as u32;
@ -316,45 +372,80 @@ Outcome:
} }
.as_scaled_ceiling(); .as_scaled_ceiling();
let height_as_widths = { let (arrangement, height_as_widths) = {
if abstract_width < 540 { if abstract_width < 540 {(
// Normal ArrangementKind::Base,
Rational { Rational {
numerator: 210, numerator: 210,
denominator: 360, denominator: 360,
} },
} else { )} else {(
// Wide ArrangementKind::Wide,
Rational { Rational {
numerator: 172, numerator: 172,
denominator: 540, denominator: 540,
} }
} )}
}; };
cmp::min(
let height
= cmp::min(
ideal_height_px, ideal_height_px,
(height_as_widths * px_size.width as i32).ceil() as u32, (height_as_widths * px_size.width as i32).ceil() as u32,
) );
};
(
PixelSize { PixelSize {
scale_factor: output.scale as u32, scale_factor: output.scale as u32,
pixels: cmp::min(height, px_size.height / 2), pixels: cmp::min(height, px_size.height / 2),
} },
arrangement,
)
}) })
} }
/// Returns layout name, overlay name
fn get_layout_names(&self) -> (String, Option<String>) {
(
String::from(match &self.overlay_layout {
Some(popover::LayoutId::System { name, .. }) => name,
_ => &self.layout_choice.name,
}),
match &self.overlay_layout {
Some(popover::LayoutId::Local(name)) => Some(name.clone()),
_ => None,
},
)
}
pub fn get_outcome(&self, now: Instant) -> Outcome { pub fn get_outcome(&self, now: Instant) -> Outcome {
// FIXME: include physical keyboard presence // FIXME: include physical keyboard presence
Outcome { Outcome {
visibility: match self.preferred_output { panel: match self.preferred_output {
None => animation::Outcome::Hidden, None => animation::Outcome::Hidden,
Some(output) => { Some(output) => {
// Hoping that this will get optimized out on branches not using `visible`. let (height, arrangement) = Self::get_preferred_height_and_arrangement(self.outputs.get(&output).unwrap())
let height = Self::get_preferred_height(self.outputs.get(&output).unwrap()) .unwrap_or((
.unwrap_or(PixelSize{pixels: 0, scale_factor: 1}); PixelSize{pixels: 0, scale_factor: 1},
ArrangementKind::Base,
));
let (layout_name, overlay) = self.get_layout_names();
// TODO: Instead of setting size to 0 when the output is invalid, // TODO: Instead of setting size to 0 when the output is invalid,
// simply go invisible. // simply go invisible.
let visible = animation::Outcome::Visible{ output, height }; let visible = animation::Outcome::Visible{
output,
height,
contents: animation::Contents {
kind: arrangement,
name: layout_name,
overlay_name: overlay,
purpose: match self.im {
InputMethod::Active(InputMethodDetails { purpose, .. }) => purpose,
InputMethod::InactiveSince(_) => ContentPurpose::Normal,
},
}
};
match (self.physical_keyboard, self.visibility_override) { match (self.physical_keyboard, self.visibility_override) {
(_, visibility::State::ForcedHidden) => animation::Outcome::Hidden, (_, visibility::State::ForcedHidden) => animation::Outcome::Hidden,
@ -446,7 +537,7 @@ pub mod test {
for _i in 0..100 { for _i in 0..100 {
now += Duration::from_millis(1); now += Duration::from_millis(1);
assert_matches!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).panel,
animation::Outcome::Visible{..}, animation::Outcome::Visible{..},
"Hidden when it should remain visible: {:?}", "Hidden when it should remain visible: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
@ -455,7 +546,10 @@ pub mod test {
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
assert_matches!(state.get_outcome(now).visibility, animation::Outcome::Visible{..}); assert_matches!(
state.get_outcome(now).panel,
animation::Outcome::Visible{..}
);
} }
/// Make sure that hiding works when input method goes away /// Make sure that hiding works when input method goes away
@ -472,7 +566,7 @@ pub mod test {
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility { while let animation::Outcome::Visible{..} = state.get_outcome(now).panel {
now += Duration::from_millis(1); now += Duration::from_millis(1);
assert!( assert!(
now < start + Duration::from_millis(250), now < start + Duration::from_millis(250),
@ -502,7 +596,7 @@ pub mod test {
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now); let state = state.apply_event(Event::InputMethod(InputMethod::InactiveSince(now)), now);
while let animation::Outcome::Visible{..} = state.get_outcome(now).visibility { while let animation::Outcome::Visible{..} = state.get_outcome(now).panel {
now += Duration::from_millis(1); now += Duration::from_millis(1);
assert!( assert!(
now < start + Duration::from_millis(250), now < start + Duration::from_millis(250),
@ -515,7 +609,7 @@ pub mod test {
for _i in 0..1000 { for _i in 0..1000 {
now += Duration::from_millis(1); now += Duration::from_millis(1);
assert_eq!( assert_eq!(
state.get_outcome(now).visibility, state.get_outcome(now).panel,
animation::Outcome::Hidden, animation::Outcome::Hidden,
"Appeared unnecessarily: {:?}", "Appeared unnecessarily: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
@ -537,7 +631,7 @@ pub mod test {
let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now); let state = state.apply_event(Event::Visibility(visibility::Event::ForceVisible), now);
assert_matches!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).panel,
animation::Outcome::Visible{..}, animation::Outcome::Visible{..},
"Failed to show: {:?}", "Failed to show: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
@ -550,7 +644,7 @@ pub mod test {
now += Duration::from_secs(1); now += Duration::from_secs(1);
assert_eq!( assert_eq!(
state.get_outcome(now).visibility, state.get_outcome(now).panel,
animation::Outcome::Hidden, animation::Outcome::Hidden,
"Failed to release forced visibility: {:?}", "Failed to release forced visibility: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
@ -571,7 +665,7 @@ pub mod test {
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Present), now); let state = state.apply_event(Event::PhysicalKeyboard(Presence::Present), now);
assert_eq!( assert_eq!(
state.get_outcome(now).visibility, state.get_outcome(now).panel,
animation::Outcome::Hidden, animation::Outcome::Hidden,
"Failed to hide: {:?}", "Failed to hide: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
@ -583,7 +677,7 @@ pub mod test {
let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now); let state = state.apply_event(Event::InputMethod(InputMethod::Active(imdetails_new())), now);
assert_eq!( assert_eq!(
state.get_outcome(now).visibility, state.get_outcome(now).panel,
animation::Outcome::Hidden, animation::Outcome::Hidden,
"Failed to remain hidden: {:?}", "Failed to remain hidden: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
@ -593,7 +687,7 @@ pub mod test {
let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now); let state = state.apply_event(Event::PhysicalKeyboard(Presence::Missing), now);
assert_matches!( assert_matches!(
state.get_outcome(now).visibility, state.get_outcome(now).panel,
animation::Outcome::Visible{..}, animation::Outcome::Visible{..},
"Failed to appear: {:?}", "Failed to appear: {:?}",
now.saturating_duration_since(start), now.saturating_duration_since(start),
@ -605,7 +699,7 @@ pub mod test {
fn size_l5() { fn size_l5() {
use crate::outputs::{Mode, Geometry, c, Size}; use crate::outputs::{Mode, Geometry, c, Size};
assert_eq!( assert_eq!(
Application::get_preferred_height(&OutputState { Application::get_preferred_height_and_arrangement(&OutputState {
current_mode: Some(Mode { current_mode: Some(Mode {
width: 720, width: 720,
height: 1440, height: 1440,
@ -619,10 +713,13 @@ pub mod test {
}), }),
scale: 2, scale: 2,
}), }),
Some(PixelSize { Some((
PixelSize {
scale_factor: 2, scale_factor: 2,
pixels: 420, pixels: 420,
}), },
ArrangementKind::Base,
)),
); );
} }
} }