Skip to content

Commit 29b928f

Browse files
committed
πŸ› fix: πŸ› fix: auto-regenerate viewer-mode .desktop entries on startup
The previous commit aligned StartupWMClass with the viewer's GTK application_id, but existing .desktop files on disk retained the old mismatched value. Users previously had to open each webapp in the manager and re-save it to apply the correction. Introduce a one-shot migration, `regenerate_app_mode_desktops`, which runs on the manager's startup worker thread alongside `migrate_legacy_desktops`. It regenerates every `AppMode::App` entry using the standard desktop file builder. Execution is gated by a `.desktop-wmclass-aligned-v1` marker file in the data directory, ensuring the migration runs exactly once per user after upgrading. Browser-mode entries are excluded, as their wm_class derivation was never affected.
1 parent 22bc88f commit 29b928f

5 files changed

Lines changed: 79 additions & 14 deletions

File tree

β€Žcrates/webapps-core/src/desktop/mod.rsβ€Ž

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ mod tests {
9595
);
9696
}
9797

98+
#[test]
99+
fn derive_wm_class_app_mode_matches_viewer_application_id() {
100+
// Must equal the viewer's GTK application_id so Wayland compositors can
101+
// associate the window with the .desktop entry (otherwise the taskbar
102+
// shows the raw app_id and a generic icon).
103+
let w = app("https://cloud.talesam.org/apps/notes", AppMode::App);
104+
let cls = derive_wm_class(&w);
105+
let expected = format!(
106+
"br.com.biglinux.webapp.{}",
107+
desktop_file_id(&w.app_url)
108+
);
109+
assert_eq!(cls, expected);
110+
}
111+
98112
#[test]
99113
fn derive_wm_class_browser_mode_includes_prefix() {
100114
let w = app("https://web.whatsapp.com/", AppMode::Browser);

β€Žcrates/webapps-core/src/desktop/wm_class.rsβ€Ž

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
use crate::models::{AppMode, BrowserId, WebApp};
22

3+
use super::paths::desktop_file_id;
4+
35
pub(super) fn derive_wm_class(webapp: &WebApp) -> String {
46
match webapp.app_mode {
57
AppMode::App => {
6-
let app_id = webapp
7-
.app_url
8-
.replace("https://", "")
9-
.replace("http://", "")
10-
.replace('/', "_")
11-
.chars()
12-
.filter(|c| c.is_alphanumeric() || *c == '_' || *c == '-')
13-
.collect::<String>();
14-
format!("br.com.biglinux.webapp.{app_id}")
8+
// Must match the GTK application_id set by the viewer
9+
// (`br.com.biglinux.webapp.{desktop_file_id}`), so Wayland compositors
10+
// can associate the window with this desktop entry β€” otherwise the
11+
// taskbar falls back to displaying the raw app_id and a generic icon.
12+
format!("br.com.biglinux.webapp.{}", desktop_file_id(&webapp.app_url))
1513
}
1614
AppMode::Browser => {
1715
let url_class = browser_url_class(&webapp.app_url);

β€Žcrates/webapps-manager/src/service/migration/mod.rsβ€Ž

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@ mod shell;
44
use std::fs;
55

66
use webapps_core::config;
7-
use webapps_core::models::{WebApp, WebAppCollection};
7+
use webapps_core::desktop;
8+
use webapps_core::models::{AppMode, WebApp, WebAppCollection};
89

9-
use super::{save_webapps, webapps_json_path};
10+
use super::{load_webapps, save_webapps, webapps_json_path};
11+
12+
/// Marker indicating the viewer-mode `StartupWMClass` realignment migration ran.
13+
///
14+
/// Pre-v4.1 desktop entries for `AppMode::App` set `StartupWMClass` to a value
15+
/// that included the URL path, while the viewer's GTK `application_id` only
16+
/// uses the host. The mismatch prevented Wayland compositors from associating
17+
/// viewer windows with their `.desktop` file, so the taskbar fell back to the
18+
/// raw `app_id` and a generic icon. This marker records that the one-shot
19+
/// regeneration has run so we only do it once per user.
20+
const WMCLASS_MIGRATION_MARKER: &str = ".desktop-wmclass-aligned-v1";
1021

1122
pub fn migrate_legacy_desktops() -> usize {
1223
let json_path = webapps_json_path();
@@ -24,6 +35,42 @@ pub fn migrate_legacy_desktops() -> usize {
2435
persist_migrated_webapps(webapps)
2536
}
2637

38+
/// Regenerate `AppMode::App` desktop entries once, so existing installs pick
39+
/// up the corrected `StartupWMClass` without the user having to re-save each
40+
/// webapp in the manager.
41+
pub fn regenerate_app_mode_desktops() -> usize {
42+
let marker = config::data_dir().join(WMCLASS_MIGRATION_MARKER);
43+
if marker.exists() {
44+
return 0;
45+
}
46+
47+
let collection = load_webapps();
48+
let mut regenerated = 0;
49+
for app in &collection.webapps {
50+
if app.app_mode != AppMode::App {
51+
continue;
52+
}
53+
match desktop::install_desktop_entry(app) {
54+
Ok(()) => regenerated += 1,
55+
Err(err) => log::warn!(
56+
"Regenerate desktop entry for {}: {err}",
57+
app.app_name
58+
),
59+
}
60+
}
61+
62+
if let Err(err) = fs::create_dir_all(config::data_dir())
63+
.and_then(|()| fs::write(&marker, ""))
64+
{
65+
log::warn!(
66+
"Write StartupWMClass migration marker {}: {err}",
67+
marker.display()
68+
);
69+
}
70+
71+
regenerated
72+
}
73+
2774
fn collect_legacy_webapps(entries: fs::ReadDir) -> Vec<WebApp> {
2875
let mut webapps = Vec::new();
2976

β€Žcrates/webapps-manager/src/service/mod.rsβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub use crud::{
1313
};
1414
pub use icons::resolve_icon_path;
1515
pub use io::{export_webapps, import_webapps};
16-
pub use migration::migrate_legacy_desktops;
16+
pub use migration::{migrate_legacy_desktops, regenerate_app_mode_desktops};
1717
pub use repository::{load_webapps, save_webapps};
1818
pub use welcome::{mark_welcome_shown, should_show_welcome};
1919

β€Žcrates/webapps-manager/src/window/mod.rsβ€Ž

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,19 @@ pub fn build(app: &adw::Application) {
7676
ui_async::run_with_result(
7777
|| {
7878
let migrated = service::migrate_legacy_desktops();
79+
let regenerated = service::regenerate_app_mode_desktops();
7980
let webapps = service::load_webapps();
80-
(migrated, webapps)
81+
(migrated, regenerated, webapps)
8182
},
82-
move |(migrated, webapps): (usize, WebAppCollection)| {
83+
move |(migrated, regenerated, webapps): (usize, usize, WebAppCollection)| {
8384
if migrated > 0 {
8485
log::info!("Migrated {migrated} legacy webapps from .desktop files");
8586
}
87+
if regenerated > 0 {
88+
log::info!(
89+
"Regenerated {regenerated} viewer-mode .desktop entries (StartupWMClass alignment)"
90+
);
91+
}
8692
state::apply_webapps(&context_for_load.state, webapps);
8793
list::populate_list(&context_for_load);
8894
},

0 commit comments

Comments
Β (0)