11//! D-Bus command execution for noticenterctl.
22
3- use anyhow:: Result ;
3+ use std:: future:: Future ;
4+ use std:: time:: Duration ;
5+
6+ use anyhow:: { anyhow, Result } ;
47use unixnotis_core:: { util, ControlProxy } ;
58
69use crate :: cli_args:: { Command , DndState } ;
710use crate :: main_log_follow:: follow_debug_logs;
811use crate :: main_output:: { print_inhibitors, print_notifications} ;
912
13+ const CONTROL_CALL_TIMEOUT : Duration = Duration :: from_secs ( 5 ) ;
14+
1015pub ( crate ) async fn handle_command ( proxy : & ControlProxy < ' _ > , command : Command ) -> Result < ( ) > {
1116 // CLI forwards work to the daemon
1217 match command {
1318 Command :: TogglePanel => {
1419 // Simple toggle keeps the daemon in control of its own visibility rules.
15- proxy. toggle_panel ( ) . await ?;
20+ run_control_call ( proxy. toggle_panel ( ) ) . await ?;
1621 }
1722 Command :: OpenPanel { debug } => {
1823 // Debug mode opens the panel and streams daemon logs for real-time triage.
1924 if let Some ( level) = debug {
20- proxy. open_panel_debug ( level. into ( ) ) . await ?;
25+ run_control_call ( proxy. open_panel_debug ( level. into ( ) ) ) . await ?;
2126 // Panel open should still succeed when journal follow is unavailable.
2227 if let Err ( err) = follow_debug_logs ( ) {
2328 eprintln ! ( "debug log follow unavailable: {err}" ) ;
2429 }
2530 } else {
26- proxy. open_panel ( ) . await ?;
31+ run_control_call ( proxy. open_panel ( ) ) . await ?;
2732 }
2833 }
2934 Command :: ClosePanel => {
3035 // Explicit close avoids accidental toggles when the panel is hidden.
31- proxy. close_panel ( ) . await ?;
36+ run_control_call ( proxy. close_panel ( ) ) . await ?;
3237 }
3338 Command :: Clear => {
3439 // Clear removes both active notifications and history entries.
35- proxy. clear_all ( ) . await ?;
40+ run_control_call ( proxy. clear_all ( ) ) . await ?;
3641 }
3742 Command :: Dismiss { id } => {
3843 // Dismiss targets a single notification by id.
39- proxy. dismiss ( id) . await ?;
44+ run_control_call ( proxy. dismiss ( id) ) . await ?;
4045 }
4146 Command :: ListActive { full } => {
4247 // Full output needs the debug gate
@@ -45,7 +50,7 @@ pub(crate) async fn handle_command(proxy: &ControlProxy<'_>, command: Command) -
4550 // Fall back to the safe view
4651 eprintln ! ( "--full requires UNIXNOTIS_DIAGNOSTIC=1; using redacted output" ) ;
4752 }
48- let notifications = proxy. list_active ( ) . await ?;
53+ let notifications = run_control_call ( proxy. list_active ( ) ) . await ?;
4954 // Shared output helper
5055 print_notifications ( "active" , & notifications, allow_full) ;
5156 }
@@ -55,34 +60,34 @@ pub(crate) async fn handle_command(proxy: &ControlProxy<'_>, command: Command) -
5560 if full && !util:: diagnostic_mode ( ) {
5661 eprintln ! ( "--full requires UNIXNOTIS_DIAGNOSTIC=1; using redacted output" ) ;
5762 }
58- let notifications = proxy. list_history ( ) . await ?;
63+ let notifications = run_control_call ( proxy. list_history ( ) ) . await ?;
5964 print_notifications ( "history" , & notifications, allow_full) ;
6065 }
6166 Command :: Dnd { state } => match state {
6267 DndState :: On => {
6368 // Explicit enable avoids ambiguous scripts.
64- proxy. set_dnd ( true ) . await ?;
69+ run_control_call ( proxy. set_dnd ( true ) ) . await ?;
6570 }
6671 DndState :: Off => {
6772 // Explicit disable avoids ambiguous scripts.
68- proxy. set_dnd ( false ) . await ?;
73+ run_control_call ( proxy. set_dnd ( false ) ) . await ?;
6974 }
7075 DndState :: Toggle => {
7176 // Toggle must happen atomically in the daemon to avoid read-modify-write races.
72- proxy. toggle_dnd ( ) . await ?;
77+ run_control_call ( proxy. toggle_dnd ( ) ) . await ?;
7378 }
7479 } ,
7580 Command :: Inhibit { reason, scope } => {
7681 // Print the token only
77- let token = proxy. inhibit ( & reason, scope. as_scope ( ) ) . await ?;
82+ let token = run_control_call ( proxy. inhibit ( & reason, scope. as_scope ( ) ) ) . await ?;
7883 println ! ( "{token}" ) ;
7984 }
8085 Command :: Uninhibit { id } => {
8186 // Token removal is safe to repeat if a previous call already released it.
82- proxy. uninhibit ( id) . await ?;
87+ run_control_call ( proxy. uninhibit ( id) ) . await ?;
8388 }
8489 Command :: ListInhibitors => {
85- let inhibitors = proxy. list_inhibitors ( ) . await ?;
90+ let inhibitors = run_control_call ( proxy. list_inhibitors ( ) ) . await ?;
8691 // Shared output helper
8792 print_inhibitors ( & inhibitors) ;
8893 }
@@ -96,3 +101,14 @@ pub(crate) async fn handle_command(proxy: &ControlProxy<'_>, command: Command) -
96101
97102 Ok ( ( ) )
98103}
104+
105+ async fn run_control_call < T > ( call : impl Future < Output = zbus:: Result < T > > ) -> Result < T > {
106+ match tokio:: time:: timeout ( CONTROL_CALL_TIMEOUT , call) . await {
107+ Ok ( Ok ( value) ) => Ok ( value) ,
108+ Ok ( Err ( err) ) => Err ( err. into ( ) ) ,
109+ Err ( _) => Err ( anyhow ! (
110+ "timed out waiting for unixnotis daemon response after {}s" ,
111+ CONTROL_CALL_TIMEOUT . as_secs( )
112+ ) ) ,
113+ }
114+ }
0 commit comments