bar app icons & tooltips

This commit is contained in:
galister
2026-01-05 15:45:19 +09:00
parent b86525d65d
commit b56aa1a8de
30 changed files with 291 additions and 129 deletions

View File

@@ -88,14 +88,14 @@
<!-- An app with a single icon. -->
<template name="App">
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" tooltip="${name}" tooltip_side="bottom">
<sprite width="56" height="56" color="~text_color" src_ext="${src}" />
<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>
</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}" _press="::OverlayToggle ${name}" tooltip="${name}" tooltip_side="bottom">
<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" />
@@ -104,13 +104,13 @@
</template>
<template name="Panel">
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" tooltip="${name}" tooltip_side="bottom">
<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>
</template>
<template name="Mirror">
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" tooltip="${name}" tooltip_side="bottom">
<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" />
@@ -119,7 +119,7 @@
</template>
<template name="Set">
<Button macro="button_style" id="set_${idx}" _press="::SetSwitch ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="bottom">
<Button macro="button_style" id="set_${idx}" _release="::SetSwitch ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="bottom">
<sprite width="56" height="56" color="~text_color" src_builtin="watch/set2.svg" />
<div position="absolute" margin_top="16" margin_left="-8">
<label text="${display}" size="26" color="~color_faded_20" weight="bold" />
@@ -139,9 +139,9 @@
</div>
<VerticalSeparator />
<div id="apps_root" gap="8">
<App id="test1" name="Blender" src="/usr/share/icons/hicolor/scalable/apps/blender-5.0.svg" />
<App id="test2" name="Inkscape" src="/usr/share/icons/hicolor/scalable/apps/org.inkscape.Inkscape.svg" />
<App id="test3" name="GIMP" src="/usr/share/icons/hicolor/scalable/apps/gimp.svg" />
<App id="test1" name="Blender" icon="/usr/share/icons/hicolor/scalable/apps/blender-5.0.svg" />
<App id="test2" name="Inkscape" icon="/usr/share/icons/hicolor/scalable/apps/org.inkscape.Inkscape.svg" />
<App id="test3" name="GIMP" icon="/usr/share/icons/hicolor/scalable/apps/gimp.svg" />
</div>
</div>
<div id="tray_root" flex_direction="row" gap="16">

View File

