Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions asusd/src/aura_slash/trait_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ impl SlashZbus {
}

#[zbus(property)]
async fn mode(&self) -> zbus::fdo::Result<u8> {
async fn mode(&self) -> zbus::fdo::Result<SlashMode> {
let config = self.0.lock_config().await;
Ok(config.display_interval)
Ok(config.display_mode)
}

/// Set interval between slash animations (0-255)
Expand Down
4 changes: 4 additions & 0 deletions rog-control-center/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub struct Config {
pub start_fullscreen: bool,
pub fullscreen_width: u32,
pub fullscreen_height: u32,
#[serde(default)]
pub language: String,
// This field must be last
pub notifications: EnabledNotifications,
}
Expand All @@ -40,6 +42,7 @@ impl Default for Config {
start_fullscreen: false,
fullscreen_width: 1920,
fullscreen_height: 1080,
language: String::new(),
notifications: EnabledNotifications::default(),
ac_command: String::new(),
bat_command: String::new(),
Expand Down Expand Up @@ -100,6 +103,7 @@ impl From<Config461> for Config {
start_fullscreen: false,
fullscreen_width: 1920,
fullscreen_height: 1080,
language: String::new(),
notifications: c.enabled_notifications,
}
}
Expand Down
32 changes: 27 additions & 5 deletions rog-control-center/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,16 @@ async fn main() -> Result<()> {
}
}

// Try to open a proxy and check for app state first
{
// Single-instance guard. Skipped when this binary re-spawned itself for a
// "Reload Window" — the child carries ROGCC_NO_SINGLE_INSTANCE so it doesn't
// see the still-registered parent and exit; the parent quits right after
// spawning, freeing the name for future reloads. The bypass is one-shot:
// consume it below so a reload triggered from *this* process can't pass the
// skip on to its own child (otherwise the guard stays off forever).
let skip_single_instance = std::env::var_os("ROGCC_NO_SINGLE_INSTANCE").is_some();
std::env::remove_var("ROGCC_NO_SINGLE_INSTANCE");
if !skip_single_instance {
// Try to open a proxy and check for app state first
let user_con = zbus::blocking::Connection::session()?;
if let Ok(proxy) = ROGCCZbusProxyBlocking::new(&user_con) {
if let Ok(state) = proxy.state() {
Expand Down Expand Up @@ -92,6 +100,18 @@ async fn main() -> Result<()> {
// return Ok(());
}

// Apply the configured UI language through env vars before Runtime::new
// (they're process-global). "Reload Window" restarts the process, which
// re-reads config.language here so gettext resolves @tr() in the chosen
// locale at init_translations — no setlocale / unsafe / libc needed.
let startup_language = Config::new().load().language;
if !startup_language.is_empty() {
let locale = format!("{startup_language}.UTF-8");
env::set_var("LANG", &locale);
env::set_var("LC_ALL", &locale);
env::set_var("LANGUAGE", &startup_language);
}

Comment thread
NB-Group marked this conversation as resolved.
// start tokio
let rt = Runtime::new().expect("Unable to create Runtime");
// Enter the runtime so that `tokio::spawn` is available immediately.
Expand Down Expand Up @@ -171,6 +191,9 @@ async fn main() -> Result<()> {
};
let config = Arc::new(Mutex::new(config));

// setlocale + LANG/LC_ALL env were applied before Runtime::new (see above);
// gettext picks them up at init_translations below.

// GPU power status channel: written by the dGPU status monitor in
// notify.rs, read by the tray to color its icon
let (gpu_status_tx, gpu_status_rx) =
Expand All @@ -185,11 +208,10 @@ async fn main() -> Result<()> {
}

if std::env::var("RUST_TRANSLATIONS").is_ok() {
// don't care about content
log::debug!("---- Using local-dir translations");
log::debug!("Using system-installed translations");
slint::init_translations!("/usr/share/locale/");
} else {
log::debug!("Using system installed translations");
log::debug!("Using local translations");
slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/"));
}

Expand Down
119 changes: 119 additions & 0 deletions rog-control-center/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ pub fn setup_window(
let ui = MainWindow::new().expect("Couldn't create main window");
// propagate TUF flag to the UI so the sidebar can swap logo branding
ui.set_is_tuf(is_tuf);
ui.set_app_version(env!("CARGO_PKG_VERSION").into());
if let Err(e) = ui.window().show() {
warn!("Couldn't show main window: {e:?}");
}
Expand Down Expand Up @@ -202,6 +203,62 @@ fn ui_shortcut_status(status: ShortcutStatus) -> GlobalShortcutStatus {
}
}

/// Locale codes that have a translation on disk: the source `translations/`
/// tree (every subdir is ours) plus any installed under `/usr/share/locale`
/// that actually ships our catalog. Sorted + deduped, with an "en" fallback so
/// the picker is never empty. The locale *list* is automatic; the native
/// display names live in `language_display_name` (add a line there when a new
/// translation lands — until then it shows the raw code).
fn available_languages() -> Vec<SharedString> {
let mut set: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
// English is the source language (no .mo needed) — always present, so a
// fresh config never lands on a translation by default.
set.insert("en".to_owned());
let dev = concat!(env!("CARGO_MANIFEST_DIR"), "/translations");
for dir in [dev, "/usr/share/locale"] {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
// /usr/share/locale holds every app's locales, so only count
// dirs carrying our catalog; the source tree is all ours.
let ours = dir == dev || path.join("LC_MESSAGES/rog-control-center.mo").exists();
if ours {
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
set.insert(name.to_string());
}
}
}
}
}
let mut v: Vec<SharedString> = set.into_iter().map(SharedString::from).collect();
if v.is_empty() {
v.push(SharedString::from("en"));
}
v
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/// Native display name for a locale code — language pickers conventionally
/// show each language in its own tongue. Unknown codes fall back to themselves
/// so a newly added translation still shows up (as its code) until named here.
fn language_display_name(code: &str) -> SharedString {
let name = match code {
"en" => "English",
"zh_CN" => "简体中文",
"fr" => "Français",
"it" => "Italiano",
"ru" => "Русский",
"tr" => "Türkçe",
"uk_UA" => "Українська",
"pt_BR" => "Português (Brasil)",
"az" => "Azərbaycanca",
other => other,
};
SharedString::from(name)
}
Comment thread
NB-Group marked this conversation as resolved.

pub fn setup_app_settings_page(
ui: &MainWindow,
config: Arc<Mutex<Config>>,
Expand Down Expand Up @@ -365,4 +422,66 @@ pub fn setup_app_settings_page(
});
});
}

