Compare commits

...

10 commits

Author SHA1 Message Date
60bce9eae4 chore: align idle with local cosmic stack 2026-05-24 11:11:12 +02:00
Jeremy Soller
c95d066b5b Add pull request template 2026-02-13 12:35:27 -07:00
Ian Douglas Scott
6d3dbedd50 Use system_actions for suspend 2026-01-22 09:49:36 -08:00
Vukašin Vojinović
983d34ad96 chore: update dependencies
Also migrates to Rust 2024 edition.
2025-11-13 14:59:24 -07:00
Vukašin Vojinović
16abcc66e1 chore: clippy 2025-11-13 14:59:24 -07:00
MabaKalox
267bb837f1
fix: use loginctl lock-session as fallback in case system_actions was not readable 2025-02-25 19:13:24 +01:00
Alex Saveau
7899fccbbe Slow down the fade to black time 2024-12-24 14:56:04 -08:00
Ian Douglas Scott
b0a2103dfc Reduce Cargo.lock build for CI build 2024-12-24 14:23:04 -08:00
Ian Douglas Scott
8e9706bd21 Run cargo update
Fixes parsing of newer `system_actions`.
2024-12-24 13:18:44 -08:00
Ian Douglas Scott
e3f2e505bb Add a slight delay between screen off and locking 2024-12-24 13:15:37 -08:00
9 changed files with 1024 additions and 775 deletions

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,8 @@
- [ ] I have disclosed use of any AI generated code in my commit messages.
- If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR.
- In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment.
- [ ] I understand these changes in full and will be able to respond to review comments.
- [ ] My change is accurately described in the commit message.
- [ ] My contribution is tested and working as described.
- [ ] I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.

1641
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,31 @@
[package]
name = "cosmic-idle"
version = "0.1.0"
edition = "2021"
edition = "2024"
[dependencies]
keyframe = "1.1.1"
wayland-client = "0.31.5"
wayland-protocols = { version = "0.32.3", features = ["client", "staging"] }
wayland-protocols-wlr = { version = "0.3.3", features = ["client"] }
cosmic-config = { git = "https://github.com/pop-os/libcosmic", features = ["calloop"] }
wayland-client = "0.31.11"
wayland-protocols = { version = "0.32.9", features = ["client", "staging"] }
wayland-protocols-wlr = { version = "0.3.9", features = ["client"] }
cosmic-config = { path = "../libcosmic/cosmic-config", features = ["calloop"] }
cosmic-idle-config = { path = "./cosmic-idle-config" }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" }
calloop = { version = "0.14.0", features = ["executor"] }
calloop-wayland-source = "0.4.0"
log = "0.4.22"
env_logger = "0.11.5"
cosmic-settings-config = { path = "../cosmic-settings-daemon/config" }
calloop = { version = "0.14.3", features = ["executor"] }
calloop-wayland-source = "0.4.1"
log = "0.4.28"
env_logger = "0.11.8"
upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings" }
zbus = "4.0.0"
futures-lite = "2.3.0"
zbus = "5.12"
futures-lite = "2.6.1"
[workspace]
members = [
"cosmic-idle-config"
]
[patch.'https://github.com/pop-os/libcosmic']
cosmic-config = { path = "../libcosmic/cosmic-config" }
[patch.'https://github.com/pop-os/cosmic-settings-daemon']
cosmic-settings-config = { path = "../cosmic-settings-daemon/config" }

View file

@ -1,8 +1,8 @@
[package]
name = "cosmic-idle-config"
version = "0.1.0"
edition = "2021"
edition = "2024"
[dependencies]
cosmic-config = { git = "https://github.com/pop-os/libcosmic" }
serde = { version = "1.0.210", features = ["derive"] }
cosmic-config = { path = "../../libcosmic/cosmic-config" }
serde = { version = "1.0.228", features = ["derive"] }

View file

@ -1,4 +1,4 @@
use cosmic_config::{cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry};
use cosmic_config::{CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone, CosmicConfigEntry)]

View file

@ -1,2 +1 @@
reorder_imports = true
imports_granularity = "Crate"

View file