@@ -4,6 +4,12 @@
},
"DEFAULT": "Default",
"DISABLED": "Disabled",
"OVERLAY_TOOLTIP": {
"SCREEN": "Screen {}",
"MIRROR": "Mirror {}",
"PANEL": "Panel {}",
"APP": "{}"
},
"EDIT_MODE": {
"ADJUST_CURVATURE": "Adjust curvature",
"ALPHA_BLEND_MODE": "Alpha blend mode",

View File

@@ -19,6 +19,7 @@ use smithay::{
shm::ShmState,
}
};
use wlx_common::desktop_finder::DesktopFinder;
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
@@ -301,23 +302,57 @@ impl WvrServerState {
continue;
};
let [size_x, size_y] = match wvr_server.processes.get(&process_handle) {
Some(Process::Managed(p)) => p.resolution,
_ => [1920, 1080],
// 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 window_handle = wvr_server
.wm
.create_window(toplevel.clone(), process_handle, size_x, size_y);
let title: Arc<str> = with_states(toplevel.wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.and_then(|t| t.lock().unwrap().title.clone())
.map_or_else(|| format!("P{}", client.pid).into(), String::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))
});
if let Some(xdg_title) = xdg_title {
needs_title = false;
title = xdg_title.into();
}
// 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) {
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() {
icon = Some(icon_path.as_ref().into());
}
}
}
// Fall back to identicon
let icon = match icon {
Some(icon) => icon,
None => DesktopFinder::create_icon(&*title)?.into(),
};
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Create(
OverlaySelector::Nothing,
Box::new(move |app: &mut AppState| {
@@ -325,7 +360,8 @@ impl WvrServerState {
title,
app,
window_handle,
size_x.max(size_y),
icon,
[size_x, size_y],
).context("Could not create WvrWindow overlay").inspect_err(|e| log::warn!("{e:?}")).ok()
}),
)));
@@ -500,11 +536,13 @@ impl WvrServerState {
pub fn spawn_process(
&mut self,
app_name: &str,
exec_path: &str,
args: &[&str],
env: &[(&str, &str)],
resolution: [u32; 2],
working_dir: Option<&str>,
icon: Option<&str>,
userdata: HashMap<String, String>,
) -> anyhow::Result<process::ProcessHandle> {
let auth_key = generate_auth_key();
@@ -528,6 +566,7 @@ impl WvrServerState {
auth_key,
child,
exec_path: String::from(exec_path),
app_name: String::from(app_name),
userdata,
args: args.iter().map(|x| String::from(*x)).collect(),
working_dir: working_dir.map(String::from),
@@ -535,6 +574,7 @@ impl WvrServerState {
.iter()
.map(|(a, b)| (String::from(*a), String::from(*b)))
.collect(),
icon: icon.map(Arc::from),
resolution,
}));

View File

@@ -1,4 +1,4 @@
use std::{collections::HashMap, io::Read};
use std::{collections::HashMap, io::Read, sync::Arc};
use wayvr_ipc::packet_server;
@@ -9,12 +9,13 @@ use crate::gen_id;
pub struct WayVRProcess {
pub auth_key: String,
pub child: std::process::Child,
pub app_name: String,
pub exec_path: String,
pub args: Vec<String>,
pub env: Vec<(String, String)>,
pub working_dir: Option<String>,
pub resolution: [u32; 2],
pub icon: Option<Arc<str>>,
pub userdata: HashMap<String, String>,
}
@@ -47,8 +48,11 @@ impl Process {
pub fn get_name(&self) -> String {
match self {
Self::Managed(p) => p.get_name().unwrap_or_else(|| String::from("unknown")),
Self::External(p) => p.get_name().unwrap_or_else(|| String::from("unknown")),
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")),
}
}
@@ -71,7 +75,8 @@ impl Process {
impl Drop for WayVRProcess {
fn drop(&mut self) {
log::info!(
"Sending SIGTERM (graceful exit) to process {}",
"Sending SIGTERM (graceful exit) to process {} ({})",
self.child.id(),
self.exec_path.as_str()
);
self.terminate();

View File

@@ -239,11 +239,13 @@ impl Connection {
let env_vec = gen_env_vec(&packet_params.env);
let res = params.wvr_server.spawn_process(
&packet_params.name,
&packet_params.exec,
&args_vec,
&env_vec,
packet_params.resolution,
None,
packet_params.icon.as_deref(),
packet_params.userdata,
);

View File

@@ -3,7 +3,6 @@ use dash_frontend::{
settings::{self, SettingsIO},
};
use glam::{Affine2, Affine3A, Vec2, vec2, vec3};
use tracing::instrument::WithSubscriber;
use wayvr_ipc::{
packet_client::WvrProcessLaunchParams,
packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle},
@@ -376,11 +375,13 @@ impl DashInterface<AppState> for DashInterfaceLive {
wvr_server
.spawn_process(
&params.name,
&params.exec,
&args_vec,
&env_vec,
params.resolution,
None,
params.icon.as_deref(),
params.userdata,
)
.map(|x| x.as_packet())
@@ -434,4 +435,11 @@ impl DashInterface<AppState> for DashInterfaceLive {
.enqueue(TaskType::Playspace(PlayspaceTask::Recenter));
Ok(())
}
fn desktop_finder<'a>(
&'a mut self,
data: &'a mut AppState,
) -> &'a mut wlx_common::desktop_finder::DesktopFinder {
&mut data.desktop_finder
}
}

View File

@@ -9,7 +9,7 @@ use crate::{
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, ParseDocumentExtra, 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::{div::WidgetDiv, rectangle::WidgetRectangle, EventResult}
};
use super::{
@@ -319,6 +319,7 @@ pub(super) fn create_keyboard_panel(
("Panel", panels_root)
},
OverlayCategory::WayVR => {
params.insert("icon".into(), meta.icon.as_ref().expect("WayVR overlay without Icon attribute!").as_ref().into());
("App", apps_root)
},
_ => continue

View File

@@ -51,9 +51,11 @@ pub fn create_wl_window_overlay(
name: Arc<str>,
app: &mut AppState,
window: wayvr::window::WindowHandle,
size_major: u32,
icon: Arc<str>,
size: [u32; 2],
) -> anyhow::Result<OverlayWindowConfig> {
let scale = size_major as f32 / 1920.0;
let scale = size[0].max(size[1]) as f32 / 1920.0;
let curve_scale = size[0] as f32 / 1920.0;
Ok(OverlayWindowConfig {
name: name.clone(),
@@ -61,7 +63,7 @@ pub fn create_wl_window_overlay(
grabbable: true,
interactable: true,
positioning: Positioning::Floating,
curvature: Some(0.15),
curvature: Some(0.15 * curve_scale),
transform: Affine3A::from_scale_rotation_translation(
Vec3::ONE * scale,
Quat::IDENTITY,
@@ -72,12 +74,15 @@ pub fn create_wl_window_overlay(
keyboard_focus: Some(KeyboardFocus::WayVR),
category: OverlayCategory::WayVR,
show_on_spawn: true,
..OverlayWindowConfig::from_backend(Box::new(WvrWindowBackend::new(name, app, window)?))
..OverlayWindowConfig::from_backend(Box::new(WvrWindowBackend::new(
name, app, window, icon,
)?))
})
}
pub struct WvrWindowBackend {
name: Arc<str>,
icon: Arc<str>,
pipeline: Option<ScreenPipeline>,
popups_pipeline: Arc<WGfxPipeline<Vert2Uv>>,
interaction_transform: Option<Affine2>,
@@ -100,6 +105,7 @@ impl WvrWindowBackend {
name: Arc<str>,
app: &mut AppState,
window: wayvr::window::WindowHandle,
icon: Arc<str>,
) -> anyhow::Result<Self> {
let popups_pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
@@ -170,6 +176,7 @@ impl WvrWindowBackend {
Ok(Self {
name,
icon,
pipeline: None,
window,
popups: vec![],
@@ -508,6 +515,7 @@ impl OverlayBackend for WvrWindowBackend {
fn get_attrib(&self, attrib: BackendAttrib) -> Option<BackendAttribValue> {
match attrib {
BackendAttrib::Stereo => self.stereo.map(BackendAttribValue::Stereo),
BackendAttrib::Icon => Some(BackendAttribValue::Icon(self.icon.clone())),
_ => None,
}
}

View File

@@ -9,6 +9,7 @@ use wgui::{
use wlx_common::{
audio,
config::GeneralConfig,
desktop_finder::DesktopFinder,
overlays::{ToastDisplayMethod, ToastTopic},
};
@@ -57,6 +58,8 @@ pub struct AppState {
pub ipc_server: ipc_server::WayVRServer,
pub wayvr_signals: SyncEventQueue<WayVRSignal>,
pub desktop_finder: DesktopFinder,
#[cfg(feature = "osc")]
pub osc_sender: Option<OscSender>,
@@ -136,6 +139,9 @@ impl AppState {
let ipc_server = ipc_server::WayVRServer::new()?;
let mut desktop_finder = DesktopFinder::new();
desktop_finder.refresh();
Ok(Self {
session,
tasks,
@@ -159,6 +165,7 @@ impl AppState {
xr_backend,
ipc_server,
wayvr_signals,
desktop_finder,
#[cfg(feature = "osc")]
osc_sender,

View File

@@ -112,6 +112,7 @@ pub struct OverlayMeta {
pub name: Arc<str>,
pub category: OverlayCategory,
pub visible: bool,
pub icon: Option<Arc<str>>,
}
#[allow(clippy::enum_variant_names)]

View File

@@ -5,8 +5,9 @@ use std::{
use anyhow::Context;
use glam::{Affine3A, Vec3, Vec3A};
use slotmap::{HopSlotMap, Key, SecondaryMap};
use wgui::log::LogErr;
use wlx_common::{
astr_containers::{AStrMap, AStrMapExt}, common::LogErr, config::SerializedWindowSet, overlays::{BackendAttrib, ToastTopic}
astr_containers::{AStrMap, AStrMapExt}, config::SerializedWindowSet, overlays::{BackendAttrib, BackendAttribValue, ToastTopic}
};
use crate::{
@@ -773,11 +774,18 @@ 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) {
Some(icon)
} else {
None
};
meta.push(OverlayMeta {
id,
name: data.config.name.clone(),
category: data.config.category,
visible: data.config.is_active(),
icon,
});
}