// Discover shipped translations at startup so the picker lists every
// language without a hardcoded array.
let codes = available_languages();
let configured_language = config
.try_lock()
.map(|lock| lock.language.clone())
.unwrap_or_default();
// Match the persisted language; fall back to "en" (source language), then
// index 0 — so a stale config like the old "en_US" still lands on English.
let current_idx = codes
.iter()
.position(|l| l.as_str() == configured_language.as_str())
.or_else(|| codes.iter().position(|l| l.as_str() == "en"))
.unwrap_or(0) as i32;
// The picker shows each language in its own name (standard for language
// selectors); the raw code is what gets persisted, so keep both in lockstep.
let display: Vec<SharedString> = codes
.iter()
.map(|c| language_display_name(c.as_str()))
.collect();
global.set_available_languages(slint::ModelRc::new(slint::VecModel::from(display)));
global.set_current_language(current_idx);

let config_copy = config.clone();
global.on_cb_change_language(move |index: i32| {
if let Some(code) = codes.get(index as usize) {
if let Ok(mut lock) = config_copy.try_lock() {
lock.language = code.to_string();
lock.write();
log::info!("Language changed to {code}; reload to apply");
} else {
log::warn!("config lock busy; language change to {code} not saved");
}
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Reload Window: spawn a fresh instance flagged to skip the single-instance
// guard (ROGCC_NO_SINGLE_INSTANCE), then quit this one. spawn+quit is
// reliable where exec() was not: the old DBus name is released on quit and
// the new image never races the check. The child re-reads config.language
// and re-resolves @tr() in the chosen locale.
global.on_cb_reload_window(move || {
let exe = match std::env::current_exe() {
Ok(e) => e,
Err(e) => {
log::error!("reload: cannot resolve current exe: {e}");
return;
}
};
log::info!("reload: spawning {:?}", exe);
match std::process::Command::new(exe)
.env("ROGCC_NO_SINGLE_INSTANCE", "1")
.spawn()
{
Ok(_) => {
slint::quit_event_loop()
.unwrap_or_else(|e| log::error!("reload: quit_event_loop: {e}"));
}
Err(e) => log::error!("reload: spawn failed: {e}"),
}
});
Comment thread
NB-Group marked this conversation as resolved.
}
13 changes: 13 additions & 0 deletions rog-control-center/src/ui/setup_aura.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,19 @@ pub fn setup_aura_page(
show_toast("LED mode applied".into(), "LED mode failed".into(), t, r);
});
});

