bar dropdown backend logic

This commit is contained in:
galister
2026-01-05 20:36:44 +09:00
parent b56aa1a8de
commit ac9bfc9fc4
18 changed files with 476 additions and 185 deletions

View File

@@ -1 +1,2 @@
style_edition = "2024"
edition = "2024"

View File

@@ -3,7 +3,7 @@ use std::ffi::OsStr;
use std::io::Read;
use std::path::{Component, Path, PathBuf};
#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub enum AssetPath<'a> {
WguiInternal(&'a str), // tied to internal wgui AssetProvider. Used internally
BuiltIn(&'a str), // tied to user AssetProvider
@@ -12,7 +12,7 @@ pub enum AssetPath<'a> {
}
// see AssetPath above for documentation
#[derive(Clone)]
#[derive(Debug, Clone)]
pub enum AssetPathOwned {
WguiInternal(PathBuf),
BuiltIn(PathBuf),

View File

@@ -1,27 +1,17 @@
use std::fmt::Debug;
pub trait LogErr {
fn log_err(self) -> Self;
fn log_err_with(self, additional: &str) -> Self;
fn log_warn(self) -> Self;
fn log_warn_with(self, additional: &str) -> Self;
fn log_err(self, additional: &str) -> Self;
fn log_err_with<D: Debug>(self, additional: &D) -> Self;
fn log_warn(self, additional: &str) -> Self;
fn log_warn_with<D: Debug>(self, additional: &D) -> Self;
}
impl<T, E> LogErr for Result<T, E>
where
E: Debug + Send + Sync + 'static,
{
fn log_warn(self) -> Result<T, E> {
match self {
Ok(ok) => Ok(ok),
Err(error) => {
log::warn!("{error:?}");
Err(error)
}
}
}
fn log_warn_with(self, additional: &str) -> Result<T, E> {
fn log_warn(self, additional: &str) -> Result<T, E> {
match self {
Ok(ok) => Ok(ok),
Err(error) => {
@@ -31,17 +21,17 @@ where
}
}
fn log_err(self) -> Result<T, E> {
fn log_warn_with<D: Debug>(self, additional: &D) -> Result<T, E> {
match self {
Ok(ok) => Ok(ok),
Err(error) => {
log::error!("{error:?}");
log::warn!("{additional:?}: {error:?}");
Err(error)
}
}
}
fn log_err_with(self, additional: &str) -> Result<T, E> {
fn log_err(self, additional: &str) -> Result<T, E> {
match self {
Ok(ok) => Ok(ok),
Err(error) => {
@@ -50,4 +40,14 @@ where
}
}
}
fn log_err_with<D: Debug>(self, additional: &D) -> Self {
match self {
Ok(ok) => Ok(ok),
Err(error) => {
log::error!("{additional:?}: {error:?}");
Err(error)
}
}
}
}

View File

@@ -10,16 +10,11 @@ mod widget_rectangle;
mod widget_sprite;
use crate::{
assets::{normalize_path, AssetPath, AssetPathOwned},
components::{Component, ComponentWeak},
drawing::{self},
globals::WguiGlobals,
layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair},
parser::{
assets::{normalize_path, AssetPath, AssetPathOwned}, components::{Component, ComponentWeak}, drawing::{self}, globals::WguiGlobals, layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair}, log::LogErr, parser::{
component_button::parse_component_button, component_checkbox::{parse_component_checkbox, CheckboxKind}, component_radio_group::parse_component_radio_group, component_slider::parse_component_slider, widget_div::parse_widget_div, widget_image::parse_widget_image, widget_label::parse_widget_label, widget_rectangle::parse_widget_rectangle, widget_sprite::parse_widget_sprite
},
widget::ConstructEssentials,
}, widget::ConstructEssentials
};
use anyhow::Context;
use ouroboros::self_referencing;
use smallvec::SmallVec;
use std::{cell::RefMut, collections::HashMap, path::Path, rc::Rc};
@@ -1109,7 +1104,7 @@ fn get_doc_from_asset_path(
allow_dtd: true,
..Default::default()
};
roxmltree::Document::parse_with_options(xml, opt).unwrap()
roxmltree::Document::parse_with_options(xml, opt).context("Unable to parse XML").log_err_with(&asset_path).unwrap()
}));
let root = document.borrow_doc().root();

View File

@@ -13,11 +13,6 @@
width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}"
/>
<macro name="button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="8"
align_items="center" justify_content="center" padding="8" width="80" height="80" />
<macro name="bg_rect" width="100%" color="~color_bg" round="16" border="2" border_color="~color_accent" />
<template name="VerticalSeparator">
<rectangle width="2" height="100%" color="~color_accent" />
</template>
@@ -85,37 +80,91 @@
</div>
</template>
<macro name="button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="8"
align_items="center" justify_content="center" padding="8" width="80" height="80" overflow="visible"/>
<macro name="menu_button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="8"
align_items="center" justify_content="center" padding="8" width="100%" height="60" />
<macro name="bg_rect" width="100%" color="~color_bg" round="16" border="2" border_color="~color_accent" />
<macro name="dropdown_root" new_pass="1" width="200" color="~color_bg" flex_direction="column" position="absolute" margin_top="80" display="none" />
<macro name="dropdown_title" color="~color_faded_50" width="100%" padding="16" overflow="hidden" />
<template name="MenuButton">
<Button macro="menu_button_style" _release="${action}">
<label translation="${translation}" size="24" />
</Button>
</template>
<!-- An app with a single icon. -->
<template name="App">
<Button macro="button_style" id="overlay_${idx}" _short_release="::OverlayToggle ${name}" tooltip="OVERLAY_TOOLTIP.APP;${name}" tooltip_side="bottom">
<sprite width="56" height="56" color="~text_color" src_ext="${icon}" />
</Button>
<div>
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" _release="::ElementSetDisplay dropdown_${idx} none">
<sprite width="56" height="56" color="~text_color" src_ext="${icon}" />
</Button>
<div macro="dropdown_root" id="dropdown_${idx}">
<rectangle macro="dropdown_title">
<label text="${name}" weight="bold" size="24" />
</rectangle>
<MenuButton translation="BAR.TOGGLE_HIDE" action="::OverlaySoftToggle ${name}" />
<MenuButton translation="BAR.TOGGLE_IN_SET" action="::OverlayToggle ${name}" />
<MenuButton translation="BAR.CLOSE" action="::WvrOverlayTermProcess ${name}" />
<MenuButton translation="BAR.FORCE_CLOSE" action="::WvrOverlayKillProcess ${name}" />
</div>
</div>
</template>
<!-- A screen with a shortened connector name, e.g. "H1" for HDMI-A-1 or "D2" for DP-2 -->
<template name="Screen">
<Button macro="button_style" id="overlay_${idx}" _short_release="::OverlayToggle ${name}" tooltip="OVERLAY_TOOLTIP.SCREEN;${name}" tooltip_side="bottom">
<sprite width="56" height="56" color="~text_color" src_builtin="edit/screen.svg" />
<div position="absolute" margin_top="-10">
<label text="${display}" size="26" color="~color_faded_20" weight="bold" />
<div>
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" _release="::ElementSetDisplay dropdown_${idx} none">
<sprite width="56" height="56" color="~text_color" src_builtin="edit/screen.svg" />
<div position="absolute" margin_top="-10">
<label text="${display}" size="26" color="~color_faded_20" weight="bold" />
</div>
</Button>
<div macro="dropdown_root" id="dropdown_${idx}">
<rectangle macro="dropdown_title">
<label text="${name}" weight="bold" size="24" />
</rectangle>
<MenuButton translation="BAR.TOGGLE_HIDE" action="::OverlaySoftToggle ${name}" />
<MenuButton translation="BAR.TOGGLE_IN_SET" action="::OverlayToggle ${name}" />
</div>
</Button>
</div>
</template>
<template name="Panel">
<Button macro="button_style" id="overlay_${idx}" _short_release="::OverlayToggle ${name}" tooltip="OVERLAY_TOOLTIP.PANEL;${name}" tooltip_side="bottom">
<sprite width="56" height="56" color="~text_color" src_builtin="edit/panel.svg" />
</Button>
<div>
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" _release="::ElementSetDisplay dropdown_${idx} none">
<sprite width="56" height="56" color="~text_color" src_builtin="edit/panel.svg" />
</Button>
<div macro="dropdown_root" id="dropdown_${idx}">
<rectangle macro="dropdown_title">
<label text="${name}" weight="bold" size="24" />
</rectangle>
<MenuButton translation="BAR.TOGGLE_HIDE" action="::OverlaySoftToggle ${name}" />
<MenuButton translation="BAR.TOGGLE_IN_SET" action="::OverlayToggle ${name}" />
</div>
</div>
</template>
<template name="Mirror">
<Button macro="button_style" id="overlay_${idx}" _short_release="::OverlayToggle ${name}" tooltip="OVERLAY_TOOLTIP.MIRROR;${name}" tooltip_side="bottom">
<sprite width="56" height="56" color="~text_color" src_builtin="edit/mirror.svg" />
<div position="absolute" margin_top="7" margin_left="20">
<label text="${display}" size="26" color="~color_faded_20" weight="bold" />
<div>
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" _release="::ElementSetDisplay dropdown_${idx} none">
<sprite width="56" height="56" color="~text_color" src_builtin="edit/mirror.svg" />
<div position="absolute" margin_top="7" margin_left="20">
<label text="${display}" size="26" color="~color_faded_20" weight="bold" />
</div>
</Button>
<div macro="dropdown_root" id="dropdown_${idx}">
<rectangle macro="dropdown_title">
<label text="${name}" weight="bold" size="24" />
</rectangle>
<MenuButton translation="BAR.TOGGLE_HIDE" action="::OverlaySoftToggle ${name}" />
<MenuButton translation="BAR.TOGGLE_IN_SET" action="::OverlayToggle ${name}" />
<MenuButton translation="BAR.CLOSE" action="::OverlayDrop ${name}" />
</div>
</Button>
</div>
</template>
<template name="Set">

