diff --git a/rust/agama-dbus-server/src/locale.rs b/rust/agama-dbus-server/src/locale.rs index fa25bd5a3e..c42f07e86d 100644 --- a/rust/agama-dbus-server/src/locale.rs +++ b/rust/agama-dbus-server/src/locale.rs @@ -1,7 +1,7 @@ use crate::error::Error; use agama_lib::connection_to; use anyhow::Context; -use std::process::Command; +use std::{fs::read_dir, process::Command}; use zbus::{dbus_interface, Connection}; pub struct Locale { @@ -9,6 +9,7 @@ pub struct Locale { keymap: String, timezone_id: String, supported_locales: Vec, + ui_locale: String, } #[dbus_interface(name = "org.opensuse.Agama.Locale1")] @@ -89,6 +90,44 @@ impl Locale { Ok(()) } + #[dbus_interface(property, name = "UILocale")] + fn ui_locale(&self) -> &str { + &self.ui_locale + } + + #[dbus_interface(property, name = "UILocale")] + fn set_ui_locale(&mut self, locale: &str) { + self.ui_locale = locale.to_string(); + } + + /// Gets list of locales available on system. + /// + /// # Examples + /// + /// ``` + /// use agama_dbus_server::locale::Locale; + /// let locale = Locale::new(); + /// assert!(locale.list_ui_locales().unwrap().len() > 0); + /// ``` + #[dbus_interface(name = "ListUILocales")] + pub fn list_ui_locales(&self) -> Result, Error> { + // english is always available ui localization + let mut result = vec!["en".to_string()]; + const DIR: &str = "/usr/share/YaST2/locale/"; + let entries = read_dir(DIR).context("Reading YaST2 locale")?; + for entry in entries { + let entry = entry.context("Failed to read entry in YaST2 locale dir")?; + let name = entry + .file_name() + .to_str() + .context("Non valid UTF entry found in YaST2 locale dir")? + .to_string(); + result.push(name) + } + + Ok(result) + } + /* support only keymaps for console for now fn list_x11_keyboards(&self) -> Result, Error> { let keyboards = agama_locale_data::get_xkeyboards()?; @@ -176,16 +215,23 @@ impl Locale { } impl Locale { - fn new() -> Self { + pub fn new() -> Self { Self { locales: vec!["en_US.UTF-8".to_string()], keymap: "us".to_string(), timezone_id: "America/Los_Angeles".to_string(), supported_locales: vec!["en_US.UTF-8".to_string()], + ui_locale: "en".to_string(), } } } +impl Default for Locale { + fn default() -> Self { + Self::new() + } +} + pub async fn start_service(address: &str) -> Result> { const SERVICE_NAME: &str = "org.opensuse.Agama.Locale1"; const SERVICE_PATH: &str = "/org/opensuse/Agama/Locale1"; diff --git a/rust/agama-dbus-server/src/questions/answers.rs b/rust/agama-dbus-server/src/questions/answers.rs index d6ef306e59..60ade14fa0 100644 --- a/rust/agama-dbus-server/src/questions/answers.rs +++ b/rust/agama-dbus-server/src/questions/answers.rs @@ -73,7 +73,7 @@ impl Answers { } fn find_answer(&self, question: &GenericQuestion) -> Option<&Answer> { - self.answers.iter().find(|a| a.responds(&question)) + self.answers.iter().find(|a| a.responds(question)) } } diff --git a/rust/package/agama-cli.changes b/rust/package/agama-cli.changes index 47ac92275d..9cc5544198 100644 --- a/rust/package/agama-cli.changes +++ b/rust/package/agama-cli.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Wed Aug 30 12:57:59 UTC 2023 - Josef Reidinger + +- Locale service: add value for UI locale (gh#openSUSE/agama#725) + ------------------------------------------------------------------- Thu Aug 3 08:34:14 UTC 2023 - Imobach Gonzalez Sosa @@ -8,7 +13,7 @@ Thu Aug 3 08:34:14 UTC 2023 - Imobach Gonzalez Sosa InstallSettings and NetworkSettings. - Improve error reporting when working with the "config" subcommand. - + ------------------------------------------------------------------- Wed Aug 2 10:03:18 UTC 2023 - Imobach Gonzalez Sosa diff --git a/service/lib/agama/dbus/clients/locale.rb b/service/lib/agama/dbus/clients/locale.rb index ff27aaf19c..38b82859e3 100644 --- a/service/lib/agama/dbus/clients/locale.rb +++ b/service/lib/agama/dbus/clients/locale.rb @@ -26,6 +26,8 @@ module DBus module Clients # D-Bus client for locale configuration class Locale < Base + INTERFACE_NAME = "org.opensuse.Agama.Locale1" + def initialize super @@ -44,6 +46,18 @@ def supported_locales=(locales) dbus_object.supported_locales = locales end + def ui_locale + dbus_object[INTERFACE_NAME]["UILocale"] + end + + def ui_locale=(locale) + dbus_object[INTERFACE_NAME]["UILocale"] = locale + end + + def available_ui_locales + dbus_object.ListUILocales + end + # Finishes the language installation def finish dbus_object.Commit @@ -58,7 +72,20 @@ def finish def on_language_selected(&block) on_properties_change(dbus_object) do |_, changes, _| languages = changes["Locales"] - block.call(languages) + block.call(languages) if languages + end + end + + # Registers a callback to run when the ui locale changes + # + # @note Signal subscription is done only once. Otherwise, the latest subscription overrides + # the previous one. + # + # @param block [Proc] Callback to run when an ui locale changes + def on_ui_locale_change(&block) + on_properties_change(dbus_object) do |_, changes, _| + locale = changes["UILocale"] + block.call(locale) if locale end end diff --git a/service/lib/agama/dbus/language_service.rb b/service/lib/agama/dbus/language_service.rb deleted file mode 100644 index 3f4f2ac41e..0000000000 --- a/service/lib/agama/dbus/language_service.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -# Copyright (c) [2022] SUSE LLC -# -# All Rights Reserved. -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of version 2 of the GNU General Public License as published -# by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, contact SUSE LLC. -# -# To contact SUSE LLC about this file by physical or electronic mail, you may -# find current contact information at www.suse.com. - -require "dbus" -require "agama/dbus/bus" -require "agama/dbus/language" -require "agama/language" - -module Agama - module DBus - # D-Bus service (org.opensuse.Agama.Language1) - # - # It connects to the system D-Bus and answers requests on objects below - # `/org/opensuse/Agama/Language1`. - class LanguageService - # Service name - # - # @return [String] - SERVICE_NAME = "org.opensuse.Agama.Language1" - private_constant :SERVICE_NAME - - # Agama D-Bus - # - # @return [::DBus::BusConnection] - attr_reader :bus - - # @param _config [Config] Configuration object - # @param logger [Logger] - def initialize(_config, logger = nil) - @logger = logger || Logger.new($stdout) - @bus = Bus.current - end - - # Exports the installer object through the D-Bus service - def export - dbus_objects.each { |o| service.export(o) } - - paths = dbus_objects.map(&:path).join(", ") - logger.info "Exported #{paths} objects" - end - - # Call this from some main loop to dispatch the D-Bus messages - def dispatch - bus.dispatch_message_queue - end - - private - - # @return [Logger] - attr_reader :logger - - # @return [::DBus::ObjectServer] - def service - @service ||= bus.request_service(SERVICE_NAME) - end - - # @return [Array<::DBus::Object>] - def dbus_objects - @dbus_objects ||= [ - language_dbus - ] - end - - # @return [Agama::DBus::Language] - def language_dbus - @language_dbus ||= Agama::DBus::Language.new(language_backend(logger), logger) - end - - # @return [Agama::Language] - def language_backend(logger) - @language_backend ||= Agama::Language.new(logger).tap(&:probe) - end - end - end -end diff --git a/service/lib/agama/dbus/manager_service.rb b/service/lib/agama/dbus/manager_service.rb index 06c5e21cc3..c769e08326 100644 --- a/service/lib/agama/dbus/manager_service.rb +++ b/service/lib/agama/dbus/manager_service.rb @@ -24,9 +24,11 @@ require "agama/users" require "agama/cockpit_manager" require "agama/dbus/bus" +require "agama/dbus/clients/locale" require "agama/dbus/manager" require "agama/dbus/users" require "agama/dbus/storage/proposal" +require "agama/ui_locale" module Agama module DBus @@ -76,6 +78,10 @@ def initialize(config, logger = nil) # @note The service runs its startup phase def start setup_cockpit + # We need locale for data from users + locale_client = Clients::Locale.new + # TODO: test if we need to pass block with additional actions + @ui_locale = UILocale.new(locale_client) export manager.on_progress_change { dispatch } # make single thread more responsive manager.startup_phase diff --git a/service/lib/agama/dbus/software_service.rb b/service/lib/agama/dbus/software_service.rb index 22d73a1f51..8ce6ed9a4b 100644 --- a/service/lib/agama/dbus/software_service.rb +++ b/service/lib/agama/dbus/software_service.rb @@ -21,8 +21,10 @@ require "dbus" require "agama/dbus/bus" +require "agama/dbus/clients/locale" require "agama/dbus/software" require "agama/software" +require "agama/ui_locale" module Agama module DBus @@ -48,6 +50,14 @@ def initialize(config, logger = nil) @backend.on_progress_change { dispatch } end + # Starts software service. It does more then just #export method. + def start + locale_client = Clients::Locale.new + # TODO: test if we need to pass block with additional actions + @ui_locale = UILocale.new(locale_client) + export + end + # Exports the software object through the D-Bus service def export dbus_objects.each { |o| service.export(o) } diff --git a/service/lib/agama/dbus/storage_service.rb b/service/lib/agama/dbus/storage_service.rb index 2f4ee60167..4688c44576 100644 --- a/service/lib/agama/dbus/storage_service.rb +++ b/service/lib/agama/dbus/storage_service.rb @@ -21,8 +21,10 @@ require "dbus" require "agama/dbus/bus" +require "agama/dbus/clients/locale" require "agama/dbus/storage" require "agama/storage" +require "agama/ui_locale" module Agama module DBus @@ -48,6 +50,14 @@ def bus Bus.current end + # Starts storage service. It does more then just #export method. + def start + locale_client = Clients::Locale.new + # TODO: test if we need to pass block with additional actions + @ui_locale = UILocale.new(locale_client) + export + end + # Exports the storage proposal object through the D-Bus service def export dbus_objects.each { |o| service.export(o) } diff --git a/service/lib/agama/ui_locale.rb b/service/lib/agama/ui_locale.rb new file mode 100644 index 0000000000..cc67ba9475 --- /dev/null +++ b/service/lib/agama/ui_locale.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Copyright (c) [2022] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "yast" + +Yast.import "WFM" + +module Agama + # Object responsible for managing changes of localization produced by D-Bus backend. + class UILocale + include Yast::I18n + include Yast::Logger + # creates new UILocale object that will handle change of UI locale. + # + # @param [Agama::DBus::Clients::Locale] locale_client to communicate with dbus service + # @yield block is callback that is called when ui locale is changed + # to allow user to call methods that needs retranslation + def initialize(locale_client, &block) + @client = locale_client + change_locale(@client.ui_locale) + @client.on_ui_locale_change do |locale| + change_locale(locale) + block.call(locale) if block_given? + end + end + + private + + def change_locale(locale) + # TODO: check if we can use UTF-8 everywhere including strange arch consoles + Yast::WFM.SetLanguage(locale, "UTF-8") + # explicitelly set ENV to get localization also from libraries like libstorage + ENV["LANG"] = locale + ".UTF-8" + log.info "set yast language to #{locale}" + # explicit call to textdomain to force fast gettext change of language ASAP + textdomain "installation" + end + end +end diff --git a/service/package/rubygem-agama.changes b/service/package/rubygem-agama.changes index fd7da96943..a3f9bbd55e 100644 --- a/service/package/rubygem-agama.changes +++ b/service/package/rubygem-agama.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Wed Aug 30 12:39:18 UTC 2023 - Josef Reidinger + +- Respect UI locale in dbus services (gh#openSUSE/agama#725) + ------------------------------------------------------------------- Mon Aug 28 07:59:26 UTC 2023 - Knut Anderssen diff --git a/service/test/agama/dbus/manager_service_test.rb b/service/test/agama/dbus/manager_service_test.rb index 24bdfed15f..c46b5402a1 100644 --- a/service/test/agama/dbus/manager_service_test.rb +++ b/service/test/agama/dbus/manager_service_test.rb @@ -29,7 +29,11 @@ let(:config) { Agama::Config.new } let(:logger) { Logger.new($stdout, level: :warn) } let(:manager) { Agama::Manager.new(config, logger) } - let(:bus) { instance_double(Agama::DBus::Bus, request_name: nil) } + let(:locale_interface) { double("[]" => "en", on_signal: nil) } + let(:locale_service) do + double(object: double(introspect: nil, path: "test", "[]": locale_interface)) + end + let(:bus) { instance_double(Agama::DBus::Bus, request_name: nil, service: locale_service) } let(:bus_service) do instance_double(::DBus::ObjectServer, export: nil) end