Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 484e990c68 | |||
| 77d6994b8f | |||
| fff18bfb9d |
@@ -19,7 +19,7 @@ Teil des Moonarch-Ökosystems.
|
|||||||
## Projektstruktur
|
## Projektstruktur
|
||||||
|
|
||||||
- `src/` — Rust-Quellcode (main.rs, lockscreen.rs, auth.rs, fingerprint.rs, config.rs, i18n.rs, users.rs, power.rs)
|
- `src/` — Rust-Quellcode (main.rs, lockscreen.rs, auth.rs, fingerprint.rs, config.rs, i18n.rs, users.rs, power.rs)
|
||||||
- `resources/` — GResource-Assets (style.css, wallpaper.jpg, default-avatar.svg)
|
- `resources/` — GResource-Assets (style.css, default-avatar.svg)
|
||||||
- `config/` — PAM-Konfiguration und Beispiel-Config
|
- `config/` — PAM-Konfiguration und Beispiel-Config
|
||||||
|
|
||||||
## Kommandos
|
## Kommandos
|
||||||
|
|||||||
Generated
+1
-1
@@ -575,7 +575,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moonlock"
|
name = "moonlock"
|
||||||
version = "0.6.0"
|
version = "0.6.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gdk-pixbuf",
|
"gdk-pixbuf",
|
||||||
"gdk4",
|
"gdk4",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "moonlock"
|
name = "moonlock"
|
||||||
version = "0.6.1"
|
version = "0.6.4"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "A secure Wayland lockscreen with GTK4, PAM and fingerprint support"
|
description = "A secure Wayland lockscreen with GTK4, PAM and fingerprint support"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
Architectural and design decisions for Moonlock, in reverse chronological order.
|
Architectural and design decisions for Moonlock, in reverse chronological order.
|
||||||
|
|
||||||
|
## 2026-03-28 – Remove embedded wallpaper from binary
|
||||||
|
|
||||||
|
- **Who**: Nyx, Dom
|
||||||
|
- **Why**: Wallpaper is installed by moonarch to /usr/share/moonarch/wallpaper.jpg. Embedding a 374K JPEG in the binary is redundant. GTK background color (Catppuccin Mocha base) is a clean fallback.
|
||||||
|
- **Tradeoffs**: Without moonarch installed AND without config, lockscreen shows plain dark background instead of wallpaper. Acceptable — that's the expected minimal state.
|
||||||
|
- **How**: Remove wallpaper.jpg from GResources, return None from resolve_background_path when no file found, skip background picture creation when no texture available.
|
||||||
|
|
||||||
## 2026-03-28 – Audit-driven security and lifecycle fixes (v0.6.0)
|
## 2026-03-28 – Audit-driven security and lifecycle fixes (v0.6.0)
|
||||||
|
|
||||||
- **Who**: Nyx, Dom
|
- **Who**: Nyx, Dom
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/dev/moonarch/moonlock">
|
<gresource prefix="/dev/moonarch/moonlock">
|
||||||
<file>style.css</file>
|
<file>style.css</file>
|
||||||
<file>wallpaper.jpg</file>
|
|
||||||
<file>default-avatar.svg</file>
|
<file>default-avatar.svg</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
</gresources>
|
</gresources>
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ window.lockscreen.visible {
|
|||||||
|
|
||||||
/* Round avatar image */
|
/* Round avatar image */
|
||||||
.avatar {
|
.avatar {
|
||||||
border-radius: 50%;
|
border-radius: 9999px;
|
||||||
min-width: 128px;
|
min-width: 128px;
|
||||||
min-height: 128px;
|
min-height: 128px;
|
||||||
background-color: @theme_selected_bg_color;
|
background-color: @theme_selected_bg_color;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 366 KiB |
+10
-11
@@ -6,7 +6,6 @@ use std::fs;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
const MOONARCH_WALLPAPER: &str = "/usr/share/moonarch/wallpaper.jpg";
|
const MOONARCH_WALLPAPER: &str = "/usr/share/moonarch/wallpaper.jpg";
|
||||||
const GRESOURCE_PREFIX: &str = "/dev/moonarch/moonlock";
|
|
||||||
|
|
||||||
fn default_config_paths() -> Vec<PathBuf> {
|
fn default_config_paths() -> Vec<PathBuf> {
|
||||||
let mut paths = vec![PathBuf::from("/etc/moonlock/moonlock.toml")];
|
let mut paths = vec![PathBuf::from("/etc/moonlock/moonlock.toml")];
|
||||||
@@ -64,17 +63,17 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
|
|||||||
merged
|
merged
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_background_path(config: &Config) -> PathBuf {
|
pub fn resolve_background_path(config: &Config) -> Option<PathBuf> {
|
||||||
resolve_background_path_with(config, Path::new(MOONARCH_WALLPAPER))
|
resolve_background_path_with(config, Path::new(MOONARCH_WALLPAPER))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path) -> PathBuf {
|
pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path) -> Option<PathBuf> {
|
||||||
if let Some(ref bg) = config.background_path {
|
if let Some(ref bg) = config.background_path {
|
||||||
let path = PathBuf::from(bg);
|
let path = PathBuf::from(bg);
|
||||||
if path.is_file() && !path.is_symlink() { return path; }
|
if path.is_file() && !path.is_symlink() { return Some(path); }
|
||||||
}
|
}
|
||||||
if moonarch_wallpaper.is_file() { return moonarch_wallpaper.to_path_buf(); }
|
if moonarch_wallpaper.is_file() { return Some(moonarch_wallpaper.to_path_buf()); }
|
||||||
PathBuf::from(format!("{GRESOURCE_PREFIX}/wallpaper.jpg"))
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -109,7 +108,7 @@ mod tests {
|
|||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
let wp = dir.path().join("bg.jpg"); fs::write(&wp, "fake").unwrap();
|
let wp = dir.path().join("bg.jpg"); fs::write(&wp, "fake").unwrap();
|
||||||
let c = Config { background_path: Some(wp.to_str().unwrap().to_string()), ..Config::default() };
|
let c = Config { background_path: Some(wp.to_str().unwrap().to_string()), ..Config::default() };
|
||||||
assert_eq!(resolve_background_path_with(&c, Path::new("/nonexistent")), wp);
|
assert_eq!(resolve_background_path_with(&c, Path::new("/nonexistent")), Some(wp));
|
||||||
}
|
}
|
||||||
#[test] fn empty_user_config_preserves_system_fingerprint() {
|
#[test] fn empty_user_config_preserves_system_fingerprint() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
@@ -120,10 +119,10 @@ mod tests {
|
|||||||
let c = load_config(Some(&[sys_conf, usr_conf]));
|
let c = load_config(Some(&[sys_conf, usr_conf]));
|
||||||
assert!(!c.fingerprint_enabled);
|
assert!(!c.fingerprint_enabled);
|
||||||
}
|
}
|
||||||
#[test] fn resolve_gresource_fallback() {
|
#[test] fn resolve_no_wallpaper_returns_none() {
|
||||||
let c = Config::default();
|
let c = Config::default();
|
||||||
let r = resolve_background_path_with(&c, Path::new("/nonexistent"));
|
let r = resolve_background_path_with(&c, Path::new("/nonexistent"));
|
||||||
assert!(r.to_str().unwrap().contains("moonlock"));
|
assert!(r.is_none());
|
||||||
}
|
}
|
||||||
#[test] fn toml_parse_error_returns_default() {
|
#[test] fn toml_parse_error_returns_default() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
@@ -141,8 +140,8 @@ mod tests {
|
|||||||
fs::write(&real, "fake").unwrap();
|
fs::write(&real, "fake").unwrap();
|
||||||
std::os::unix::fs::symlink(&real, &link).unwrap();
|
std::os::unix::fs::symlink(&real, &link).unwrap();
|
||||||
let c = Config { background_path: Some(link.to_str().unwrap().to_string()), ..Config::default() };
|
let c = Config { background_path: Some(link.to_str().unwrap().to_string()), ..Config::default() };
|
||||||
// Symlink should be rejected — falls through to moonarch wallpaper or gresource
|
// Symlink should be rejected — falls through to None
|
||||||
let r = resolve_background_path_with(&c, Path::new("/nonexistent"));
|
let r = resolve_background_path_with(&c, Path::new("/nonexistent"));
|
||||||
assert_ne!(r, link);
|
assert!(r.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-22
@@ -44,7 +44,7 @@ struct LockscreenState {
|
|||||||
/// The `blur_cache` and `avatar_cache` are shared across monitors for multi-monitor
|
/// The `blur_cache` and `avatar_cache` are shared across monitors for multi-monitor
|
||||||
/// setups, avoiding redundant GPU renders and SVG rasterizations.
|
/// setups, avoiding redundant GPU renders and SVG rasterizations.
|
||||||
pub fn create_lockscreen_window(
|
pub fn create_lockscreen_window(
|
||||||
bg_texture: &gdk::Texture,
|
bg_texture: Option<&gdk::Texture>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
app: >k::Application,
|
app: >k::Application,
|
||||||
unlock_callback: Rc<dyn Fn()>,
|
unlock_callback: Rc<dyn Fn()>,
|
||||||
@@ -86,9 +86,11 @@ pub fn create_lockscreen_window(
|
|||||||
let overlay = gtk::Overlay::new();
|
let overlay = gtk::Overlay::new();
|
||||||
window.set_child(Some(&overlay));
|
window.set_child(Some(&overlay));
|
||||||
|
|
||||||
// Background wallpaper
|
// Background wallpaper (if available — otherwise GTK background color shows through)
|
||||||
let background = create_background_picture(bg_texture, config.background_blur, blur_cache);
|
if let Some(texture) = bg_texture {
|
||||||
|
let background = create_background_picture(texture, config.background_blur, blur_cache);
|
||||||
overlay.set_child(Some(&background));
|
overlay.set_child(Some(&background));
|
||||||
|
}
|
||||||
|
|
||||||
// Centered vertical box
|
// Centered vertical box
|
||||||
let main_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
let main_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||||
@@ -430,18 +432,16 @@ pub fn start_fingerprint(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Load the wallpaper as a texture once, for sharing across all windows.
|
/// Load the wallpaper as a texture once, for sharing across all windows.
|
||||||
|
/// Returns None if no wallpaper path is provided or the file cannot be loaded.
|
||||||
/// Blur is applied at render time via GPU (GskBlurNode), not here.
|
/// Blur is applied at render time via GPU (GskBlurNode), not here.
|
||||||
pub fn load_background_texture(bg_path: &Path) -> gdk::Texture {
|
pub fn load_background_texture(bg_path: &Path) -> Option<gdk::Texture> {
|
||||||
let fallback = "/dev/moonarch/moonlock/wallpaper.jpg";
|
|
||||||
|
|
||||||
if bg_path.starts_with("/dev/moonarch/moonlock") {
|
|
||||||
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);
|
let file = gio::File::for_path(bg_path);
|
||||||
gdk::Texture::from_file(&file).unwrap_or_else(|_| {
|
match gdk::Texture::from_file(&file) {
|
||||||
gdk::Texture::from_resource(fallback)
|
Ok(texture) => Some(texture),
|
||||||
})
|
Err(e) => {
|
||||||
|
log::warn!("Failed to load wallpaper {}: {e}", bg_path.display());
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,6 +483,10 @@ fn create_background_picture(
|
|||||||
|
|
||||||
/// Render a blurred texture using the widget's GPU renderer.
|
/// Render a blurred texture using the widget's GPU renderer.
|
||||||
/// Returns None if the renderer is not available.
|
/// Returns None if the renderer is not available.
|
||||||
|
///
|
||||||
|
/// To avoid edge darkening (blur samples transparent pixels outside bounds),
|
||||||
|
/// the texture is rendered with padding equal to 3x the blur sigma. The blur
|
||||||
|
/// is applied to the padded area, then cropped back to the original size.
|
||||||
fn render_blurred_texture(
|
fn render_blurred_texture(
|
||||||
widget: &impl IsA<gtk::Widget>,
|
widget: &impl IsA<gtk::Widget>,
|
||||||
texture: &gdk::Texture,
|
texture: &gdk::Texture,
|
||||||
@@ -490,18 +494,24 @@ fn render_blurred_texture(
|
|||||||
) -> Option<gdk::Texture> {
|
) -> Option<gdk::Texture> {
|
||||||
let native = widget.native()?;
|
let native = widget.native()?;
|
||||||
let renderer = native.renderer()?;
|
let renderer = native.renderer()?;
|
||||||
|
|
||||||
|
let w = texture.width() as f32;
|
||||||
|
let h = texture.height() as f32;
|
||||||
|
// Padding must cover the blur kernel radius (typically ~3x sigma)
|
||||||
|
let pad = (sigma * 3.0).ceil();
|
||||||
|
|
||||||
let snapshot = gtk::Snapshot::new();
|
let snapshot = gtk::Snapshot::new();
|
||||||
let bounds = graphene::Rect::new(
|
// Clip output to original texture size
|
||||||
0.0,
|
snapshot.push_clip(&graphene::Rect::new(pad, pad, w, h));
|
||||||
0.0,
|
|
||||||
texture.width() as f32,
|
|
||||||
texture.height() as f32,
|
|
||||||
);
|
|
||||||
snapshot.push_blur(sigma as f64);
|
snapshot.push_blur(sigma as f64);
|
||||||
snapshot.append_texture(texture, &bounds);
|
// Render texture with padding on all sides (edges repeat via oversized bounds)
|
||||||
snapshot.pop();
|
snapshot.append_texture(texture, &graphene::Rect::new(0.0, 0.0, w + 2.0 * pad, h + 2.0 * pad));
|
||||||
|
snapshot.pop(); // blur
|
||||||
|
snapshot.pop(); // clip
|
||||||
|
|
||||||
let node = snapshot.to_node()?;
|
let node = snapshot.to_node()?;
|
||||||
Some(renderer.render_texture(&node, None))
|
let viewport = graphene::Rect::new(pad, pad, w, h);
|
||||||
|
Some(renderer.render_texture(&node, Some(&viewport)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load an image file and set it as the avatar. Stores the texture in the cache.
|
/// Load an image file and set it as the avatar. Stores the texture in the cache.
|
||||||
|
|||||||
+7
-7
@@ -24,7 +24,7 @@ fn load_css(display: &gdk::Display) {
|
|||||||
gtk::style_context_add_provider_for_display(
|
gtk::style_context_add_provider_for_display(
|
||||||
display,
|
display,
|
||||||
&css_provider,
|
&css_provider,
|
||||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
gtk::STYLE_PROVIDER_PRIORITY_USER,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,16 +40,16 @@ fn activate(app: >k::Application) {
|
|||||||
load_css(&display);
|
load_css(&display);
|
||||||
|
|
||||||
let config = config::load_config(None);
|
let config = config::load_config(None);
|
||||||
let bg_path = config::resolve_background_path(&config);
|
let bg_texture = config::resolve_background_path(&config)
|
||||||
let bg_texture = lockscreen::load_background_texture(&bg_path);
|
.and_then(|path| lockscreen::load_background_texture(&path));
|
||||||
|
|
||||||
if gtk4_session_lock::is_supported() {
|
if gtk4_session_lock::is_supported() {
|
||||||
activate_with_session_lock(app, &display, &bg_texture, &config);
|
activate_with_session_lock(app, &display, bg_texture.as_ref(), &config);
|
||||||
} else {
|
} else {
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
log::warn!("ext-session-lock-v1 not supported — running in development mode");
|
log::warn!("ext-session-lock-v1 not supported — running in development mode");
|
||||||
activate_without_lock(app, &bg_texture, &config);
|
activate_without_lock(app, bg_texture.as_ref(), &config);
|
||||||
}
|
}
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
{
|
{
|
||||||
@@ -62,7 +62,7 @@ fn activate(app: >k::Application) {
|
|||||||
fn activate_with_session_lock(
|
fn activate_with_session_lock(
|
||||||
app: >k::Application,
|
app: >k::Application,
|
||||||
display: &gdk::Display,
|
display: &gdk::Display,
|
||||||
bg_texture: &gdk::Texture,
|
bg_texture: Option<&gdk::Texture>,
|
||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
) {
|
) {
|
||||||
let lock = gtk4_session_lock::Instance::new();
|
let lock = gtk4_session_lock::Instance::new();
|
||||||
@@ -158,7 +158,7 @@ fn init_fingerprint_async(all_handles: Vec<lockscreen::LockscreenHandles>) {
|
|||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
fn activate_without_lock(
|
fn activate_without_lock(
|
||||||
app: >k::Application,
|
app: >k::Application,
|
||||||
bg_texture: &gdk::Texture,
|
bg_texture: Option<&gdk::Texture>,
|
||||||
config: &config::Config,
|
config: &config::Config,
|
||||||
) {
|
) {
|
||||||
let app_clone = app.clone();
|
let app_clone = app.clone();
|
||||||
|
|||||||
Reference in New Issue
Block a user