View File

@@ -2,6 +2,12 @@
"ANCHOR": {
"CENTER": "Center"
},
"BAR": {
"HIDE": "Hide",
"TOGGLE_IN_SET": "Toggle in set",
"CLOSE": "Close",
"FORCE_CLOSE": "Force close"
},
"DEFAULT": "Default",
"DISABLED": "Disabled",
"OVERLAY_TOOLTIP": {

View File

@@ -8,7 +8,7 @@ use std::{
use serde::Deserialize;
use crate::{
backend::input,
backend::{input, wayvr::process::KillSignal},
state::AppState,
windowing::{OverlaySelector, window::OverlayWindowConfig},
};

View File

@@ -11,15 +11,18 @@ use process::ProcessVec;
use slotmap::SecondaryMap;
use smallvec::SmallVec;
use smithay::{
desktop::PopupManager, input::{keyboard::XkbConfig, SeatState}, output::{Mode, Output}, reexports::wayland_server::{self, backend::ClientId}, wayland::{
compositor::{self, with_states, SurfaceData},
desktop::PopupManager,
input::{SeatState, keyboard::XkbConfig},
output::{Mode, Output},
reexports::wayland_server::{self, backend::ClientId},
wayland::{
compositor::{self, SurfaceData, with_states},
dmabuf::{DmabufFeedbackBuilder, DmabufState},
selection::data_device::DataDeviceState,
shell::xdg::{ToplevelSurface, XdgShellState, XdgToplevelSurfaceData},
shm::ShmState,
}
},
};
use wlx_common::desktop_finder::DesktopFinder;
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
@@ -32,18 +35,23 @@ use vulkano::image::view::ImageView;
use wayvr_ipc::packet_server;
use wgui::gfx::WGfx;
use wlx_capture::frame::Transform;
use wlx_common::desktop_finder::DesktopFinder;
use xkbcommon::xkb;
use crate::{
backend::{
task::{OverlayTask, TaskType},
wayvr::{image_importer::ImageImporter, process::{Process}, window::Window},
wayvr::{
image_importer::ImageImporter,
process::{KillSignal, Process},
window::Window,
},
},
graphics::WGfxExtras,
ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal},
overlays::wayvr::create_wl_window_overlay,
state::AppState,
subsystem::hid::{WheelDelta, MODS_TO_KEYS},
subsystem::hid::{MODS_TO_KEYS, WheelDelta},
windowing::{OverlayID, OverlaySelector},
};
@@ -79,7 +87,7 @@ pub enum WayVRTask {
NewToplevel(ClientId, ToplevelSurface),
DropToplevel(ClientId, ToplevelSurface),
NewExternalProcess(ExternalProcessRequest),
ProcessTerminationRequest(process::ProcessHandle),
ProcessTerminationRequest(process::ProcessHandle, KillSignal),
CloseWindowRequest(window::WindowHandle),
}
@@ -303,31 +311,43 @@ impl WvrServerState {
};
// Size, icon & fallback title comes from process
let ([size_x, size_y], fallback_title, icon, is_cage) = match wvr_server.processes.get(&process_handle) {
Some(Process::Managed(p)) => (p.resolution, Some(p.app_name.clone()), p.icon.as_ref().cloned(), p.exec_path.ends_with("cage")),
_ => ([1920, 1080], None, None, false)
};
let ([size_x, size_y], fallback_title, icon, is_cage) =
match wvr_server.processes.get(&process_handle) {
Some(Process::Managed(p)) => (
p.resolution,
Some(p.app_name.clone()),
p.icon.as_ref().cloned(),
p.exec_path.ends_with("cage"),
),
_ => ([1920, 1080], None, None, false),
};
let window_handle = wvr_server
.wm
.create_window(toplevel.clone(), process_handle, size_x, size_y);
let window_handle = wvr_server.wm.create_window(
toplevel.clone(),
process_handle,
size_x,
size_y,
);
let mut title: Arc<str> = fallback_title.unwrap_or_else(|| format!("P{}", client.pid)).into();
let mut title: Arc<str> = fallback_title
.unwrap_or_else(|| format!("P{}", client.pid))
.into();
let mut icon = icon;
// Try to get title from xdg_toplevel, unless it's running in cage
if !is_cage {
let mut needs_title = true;
let (xdg_title, app_id): (Option<String>, Option<String>) = with_states(toplevel.wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.map(|t| {
let t = t.lock().unwrap();
(t.title.clone(), t.app_id.clone())
})
.unwrap_or((None, None))
});
let (xdg_title, app_id): (Option<String>, Option<String>) =
with_states(toplevel.wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.map(|t| {
let t = t.lock().unwrap();
(t.title.clone(), t.app_id.clone())
})
.unwrap_or((None, None))
});
if let Some(xdg_title) = xdg_title {
needs_title = false;
title = xdg_title.into();
@@ -335,15 +355,17 @@ impl WvrServerState {
// Try to get title & icon from desktop entry
if let Some(app_id) = app_id
&& let Some(desktop_entry) = app.desktop_finder.get_cached_entry(&app_id) {
&& let Some(desktop_entry) =
app.desktop_finder.get_cached_entry(&app_id)
{
if needs_title {
title = desktop_entry.app_name.as_ref().into();
}
if icon.is_none()
&& let Some(icon_path) = desktop_entry.icon_path.as_ref() {
&& let Some(icon_path) = desktop_entry.icon_path.as_ref()
{
icon = Some(icon_path.as_ref().into());
}
}
}
@@ -362,7 +384,10 @@ impl WvrServerState {
window_handle,
icon,
[size_x, size_y],
).context("Could not create WvrWindow overlay").inspect_err(|e| log::warn!("{e:?}")).ok()
)
.context("Could not create WvrWindow overlay")
.inspect_err(|e| log::warn!("{e:?}"))
.ok()
}),
)));
@@ -393,12 +418,18 @@ impl WvrServerState {
wvr_server.wm.remove_window(window_handle);
}
}
WayVRTask::ProcessTerminationRequest(process_handle) => {
WayVRTask::ProcessTerminationRequest(process_handle, signal) => {
if let Some(process) = wvr_server.processes.get_mut(&process_handle) {
process.terminate();
process.kill(signal);
}
for (h,w) in wvr_server.wm.windows.iter() {
// Don't clean up all windows in case of SIGTERM,
// the app might display a confirmation dialog, etc.
if !matches!(signal, KillSignal::Kill) {
continue;
}
for (h, w) in wvr_server.wm.windows.iter() {
if w.process != process_handle {
continue;
}
@@ -406,17 +437,19 @@ impl WvrServerState {
let Some(oid) = wvr_server.window_to_overlay.get(&h) else {
continue;
};
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Drop(
OverlaySelector::Id(*oid),
)));
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Drop(
OverlaySelector::Id(*oid),
)));
}
}
WayVRTask::CloseWindowRequest(window_handle) => {
if let Some(w) = wvr_server.wm.windows.get(&window_handle) {
log::info!("Sending window close to {window_handle:?}");
w.toplevel.send_close();
} else {
log::warn!("Could not close window - no such handle found: {window_handle:?}");
log::warn!(
"Could not close window - no such handle found: {window_handle:?}"
);
}
}
}
@@ -434,9 +467,13 @@ impl WvrServerState {
Ok(tasks)
}
pub fn terminate_process(&mut self, process_handle: process::ProcessHandle) {
pub fn terminate_process(
&mut self,
process_handle: process::ProcessHandle,
signal: KillSignal,
) {
self.tasks
.send(WayVRTask::ProcessTerminationRequest(process_handle));
.send(WayVRTask::ProcessTerminationRequest(process_handle, signal));
}
pub fn close_window(&mut self, window_handle: window::WindowHandle) {

View File

@@ -31,6 +31,12 @@ pub enum Process {
External(ExternalProcess), // External process not directly controlled by us
}
#[derive(Clone, Copy)]
pub enum KillSignal {
Term,
Kill,
}
impl Process {
pub fn is_running(&mut self) -> bool {
match self {
@@ -39,20 +45,25 @@ impl Process {
}
}
pub fn terminate(&mut self) {
pub fn kill(&mut self, signal: KillSignal) {
let signal = match signal {
KillSignal::Term => libc::SIGTERM,
KillSignal::Kill => libc::SIGKILL,
};
match self {
Self::Managed(p) => p.terminate(),
Self::External(p) => p.terminate(),
Self::Managed(p) => p.kill(signal),
Self::External(p) => p.kill(signal),
}
}
pub fn get_name(&self) -> String {
match self {
Self::Managed(p) => p.get_name()
Self::Managed(p) => p
.get_name()
.or_else(|| p.exec_path.split('/').last().map(String::from))
.unwrap_or_else(|| String::from("unknown")),
Self::External(p) => p.get_name()
.unwrap_or_else(|| String::from("unknown")),
Self::External(p) => p.get_name().unwrap_or_else(|| String::from("unknown")),
}
}
@@ -79,7 +90,7 @@ impl Drop for WayVRProcess {
self.child.id(),
self.exec_path.as_str()
);
self.terminate();
self.kill(libc::SIGTERM);
}
}
@@ -113,10 +124,10 @@ impl WayVRProcess {
}
}
fn terminate(&mut self) {
fn kill(&mut self, signal: i32) {
unsafe {
// Gracefully stop process
libc::kill(self.child.id() as i32, libc::SIGTERM);
libc::kill(self.child.id() as i32, signal);
}
}
@@ -147,11 +158,11 @@ impl ExternalProcess {
}
}
fn terminate(&mut self) {
fn kill(&mut self, signal: i32) {
if self.pid != 0 {
unsafe {
// send SIGINT (^C)
libc::kill(self.pid as i32, libc::SIGINT);
libc::kill(self.pid as i32, signal);
}
}
self.pid = 0;

View File

@@ -10,20 +10,28 @@ use std::{
use anyhow::Context;
use wgui::{
components::button::ComponentButton,
event::{CallbackData, CallbackMetadata, EventCallback, EventListenerKind, MouseButtonIndex},
event::{
CallbackData, CallbackMetadata, EventCallback, EventListenerKind, MouseButtonIndex,
StyleSetRequest,
},
layout::Layout,
parser::CustomAttribsInfoOwned,
log::LogErr,
parser::{CustomAttribsInfoOwned, Fetchable, ParserState},
taffy,
widget::EventResult,
};
use wlx_common::overlays::ToastTopic;
use crate::{
RUNNING,
backend::task::{OverlayTask, PlayspaceTask, TaskType},
overlays::{dashboard::DASH_NAME, toast::Toast},
backend::{
task::{OverlayTask, PlayspaceTask, TaskType},
wayvr::process::KillSignal,
},
overlays::{dashboard::DASH_NAME, toast::Toast, wayvr::WvrCommand},
state::AppState,
subsystem::hid::VirtualKey,
windowing::OverlaySelector,
windowing::{OverlaySelector, backend::OverlayEventData},
};
#[allow(clippy::type_complexity)]
@@ -176,6 +184,7 @@ fn short_duration(btn: &ComponentButton, app: &AppState) -> bool {
#[allow(clippy::too_many_lines)]
pub(super) fn setup_custom_button<S: 'static>(
layout: &mut Layout,
parser_state: &ParserState,
attribs: &CustomAttribsInfoOwned,
_app: &AppState,
button: Rc<ComponentButton>,
@@ -193,6 +202,37 @@ pub(super) fn setup_custom_button<S: 'static>(
let button = button.clone();
let callback: EventCallback<AppState, S> = match command {
"::ElementSetDisplay" => {
let (Some(id), Some(value)) = (args.next(), args.next()) else {
log::warn!(
"{command} has incorrect arguments. Should be: {command} <element_id> <display>"
);
return;
};
let Ok(widget_id) = parser_state.data.get_widget_id(id) else {
log::warn!("{command}: no element exists with ID '{id}'");
return;
};
let display = match value {
"none" => taffy::Display::None,
"flex" => taffy::Display::Flex,
"block" => taffy::Display::Block,
"grid" => taffy::Display::Grid,
_ => {
log::warn!("{command} has invalid display argument: '{value}'");
return;
}
};
Box::new(move |common, _data, _app, _| {
common
.alterables
.set_style(widget_id, StyleSetRequest::Display(display));
Ok(EventResult::Consumed)
})
}
"::DashToggle" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
@@ -284,8 +324,84 @@ pub(super) fn setup_custom_button<S: 'static>(
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::SoftToggleOverlay(
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::SoftToggleOverlay(
OverlaySelector::Name(arg.clone()),
)));
Ok(EventResult::Consumed)
})
}
"::OverlayDrop" => {
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
if arg.len() < 1 {
log::error!("{command} has missing arguments");
return;
};
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::Drop(OverlaySelector::Name(
arg.clone(),
))));
Ok(EventResult::Consumed)
})
}
"::WvrOverlayCloseWindow" => {
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
if arg.len() < 1 {
log::error!("{command} has missing arguments");
return;
};
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(arg.clone()),
Box::new(move |app, owc| {
let _ = owc
.backend
.notify(app, OverlayEventData::WvrCommand(WvrCommand::CloseWindow))
.log_warn("Could not close window");
}),
)));
Ok(EventResult::Consumed)
})
}
"::WvrOverlayKillProcess" | "::WvrOverlayTermProcess" => {
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
if arg.len() < 1 {
log::error!("{command} has missing arguments");
return;
};
let signal = if command == "::OverlayKillProcess" {
KillSignal::Kill
} else {
KillSignal::Term
};
Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(arg.clone()),
Box::new(move |app, owc| {
let _ = owc
.backend
.notify(
app,
OverlayEventData::WvrCommand(WvrCommand::KillProcess(signal)),
)
.log_warn("Could not kill process");
}),
)));
Ok(EventResult::Consumed)
})

