refactor: fixed security.yml and improved play/pause
Code Quality / quality-checks (push) Has been cancelled
Security Scan / security-audit (push) Has been cancelled

- New `--media-pause-icon` option
- Unified play/pause button action
- Move MPRIS lookup to spawn_blocking
- Speed up security CI with cargo-binstall
This commit is contained in:
2026-04-11 12:55:45 +02:00
parent 37fb09c7c6
commit 05593e0959
5 changed files with 121 additions and 74 deletions
+6 -9
View File
@@ -25,10 +25,12 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-binstall
run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install.sh | sh
- name: Install security tools
run: |
cargo install cargo-audit
cargo install cargo-deny
cargo binstall --no-confirm cargo-audit cargo-deny cargo-cyclonedx cargo-outdated
- name: Run cargo audit
run: cargo audit
@@ -37,21 +39,16 @@ jobs:
run: cargo deny check
- name: Generate Software Bill of Materials (SBOM)
run: |
cargo install cargo-cyclonedx
cargo cyclonedx --format json --output bom.json
run: cargo cyclonedx --format json --override-filename bom
- 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"
run: cargo outdated --exit-code 1 || echo "Some dependencies are outdated"
- name: Security summary
run: |
+9
View File
@@ -127,6 +127,9 @@ pub struct Config {
#[arg(long)]
pub media_play_icon: Option<String>,
#[arg(long)]
pub media_pause_icon: Option<String>,
#[arg(long)]
pub media_next_icon: Option<String>,
@@ -251,6 +254,12 @@ impl Config {
}
}
if !is_cli("media_pause_icon") {
if let Some(toml::Value::String(s)) = table.get("media_pause_icon") {
config.media_pause_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());
+1 -1
View File
@@ -595,7 +595,7 @@ impl PointerHandler for WaylandLock {
for (action, rx, ry, rw, rh) in &surface.renderer.media_rects {
if x >= *rx && x <= rx + rw && y >= *ry && y <= ry + rh {
match action.as_str() {
"play" => self.system_manager.media_play_pause(),
"play_pause" => self.system_manager.media_play_pause(),
"stop" => self.system_manager.media_stop(),
"next" => self.system_manager.media_next(),
"prev" => self.system_manager.media_prev(),
+40 -4
View File
@@ -33,6 +33,7 @@ pub struct Renderer {
media_prev_icon_surface: Option<ImageSurface>,
media_stop_icon_surface: Option<ImageSurface>,
media_play_icon_surface: Option<ImageSurface>,
media_pause_icon_surface: Option<ImageSurface>,
media_next_icon_surface: Option<ImageSurface>,
pub media_rects: Vec<(String, f64, f64, f64, f64)>,
}
@@ -73,6 +74,7 @@ impl Renderer {
media_prev_icon_surface: None,
media_stop_icon_surface: None,
media_play_icon_surface: None,
media_pause_icon_surface: None,
media_next_icon_surface: None,
media_rects: Vec::new(),
};
@@ -227,6 +229,25 @@ impl Renderer {
self.media_play_icon_surface = self.load_icon(&play_path);
}
let pause_names = ["media-playback-pause-symbolic", "media-playback-pause"];
let pause_path = self
.config
.media_pause_icon
.clone()
.or_else(|| {
for name in &pause_names {
if let Some(path) = self.find_system_icon(name) {
return Some(path);
}
}
None
})
.unwrap_or_default();
if !pause_path.is_empty() {
log::debug!("Resolved Media Pause icon path: {}", pause_path);
self.media_pause_icon_surface = self.load_icon(&pause_path);
}
let next_names = ["media-skip-forward-symbolic", "media-skip-forward"];
let next_path = self
.config
@@ -959,13 +980,13 @@ impl Renderer {
self.context.show_text(&display_text).unwrap();
let status_text = if self.system_status.media_playing {
if let Some(ref icon) = self.media_play_icon_surface {
let play_y = start_y + 40.0;
if let Some(ref icon) = self.media_pause_icon_surface {
let pause_y = start_y + 40.0;
let rx = center_x - icon.width() as f64 / 2.0;
let ry = play_y - icon.height() as f64 / 2.0;
let ry = pause_y - icon.height() as f64 / 2.0;
self.draw_icon_at(rx, ry, icon);
self.media_rects.push((
"play".to_string(),
"play_pause".to_string(),
rx,
ry,
icon.width() as f64,
@@ -975,8 +996,23 @@ impl Renderer {
} else {
"▶ Playing"
}
} else {
if let Some(ref icon) = self.media_play_icon_surface {
let play_y = start_y + 40.0;
let rx = center_x - icon.width() as f64 / 2.0;
let ry = play_y - icon.height() as f64 / 2.0;
self.draw_icon_at(rx, ry, icon);
self.media_rects.push((
"play_pause".to_string(),
rx,
ry,
icon.width() as f64,
icon.height() as f64,
));
""
} else {
"⏸ Paused"
}
};
if !status_text.is_empty() {
+52 -47
View File
@@ -38,7 +38,6 @@ impl SystemManager {
let s_clone = status.clone();
let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::<SystemCommand>();
// Spawn a thread to update status periodically and handle commands
std::thread::spawn(move || {
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
@@ -55,7 +54,6 @@ impl SystemManager {
let mut last_art_data: Option<Arc<Vec<u8>>> = None;
loop {
// Try to connect to system DBus if not connected
if conn.is_none() {
match Connection::system().await {
Ok(c) => conn = Some(c),
@@ -81,7 +79,9 @@ impl SystemManager {
use std::collections::HashMap;
if let Ok(props) = reply.body().deserialize::<HashMap<String, zbus::zvariant::OwnedValue>>() {
if let Some(v) = props.get("Percentage") {
new_status.battery_percent = v.downcast_ref::<f64>().ok();
if let Ok(val) = v.downcast_ref::<f64>() {
new_status.battery_percent = Some(val);
}
}
if let Some(v) = props.get("State") {
if let Ok(state) = v.downcast_ref::<u32>() {
@@ -90,47 +90,7 @@ impl SystemManager {
}
}
}
}
// 2. MPRIS status
if let Ok(finder) = PlayerFinder::new() {
if let Ok(player) = finder.find_active() {
if let Ok(metadata) = player.get_metadata() {
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;
if let Some(ref url) = last_art_url {
if url.starts_with("file://") {
let path = url.trim_start_matches("file://");
if let Ok(data) = std::fs::read(path) {
last_art_data = Some(Arc::new(data));
}
} else if url.starts_with("http") {
#[cfg(feature = "networking")]
if let Ok(resp) = reqwest::get(url).await {
if let Ok(bytes) = resp.bytes().await {
last_art_data = Some(Arc::new(bytes.to_vec()));
}
}
#[cfg(not(feature = "networking"))]
{
log::debug!("Networking disabled, skipping remote album art: {}", url);
}
}
}
}
new_status.media_art_data = last_art_data.clone();
}
new_status.media_playing = player.get_playback_status().map(|s| matches!(s, mpris::PlaybackStatus::Playing)).unwrap_or(false);
}
}
// 3. WiFi status (NetworkManager)
if let Some(ref c) = conn {
if let Ok(reply) = c.call_method(
Some("org.freedesktop.NetworkManager"),
"/org/freedesktop/NetworkManager",
@@ -149,7 +109,7 @@ impl SystemManager {
).await {
if let Ok(val) = dev_type_reply.body().deserialize::<zbus::zvariant::OwnedValue>() {
if let Ok(dev_type) = val.downcast_ref::<u32>() {
if dev_type == 2 { // WiFi
if dev_type == 2 {
if let Ok(active_ap_reply) = c.call_method(
Some("org.freedesktop.NetworkManager"),
&dev_path,
@@ -230,6 +190,54 @@ impl SystemManager {
}
}
let mpris_status = tokio::task::spawn_blocking(move || {
let mut media_title = None;
let mut media_artist = None;
let mut media_art_url = None;
let mut media_playing = false;
if let Ok(finder) = PlayerFinder::new() {
if let Ok(player) = finder.find_active() {
if let Ok(metadata) = player.get_metadata() {
media_title = metadata.title().map(|s| s.to_string());
media_artist = metadata.artists().map(|a| a.join(", "));
media_art_url = metadata.art_url().map(|u| u.to_string());
}
media_playing = player.get_playback_status().map(|s| matches!(s, mpris::PlaybackStatus::Playing)).unwrap_or(false);
}
}
(media_title, media_artist, media_art_url, media_playing)
}).await.unwrap_or((None, None, None, false));
new_status.media_title = mpris_status.0;
new_status.media_artist = mpris_status.1;
new_status.media_art_url = mpris_status.2;
new_status.media_playing = mpris_status.3;
if new_status.media_art_url != last_art_url {
last_art_url = new_status.media_art_url.clone();
last_art_data = None;
if let Some(ref url) = last_art_url {
if url.starts_with("file://") {
let path = url.trim_start_matches("file://");
if let Ok(data) = std::fs::read(path) {
last_art_data = Some(Arc::new(data));
}
} else if url.starts_with("http") {
#[cfg(feature = "networking")]
if let Ok(resp) = reqwest::get(url).await {
if let Ok(bytes) = resp.bytes().await {
last_art_data = Some(Arc::new(bytes.to_vec()));
}
}
#[cfg(not(feature = "networking"))]
{
log::debug!("Networking disabled, skipping remote album art: {}", url);
}
}
}
}
new_status.media_art_data = last_art_data.clone();
{
if let Ok(mut s) = s_clone.lock() {
*s = new_status;
@@ -243,9 +251,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(
tokio::time::Duration::from_secs(5),
c.call_method(
@@ -256,7 +262,6 @@ impl SystemManager {
&(true),
)
).await;
if result.is_err() {
error!("System command {} timed out", method);
}