App launcher

[skip ci]
This commit is contained in:
Aleksander
2025-12-23 19:17:51 +01:00
parent 1b4c2a9006
commit 2d40b8ac00
18 changed files with 400 additions and 84 deletions
+242 -5
View File
@@ -1,27 +1,71 @@
use std::{collections::HashMap, rc::Rc};
use anyhow::Context;
use wayvr_ipc::{packet_client::WvrProcessLaunchParams, packet_server::WvrDisplayHandle};
use wgui::{
assets::AssetPath,
components::checkbox::ComponentCheckbox,
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState},
widget::label::WidgetLabel,
};
use wlx_common::dash_interface::BoxDashInterface;
use crate::util::desktop_finder::DesktopEntry;
use crate::{
frontend::{FrontendTask, FrontendTasks},
settings::SettingsIO,
task::Tasks,
util::desktop_finder::DesktopEntry,
views::display_list,
};
#[derive(Clone, Eq, PartialEq)]
enum RunMode {
Cage,
Wayland,
}
enum Task {
SetRunMode(RunMode),
DisplayClick(WvrDisplayHandle),
}
struct LaunchParams<'a> {
display_handle: WvrDisplayHandle,
application: &'a DesktopEntry,
run_mode: RunMode,
globals: &'a WguiGlobals,
frontend_tasks: &'a FrontendTasks,
interface: &'a mut BoxDashInterface,
on_launched: &'a dyn Fn(),
}
pub struct View {
#[allow(dead_code)]
pub state: ParserState,
//entry: DesktopEntry,
state: ParserState,
entry: DesktopEntry,
view_display_list: display_list::View,
tasks: Tasks<Task>,
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
cb_cage_mode: Rc<ComponentCheckbox>,
cb_wayland_mode: Rc<ComponentCheckbox>,
run_mode: RunMode,
on_launched: Box<dyn Fn()>,
}
pub struct Params<'a> {
pub globals: WguiGlobals,
pub globals: &'a WguiGlobals,
pub entry: DesktopEntry,
pub layout: &'a mut Layout,
pub parent_id: WidgetID,
pub settings: &'a dyn SettingsIO,
pub frontend_tasks: &'a FrontendTasks,
pub on_launched: Box<dyn Fn()>,
}
impl View {
@@ -33,6 +77,44 @@ impl View {
};
let mut state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
let cb_cage_mode = state.fetch_component_as::<ComponentCheckbox>("cb_cage_mode")?;
let cb_wayland_mode = state.fetch_component_as::<ComponentCheckbox>("cb_wayland_mode")?;
{
let mut label_exec = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_exec")?;
let mut label_args = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_args")?;
label_exec.set_text_simple(
&mut params.globals.get(),
Translation::from_raw_text_string(params.entry.app_name.clone()),
);
label_args.set_text_simple(
&mut params.globals.get(),
Translation::from_raw_text_string(params.entry.exec_args.join(" ")),
);
}
let display_list_parent = state.fetch_widget(&params.layout.state, "display_list_parent")?.id;
let tasks = Tasks::new();
let on_display_click = {
let tasks = tasks.clone();
Box::new(move |disp_handle: WvrDisplayHandle| {
tasks.push(Task::DisplayClick(disp_handle));
})
};
let view_display_list = display_list::View::new(display_list::Params {
frontend_tasks: params.frontend_tasks.clone(),
globals: params.globals,
layout: params.layout,
parent_id: display_list_parent,
on_click: Some(on_display_click),
})?;
let id_icon_parent = state.get_widget_id("icon_parent")?;
// app icon
@@ -48,6 +130,30 @@ impl View {
)?;
}
let run_mode = if params.settings.get().tweaks.xwayland_by_default {
RunMode::Cage
} else {
RunMode::Wayland
};
tasks.push(Task::SetRunMode(run_mode.clone()));
cb_cage_mode.on_toggle({
let tasks = tasks.clone();
Box::new(move |_, _| {
tasks.push(Task::SetRunMode(RunMode::Cage));
Ok(())
})
});
cb_wayland_mode.on_toggle({
let tasks = tasks.clone();
Box::new(move |_, _| {
tasks.push(Task::SetRunMode(RunMode::Wayland));
Ok(())
})
});
let mut label_title = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_title")?;
label_title.set_text_simple(
@@ -56,8 +162,139 @@ impl View {
);
Ok(Self {
//entry: params.entry,
state,
view_display_list,
tasks,
cb_cage_mode,
cb_wayland_mode,
run_mode,
entry: params.entry,
frontend_tasks: params.frontend_tasks.clone(),
globals: params.globals.clone(),
on_launched: params.on_launched,
})
}
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::SetRunMode(run_mode) => self.action_set_run_mode(layout, run_mode)?,
Task::DisplayClick(disp_handle) => self.action_display_click(disp_handle, interface),
}
}
}
self.view_display_list.update(layout, interface)?;
Ok(())
}
fn action_set_run_mode(&mut self, layout: &mut Layout, run_mode: RunMode) -> anyhow::Result<()> {
let (n1, n2) = match run_mode {
RunMode::Cage => (true, false),
RunMode::Wayland => (false, true),
};
let mut c = layout.start_common();
self.cb_cage_mode.set_checked(&mut c.common(), n1);
self.cb_wayland_mode.set_checked(&mut c.common(), n2);
c.finish()?;
Ok(())
}
fn action_display_click(&mut self, handle: WvrDisplayHandle, interface: &mut BoxDashInterface) {
View::try_launch(LaunchParams {
application: &self.entry,
display_handle: handle,
frontend_tasks: &self.frontend_tasks,
globals: &self.globals,
run_mode: self.run_mode.clone(),
interface,
on_launched: &self.on_launched,
});
}
fn try_launch(params: LaunchParams) {
let globals = params.globals.clone();
let frontend_tasks = params.frontend_tasks.clone();
// launch app itself
let Err(e) = View::launch(params) else { return };
let str_failed = globals.i18n().translate("FAILED_TO_LAUNCH_APPLICATION");
frontend_tasks.push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
"{} {:?}",
str_failed, e
))));
}
fn launch(params: LaunchParams) -> anyhow::Result<()> {
let mut env = Vec::<String>::new();
if params.run_mode == RunMode::Wayland {
// This list could be larger, feel free to expand it
env.push("QT_QPA_PLATFORM=wayland".into());
env.push("GDK_BACKEND=wayland".into());
env.push("SDL_VIDEODRIVER=wayland".into());
env.push("XDG_SESSION_TYPE=wayland".into());
env.push("ELECTRON_OZONE_PLATFORM_HINT=wayland".into());
}
// TODO: refactor this after we ditch old wayvr-dashboard completely
let desktop_file = params.application.to_desktop_file();
let mut userdata = HashMap::<String, String>::new();
userdata.insert("desktop_file".into(), serde_json::to_string(&desktop_file)?);
let exec_args_str = desktop_file.exec_args.join(" ");
params
.interface
.display_set_visible(params.display_handle.clone(), true)?;
let args = match params.run_mode {
RunMode::Cage => format!("-- {} {}", desktop_file.exec_path, exec_args_str),
RunMode::Wayland => exec_args_str,
};
let exec = match params.run_mode {
RunMode::Cage => "cage",
RunMode::Wayland => &desktop_file.name,
};
let display = params
.interface
.display_get(params.display_handle.clone())
.context("Display not found")?;
params.interface.process_launch(WvrProcessLaunchParams {
env,
exec: String::from(exec),
name: desktop_file.name,
target_display: params.display_handle,
args,
userdata,
})?;
let str_launched_on = params
.globals
.i18n()
.translate_and_replace("APPLICATION_LAUNCHED_ON", ("{DISPLAY_NAME}", &display.name));
params
.frontend_tasks
.push(FrontendTask::PushToast(Translation::from_raw_text_string(
str_launched_on,
)));
(*params.on_launched)();
// we're done!
Ok(())
}
}
+43 -36
View File
@@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc};
use wayvr_ipc::{
packet_client::{self},
packet_server::{self},
packet_server::{self, WvrDisplayHandle},
};
use wgui::{
assets::AssetPath,
@@ -31,16 +31,17 @@ use crate::{
enum Task {
AddDisplay,
AddDisplayFinish(add_display::Result),
DisplayOptions(packet_server::WvrDisplay),
DisplayClicked(packet_server::WvrDisplay),
DisplayOptionsFinish,
Refresh,
}
pub struct Params<'a> {
pub globals: WguiGlobals,
pub globals: &'a WguiGlobals,
pub frontend_tasks: FrontendTasks,
pub layout: &'a mut Layout,
pub parent_id: WidgetID,
pub on_click: Option<Box<dyn Fn(WvrDisplayHandle)>>,
}
struct State {
@@ -56,6 +57,7 @@ pub struct View {
globals: WguiGlobals,
state: Rc<RefCell<State>>,
id_list_parent: WidgetID,
on_click: Option<Box<dyn Fn(WvrDisplayHandle)>>,
}
impl View {
@@ -84,9 +86,10 @@ impl View {
parser_state,
tasks,
frontend_tasks: params.frontend_tasks,
globals: params.globals,
globals: params.globals.clone(),
state,
id_list_parent: list_parent.id,
on_click: params.on_click,
})
}
@@ -98,11 +101,11 @@ impl View {
}
for task in tasks {
match task {
Task::AddDisplay => self.add_display(),
Task::AddDisplayFinish(result) => self.add_display_finish(interface, result)?,
Task::DisplayOptionsFinish => self.display_options_finish(),
Task::AddDisplay => self.action_add_display(),
Task::AddDisplayFinish(result) => self.action_add_display_finish(interface, result)?,
Task::DisplayOptionsFinish => self.action_display_options_finish(),
Task::Refresh => self.refresh(layout, interface)?,
Task::DisplayOptions(display) => self.display_options(display)?,
Task::DisplayClicked(display) => self.action_display_clicked(display)?,
}
}
}
@@ -189,7 +192,7 @@ fn fill_display_list(
button.on_click({
let tasks = tasks.clone();
Box::new(move |_, _| {
tasks.push(Task::DisplayOptions(entry.clone()));
tasks.push(Task::DisplayClicked(entry.clone()));
Ok(())
})
});
@@ -199,7 +202,7 @@ fn fill_display_list(
}
impl View {
fn add_display(&mut self) {
fn action_add_display(&mut self) {
self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
title: Translation::from_translation_key("ADD_DISPLAY"),
on_content: {
@@ -230,7 +233,7 @@ impl View {
}));
}
fn add_display_finish(
fn action_add_display_finish(
&mut self,
interface: &mut BoxDashInterface,
result: add_display::Result,
@@ -246,7 +249,7 @@ impl View {
Ok(())
}
fn display_options_finish(&mut self) {
fn action_display_options_finish(&mut self) {
self.state.borrow_mut().view_display_options = None;
self.tasks.push(Task::Refresh);
}
@@ -291,31 +294,35 @@ impl View {
Ok(())
}
fn display_options(&mut self, display: packet_server::WvrDisplay) -> anyhow::Result<()> {
self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
title: Translation::from_translation_key("DISPLAY_OPTIONS"),
on_content: {
let frontend_tasks = self.frontend_tasks.clone();
let globals = self.globals.clone();
let state = self.state.clone();
let tasks = self.tasks.clone();
fn action_display_clicked(&mut self, display: packet_server::WvrDisplay) -> anyhow::Result<()> {
if let Some(on_click) = &mut self.on_click {
(*on_click)(display.handle);
} else {
self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
title: Translation::from_translation_key("DISPLAY_OPTIONS"),
on_content: {
let frontend_tasks = self.frontend_tasks.clone();
let globals = self.globals.clone();
let state = self.state.clone();
let tasks = self.tasks.clone();
Rc::new(move |data| {
state.borrow_mut().view_display_options = Some((
data.handle,
display_options::View::new(display_options::Params {
globals: globals.clone(),
layout: data.layout,
parent_id: data.id_content,
on_submit: tasks.make_callback(Task::DisplayOptionsFinish),
display: display.clone(),
frontend_tasks: frontend_tasks.clone(),
})?,
));
Ok(())
})
},
}));
Rc::new(move |data| {
state.borrow_mut().view_display_options = Some((
data.handle,
display_options::View::new(display_options::Params {
globals: globals.clone(),
layout: data.layout,
parent_id: data.id_content,
on_submit: tasks.make_callback(Task::DisplayOptionsFinish),
display: display.clone(),
frontend_tasks: frontend_tasks.clone(),
})?,
));
Ok(())
})
},
}));
}
Ok(())
}
+3 -3
View File
@@ -82,7 +82,7 @@ impl View {
for task in tasks {
match task {
Task::Refresh => self.refresh(layout, interface)?,
Task::TerminateProcess(process) => self.terminate_process(interface, process)?,
Task::TerminateProcess(process) => self.action_terminate_process(interface, process)?,
}
}
}
@@ -100,7 +100,7 @@ fn get_desktop_file_from_process(
continue;
}
// TODO: refactor this after we ditch wayvr-ipc completely
// TODO: refactor this after we ditch old wayvr-dashboard completely
let Some(dfile_str) = process.userdata.get("desktop_file") else {
continue;
};
@@ -294,7 +294,7 @@ impl View {
Ok(())
}
fn terminate_process(
fn action_terminate_process(
&mut self,
interface: &mut BoxDashInterface,
process: packet_server::WvrProcess,