process_list::View done
This commit is contained in:
@@ -429,7 +429,7 @@ impl Frontend {
|
||||
}
|
||||
|
||||
fn action_recenter_playspace(&mut self) -> anyhow::Result<()> {
|
||||
log::info!("todo");
|
||||
self.interface.recenter_playspace()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use wgui::{
|
||||
|
||||
use crate::{
|
||||
tab::{Tab, TabParams, TabType, TabUpdateParams},
|
||||
views::display_list,
|
||||
views::{display_list, process_list},
|
||||
};
|
||||
|
||||
pub struct TabProcesses {
|
||||
@@ -13,6 +13,7 @@ pub struct TabProcesses {
|
||||
pub state: ParserState,
|
||||
|
||||
view_display_list: display_list::View,
|
||||
view_process_list: process_list::View,
|
||||
}
|
||||
|
||||
impl Tab for TabProcesses {
|
||||
@@ -22,6 +23,7 @@ impl Tab for TabProcesses {
|
||||
|
||||
fn update(&mut self, params: TabUpdateParams) -> anyhow::Result<()> {
|
||||
self.view_display_list.update(params.layout, params.interface)?;
|
||||
self.view_process_list.update(params.layout, params.interface)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -45,6 +47,12 @@ impl TabProcesses {
|
||||
globals: params.globals.clone(),
|
||||
frontend_tasks: params.frontend_tasks.clone(),
|
||||
})?,
|
||||
view_process_list: process_list::View::new(process_list::Params {
|
||||
layout: params.layout,
|
||||
parent_id: state.get_widget_id("process_list_parent")?,
|
||||
globals: params.globals.clone(),
|
||||
frontend_tasks: params.frontend_tasks.clone(),
|
||||
})?,
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
use gio::prelude::{AppInfoExt, IconExt};
|
||||
use gtk::traits::IconThemeExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// compatibility with wayvr-ipc
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct DesktopFile {
|
||||
pub name: String,
|
||||
pub icon: Option<String>,
|
||||
pub exec_path: String,
|
||||
pub exec_args: Vec<String>,
|
||||
pub categories: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)] // TODO: remove this
|
||||
|
||||
@@ -2,3 +2,4 @@ pub mod desktop_finder;
|
||||
pub mod pactl_wrapper;
|
||||
pub mod popup_manager;
|
||||
pub mod toast_manager;
|
||||
pub mod various;
|
||||
|
||||
93
dash-frontend/src/util/various.rs
Normal file
93
dash-frontend/src/util/various.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use wayvr_ipc::packet_server;
|
||||
use wgui::{
|
||||
assets::{AssetPath, AssetPathOwned},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
|
||||
taffy::{self, prelude::length},
|
||||
widget::{
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
sprite::{WidgetSprite, WidgetSpriteParams},
|
||||
},
|
||||
};
|
||||
use wlx_common::dash_interface::BoxDashInterface;
|
||||
|
||||
use crate::util::desktop_finder;
|
||||
|
||||
pub fn get_desktop_file_icon_path(desktop_file: &desktop_finder::DesktopFile) -> AssetPathOwned {
|
||||
/*
|
||||
FIXME: why is the compiler complaining about trailing irrefutable patterns there?!?!
|
||||
looking at the PathBuf::from_str implementation, it always returns Ok() and it's inline, maybe that's why.
|
||||
*/
|
||||
if let Some(icon) = &desktop_file.icon
|
||||
&& let Ok(path) = PathBuf::from_str(icon)
|
||||
{
|
||||
return AssetPathOwned::File(path);
|
||||
}
|
||||
|
||||
AssetPathOwned::BuiltIn(PathBuf::from_str("dashboard/terminal.svg").unwrap())
|
||||
}
|
||||
|
||||
pub fn get_all_windows(interface: &mut BoxDashInterface) -> anyhow::Result<Vec<packet_server::WvrWindow>> {
|
||||
let mut windows = Vec::<packet_server::WvrWindow>::new();
|
||||
|
||||
for display in interface.display_list()? {
|
||||
let Ok(window_list) = interface.display_window_list(display.handle) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for window in window_list {
|
||||
windows.push(window)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(windows)
|
||||
}
|
||||
|
||||
pub fn mount_simple_label(
|
||||
globals: &WguiGlobals,
|
||||
layout: &mut Layout,
|
||||
parent_id: WidgetID,
|
||||
translation: Translation,
|
||||
) -> anyhow::Result<()> {
|
||||
layout.add_child(
|
||||
parent_id,
|
||||
WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: translation,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
taffy::Style::default(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mount_simple_sprite_square(
|
||||
globals: &WguiGlobals,
|
||||
layout: &mut Layout,
|
||||
parent_id: WidgetID,
|
||||
size_px: f32,
|
||||
path: AssetPath,
|
||||
) -> anyhow::Result<()> {
|
||||
layout.add_child(
|
||||
parent_id,
|
||||
WidgetSprite::create(WidgetSpriteParams {
|
||||
glyph_data: Some(CustomGlyphData::new(CustomGlyphContent::from_assets(globals, path)?)),
|
||||
..Default::default()
|
||||
}),
|
||||
taffy::Style {
|
||||
size: taffy::Size {
|
||||
width: length(size_px),
|
||||
height: length(size_px),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,3 +3,4 @@ pub mod app_launcher;
|
||||
pub mod audio_settings;
|
||||
pub mod display_list;
|
||||
pub mod display_options;
|
||||
pub mod process_list;
|
||||
|
||||
310
dash-frontend/src/views/process_list.rs
Normal file
310
dash-frontend/src/views/process_list.rs
Normal file
@@ -0,0 +1,310 @@
|
||||
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},
|
||||
widget::{
|
||||
ConstructEssentials,
|
||||
div::WidgetDiv,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
},
|
||||
};
|
||||
use wlx_common::dash_interface::BoxDashInterface;
|
||||
|
||||
use crate::{
|
||||
frontend::FrontendTasks,
|
||||
task::Tasks,
|
||||
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 frontend_tasks: FrontendTasks,
|
||||
pub layout: &'a mut Layout,
|
||||
pub parent_id: WidgetID,
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
#[allow(dead_code)]
|
||||
pub parser_state: ParserState,
|
||||
tasks: Tasks<Task>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
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(¶ms.layout.state, "list_parent")?;
|
||||
|
||||
let tasks = Tasks::new();
|
||||
|
||||
tasks.push(Task::Refresh);
|
||||
|
||||
Ok(Self {
|
||||
parser_state,
|
||||
tasks,
|
||||
frontend_tasks: params.frontend_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.terminate_process(interface, process)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_desktop_file_from_process(
|
||||
windows: &Vec<packet_server::WvrWindow>,
|
||||
process: &packet_server::WvrProcess,
|
||||
) -> Option<desktop_finder::DesktopFile> {
|
||||
for window in windows {
|
||||
if window.process_handle != process.handle {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: refactor this after we ditch wayvr-ipc completely
|
||||
let Some(dfile_str) = process.userdata.get("desktop_file") else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(desktop_file) = serde_json::from_str::<desktop_finder::DesktopFile>(dfile_str) else {
|
||||
debug_assert!(false); // invalid json???
|
||||
continue;
|
||||
};
|
||||
|
||||
return Some(desktop_file);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
struct ProcessEntryResult {
|
||||
btn_terminate: Rc<ComponentButton>,
|
||||
}
|
||||
|
||||
fn construct_process_entry(
|
||||
ess: &mut ConstructEssentials,
|
||||
globals: &WguiGlobals,
|
||||
process: &packet_server::WvrProcess,
|
||||
display: Option<&packet_server::WvrDisplay>,
|
||||
all_windows: &Vec<packet_server::WvrWindow>,
|
||||
) -> 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(all_windows, 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()),
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(display) = display {
|
||||
// show display icon if available
|
||||
|
||||
// "on" text
|
||||
util::various::mount_simple_label(
|
||||
globals,
|
||||
ess.layout,
|
||||
cell.id,
|
||||
Translation::from_translation_key("PROCESS_LIST.LOCATED_ON"),
|
||||
)?;
|
||||
|
||||
// "display" icon
|
||||
util::various::mount_simple_sprite_square(
|
||||
globals,
|
||||
ess.layout,
|
||||
cell.id,
|
||||
24.0,
|
||||
AssetPath::BuiltIn("dashboard/display.svg"),
|
||||
)?;
|
||||
|
||||
// display name itself
|
||||
util::various::mount_simple_label(
|
||||
globals,
|
||||
ess.layout,
|
||||
cell.id,
|
||||
Translation::from_raw_text_string(display.name.clone()),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(ProcessEntryResult { btn_terminate })
|
||||
}
|
||||
|
||||
fn fill_process_list(
|
||||
globals: &WguiGlobals,
|
||||
ess: &mut ConstructEssentials,
|
||||
tasks: &Tasks<Task>,
|
||||
list: &Vec<packet_server::WvrProcess>,
|
||||
displays: &Vec<packet_server::WvrDisplay>,
|
||||
all_windows: &Vec<packet_server::WvrWindow>,
|
||||
) -> anyhow::Result<()> {
|
||||
for process_entry in list {
|
||||
let mut matching_display = None;
|
||||
for display in displays {
|
||||
if process_entry.display_handle == display.handle {
|
||||
matching_display = Some(display);
|
||||
}
|
||||
}
|
||||
|
||||
let entry_res = construct_process_entry(ess, globals, process_entry, matching_display, all_windows)?;
|
||||
|
||||
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 displays = interface.display_list()?;
|
||||
let all_windows = util::various::get_all_windows(interface)?;
|
||||
|
||||
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,
|
||||
&displays,
|
||||
&all_windows,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
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 terminate_process(
|
||||
&mut self,
|
||||
interface: &mut BoxDashInterface,
|
||||
process: packet_server::WvrProcess,
|
||||
) -> anyhow::Result<()> {
|
||||
interface.process_terminate(process.handle)?;
|
||||
self.tasks.push(Task::Refresh);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user