From 2d481df23798e89a87972ad39147befa0cbc2480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Gon=C3=A7alves?= Date: Wed, 30 Jul 2025 07:42:27 -0300 Subject: [PATCH] Adds the option to enable buttons for logout, reboot, and shutdown. When using KWin, it also includes a built-in feature similar to xkill, which changes the mouse cursor to an X that, when clicking on a window, forcefully closes the application. --- data/net.nokyan.Resources.gschema.xml.in | 16 +++ data/resources/ui/dialogs/settings_dialog.ui | 30 +++++ data/resources/ui/pages/applications.ui | 101 ++++++++++++--- data/resources/ui/pages/processes.ui | 127 ++++++++++++++----- src/application.rs | 45 ++++++- src/ui/dialogs/settings_dialog.rs | 48 ++++++- src/ui/pages/applications/mod.rs | 40 +++++- src/ui/pages/processes/mod.rs | 44 ++++++- src/ui/window.rs | 97 +++++++++++++- src/utils/app.rs | 7 + src/utils/process.rs | 116 ++++++++++++++++- src/utils/settings.rs | 6 +- 12 files changed, 616 insertions(+), 61 deletions(-) diff --git a/data/net.nokyan.Resources.gschema.xml.in b/data/net.nokyan.Resources.gschema.xml.in index 59062a61..feb0f516 100644 --- a/data/net.nokyan.Resources.gschema.xml.in +++ b/data/net.nokyan.Resources.gschema.xml.in @@ -209,5 +209,21 @@ false Whether to show niceness values for CPU priorities + + false + Show logout button in interface + + + false + Show reboot button in interface + + + false + Show shutdown button in interface + + + false + Show kill window button in interface when KWin is detected + \ No newline at end of file diff --git a/data/resources/ui/dialogs/settings_dialog.ui b/data/resources/ui/dialogs/settings_dialog.ui index 5f75246b..34b5dec0 100644 --- a/data/resources/ui/dialogs/settings_dialog.ui +++ b/data/resources/ui/dialogs/settings_dialog.ui @@ -114,6 +114,36 @@ + + + System Action Buttons + Control the visibility of system action buttons in the interface + + + Show Kill Window Button + Display kill window button when KWin is detected + + + + + Show Logout Button + Display logout button in the interface + + + + + Show Reboot Button + Display reboot button in the interface + + + + + Show Shutdown Button + Display shutdown button in the interface + + + + diff --git a/data/resources/ui/pages/applications.ui b/data/resources/ui/pages/applications.ui index 786eebce..361b25cd 100644 --- a/data/resources/ui/pages/applications.ui +++ b/data/resources/ui/pages/applications.ui @@ -79,28 +79,93 @@ 16 true 16 - end - - info-symbolic - false - Show App Information - - Show App Information - - + + 16 + start + + + window-close-symbolic + Kill a Window + app.kill-window + False + + Kill a Window + + + + + + + system-log-out-symbolic + Logout + app.logout + + Logout + + + + + + + system-reboot-symbolic + Reboot + app.reboot + + Reboot + + + + + + + system-shutdown-symbolic + Shutdown + app.shutdown + + Shutdown + + + + - - End App - end_app_menu - false - + + 16 + end + true + + + info-symbolic + false + Show App Information + + Show App Information + + + + + + + End App + end_app_menu + false + + + diff --git a/data/resources/ui/pages/processes.ui b/data/resources/ui/pages/processes.ui index 1a2aeb52..d0af2b39 100644 --- a/data/resources/ui/pages/processes.ui +++ b/data/resources/ui/pages/processes.ui @@ -125,41 +125,106 @@ 16 true 16 - end - - options-symbolic - false - Show Process Options - - Show Process Options - - - - - - - info-symbolic - false - Show Process Information - - Show Process Information - - + + 16 + start + + + window-close-symbolic + Kill a Window + app.kill-window + False + + Kill a Window + + + + + + + system-log-out-symbolic + Logout + app.logout + + Logout + + + + + + + system-reboot-symbolic + Reboot + app.reboot + + Reboot + + + + + + + system-shutdown-symbolic + Shutdown + app.shutdown + + Shutdown + + + + - - End Process - end_process_menu - false - + + 16 + end + true + + + options-symbolic + false + Show Process Options + + Show Process Options + + + + + + + info-symbolic + false + Show Process Information + + Show Process Information + + + + + + + End Process + end_process_menu + false + + + diff --git a/src/application.rs b/src/application.rs index af11c1fd..8e1d462d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -212,6 +212,48 @@ impl Application { } )); self.add_action(&action_process_options); + + // Kill Window if using kwin + let action_kill_window = gio::SimpleAction::new("kill-window", None); + action_kill_window.connect_activate(clone!( + #[weak(rename_to = this)] + self, + move |_, _| { + this.main_window().shortcut_kill_window(); + } + )); + self.add_action(&action_kill_window); + + // System actions with confirmation dialogs + let action_logout = gio::SimpleAction::new("logout", None); + action_logout.connect_activate(clone!( + #[weak(rename_to = this)] + self, + move |_, _| { + this.main_window().shortcut_logout(); + } + )); + self.add_action(&action_logout); + + let action_reboot = gio::SimpleAction::new("reboot", None); + action_reboot.connect_activate(clone!( + #[weak(rename_to = this)] + self, + move |_, _| { + this.main_window().shortcut_reboot(); + } + )); + self.add_action(&action_reboot); + + let action_shutdown = gio::SimpleAction::new("shutdown", None); + action_shutdown.connect_activate(clone!( + #[weak(rename_to = this)] + self, + move |_, _| { + this.main_window().shortcut_shutdown(); + } + )); + self.add_action(&action_shutdown); } // Sets up keyboard shortcuts @@ -250,7 +292,8 @@ impl Application { let settings = ResSettingsDialog::new(); - settings.init(); + let kwin_running = self.main_window().is_kwin_running(); + settings.init(kwin_running); settings.present(Some(&self.main_window())); imp.settings_window_opened.set(true); diff --git a/src/ui/dialogs/settings_dialog.rs b/src/ui/dialogs/settings_dialog.rs index a6469ffd..f33ffc92 100644 --- a/src/ui/dialogs/settings_dialog.rs +++ b/src/ui/dialogs/settings_dialog.rs @@ -38,6 +38,16 @@ mod imp { #[template_child] pub normalize_cpu_usage_row: TemplateChild, + // System action buttons + #[template_child] + pub show_kill_window_button_row: TemplateChild, + #[template_child] + pub show_logout_button_row: TemplateChild, + #[template_child] + pub show_reboot_button_row: TemplateChild, + #[template_child] + pub show_shutdown_button_row: TemplateChild, + #[template_child] pub apps_show_memory_row: TemplateChild, #[template_child] @@ -163,12 +173,12 @@ impl ResSettingsDialog { glib::Object::new::() } - pub fn init(&self) { - self.setup_widgets(); + pub fn init(&self, kwin_running: bool) { + self.setup_widgets(kwin_running); self.setup_signals(); } - pub fn setup_widgets(&self) { + pub fn setup_widgets(&self, kwin_running: bool) { trace!("Setting up ResSettingsDialog widgets…"); let imp = self.imp(); @@ -192,6 +202,17 @@ impl ResSettingsDialog { imp.normalize_cpu_usage_row .set_active(SETTINGS.normalize_cpu_usage()); + // System action buttons + imp.show_kill_window_button_row + .set_active(SETTINGS.show_kill_window_button()); + imp.show_kill_window_button_row.set_visible(kwin_running); + imp.show_logout_button_row + .set_active(SETTINGS.show_logout_button()); + imp.show_reboot_button_row + .set_active(SETTINGS.show_reboot_button()); + imp.show_shutdown_button_row + .set_active(SETTINGS.show_shutdown_button()); + imp.apps_show_memory_row .set_active(SETTINGS.apps_show_memory()); imp.apps_show_cpu_row.set_active(SETTINGS.apps_show_cpu()); @@ -321,6 +342,27 @@ impl ResSettingsDialog { let _ = SETTINGS.set_normalize_cpu_usage(switch_row.is_active()); }); + // System action buttons + imp.show_kill_window_button_row + .connect_active_notify(|switch_row| { + let _ = SETTINGS.set_show_kill_window_button(switch_row.is_active()); + }); + + imp.show_logout_button_row + .connect_active_notify(|switch_row| { + let _ = SETTINGS.set_show_logout_button(switch_row.is_active()); + }); + + imp.show_reboot_button_row + .connect_active_notify(|switch_row| { + let _ = SETTINGS.set_show_reboot_button(switch_row.is_active()); + }); + + imp.show_shutdown_button_row + .connect_active_notify(|switch_row| { + let _ = SETTINGS.set_show_shutdown_button(switch_row.is_active()); + }); + imp.apps_show_cpu_row.connect_active_notify(|switch_row| { let _ = SETTINGS.set_apps_show_cpu(switch_row.is_active()); }); diff --git a/src/ui/pages/applications/mod.rs b/src/ui/pages/applications/mod.rs index 9045be12..b8b47315 100644 --- a/src/ui/pages/applications/mod.rs +++ b/src/ui/pages/applications/mod.rs @@ -58,6 +58,14 @@ mod imp { #[template_child] pub applications_scrolled_window: TemplateChild, #[template_child] + pub kill_window_button: TemplateChild, + #[template_child] + pub logout_button: TemplateChild, + #[template_child] + pub reboot_button: TemplateChild, + #[template_child] + pub shutdown_button: TemplateChild, + #[template_child] pub information_button: TemplateChild, #[template_child] pub end_application_button: TemplateChild, @@ -125,6 +133,10 @@ mod imp { info_dialog_closed: Default::default(), sender: Default::default(), applications_scrolled_window: Default::default(), + kill_window_button: Default::default(), + logout_button: Default::default(), + reboot_button: Default::default(), + shutdown_button: Default::default(), end_application_button: Default::default(), uses_progress_bar: Cell::new(false), icon: RefCell::new(ThemedIcon::new("app-symbolic").into()), @@ -580,9 +592,17 @@ impl ResApplications { .and_then(|object| object.downcast::().ok()) } - pub fn refresh_apps_list(&self, apps_context: &AppsContext) { + pub fn refresh_apps_list(&self, apps_context: &AppsContext, kwin_running: bool) { let imp = self.imp(); + // Update button visibility based on settings + imp.kill_window_button + .set_visible(kwin_running && SETTINGS.show_kill_window_button()); + imp.logout_button.set_visible(SETTINGS.show_logout_button()); + imp.reboot_button.set_visible(SETTINGS.show_reboot_button()); + imp.shutdown_button + .set_visible(SETTINGS.show_shutdown_button()); + if imp.info_dialog_closed.get() { let _ = imp.open_info_dialog.take(); imp.info_dialog_closed.set(false); @@ -1539,6 +1559,10 @@ fn get_action_name(action: ProcessAction, name: &str) -> String { ProcessAction::STOP => i18n_f("Halt {}?", &[name]), ProcessAction::KILL => i18n_f("Kill {}?", &[name]), ProcessAction::CONT => i18n_f("Continue {}?", &[name]), + ProcessAction::KILLWINDOW => i18n("Kill a Window?"), + ProcessAction::LOGOUT => i18n("Logout?"), + ProcessAction::REBOOT => i18n("Reboot?"), + ProcessAction::SHUTDOWN => i18n("Shutdown?"), } } @@ -1552,6 +1576,16 @@ fn get_action_warning(action: ProcessAction) -> String { "Killing an app can come with serious risks such as losing data and security implications. Use with caution.", ), ProcessAction::CONT => String::new(), + ProcessAction::KILLWINDOW => i18n("Click on a window to kill it."), + ProcessAction::LOGOUT => { + i18n("This action will be executed without checking for unsaved files.") + } + ProcessAction::REBOOT => { + i18n("This action will be executed without checking for unsaved files.") + } + ProcessAction::SHUTDOWN => { + i18n("This action will be executed without checking for unsaved files.") + } } } @@ -1561,5 +1595,9 @@ fn get_action_description(action: ProcessAction) -> String { ProcessAction::STOP => i18n("Halt App"), ProcessAction::KILL => i18n("Kill App"), ProcessAction::CONT => i18n("Continue App"), + ProcessAction::KILLWINDOW => i18n("Kill Window"), + ProcessAction::LOGOUT => i18n("Logout"), + ProcessAction::REBOOT => i18n("Reboot"), + ProcessAction::SHUTDOWN => i18n("Shutdown"), } } diff --git a/src/ui/pages/processes/mod.rs b/src/ui/pages/processes/mod.rs index 13b96831..b969ea5a 100644 --- a/src/ui/pages/processes/mod.rs +++ b/src/ui/pages/processes/mod.rs @@ -87,6 +87,14 @@ mod imp { #[template_child] pub processes_scrolled_window: TemplateChild, #[template_child] + pub kill_window_button: TemplateChild, + #[template_child] + pub logout_button: TemplateChild, + #[template_child] + pub reboot_button: TemplateChild, + #[template_child] + pub shutdown_button: TemplateChild, + #[template_child] pub options_button: TemplateChild, #[template_child] pub information_button: TemplateChild, @@ -155,6 +163,10 @@ mod imp { search_bar: Default::default(), search_entry: Default::default(), processes_scrolled_window: Default::default(), + kill_window_button: Default::default(), + logout_button: Default::default(), + reboot_button: Default::default(), + shutdown_button: Default::default(), options_button: Default::default(), information_button: Default::default(), end_process_button: Default::default(), @@ -734,9 +746,17 @@ impl ResProcesses { } } - pub fn refresh_processes_list(&self, apps_context: &AppsContext) { + pub fn refresh_processes_list(&self, apps_context: &AppsContext, kwin_running: bool) { let imp = self.imp(); + // Update button visibility based on settings + imp.kill_window_button + .set_visible(kwin_running && SETTINGS.show_kill_window_button()); + imp.logout_button.set_visible(SETTINGS.show_logout_button()); + imp.reboot_button.set_visible(SETTINGS.show_reboot_button()); + imp.shutdown_button + .set_visible(SETTINGS.show_shutdown_button()); + if imp.info_dialog_closed.get() { let _ = imp.open_info_dialog.take(); imp.info_dialog_closed.set(false); @@ -2073,6 +2093,10 @@ fn get_action_name(action: ProcessAction, name: &str) -> String { ProcessAction::STOP => i18n_f("Halt {}?", &[name]), ProcessAction::KILL => i18n_f("Kill {}?", &[name]), ProcessAction::CONT => i18n_f("Continue {}?", &[name]), + ProcessAction::KILLWINDOW => i18n("Kill a Window?"), + ProcessAction::LOGOUT => i18n("Logout?"), + ProcessAction::REBOOT => i18n("Reboot?"), + ProcessAction::SHUTDOWN => i18n("Shutdown?"), } } @@ -2102,6 +2126,10 @@ fn get_action_name_multiple(action: ProcessAction, count: usize) -> String { count as u32, &[&count.to_string()], ), + ProcessAction::KILLWINDOW => i18n("Kill a Window?"), + ProcessAction::LOGOUT => i18n("Logout?"), + ProcessAction::REBOOT => i18n("Reboot?"), + ProcessAction::SHUTDOWN => i18n("Shutdown?"), } } @@ -2115,6 +2143,16 @@ fn get_action_warning(action: ProcessAction) -> String { "Killing a process can come with serious risks such as losing data and security implications. Use with caution.", ), ProcessAction::CONT => String::new(), + ProcessAction::KILLWINDOW => i18n("Click on a window to kill it."), + ProcessAction::LOGOUT => { + i18n("This action will be executed without checking for unsaved files.") + } + ProcessAction::REBOOT => { + i18n("This action will be executed without checking for unsaved files.") + } + ProcessAction::SHUTDOWN => { + i18n("This action will be executed without checking for unsaved files.") + } } } @@ -2124,5 +2162,9 @@ fn get_action_description(action: ProcessAction) -> String { ProcessAction::STOP => i18n("Halt Process"), ProcessAction::KILL => i18n("Kill Process"), ProcessAction::CONT => i18n("Continue Process"), + ProcessAction::KILLWINDOW => i18n("Kill Window"), + ProcessAction::LOGOUT => i18n("Logout"), + ProcessAction::REBOOT => i18n("Reboot"), + ProcessAction::SHUTDOWN => i18n("Shutdown"), } } diff --git a/src/ui/window.rs b/src/ui/window.rs index c7e627e9..b9bfdc02 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -7,7 +7,7 @@ use adw::{ToolbarView, prelude::*, subclass::prelude::*}; use anyhow::{Context, Result}; use gtk::glib::{GString, MainContext, clone, timeout_future}; use gtk::{Widget, gdk, gio, glib}; -use log::{debug, info, trace, warn}; +use log::{debug, error, info, trace, warn}; use crate::application::Application; use crate::config::PROFILE; @@ -455,6 +455,77 @@ impl MainWindow { } } + pub fn shortcut_kill_window(&self) { + // Simply execute the kill window command directly + match Process::execute_kill_window() { + Ok(()) => info!("Kill Window mode activated"), + Err(e) => error!("Failed to activate Kill Window mode: {}", e), + } + } + + fn show_system_action_confirmation_dialog(&self, action_name: &str, command_description: &str) { + let dialog = adw::AlertDialog::builder() + .heading(format!("Confirm {}", action_name)) + .body(format!( + "Are you sure you want to {}?\n\nThis action will be executed without checking for unsaved files.", + command_description + )) + .build(); + + dialog.add_response("cancel", "Cancel"); + dialog.add_response("confirm", action_name); + dialog.set_response_appearance("confirm", adw::ResponseAppearance::Destructive); + + let action_name_owned = action_name.to_string(); + dialog.connect_response(None, move |_, response| { + if response == "confirm" { + match action_name_owned.as_str() { + "Logout" => { + if let Err(e) = Process::execute_logout() { + error!("Failed to initiate logout: {}", e); + } else { + info!("Logout initiated"); + } + } + "Reboot" => { + if let Err(e) = Process::execute_reboot() { + error!("Failed to initiate reboot: {}", e); + } else { + info!("Reboot initiated"); + } + } + "Shutdown" => { + if let Err(e) = Process::execute_shutdown() { + error!("Failed to initiate shutdown: {}", e); + } else { + info!("Shutdown initiated"); + } + } + _ => {} + } + } + }); + + dialog.present(Some(self)); + } + + pub fn shortcut_logout(&self) { + self.show_system_action_confirmation_dialog("Logout", "log out"); + } + + pub fn shortcut_reboot(&self) { + self.show_system_action_confirmation_dialog("Reboot", "reboot the system"); + } + + pub fn shortcut_shutdown(&self) { + self.show_system_action_confirmation_dialog("Shutdown", "shut down the system"); + } + + pub fn is_kwin_running(&self) -> bool { + let imp = self.imp(); + imp.apps_context.borrow().is_kwin_running() + } + fn init_gpu_pages(self: &MainWindow, gpus: &[Gpu]) { let imp = self.imp(); @@ -637,11 +708,15 @@ impl MainWindow { let mut apps_context = imp.apps_context.borrow_mut(); apps_context.refresh(process_data); + // Check if kwin is running for the kill window button + let kwin_running = apps_context.is_kwin_running(); + // if CTRL is held, don't update apps and processes like Windows Task Manager if !imp.pause_updates.get() { trace!("Skipping visual apps and processes updates"); - imp.apps.refresh_apps_list(&apps_context); - imp.processes.refresh_processes_list(&apps_context); + imp.apps.refresh_apps_list(&apps_context, kwin_running); + imp.processes + .refresh_processes_list(&apps_context, kwin_running); } /* @@ -1250,6 +1325,10 @@ fn get_action_success(action: ProcessAction, name: &str) -> String { ProcessAction::STOP => i18n_f("Successfully halted {}", &[name]), ProcessAction::KILL => i18n_f("Successfully killed {}", &[name]), ProcessAction::CONT => i18n_f("Successfully continued {}", &[name]), + ProcessAction::KILLWINDOW => i18n("Kill Window mode activated"), + ProcessAction::LOGOUT => i18n("Logout initiated"), + ProcessAction::REBOOT => i18n("Reboot initiated"), + ProcessAction::SHUTDOWN => i18n("Shutdown initiated"), } } @@ -1279,6 +1358,10 @@ fn get_processes_success(action: ProcessAction, count: usize) -> String { count as u32, &[&count.to_string()], ), + ProcessAction::KILLWINDOW => i18n("Kill Window mode activated"), + ProcessAction::LOGOUT => i18n("Logout initiated"), + ProcessAction::REBOOT => i18n("Reboot initiated"), + ProcessAction::SHUTDOWN => i18n("Shutdown initiated"), } } @@ -1308,6 +1391,10 @@ fn get_action_failure(action: ProcessAction, count: usize) -> String { count as u32, &[&count.to_string()], ), + ProcessAction::KILLWINDOW => i18n("Failed to activate Kill Window mode"), + ProcessAction::LOGOUT => i18n("Failed to initiate logout"), + ProcessAction::REBOOT => i18n("Failed to initiate reboot"), + ProcessAction::SHUTDOWN => i18n("Failed to initiate shutdown"), } } @@ -1317,5 +1404,9 @@ pub fn get_named_action_failure(action: ProcessAction, name: &str) -> String { ProcessAction::STOP => i18n_f("There was a problem halting {}", &[name]), ProcessAction::KILL => i18n_f("There was a problem killing {}", &[name]), ProcessAction::CONT => i18n_f("There was a problem continuing {}", &[name]), + ProcessAction::KILLWINDOW => i18n("Failed to activate Kill Window mode"), + ProcessAction::LOGOUT => i18n("Failed to initiate logout"), + ProcessAction::REBOOT => i18n("Failed to initiate reboot"), + ProcessAction::SHUTDOWN => i18n("Failed to initiate shutdown"), } } diff --git a/src/utils/app.rs b/src/utils/app.rs index a987364b..dbdcb0de 100644 --- a/src/utils/app.rs +++ b/src/utils/app.rs @@ -762,6 +762,13 @@ impl AppsContext { }) } + /// Check if kwin_wayland or kwin_x11 is running + pub fn is_kwin_running(&self) -> bool { + self.processes_iter().any(|process| { + process.executable_name == "kwin_wayland" || process.executable_name == "kwin_x11" + }) + } + /// Refreshes the statistics about the running applications and processes. pub fn refresh(&mut self, new_process_data: Vec) { trace!("Refreshing AppsContext…"); diff --git a/src/utils/process.rs b/src/utils/process.rs index 690013d5..ed18a3f6 100644 --- a/src/utils/process.rs +++ b/src/utils/process.rs @@ -13,8 +13,8 @@ use std::{ use strum_macros::Display; use gtk::{ - gio::{Icon, ThemedIcon}, - glib::GString, + gio::{DBusCallFlags, DBusProxy, DBusProxyFlags, Icon, ThemedIcon, prelude::DBusProxyExt}, + glib::{GString, Variant}, }; use crate::config; @@ -80,6 +80,10 @@ pub enum ProcessAction { STOP, KILL, CONT, + KILLWINDOW, + LOGOUT, + REBOOT, + SHUTDOWN, } impl Process { @@ -339,6 +343,114 @@ impl Process { } } + pub fn execute_kill_window() -> Result<()> { + debug!("Executing Kill Window command via D-Bus"); + + // Create a D-Bus proxy for the KDE global accelerator service + let proxy = DBusProxy::for_bus_sync( + gtk::gio::BusType::Session, + DBusProxyFlags::NONE, + None, // info + "org.kde.kglobalaccel", + "/component/kwin", + "org.kde.kglobalaccel.Component", + None::<>k::gio::Cancellable>, + ) + .context("Failed to create D-Bus proxy")?; + + // Create the method call parameters + let shortcut_name = Variant::from("Kill Window"); + let parameters = Variant::tuple_from_iter([shortcut_name]); + + // Call the D-Bus method + let result = proxy.call_sync( + "invokeShortcut", + Some(¶meters), + DBusCallFlags::NONE, + -1, + None::<>k::gio::Cancellable>, + ); + + match result { + Ok(_) => { + info!("Successfully invoked Kill Window"); + Ok(()) + } + Err(err) => { + error!("Kill Window D-Bus call failed: {}", err); + bail!("Kill Window D-Bus call failed: {}", err) + } + } + } + + pub fn execute_logout() -> Result<()> { + debug!("Executing logout command"); + let result = Command::new("pkill") + .args([ + "-u", + &std::env::var("USER").unwrap_or_else(|_| "user".to_string()), + ]) + .status(); + + match result { + Ok(status) if status.success() => { + info!("Successfully initiated logout"); + Ok(()) + } + Ok(status) => { + error!("Logout command failed with exit code: {:?}", status.code()); + bail!("Logout command failed") + } + Err(err) => { + error!("Failed to execute logout command: {}", err); + bail!("Failed to execute logout command: {}", err) + } + } + } + + pub fn execute_reboot() -> Result<()> { + debug!("Executing reboot command"); + let result = Command::new("reboot").status(); + + match result { + Ok(status) if status.success() => { + info!("Successfully initiated reboot"); + Ok(()) + } + Ok(status) => { + error!("Reboot command failed with exit code: {:?}", status.code()); + bail!("Reboot command failed") + } + Err(err) => { + error!("Failed to execute reboot command: {}", err); + bail!("Failed to execute reboot command: {}", err) + } + } + } + + pub fn execute_shutdown() -> Result<()> { + debug!("Executing shutdown command"); + let result = Command::new("poweroff").status(); + + match result { + Ok(status) if status.success() => { + info!("Successfully initiated shutdown"); + Ok(()) + } + Ok(status) => { + error!( + "Shutdown command failed with exit code: {:?}", + status.code() + ); + bail!("Shutdown command failed") + } + Err(err) => { + error!("Failed to execute shutdown command: {}", err); + bail!("Failed to execute shutdown command: {}", err) + } + } + } + #[must_use] pub fn cpu_time_ratio(&self) -> f32 { if self.cpu_time_last == 0 { diff --git a/src/utils/settings.rs b/src/utils/settings.rs index 0f16b070..2eaf0fcd 100644 --- a/src/utils/settings.rs +++ b/src/utils/settings.rs @@ -380,7 +380,11 @@ impl Settings { show_logical_cpus, show_graph_grids, normalize_cpu_usage, - detailed_priority + detailed_priority, + show_logout_button, + show_reboot_button, + show_shutdown_button, + show_kill_window_button ); }