View File

@@ -186,7 +186,7 @@ impl<S: 'static> GuiPanel<S> {
.parser_state
.fetch_component_from_widget_id_as::<ComponentButton>(elem.widget_id)
{
setup_custom_button::<S>(&mut self.layout, elem, app, button);
setup_custom_button::<S>(&mut self.layout, &self.parser_state, elem, app, button);
}
if let Some(on_custom_attrib) = &self.on_custom_attrib {

View File

@@ -300,6 +300,8 @@ impl Connection {
params: &mut TickParams,
process_handle: packet_server::WvrProcessHandle,
) {
use crate::backend::wayvr::process::KillSignal;
let native_handle = &wayvr::process::ProcessHandle::from_packet(process_handle);
let process = params.wvr_server.processes.get_mut(native_handle);
@@ -307,7 +309,7 @@ impl Connection {
return;
};
process.terminate();
process.kill(KillSignal::Term);
}
#[cfg(feature = "wayvr")]

View File

@@ -30,7 +30,10 @@ use crate::{
backend::{
input::{Haptics, HoverResult, PointerHit, PointerMode},
task::{OverlayTask, PlayspaceTask, TaskType},
wayvr::{process::ProcessHandle, window::WindowHandle},
wayvr::{
process::{KillSignal, ProcessHandle},
window::WindowHandle,
},
},
ipc::ipc_server::{gen_args_vec, gen_env_vec},
state::AppState,
@@ -402,7 +405,7 @@ impl DashInterface<AppState> for DashInterfaceLive {
handle: WvrProcessHandle,
) -> anyhow::Result<()> {
let wvr_server = app.wvr_server.as_mut().unwrap();
wvr_server.terminate_process(ProcessHandle::from_packet(handle));
wvr_server.terminate_process(ProcessHandle::from_packet(handle), KillSignal::Term);
Ok(())
}

View File

@@ -1,15 +1,28 @@
use std::{collections::{HashMap}, rc::Rc, time::Duration};
use std::{collections::HashMap, rc::Rc, time::Duration};
use crate::{
app_misc,
gui::{panel::{GuiPanel, NewGuiPanelParams}, timer::GuiTimer},
gui::{
panel::{GuiPanel, NewGuiPanelParams},
timer::GuiTimer,
},
state::AppState,
subsystem::hid::XkbKeymap, windowing::{backend::OverlayEventData, window::OverlayCategory},
subsystem::hid::XkbKeymap,
windowing::{backend::OverlayEventData, window::OverlayCategory},
};
use anyhow::Context;
use glam::{FloatExt, Mat4, Vec2, vec2, vec3};
use wgui::{
animation::{Animation, AnimationEasing}, assets::AssetPath, components::button::ComponentButton, drawing::{self, Color}, event::{self, CallbackDataCommon, CallbackMetadata, EventAlterables, EventListenerKind}, layout::LayoutUpdateParams, parser::{Fetchable, ParseDocumentParams}, renderer_vk::util, taffy::{self, prelude::length}, widget::{div::WidgetDiv, rectangle::WidgetRectangle, EventResult}
animation::{Animation, AnimationEasing},
assets::AssetPath,
components::button::ComponentButton,
drawing::{self, Color},
event::{self, CallbackDataCommon, CallbackMetadata, EventAlterables, EventListenerKind},
layout::LayoutUpdateParams,
parser::{Fetchable, ParseDocumentParams},
renderer_vk::util,
taffy::{self, prelude::length},
widget::{EventResult, div::WidgetDiv, rectangle::WidgetRectangle},
};
use super::{
@@ -276,7 +289,7 @@ pub(super) fn create_keyboard_panel(
for i in 0..num_sets {
let mut params = HashMap::new();
params.insert("idx".into(), i.to_string().into());
params.insert("display".into(), (i+1).to_string().into());
params.insert("display".into(), (i + 1).to_string().into());
panel.parser_state.instantiate_template(
&doc_params,
"Set",
@@ -284,7 +297,9 @@ pub(super) fn create_keyboard_panel(
sets_root,
params,
)?;
let set_button = panel.parser_state.fetch_component_as::<ComponentButton>(&format!("set_{i}"))?;
let set_button = panel
.parser_state
.fetch_component_as::<ComponentButton>(&format!("set_{i}"))?;
if panel.state.current_set == Some(i) {
let mut com = CallbackDataCommon {
alterables: &mut alterables,
@@ -308,21 +323,34 @@ pub(super) fn create_keyboard_panel(
let (template, root) = match meta.category {
OverlayCategory::Screen => {
params.insert("display".into(), format!("{}{}", (*meta.name).chars().next().unwrap_or_default(), (*meta.name).chars().last().unwrap_or_default()).into());
params.insert(
"display".into(),
format!(
"{}{}",
(*meta.name).chars().next().unwrap_or_default(),
(*meta.name).chars().last().unwrap_or_default()
)
.into(),
);
("Screen", panels_root)
},
}
OverlayCategory::Mirror => {
params.insert("display".into(), meta.name.as_ref().into());
("Mirror", panels_root)
},
OverlayCategory::Panel => {
("Panel", panels_root)
},
}
OverlayCategory::Panel => ("Panel", panels_root),
OverlayCategory::WayVR => {
params.insert("icon".into(), meta.icon.as_ref().expect("WayVR overlay without Icon attribute!").as_ref().into());
params.insert(
"icon".into(),
meta.icon
.as_ref()
.expect("WayVR overlay without Icon attribute!")
.as_ref()
.into(),
);
("App", apps_root)
},
_ => continue
}
_ => continue,
};
params.insert("idx".into(), i.to_string().into());
@@ -334,7 +362,9 @@ pub(super) fn create_keyboard_panel(
root,
params,
)?;
let overlay_buttons = panel.parser_state.fetch_component_as::<ComponentButton>(&format!("overlay_{i}"))?;
let overlay_buttons = panel
.parser_state
.fetch_component_as::<ComponentButton>(&format!("overlay_{i}"))?;
if meta.visible {
let mut com = CallbackDataCommon {
alterables: &mut alterables,
@@ -354,8 +384,7 @@ pub(super) fn create_keyboard_panel(
let mut overlay_buttons = panel.state.overlay_buttons.clone();
for visible in &*overlays {
if let Some(btn) = overlay_buttons.remove(*visible)
{
if let Some(btn) = overlay_buttons.remove(*visible) {
btn.set_sticky_state(&mut com, true);
}
}

View File

@@ -1,24 +1,42 @@
use std::{
cell::Cell, collections::HashMap, process::{Child, Command}, rc::Rc, sync::atomic::Ordering
cell::Cell,
collections::{HashMap, HashSet},
process::{Child, Command},
rc::Rc,
sync::atomic::Ordering,
};
use crate::{
backend::input::{HoverResult, PointerHit}, gui::panel::GuiPanel, overlays::keyboard::{builder::create_keyboard_panel, layout::AltModifier}, state::AppState, subsystem::{
KEYMAP_CHANGE,
backend::input::{HoverResult, PointerHit},
gui::panel::GuiPanel,
overlays::keyboard::{builder::create_keyboard_panel, layout::AltModifier},
state::AppState,
subsystem::{
dbus::DbusConnector,
hid::{
get_keymap_wl, get_keymap_x11, KeyModifier, VirtualKey, WheelDelta, XkbKeymap, ALT, CTRL, META, SHIFT, SUPER
ALT, CTRL, KeyModifier, META, SHIFT, SUPER, VirtualKey, WheelDelta, XkbKeymap,
get_keymap_wl, get_keymap_x11,
},
}, windowing::{
backend::{FrameMeta, OverlayBackend, OverlayEventData, OverlayMeta, RenderResources, ShouldRender},
window::{OverlayCategory, OverlayWindowConfig}, OverlayID,
}, KEYMAP_CHANGE
},
windowing::{
OverlayID,
backend::{
FrameMeta, OverlayBackend, OverlayEventData, OverlayMeta, RenderResources, ShouldRender,
},
window::{OverlayCategory, OverlayWindowConfig},
},
};
use anyhow::Context;
use glam::{Affine3A, Quat, Vec3, vec3};
use regex::Regex;
use slotmap::{new_key_type, SecondaryMap, SlotMap};
use slotmap::{SecondaryMap, SlotMap, new_key_type};
use wgui::{
components::button::ComponentButton, drawing, event::{InternalStateChangeEvent, MouseButton, MouseButtonIndex}
components::button::ComponentButton,
drawing,
event::{InternalStateChangeEvent, MouseButton, MouseButtonIndex},
layout::{Layout, WidgetID},
parser::ParserState,
};
use wlx_common::overlays::{BackendAttrib, BackendAttribValue};
use wlx_common::windowing::{OverlayWindowState, Positioning};
@@ -329,7 +347,7 @@ impl KeyboardState {
overlay_buttons
},
overlay_metas: {
let mut overlay_metas= SecondaryMap::new();
let mut overlay_metas = SecondaryMap::new();
std::mem::swap(&mut overlay_metas, &mut self.overlay_metas);
overlay_metas
},

View File

@@ -28,7 +28,7 @@ use crate::{
backend::{
XrBackend,
input::{self, HoverResult},
wayvr::{self, SurfaceBufWithImage, window::WindowHandle},
wayvr::{self, SurfaceBufWithImage, process::KillSignal, window::WindowHandle},
},
graphics::{ExtentExt, Vert2Uv, upload_quad_vertices},
gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS},
@@ -44,6 +44,11 @@ use crate::{
},
};
pub enum WvrCommand {
CloseWindow,
KillProcess(KillSignal),
}
const BORDER_SIZE: u32 = 5;
const BAR_SIZE: u32 = 48;
@@ -421,10 +426,24 @@ impl OverlayBackend for WvrWindowBackend {
app: &mut state::AppState,
event_data: OverlayEventData,
) -> anyhow::Result<()> {
if let OverlayEventData::IdAssigned(oid) = event_data {
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
wvr_server.overlay_added(oid, self.window);
match event_data {
OverlayEventData::IdAssigned(oid) => {
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
wvr_server.overlay_added(oid, self.window);
}
OverlayEventData::WvrCommand(WvrCommand::CloseWindow) => {
app.wvr_server.as_mut().unwrap().close_window(self.window);
}
OverlayEventData::WvrCommand(WvrCommand::KillProcess(signal)) => {
let wvr_server = app.wvr_server.as_mut().unwrap();
let Some(p) = wvr_server.wm.windows.get(&self.window) else {
return Ok(());
};
wvr_server.terminate_process(p.process, signal);
}
_ => {}
}
Ok(())
}

View File

@@ -17,6 +17,7 @@ use crate::{
task::ModifyPanelCommand,
},
graphics::{ExtentExt, RenderResult},
overlays::wayvr::WvrCommand,
state::AppState,
subsystem::hid::WheelDelta,
windowing::{OverlayID, window::OverlayCategory},
@@ -134,6 +135,7 @@ pub enum OverlayEventData {
element: String,
command: ModifyPanelCommand,
},
WvrCommand(WvrCommand),
}
pub trait OverlayBackend: Any {

View File

@@ -1,5 +1,7 @@
use std::{
collections::{HashMap, VecDeque}, rc::Rc, sync::atomic::Ordering
collections::{HashMap, VecDeque},
rc::Rc,
sync::atomic::Ordering,
};
use anyhow::Context;
@@ -7,7 +9,9 @@ use glam::{Affine3A, Vec3, Vec3A};
use slotmap::{HopSlotMap, Key, SecondaryMap};
use wgui::log::LogErr;
use wlx_common::{
astr_containers::{AStrMap, AStrMapExt}, config::SerializedWindowSet, overlays::{BackendAttrib, BackendAttribValue, ToastTopic}
astr_containers::{AStrMap, AStrMapExt},
config::SerializedWindowSet,
overlays::{BackendAttrib, BackendAttribValue, ToastTopic},
};
use crate::{
@@ -65,7 +69,7 @@ where
restore_set: 0,
sets: vec![OverlayWindowSet::default()],
anchor_local: Affine3A::from_translation(Vec3::NEG_Z),
watch_id: OverlayID::null(), // set down below
watch_id: OverlayID::null(), // set down below
keyboard_id: OverlayID::null(), // set down below
edit_mode: false,
dropped_overlays: VecDeque::with_capacity(8),
@@ -149,11 +153,7 @@ where
OverlayEventData::EditModeChanged(false),
OverlayEventData::DevicesChanged,
] {
me.mut_by_id(id)
.unwrap()
.config
.backend
.notify(app, ev)?;
me.mut_by_id(id).unwrap().config.backend.notify(app, ev)?;
}
}
@@ -205,11 +205,13 @@ where
self.sets.push(OverlayWindowSet::default());
let len = self.sets.len();
for id in [self.watch_id, self.keyboard_id] {
self.mut_by_id(id).and_then(|o| o
.config
.backend
.notify(app, OverlayEventData::NumSetsChanged(len)).log_err().ok())
.context("Could not notify NumSetsChanged")?;
self.mut_by_id(id).map(|o| {
let _ = o
.config
.backend
.notify(app, OverlayEventData::NumSetsChanged(len))
.log_err("Could not notify NumSetsChanged");
});
}
}
OverlayTask::DeleteActiveSet => {
@@ -241,11 +243,13 @@ where
self.sets.remove(set);
let len = self.sets.len();
for id in [self.watch_id, self.keyboard_id] {
self.mut_by_id(id).and_then(|o| o
.config
.backend
.notify(app, OverlayEventData::NumSetsChanged(len)).log_err().ok())
.context("Could not notify NumSetsChanged")?;
self.mut_by_id(id).map(|o| {
let _ = o
.config
.backend
.notify(app, OverlayEventData::NumSetsChanged(len))
.log_err("Could not notify NumSetsChanged");
});
}
}
OverlayTask::CleanupMirrors => {
@@ -740,12 +744,11 @@ impl<T> OverlayWindowManager<T> {
self.current_set = new_set;
for id in [self.watch_id, self.keyboard_id] {
let _ = self.mut_by_id(id)
.context("Missing overlay")
.and_then(|o|
let _ = self.mut_by_id(id).context("Missing overlay").and_then(|o| {
o.config
.backend
.notify(app, OverlayEventData::ActiveSetChanged(new_set)));
.backend
.notify(app, OverlayEventData::ActiveSetChanged(new_set))
});
let _ = self
.visible_overlays_changed(app)
@@ -774,7 +777,9 @@ impl<T> OverlayWindowManager<T> {
if matches!(data.config.category, OverlayCategory::Internal) {
continue;
}
let icon = if let Some(BackendAttribValue::Icon(icon)) = data.config.backend.get_attrib(BackendAttrib::Icon) {
let icon = if let Some(BackendAttribValue::Icon(icon)) =
data.config.backend.get_attrib(BackendAttrib::Icon)
{
Some(icon)
} else {
None
@@ -791,12 +796,11 @@ impl<T> OverlayWindowManager<T> {
let meta: Rc<[OverlayMeta]> = meta.into();
for id in [self.watch_id, self.keyboard_id] {
let _ = self.mut_by_id(id)
.context("Missing overlay")
.and_then(|o| o
.config
.backend
.notify(app, OverlayEventData::OverlaysChanged(meta.clone())));
let _ = self.mut_by_id(id).context("Missing overlay").and_then(|o| {
o.config
.backend
.notify(app, OverlayEventData::OverlaysChanged(meta.clone()))
});
}
Ok(())
@@ -816,12 +820,11 @@ impl<T> OverlayWindowManager<T> {
let vis: Rc<[OverlayID]> = vis.into();
for id in [self.watch_id, self.keyboard_id] {
let _ = self.mut_by_id(id)
.context("Missing overlay")
.and_then(|o| o
.config
.backend
.notify(app, OverlayEventData::VisibleOverlaysChanged(vis.clone())));
let _ = self.mut_by_id(id).context("Missing overlay").and_then(|o| {
o.config
.backend
.notify(app, OverlayEventData::VisibleOverlaysChanged(vis.clone()))
});
}
Ok(())