Merge branch 'enoji' into 'master'

Enoji: implemented mechanism

See merge request Librem5/squeekboard!276
This commit is contained in:
Guido Gunther
2019-12-24 14:21:51 +00:00
12 changed files with 262 additions and 40 deletions

16
data/keyboards/emoji.yaml Normal file
View File

@ -0,0 +1,16 @@
---
outlines:
default: { width: 52, height: 52 }
altline: { width: 52, height: 52 }
views:
base:
- "😀 😁 😅 😂 😊 😇 🙃"
- "😍 😘 😋 😜 😎 🥳 😔"
- "😢 😭 😡 😱 🤔 😬 🙄"
- "preferences 🤨 🤓 😴 🤢 🤮 😈"
buttons:
preferences:
action: "show_prefs"
outline: "altline"
icon: "keyboard-mode-symbolic"

View File

@ -131,14 +131,17 @@ static void drag(EekGtkKeyboard *self,
{
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
squeek_layout_drag(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard,
x, y, eek_renderer_get_transformation(priv->renderer), time, self);
x, y, eek_renderer_get_transformation(priv->renderer), time,
priv->keyboard->manager, self);
}
static void release(EekGtkKeyboard *self, guint32 time)
{
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
squeek_layout_release(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard, eek_renderer_get_transformation(priv->renderer), time, self);
squeek_layout_release(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard,
eek_renderer_get_transformation(priv->renderer), time,
priv->keyboard->manager, self);
}
static gboolean

View File