@ -3,9 +3,8 @@
use keyframe::{ease, functions::EaseInOut};
use std::time::{Duration, Instant};
use wayland_client::{
delegate_noop,
Connection, Dispatch, QueueHandle, delegate_noop,
protocol::{wl_buffer, wl_callback, wl_output, wl_pointer, wl_surface},
Connection, Dispatch, QueueHandle,
};
use wayland_protocols::wp::{
single_pixel_buffer::v1::client::wp_single_pixel_buffer_manager_v1,
@ -15,7 +14,7 @@ use wayland_protocols_wlr::layer_shell::v1::client::{zwlr_layer_shell_v1, zwlr_l
use crate::{State, StateInner};
const FADE_TIME: Duration = Duration::from_millis(2000);
const FADE_TIME: Duration = Duration::from_secs(5);
#[derive(Debug)]
pub struct FadeBlackSurface {
@ -101,12 +100,12 @@ impl Dispatch<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, ()> for State {
height,
} => {
for output in &mut state.outputs {
if let Some(fade_surface) = &mut output.fade_surface {
if &fade_surface.layer_surface == obj {
fade_surface.layer_surface.ack_configure(serial);
fade_surface.configure(&state.inner, width, height);
break;
}
if let Some(fade_surface) = &mut output.fade_surface
&& &fade_surface.layer_surface == obj
{
fade_surface.layer_surface.ack_configure(serial);
fade_surface.configure(&state.inner, width, height);
break;
}
}
}
@ -127,23 +126,23 @@ impl Dispatch<wl_callback::WlCallback, wl_surface::WlSurface> for State {
match event {
wl_callback::Event::Done { callback_data: _ } => {
for output in &mut state.outputs {
if let Some(fade_surface) = &mut output.fade_surface {
if &fade_surface.surface == surface {
if fade_surface.is_done() {
// All outputs are done fading
if state
.outputs
.iter()
.flat_map(|o| o.fade_surface.as_ref())
.all(|s| s.is_done())
{
state.fade_done();
}
} else {
fade_surface.update(&state.inner);
if let Some(fade_surface) = &mut output.fade_surface
&& &fade_surface.surface == surface
{
if fade_surface.is_done() {
// All outputs are done fading
if state
.outputs
.iter()
.flat_map(|o| o.fade_surface.as_ref())
.all(|s| s.is_done())
{
state.fade_done();
}
break;
} else {
fade_surface.update(&state.inner);
}
break;
}
}
}

View file

@ -3,8 +3,8 @@
use futures_lite::StreamExt;
use std::sync::{
atomic::{AtomicU32, Ordering},
Arc, Mutex,
atomic::{AtomicU32, Ordering},
};
use crate::{Event, EventSender};
@ -99,14 +99,14 @@ pub async fn serve(event_sender: EventSender) -> zbus::Result<()> {
let mut name_owner_stream = dbus.receive_name_owner_changed().await?;
while let Some(event) = name_owner_stream.next().await {
let args = event.args()?;
if args.new_owner.is_none() {
if let zbus::names::BusName::Unique(name) = args.name {
let mut inhibitors = inhibitors.lock().unwrap();
if !inhibitors.is_empty() {
inhibitors.retain(|inhibitor| inhibitor.client != name);
if inhibitors.is_empty() {
let _ = event_sender.send(Event::ScreensaverInhibit(false));
}
if args.new_owner.is_none()
&& let zbus::names::BusName::Unique(name) = args.name
{
let mut inhibitors = inhibitors.lock().unwrap();
if !inhibitors.is_empty() {
inhibitors.retain(|inhibitor| inhibitor.client != name);
if inhibitors.is_empty() {
let _ = event_sender.send(Event::ScreensaverInhibit(false));
}
}
}

View file

@ -1,18 +1,17 @@
#![allow(clippy::single_match)]
use calloop::{channel, EventLoop};
use calloop::{EventLoop, channel, timer};
use calloop_wayland_source::WaylandSource;
use cosmic_config::{calloop::ConfigWatchSource, CosmicConfigEntry};
use cosmic_config::{CosmicConfigEntry, calloop::ConfigWatchSource};
use cosmic_idle_config::CosmicIdleConfig;
use cosmic_settings_config::shortcuts;
use futures_lite::stream::StreamExt;
use std::process::Command;
use std::{process::Command, time::Duration};
use upower_dbus::UPowerProxy;
use wayland_client::{
delegate_noop,
globals::{registry_queue_init, GlobalListContents},
Connection, Dispatch, Proxy, QueueHandle, delegate_noop,
globals::{GlobalListContents, registry_queue_init},
protocol::{wl_compositor, wl_output, wl_registry, wl_seat},
Connection, Dispatch, Proxy, QueueHandle,
};
use wayland_protocols::{
ext::idle_notify::v1::client::{ext_idle_notification_v1, ext_idle_notifier_v1},
@ -30,6 +29,9 @@ mod fade_black;
use fade_black::FadeBlackSurface;
mod freedesktop_screensaver;
// Delay between screen off and locking
const LOCK_SCREEN_DELAY: Duration = Duration::from_millis(500);
#[derive(Debug)]
enum Event {
OnBattery(bool),
@ -99,6 +101,7 @@ struct State {
on_battery: bool,
screensaver_inhibit: bool,
system_actions: shortcuts::SystemActions,
loop_handle: calloop::LoopHandle<'static, Self>,
}
fn run_command(command: String) {
@ -157,18 +160,30 @@ impl State {
output.fade_surface = None;
}
if let Some(command) = self
let timer = timer::Timer::from_duration(LOCK_SCREEN_DELAY);
self.loop_handle
.insert_source(timer, |_, _, state| {
state.lock_screen();
timer::TimeoutAction::Drop
})
.unwrap();
}
fn lock_screen(&self) {
let command = self
.system_actions
.get(&shortcuts::action::System::LockScreen)
{
crate::run_command(command.to_string());
}
.map_or("loginctl lock-session", |s| s.as_str());
crate::run_command(command.to_string());
}
fn update_suspend_idle(&mut self, is_idle: bool) {
if is_idle {
// TODO: Make command configurable
run_command("systemctl suspend".to_string());
let command = self
.system_actions
.get(&shortcuts::action::System::Suspend)
.map_or("systemctl suspend", |s| s.as_str());
crate::run_command(command.to_string());
}
}
@ -267,6 +282,8 @@ fn main() {
let shortcuts_config = shortcuts::context().unwrap();
let system_actions = shortcuts::system_actions(&shortcuts_config);
let mut event_loop: EventLoop<State> = EventLoop::try_new().unwrap();
let mut state = State {
inner: StateInner {
registry: globals.registry().clone(),
@ -286,6 +303,7 @@ fn main() {
on_battery: false,
screensaver_inhibit: false,
system_actions,
loop_handle: event_loop.handle(),
};
globals.contents().with_list(|list| {
for global in list {
@ -296,8 +314,6 @@ fn main() {
});
state.recreate_notifications();
let mut event_loop: EventLoop<State> = EventLoop::try_new().unwrap();
WaylandSource::new(connection, event_queue)
.insert(event_loop.handle())
.unwrap();
@ -342,7 +358,7 @@ fn main() {
})
.unwrap();
while let Ok(_) = event_loop.dispatch(None, &mut state) {}
while event_loop.dispatch(None, &mut state).is_ok() {}
}
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {