Compare commits

..
Author SHA1 Message Date
galister 5be6b162ed fix headless mode dummy screen & restart propagation 2026-10-28 16:41:43 +09:00
galister 7061d6e5aa watch desktop entry changes 2026-10-28 16:25:42 +09:00
7 changed files with 488 additions and 145 deletions

No files matched your search

Generated
+29 -7
View File
@@ -1744,7 +1744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -2479,6 +2479,26 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
[[package]]
name = "inotify"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
dependencies = [
"bitflags 2.13.0",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d"
dependencies = [
"libc",
]
[[package]]
name = "input"
version = "0.9.1"
@@ -3215,7 +3235,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -4125,7 +4145,7 @@ version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"itertools 0.14.0",
"log",
"multimap",
@@ -4726,7 +4746,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -5459,10 +5479,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.4.3",
"once_cell",
"rustix 1.1.4",
"windows-sys 0.52.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -6422,7 +6442,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]
@@ -6860,6 +6880,8 @@ dependencies = [
"identicons-svg",
"idmap",
"idmap-derive",
"inotify",
"libc",
"log",
"rodio",
"serde",
+38 -19
View File
@@ -180,43 +180,37 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
setup_signal_hooks()?;
let mut used_backend = None;
auto_run(args, &mut used_backend);
auto_run(&args, &mut used_backend);
if RESTART.load(Ordering::Relaxed) {
log::warn!("Restarting...");
let exe = std::env::current_exe()?;
let mut args = vec!["--replace", "--show"];
match used_backend {
Some(XrBackend::OpenXR) => args.push("--openxr"),
Some(XrBackend::OpenVR) => args.push("--openvr"),
_ => {}
}
let _ = Command::new(exe).args(args).spawn();
let _ = Command::new(exe)
.args(restart_args(&args, used_backend))
.spawn();
}
Ok(())
}
#[allow(unused_mut, clippy::similar_names)]
fn auto_run(args: Args, used_backend: &mut Option<XrBackend>) {
fn auto_run(args: &Args, out_backend: &mut Option<XrBackend>) {
let mut tried_xr = false;
let mut tried_vr = false;
loop {
#[cfg(feature = "openxr")]
if !args_get_openvr(&args) {
if !args_get_openvr(args) {
use crate::backend::{BackendError, openxr::openxr_run};
tried_xr = true;
match openxr_run(&args, init_run_params(args.no_audio)) {
match openxr_run(args, init_run_params(args.no_audio)) {
Ok(()) => {
used_backend.replace(XrBackend::OpenXR);
out_backend.replace(XrBackend::OpenXR);
return;
}
Err(BackendError::NotSupported) => (),
Err(e) => {
used_backend.replace(XrBackend::OpenXR);
out_backend.replace(XrBackend::OpenXR);
log::error!("{e:?}");
return;
}
@@ -224,17 +218,17 @@ fn auto_run(args: Args, used_backend: &mut Option<XrBackend>) {
}
#[cfg(feature = "openvr")]
if !args_get_openxr(&args) {
if !args_get_openxr(args) {
use crate::backend::{BackendError, openvr::openvr_run};
tried_vr = true;
match openvr_run(&args, init_run_params(args.no_audio)) {
match openvr_run(args, init_run_params(args.no_audio)) {
Ok(()) => {
used_backend.replace(XrBackend::OpenVR);
out_backend.replace(XrBackend::OpenVR);
return;
}
Err(BackendError::NotSupported) => (),
Err(e) => {
used_backend.replace(XrBackend::OpenVR);
out_backend.replace(XrBackend::OpenVR);
log::error!("{e:?}");
return;
}
@@ -303,6 +297,31 @@ const fn args_get_openxr(args: &Args) -> bool {
ret
}
fn restart_args(args: &Args, backend: Option<XrBackend>) -> Vec<&'static str> {
let mut flags = vec!["--replace", "--show"];
match backend {
Some(XrBackend::OpenXR) => flags.push("--openxr"),
Some(XrBackend::OpenVR) => flags.push("--openvr"),
None => {}
}
if args.headless {
flags.push("--headless");
}
if args.no_autostart {
flags.push("--no-autostart");
}
if args.install {
flags.push("--install");
}
if args.no_audio {
flags.push("--no-audio");
}
flags
}
#[allow(clippy::single_match_else)]
fn setup_signal_hooks() -> anyhow::Result<()> {
let mut signals = Signals::new([SIGINT, SIGTERM, SIGUSR1])?;
+5 -2
View File
@@ -9,6 +9,7 @@ use glam::{Affine3A, Vec3, Vec3A};
use slotmap::{Key, SecondaryMap, SlotMap};
use wgui::{i18n::Translation, log::LogErr};
use wlx_common::{
DesktopBackend,
astr_containers::{AStrMap, AStrMapExt},
config::SerializedWindowSet,
overlays::{BackendAttrib, BackendAttribValue, ToastTopic},
@@ -84,6 +85,8 @@ where
};
if headless {
app.screens.clear();
app.feats.desktop_backend = DesktopBackend::Headless;
log::info!("Running in headless mode; keyboard will be en-US");
} else {
// create one window set for each screen.
@@ -92,12 +95,12 @@ where
match create_screens(app) {
Ok((data, backend)) => {
app.feats.desktop_backend = backend;
let last_idx = data.screens.len() - 1;
let screen_count = data.screens.len();
for (idx, (meta, mut config)) in data.screens.into_iter().enumerate() {
config.show_on_spawn = true;
me.add(OverlayWindowData::from_config(config), app);
if idx < last_idx {
if idx + 1 < screen_count {
me.sets.push(OverlayWindowSet::default());
me.switch_to_set(app, Some(me.current_set.unwrap() + 1), false);
}
+2
View File
@@ -15,6 +15,7 @@ chrono.workspace = true
glam.workspace = true
idmap = { workspace = true, features = ["serde"] }
idmap-derive.workspace = true
libc.workspace = true
log.workspace = true
serde = { workspace = true, features = ["rc"] }
serde_json.workspace = true
@@ -34,3 +35,4 @@ rodio = {
}
walkdir = "2.5.0"
wgui = { path = "../wgui/" }
inotify = { version = "0.11.5", default-features = false }
+183 -117
View File
@@ -3,14 +3,14 @@ use std::{
ffi::OsStr,
fmt::Debug,
fs::exists,
path::Path,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
thread::JoinHandle,
time::Instant,
};
use crate::{cache_dir, locale::Language};
use crate::{cache_dir, desktop_watcher::DesktopWatcher, locale::Language};
use anyhow::Context;
use freedesktop_desktop_entry::DesktopEntry as FreedesktopDesktopEntry;
use serde::{Deserialize, Serialize};
@@ -57,14 +57,32 @@ const CATEGORY_TYPE_BLOCKLIST: [&str; 1] = ["ConsoleOnly"];
struct DesktopFinderParams {
size_preferences: Vec<&'static OsStr>,
icon_folders: Vec<String>,
app_folders: Vec<String>,
app_folders: Vec<PathBuf>,
langs: Vec<String>,
}
struct CachedFileOwned {
root_index: usize,
entry: DesktopEntryOwned,
}
struct CachedFile {
root_index: usize,
entry: Rc<DesktopEntry>,
}
struct CacheBuild {
files: HashMap<PathBuf, CachedFileOwned>,
watcher: Option<DesktopWatcher>,
}
pub struct DesktopFinder {
params: Arc<DesktopFinderParams>,
// include all desktop files, so that removing a higher-prio one will reveal the lower-prio underneath
files: HashMap<PathBuf, CachedFile>,
entry_cache: HashMap<String, Rc<DesktopEntry>>,
bg_task: Option<JoinHandle<HashMap<String, DesktopEntryOwned>>>,
watcher: Option<DesktopWatcher>,
bg_task: Option<JoinHandle<CacheBuild>>,
}
impl DesktopFinder {
@@ -76,13 +94,8 @@ impl DesktopFinder {
let mut icon_folders = vec![];
if let Some(data_home) = xdg.get_data_home() {
app_folders.push(data_home.join("applications").to_string_lossy().to_string());
app_folders.push(
data_home
.join("flatpak/exports/share/applications")
.to_string_lossy()
.to_string(),
);
app_folders.push(data_home.join("applications"));
app_folders.push(data_home.join("flatpak/exports/share/applications"));
icon_folders.push(data_home.join("icons").to_string_lossy().to_string());
icon_folders.push(
data_home
@@ -97,7 +110,7 @@ impl DesktopFinder {
// /usr/share and such
for data_dir in xdg.get_data_dirs() {
app_folders.push(data_dir.join("applications").to_string_lossy().to_string());
app_folders.push(data_dir.join("applications"));
icon_folders.push(data_dir.join("icons").to_string_lossy().to_string());
}
@@ -122,126 +135,107 @@ impl DesktopFinder {
size_preferences,
langs,
}),
files: HashMap::new(),
entry_cache: HashMap::new(),
watcher: None,
bg_task: None,
}
}
fn build_cache(params: Arc<DesktopFinderParams>) -> HashMap<String, DesktopEntryOwned> {
let start = Instant::now();
fn read_single(params: &DesktopFinderParams, path: &Path) -> Option<DesktopEntryOwned> {
let file_path = path.display();
let file_name = path.file_name()?.to_string_lossy();
let desktop = match FreedesktopDesktopEntry::from_path(path, Some(&params.langs)) {
Ok(desktop) => desktop,
Err(e) => {
log::debug!("Failed to read .desktop file {}: {:?}, skipping", file_path, e);
return None;
}
};
let mut known_files = HashSet::new();
let mut entries = HashMap::<String, DesktopEntryOwned>::new();
if desktop.groups.desktop_entry().is_none() {
log::debug!("Failed to get [Desktop Entry] section for file {}, skipping", file_path);
return None;
}
if desktop.only_show_in().is_some() || desktop.terminal() || desktop.no_display() || desktop.hidden() {
return None;
}
let exec = desktop.exec()?;
if CMD_BLOCKLIST.iter().any(|blocked| exec.contains(blocked)) {
return None;
}
let exec_argv = desktop
.parse_exec_with_uris(&[], &params.langs)
.into_iter()
.flatten()
.map(|x| x.into_boxed_str())
.collect::<Box<[_]>>();
if exec_argv.is_empty() {
log::debug!("Invalid Exec in desktop file {}, skipping", file_path);
return None;
}
let app_name = desktop.name(&params.langs)?;
let mut vec_categories = Vec::<Box<str>>::new();
for cat in desktop.categories().unwrap_or_default() {
if CATEGORY_TYPE_BLOCKLIST.contains(&cat) {
return None;
}
if !cat.is_empty() {
vec_categories.push(cat.into());
}
}
let icon_path = desktop
.icon()
.and_then(|icon_name| Self::find_icon(params, icon_name))
.or_else(|| Self::create_icon(&file_name).ok());
Some(DesktopEntryOwned {
app_id: desktop.id().into(),
app_name: app_name.into(),
exec_argv,
icon_path: icon_path.map(|i| i.into_boxed_str()),
categories: vec_categories.into_boxed_slice(),
})
}
fn build_cache_full(params: Arc<DesktopFinderParams>) -> CacheBuild {
let start = Instant::now();
// install watch before scan so edits during scan remain in inotify event queue
let watcher = match DesktopWatcher::new(&params.app_folders) {
Ok(watcher) => Some(watcher),
Err(err) => {
log::warn!("Could not watch .desktop files: {err}");
None
}
};
let mut files = HashMap::new();
let icons_folder = cache_dir::get_path("icons");
if !std::fs::exists(&icons_folder).unwrap_or(false) {
let _ = std::fs::create_dir(&icons_folder);
}
for path in &params.app_folders {
log::debug!("Searching desktop entries in path {}", path);
'entries: for entry in WalkDir::new(path)
for (root_index, root) in params.app_folders.iter().enumerate() {
log::debug!("Searching desktop entries in path {}", root.display());
for entry in WalkDir::new(root)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| !e.file_type().is_dir())
.filter_map(Result::ok)
.filter(|entry| !entry.file_type().is_dir())
{
let Some(extension) = Path::new(entry.file_name()).extension() else {
continue;
};
if extension != "desktop" {
continue; // ignore, go on
}
let file_name = entry.file_name().to_string_lossy();
let file_path = entry.path().display().to_string();
let desktop = match FreedesktopDesktopEntry::from_path(entry.path(), Some(&params.langs)) {
Ok(desktop) => desktop,
Err(e) => {
log::debug!("Failed to read .desktop file {}: {:?}, skipping", file_path, e);
continue;
}
};
if desktop.groups.desktop_entry().is_none() {
log::debug!("Failed to get [Desktop Entry] section for file {}, skipping", file_path);
let path = entry.path();
if path.extension() != Some(OsStr::new("desktop")) || files.contains_key(path) {
continue;
}
// prefer entries from earlier data directories
let app_id = desktop.id();
if known_files.contains(app_id) {
continue;
if let Some(parsed) = Self::read_single(&params, path) {
files.insert(path.to_path_buf(), CachedFileOwned { root_index, entry: parsed });
}
if desktop.only_show_in().is_some() || desktop.terminal() || desktop.no_display() || desktop.hidden() {
continue;
}
let Some(exec) = desktop.exec() else {
log::debug!("Failed to get desktop entry Exec for file {}, skipping", file_path);
continue;
};
if CMD_BLOCKLIST.iter().any(|blocked| exec.contains(blocked)) {
continue;
}
let exec_argv = desktop
.parse_exec_with_uris(&[], &params.langs)
.into_iter()
.flatten()
.map(|x| x.into_boxed_str())
.collect::<Box<[_]>>();
if exec_argv.is_empty() {
log::debug!("Invalid Exec in desktop file {}, skipping", file_path);
continue;
};
let Some(app_name) = desktop.name(&params.langs) else {
log::debug!(
"Failed to get desktop entry application name for file {}, skipping",
file_path
);
continue;
};
let icon_path = desktop
.icon()
.and_then(|icon_name| Self::find_icon(&params, icon_name))
.or_else(|| Self::create_icon(&file_name).ok());
let mut vec_categories = Vec::<Box<str>>::new();
for cat in desktop.categories().unwrap_or_default() {
if CATEGORY_TYPE_BLOCKLIST.contains(&cat) {
continue 'entries;
}
if !cat.is_empty() {
vec_categories.push(cat.into());
}
}
known_files.insert(app_id.to_owned());
entries.insert(
app_id.to_owned(),
DesktopEntryOwned {
app_id: app_id.into(),
app_name: app_name.into(),
exec_argv,
icon_path: icon_path.map(|i| i.into_boxed_str()),
categories: vec_categories.into_boxed_slice(),
},
);
}
}
log::debug!("Desktop entry & icon scan took {:?}", start.elapsed());
entries
CacheBuild { files, watcher }
}
fn find_icon(params: &DesktopFinderParams, icon_name: &str) -> Option<String> {
@@ -306,29 +300,101 @@ impl DesktopFinder {
return;
};
let Ok(entries) = bg_task.join() else {
let Ok(CacheBuild { files, watcher }) = bg_task.join() else {
log::warn!("Desktop entry scan thread panicked");
return;
};
self.files = files
.into_iter()
.map(|(path, file)| {
(path, CachedFile { root_index: file.root_index, entry: Rc::new(file.entry.into()) })
})
.collect();
self.watcher = watcher;
let mut files: Vec<_> = self.files.iter().collect();
files.sort_by(|(path_a, a), (path_b, b)| {
a.root_index.cmp(&b.root_index).then_with(|| path_a.cmp(path_b))
});
self.entry_cache.clear();
for (app_id, entry) in entries {
self.entry_cache.insert(app_id, Rc::new(entry.into()));
for (_, file) in files {
self.entry_cache
.entry(file.entry.app_id.to_string())
.or_insert_with(|| Rc::clone(&file.entry));
}
}
pub fn get_cached_entry(&self, app_id: &str) -> Option<&Rc<DesktopEntry>> {
fn update_app_id(&mut self, app_id: &str) {
let best = self
.files
.iter()
.filter(|(_, file)| file.entry.app_id.as_ref() == app_id)
.min_by(|(path_a, a), (path_b, b)| {
a.root_index.cmp(&b.root_index).then_with(|| path_a.cmp(path_b))
})
.map(|(_, file)| Rc::clone(&file.entry));
if let Some(best) = best {
self.entry_cache.insert(app_id.to_owned(), best);
} else {
self.entry_cache.remove(app_id);
}
}
fn sync_file_changes(&mut self) {
self.wait_for_entries();
let Some(watcher) = self.watcher.as_mut() else {
return;
};
let changes = watcher.collect_changes();
if changes.overflowed {
log::warn!("Desktop file inotify event queue overflowed; rebuilding cache");
self.refresh();
self.wait_for_entries();
return;
}
let mut affected_ids = HashSet::new();
if !changes.removed_directories.is_empty() {
self.files.retain(|path, file| {
let removed = changes.removed_directories.iter().any(|directory| path.starts_with(directory));
if removed {
affected_ids.insert(file.entry.app_id.to_string());
}
!removed
});
}
for path in changes.files {
if let Some(previous) = self.files.remove(&path) {
affected_ids.insert(previous.entry.app_id.to_string());
}
let root_index = self.params.app_folders.iter().position(|root| path.starts_with(root));
if let Some(root_index) = root_index
&& let Some(entry) = Self::read_single(&self.params, &path)
{
affected_ids.insert(entry.app_id.to_string());
self.files.insert(path, CachedFile { root_index, entry: Rc::new(entry.into()) });
}
}
for app_id in affected_ids {
self.update_app_id(&app_id);
}
}
pub fn get_cached_entry(&mut self, app_id: &str) -> Option<&Rc<DesktopEntry>> {
self.sync_file_changes();
self.entry_cache.get(app_id)
}
pub fn find_entries(&mut self) -> HashMap<String, Rc<DesktopEntry>> {
self.wait_for_entries();
self.sync_file_changes();
self.entry_cache.clone()
}
pub fn refresh(&mut self) {
let bg_task = std::thread::spawn({
let params = self.params.clone();
move || Self::build_cache(params)
move || Self::build_cache_full(params)
});
self.bg_task = Some(bg_task);
}
+230
View File
@@ -0,0 +1,230 @@
use std::{
collections::{HashMap, HashSet},
ffi::OsStr,
io,
path::{Path, PathBuf},
};
use inotify::{EventMask, Inotify, WatchDescriptor, WatchMask};
#[derive(Default)]
pub(crate) struct DesktopChanges {
pub files: HashSet<PathBuf>,
pub removed_directories: HashSet<PathBuf>,
pub overflowed: bool,
}
pub(crate) struct DesktopWatcher {
inotify: Inotify,
roots: Vec<PathBuf>,
by_wd: HashMap<WatchDescriptor, HashSet<PathBuf>>,
by_path: HashMap<PathBuf, WatchDescriptor>,
}
impl DesktopWatcher {
pub fn new(roots: &[PathBuf]) -> io::Result<Self> {
let mut watcher = Self {
inotify: Inotify::init()?,
roots: roots.to_vec(),
by_wd: HashMap::new(),
by_path: HashMap::new(),
};
for root in roots {
for ancestor in root.ancestors().skip(1) {
if ancestor.is_dir() {
watcher.watch_directory(ancestor);
}
}
watcher.watch_tree(root, None);
}
Ok(watcher)
}
fn is_related_directory(&self, path: &Path) -> bool {
self.roots
.iter()
.any(|root| path.starts_with(root) || root.starts_with(path))
}
fn is_application_file(&self, path: &Path) -> bool {
path.extension() == Some(OsStr::new("desktop"))
&& self.roots.iter().any(|root| path.starts_with(root))
}
fn watch_directory(&mut self, path: &Path) {
if self.by_path.contains_key(path) || !path.is_dir() {
return;
}
let mask = WatchMask::CREATE
| WatchMask::DELETE
| WatchMask::MOVED_FROM
| WatchMask::MOVED_TO
| WatchMask::MODIFY
| WatchMask::CLOSE_WRITE
| WatchMask::ATTRIB
| WatchMask::DELETE_SELF
| WatchMask::MOVE_SELF
| WatchMask::ONLYDIR;
let wd = match self.inotify.watches().add(path, mask) {
Ok(wd) => wd,
Err(err) => {
log::warn!(
"Unable to watch desktop directory {}: {err}",
path.display()
);
return;
}
};
self.by_wd
.entry(wd.clone())
.or_default()
.insert(path.to_path_buf());
self.by_path.insert(path.to_path_buf(), wd);
}
fn watch_tree(
&mut self,
directory: &Path,
mut existing_files: Option<&mut HashSet<PathBuf>>,
) {
let mut pending = vec![directory.to_path_buf()];
while let Some(dir) = pending.pop() {
if !self.is_related_directory(&dir) || !dir.is_dir() {
continue;
}
self.watch_directory(&dir);
let children = match std::fs::read_dir(&dir) {
Ok(children) => children,
Err(err) => {
log::debug!(
"Cannot read desktop directory {}: {err}",
dir.display()
);
continue;
}
};
for child in children.flatten() {
let path = child.path();
match child.file_type() {
Ok(kind) if kind.is_dir() => {
if self.is_related_directory(&path) {
pending.push(path);
}
}
Ok(_) => {
if self.is_application_file(&path)
&& let Some(files) = existing_files.as_deref_mut()
{
files.insert(path);
}
}
Err(_) => {}
}
}
}
}
fn unwatch_tree(&mut self, directory: &Path) {
let descendants: Vec<_> = self
.by_path
.iter()
.filter(|(path, _)| path.starts_with(directory))
.map(|(path, wd)| (path.clone(), wd.clone()))
.collect();
for (path, wd) in descendants {
self.by_path.remove(&path);
if let Some(paths) = self.by_wd.get_mut(&wd) {
paths.remove(&path);
if paths.is_empty() {
self.by_wd.remove(&wd);
let _ = self.inotify.watches().remove(wd);
}
}
}
}
pub fn collect_changes(&mut self) -> DesktopChanges {
let mut changes = DesktopChanges::default();
let mut buffer = [0u8; 64 * 1024];
loop {
let events = match self.inotify.read_events(&mut buffer) {
Ok(events) => events,
Err(err) if err.kind() == io::ErrorKind::WouldBlock => break,
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
Err(err) => {
log::warn!(
"Unable to read desktop directory inotify events: {err}"
);
changes.overflowed = true;
break;
}
};
for event in events {
if event.mask.contains(EventMask::Q_OVERFLOW) {
changes.overflowed = true;
continue;
}
if event.mask.contains(EventMask::IGNORED) {
if let Some(paths) = self.by_wd.remove(&event.wd) {
for path in paths {
self.by_path.remove(&path);
}
}
continue;
}
let Some(directories) = self.by_wd.get(&event.wd).cloned() else {
continue;
};
for directory in directories {
let path = match event.name {
Some(name) => directory.join(name),
None => directory,
};
if event.mask.intersects(
EventMask::DELETE_SELF | EventMask::MOVE_SELF,
) {
changes.removed_directories.insert(path.clone());
self.unwatch_tree(&path);
} else if event.mask.contains(EventMask::ISDIR) {
if event.mask.intersects(
EventMask::DELETE | EventMask::MOVED_FROM,
) {
changes.removed_directories.insert(path.clone());
self.unwatch_tree(&path);
}
if event.mask.intersects(
EventMask::CREATE | EventMask::MOVED_TO,
) {
self.watch_tree(&path, Some(&mut changes.files));
}
} else if self.is_application_file(&path) {
changes.files.insert(path);
}
}
}
}
changes
}
}
+1
View File
@@ -10,6 +10,7 @@ pub mod config_io;
pub mod dash_interface;
pub mod dash_interface_emulated;
pub mod data_dir;
mod desktop_watcher;
pub mod desktop_finder;
pub mod locale;
pub mod openxr_actions;