translations: Use gnome-desktop's xkb info database for layout names
This commit is contained in:
1
debian/control
vendored
1
debian/control
vendored
@ -9,6 +9,7 @@ Build-Depends:
|
||||
ninja-build,
|
||||
pkg-config,
|
||||
libglib2.0-dev,
|
||||
libgnome-desktop-3-dev,
|
||||
libgtk-3-dev,
|
||||
libcroco3-dev,
|
||||
librust-bitflags-1-dev (>= 1.0),
|
||||
|
||||
@ -1,24 +1,93 @@
|
||||
/*! Locale-specific functions */
|
||||
/*! Locale-specific functions. */
|
||||
|
||||
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 {
|
||||
use std::os::raw::c_char;
|
||||
use super::*;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type c_int = i32;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct GnomeXkbInfo(*const c_void);
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" {
|
||||
// from libc
|
||||
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)]
|
||||
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 {
|
||||
CString::new(s)
|
||||
.unwrap_or(CString::new("").unwrap())
|
||||
|
||||
@ -39,6 +39,7 @@ cc = meson.get_compiler('c')
|
||||
deps = [
|
||||
# dependency('glib-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('libcroco-0.6'),
|
||||
dependency('wayland-client', version: '>=1.14'),
|
||||
|
||||
@ -4,7 +4,8 @@ use gio;
|
||||
use gtk;
|
||||
use std::ffi::CString;
|
||||
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 ::manager;
|
||||
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();
|
||||
writeln!(
|
||||
xml,
|
||||
@ -227,37 +228,73 @@ pub fn show(
|
||||
)
|
||||
.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()
|
||||
.map(LayoutId::get_name);
|
||||
let translated_names: Vec<Translation> = match translations {
|
||||
.map(|id| match id {
|
||||
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) => {
|
||||
translated_names
|
||||
.map(move |name| {
|
||||
translations.get(name)
|
||||
.map(|translation| translation.clone())
|
||||
.unwrap_or(Translation(name))
|
||||
.map(|status| match status {
|
||||
Status::Remaining(name) => {
|
||||
translations.get(name.as_str())
|
||||
.unwrap_or(&Translation(name.as_str()))
|
||||
.to_owned()
|
||||
},
|
||||
Status::Translated(t) => t,
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
None => {
|
||||
translated_names.map(|name| Translation(name))
|
||||
translated_names
|
||||
.map(|status| match status {
|
||||
Status::Remaining(name) => OwnedTranslation(name),
|
||||
Status::Translated(t) => t,
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
};
|
||||
|
||||
// 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()
|
||||
.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)
|
||||
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))>
|
||||
let choices_with_translations: Vec<(String, (OwnedTranslation, LayoutId))>
|
||||
= human_names.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, human_entry)| {(
|
||||
@ -268,7 +305,7 @@ pub fn show(
|
||||
|
||||
let builder = make_menu_builder(
|
||||
choices_with_translations.iter()
|
||||
.map(|(id, (translation, _))| (id.as_str(), translation.clone()))
|
||||
.map(|(id, (translation, _))| (id.as_str(), (*translation).clone()))
|
||||
.collect()
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user