Compare commits

...

13 Commits

Author SHA1 Message Date
jory 601c9677b7 Probably fix release.yml
Code Quality / quality-checks (push) Has been cancelled
Release / release-build (, arch, default) (push) Has been cancelled
Release / release-build (, debian, default) (push) Has been cancelled
Release / release-build (, fedora, default) (push) Has been cancelled
Release / release-build (, ubuntu, default) (push) Has been cancelled
Release / release-build (--no-default-features, ubuntu, no-networking) (push) Has been cancelled
Release / release (push) Has been cancelled
Security Scan / security-audit (push) Has been cancelled
2026-03-17 22:30:11 +01:00
jory 4d90e52c36 Probably fix release.yml
Code Quality / quality-checks (push) Has been cancelled
Security Scan / security-audit (push) Has been cancelled
2026-03-17 22:22:48 +01:00
jory 55c8fc0a4f refactor: Simplify deny.toml configuration
Release / release-build (, arch, default) (push) Has been cancelled
Release / release-build (, debian, default) (push) Has been cancelled
Release / release-build (, fedora, default) (push) Has been cancelled
Release / release-build (, ubuntu, default) (push) Has been cancelled
Release / release-build (--no-default-features, ubuntu, no-networking) (push) Has been cancelled
Release / release (push) Has been cancelled
2026-03-17 22:06:34 +01:00
jory aa59067bb3 fix: replace vulnerable users crate with whoami
Code Quality / quality-checks (push) Has been cancelled
Security Scan / security-audit (push) Has been cancelled
The users crate (RUSTSEC-2025-0040) has an unfixable vulnerability.
Replace it with the maintained whoami crate for getting the current
username. Also remove metrics.yml workflow as it provided no value.
2026-03-17 21:46:09 +01:00
jory c05d987ff1 fix(ci): update depgraph argument and formatting
Code Quality / quality-checks (push) Has been cancelled
Metrics Collection / collect-metrics (push) Has been cancelled
- metrics.yml: use correct --dedup-transitive-deps flag
- render.rs: apply cargo fmt fixes
2026-03-17 20:51:14 +01:00
jory a5115c0a15 Fixed typo in metrics.yml
Metrics Collection / collect-metrics (push) Has been cancelled
Code Quality / quality-checks (push) Has been cancelled
2026-03-17 20:26:28 +01:00
jory 20cc6e210e Fix all warnings previously generated by clippy 2026-03-17 20:24:12 +01:00
jory 3038c29c57 refactor(ci): extract system deps to reusable action
Metrics Collection / collect-metrics (push) Has been cancelled
Code Quality / quality-checks (push) Has been cancelled
2026-03-17 20:07:34 +01:00
jory 50c5de5371 refactor(ci): streamline GitHub workflows
Metrics Collection / collect-metrics (push) Has been cancelled
Code Quality / quality-checks (push) Has been cancelled
Security Scan / security-audit (push) Has been cancelled
- remove build.yml (redundant with release.yml)
- security.yml: remove duplicate cargo deny check (advisories redundant with full check)
- metrics.yml: remove unused rustfmt/clippy components and no-op summary step
- quality.yml: remove duplicate cargo deny check (already runs in security workflow)
2026-03-17 19:52:38 +01:00
jory 446426ea47 Fix formatting 2026-03-17 19:26:42 +01:00
jory f4f8e94e6e Added professional ci/cd pipelines to be tested
Metrics Collection / collect-metrics (push) Has been cancelled
Code Quality / quality-checks (push) Has been cancelled
Security Scan / security-audit (push) Has been cancelled
Multi-Distro Build / build-fedora (--no-default-features, no-default-features) (push) Has been cancelled
Multi-Distro Build / build-ubuntu (, default) (push) Has been cancelled
Multi-Distro Build / build-ubuntu (--no-default-features, no-default-features) (push) Has been cancelled
Multi-Distro Build / build-debian (, default) (push) Has been cancelled
Multi-Distro Build / build-debian (--no-default-features, no-default-features) (push) Has been cancelled
Multi-Distro Build / build-fedora (, default) (push) Has been cancelled
Multi-Distro Build / build-arch (, default) (push) Has been cancelled
Multi-Distro Build / build-arch (--no-default-features, no-default-features) (push) Has been cancelled
Multi-Distro Build / build-opensuse (, default) (push) Has been cancelled
Multi-Distro Build / build-opensuse (--no-default-features, no-default-features) (push) Has been cancelled
2026-03-17 19:01:09 +01:00
jory 0309522e25 Add caps lock indicator and keyboard layout support
CI / build-ubuntu (, default) (push) Has been cancelled
CI / build-ubuntu (--features networking, networking) (push) Has been cancelled
CI / build-ubuntu (--no-default-features, no-default-features) (push) Has been cancelled
CI / build-debian (push) Has been cancelled
CI / build-fedora (push) Has been cancelled
CI / build-arch (push) Has been cancelled
CI / build-opensuse (push) Has been cancelled
- Add caps lock ring color change when caps lock is enabled (matching swaylock-effects)
- Add configurable caps lock colors: ring, text, key highlight, backspace highlight
- Add show_caps_lock_text config option (enabled by default)
- Add keyboard layout display with show_keyboard_layout option (disabled by default)
- Change keyboard_layout from u32 to String for proper display
- Add rust-cache to CI and dbus-1-dev dependency
2026-03-16 17:32:54 +01:00
jory 5e47f8a69c Added capslock functionality
Added keyboard layout func
2026-03-16 15:10:23 +01:00
17 changed files with 482 additions and 353 deletions
+17
View File
@@ -0,0 +1,17 @@
name: Install system dependencies
description: Install system dependencies required for building rustlock
runs:
using: composite
steps:
- name: Install system dependencies (Ubuntu/Debian)
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y \
curl \
llvm clang libclang-dev \
pkg-config \
libglib2.0-dev libcairo2-dev libpango1.0-dev libatk1.0-dev \
libgdk-pixbuf-2.0-dev libpam0g-dev libdbus-1-dev \
libwayland-dev libxkbcommon-dev
-108
View File
@@ -1,108 +0,0 @@
name: CI
on:
push:
branches: ["master", "main"]
pull_request:
branches: ["master", "main"]
env:
CARGO_TERM_COLOR: always
jobs:
build-ubuntu:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- features: "default"
args: ""
- features: "networking"
args: "--features networking"
- features: "no-default-features"
args: "--no-default-features"
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
llvm clang libclang-dev \
pkg-config \
libglib2.0-dev libcairo2-dev libpango1.0-dev libatk1.0-dev \
libpam0g-dev \
libwayland-dev libxkbcommon-dev
- name: Build (${{ matrix.features }})
run: cargo build --verbose ${{ matrix.args }}
- name: Run tests
run: cargo test --verbose
build-debian:
runs-on: ubuntu-latest
container: debian:stable-slim
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
apt-get update
apt-get install -y \
llvm clang libclang-dev \
pkg-config \
libglib2.0-dev libcairo2-dev libpango1.0-dev libatk1.0-dev \
libpam0g-dev \
libwayland-dev libxkbcommon-dev \
cargo rustc
- name: Build
run: cargo build --verbose
build-fedora:
runs-on: ubuntu-latest
container: fedora:latest
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
dnf install -y \
llvm clang libclang-devel \
pkg-config \
glib2-devel cairo-devel pango-devel atk-devel \
pam-devel \
wayland-devel libxkbcommon-devel \
cargo rust
- name: Build
run: cargo build --verbose
build-arch:
runs-on: ubuntu-latest
container: archlinux:latest
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
pacman -Sy --noconfirm \
llvm clang pkgconf \
glib2 cairo pango atk \
pam \
wayland libxkbcommon \
rust cargo
- name: Build
run: cargo build --verbose
build-opensuse:
runs-on: ubuntu-latest
container: opensuse/tumbleweed:latest
steps:
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
zypper install -y \
llvm clang \
pkg-config \
glib2-devel cairo-devel pango-devel atk-devel \
pam-devel \
wayland-devel libxkbcommon-devel \
rust cargo
- name: Build
run: cargo build --verbose
+37
View File
@@ -0,0 +1,37 @@
name: Code Quality
on:
push:
branches: ["master", "main"]
pull_request:
branches: ["master", "main"]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
quality-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- uses: ./.github/actions/deps
- name: Install Rust with tools
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Check formatting
run: cargo fmt -- --check
- name: Run clippy
run: cargo clippy -- -D warnings
- name: Run unit tests
run: cargo test --quiet
- name: Check documentation
run: cargo doc --no-deps --document-private-items
+57 -100
View File
@@ -4,126 +4,85 @@ on:
push:
tags:
- 'v*'
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
build-ubuntu:
release-build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- variant: "default"
args: ""
distro: "ubuntu"
- variant: "no-networking"
args: "--no-default-features"
- variant: "networking"
args: "--features networking"
distro: "ubuntu"
- variant: "default"
args: ""
distro: "debian"
- variant: "default"
args: ""
distro: "fedora"
- variant: "default"
args: ""
distro: "arch"
container: ${{ matrix.distro == 'ubuntu' && 'ubuntu:latest' || matrix.distro == 'debian' && 'debian:stable-slim' || matrix.distro == 'fedora' && 'fedora:latest' || 'archlinux:latest' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install system dependencies
- name: Install system dependencies (Ubuntu/Debian)
if: matrix.distro == 'ubuntu' || matrix.distro == 'debian'
run: |
sudo apt-get update
sudo apt-get install -y \
apt-get update
apt-get install -y \
curl \
llvm clang libclang-dev \
pkg-config \
libglib2.0-dev libcairo2-dev libpango1.0-dev libatk1.0-dev \
libpam0g-dev \
libgdk-pixbuf-2.0-dev libpam0g-dev libdbus-1-dev \
libwayland-dev libxkbcommon-dev
- name: Build ${{ matrix.variant }}
- name: Install system dependencies (Fedora)
if: matrix.distro == 'fedora'
run: |
dnf install -y \
curl \
llvm clang clang-devel \
pkgconfig \
glib2-devel cairo-devel cairo-gobject-devel pango-devel atk-devel \
gdk-pixbuf2-devel pam-devel dbus-devel \
wayland-devel libxkbcommon-devel
- name: Install system dependencies (Arch)
if: matrix.distro == 'arch'
run: |
pacman -Sy --noconfirm \
base-devel \
llvm clang pkgconf \
glib2 cairo pango atk gdk-pixbuf2 \
pam dbus \
wayland libxkbcommon
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Build ${{ matrix.variant }} (${{ matrix.distro }})
run: cargo build --release ${{ matrix.args }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustlock-${{ matrix.variant }}-ubuntu
path: target/release/rustlock
build-debian:
runs-on: ubuntu-latest
container: debian:stable-slim
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install system dependencies
run: |
apt-get update
apt-get install -y \
llvm clang libclang-dev \
pkg-config \
libglib2.0-dev libcairo2-dev libpango1.0-dev libatk1.0-dev \
libpam0g-dev \
libwayland-dev libxkbcommon-dev \
cargo rust
- name: Build
run: cargo build --release
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustlock-default-debian
path: target/release/rustlock
build-fedora:
runs-on: ubuntu-latest
container: fedora:latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install system dependencies
run: |
dnf install -y \
llvm clang libclang-devel \
pkg-config \
glib2-devel cairo-devel pango-devel atk-devel \
pam-devel \
wayland-devel libxkbcommon-devel \
rust cargo
- name: Build
run: cargo build --release
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustlock-default-fedora
path: target/release/rustlock
build-arch:
runs-on: ubuntu-latest
container: archlinux:latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install system dependencies
run: |
pacman -Sy --noconfirm \
llvm clang pkgconf \
glib2 cairo pango atk \
pam \
wayland libxkbcommon \
rust cargo
- name: Build
run: cargo build --release
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: rustlock-default-arch
name: rustlock-${{ matrix.variant }}-${{ matrix.distro }}
path: target/release/rustlock
release:
runs-on: ubuntu-latest
needs: [build-ubuntu, build-debian, build-fedora, build-arch]
needs: [release-build]
permissions:
contents: write
steps:
@@ -140,17 +99,15 @@ jobs:
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: RustLock v${{ steps.tag.outputs.VERSION }}
draft: true
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
files: |
artifacts/rustlock-default-ubuntu/rustlock
artifacts/rustlock-networking-ubuntu/rustlock
artifacts/rustlock-default-debian/rustlock
artifacts/rustlock-default-fedora/rustlock
artifacts/rustlock-default-arch/rustlock
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
artifacts/rustlock-default-ubuntu/rustlock/rustlock:rustlock-default-ubuntu
artifacts/rustlock-no-networking-ubuntu/rustlock/rustlock:rustlock-no-networking-ubuntu
artifacts/rustlock-default-debian/rustlock/rustlock:rustlock-default-debian
artifacts/rustlock-default-fedora/rustlock/rustlock:rustlock-default-fedora
artifacts/rustlock-default-arch/rustlock/rustlock:rustlock-default-arch
+64
View File
@@ -0,0 +1,64 @@
name: Security Scan
on:
schedule:
- cron: '0 0 * * 0' # Weekly on Sunday at midnight
workflow_dispatch: # Manual trigger
push:
branches: [main, master]
paths:
- 'Cargo.toml'
- 'Cargo.lock'
- 'deny.toml'
- '.github/workflows/security.yml'
env:
CARGO_TERM_COLOR: always
jobs:
security-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install security tools
run: |
cargo install cargo-audit
cargo install cargo-deny
- name: Run cargo audit
run: cargo audit
- name: Run cargo deny
run: cargo deny check
- name: Generate Software Bill of Materials (SBOM)
run: |
cargo install cargo-cyclonedx
cargo cyclonedx --format json --output bom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: bom.json
retention-days: 90
- name: Check for outdated dependencies
run: |
cargo install cargo-outdated
cargo outdated --exit-code 1 || echo "Some dependencies are outdated"
- name: Security summary
run: |
echo "=== Security Scan Complete ==="
echo "✅ cargo audit - Vulnerability scanning"
echo "✅ cargo deny - Advisory and license checking"
echo "✅ SBOM generated - Software Bill of Materials"
echo "✅ Outdated dependencies checked"
echo ""
echo "Next scheduled scan: Weekly (Sunday 00:00 UTC)"
Generated
+45 -11
View File
@@ -1571,6 +1571,18 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "libredox"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
dependencies = [
"bitflags 2.11.0",
"libc",
"plain",
"redox_syscall",
]
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
@@ -1861,6 +1873,12 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "png"
version = "0.18.1"
@@ -2041,6 +2059,15 @@ dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "redox_syscall"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
dependencies = [
"bitflags 2.11.0",
]
[[package]]
name = "regex"
version = "1.12.3"
@@ -2213,10 +2240,10 @@ dependencies = [
"tokio",
"toml 1.0.6+spec-1.1.0",
"upower_dbus",
"users",
"wayland-client",
"wayland-protocols",
"wayland-protocols-wlr",
"whoami",
"xkbcommon",
"zbus",
"zeroize",
@@ -2874,16 +2901,6 @@ dependencies = [
"serde",
]
[[package]]
name = "users"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
dependencies = [
"libc",
"log",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -2947,6 +2964,12 @@ dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.114"
@@ -3148,6 +3171,17 @@ version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "whoami"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
dependencies = [
"libredox",
"wasite",
"web-sys",
]
[[package]]
name = "winapi"
version = "0.3.9"
+1 -1
View File
@@ -21,7 +21,7 @@ zeroize = "1.7"
log = "0.4"
env_logger = { version = "0.11", default-features = false, features = ["color", "humantime"] }
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
users = "0.11"
whoami = "1.0"
zbus = { version = "3.15", default-features = false, features = ["tokio"] }
mpris = "2.0"
upower_dbus = "0.3"
+2
View File
@@ -19,5 +19,7 @@ pkgs.rustPlatform.buildRustPackage {
nativeBuildInputs = [
pkgs.pkg-config
pkgs.rustPlatform.bindgenHook
pkgs.rustfmt
pkgs.clippy
];
}
+10
View File
@@ -0,0 +1,10 @@
[advisories]
vulnerability = "deny"
ignore = []
[bans]
multiple-versions = "allow"
[sources]
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
+16 -18
View File
@@ -4,7 +4,7 @@ use std::thread;
use log::{debug, error};
use pam_client::{Context, ErrorCode, Flag};
use smithay_client_toolkit::reexports::{calloop::channel, calloop::EventLoop};
use users::get_current_username;
use whoami::username;
use zeroize::Zeroizing;
const SERVICE_NAME: &str = "rustlock";
@@ -35,12 +35,9 @@ impl pam_client::ConversationHandler for LockConversation {
}
}
pub fn create_and_run_auth_loop() -> Option<(channel::Sender<Zeroizing<String>>, channel::Channel<bool>)> {
let username = get_current_username()
.expect("Failed to get username")
.to_str()
.expect("Failed to get non-unicode username")
.to_string();
pub fn create_and_run_auth_loop(
) -> Option<(channel::Sender<Zeroizing<String>>, channel::Channel<bool>)> {
let username = username();
let conversation = LockConversation { password: None };
match Context::new(SERVICE_NAME, Some(username.as_str()), conversation) {
@@ -49,7 +46,10 @@ pub fn create_and_run_auth_loop() -> Option<(channel::Sender<Zeroizing<String>>,
}
Err(err) => {
error!("Failed to initialize PAM context: {:?}", err);
error!("Ensure that the PAM service '{}' is correctly configured.", SERVICE_NAME);
error!(
"Ensure that the PAM service '{}' is correctly configured.",
SERVICE_NAME
);
return None;
}
}
@@ -67,17 +67,15 @@ pub fn create_and_run_auth_loop() -> Option<(channel::Sender<Zeroizing<String>>,
password: Some(password),
};
match Context::new(SERVICE_NAME, Some(username.as_str()), conversation) {
Ok(mut context) => {
match context.authenticate(Flag::NONE) {
Ok(()) => {
auth_res_send.send(true).unwrap();
}
Err(err) => {
error!("Pam authenticate failed with {:?}", err);
auth_res_send.send(false).unwrap();
}
Ok(mut context) => match context.authenticate(Flag::NONE) {
Ok(()) => {
auth_res_send.send(true).unwrap();
}
}
Err(err) => {
error!("Pam authenticate failed with {:?}", err);
auth_res_send.send(false).unwrap();
}
},
Err(err) => {
error!("Failed to re-initialize PAM context: {:?}", err);
auth_res_send.send(false).unwrap();
+61 -23
View File
@@ -42,6 +42,21 @@ pub struct Config {
#[arg(long, default_value = "4EAC41", value_parser = util::parse_hex_color)]
pub key_hl_color: (f64, f64, f64, f64),
#[arg(long, default_value = "4EAC41", value_parser = util::parse_hex_color)]
pub caps_lock_key_hl_color: (f64, f64, f64, f64),
#[arg(long, default_value = "DB3300", value_parser = util::parse_hex_color)]
pub caps_lock_bs_hl_color: (f64, f64, f64, f64),
#[arg(long, default_value = "E5A445", value_parser = util::parse_hex_color)]
pub caps_lock_color: (f64, f64, f64, f64),
#[arg(long, default_value = "E5A445", value_parser = util::parse_hex_color)]
pub caps_lock_text_color: (f64, f64, f64, f64),
#[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")]
pub show_caps_lock_text: bool,
#[arg(long, default_value = "00000000", value_parser = util::parse_hex_color)]
pub line_color: (f64, f64, f64, f64),
@@ -85,6 +100,9 @@ pub struct Config {
#[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "true", default_missing_value = "true")]
pub show_album_art: bool,
#[arg(long, action = clap::ArgAction::Set, num_args = 0..=1, default_value = "false", default_missing_value = "true")]
pub show_keyboard_layout: bool,
#[arg(long)]
pub image: Option<PathBuf>,
@@ -117,15 +135,14 @@ pub struct Config {
impl Config {
pub fn load() -> Self {
use clap::CommandFactory;
let mut config = Config::parse();
let cmd = Config::command();
let matches = cmd.get_matches();
// Helper to check if a value was explicitly set on command line
let is_cli = |key: &str| {
matches.value_source(key) == Some(clap::parser::ValueSource::CommandLine)
};
let is_cli =
|key: &str| matches.value_source(key) == Some(clap::parser::ValueSource::CommandLine);
// 1. Config file layer (overrides defaults and themes)
let config_path = config.config.clone().unwrap_or_else(|| {
@@ -138,7 +155,7 @@ impl Config {
if let Ok(file_content) = std::fs::read_to_string(&config_path) {
if let Ok(table) = toml::from_str::<toml::Table>(&file_content) {
log::debug!("Loaded configuration from {:?}", config_path);
let merge_bool = |val: &mut bool, key: &str| {
if !is_cli(key) {
if let Some(toml::Value::Boolean(b)) = table.get(key) {
@@ -186,49 +203,50 @@ impl Config {
merge_bool(&mut config.show_network, "show_network");
merge_bool(&mut config.show_bluetooth, "show_bluetooth");
merge_bool(&mut config.show_album_art, "show_album_art");
merge_bool(&mut config.show_keyboard_layout, "show_keyboard_layout");
if !is_cli("image") {
if let Some(toml::Value::String(s)) = table.get("image") {
config.image = Some(std::path::PathBuf::from(s));
}
}
if !is_cli("wifi_icon") {
if let Some(toml::Value::String(s)) = table.get("wifi_icon") {
config.wifi_icon = Some(s.clone());
}
}
if !is_cli("bluetooth_icon") {
if let Some(toml::Value::String(s)) = table.get("bluetooth_icon") {
config.bluetooth_icon = Some(s.clone());
}
}
if !is_cli("battery_icon") {
if let Some(toml::Value::String(s)) = table.get("battery_icon") {
config.battery_icon = Some(s.clone());
}
}
if !is_cli("media_prev_icon") {
if let Some(toml::Value::String(s)) = table.get("media_prev_icon") {
config.media_prev_icon = Some(s.clone());
}
}
if !is_cli("media_stop_icon") {
if let Some(toml::Value::String(s)) = table.get("media_stop_icon") {
config.media_stop_icon = Some(s.clone());
}
}
if !is_cli("media_play_icon") {
if let Some(toml::Value::String(s)) = table.get("media_play_icon") {
config.media_play_icon = Some(s.clone());
}
}
if !is_cli("media_next_icon") {
if let Some(toml::Value::String(s)) = table.get("media_next_icon") {
config.media_next_icon = Some(s.clone());
@@ -242,20 +260,40 @@ impl Config {
if let Some(theme) = &config.theme {
match theme.as_str() {
"modern" => {
if config.effect_blur.is_none() && !is_cli("effect_blur") { config.effect_blur = Some((10, 3)); }
if config.effect_vignette.is_none() && !is_cli("effect_vignette") { config.effect_vignette = Some((0.5, 0.5)); }
if !is_cli("indicator_radius") { config.indicator_radius = 120; }
if !is_cli("ring_color") { config.ring_color = (0.2, 0.6, 0.8, 1.0); }
if config.effect_blur.is_none() && !is_cli("effect_blur") {
config.effect_blur = Some((10, 3));
}
if config.effect_vignette.is_none() && !is_cli("effect_vignette") {
config.effect_vignette = Some((0.5, 0.5));
}
if !is_cli("indicator_radius") {
config.indicator_radius = 120;
}
if !is_cli("ring_color") {
config.ring_color = (0.2, 0.6, 0.8, 1.0);
}
}
"pixel" => {
if config.effect_pixelate.is_none() && !is_cli("effect_pixelate") { config.effect_pixelate = Some(10); }
if !is_cli("indicator_radius") { config.indicator_radius = 80; }
if !is_cli("ring_color") { config.ring_color = (0.8, 0.2, 0.2, 1.0); }
if config.effect_pixelate.is_none() && !is_cli("effect_pixelate") {
config.effect_pixelate = Some(10);
}
if !is_cli("indicator_radius") {
config.indicator_radius = 80;
}
if !is_cli("ring_color") {
config.ring_color = (0.8, 0.2, 0.2, 1.0);
}
}
"glass" => {
if config.effect_blur.is_none() && !is_cli("effect_blur") { config.effect_blur = Some((20, 5)); }
if !is_cli("inside_color") { config.inside_color = (1.0, 1.0, 1.0, 0.1); }
if !is_cli("ring_color") { config.ring_color = (1.0, 1.0, 1.0, 0.5); }
if config.effect_blur.is_none() && !is_cli("effect_blur") {
config.effect_blur = Some((20, 5));
}
if !is_cli("inside_color") {
config.inside_color = (1.0, 1.0, 1.0, 0.1);
}
if !is_cli("ring_color") {
config.ring_color = (1.0, 1.0, 1.0, 0.5);
}
}
_ => {
log::warn!("Unknown theme: {}", theme);
+5 -1
View File
@@ -100,7 +100,11 @@ impl InputHandler {
}
/// Update timers (should be called periodically)
pub fn update(&mut self) {
pub fn update(&mut self) {}
/// Get the current Caps Lock state
pub fn caps_lock(&self) -> bool {
self.caps_lock
}
}
+7 -6
View File
@@ -69,7 +69,7 @@ impl LockedSurface {
use wayland_client::Proxy;
self.wayland_surface
.as_ref()
.map_or(false, |ws| ws.id() == surface.id())
.is_some_and(|ws| ws.id() == surface.id())
}
/// Update the surface state (called on each frame)
@@ -107,6 +107,9 @@ impl LockedSurface {
self.key_highlight_shown = false;
}
// Update caps lock state in renderer
self.renderer.caps_lock = self.input_handler.caps_lock();
// Set background if available and not already applied
if !self.background_applied {
if let Some(ref background) = self.background {
@@ -162,11 +165,9 @@ impl LockedSurface {
event: smithay_client_toolkit::seat::keyboard::KeyEvent,
modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
) -> Option<InputAction> {
let action = self.input_handler.handle_key_event(
event.keysym,
event.utf8,
modifiers,
);
let action = self
.input_handler
.handle_key_event(event.keysym, event.utf8, modifiers);
match action {
InputAction::PasswordChanged => {
+22 -15
View File
@@ -5,18 +5,17 @@ mod lock;
mod render;
mod screenshot;
mod system;
mod timer;
mod util;
use config::Config;
use lock::LockManager;
use screenshot::{CaptureData, Screenshot, ScreenshotManager};
use system::SystemManager;
use std::error::Error;
use std::fs::OpenOptions;
use std::io::Write;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use system::SystemManager;
use wayland_client::protocol::wl_output::WlOutput;
use wayland_client::protocol::wl_surface::WlSurface;
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
@@ -161,7 +160,7 @@ impl WaylandLock {
if let Some(info) = self.output_state.info(output) {
if let Some(mode) = info.modes.first() {
let (w, h) = mode.dimensions;
return (w as i32, h as i32);
return (w, h);
}
}
(1920, 1080)
@@ -490,7 +489,7 @@ impl Dispatch<ZwlrScreencopyFrameV1, CaptureData> for WaylandLock {
stride,
} => {
let format = format.into_result().unwrap();
let mut info = data.info.lock().unwrap();
*info = Some(screenshot::BufferInfo {
width,
@@ -504,7 +503,8 @@ impl Dispatch<ZwlrScreencopyFrameV1, CaptureData> for WaylandLock {
let size = (stride as usize) * (height as usize);
match SlotPool::new(size, &state.shm_state) {
Ok(mut pool) => {
match pool.create_buffer(width as i32, height as i32, stride as i32, format) {
match pool.create_buffer(width as i32, height as i32, stride as i32, format)
{
Ok((buffer, _canvas)) => {
frame.copy(buffer.wl_buffer());
*data.buffer.lock().unwrap() = Some(buffer);
@@ -526,8 +526,10 @@ impl Dispatch<ZwlrScreencopyFrameV1, CaptureData> for WaylandLock {
let info = data.info.lock().unwrap().take();
let flags = data.flags.lock().unwrap().take();
let pool = data.pool.lock().unwrap().take();
if let (Some(buffer), Some(info), Some(flags), Some(mut pool)) = (buffer, info, flags, pool) {
if let (Some(buffer), Some(info), Some(flags), Some(mut pool)) =
(buffer, info, flags, pool)
{
let handle = screenshot::ScreencopyBufferHandle {
buffer,
info,
@@ -585,6 +587,7 @@ fn main() -> Result<(), Box<dyn Error>> {
log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::Debug))?;
log::info!("Starting rustlock v{}", env!("CARGO_PKG_VERSION"));
#[allow(clippy::arc_with_non_send_sync)]
let lock_manager = Arc::new(Mutex::new(LockManager::new(config.clone())));
let ctrlc_exit = Arc::new(std::sync::atomic::AtomicBool::new(false));
@@ -609,7 +612,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// Initialize state without pool first
// We'll create the pool after we know the output dimensions
// Initialize pool with a minimal size, it will be resized once outputs are detected
let pool = SlotPool::new(1, &shm_state)?;
@@ -624,7 +627,7 @@ fn main() -> Result<(), Box<dyn Error>> {
registry_state: RegistryState::new(&globals),
session_lock_state: SessionLockState::new(&globals, &qh),
seat_state: SeatState::new(&globals, &qh),
shm_state: shm_state,
shm_state,
pool,
session_lock: None,
lock_surfaces: Vec::new(),
@@ -649,7 +652,8 @@ fn main() -> Result<(), Box<dyn Error>> {
if let Ok(img) = image::open(image_path) {
let img = img.to_rgba8();
let (w, h) = img.dimensions();
let mut surface = cairo::ImageSurface::create(cairo::Format::ARgb32, w as i32, h as i32).unwrap();
let mut surface =
cairo::ImageSurface::create(cairo::Format::ARgb32, w as i32, h as i32).unwrap();
{
let mut surface_data = surface.data().unwrap();
for y in 0..h {
@@ -663,18 +667,21 @@ fn main() -> Result<(), Box<dyn Error>> {
}
}
}
let mut ss = Screenshot::new(surface);
let _ = ss.apply_effects(&state.config);
let surface = ss.into_inner();
let num_outputs = state.output_state.outputs().count();
state.captured_backgrounds = vec![Some(surface); num_outputs];
// Disable screenshots if image was successfully loaded
state.config.screenshots = false;
} else {
log::error!("Failed to load custom background image from {:?}", image_path);
log::error!(
"Failed to load custom background image from {:?}",
image_path
);
}
}
@@ -725,7 +732,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
let mut status = state.system_manager.get_status();
status.keyboard_layout = Some(state.current_layout);
status.keyboard_layout = Some(state.current_layout.to_string());
if let Ok(mut lm) = state.lock_manager.lock() {
lm.set_system_status(status);
+125 -57
View File
@@ -21,7 +21,7 @@ pub struct Renderer {
password_display: String,
uptime_cache: String,
last_uptime_update: Option<Instant>,
caps_lock: bool,
pub caps_lock: bool,
pub system_status: SystemStatus,
media_art_surface: Option<ImageSurface>,
last_art_url: Option<String>,
@@ -230,11 +230,15 @@ impl Renderer {
self.draw_bluetooth();
}
if self.config.show_keyboard_layout {
self.draw_keyboard_layout();
}
if !self.password_display.is_empty() {
self.draw_password_display();
}
if self.caps_lock {
if self.caps_lock && self.config.show_caps_lock_text {
self.draw_caps_lock_indicator();
}
@@ -347,8 +351,13 @@ impl Renderer {
self.context.stroke().unwrap();
}
// Use caps lock color when caps lock is on and indicator is enabled, otherwise ring color
let (r, g, b, a) = if self.caps_lock {
self.config.caps_lock_color
} else {
self.config.ring_color
};
self.context.new_path();
let (r, g, b, a) = self.config.ring_color;
self.context.set_source_rgba(r, g, b, a * self.fade_alpha);
self.context.set_line_width(thickness);
self.context
@@ -384,12 +393,16 @@ impl Renderer {
let center_y = self.height as f64 / 2.0;
let radius = self.config.indicator_radius as f64;
self.context.new_path();
self.context.set_font_size(12.0);
self.context.set_source_rgba(1.0, 0.5, 0.0, self.fade_alpha);
let text = "CAPS LOCK";
// Use configurable caps lock text color
let (r, g, b, a) = self.config.caps_lock_text_color;
self.context.set_source_rgba(r, g, b, a * self.fade_alpha);
// Increase font size for bigger letters
self.context.set_font_size(24.0);
let text = "Caps Lock";
let te = self.context.text_extents(text).unwrap();
// Position above the ring
self.context
.move_to(center_x - te.width() / 2.0, center_y + radius / 1.1 + 20.0);
.move_to(center_x - te.width() / 2.0, center_y - radius - 10.0);
self.context.show_text(text).unwrap();
}
@@ -439,7 +452,11 @@ impl Renderer {
};
if intensity > 0.0 {
let (r, g, b, a) = self.config.key_hl_color;
let (r, g, b, a) = if self.caps_lock {
self.config.caps_lock_key_hl_color
} else {
self.config.key_hl_color
};
self.context
.set_source_rgba(r, g, b, a * intensity * self.fade_alpha);
self.context.set_line_width(thickness + 1.5);
@@ -482,36 +499,39 @@ impl Renderer {
let art_size = 56.0;
let spacing = 80.0;
if self.config.show_album_art {
if self.system_status.media_art_url != self.last_art_url {
self.last_art_url = self.system_status.media_art_url.clone();
self.media_art_surface = None;
if let Some(ref data) = self.system_status.media_art_data {
if let Ok(img) = image::load_from_memory(data) {
let img = img.to_rgba8();
let (w, h) = img.dimensions();
let mut surface = ImageSurface::create(Format::ARgb32, w as i32, h as i32).unwrap();
{
let mut surface_data = surface.data().unwrap();
for y in 0..h {
for x in 0..w {
let pixel = img.get_pixel(x, y);
let idx = ((y * w + x) * 4) as usize;
surface_data[idx] = pixel[2];
surface_data[idx + 1] = pixel[1];
surface_data[idx + 2] = pixel[0];
surface_data[idx + 3] = pixel[3];
}
if self.config.show_album_art && self.system_status.media_art_url != self.last_art_url {
self.last_art_url = self.system_status.media_art_url.clone();
self.media_art_surface = None;
if let Some(ref data) = self.system_status.media_art_data {
if let Ok(img) = image::load_from_memory(data) {
let img = img.to_rgba8();
let (w, h) = img.dimensions();
let mut surface =
ImageSurface::create(Format::ARgb32, w as i32, h as i32).unwrap();
{
let mut surface_data = surface.data().unwrap();
for y in 0..h {
for x in 0..w {
let pixel = img.get_pixel(x, y);
let idx = ((y * w + x) * 4) as usize;
surface_data[idx] = pixel[2];
surface_data[idx + 1] = pixel[1];
surface_data[idx + 2] = pixel[0];
surface_data[idx + 3] = pixel[3];
}
}
self.media_art_surface = Some(surface);
}
self.media_art_surface = Some(surface);
}
}
}
let has_art = self.config.show_album_art && self.media_art_surface.is_some();
let text_x = if has_art { center_x - spacing / 2.0 } else { center_x };
let text_x = if has_art {
center_x - spacing / 2.0
} else {
center_x
};
let art_x = center_x - spacing - art_size / 2.0;
if has_art {
@@ -527,7 +547,8 @@ impl Renderer {
}
self.context.new_path();
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.9);
self.context
.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.9);
self.context.set_font_size(16.0);
let display_text = if let Some(ref artist) = self.system_status.media_artist {
@@ -537,14 +558,19 @@ impl Renderer {
};
let te = self.context.text_extents(&display_text).unwrap();
self.context.move_to(text_x - te.width() / 2.0, start_y + 20.0);
self.context
.move_to(text_x - te.width() / 2.0, start_y + 20.0);
self.context.show_text(&display_text).unwrap();
let status_text = if self.system_status.media_playing {
let status_text = if self.system_status.media_playing {
if let Some(ref icon) = self.media_play_icon_surface {
// Draw play icon instead of text
let play_y = start_y + 40.0;
self.draw_icon_at(center_x - icon.width() as f64 / 2.0, play_y - icon.height() as f64 / 2.0, icon);
self.draw_icon_at(
center_x - icon.width() as f64 / 2.0,
play_y - icon.height() as f64 / 2.0,
icon,
);
""
} else {
"▶ Playing"
@@ -552,51 +578,57 @@ impl Renderer {
} else {
"⏸ Paused"
};
if !status_text.is_empty() {
self.context.set_font_size(12.0);
let se = self.context.text_extents(status_text).unwrap();
self.context.move_to(text_x - se.width() / 2.0, start_y + 40.0);
self.context
.move_to(text_x - se.width() / 2.0, start_y + 40.0);
self.context.show_text(status_text).unwrap();
}
let controls_y = start_y + 65.0;
// Draw media control icons
let icon_size = 20.0;
let icon_spacing = 40.0;
let controls_center_x = center_x;
// Previous icon
if let Some(ref icon) = self.media_prev_icon_surface {
let ix = controls_center_x - icon_spacing;
self.draw_icon_at(ix - icon_size / 2.0, controls_y - icon_size / 2.0, icon);
} else {
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7);
self.context
.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7);
self.context.set_font_size(16.0);
self.context.move_to(controls_center_x - icon_spacing - 8.0, controls_y);
self.context
.move_to(controls_center_x - icon_spacing - 8.0, controls_y);
self.context.show_text("").unwrap();
}
// Stop icon
if let Some(ref icon) = self.media_stop_icon_surface {
let ix = controls_center_x;
self.draw_icon_at(ix - icon_size / 2.0, controls_y - icon_size / 2.0, icon);
} else {
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7);
self.context
.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7);
self.context.set_font_size(16.0);
self.context.move_to(controls_center_x - 8.0, controls_y);
self.context.show_text("").unwrap();
}
// Next icon
if let Some(ref icon) = self.media_next_icon_surface {
let ix = controls_center_x + icon_spacing;
self.draw_icon_at(ix - icon_size / 2.0, controls_y - icon_size / 2.0, icon);
} else {
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7);
self.context
.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.7);
self.context.set_font_size(16.0);
self.context.move_to(controls_center_x + icon_spacing - 8.0, controls_y);
self.context
.move_to(controls_center_x + icon_spacing - 8.0, controls_y);
self.context.show_text("").unwrap();
}
}
@@ -620,19 +652,17 @@ impl Renderer {
self.context.move_to(text_x, y);
self.context.show_text(ssid).unwrap();
} else {
let strength = self.system_status.wifi_strength.unwrap_or(0);
let icon = if strength > 75 { "📶" } else if strength > 50 { "📶" } else if strength > 25 { "📶" } else { "📶" };
let text = format!("{} {}", icon, ssid);
self.context.new_path();
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha);
self.context.set_font_size(16.0);
self.context.move_to(x, y);
self.context.show_text(&text).unwrap();
self.context.show_text(ssid).unwrap();
}
} else {
let text = "📵 No WiFi";
let text = "No WiFi";
self.context.new_path();
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.5);
self.context
.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.5);
self.context.set_font_size(16.0);
self.context.move_to(x, y);
self.context.show_text(text).unwrap();
@@ -656,7 +686,14 @@ impl Renderer {
self.context.move_to(text_x, y);
self.context.show_text(&battery_text).unwrap();
} else {
self.draw_battery_icon_at(x, y - 12.0, icon_width, 15.0, percent, self.system_status.is_charging);
self.draw_battery_icon_at(
x,
y - 12.0,
icon_width,
15.0,
percent,
self.system_status.is_charging,
);
let battery_text = format!("{:.0}%", percent);
self.context.new_path();
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha);
@@ -686,7 +723,10 @@ impl Renderer {
self.context.move_to(text_x, y);
self.context.show_text(&devices).unwrap();
} else {
let text = format!("🔵 {} device(s)", self.system_status.bluetooth_devices.len());
let text = format!(
"🔵 {} device(s)",
self.system_status.bluetooth_devices.len()
);
self.context.new_path();
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha);
self.context.set_font_size(14.0);
@@ -696,13 +736,31 @@ impl Renderer {
} else {
let text = "🔴 Bluetooth off";
self.context.new_path();
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.5);
self.context
.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha * 0.5);
self.context.set_font_size(14.0);
self.context.move_to(x, y);
self.context.show_text(text).unwrap();
}
}
fn draw_keyboard_layout(&self) {
if self.config.show_keyboard_layout {
if let Some(ref layout) = self.system_status.keyboard_layout {
let margin = 20.0;
let x = margin;
let y = margin + 80.0;
self.context.new_path();
self.context.set_source_rgba(1.0, 1.0, 1.0, self.fade_alpha);
self.context.set_font_size(16.0);
let text = format!("Layout: {}", layout);
self.context.move_to(x, y);
self.context.show_text(&text).unwrap();
}
}
}
fn draw_icon_at(&self, x: f64, y: f64, surface: &ImageSurface) {
self.context.save().unwrap();
let target_size = 24.0;
@@ -716,7 +774,15 @@ impl Renderer {
self.context.restore().unwrap();
}
fn draw_battery_icon_at(&self, x: f64, y: f64, width: f64, height: f64, percent: f64, charging: bool) {
fn draw_battery_icon_at(
&self,
x: f64,
y: f64,
width: f64,
height: f64,
percent: f64,
charging: bool,
) {
let alpha = self.fade_alpha;
self.context.new_path();
self.context.set_source_rgba(1.0, 1.0, 1.0, alpha * 0.5);
@@ -724,7 +790,8 @@ impl Renderer {
self.context.rectangle(x, y, width, height);
self.context.stroke().unwrap();
self.context.new_path();
self.context.rectangle(x + width, y + height / 4.0, 3.0, height / 2.0);
self.context
.rectangle(x + width, y + height / 4.0, 3.0, height / 2.0);
self.context.fill().unwrap();
let fill_width = (width - 4.0) * (percent / 100.0);
self.context.new_path();
@@ -733,7 +800,8 @@ impl Renderer {
} else {
self.context.set_source_rgba(0.2, 1.0, 0.2, alpha * 0.8);
}
self.context.rectangle(x + 2.0, y + 2.0, fill_width, height - 4.0);
self.context
.rectangle(x + 2.0, y + 2.0, fill_width, height - 4.0);
self.context.fill().unwrap();
if charging {
self.context.new_path();
+6 -6
View File
@@ -115,10 +115,10 @@ impl Screenshot {
for y in 0..height {
melt_amount += rng.gen_range(0.0..factor);
let src_y = (y as f32 - melt_amount).max(0.0) as i32;
let src_idx = (src_y as usize * stride) + (x as usize * 4);
let dst_idx = (y as usize * stride) + (x as usize * 4);
// Copy the pixel from above to create a smear
let pixel = [
data[src_idx],
@@ -394,24 +394,24 @@ impl ScreenshotManager {
flipped[dst_offset..dst_offset + src_stride]
.copy_from_slice(&converted_data[src_offset..src_offset + src_stride]);
}
return Ok(ImageSurface::create_for_data(
return ImageSurface::create_for_data(
flipped,
cairo::Format::ARgb32,
info.width as i32,
info.height as i32,
src_stride as i32,
)
.context("Failed to create flipped Cairo surface")?);
.context("Failed to create flipped Cairo surface");
}
Ok(ImageSurface::create_for_data(
ImageSurface::create_for_data(
converted_data,
cairo::Format::ARgb32,
info.width as i32,
info.height as i32,
pixel_width as i32,
)
.context("Failed to create Cairo surface")?)
.context("Failed to create Cairo surface")
}
}
+7 -7
View File
@@ -1,8 +1,8 @@
use std::sync::{Arc, Mutex};
use log::{debug, error};
use mpris::PlayerFinder;
use zbus::Connection;
use log::{error, debug};
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc;
use zbus::Connection;
#[derive(Clone, Default)]
pub struct SystemStatus {
@@ -17,7 +17,7 @@ pub struct SystemStatus {
pub wifi_strength: Option<u8>,
pub bluetooth_connected: bool,
pub bluetooth_devices: Vec<String>,
pub keyboard_layout: Option<u32>,
pub keyboard_layout: Option<String>,
}
#[derive(Debug, Clone, Copy)]
@@ -89,7 +89,7 @@ impl SystemManager {
new_status.media_title = metadata.title().map(|s| s.to_string());
new_status.media_artist = metadata.artists().map(|a| a.join(", "));
new_status.media_art_url = metadata.art_url().map(|u| u.to_string());
if new_status.media_art_url != last_art_url {
last_art_url = new_status.media_art_url.clone();
last_art_data = None;
@@ -216,7 +216,7 @@ impl SystemManager {
SystemCommand::Reboot => "Reboot",
SystemCommand::Suspend => "Suspend",
};
debug!("Executing system command: {}", method);
// Set a timeout for the DBus call to prevent hanging the background thread
let result = tokio::time::timeout(
@@ -230,7 +230,7 @@ impl SystemManager {
)
).await;
if let Err(_) = result {
if result.is_err() {
error!("System command {} timed out", method);
}
}