use std::rc::Rc; use wayvr_ipc::packet_server::{self}; use wgui::{ assets::AssetPath, components::{ self, button::ComponentButton, tooltip::{TooltipInfo, TooltipSide}, }, globals::WguiGlobals, i18n::Translation, layout::{Layout, WidgetID}, parser::{Fetchable, ParseDocumentParams, ParserState}, taffy::{self, prelude::length}, task::Tasks, widget::{ ConstructEssentials, div::WidgetDiv, label::{WidgetLabel, WidgetLabelParams}, }, }; use wlx_common::dash_interface::BoxDashInterface; use crate::util::{ self, desktop_finder::{self}, various::get_desktop_file_icon_path, }; #[derive(Clone)] enum Task { Refresh, TerminateProcess(packet_server::WvrProcess), } pub struct Params<'a> { pub globals: WguiGlobals, pub layout: &'a mut Layout, pub parent_id: WidgetID, } pub struct View { #[allow(dead_code)] pub parser_state: ParserState, tasks: Tasks, globals: WguiGlobals, id_list_parent: WidgetID, } impl View { pub fn new(params: Params) -> anyhow::Result { let doc_params = &ParseDocumentParams { globals: params.globals.clone(), path: AssetPath::BuiltIn("gui/view/process_list.xml"), extra: Default::default(), }; let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?; let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?; let tasks = Tasks::new(); tasks.push(Task::Refresh); Ok(Self { parser_state, tasks, globals: params.globals, id_list_parent: list_parent.id, }) } pub fn update(&mut self, layout: &mut Layout, interface: &mut BoxDashInterface) -> anyhow::Result<()> { loop { let tasks = self.tasks.drain(); if tasks.is_empty() { break; } for task in tasks { match task { Task::Refresh => self.refresh(layout, interface)?, Task::TerminateProcess(process) => self.action_terminate_process(interface, process)?, } } } Ok(()) } } fn get_desktop_file_from_process(process: &packet_server::WvrProcess) -> Option { // TODO: refactor this after we ditch old wayvr-dashboard completely let Some(dfile_str) = process.userdata.get("desktop_file") else { return None; }; let Ok(desktop_file) = serde_json::from_str::(dfile_str) else { debug_assert!(false); // invalid json??? return None; }; Some(desktop_file) } struct ProcessEntryResult { btn_terminate: Rc, } fn construct_process_entry( ess: &mut ConstructEssentials, globals: &WguiGlobals, process: &packet_server::WvrProcess, ) -> anyhow::Result { let (cell, _) = ess.layout.add_child( ess.parent, WidgetDiv::create(), taffy::Style { flex_direction: taffy::FlexDirection::Row, align_items: Some(taffy::AlignItems::Center), gap: length(8.0), ..Default::default() }, )?; let text_terminate_process = Translation::from_raw_text_string(globals.i18n().translate_and_replace( "PROCESS_LIST.TERMINATE_PROCESS_NAMED_X", ("{PROCESS_NAME}", &process.name), )); //"Terminate process" button let (_, btn_terminate) = components::button::construct( &mut ConstructEssentials { layout: ess.layout, parent: cell.id, }, components::button::Params { sprite_src: Some(AssetPath::BuiltIn("dashboard/remove_circle.svg")), tooltip: Some(TooltipInfo { text: text_terminate_process, side: TooltipSide::Right, }), ..Default::default() }, )?; if let Some(desktop_file) = get_desktop_file_from_process(process) { // desktop file icon and process name util::various::mount_simple_sprite_square( globals, ess.layout, cell.id, 24.0, get_desktop_file_icon_path(&desktop_file).as_ref(), )?; util::various::mount_simple_label( globals, ess.layout, cell.id, Translation::from_raw_text_string(desktop_file.name.clone()), )?; } else { // just show a process name util::various::mount_simple_label( globals, ess.layout, cell.id, Translation::from_raw_text_string(process.name.clone()), )?; } Ok(ProcessEntryResult { btn_terminate }) } fn fill_process_list( globals: &WguiGlobals, ess: &mut ConstructEssentials, tasks: &Tasks, list: &Vec, ) -> anyhow::Result<()> { for process_entry in list { let entry_res = construct_process_entry(ess, globals, process_entry)?; entry_res.btn_terminate.on_click({ let tasks = tasks.clone(); let entry = process_entry.clone(); Box::new(move |_, _| { tasks.push(Task::TerminateProcess(entry.clone())); Ok(()) }) }); } Ok(()) } impl View { fn refresh(&mut self, layout: &mut Layout, interface: &mut BoxDashInterface) -> anyhow::Result<()> { layout.remove_children(self.id_list_parent); let mut text: Option = None; match interface.process_list() { Ok(list) => { if list.is_empty() { text = Some(Translation::from_translation_key("PROCESS_LIST.NO_PROCESSES_FOUND")) } else { fill_process_list( &self.globals, &mut ConstructEssentials { layout, parent: self.id_list_parent, }, &self.tasks, &list, )?; } } Err(e) => text = Some(Translation::from_raw_text(&format!("Error: {:?}", e))), } if let Some(text) = text.take() { layout.add_child( self.id_list_parent, WidgetLabel::create( &mut self.globals.get(), WidgetLabelParams { content: text, ..Default::default() }, ), Default::default(), )?; } Ok(()) } fn action_terminate_process( &mut self, interface: &mut BoxDashInterface, process: packet_server::WvrProcess, ) -> anyhow::Result<()> { interface.process_terminate(process.handle)?; self.tasks.push(Task::Refresh); Ok(()) } }