translations: Use gnome-desktop's xkb info database for layout names

This commit is contained in:
Dorota Czaplejewicz
2019-12-09 13:38:38 +00:00
parent 647fde26f5
commit a93f3c55e7
4 changed files with 124 additions and 16 deletions

1
debian/control vendored
View File

@ -9,6 +9,7 @@ Build-Depends:
ninja-build, ninja-build,
pkg-config, pkg-config,
libglib2.0-dev, libglib2.0-dev,
libgnome-desktop-3-dev,
libgtk-3-dev, libgtk-3-dev,
libcroco3-dev, libcroco3-dev,
librust-bitflags-1-dev (>= 1.0), librust-bitflags-1-dev (>= 1.0),

View File

@ -1,24 +1,93 @@
/*! Locale-specific functions */ /*! Locale-specific functions. */
use std::cmp; use std::cmp;
use std::ffi::CString; use std::ffi::{ CStr, CString };
use std::os::raw::c_char;
use std::ptr;
use std::str::Utf8Error;
mod c { mod c {
use std::os::raw::c_char; use super::*;
use std::os::raw::c_void;
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub type c_int = i32; pub type c_int = i32;
#[derive(Clone, Copy)]
#[repr(C)]
pub struct GnomeXkbInfo(*const c_void);
#[no_mangle] #[no_mangle]
extern "C" { extern "C" {
// from libc // from libc
pub fn strcoll(cs: *const c_char, ct: *const c_char) -> c_int; pub fn strcoll(cs: *const c_char, ct: *const c_char) -> c_int;
// from gnome-desktop3
pub fn gnome_xkb_info_new() -> GnomeXkbInfo;
pub fn gnome_xkb_info_get_layout_info (
info: GnomeXkbInfo,
id: *const c_char,
display_name: *mut *const c_char,
short_name: *const *const c_char,
xkb_layout: *const *const c_char,
xkb_variant: *const *const c_char
) -> c_int;
pub fn g_object_unref(o: GnomeXkbInfo);
}
}
#[derive(Debug)]
pub enum Error {
StringConversion(Utf8Error),
NoInfo,
}
pub struct XkbInfo(c::GnomeXkbInfo);
impl XkbInfo {
pub fn new() -> XkbInfo {
XkbInfo(unsafe { c::gnome_xkb_info_new() })
}
pub fn get_display_name(&self, id: &str) -> Result<String, Error> {
let id = cstring_safe(id);
let id_ref = id.as_ptr();
let mut display_name: *const c_char = ptr::null();
let found = unsafe {
c::gnome_xkb_info_get_layout_info(
self.0,
id_ref,
&mut display_name as *mut *const c_char,
ptr::null(), ptr::null(), ptr::null(),
)
};
if found != 0 && !display_name.is_null() {
let display_name = unsafe { CStr::from_ptr(display_name) };
display_name.to_str()
.map(str::to_string)
.map_err(Error::StringConversion)
} else {
Err(Error::NoInfo)
}
}
}
impl Drop for XkbInfo {
fn drop(&mut self) {
unsafe { c::g_object_unref(self.0) }
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Translation<'a>(pub &'a str); pub struct Translation<'a>(pub &'a str);
impl<'a> Translation<'a> {
pub fn to_owned(&'a self) -> OwnedTranslation {
OwnedTranslation(self.0.to_owned())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OwnedTranslation(pub String);
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())

View File

@ -39,6 +39,7 @@ cc = meson.get_compiler('c')
deps = [ deps = [
# dependency('glib-2.0', version: '>=2.26.0'), # dependency('glib-2.0', version: '>=2.26.0'),
dependency('gio-2.0', version: '>=2.26.0'), dependency('gio-2.0', version: '>=2.26.0'),
dependency('gnome-desktop-3.0', version: '>=3.0'),
dependency('gtk+-3.0', version: '>=3.0'), dependency('gtk+-3.0', version: '>=3.0'),
dependency('libcroco-0.6'), dependency('libcroco-0.6'),
dependency('wayland-client', version: '>=1.14'), dependency('wayland-client', version: '>=1.14'),

View File

@ -4,7 +4,8 @@ use gio;
use gtk; use gtk;
use std::ffi::CString; use std::ffi::CString;
use ::layout::c::{ Bounds, EekGtkKeyboard }; use ::layout::c::{ Bounds, EekGtkKeyboard };
use ::locale::{ Translation, compare_current_locale }; use ::locale;
use ::locale::{ OwnedTranslation, Translation, compare_current_locale };
use ::locale_config::system_locale; use ::locale_config::system_locale;
use ::manager; use ::manager;
use ::resources; use ::resources;
@ -94,7 +95,7 @@ mod variants {
} }
} }
fn make_menu_builder(inputs: Vec<(&str, Translation)>) -> gtk::Builder { fn make_menu_builder(inputs: Vec<(&str, OwnedTranslation)>) -> gtk::Builder {
let mut xml: Vec<u8> = Vec::new(); let mut xml: Vec<u8> = Vec::new();
writeln!( writeln!(
xml, xml,
@ -227,37 +228,73 @@ pub fn show(
) )
.and_then(|lang| resources::get_layout_names(lang.as_str())); .and_then(|lang| resources::get_layout_names(lang.as_str()));
// The actual translation procedure attempts to take all xkb names
// from gnome-desktop's xkb info.
// Remaining names are translated using the internal database,
// which is only available if the locale is set.
// The result is a rather ugly and verbose translation procedure...
enum Status {
/// xkb names should get all translated here
Translated(OwnedTranslation),
/// Builtin names need builtin translations
Remaining(String),
}
let xkb_translator = locale::XkbInfo::new();
let translated_names = all_layouts.iter() let translated_names = all_layouts.iter()
.map(LayoutId::get_name); .map(|id| match id {
let translated_names: Vec<Translation> = match translations { LayoutId::System { name, kind: _ } => {
xkb_translator.get_display_name(name)
.map(|s| Status::Translated(OwnedTranslation(s)))
.unwrap_or_else(|e| {
eprintln!(
"No display name for xkb layout {}: {:?}",
name,
e,
);
Status::Remaining(name.clone())
})
},
LayoutId::Local(name) => Status::Remaining(name.clone()),
});
let translated_names: Vec<OwnedTranslation> = match translations {
Some(translations) => { Some(translations) => {
translated_names translated_names
.map(move |name| { .map(|status| match status {
translations.get(name) Status::Remaining(name) => {
.map(|translation| translation.clone()) translations.get(name.as_str())
.unwrap_or(Translation(name)) .unwrap_or(&Translation(name.as_str()))
.to_owned()
},
Status::Translated(t) => t,
}) })
.collect() .collect()
}, },
None => { None => {
translated_names.map(|name| Translation(name)) translated_names
.map(|status| match status {
Status::Remaining(name) => OwnedTranslation(name),
Status::Translated(t) => t,
})
.collect() .collect()
}, },
}; };
// sorted collection of human and machine names // sorted collection of human and machine names
let mut human_names: Vec<(Translation, LayoutId)> = translated_names let mut human_names: Vec<(OwnedTranslation, LayoutId)> = translated_names
.into_iter() .into_iter()
.zip(all_layouts.clone().into_iter()) .zip(all_layouts.clone().into_iter())
.collect(); .collect();
human_names.sort_unstable_by(|(tr_a, _), (tr_b, _)| { human_names.sort_unstable_by(|(tr_a, _), (tr_b, _)| {
compare_current_locale(tr_a.0, tr_b.0) compare_current_locale(&tr_a.0, &tr_b.0)
}); });
// GVariant doesn't natively support `enum`s, // GVariant doesn't natively support `enum`s,
// so the `choices` vector will serve as a lookup table. // so the `choices` vector will serve as a lookup table.
let choices_with_translations: Vec<(String, (Translation, LayoutId))> let choices_with_translations: Vec<(String, (OwnedTranslation, LayoutId))>
= human_names.into_iter() = human_names.into_iter()
.enumerate() .enumerate()
.map(|(i, human_entry)| {( .map(|(i, human_entry)| {(
@ -268,7 +305,7 @@ pub fn show(
let builder = make_menu_builder( let builder = make_menu_builder(
choices_with_translations.iter() choices_with_translations.iter()
.map(|(id, (translation, _))| (id.as_str(), translation.clone())) .map(|(id, (translation, _))| (id.as_str(), (*translation).clone()))
.collect() .collect()
); );