Files
wayvr/dash-frontend/src/views/process_list.rs
Aleksander 4f7204ccf7 remove RcFrontend & RcLayout
[skip ci]
2025-12-26 12:43:14 +01:00

249 lines
5.6 KiB
Rust

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<Task>,
globals: WguiGlobals,
id_list_parent: WidgetID,
}
impl View {
pub fn new(params: Params) -> anyhow::Result<Self> {
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(&params.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<desktop_finder::DesktopFile> {
// 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::<desktop_finder::DesktopFile>(dfile_str) else {
debug_assert!(false); // invalid json???
return None;
};
Some(desktop_file)
}
struct ProcessEntryResult {
btn_terminate: Rc<ComponentButton>,
}
fn construct_process_entry(
ess: &mut ConstructEssentials,
globals: &WguiGlobals,
process: &packet_server::WvrProcess,
) -> anyhow::Result<ProcessEntryResult> {
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<Task>,
list: &Vec<packet_server::WvrProcess>,
) -> 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<Translation> = 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(())
}
}