@ -71,6 +71,8 @@ struct _EekboardContextServicePrivate {
LevelKeyboard *keyboard; // currently used keyboard
GHashTable *keyboard_hash; // a table of available keyboards, per layout
char *overlay;
GSettings *settings;
uint32_t hint;
uint32_t purpose;
@ -214,15 +216,17 @@ settings_get_layout(GSettings *settings, char **type, char **layout)
void
eekboard_context_service_update_layout(EekboardContextService *context, enum squeek_arrangement_kind t)
{
g_autofree gchar *keyboard_type = NULL;
g_autofree gchar *keyboard_layout = NULL;
settings_get_layout(context->priv->settings, &keyboard_type, &keyboard_layout);
if (!keyboard_type) {
keyboard_type = g_strdup("us");
if (context->priv->overlay) {
keyboard_layout = g_strdup(context->priv->overlay);
} else {
g_autofree gchar *keyboard_type = NULL;
settings_get_layout(context->priv->settings,
&keyboard_type, &keyboard_layout);
}
if (!keyboard_layout) {
keyboard_layout = g_strdup("undefined");
keyboard_layout = g_strdup("us");
}
EekboardContextServicePrivate *priv = EEKBOARD_CONTEXT_SERVICE_GET_PRIVATE(context);
@ -262,6 +266,8 @@ settings_handle_layout_changed(GSettings *s,
(void)keys;
(void)n_keys;
EekboardContextService *context = user_data;
free(context->priv->overlay);
context->priv->overlay = NULL;
update_layout_and_type(context);
return TRUE;
}
@ -391,6 +397,8 @@ eekboard_context_service_init (EekboardContextService *self)
g_warning ("Could not connect to gsettings updates, layout"
" changing unavailable");
}
self->priv->overlay = NULL;
}
/**
@ -463,6 +471,7 @@ eekboard_context_service_destroy (EekboardContextService *context)
if (context->priv->enabled) {
eekboard_context_service_disable (context);
}
free(context->priv->overlay);
g_signal_emit (context, signals[DESTROYED], 0);
}
@ -498,3 +507,14 @@ void eekboard_context_service_set_hint_purpose(EekboardContextService *context,
update_layout_and_type(context);
}
}
void
eekboard_context_service_set_overlay(EekboardContextService *context, const char* name) {
context->priv->overlay = strdup(name);
update_layout_and_type(context);
}
const char*
eekboard_context_service_get_overlay(EekboardContextService *context) {
return context->priv->overlay;
}

View File

@ -36,6 +36,7 @@ void squeek_layout_free(struct squeek_layout*);
void squeek_layout_release(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
struct transformation widget_to_layout,
uint32_t timestamp,
EekboardContextService *manager,
EekGtkKeyboard *ui_keyboard);
void squeek_layout_release_all_only(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard, uint32_t timestamp);
void squeek_layout_depress(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
@ -45,7 +46,8 @@ void squeek_layout_depress(struct squeek_layout *layout, struct zwp_virtual_keyb
void squeek_layout_drag(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
double x_widget, double y_widget,
struct transformation widget_to_layout,
uint32_t timestamp, EekGtkKeyboard *ui_keyboard);
uint32_t timestamp, EekboardContextService *manager,
EekGtkKeyboard *ui_keyboard);
void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);
void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);
#endif

View File

@ -26,6 +26,7 @@ use std::vec::Vec;
use ::action::Action;
use ::drawing;
use ::keyboard::{ KeyState, PressType };
use ::manager;
use ::submission::{ Timestamp, VirtualKeyboard };
use ::util::find_max_double;
@ -258,6 +259,7 @@ pub mod c {
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
widget_to_layout: Transformation,
time: u32,
manager: manager::c::Manager,
ui_keyboard: EekGtkKeyboard,
) {
let time = Timestamp(time);
@ -273,6 +275,7 @@ pub mod c {
&widget_to_layout,
time,
ui_keyboard,
manager,
key,
);
}
@ -344,6 +347,7 @@ pub mod c {
x_widget: f64, y_widget: f64,
widget_to_layout: Transformation,
time: u32,
manager: manager::c::Manager,
ui_keyboard: EekGtkKeyboard,
) {
let time = Timestamp(time);
@ -378,6 +382,7 @@ pub mod c {
&widget_to_layout,
time,
ui_keyboard,
manager,
key,
);
}
@ -395,6 +400,7 @@ pub mod c {
&widget_to_layout,
time,
ui_keyboard,
manager,
key,
);
}
@ -853,6 +859,7 @@ mod seat {
widget_to_layout: &c::Transformation,
time: Timestamp,
ui_keyboard: c::EekGtkKeyboard,
manager: manager::c::Manager,
key: &Rc<RefCell<KeyState>>,
) {
layout.release_key(virtual_keyboard, &mut key.clone(), time);
@ -874,7 +881,8 @@ mod seat {
};
::popover::show(
ui_keyboard,
widget_to_layout.reverse_bounds(bounds)
widget_to_layout.reverse_bounds(bounds),
manager,
);
}
}

View File

@ -25,6 +25,7 @@ mod layout;
mod locale;
mod locale_config;
mod logging;
mod manager;
mod outputs;
mod popover;
mod resources;

View File

@ -16,6 +16,9 @@ mod c {
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Translation<'a>(pub &'a str);
fn cstring_safe(s: &str) -> CString {
CString::new(s)
.unwrap_or(CString::new("").unwrap())

34
src/manager.rs Normal file
View File

@ -0,0 +1,34 @@
/*! 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);
#[no_mangle]
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

@ -2,9 +2,11 @@
use gio;
use gtk;
use ::layout::c::EekGtkKeyboard;
use ::locale::compare_current_locale;
use std::ffi::CString;
use ::layout::c::{ Bounds, EekGtkKeyboard };
use ::locale::{ Translation, compare_current_locale };
use ::locale_config::system_locale;
use ::manager;
use ::resources;
use gio::ActionMapExt;
@ -92,7 +94,7 @@ mod variants {
}
}
fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
fn make_menu_builder(inputs: Vec<(&str, Translation)>) -> gtk::Builder {
let mut xml: Vec<u8> = Vec::new();
writeln!(
xml,
@ -101,7 +103,7 @@ fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
<menu id=\"app-menu\">
<section>"
).unwrap();
for (input_name, human_name) in inputs {
for (input_name, translation) in inputs {
writeln!(
xml,
"
@ -110,7 +112,7 @@ fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
<attribute name=\"action\">layout</attribute>
<attribute name=\"target\">{}</attribute>
</item>",
human_name,
translation.0,
input_name,
).unwrap();
}
@ -141,16 +143,79 @@ fn set_layout(kind: String, name: String) {
settings.apply();
}
pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
/// A reference to what the user wants to see
#[derive(PartialEq, Clone, Debug)]
enum LayoutId {
/// Affects the layout in system settings
System {
kind: String,
name: String,
},
/// Only affects what this input method presents
Local(String),
}
impl LayoutId {
fn get_name(&self) -> &str {
match &self {
LayoutId::System { kind: _, name } => name.as_str(),
LayoutId::Local(name) => name.as_str(),
}
}
}
fn set_visible_layout(
manager: manager::c::Manager,
layout_id: LayoutId,
) {
match layout_id {
LayoutId::System { 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
fn get_current_layout(
manager: manager::c::Manager,
system_layouts: &Vec<LayoutId>,
) -> Option<LayoutId> {
match manager::get_overlay(manager) {
Some(name) => Some(LayoutId::Local(name)),
None => system_layouts.get(0).map(LayoutId::clone),
}
}
pub fn show(
window: EekGtkKeyboard,
position: Bounds,
manager: manager::c::Manager,
) {
unsafe { gtk::set_initialized() };
let window = unsafe { gtk::Widget::from_glib_none(window.0) };
let overlay_layouts = resources::get_overlays().into_iter()
.map(|name| LayoutId::Local(name.to_string()));
let settings = gio::Settings::new("org.gnome.desktop.input-sources");
let inputs = settings.get_value("sources").unwrap();
let inputs = variants::get_tuples(inputs);
let input_names: Vec<&str> = inputs.iter()
.map(|(_kind, name)| name.as_str())
let system_layouts: Vec<LayoutId> = inputs.into_iter()
.map(|(kind, name)| LayoutId::System { kind, name })
.collect();
let all_layouts: Vec<LayoutId> = system_layouts.clone()
.into_iter()
.chain(overlay_layouts)
.collect();
let translations = system_locale()
@ -162,26 +227,56 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
)
.and_then(|lang| resources::get_layout_names(lang.as_str()));
// sorted collection of human and machine names
let mut human_names: Vec<(&str, &str)> = match translations {
let translated_names = all_layouts.iter()
.map(LayoutId::get_name);
let translated_names: Vec<Translation> = match translations {
Some(translations) => {
input_names.iter()
.map(|name| (*name, *translations.get(name).unwrap_or(name)))
translated_names
.map(move |name| {
translations.get(name)
.map(|translation| translation.clone())
.unwrap_or(Translation(name))
})
.collect()
},
// display bare codes
None => {
input_names.iter()
.map(|n| (*n, *n)) // turns &&str into &str
translated_names.map(|name| Translation(name))
.collect()
}
},
};
human_names.sort_unstable_by(|(_, human_label_a), (_, human_label_b)| {
compare_current_locale(human_label_a, human_label_b)
// sorted collection of human and machine names
let mut human_names: Vec<(Translation, LayoutId)> = translated_names
.into_iter()
.zip(all_layouts.clone().into_iter())
.collect();
human_names.sort_unstable_by(|(tr_a, _), (tr_b, _)| {
compare_current_locale(tr_a.0, tr_b.0)
});
let builder = make_menu_builder(human_names);
// GVariant doesn't natively support `enum`s,
// so the `choices` vector will serve as a lookup table.
let choices_with_translations: Vec<(String, (Translation, LayoutId))>
= human_names.into_iter()
.enumerate()
.map(|(i, human_entry)| {(
format!("{}_{}", i, human_entry.1.get_name()),
human_entry,
)}).collect();
let builder = make_menu_builder(
choices_with_translations.iter()
.map(|(id, (translation, _))| (id.as_str(), translation.clone()))
.collect()
);
let choices: Vec<(String, LayoutId)>
= choices_with_translations.into_iter()
.map(|(id, (_tr, layout))| (id, layout))
.collect();
// Much more debuggable to populate the model & menu
// from a string representation
// than add items imperatively
@ -195,16 +290,21 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
height: position.width.floor() as i32,
});
if let Some(current_name) = input_names.get(0) {
let current_name = current_name.to_variant();
if let Some(current_layout) = get_current_layout(manager, &system_layouts) {
let current_name_variant = choices.iter()
.find(
|(_id, layout)| layout == &current_layout
).unwrap()
.0.to_variant();
let layout_action = gio::SimpleAction::new_stateful(
"layout",
Some(current_name.type_()),
&current_name,
Some(current_name_variant.type_()),
&current_name_variant,
);
layout_action.connect_change_state(|_action, state| {
let menu_inner = menu.clone();
layout_action.connect_change_state(move |_action, state| {
match state {
Some(v) => {
v.get::<String>()
@ -212,10 +312,20 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
eprintln!("Variant is not string: {:?}", v);
None
})
.map(|state| set_layout("xkb".into(), state));
.map(|state| {
let (_id, layout) = choices.iter()
.find(
|choices| state == choices.0
).unwrap();
set_visible_layout(
manager,
layout.clone(),
)
});
},
None => eprintln!("No variant selected"),
};
menu_inner.popdown();
});
let action_group = gio::SimpleActionGroup::new();

View File

@ -3,6 +3,7 @@
*/
use std::collections::HashMap;
use ::locale::Translation;
use std::iter::FromIterator;
@ -23,6 +24,8 @@ const KEYBOARDS: &[(*const str, *const str)] = &[
("no", include_str!("../data/keyboards/no.yaml")),
("number", include_str!("../data/keyboards/number.yaml")),
("se", include_str!("../data/keyboards/se.yaml")),
// Overlays
("emoji", include_str!("../data/keyboards/emoji.yaml")),
];
pub fn get_keyboard(needle: &str) -> Option<&'static str> {
@ -39,6 +42,18 @@ pub fn get_keyboard(needle: &str) -> Option<&'static str> {
})
}
const OVERLAY_NAMES: &[*const str] = &[
"emoji"
];
pub fn get_overlays() -> Vec<&'static str> {
OVERLAY_NAMES.iter()
.map(|name| {
let name: *const str = *name;
unsafe { &*name }
}).collect()
}
/// Translations of the layout identifier strings
const LAYOUT_NAMES: &[(*const str, *const str)] = &[
("de-DE", include_str!("../data/langs/de-DE.txt")),
@ -49,7 +64,7 @@ const LAYOUT_NAMES: &[(*const str, *const str)] = &[
];
pub fn get_layout_names(lang: &str)
-> Option<HashMap<&'static str, &'static str>>
-> Option<HashMap<&'static str, Translation<'static>>>
{
let translations = LAYOUT_NAMES.iter()
.find(|(name, _data)| {
@ -63,7 +78,7 @@ pub fn get_layout_names(lang: &str)
translations.map(make_mapping)
}
fn parse_line(line: &str) -> Option<(&str, &str)> {
fn parse_line(line: &str) -> Option<(&str, Translation)> {
let comment = line.trim().starts_with("#");
if comment {
None
@ -71,11 +86,11 @@ fn parse_line(line: &str) -> Option<(&str, &str)> {
let mut iter = line.splitn(2, " ");
let name = iter.next().unwrap();
// will skip empty and unfinished lines
iter.next().map(|tr| (name, tr.trim()))
iter.next().map(|tr| (name, Translation(tr.trim())))
}
}
fn make_mapping(data: &str) -> HashMap<&str, &str> {
fn make_mapping(data: &str) -> HashMap<&str, Translation> {
HashMap::from_iter(
data.split("\n")
.filter_map(parse_line)
@ -86,10 +101,17 @@ fn make_mapping(data: &str) -> HashMap<&str, &str> {
mod test {
use super::*;
#[test]
fn check_overlays_present() {
for name in get_overlays() {
assert!(get_keyboard(name).is_some());
}
}
#[test]
fn mapping_line() {
assert_eq!(
Some(("name", "translation")),
Some(("name", Translation("translation"))),
parse_line("name translation")
);
}

View File

@ -21,6 +21,7 @@ pub mod c {
use std::borrow::ToOwned;
// The lifetime on input limits the existence of the result
pub fn as_str(s: &*const c_char) -> Result<Option<&str>, Utf8Error> {
if s.is_null() {
Ok(None)

View File

@ -58,6 +58,8 @@ foreach layout : [
'no',
'number',
'se',
'emoji',
]
test(
'test_layout_' + layout,