Merge branch 'settings' into 'master'

Implement the word-of-mouth layout selection

See merge request Librem5/squeekboard!260
This commit is contained in:
David Boddie
2019-11-29 18:19:17 +00:00
3 changed files with 86 additions and 53 deletions

View File

@ -30,7 +30,7 @@ Most common testing is done in CI. Occasionally, and for each release, do perfor
- the application draws correctly
- it shows when relevant
- it changes layouts
- it changes levels
- it changes views
Testing with an application:
@ -50,10 +50,8 @@ Testing layouts:
Layouts can be selected using the GNOME Settings application.
```
# define all available layouts
$ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('xkb', 'ua')]"
# choose the active layout
$ gsettings set org.gnome.desktop.input-sources current 1
# define all available layouts. First one is currently selected.
$ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('xkb', 'de')]"
```
Coding

View File

@ -207,22 +207,8 @@ static void
settings_get_layout(GSettings *settings, char **type, char **layout)
{
GVariant *inputs = g_settings_get_value(settings, "sources");
guint32 index;
g_settings_get(settings, "current", "u", &index);
GVariantIter *iter;
g_variant_get(inputs, "a(ss)", &iter);
for (unsigned i = 0;
g_variant_iter_loop(iter, "(ss)", type, layout);
i++) {
if (i == index) {
//printf("Found layout %s %s\n", *type, *layout);
break;
}
}
g_variant_iter_free(iter);
g_variant_unref(inputs);
// current layout is always first
g_variant_get_child(inputs, 0, "(ss)", type, layout);
}
void

View File

@ -18,8 +18,11 @@ use std::io::Write;
mod variants {
use glib;
use glib::Variant;
use glib_sys;
use std::os::raw::c_char;
use glib::ToVariant;
use glib::translate::FromGlibPtrFull;
use glib::translate::ToGlibPtr;
@ -49,6 +52,44 @@ mod variants {
})
.collect()
}
/// "a(ss)" variant
/// Rust doesn't allow implementing existing traits for existing types
pub struct ArrayPairString(pub Vec<(String, String)>);
impl ToVariant for ArrayPairString {
fn to_variant(&self) -> Variant {
let tspec = "a(ss)".to_glib_none();
let builder = unsafe {
let vtype = glib_sys::g_variant_type_checked_(tspec.0);
glib_sys::g_variant_builder_new(vtype)
};
let ispec = "(ss)".to_glib_none();
for (a, b) in &self.0 {
let a = a.to_glib_none();
let b = b.to_glib_none();
// string pointers are weak references
// and will get silently invalidated
// as soon as the source is out of scope
{
let a: *const c_char = a.0;
let b: *const c_char = b.0;
unsafe {
glib_sys::g_variant_builder_add(
builder,
ispec.0,
a, b
);
}
}
}
unsafe {
let ret = glib_sys::g_variant_builder_end(builder);
glib_sys::g_variant_builder_unref(builder);
glib::Variant::from_glib_full(ret)
}
}
}
}
fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
@ -88,12 +129,15 @@ fn make_menu_builder(inputs: Vec<(&str, &str)>) -> gtk::Builder {
fn set_layout(kind: String, name: String) {
let settings = gio::Settings::new("org.gnome.desktop.input-sources");
let inputs = settings.get_value("sources").unwrap();
let inputs = variants::get_tuples(inputs).into_iter();
for (index, (ikind, iname)) in inputs.enumerate() {
if (&ikind, &iname) == (&kind, &name) {
settings.set_uint("current", index as u32);
}
}
let current = (kind.clone(), name.clone());
let inputs = variants::get_tuples(inputs).into_iter()
.filter(|t| t != &current);
let inputs = vec![(kind, name)].into_iter()
.chain(inputs).collect();
settings.set_value(
"sources",
&variants::ArrayPairString(inputs).to_variant()
);
settings.apply();
}
@ -103,7 +147,6 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
let settings = gio::Settings::new("org.gnome.desktop.input-sources");
let inputs = settings.get_value("sources").unwrap();
let current = settings.get_uint("current") as usize;
let inputs = variants::get_tuples(inputs);
let input_names: Vec<&str> = inputs.iter()
@ -152,38 +195,44 @@ pub fn show(window: EekGtkKeyboard, position: ::layout::c::Bounds) {
height: position.width.floor() as i32,
});
let initial_state = input_names[current].to_variant();
let action = input_names.get(0).map(|current_name| {
let current_name = current_name.to_variant();
let layout_action = gio::SimpleAction::new_stateful(
"layout",
Some(initial_state.type_()),
&initial_state,
);
let layout_action = gio::SimpleAction::new_stateful(
"layout",
Some(current_name.type_()),
&current_name,
);
let action_group = gio::SimpleActionGroup::new();
action_group.add_action(&layout_action);
let action_group = gio::SimpleActionGroup::new();
action_group.add_action(&layout_action);
menu.insert_action_group("popup", Some(&action_group));
layout_action
});
menu.insert_action_group("popup", Some(&action_group));
menu.bind_model(Some(&model), Some("popup"));
menu.connect_closed(move |_menu| {
let state = match layout_action.get_state() {
Some(v) => {
let s = v.get::<String>().or_else(|| {
eprintln!("Variant is not string: {:?}", v);
if let Some(layout_action) = &action {
let state = match layout_action.get_state() {
Some(v) => {
let s = v.get::<String>().or_else(|| {
eprintln!("Variant is not string: {:?}", v);
None
});
// FIXME: the `get_state` docs call for unrefing,
// but the function is nowhere to be found
// glib::Variant::unref(v);
s
},
None => {
eprintln!("No variant selected");
None
});
// FIXME: the `get_state` docs call for unrefing,
// but the function is nowhere to be found
// glib::Variant::unref(v);
s
},
None => {
eprintln!("No variant selected");
None
},
};
set_layout("xkb".into(), state.unwrap_or("us".into()));
},
};
set_layout("xkb".into(), state.unwrap_or("us".into()));
}
});
menu.popup();