diff --git a/resources/resources.gresource.xml b/resources/resources.gresource.xml
index c379862..35a93a7 100644
--- a/resources/resources.gresource.xml
+++ b/resources/resources.gresource.xml
@@ -2,7 +2,7 @@
style.css
- wallpaper.jpg
+ wallpaper.jpg
default-avatar.svg
diff --git a/resources/style.css b/resources/style.css
index fd9283b..574e4d7 100644
--- a/resources/style.css
+++ b/resources/style.css
@@ -31,7 +31,7 @@ window.wallpaper {
margin-bottom: 40px;
}
-/* Action button — square card */
+/* Action button — circular card */
.action-button {
min-width: 120px;
min-height: 120px;
diff --git a/src/config.rs b/src/config.rs
index 87b0732..d3138a2 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -6,7 +6,6 @@ use std::fs;
use std::path::{Path, PathBuf};
const MOONARCH_WALLPAPER: &str = "/usr/share/moonarch/wallpaper.jpg";
-const GRESOURCE_PREFIX: &str = "/dev/moonarch/moonset";
/// Default config search paths: system-wide, then user-specific.
fn default_config_paths() -> Vec {
@@ -65,7 +64,8 @@ pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path)
}
// GResource fallback path (loaded from compiled resources at runtime)
- PathBuf::from(format!("{GRESOURCE_PREFIX}/wallpaper.jpg"))
+ let prefix = crate::GRESOURCE_PREFIX;
+ PathBuf::from(format!("{prefix}/wallpaper.jpg"))
}
#[cfg(test)]
diff --git a/src/main.rs b/src/main.rs
index e12217a..9a972a8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,9 +12,11 @@ use gtk4::prelude::*;
use gtk4::{self as gtk, gio};
use gtk4_layer_shell::LayerShell;
+pub(crate) const GRESOURCE_PREFIX: &str = "/dev/moonarch/moonset";
+
fn load_css(display: &gdk::Display) {
let css_provider = gtk::CssProvider::new();
- css_provider.load_from_resource("/dev/moonarch/moonset/style.css");
+ css_provider.load_from_resource(&format!("{GRESOURCE_PREFIX}/style.css"));
gtk::style_context_add_provider_for_display(
display,
&css_provider,
diff --git a/src/panel.rs b/src/panel.rs
index 3d262fa..0510689 100644
--- a/src/panel.rs
+++ b/src/panel.rs
@@ -81,12 +81,15 @@ pub fn action_definitions() -> Vec {
/// Load the wallpaper as a texture once, for sharing across all windows.
pub fn load_background_texture(bg_path: &Path) -> gdk::Texture {
- if bg_path.starts_with("/dev/moonarch/moonset") {
- gdk::Texture::from_resource(bg_path.to_str().unwrap_or(""))
+ let fallback = format!("{}/wallpaper.jpg", crate::GRESOURCE_PREFIX);
+
+ if bg_path.starts_with(crate::GRESOURCE_PREFIX) {
+ let resource_path = bg_path.to_str().unwrap_or(&fallback);
+ gdk::Texture::from_resource(resource_path)
} else {
let file = gio::File::for_path(bg_path);
gdk::Texture::from_file(&file).unwrap_or_else(|_| {
- gdk::Texture::from_resource("/dev/moonarch/moonset/wallpaper.jpg")
+ gdk::Texture::from_resource(&fallback)
})
}
}
@@ -127,7 +130,7 @@ pub fn create_panel_window(texture: &gdk::Texture, app: >k::Application) -> gt
username: "user".to_string(),
display_name: "User".to_string(),
home: dirs::home_dir().unwrap_or_default(),
- uid: 0,
+ uid: u32::MAX,
});
// State for confirm box
@@ -233,11 +236,9 @@ pub fn create_panel_window(texture: &gdk::Texture, app: >k::Application) -> gt
let bb = button_box_clone.clone();
glib::idle_add_local_once(move || {
w.add_css_class("visible");
- glib::idle_add_local_once(move || {
- if let Some(first) = bb.first_child() {
- first.grab_focus();
- }
- });
+ if let Some(first) = bb.first_child() {
+ first.grab_focus();
+ }
});
});
diff --git a/src/power.rs b/src/power.rs
index f147959..a50bff6 100644
--- a/src/power.rs
+++ b/src/power.rs
@@ -2,16 +2,15 @@
// ABOUTME: Wrappers around system commands for the session power menu.
use std::fmt;
-use std::process::Command;
-use std::time::Duration;
+use std::io::Read;
+use std::process::{Command, Stdio};
+use std::time::{Duration, Instant};
-#[allow(dead_code)]
const POWER_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug)]
pub enum PowerError {
CommandFailed { action: &'static str, message: String },
- #[allow(dead_code)]
Timeout { action: &'static str },
}
@@ -32,55 +31,73 @@ impl std::error::Error for PowerError {}
/// Run a command with timeout and return a PowerError on failure.
fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), PowerError> {
- let child = Command::new(program)
+ let mut child = Command::new(program)
.args(args)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
.spawn()
.map_err(|e| PowerError::CommandFailed {
action,
message: e.to_string(),
})?;
- let output = child
- .wait_with_output()
- .map_err(|e| PowerError::CommandFailed {
- action,
- message: e.to_string(),
- })?;
-
- if !output.status.success() {
- let stderr = String::from_utf8_lossy(&output.stderr);
- return Err(PowerError::CommandFailed {
- action,
- message: format!("exit code {}: {}", output.status, stderr.trim()),
- });
+ let deadline = Instant::now() + POWER_TIMEOUT;
+ loop {
+ match child.try_wait() {
+ Ok(Some(status)) => {
+ if !status.success() {
+ let mut stderr_buf = String::new();
+ if let Some(mut stderr) = child.stderr.take() {
+ let _ = stderr.read_to_string(&mut stderr_buf);
+ }
+ return Err(PowerError::CommandFailed {
+ action,
+ message: format!("exit code {}: {}", status, stderr_buf.trim()),
+ });
+ }
+ return Ok(());
+ }
+ Ok(None) => {
+ if Instant::now() >= deadline {
+ let _ = child.kill();
+ let _ = child.wait();
+ return Err(PowerError::Timeout { action });
+ }
+ std::thread::sleep(Duration::from_millis(100));
+ }
+ Err(e) => {
+ return Err(PowerError::CommandFailed {
+ action,
+ message: e.to_string(),
+ });
+ }
+ }
}
-
- Ok(())
}
/// Lock the current session by launching moonlock.
pub fn lock() -> Result<(), PowerError> {
- run_command("lock", "moonlock", &[])
+ run_command("lock", "/usr/bin/moonlock", &[])
}
/// Quit the Niri compositor (logout).
pub fn logout() -> Result<(), PowerError> {
- run_command("logout", "niri", &["msg", "action", "quit"])
+ run_command("logout", "/usr/bin/niri", &["msg", "action", "quit"])
}
/// Hibernate the system via systemctl.
pub fn hibernate() -> Result<(), PowerError> {
- run_command("hibernate", "systemctl", &["hibernate"])
+ run_command("hibernate", "/usr/bin/systemctl", &["hibernate"])
}
/// Reboot the system via loginctl.
pub fn reboot() -> Result<(), PowerError> {
- run_command("reboot", "loginctl", &["reboot"])
+ run_command("reboot", "/usr/bin/loginctl", &["reboot"])
}
/// Shut down the system via loginctl.
pub fn shutdown() -> Result<(), PowerError> {
- run_command("shutdown", "loginctl", &["poweroff"])
+ run_command("shutdown", "/usr/bin/loginctl", &["poweroff"])
}
#[cfg(test)]
diff --git a/src/users.rs b/src/users.rs
index 7451907..f853ced 100644
--- a/src/users.rs
+++ b/src/users.rs
@@ -5,7 +5,6 @@ use nix::unistd::{getuid, User as NixUser};
use std::path::{Path, PathBuf};
const DEFAULT_ACCOUNTSSERVICE_DIR: &str = "/var/lib/AccountsService/icons";
-const GRESOURCE_PREFIX: &str = "/dev/moonarch/moonset";
/// Represents the current user for the power menu.
#[derive(Debug, Clone)]
@@ -74,7 +73,8 @@ pub fn get_avatar_path_with(
/// Return the GResource path to the default avatar SVG.
pub fn get_default_avatar_path() -> String {
- format!("{GRESOURCE_PREFIX}/default-avatar.svg")
+ let prefix = crate::GRESOURCE_PREFIX;
+ format!("{prefix}/default-avatar.svg")
}
#[cfg(test)]