refactor: fixed security.yml and improved play/pause
- 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:
@@ -25,10 +25,12 @@ jobs:
|
|||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
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
|
- name: Install security tools
|
||||||
run: |
|
run: |
|
||||||
cargo install cargo-audit
|
cargo binstall --no-confirm cargo-audit cargo-deny cargo-cyclonedx cargo-outdated
|
||||||
cargo install cargo-deny
|
|
||||||
|
|
||||||
- name: Run cargo audit
|
- name: Run cargo audit
|
||||||
run: cargo audit
|
run: cargo audit
|
||||||
@@ -37,21 +39,16 @@ jobs:
|
|||||||
run: cargo deny check
|
run: cargo deny check
|
||||||
|
|
||||||
- name: Generate Software Bill of Materials (SBOM)
|
- name: Generate Software Bill of Materials (SBOM)
|
||||||
run: |
|
run: cargo cyclonedx --format json --override-filename bom
|
||||||
cargo install cargo-cyclonedx
|
|
||||||
cargo cyclonedx --format json --output bom.json
|
|
||||||
|
|
||||||
- name: Upload SBOM
|
- name: Upload SBOM
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sbom
|
name: sbom
|
||||||
path: bom.json
|
path: bom.json
|
||||||
retention-days: 90
|
|
||||||
|
|
||||||
- name: Check for outdated dependencies
|
- name: Check for outdated dependencies
|
||||||
run: |
|
run: cargo outdated --exit-code 1 || echo "Some dependencies are outdated"
|
||||||
cargo install cargo-outdated
|
|
||||||
cargo outdated --exit-code 1 || echo "Some dependencies are outdated"
|
|
||||||
|
|
||||||
- name: Security summary
|
- name: Security summary
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ pub struct Config {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub media_play_icon: Option<String>,
|
pub media_play_icon: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
pub media_pause_icon: Option<String>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub media_next_icon: Option<String>,
|
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 !is_cli("media_next_icon") {
|
||||||
if let Some(toml::Value::String(s)) = table.get("media_next_icon") {
|
if let Some(toml::Value::String(s)) = table.get("media_next_icon") {
|
||||||
config.media_next_icon = Some(s.clone());
|
config.media_next_icon = Some(s.clone());
|
||||||
|
|||||||
+1
-1
@@ -595,7 +595,7 @@ impl PointerHandler for WaylandLock {
|
|||||||
for (action, rx, ry, rw, rh) in &surface.renderer.media_rects {
|
for (action, rx, ry, rw, rh) in &surface.renderer.media_rects {
|
||||||
if x >= *rx && x <= rx + rw && y >= *ry && y <= ry + rh {
|
if x >= *rx && x <= rx + rw && y >= *ry && y <= ry + rh {
|
||||||
match action.as_str() {
|
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(),
|
"stop" => self.system_manager.media_stop(),
|
||||||
"next" => self.system_manager.media_next(),
|
"next" => self.system_manager.media_next(),
|
||||||
"prev" => self.system_manager.media_prev(),
|
"prev" => self.system_manager.media_prev(),
|
||||||
|
|||||||
+40
-4
@@ -33,6 +33,7 @@ pub struct Renderer {
|
|||||||
media_prev_icon_surface: Option<ImageSurface>,
|
media_prev_icon_surface: Option<ImageSurface>,
|
||||||
media_stop_icon_surface: Option<ImageSurface>,
|
media_stop_icon_surface: Option<ImageSurface>,
|
||||||
media_play_icon_surface: Option<ImageSurface>,
|
media_play_icon_surface: Option<ImageSurface>,
|
||||||
|
media_pause_icon_surface: Option<ImageSurface>,
|
||||||
media_next_icon_surface: Option<ImageSurface>,
|
media_next_icon_surface: Option<ImageSurface>,
|
||||||
pub media_rects: Vec<(String, f64, f64, f64, f64)>,
|
pub media_rects: Vec<(String, f64, f64, f64, f64)>,
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,7 @@ impl Renderer {
|
|||||||
media_prev_icon_surface: None,
|
media_prev_icon_surface: None,
|
||||||
media_stop_icon_surface: None,
|
media_stop_icon_surface: None,
|
||||||
media_play_icon_surface: None,
|
media_play_icon_surface: None,
|
||||||
|
media_pause_icon_surface: None,
|
||||||
media_next_icon_surface: None,
|
media_next_icon_surface: None,
|
||||||
media_rects: Vec::new(),
|
media_rects: Vec::new(),
|
||||||
};
|
};
|
||||||
@@ -227,6 +229,25 @@ impl Renderer {
|
|||||||
self.media_play_icon_surface = self.load_icon(&play_path);
|
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_names = ["media-skip-forward-symbolic", "media-skip-forward"];
|
||||||
let next_path = self
|
let next_path = self
|
||||||
.config
|
.config
|
||||||
@@ -959,13 +980,13 @@ impl Renderer {
|
|||||||
self.context.show_text(&display_text).unwrap();
|
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 {
|
if let Some(ref icon) = self.media_pause_icon_surface {
|
||||||
let play_y = start_y + 40.0;
|
let pause_y = start_y + 40.0;
|
||||||
let rx = center_x - icon.width() as f64 / 2.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.draw_icon_at(rx, ry, icon);
|
||||||
self.media_rects.push((
|
self.media_rects.push((
|
||||||
"play".to_string(),
|
"play_pause".to_string(),
|
||||||
rx,
|
rx,
|
||||||
ry,
|
ry,
|
||||||
icon.width() as f64,
|
icon.width() as f64,
|
||||||
@@ -975,8 +996,23 @@ impl Renderer {
|
|||||||
} else {
|
} else {
|
||||||
"▶ Playing"
|
"▶ 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 {
|
} else {
|
||||||
"⏸ Paused"
|
"⏸ Paused"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !status_text.is_empty() {
|
if !status_text.is_empty() {
|
||||||
|
|||||||
+52
-47
@@ -38,7 +38,6 @@ impl SystemManager {
|
|||||||
let s_clone = status.clone();
|
let s_clone = status.clone();
|
||||||
let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::<SystemCommand>();
|
let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::<SystemCommand>();
|
||||||
|
|
||||||
// Spawn a thread to update status periodically and handle commands
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let rt = match tokio::runtime::Runtime::new() {
|
let rt = match tokio::runtime::Runtime::new() {
|
||||||
Ok(rt) => rt,
|
Ok(rt) => rt,
|
||||||
@@ -55,7 +54,6 @@ impl SystemManager {
|
|||||||
let mut last_art_data: Option<Arc<Vec<u8>>> = None;
|
let mut last_art_data: Option<Arc<Vec<u8>>> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Try to connect to system DBus if not connected
|
|
||||||
if conn.is_none() {
|
if conn.is_none() {
|
||||||
match Connection::system().await {
|
match Connection::system().await {
|
||||||
Ok(c) => conn = Some(c),
|
Ok(c) => conn = Some(c),
|
||||||
@@ -81,7 +79,9 @@ impl SystemManager {
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
if let Ok(props) = reply.body().deserialize::<HashMap<String, zbus::zvariant::OwnedValue>>() {
|
if let Ok(props) = reply.body().deserialize::<HashMap<String, zbus::zvariant::OwnedValue>>() {
|
||||||
if let Some(v) = props.get("Percentage") {
|
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 Some(v) = props.get("State") {
|
||||||
if let Ok(state) = v.downcast_ref::<u32>() {
|
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(
|
if let Ok(reply) = c.call_method(
|
||||||
Some("org.freedesktop.NetworkManager"),
|
Some("org.freedesktop.NetworkManager"),
|
||||||
"/org/freedesktop/NetworkManager",
|
"/org/freedesktop/NetworkManager",
|
||||||
@@ -149,7 +109,7 @@ impl SystemManager {
|
|||||||
).await {
|
).await {
|
||||||
if let Ok(val) = dev_type_reply.body().deserialize::<zbus::zvariant::OwnedValue>() {
|
if let Ok(val) = dev_type_reply.body().deserialize::<zbus::zvariant::OwnedValue>() {
|
||||||
if let Ok(dev_type) = val.downcast_ref::<u32>() {
|
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(
|
if let Ok(active_ap_reply) = c.call_method(
|
||||||
Some("org.freedesktop.NetworkManager"),
|
Some("org.freedesktop.NetworkManager"),
|
||||||
&dev_path,
|
&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() {
|
if let Ok(mut s) = s_clone.lock() {
|
||||||
*s = new_status;
|
*s = new_status;
|
||||||
@@ -243,9 +251,7 @@ impl SystemManager {
|
|||||||
SystemCommand::Reboot => "Reboot",
|
SystemCommand::Reboot => "Reboot",
|
||||||
SystemCommand::Suspend => "Suspend",
|
SystemCommand::Suspend => "Suspend",
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Executing system command: {}", method);
|
debug!("Executing system command: {}", method);
|
||||||
// Set a timeout for the DBus call to prevent hanging the background thread
|
|
||||||
let result = tokio::time::timeout(
|
let result = tokio::time::timeout(
|
||||||
tokio::time::Duration::from_secs(5),
|
tokio::time::Duration::from_secs(5),
|
||||||
c.call_method(
|
c.call_method(
|
||||||
@@ -256,7 +262,6 @@ impl SystemManager {
|
|||||||
&(true),
|
&(true),
|
||||||
)
|
)
|
||||||
).await;
|
).await;
|
||||||
|
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
error!("System command {} timed out", method);
|
error!("System command {} timed out", method);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user