bar app icons & tooltips
This commit is contained in:
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -1428,20 +1428,16 @@ dependencies = [
|
||||
"async-native-tls",
|
||||
"chrono",
|
||||
"glam",
|
||||
"glob",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"identicons-svg",
|
||||
"keyvalues-parser",
|
||||
"log",
|
||||
"rust-embed",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smol",
|
||||
"smol-hyper",
|
||||
"strum",
|
||||
"walkdir",
|
||||
"wayvr-ipc",
|
||||
"wgui",
|
||||
"wlx-common",
|
||||
@@ -6923,12 +6919,16 @@ dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"glam",
|
||||
"glob",
|
||||
"identicons-svg",
|
||||
"idmap",
|
||||
"idmap-derive",
|
||||
"log",
|
||||
"rodio",
|
||||
"rust-ini",
|
||||
"serde",
|
||||
"smol",
|
||||
"walkdir",
|
||||
"wayvr-ipc",
|
||||
"wgui",
|
||||
"xdg 3.0.0",
|
||||
|
||||
@@ -24,7 +24,3 @@ hyper = { version = "1.8.1", features = ["client", "http1", "http2"] }
|
||||
http-body-util = "0.1.3"
|
||||
async-native-tls = "0.5.0"
|
||||
smol-hyper = "0.1.1"
|
||||
glob = "0.3.3"
|
||||
walkdir = "2.5.0"
|
||||
rust-ini = "0.21.3"
|
||||
identicons-svg = "0.1.0"
|
||||
|
||||
@@ -19,11 +19,10 @@ use wlx_common::{audio, dash_interface::BoxDashInterface, timestep::Timestep};
|
||||
use crate::{
|
||||
assets, settings,
|
||||
tab::{
|
||||
Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses,
|
||||
settings::TabSettings,
|
||||
apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings,
|
||||
Tab, TabType,
|
||||
},
|
||||
util::{
|
||||
desktop_finder::DesktopFinder,
|
||||
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
|
||||
toast_manager::ToastManager,
|
||||
various::AsyncExecutor,
|
||||
@@ -64,8 +63,6 @@ pub struct Frontend<T> {
|
||||
|
||||
window_audio_settings: WguiWindow,
|
||||
view_audio_settings: Option<views::audio_settings::View>,
|
||||
|
||||
pub(crate) desktop_finder: DesktopFinder,
|
||||
}
|
||||
|
||||
pub struct FrontendUpdateParams<'a, T> {
|
||||
@@ -137,9 +134,6 @@ impl<T: 'static> Frontend<T> {
|
||||
|
||||
let timestep = Timestep::new(60.0);
|
||||
|
||||
let mut desktop_finder = DesktopFinder::new();
|
||||
desktop_finder.refresh();
|
||||
|
||||
let mut frontend = Self {
|
||||
layout,
|
||||
state,
|
||||
@@ -159,7 +153,6 @@ impl<T: 'static> Frontend<T> {
|
||||
window_audio_settings: WguiWindow::default(),
|
||||
view_audio_settings: None,
|
||||
executor: Rc::new(smol::LocalExecutor::new()),
|
||||
desktop_finder,
|
||||
};
|
||||
|
||||
// init some things first
|
||||
@@ -292,7 +285,7 @@ impl<T: 'static> Frontend<T> {
|
||||
|
||||
fn process_task(&mut self, data: &mut T, task: FrontendTask) -> anyhow::Result<()> {
|
||||
match task {
|
||||
FrontendTask::SetTab(tab_type) => self.set_tab(tab_type)?,
|
||||
FrontendTask::SetTab(tab_type) => self.set_tab(data, tab_type)?,
|
||||
FrontendTask::RefreshClock => self.update_time()?,
|
||||
FrontendTask::RefreshBackground => self.update_background()?,
|
||||
FrontendTask::MountPopup(params) => self.mount_popup(params)?,
|
||||
@@ -305,14 +298,14 @@ impl<T: 'static> Frontend<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tab(&mut self, tab_type: TabType) -> anyhow::Result<()> {
|
||||
fn set_tab(&mut self, data: &mut T, tab_type: TabType) -> anyhow::Result<()> {
|
||||
log::info!("Setting tab to {tab_type:?}");
|
||||
let widget_content = self.state.fetch_widget(&self.layout.state, "content")?;
|
||||
self.layout.remove_children(widget_content.id);
|
||||
|
||||
let tab: Box<dyn Tab<T>> = match tab_type {
|
||||
TabType::Home => Box::new(TabHome::new(self, widget_content.id)?),
|
||||
TabType::Apps => Box::new(TabApps::new(self, widget_content.id)?),
|
||||
TabType::Apps => Box::new(TabApps::new(self, widget_content.id, data)?),
|
||||
TabType::Games => Box::new(TabGames::new(self, widget_content.id)?),
|
||||
TabType::Monado => Box::new(TabMonado::new(self, widget_content.id)?),
|
||||
TabType::Processes => Box::new(TabProcesses::new(self, widget_content.id)?),
|
||||
|
||||
@@ -14,14 +14,12 @@ use wgui::{
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
task::Tasks,
|
||||
};
|
||||
use wlx_common::desktop_finder::DesktopEntry;
|
||||
|
||||
use crate::{
|
||||
frontend::{Frontend, FrontendTask, FrontendTasks},
|
||||
tab::{Tab, TabType},
|
||||
util::{
|
||||
desktop_finder::DesktopEntry,
|
||||
popup_manager::{MountPopupParams, PopupHandle},
|
||||
},
|
||||
util::popup_manager::{MountPopupParams, PopupHandle},
|
||||
views::{self, app_launcher},
|
||||
};
|
||||
|
||||
@@ -127,16 +125,16 @@ fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> {
|
||||
}
|
||||
|
||||
impl<T> TabApps<T> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, data: &mut T) -> anyhow::Result<Self> {
|
||||
let globals = frontend.layout.state.globals.clone();
|
||||
let tasks = Tasks::new();
|
||||
let state = Rc::new(RefCell::new(State { view_launcher: None }));
|
||||
|
||||
let mut entries = frontend.desktop_finder.find_entries();
|
||||
let mut entries = frontend.interface.desktop_finder(data).find_entries();
|
||||
let parser_state = wgui::parser::parse_from_assets(&doc_params(globals.clone()), &mut frontend.layout, parent_id)?;
|
||||
let app_list_parent = parser_state.fetch_widget(&frontend.layout.state, "app_list_parent")?;
|
||||
let app_list = AppList {
|
||||
entries_to_mount: entries.drain(..).collect(),
|
||||
entries_to_mount: entries.into_values().collect(),
|
||||
list_parent: app_list_parent,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod cached_fetcher;
|
||||
pub mod desktop_finder;
|
||||
pub mod http_client;
|
||||
pub mod pactl_wrapper;
|
||||
pub mod popup_manager;
|
||||
|
||||
@@ -11,11 +11,10 @@ use wgui::{
|
||||
sprite::{WidgetSprite, WidgetSpriteParams},
|
||||
},
|
||||
};
|
||||
use wlx_common::desktop_finder;
|
||||
|
||||
pub type AsyncExecutor = Rc<smol::LocalExecutor<'static>>;
|
||||
|
||||
use crate::util::desktop_finder;
|
||||
|
||||
// the compiler wants to scream
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
pub fn get_desktop_file_icon_path(desktop_file: &desktop_finder::DesktopEntry) -> AssetPathOwned {
|
||||
|
||||
@@ -12,12 +12,11 @@ use wgui::{
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
};
|
||||
use wlx_common::dash_interface::BoxDashInterface;
|
||||
use wlx_common::{dash_interface::BoxDashInterface, desktop_finder::DesktopEntry};
|
||||
|
||||
use crate::{
|
||||
frontend::{FrontendTask, FrontendTasks},
|
||||
settings::SettingsIO,
|
||||
util::desktop_finder::DesktopEntry,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
|
||||
@@ -325,6 +324,7 @@ impl View {
|
||||
name: params.application.app_name.to_string(),
|
||||
args,
|
||||
resolution,
|
||||
icon: params.application.icon_path.as_ref().map(|x| x.as_ref().to_string()),
|
||||
userdata,
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -20,13 +20,9 @@ use wgui::{
|
||||
ConstructEssentials,
|
||||
},
|
||||
};
|
||||
use wlx_common::dash_interface::BoxDashInterface;
|
||||
use wlx_common::{dash_interface::BoxDashInterface, desktop_finder::DesktopEntry};
|
||||
|
||||
use crate::util::{
|
||||
self,
|
||||
desktop_finder::{self},
|
||||
various::get_desktop_file_icon_path,
|
||||
};
|
||||
use crate::util::{self, various::get_desktop_file_icon_path};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
@@ -94,13 +90,13 @@ impl View {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_desktop_entry_from_process(process: &packet_server::WvrProcess) -> Option<desktop_finder::DesktopEntry> {
|
||||
fn get_desktop_entry_from_process(process: &packet_server::WvrProcess) -> Option<DesktopEntry> {
|
||||
// TODO: refactor this after we ditch old wayvr-dashboard completely
|
||||
let Some(dfile_str) = process.userdata.get("desktop-entry") else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Ok(desktop_file) = serde_json::from_str::<desktop_finder::DesktopEntry>(dfile_str) else {
|
||||
let Ok(desktop_file) = serde_json::from_str::<DesktopEntry>(dfile_str) else {
|
||||
debug_assert!(false); // invalid json???
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ pub struct WvrProcessLaunchParams {
|
||||
pub exec: String,
|
||||
pub env: Vec<String>,
|
||||
pub args: String,
|
||||
pub icon: Option<String>,
|
||||
pub resolution: [u32; 2],
|
||||
pub userdata: HashMap<String, String>,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::assets::AssetProvider;
|
||||
|
||||
// a string which optionally has translation key in it
|
||||
@@ -98,10 +100,27 @@ impl I18n {
|
||||
}
|
||||
|
||||
let data_english = provider.load_from_path("lang/en.json")?;
|
||||
let data_translated = provider.load_from_path(&format!("lang/{lang}.json"))?;
|
||||
let path = format!("lang/{lang}.json");
|
||||
let data_translated = provider
|
||||
.load_from_path(&path)
|
||||
.with_context(|| path.clone())
|
||||
.context("Could not load translation file")?;
|
||||
|
||||
let json_root_fallback = serde_json::from_str(str::from_utf8(&data_english)?)?;
|
||||
let json_root_translated = serde_json::from_str(str::from_utf8(&data_translated)?)?;
|
||||
let json_root_fallback = serde_json::from_str(
|
||||
str::from_utf8(&data_english)
|
||||
.with_context(|| path.clone())
|
||||
.context("Translation file not valid UTF-8")?,
|
||||
)
|
||||
.with_context(|| path.clone())
|
||||
.context("Translation file not valid JSON")?;
|
||||
|
||||
let json_root_translated = serde_json::from_str(
|
||||
str::from_utf8(&data_translated)
|
||||
.with_context(|| path.clone())
|
||||
.context("Translation file not valid UTF-8")?,
|
||||
)
|
||||
.with_context(|| path.clone())
|
||||
.context("Translation file not valid JSON")?;
|
||||
|
||||
Ok(Self {
|
||||
json_root_translated,
|
||||
@@ -110,22 +129,16 @@ impl I18n {
|
||||
}
|
||||
|
||||
pub fn translate(&mut self, translation_key_full: &str) -> Rc<str> {
|
||||
let translation_key = translation_key_full
|
||||
.split_once(';')
|
||||
.map_or(translation_key_full, |(a, _)| a);
|
||||
let mut sections = translation_key_full.split(';');
|
||||
let translation_key = sections.next().map_or(translation_key_full, |a| a);
|
||||
|
||||
if let Some(translated) = find_translation(translation_key, &self.json_root_translated) {
|
||||
return Rc::from(translated);
|
||||
return Rc::from(format_translated(translated, sections));
|
||||
}
|
||||
|
||||
if let Some(translated_fallback) = find_translation(translation_key, &self.json_root_fallback) {
|
||||
log::warn!("missing translation for key \"{translation_key}\", using \"en\" instead");
|
||||
return Rc::from(translated_fallback);
|
||||
}
|
||||
|
||||
// not even found in fallback, check if the translation contains ";" (to be used as "MY_TRANSLATION_KEY;A fallback text")
|
||||
if let Some((idx, _)) = translation_key_full.match_indices(';').next() {
|
||||
return Rc::from(&translation_key_full[idx + 1..]);
|
||||
return Rc::from(format_translated(translated_fallback, sections));
|
||||
}
|
||||
|
||||
log::error!("missing translation for key \"{translation_key}\"");
|
||||
@@ -137,3 +150,28 @@ impl I18n {
|
||||
translated.replace(to_replace.0, to_replace.1)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_translated<'a, I>(format: &str, args: I) -> String
|
||||
where
|
||||
I: IntoIterator<Item = &'a str>,
|
||||
{
|
||||
let mut result = String::new();
|
||||
let mut args = args.into_iter();
|
||||
|
||||
let mut chars = format.chars().peekable();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '{' && chars.peek() == Some(&'}') {
|
||||
chars.next(); //consume }
|
||||
if let Some(arg) = args.next() {
|
||||
result.push_str(arg);
|
||||
} else {
|
||||
// no more args → keep literal {}
|
||||
result.push_str("{}");
|
||||
}
|
||||
} else {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ pub mod gfx;
|
||||
pub mod globals;
|
||||
pub mod i18n;
|
||||
pub mod layout;
|
||||
pub mod log;
|
||||
pub mod parser;
|
||||
pub mod renderer_vk;
|
||||
pub mod sound;
|
||||
|
||||
53
wgui/src/log.rs
Normal file
53
wgui/src/log.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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> {
|
||||
match self {
|
||||
Ok(ok) => Ok(ok),
|
||||
Err(error) => {
|
||||
log::warn!("{additional}: {error:?}");
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn log_err(self) -> Result<T, E> {
|
||||
match self {
|
||||
Ok(ok) => Ok(ok),
|
||||
Err(error) => {
|
||||
log::error!("{error:?}");
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn log_err_with(self, additional: &str) -> Result<T, E> {
|
||||
match self {
|
||||
Ok(ok) => Ok(ok),
|
||||
Err(error) => {
|
||||
log::error!("{additional}: {error:?}");
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,3 +22,7 @@ rodio = { version = "0.21.1", default-features = false, features = [
|
||||
"mp3",
|
||||
"hound",
|
||||
] }
|
||||
glob = "0.3.3"
|
||||
walkdir = "2.5.0"
|
||||
rust-ini = "0.21.3"
|
||||
identicons-svg = "0.1.0"
|
||||
|
||||
@@ -9,33 +9,3 @@ pub enum LeftRight {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
pub trait LogErr {
|
||||
fn log_err(self) -> Self;
|
||||
fn log_warn(self) -> 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_err(self) -> Result<T, E> {
|
||||
match self {
|
||||
Ok(ok) => Ok(ok),
|
||||
Err(error) => {
|
||||
log::error!("{error:?}");
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ use wayvr_ipc::{
|
||||
packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle},
|
||||
};
|
||||
|
||||
use crate::desktop_finder::DesktopFinder;
|
||||
|
||||
pub trait DashInterface<T> {
|
||||
fn window_list(&mut self, data: &mut T) -> anyhow::Result<Vec<WvrWindow>>;
|
||||
fn window_set_visible(&mut self, data: &mut T, handle: WvrWindowHandle, visible: bool) -> anyhow::Result<()>;
|
||||
@@ -12,6 +14,7 @@ pub trait DashInterface<T> {
|
||||
fn process_list(&mut self, data: &mut T) -> anyhow::Result<Vec<WvrProcess>>;
|
||||
fn process_terminate(&mut self, data: &mut T, handle: WvrProcessHandle) -> anyhow::Result<()>;
|
||||
fn recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()>;
|
||||
fn desktop_finder<'a>(&'a mut self, data: &'a mut T) -> &'a mut DesktopFinder;
|
||||
}
|
||||
|
||||
pub type BoxDashInterface<T> = Box<dyn DashInterface<T>>;
|
||||
|
||||
@@ -3,7 +3,7 @@ use wayvr_ipc::{
|
||||
packet_server::{WvrProcess, WvrProcessHandle, WvrWindow, WvrWindowHandle},
|
||||
};
|
||||
|
||||
use crate::{dash_interface::DashInterface, gen_id};
|
||||
use crate::{dash_interface::DashInterface, desktop_finder::DesktopFinder, gen_id};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EmuProcess {
|
||||
@@ -54,6 +54,7 @@ gen_id!(EmuProcessVec, EmuProcess, EmuProcessCell, EmuProcessHandle);
|
||||
pub struct DashInterfaceEmulated {
|
||||
processes: EmuProcessVec,
|
||||
windows: EmuWindowVec,
|
||||
desktop_finder: DesktopFinder,
|
||||
}
|
||||
|
||||
impl DashInterfaceEmulated {
|
||||
@@ -69,7 +70,14 @@ impl DashInterfaceEmulated {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
Self { processes, windows }
|
||||
let mut desktop_finder = DesktopFinder::new();
|
||||
desktop_finder.refresh();
|
||||
|
||||
Self {
|
||||
processes,
|
||||
windows,
|
||||
desktop_finder,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,4 +165,8 @@ impl DashInterface<()> for DashInterfaceEmulated {
|
||||
// stub!
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn desktop_finder<'a>(&'a mut self, _: &'a mut ()) -> &'a mut DesktopFinder {
|
||||
&mut self.desktop_finder
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::{
|
||||
collections::HashSet, ffi::OsStr, fmt::Debug, fs::exists, path::Path, rc::Rc, sync::Arc, thread::JoinHandle,
|
||||
collections::{HashMap, HashSet}, ffi::OsStr, fmt::Debug, fs::exists, path::Path, rc::Rc, sync::Arc, thread::JoinHandle,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use ini::Ini;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use walkdir::WalkDir;
|
||||
use wlx_common::cache_dir;
|
||||
use crate::cache_dir;
|
||||
|
||||
struct DesktopEntryOwned {
|
||||
exec_path: String,
|
||||
@@ -50,8 +50,8 @@ struct DesktopFinderParams {
|
||||
|
||||
pub struct DesktopFinder {
|
||||
params: Arc<DesktopFinderParams>,
|
||||
entry_cache: Vec<DesktopEntry>,
|
||||
bg_task: Option<JoinHandle<Vec<DesktopEntryOwned>>>,
|
||||
entry_cache: HashMap<String,DesktopEntry>,
|
||||
bg_task: Option<JoinHandle<HashMap<String,DesktopEntryOwned>>>,
|
||||
}
|
||||
|
||||
impl DesktopFinder {
|
||||
@@ -98,16 +98,16 @@ impl DesktopFinder {
|
||||
icon_folders,
|
||||
size_preferences,
|
||||
}),
|
||||
entry_cache: Vec::new(),
|
||||
entry_cache: HashMap::new(),
|
||||
bg_task: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_cache(params: Arc<DesktopFinderParams>) -> Vec<DesktopEntryOwned> {
|
||||
fn build_cache(params: Arc<DesktopFinderParams>) -> HashMap<String, DesktopEntryOwned> {
|
||||
let start = Instant::now();
|
||||
|
||||
let mut known_files = HashSet::new();
|
||||
let mut entries = Vec::<DesktopEntryOwned>::new();
|
||||
let mut entries = HashMap::<String, DesktopEntryOwned>::new();
|
||||
|
||||
let icons_folder = cache_dir::get_path("icons");
|
||||
if !std::fs::exists(&icons_folder).unwrap_or(false) {
|
||||
@@ -131,6 +131,9 @@ impl DesktopFinder {
|
||||
}
|
||||
|
||||
let file_name = entry.file_name().to_string_lossy();
|
||||
let Some(app_id) = Path::new(entry.file_name()).file_stem().map(|x| x.to_string_lossy().to_string()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if known_files.contains(file_name.as_ref()) {
|
||||
// as per xdg spec, user entries of the same filename will override system entries
|
||||
@@ -220,7 +223,7 @@ impl DesktopFinder {
|
||||
|
||||
known_files.insert(file_name.to_string());
|
||||
|
||||
entries.push(DesktopEntryOwned {
|
||||
entries.insert(app_id, DesktopEntryOwned {
|
||||
app_name: String::from(app_name),
|
||||
exec_path: String::from(exec_path),
|
||||
exec_args: exec_args.join(" "),
|
||||
@@ -265,7 +268,7 @@ impl DesktopFinder {
|
||||
None
|
||||
}
|
||||
|
||||
fn create_icon(desktop_entry_name: &str) -> anyhow::Result<String> {
|
||||
pub fn create_icon(desktop_entry_name: &str) -> anyhow::Result<String> {
|
||||
let relative_path = format!("icons/{}.svg", desktop_entry_name);
|
||||
let file_path = cache_dir::get_path(&relative_path).to_string_lossy().to_string();
|
||||
|
||||
@@ -295,12 +298,16 @@ impl DesktopFinder {
|
||||
};
|
||||
|
||||
self.entry_cache.clear();
|
||||
for entry in entries {
|
||||
self.entry_cache.push(entry.into());
|
||||
for (app_id, entry) in entries {
|
||||
self.entry_cache.insert(app_id, entry.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_entries(&mut self) -> Vec<DesktopEntry> {
|
||||
pub fn get_cached_entry(&self, app_id: &str) -> Option<&DesktopEntry> {
|
||||
self.entry_cache.get(app_id)
|
||||
}
|
||||
|
||||
pub fn find_entries(&mut self) -> HashMap<String, DesktopEntry> {
|
||||
self.wait_for_entries();
|
||||
self.entry_cache.clone()
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub mod common;
|
||||
pub mod config;
|
||||
pub mod dash_interface;
|
||||
pub mod dash_interface_emulated;
|
||||
pub mod desktop_finder;
|
||||
mod handle;
|
||||
pub mod overlays;
|
||||
pub mod timestep;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use idmap_derive::IntegerId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -21,12 +23,15 @@ pub enum ToastDisplayMethod {
|
||||
pub enum BackendAttrib {
|
||||
Stereo,
|
||||
MouseTransform,
|
||||
Icon,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum BackendAttribValue {
|
||||
Stereo(StereoMode),
|
||||
MouseTransform(MouseTransform),
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
Icon(Arc<str>),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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(
|
||||
¶ms.name,
|
||||
¶ms.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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user