// Speed slider: stamp the new speed onto the active effect,
// mirror it in the UI, then run the existing apply path (which
// also pushes to hardware and toasts) so the write logic stays
// in one place.
let w_speed = weak.clone();
h.global::<AuraPageData>().on_cb_speed(move |speed| {
let Some(ui) = w_speed.upgrade() else { return };
let mut data = ui.global::<AuraPageData>().get_led_mode_data();
data.speed = speed;
ui.global::<AuraPageData>().set_led_mode_data(data);
ui.global::<AuraPageData>().invoke_apply_led_mode_data();
});
h.invoke_external_colour_change();
})
.ok();
Expand Down
4 changes: 4 additions & 0 deletions rog-control-center/src/ui/setup_gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ fn set_apu_mem(proxy: AsusArmouryProxy<'static>, handle: Weak<MainWindow>, value
let new_current = p.current_value().await.unwrap_or(value);
let choices = p.possible_values().await.unwrap_or_default();
let new_index = choices.iter().position(|v| *v == new_current).unwrap_or(0) as i32;
// Refresh the labels too: the firmware's option set can change after a
// write, so a stale model would leave the dropdown out of sync.
let labels: Vec<SharedString> = choices.iter().map(|v| apu_mem_val_to_label(*v)).collect();
w.upgrade_in_event_loop(move |h| {
h.global::<GPUPageData>().set_apu_mem_choices(labels.as_slice().into());
h.global::<GPUPageData>().set_apu_mem_index(new_index);
})
.unwrap_or_else(|e| error!("setup_gpu: failed to refresh apu_mem index: {e:?}"));
Expand Down
27 changes: 25 additions & 2 deletions rog-control-center/src/ui/setup_slash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,29 @@ pub fn setup_slash_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
set_ui_props_async!(handle, slash, SlashPageData, show_battery_warning);
set_ui_props_async!(handle, slash, SlashPageData, show_on_lid_closed);

if let Ok(mode) = slash.mode().await {
let idx = slash_mode_to_index(mode);
// Read the persisted mode, retrying briefly — a single read right
// after (re)start can race with asusd and fail, which would leave
// the UI stuck on the Static default. The mode-changed listener
// below only fires on *changes*, so it won't recover an initial
// read failure on its own.
let mut mode: Option<SlashMode> = None;
for attempt in 0..5u32 {
match slash.mode().await {
Ok(m) => {
mode = Some(m);
break;
}
Err(e) => {
log::warn!("slash mode read attempt {} failed: {e}", attempt + 1);
if attempt < 4 {
tokio::time::sleep(std::time::Duration::from_millis(300)).await;
}
}
}
}
Comment thread
NB-Group marked this conversation as resolved.
if let Some(m) = mode {
let idx = slash_mode_to_index(m);
log::info!("slash mode at startup: {:?} (index {})", m, idx);
let choices = slash_modes();
handle
.upgrade_in_event_loop(move |handle| {
Expand All @@ -64,6 +85,8 @@ pub fn setup_slash_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
global.set_mode(idx);
})
.ok();
} else {
log::error!("slash mode unreadable after retries; UI will show Static");
}

handle
Expand Down
14 changes: 14 additions & 0 deletions rog-control-center/src/ui/setup_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,18 @@ pub fn setup_system_page(
.set_dgpu_name(dgpu_model.into());
ui.global::<SystemPageData>().set_has_igpu(has_igpu);

// Real product name from DMI (e.g. "ROG Zephyrus G16 GU605MV")
let real_product_name = match std::fs::read_to_string("/sys/class/dmi/id/product_name") {
Ok(s) => s.trim().to_string(),
Err(e) => {
log::debug!("DMI product_name unreadable: {e}");
String::new()
}
};
if !real_product_name.is_empty() {
ui.global::<SystemPageData>().set_product_name(real_product_name.into());
}

if let Ok(sys_props) = platform
.supported_properties()
.map_err(|e| log::error!("Failed to get supported properties: {}", e))
Expand Down Expand Up @@ -147,6 +159,7 @@ pub fn setup_system_page(
let igpu_temp = rog_platform::gpu_pci::get_igpu_temp();
let (cpu_fan, gpu_fan, mid_fan) = rog_platform::platform::get_fan_rpms();
let cpu_freq = rog_platform::cpu::get_cpu_frequency_mhz();
let gpu_freq = rog_platform::gpu_pci::get_gpu_frequency_mhz().unwrap_or(-1.0);
let ram_usage = rog_platform::cpu::get_ram_usage_pct();
let gpu_usage = rog_platform::gpu_pci::get_gpu_usage_pct();
let igpu_usage = rog_platform::gpu_pci::get_igpu_usage_pct();
Expand Down Expand Up @@ -183,6 +196,7 @@ pub fn setup_system_page(
data.set_igpu_usage_val(igpu_usage);
data.set_ram_usage_val(ram_usage);
data.set_cpu_freq_mhz(cpu_freq);
data.set_gpu_freq_mhz(gpu_freq);
data.set_cpu_fan_rpm(cpu_fan);
data.set_gpu_fan_rpm(gpu_fan);
data.set_mid_fan_rpm(mid_fan);
Expand Down
Binary file not shown.
Loading