popover: Install emoji layout

In order to do that, an additional piece of state (layout switcher) was exposed to the event handlers, a separation between squeekboard-only and system layouts was introduced, along with a Translation structure to prevent mixing up strings.
This commit is contained in:
Dorota Czaplejewicz
2019-12-08 14:32:31 +00:00
parent a799178b6a
commit bafd1e6eb3
11 changed files with 234 additions and 46 deletions

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

@ -0,0 +1,15 @@
---
outlines:
default: { width: 37.46341, height: 52 }
altline: { width: 48.39024, 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); EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self);
squeek_layout_drag(priv->keyboard->layout, priv->keyboard->manager->virtual_keyboard, 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) static void release(EekGtkKeyboard *self, guint32 time)
{ {
EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); 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 static gboolean

View File

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

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, void squeek_layout_release(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
struct transformation widget_to_layout, struct transformation widget_to_layout,
uint32_t timestamp, uint32_t timestamp,
EekboardContextService *manager,
EekGtkKeyboard *ui_keyboard); 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_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, 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, void squeek_layout_drag(struct squeek_layout *layout, struct zwp_virtual_keyboard_v1 *virtual_keyboard,
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, 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_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); void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);
#endif #endif

View File

@ -26,6 +26,7 @@ use std::vec::Vec;
use ::action::Action; use ::action::Action;
use ::drawing; use ::drawing;
use ::keyboard::{ KeyState, PressType }; use ::keyboard::{ KeyState, PressType };
use ::manager;
use ::submission::{ Timestamp, VirtualKeyboard }; use ::submission::{ Timestamp, VirtualKeyboard };
use ::util::find_max_double; use ::util::find_max_double;
@ -258,6 +259,7 @@ pub mod c {
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
widget_to_layout: Transformation, widget_to_layout: Transformation,
time: u32, time: u32,
manager: manager::c::Manager,
ui_keyboard: EekGtkKeyboard, ui_keyboard: EekGtkKeyboard,
) { ) {
let time = Timestamp(time); let time = Timestamp(time);
@ -273,6 +275,7 @@ pub mod c {
&widget_to_layout, &widget_to_layout,
time, time,
ui_keyboard, ui_keyboard,
manager,
key, key,
); );
} }
@ -344,6 +347,7 @@ 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,
ui_keyboard: EekGtkKeyboard, ui_keyboard: EekGtkKeyboard,
) { ) {
let time = Timestamp(time); let time = Timestamp(time);
@ -378,6 +382,7 @@ pub mod c {
&widget_to_layout, &widget_to_layout,
time, time,
ui_keyboard, ui_keyboard,
manager,
key, key,
); );
} }
@ -395,6 +400,7 @@ pub mod c {
&widget_to_layout, &widget_to_layout,
time, time,
ui_keyboard, ui_keyboard,
manager,
key, key,
); );
} }
@ -853,6 +859,7 @@ mod seat {
widget_to_layout: &c::Transformation, widget_to_layout: &c::Transformation,
time: Timestamp, time: Timestamp,
ui_keyboard: c::EekGtkKeyboard, ui_keyboard: c::EekGtkKeyboard,
manager: manager::c::Manager,
key: &Rc<RefCell<KeyState>>, key: &Rc<RefCell<KeyState>>,
) { ) {
layout.release_key(virtual_keyboard, &mut key.clone(), time); layout.release_key(virtual_keyboard, &mut key.clone(), time);
@ -874,7 +881,8 @@ mod seat {
}; };
::popover::show( ::popover::show(
ui_keyboard, 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;
mod locale_config; mod locale_config;
mod logging; mod logging;
mod manager;
mod outputs; mod outputs;
mod popover; mod popover;
mod resources; 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 { fn cstring_safe(s: &str) -> CString {
CString::new(s) CString::new(s)
.unwrap_or(CString::new("").unwrap()) .unwrap_or(CString::new("").unwrap())

18
src/manager.rs Normal file
View File

@ -0,0 +1,18 @@
/*! Procedures relating to the management of the switching of layouts */
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,
);
}
}

View File

@ -2,9 +2,11 @@
use gio; use gio;
use gtk; use gtk;
use ::layout::c::EekGtkKeyboard; use std::ffi::CString;
use ::locale::compare_current_locale; use ::layout::c::{ Bounds, EekGtkKeyboard };
use ::locale::{ Translation, compare_current_locale };
use ::locale_config::system_locale; use ::locale_config::system_locale;
use ::manager;
use ::resources; use ::resources;
use gio::ActionMapExt; 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(); let mut xml: Vec<u8> = Vec::new();
writeln!( writeln!(
xml, xml,
@ -101,7 +103,7 @@ fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
<menu id=\"app-menu\"> <menu id=\"app-menu\">
<section>" <section>"
).unwrap(); ).unwrap();
for (input_name, human_name) in inputs { for (input_name, translation) in inputs {
writeln!( writeln!(
xml, xml,
" "
@ -110,7 +112,7 @@ fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
<attribute name=\"action\">layout</attribute> <attribute name=\"action\">layout</attribute>
<attribute name=\"target\">{}</attribute> <attribute name=\"target\">{}</attribute>
</item>", </item>",
human_name, translation.0,
input_name, input_name,
).unwrap(); ).unwrap();
} }
@ -141,16 +143,68 @@ fn set_layout(kind: String, name: String) {
settings.apply(); 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(&'static str),
}
impl LayoutId {
fn get_name(&self) -> &str {
match &self {
LayoutId::System { kind: _, name } => name.as_str(),
LayoutId::Local(name) => name,
}
}
}
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).unwrap();
let name_ptr = name.as_ptr();
unsafe {
manager::c::eekboard_context_service_set_overlay(
manager,
name_ptr,
)
}
},
}
}
pub fn show(
window: EekGtkKeyboard,
position: Bounds,
manager: manager::c::Manager,
) {
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) };
let overlay_layouts = resources::get_overlays().into_iter()
.map(|name| LayoutId::Local(name));
let settings = gio::Settings::new("org.gnome.desktop.input-sources"); let settings = gio::Settings::new("org.gnome.desktop.input-sources");
let inputs = settings.get_value("sources").unwrap(); let inputs = settings.get_value("sources").unwrap();
let inputs = variants::get_tuples(inputs); let inputs = variants::get_tuples(inputs);
let input_names: Vec<&str> = inputs.iter() let system_layouts: Vec<LayoutId> = inputs.into_iter()
.map(|(_kind, name)| name.as_str()) .map(|(kind, name)| LayoutId::System { kind, name })
.collect();
let all_layouts: Vec<LayoutId> = system_layouts.clone()
.into_iter()
.chain(overlay_layouts)
.collect(); .collect();
let translations = system_locale() let translations = system_locale()
@ -162,26 +216,56 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
) )
.and_then(|lang| resources::get_layout_names(lang.as_str())); .and_then(|lang| resources::get_layout_names(lang.as_str()));
// sorted collection of human and machine names let use_codes: Box<dyn FnMut(&str) -> Translation>
let mut human_names: Vec<(&str, &str)> = match translations { = Box::new(|name| Translation(name));
Some(translations) => {
input_names.iter()
.map(|name| (*name, *translations.get(name).unwrap_or(name)))
.collect()
},
// display bare codes
None => {
input_names.iter()
.map(|n| (*n, *n)) // turns &&str into &str
.collect()
}
};
human_names.sort_unstable_by(|(_, human_label_a), (_, human_label_b)| { let translated_names = all_layouts.iter()
compare_current_locale(human_label_a, human_label_b) .map(LayoutId::get_name)
// use a different closure depending on whether we have translations
.map(match translations {
Some(translations) => {
let use_translations: Box<dyn FnMut(&str) -> Translation>
= Box::new(move |name| {
translations.get(name)
.map(|translation| translation.clone())
.unwrap_or(Translation(name))
});
use_translations
},
None => use_codes,
}); });
let builder = make_menu_builder(human_names); // sorted collection of human and machine names
let mut human_names: Vec<(Translation, LayoutId)> = translated_names
.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)
});
// 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 // Much more debuggable to populate the model & menu
// from a string representation // from a string representation
// than add items imperatively // than add items imperatively
@ -195,16 +279,21 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
height: position.width.floor() as i32, height: position.width.floor() as i32,
}); });
if let Some(current_name) = input_names.get(0) { if let Some(current_layout) = system_layouts.get(0) {
let current_name = current_name.to_variant(); let current_name_variant = choices.iter()
.find(
|(_id, layout)| layout == current_layout
).unwrap()
.0.to_variant();
let layout_action = gio::SimpleAction::new_stateful( let layout_action = gio::SimpleAction::new_stateful(
"layout", "layout",
Some(current_name.type_()), Some(current_name_variant.type_()),
&current_name, &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 { match state {
Some(v) => { Some(v) => {
v.get::<String>() v.get::<String>()
@ -212,10 +301,20 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
eprintln!("Variant is not string: {:?}", v); eprintln!("Variant is not string: {:?}", v);
None 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"), None => eprintln!("No variant selected"),
}; };
menu_inner.popdown();
}); });
let action_group = gio::SimpleActionGroup::new(); let action_group = gio::SimpleActionGroup::new();

View File

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

View File

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