diff --git a/Cargo.lock b/Cargo.lock index 8ffedc8..0ec8ae3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3287,6 +3287,33 @@ dependencies = [ "serde_core", ] +[[package]] +name = "session-lock" +version = "0.2.0" +dependencies = [ + "env_logger", + "layer-shika", + "log", +] + +[[package]] +name = "session-lock-selectors" +version = "0.2.0" +dependencies = [ + "env_logger", + "layer-shika", + "log", +] + +[[package]] +name = "session-lock-standalone" +version = "0.2.0" +dependencies = [ + "env_logger", + "layer-shika", + "log", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 81c8e0a..fddcc25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,9 @@ members = [ "examples/declarative-config", "examples/event-loop", "examples/runtime-surface-config", + "examples/session-lock", + "examples/session-lock-selectors", + "examples/session-lock-standalone", "examples/simple-popup", ] diff --git a/examples/README.md b/examples/README.md index ad99f70..21fb275 100644 --- a/examples/README.md +++ b/examples/README.md @@ -13,6 +13,8 @@ cargo run -p multi-surface cargo run -p declarative-config cargo run -p runtime-surface-config cargo run -p simple-popup +cargo run -p session-lock +cargo run -p session-lock-standalone # Or from the example directory cd examples/simple-bar @@ -29,6 +31,8 @@ cargo run 4. **event-loop** - Explore event loop integration with timers and channels 5. **runtime-surface-config** - Surface configuration manipulation at runtime 6. **simple-popup** - Showing popups and content sizing +7. **session-lock-standalone** - Minimal lock-only application without layer surfaces +8. **session-lock** - Lock screen with layer shell surfaces (status bar + lock) ## Common Patterns diff --git a/examples/session-lock-selectors/Cargo.toml b/examples/session-lock-selectors/Cargo.toml new file mode 100644 index 0000000..b305ca9 --- /dev/null +++ b/examples/session-lock-selectors/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "session-lock-selectors" +version.workspace = true +edition.workspace = true +license.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +layer-shika = { path = "../.." } +env_logger = "0.11.7" +log.workspace = true diff --git a/examples/session-lock-selectors/README.md b/examples/session-lock-selectors/README.md new file mode 100644 index 0000000..7ebfae9 --- /dev/null +++ b/examples/session-lock-selectors/README.md @@ -0,0 +1,50 @@ +# Session Lock Selectors Example + +This example demonstrates using **selectors** to configure session lock surfaces with different properties per output. It shows how to set different themes or configurations for lock screens on different monitors. + +## Key Features + +- Creates both a layer shell surface (status bar) and session lock surfaces +- Uses `select_lock()` to apply configurations to specific lock surfaces +- Demonstrates per-output theming (dark theme on primary output) +- Shows how to handle lock/unlock callbacks with selectors + +## Selector Usage + +```rust +// Apply to all lock surfaces +shell.select_lock(Surface::all()) + .on_callback_with_args("unlock_requested", handler); + +// Apply only to primary output's lock surface +shell.select_lock(Output::Primary) + .set_property("theme", &Value::from("dark"))?; + +// Apply to regular layer surface +shell.select(Surface::named("Main")) + .on_callback("lock_requested", handler); +``` + +## Run + +```bash +cargo run -p session-lock-selectors +``` + +## Usage Flow + +1. Application starts with a status bar showing a "Lock" button +2. Click the "Lock" button to activate the session lock +3. Lock screens appear on all outputs +4. Primary output's lock screen uses dark theme +5. Enter any password and click "Unlock" to deactivate +6. Application returns to normal mode with status bar + +## When to Use This Pattern + +Use session lock selectors when you need to: + +- Configure lock screens differently per output +- Apply different themes or properties to specific monitors +- Handle callbacks consistently across all lock surfaces +- Combine layer shell surfaces with session locks diff --git a/examples/session-lock-selectors/src/main.rs b/examples/session-lock-selectors/src/main.rs new file mode 100644 index 0000000..4a5f0f7 --- /dev/null +++ b/examples/session-lock-selectors/src/main.rs @@ -0,0 +1,51 @@ +use layer_shika::prelude::*; +use layer_shika::slint_interpreter::Value; +use std::path::PathBuf; +use std::rc::Rc; + +fn main() -> Result<()> { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .init(); + + let ui_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("ui/lock.slint"); + + let mut shell = Shell::from_file(ui_path) + .surface("Main") + .height(42) + .exclusive_zone(42) + .namespace("session-lock-selectors-example") + .build()?; + + let lock = Rc::new(shell.create_session_lock("LockScreen")?); + + let lock_clone = Rc::clone(&lock); + shell.select_lock(Surface::all()).on_callback_with_args( + "unlock_requested", + move |args, _ctx| { + if let Some(password) = args.first() { + log::info!("Password entered: {:?}", password); + } + lock_clone.deactivate().ok(); + }, + ); + + shell + .select_lock(Surface::all()) + .on_callback("cancel_requested", |_ctx| {}); + + shell + .select_lock(Output::Primary) + .set_property("theme", &Value::from(slint::SharedString::from("dark")))?; + + let lock_clone = Rc::clone(&lock); + shell + .select(Surface::named("Main")) + .on_callback("lock_requested", move |_ctx| { + lock_clone.activate().ok(); + }); + + shell.run()?; + + Ok(()) +} diff --git a/examples/session-lock-selectors/ui/lock.slint b/examples/session-lock-selectors/ui/lock.slint new file mode 100644 index 0000000..891e044 --- /dev/null +++ b/examples/session-lock-selectors/ui/lock.slint @@ -0,0 +1,118 @@ +import { + Button, + LineEdit, + HorizontalBox, + VerticalBox, +} from "std-widgets.slint"; + +export component Main inherits Window { + background: transparent; + callback lock_requested(); + + default-font-size: 16px; + HorizontalLayout { + alignment: center; + Button { + text: "Lock Screen"; + clicked => root.lock_requested(); + } + } +} + +export component LockScreen inherits Window { + out property password; + in property theme: "light"; + property is_primary; + + callback unlock_requested(string); + callback cancel_requested(); + + is_primary: theme == "dark" ? true : false; + background: theme == "dark" ? #1a1a1a : #f0f0f0; + + Rectangle { + width: 100%; + height: 100%; + + HorizontalLayout { + alignment: center; + + VerticalLayout { + alignment: center; + spacing: 20px; + + Text { + text: "Session Locked"; + font-size: 32px; + color: theme == "dark" ? #ffffff : #000000; + horizontal-alignment: center; + } + + HorizontalLayout { + alignment: center; + + if is_primary: Rectangle { + width: self.preferred-width; + height: 30px; + background: #4a90e2; + border-radius: 15px; + + HorizontalLayout { + padding: 8px; + Text { + text: "Primary Monitor"; + color: #ffffff; + font-size: 12px; + } + } + } + } + + Text { + text: "Enter password to unlock"; + color: theme == "dark" ? #cccccc : #333333; + horizontal-alignment: center; + } + + VerticalLayout { + width: 300px; + spacing: 12px; + + LineEdit { + text: root.password; + placeholder-text: "Password"; + edited => { + root.password = self.text; + } + } + + HorizontalLayout { + spacing: 12px; + + Button { + text: "Unlock"; + primary: true; + clicked => { + root.unlock_requested(root.password); + } + } + + Button { + text: "Cancel"; + clicked => { + root.cancel_requested(); + } + } + } + } + + Text { + text: "Theme: " + theme; + color: theme == "dark" ? #888888 : #666666; + font-size: 10px; + horizontal-alignment: center; + } + } + } + } +} diff --git a/examples/session-lock-standalone/Cargo.toml b/examples/session-lock-standalone/Cargo.toml new file mode 100644 index 0000000..57c7fa3 --- /dev/null +++ b/examples/session-lock-standalone/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "session-lock-standalone" +version.workspace = true +edition.workspace = true +license.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +layer-shika = { path = "../.." } +env_logger = "0.11.7" +log.workspace = true diff --git a/examples/session-lock-standalone/README.md b/examples/session-lock-standalone/README.md new file mode 100644 index 0000000..5f9f4d2 --- /dev/null +++ b/examples/session-lock-standalone/README.md @@ -0,0 +1,53 @@ +# Session Lock Standalone Example + +This example demonstrates creating a **standalone session lock application** without any layer shell surfaces. This is useful for dedicated lock screen applications that don't need persistent UI elements. + +## Key Differences from Regular Session Lock + +### Regular Session Lock (`session-lock`) +- Creates a layer shell surface (status bar, panel, etc.) +- Requires `wlr-layer-shell` protocol +- Lock is activated via button click or external trigger +- Application has persistent UI even when unlocked + +### Standalone Session Lock (this example) +- **No layer shell surfaces** - minimal mode +- Does NOT require `wlr-layer-shell` protocol +- Lock activates immediately on startup +- Application exits when lock is deactivated +- Lighter weight and simpler architecture + +## Usage + +```bash +cargo run --package session-lock-standalone +``` + +The lock screen will appear immediately on all outputs. Enter any password and click "Unlock" to deactivate and exit. + +## Code Highlights + +```rust +// Build shell WITHOUT calling .surface() - this creates minimal mode +let mut shell = Shell::from_file(ui_path).build()?; + +// Create and activate the session lock immediately +let lock = Rc::new(shell.create_session_lock("LockScreen")?); +lock.activate()?; + +// Shell runs until lock is deactivated +shell.run()?; +``` + +## When to Use This Pattern + +Use standalone session locks for: +- Dedicated lock screen applications +- Screen locker daemons that only show UI when locking +- Simpler lock-only tools without status bars +- Testing and development of lock screens in isolation + +Use regular session locks with layer surfaces when: +- You need persistent UI (status bar, panel, dock) +- Lock is one feature among others in your application +- You want to trigger lock activation from UI elements diff --git a/examples/session-lock-standalone/src/main.rs b/examples/session-lock-standalone/src/main.rs new file mode 100644 index 0000000..d26c53f --- /dev/null +++ b/examples/session-lock-standalone/src/main.rs @@ -0,0 +1,44 @@ +use layer_shika::prelude::*; +use layer_shika::slint_interpreter::Value; +use std::path::PathBuf; +use std::rc::Rc; + +fn main() -> Result<()> { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .init(); + + let ui_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("ui/lock.slint"); + + let mut shell = Shell::from_file(ui_path).build()?; + + let lock = Rc::new(shell.create_session_lock("LockScreen")?); + + let lock_clone = Rc::clone(&lock); + shell.select_lock(Surface::all()).on_callback_with_args( + "unlock_requested", + move |args, _ctx| { + if let Some(password) = args.first() { + log::info!("Password entered: {:?}", password); + } + lock_clone.deactivate().ok(); + }, + ); + + shell + .select_lock(Surface::all()) + .on_callback("cancel_requested", |_ctx| { + log::info!("Cancel requested button pressed"); + }); + + shell + .select_lock(Surface::all()) + .set_property("theme", &Value::from(slint::SharedString::from("dark"))) + .ok(); + + lock.activate()?; + + shell.run()?; + + Ok(()) +} diff --git a/examples/session-lock-standalone/ui/lock.slint b/examples/session-lock-standalone/ui/lock.slint new file mode 100644 index 0000000..0bcbb3e --- /dev/null +++ b/examples/session-lock-standalone/ui/lock.slint @@ -0,0 +1,79 @@ +import { Button, LineEdit, HorizontalBox } from "std-widgets.slint"; + +// Standalone session lock - no layer surface needed! +// This component is only displayed when the lock is activated. +export component LockScreen inherits Window { + in-out property password; + in-out property theme: "dark"; + callback unlock_requested(string); + callback cancel_requested(); + + background: theme == "dark" ? #1a1a1a : #f0f0f0; + + Rectangle { + width: 100%; + height: 100%; + + HorizontalLayout { + alignment: center; + + VerticalLayout { + alignment: center; + spacing: 20px; + + Text { + text: "🔒 Session Locked"; + font-size: 32px; + color: theme == "dark" ? #ffffff : #000000; + } + + Text { + text: "This is a standalone lock screen without any layer surfaces"; + font-size: 14px; + color: theme == "dark" ? #cccccc : #666666; + } + + Text { + text: "Enter password to unlock"; + font-size: 16px; + color: theme == "dark" ? #ffffff : #000000; + } + + VerticalLayout { + width: 300px; + spacing: 12px; + + LineEdit { + placeholder-text: "Password"; + text: root.password; + edited => { + root.password = self.text; + } + accepted => { + root.unlock_requested(root.password); + } + } + + HorizontalLayout { + spacing: 12px; + + Button { + text: "Unlock"; + primary: true; + clicked => { + root.unlock_requested(root.password); + } + } + + Button { + text: "Cancel"; + clicked => { + root.cancel_requested(); + } + } + } + } + } + } + } +} diff --git a/examples/session-lock/Cargo.toml b/examples/session-lock/Cargo.toml new file mode 100644 index 0000000..c9137a1 --- /dev/null +++ b/examples/session-lock/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "session-lock" +version.workspace = true +edition.workspace = true +license.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +layer-shika = { path = "../.." } +env_logger = "0.11.7" +log.workspace = true diff --git a/examples/session-lock/README.md b/examples/session-lock/README.md new file mode 100644 index 0000000..4fab445 --- /dev/null +++ b/examples/session-lock/README.md @@ -0,0 +1,14 @@ +# session-lock example + +Demonstrates the ext-session-lock-v1 integration with a simple lock screen, activated by a bar button click. + +## Run + +```bash +cargo run -p session-lock +``` + +## Notes + +- Requires a compositor that supports ext-session-lock-v1. +- The example unlocks when the password field is non-empty. diff --git a/examples/session-lock/src/main.rs b/examples/session-lock/src/main.rs new file mode 100644 index 0000000..c6e77e8 --- /dev/null +++ b/examples/session-lock/src/main.rs @@ -0,0 +1,41 @@ +use layer_shika::prelude::*; +use std::path::PathBuf; +use std::rc::Rc; + +fn main() -> Result<()> { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .init(); + + let ui_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("ui/lock.slint"); + + let mut shell = Shell::from_file(ui_path) + .surface("Main") + .height(42) + .exclusive_zone(42) + .namespace("session-lock-example") + .build()?; + + let lock = Rc::new(shell.create_session_lock("LockScreen")?); + + let lock_clone = Rc::clone(&lock); + shell + .select_lock(Surface::named("LockScreen")) + .on_callback_with_args("unlock_requested", move |args, _ctx| { + if let Some(password) = args.first() { + log::info!("Password entered: {:?}", password); + } + lock_clone.deactivate().ok(); + }); + + let lock_clone = Rc::clone(&lock); + shell + .select(Surface::named("Main")) + .on_callback("lock_requested", move |_ctx| { + lock_clone.activate().ok(); + }); + + shell.run()?; + + Ok(()) +} diff --git a/examples/session-lock/ui/lock.slint b/examples/session-lock/ui/lock.slint new file mode 100644 index 0000000..7548676 --- /dev/null +++ b/examples/session-lock/ui/lock.slint @@ -0,0 +1,60 @@ +import { Button, LineEdit, HorizontalBox } from "std-widgets.slint"; + +export component Main inherits Window { + background: transparent; + callback lock_requested(); + + default-font-size: 16px; + HorizontalLayout { + alignment: center; + Button { + text: "Lock"; + clicked => root.lock_requested(); + } + } +} + +export component LockScreen inherits Window { + in-out property password; + callback unlock_requested(string); + + Rectangle { + width: 100%; + height: 100%; + + HorizontalLayout { + alignment: center; + + VerticalLayout { + alignment: center; + + Text { + text: "Session Locked"; + font-size: 28px; + } + + Text { + text: "Enter a password to unlock"; + } + + VerticalLayout { + width: 255px; + + LineEdit { + text: root.password; + edited => { + root.password = self.text; + } + } + + Button { + text: "Unlock"; + clicked => { + root.unlock_requested(root.password); + } + } + } + } + } + } +} diff --git a/examples/simple-bar/ui/bar.slint b/examples/simple-bar/ui/bar.slint index f5e9f9a..d49f46b 100644 --- a/examples/simple-bar/ui/bar.slint +++ b/examples/simple-bar/ui/bar.slint @@ -1,7 +1,7 @@ import { VerticalBox, HorizontalBox } from "std-widgets.slint"; export component Bar inherits Window { - default-font-size: 16px; + //default-font-size: 16px; HorizontalBox { // Left section