Skip to content

Commit 03f5eb1

Browse files
committed
fix(tb): notifications
1 parent 680cd23 commit 03f5eb1

File tree

14 files changed

+398
-155
lines changed

14 files changed

+398
-155
lines changed

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ windows = { workspace = true, features = [
169169
"Win32_UI_Shell_PropertiesSystem",
170170
"Win32_UI_Accessibility",
171171
"Win32_UI_Controls",
172+
"Win32_UI_Notifications",
172173
"Win32_Graphics_Dwm",
173174
"Win32_Graphics_Gdi",
174175
"Win32_UI_HiDpi",

changelog.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22

3-
## [Unreleased]
3+
## [Unreleased]
4+
### fix
5+
- notifications not showing or working as expected.
6+
47
## [2.4.2]
58
### features
69
- tiling window manager animations.

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"@commitlint/config-conventional": "^19.2.2",
4747
"@nyariv/sandboxjs": "^0.8.24",
4848
"@reduxjs/toolkit": "^2.5.0",
49-
"@seelen-ui/lib": "^2.4.1-next.20250902085054",
49+
"@seelen-ui/lib": "^2.4.1-next.20250911151945",
5050
"@seelen/translation-toolkit": "^1.1.10",
5151
"@stylistic/eslint-plugin": "^1.6.2",
5252
"@types/d3": "^7.4.3",

src/background/exposed.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::collections::HashMap;
2+
use std::os::windows::process::CommandExt;
23
use std::path::PathBuf;
34

45
use seelen_core::state::RelaunchArguments;
@@ -8,6 +9,7 @@ use seelen_core::{command_handler_list, system_state::Color};
89
use tauri::{Builder, WebviewWindow, Wry};
910
use tauri_plugin_shell::ShellExt;
1011
use translators::Translator;
12+
use windows::Win32::System::Threading::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW};
1113

1214
use crate::app::{get_app_handle, Seelen};
1315
use crate::error::Result;
@@ -27,8 +29,17 @@ use crate::windows_api::WindowsApi;
2729
use crate::{log_error, utils};
2830

2931
#[tauri::command(async)]
30-
fn open_file(path: String) -> Result<()> {
31-
open::that_detached(path)?;
32+
pub fn open_file(path: String) -> Result<()> {
33+
std::process::Command::new("cmd")
34+
.raw_arg("/c")
35+
.raw_arg("start")
36+
.raw_arg("\"\"")
37+
.raw_arg(format!("\"{path}\""))
38+
.creation_flags(CREATE_NO_WINDOW.0 | CREATE_NEW_PROCESS_GROUP.0)
39+
.stdin(std::process::Stdio::null())
40+
.stdout(std::process::Stdio::null())
41+
.stderr(std::process::Stdio::null())
42+
.spawn()?;
3243
Ok(())
3344
}
3445

@@ -44,7 +55,7 @@ fn select_file_on_explorer(path: String) -> Result<()> {
4455

4556
#[tauri::command(async)]
4657
async fn run(
47-
program: PathBuf,
58+
program: String,
4859
args: Option<RelaunchArguments>,
4960
working_dir: Option<PathBuf>,
5061
) -> Result<()> {
@@ -55,13 +66,15 @@ async fn run(
5566
},
5667
None => String::new(),
5768
};
69+
5870
log::trace!("Running: {program:?} {args} in {working_dir:?}");
5971

6072
// we create a link file to trick with explorer into a separated process
6173
// and without elevation in case Seelen UI was running as admin
6274
// this could take some delay like is creating a file but just are some milliseconds
6375
// and this exposed funtion is intended to just run certain times
64-
let lnk_file = WindowsApi::create_temp_shortcut(&program, &args, working_dir.as_deref())?;
76+
let lnk_file =
77+
WindowsApi::create_temp_shortcut(&PathBuf::from(program), &args, working_dir.as_deref())?;
6578
get_app_handle()
6679
.shell()
6780
.command(SEELEN_COMMON.system_dir().join("explorer.exe"))

src/background/modules/notifications/application.rs

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ use std::{
99

1010
use lazy_static::lazy_static;
1111
use parking_lot::Mutex;
12-
use seelen_core::system_state::{AppNotification, Toast, ToastBindingEntry};
12+
use seelen_core::system_state::{
13+
AppNotification, Toast, ToastActionActivationType, ToastBindingChild, ToastText,
14+
};
1315
use tauri::Manager;
1416
use windows::{
1517
ApplicationModel::AppInfo,
@@ -27,10 +29,10 @@ use crate::{
2729
app::get_app_handle,
2830
error::Result,
2931
event_manager, log_error,
30-
modules::uwp::get_hightest_quality_posible,
32+
modules::{start::application::START_MENU_MANAGER, uwp::get_hightest_quality_posible},
3133
trace_lock,
3234
utils::{convert_file_to_src, icon_extractor::extract_and_save_icon_umid, spawn_named_thread},
33-
windows_api::traits::EventRegistrationTokenExt,
35+
windows_api::{traits::EventRegistrationTokenExt, types::AppUserModelId, WindowsApi},
3436
};
3537

3638
lazy_static! {
@@ -183,20 +185,26 @@ impl NotificationManager {
183185
}
184186

185187
fn clean_toast(toast: &mut Toast, umid: &str) -> Result<()> {
188+
if toast.launch.is_none() {
189+
toast.launch = Some(format!("shell:AppsFolder\\{umid}"));
190+
toast.activation_type = ToastActionActivationType::Protocol;
191+
}
192+
186193
let package_path = AppInfo::GetFromAppUserModelId(&umid.into())
187194
.and_then(|info| info.Package())
188195
.and_then(|package| package.InstalledPath())
189196
.map(|path| PathBuf::from(path.to_os_string()));
190197

191-
for entry in &mut toast.visual.binding.entries {
192-
let ToastBindingEntry::Image(image) = entry else {
198+
for entry in &mut toast.visual.binding.children {
199+
let ToastBindingChild::Image(image) = entry else {
193200
continue;
194201
};
195202

196203
if image.src.is_empty() {
197204
continue;
198205
}
199206

207+
log::trace!("Resolving image src:e \"{}\"", image.src);
200208
let uri = Uri::CreateUri(&image.src.clone().into())?;
201209
let scheme = uri.SchemeName()?.to_string_lossy();
202210
let uri_path = PathBuf::from(
@@ -252,7 +260,14 @@ impl NotificationManager {
252260
image.src = convert_file_to_src(&path);
253261
}
254262
"file" => {
255-
image.src = convert_file_to_src(&uri_path);
263+
image.src = if uri_path.exists() {
264+
convert_file_to_src(&uri_path)
265+
} else {
266+
// telegram desktop from ms store uses file intead of ms-appdata
267+
// so this causes the image to be missing, windows doesn't show image as well
268+
// so we decide to follow same behavior.
269+
"".to_string()
270+
}
256271
}
257272
_ => {}
258273
}
@@ -280,15 +295,13 @@ impl NotificationManager {
280295
let app_umid = app_info.AppUserModelId()?;
281296

282297
let visuals = notification.Visual()?;
283-
let text_sequence = visuals
284-
.GetBinding(&KnownNotificationBindings::ToastGeneric()?)?
285-
.GetTextElements()?;
286-
let mut notification_text = Vec::new();
287-
for text in text_sequence {
288-
let text = text.Text()?.to_string_lossy().trim().to_string();
289-
if !text.is_empty() {
290-
notification_text.push(text);
291-
}
298+
let binding = visuals.GetBinding(&KnownNotificationBindings::ToastGeneric()?)?;
299+
let text_sequence = binding.GetTextElements()?;
300+
301+
let mut notification_text = String::new();
302+
for text in &text_sequence {
303+
let text = text.Text()?.to_string_lossy().trim().replace("\r\n", "\n");
304+
notification_text.push_str(&text);
292305
}
293306

294307
let history = self.manager.History()?;
@@ -304,29 +317,46 @@ impl NotificationManager {
304317
for toast_notification in toast_notifications {
305318
// this can be null when the notification count is bigger than the max allowed by default 20
306319
if let Ok(content) = toast_notification.Content() {
307-
let data = content.GetXml()?.to_string();
308-
let mut toast: Toast = quick_xml::de::from_str(&data)?;
309-
310-
let mut toast_text = Vec::new();
311-
for entry in &toast.visual.binding.entries {
312-
if let ToastBindingEntry::Text(text) = entry {
313-
if !text.content.is_empty() {
314-
toast_text.push(text.content.clone().replace("\r\n", "\n"));
315-
}
316-
}
317-
}
320+
let toast_xml = content.GetXml()?.to_string();
321+
let mut toast: Toast = quick_xml::de::from_str(&toast_xml)?;
322+
let toast_text = get_text_from_toast(&toast);
318323

319324
if notification_text == toast_text {
320325
Self::clean_toast(&mut toast, &app_umid.to_string())?;
321326
notification_content = Some(toast);
327+
328+
println!(
329+
"??????????? Group {:?} - Tag: {:?}",
330+
toast_notification.Group(),
331+
toast_notification.Tag()
332+
);
322333
break;
323334
}
324335
}
325336
}
326337

327-
if notification_content.is_none() {
328-
log::debug!("NONE FOR {notification_text:#?}");
329-
}
338+
let notification_content = match notification_content {
339+
Some(content) => content,
340+
None => {
341+
log::debug!("Toast content not found, generating one from plain text");
342+
let mut toast = Toast::default();
343+
let content = &mut toast.visual.binding.children;
344+
for text in text_sequence {
345+
let text = text
346+
.Text()?
347+
.to_string_lossy()
348+
.replace("\r\n", "\n")
349+
.trim()
350+
.to_owned();
351+
content.push(ToastBindingChild::Text(ToastText {
352+
id: None,
353+
content: text,
354+
}));
355+
}
356+
Self::clean_toast(&mut toast, &app_umid.to_string())?;
357+
toast
358+
}
359+
};
330360

331361
// pre-extraction to avoid flickering on the ui
332362
extract_and_save_icon_umid(&app_umid.to_string().into());
@@ -337,9 +367,36 @@ impl NotificationManager {
337367
app_name: display_info.DisplayName()?.to_string(),
338368
app_description: display_info.Description()?.to_string(),
339369
date: u_notification.CreationTime()?.UniversalTime,
340-
content: notification_content.ok_or("Failed to get notification content")?,
370+
content: notification_content,
341371
});
342372
self.notifications.sort_by(|a, b| b.date.cmp(&a.date));
343373
Ok(())
344374
}
345375
}
376+
377+
fn get_text_from_toast(toast: &Toast) -> String {
378+
let mut text = String::new();
379+
for entry in &toast.visual.binding.children {
380+
// text inside groups are intended to be ignored for the comparison
381+
if let ToastBindingChild::Text(entry) = entry {
382+
text.push_str(entry.content.replace("\r\n", "\n").trim());
383+
}
384+
}
385+
text
386+
}
387+
388+
pub fn get_toast_activator_clsid(app_umid: &AppUserModelId) -> Result<String> {
389+
match app_umid {
390+
AppUserModelId::PropertyStore(umid) => {
391+
let guard = START_MENU_MANAGER.load();
392+
if let Some(item) = guard.get_by_file_umid(umid) {
393+
let clsid = WindowsApi::get_file_toast_activator(&item.path)?;
394+
return Ok(clsid);
395+
}
396+
}
397+
_ => {
398+
// todo search for the clsid in the AppManifest
399+
}
400+
};
401+
Err(format!("Unable to get toast activator clsid for: {app_umid:?}").into())
402+
}

0 commit comments

Comments
 (0)