fix: update all dependencies, remove pelite and directories crates
- Update iced 0.13 -> 0.14 (API migration: application builder, theme, font loading) - Update ksni 0.2 -> 0.3 (blocking API) - Update dirs 5 -> 6, toml 0.8 -> 1, reqwest 0.12 -> 0.13, rfd 0.15 -> 0.17, iced_fonts 0.1 -> 0.3 - Remove pelite dependency (PE file parsing was unreliable and unnecessary) - Remove directories dependency (consolidated with dirs crate) Closes #8, #9, #10
This commit is contained in:
Generated
+933
-1426
File diff suppressed because it is too large
Load Diff
+8
-14
@@ -19,7 +19,7 @@ clap = { version = "4", features = ["derive"] }
|
||||
|
||||
# Config serialisation
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
toml = "1"
|
||||
|
||||
# GitHub API responses
|
||||
serde_json = "1"
|
||||
@@ -27,28 +27,22 @@ serde_json = "1"
|
||||
# Error handling
|
||||
anyhow = "1"
|
||||
|
||||
# XDG config / data paths
|
||||
directories = "5"
|
||||
|
||||
# Home directory lookup
|
||||
dirs = "5"
|
||||
# XDG config / data / home paths
|
||||
dirs = "6"
|
||||
|
||||
# Terminal colour output
|
||||
owo-colors = "4"
|
||||
|
||||
# System tray via D-Bus StatusNotifierItem (KDE, GNOME+AppIndicator, Xfce, etc.)
|
||||
ksni = "0.2"
|
||||
ksni = { version = "0.3", features = ["blocking"] }
|
||||
|
||||
# HTTP for GE-Proton GitHub releases API
|
||||
reqwest = { version = "0.12", features = ["blocking", "json"] }
|
||||
reqwest = { version = "0.13", features = ["blocking", "json"] }
|
||||
|
||||
# GUI for the setup wizard
|
||||
iced = { version = "0.13", features = ["tokio"] }
|
||||
iced_fonts = { version = "0.1", features = ["bootstrap"] }
|
||||
iced = { version = "0.14", features = ["tokio"] }
|
||||
iced_fonts = { version = "0.3", features = ["bootstrap"] }
|
||||
tokio = { version = "1.52.1", features = ["rt"] }
|
||||
|
||||
# PE exe version info (game name detection)
|
||||
pelite = "0.10"
|
||||
|
||||
# Native file dialogs via XDG Desktop Portal
|
||||
rfd = "0.15"
|
||||
rfd = "0.17"
|
||||
|
||||
+2
-3
@@ -1,5 +1,4 @@
|
||||
use anyhow::{Context, Result};
|
||||
use directories::ProjectDirs;
|
||||
use owo_colors::OwoColorize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
@@ -234,9 +233,9 @@ impl Default for Config {
|
||||
|
||||
impl Config {
|
||||
pub fn config_path() -> Result<PathBuf> {
|
||||
let dirs = ProjectDirs::from("co.aleshym", "", "umutray")
|
||||
let config_dir = dirs::config_dir()
|
||||
.context("Could not determine config directory")?;
|
||||
Ok(dirs.config_dir().join("config.toml"))
|
||||
Ok(config_dir.join("umutray").join("config.toml"))
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Self> {
|
||||
|
||||
+2
-38
@@ -1,8 +1,6 @@
|
||||
use crate::config::{Config, Launcher};
|
||||
use anyhow::Result;
|
||||
use owo_colors::OwoColorize;
|
||||
use pelite::pe64::Pe as _;
|
||||
use pelite::pe32::Pe as _;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
@@ -280,9 +278,7 @@ fn scan_exe_dir(
|
||||
/// Epic `.egstore/*.item` JSON files at the game's installation root.
|
||||
/// 4. Launcher path — reads the game name from well-known directory
|
||||
/// structures laid down by the launcher (e.g. `Epic Games/<Name>/`).
|
||||
/// 5. PE version info — reads `ProductName` or `FileDescription` from
|
||||
/// the exe's embedded Windows version resource.
|
||||
/// 6. Nearest non-generic parent directory name, or raw exe stem.
|
||||
/// 5. Nearest non-generic parent directory name, or raw exe stem.
|
||||
/// No name generation — if the directory name is unknown, it is used
|
||||
/// as-is rather than being fabricated from the exe filename.
|
||||
///
|
||||
@@ -325,12 +321,7 @@ fn resolve_uncached(exe_path: &Path) -> String {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Stage 5 – PE version info embedded in the exe itself
|
||||
if let Some(name) = read_pe_product_name(exe_path) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Stage 6 – nearest non-generic parent directory, or raw exe stem.
|
||||
// Stage 5 – nearest non-generic parent directory, or raw exe stem.
|
||||
// No name generation: if we don't know, we say so honestly.
|
||||
prettify_exe_name(exe_path)
|
||||
}
|
||||
@@ -426,33 +417,6 @@ fn name_from_launcher_path(exe_path: &Path) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Read the `ProductName` (preferred) or `FileDescription` from the exe's
|
||||
/// embedded PE version info resource. This is the same data Windows uses for
|
||||
/// "Properties → Details" and is present in the vast majority of game exes.
|
||||
fn read_pe_product_name(exe_path: &Path) -> Option<String> {
|
||||
let map = pelite::FileMap::open(exe_path).ok()?;
|
||||
// Try 64-bit first, fall back to 32-bit.
|
||||
let vi = pelite::pe64::PeFile::from_bytes(&map)
|
||||
.ok()
|
||||
.and_then(|pe| pe.resources().ok()?.version_info().ok())
|
||||
.or_else(|| {
|
||||
pelite::pe32::PeFile::from_bytes(&map)
|
||||
.ok()?
|
||||
.resources().ok()?
|
||||
.version_info().ok()
|
||||
})?;
|
||||
let lang = *vi.translation().first()?;
|
||||
// ProductName is the canonical game title; FileDescription is a fallback.
|
||||
let name = vi
|
||||
.value(lang, "ProductName")
|
||||
.or_else(|| vi.value(lang, "FileDescription"))?;
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(name.to_string())
|
||||
}
|
||||
|
||||
/// Heuristic last-resort name derivation from an exe path.
|
||||
///
|
||||
/// Walks up parent directories looking for a non-generic name; falls back to
|
||||
|
||||
+55
-43
@@ -684,13 +684,13 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
if let Ok(mut a) = state.close_action.lock() {
|
||||
*a = Some(CloseAction::Quit);
|
||||
}
|
||||
iced::window::get_oldest().and_then(iced::window::close)
|
||||
iced::window::oldest().and_then(iced::window::close)
|
||||
}
|
||||
Message::ConfirmMinimize => {
|
||||
if let Ok(mut a) = state.close_action.lock() {
|
||||
*a = Some(CloseAction::MinimizeToTray);
|
||||
}
|
||||
iced::window::get_oldest().and_then(iced::window::close)
|
||||
iced::window::oldest().and_then(iced::window::close)
|
||||
}
|
||||
Message::CancelClose => {
|
||||
state.close_dialog_open = false;
|
||||
@@ -728,7 +728,13 @@ fn subscription(_: &Dashboard) -> Subscription<Message> {
|
||||
Subscription::batch([
|
||||
iced::time::every(Duration::from_secs(2)).map(|_| Message::PollProcesses),
|
||||
iced::time::every(Duration::from_secs(5)).map(|_| Message::ReloadConfig),
|
||||
iced::window::close_requests().map(Message::CloseRequested),
|
||||
iced::event::listen_with(|event, _status, id| {
|
||||
if let iced::Event::Window(iced::window::Event::CloseRequested) = event {
|
||||
Some(Message::CloseRequested(id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -761,7 +767,7 @@ fn view(state: &Dashboard) -> Element<'_, Message> {
|
||||
color: Some(DIM),
|
||||
}),
|
||||
].spacing(2),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
settings_btn,
|
||||
]
|
||||
.align_y(Alignment::Center),
|
||||
@@ -769,7 +775,7 @@ fn view(state: &Dashboard) -> Element<'_, Message> {
|
||||
.padding(Padding { top: 20.0, right: 24.0, bottom: 6.0, left: 24.0 });
|
||||
|
||||
// ── Accent bar under title ────────────────────────────────────────────
|
||||
let accent_bar = container(Space::new(0, 0))
|
||||
let accent_bar = container(Space::new())
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(2.0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
@@ -792,14 +798,14 @@ fn view(state: &Dashboard) -> Element<'_, Message> {
|
||||
accent_bar,
|
||||
container(
|
||||
column![
|
||||
Space::new(0, 40),
|
||||
Space::new().height(40),
|
||||
text("No launchers configured").size(16).style(|_: &Theme| text::Style {
|
||||
color: Some(DIM),
|
||||
}),
|
||||
text("Add a launcher to get started.").size(12).style(|_: &Theme| text::Style {
|
||||
color: Some(MUTED),
|
||||
}),
|
||||
Space::new(0, 12),
|
||||
Space::new().height(12),
|
||||
add_btn,
|
||||
]
|
||||
.spacing(6)
|
||||
@@ -893,7 +899,7 @@ fn view(state: &Dashboard) -> Element<'_, Message> {
|
||||
detect_btn,
|
||||
proton_btn,
|
||||
add_btn,
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
footer_status,
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
@@ -938,7 +944,7 @@ fn view(state: &Dashboard) -> Element<'_, Message> {
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
let body = column![
|
||||
@@ -1036,7 +1042,7 @@ fn launcher_card<'a>(
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
// ── Status pill ───────────────────────────────────────────────────────
|
||||
@@ -1065,7 +1071,7 @@ fn launcher_card<'a>(
|
||||
status_pill,
|
||||
].align_y(Alignment::Center),
|
||||
].spacing(6),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
action,
|
||||
]
|
||||
.align_y(Alignment::Center);
|
||||
@@ -1095,7 +1101,7 @@ fn launcher_card<'a>(
|
||||
let section_header = row![
|
||||
text(section_label).size(10)
|
||||
.style(move |_: &Theme| text::Style { color: Some(DIM) }),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
rescan_btn,
|
||||
]
|
||||
.align_y(Alignment::Center);
|
||||
@@ -1139,7 +1145,7 @@ fn launcher_card<'a>(
|
||||
text(display).size(13),
|
||||
text(exe).size(9).style(move |_: &Theme| text::Style { color: Some(MUTED) }),
|
||||
].spacing(2),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
add_btn,
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
@@ -1200,6 +1206,7 @@ fn launcher_card<'a>(
|
||||
radius: 10.0.into(),
|
||||
},
|
||||
shadow: NO_SHADOW,
|
||||
snap: false,
|
||||
})
|
||||
.padding([3, 9])
|
||||
.into()
|
||||
@@ -1222,7 +1229,7 @@ fn launcher_card<'a>(
|
||||
play,
|
||||
container(status_dot).padding(Padding { top: 0.0, left: 2.0, right: 2.0, bottom: 0.0 }),
|
||||
text(&g.display).size(14),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
overlays,
|
||||
container(remove_game).padding(Padding { top: 0.0, left: 8.0, right: 0.0, bottom: 0.0 }),
|
||||
]
|
||||
@@ -1257,7 +1264,7 @@ fn launcher_card<'a>(
|
||||
row![
|
||||
text("No games configured").size(12)
|
||||
.style(move |_: &Theme| text::Style { color: Some(MUTED) }),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
scan_btn,
|
||||
browse_btn,
|
||||
]
|
||||
@@ -1339,7 +1346,7 @@ fn launcher_card<'a>(
|
||||
sections.push(
|
||||
row![
|
||||
add_game_btn,
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
scan_btn,
|
||||
browse_btn,
|
||||
]
|
||||
@@ -1360,7 +1367,7 @@ fn context_menu_card(l: &crate::config::Launcher) -> Element<'_, Message> {
|
||||
}),
|
||||
]
|
||||
.spacing(3),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
button(icon("\u{f659}", 13))
|
||||
.on_press(Message::HideContextMenu)
|
||||
.style(btn_ghost)
|
||||
@@ -1395,11 +1402,11 @@ fn context_menu_card(l: &crate::config::Launcher) -> Element<'_, Message> {
|
||||
|
||||
column![
|
||||
header,
|
||||
Space::new(0, 4),
|
||||
Space::new().height(4),
|
||||
menu_btn("Open install folder", "\u{f3e8}", Message::OpenPrefix(l.name.clone())),
|
||||
menu_btn("Re-run setup wizard", "\u{f130}", Message::RerunSetup(l.name.clone())),
|
||||
menu_btn("Run diagnostics", "\u{f52a}", Message::DiagnosePressed(l.name.clone())),
|
||||
Space::new(0, 2),
|
||||
Space::new().height(2),
|
||||
remove,
|
||||
]
|
||||
.spacing(2)
|
||||
@@ -1418,7 +1425,7 @@ fn diagnose_card<'a>(
|
||||
}),
|
||||
]
|
||||
.spacing(3),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
button(icon("\u{f659}", 13))
|
||||
.on_press(Message::HideDiagnose)
|
||||
.style(btn_ghost)
|
||||
@@ -1452,7 +1459,7 @@ fn diagnose_card<'a>(
|
||||
color: Some(color),
|
||||
}),
|
||||
text(format!(" {}", c.label)).size(12),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
text(&c.detail).size(11).style(|_: &Theme| text::Style {
|
||||
color: Some(DIM),
|
||||
}),
|
||||
@@ -1469,7 +1476,7 @@ fn diagnose_card<'a>(
|
||||
|
||||
column![
|
||||
header,
|
||||
Space::new(0, 6),
|
||||
Space::new().height(6),
|
||||
body,
|
||||
]
|
||||
.spacing(4)
|
||||
@@ -1483,7 +1490,7 @@ fn settings_section<'a>(title: &str, content: Element<'a, Message>) -> Element<'
|
||||
text(title.to_uppercase()).size(10).style(|_: &Theme| text::Style {
|
||||
color: Some(DIM),
|
||||
}),
|
||||
Space::new(0, 4),
|
||||
Space::new().height(4),
|
||||
content,
|
||||
]
|
||||
.spacing(2),
|
||||
@@ -1544,7 +1551,7 @@ fn view_close_dialog() -> Element<'static, Message> {
|
||||
column![
|
||||
title_row,
|
||||
description,
|
||||
Space::new(0, 8),
|
||||
Space::new().height(8),
|
||||
minimize_btn,
|
||||
quit_btn,
|
||||
cancel_btn,
|
||||
@@ -1578,7 +1585,7 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
||||
icon("\u{f3e2}", 20).style(|_: &Theme| text::Style { color: Some(ACCENT) }),
|
||||
text(" Settings").size(22),
|
||||
].align_y(Alignment::Center),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
button(
|
||||
row![icon("\u{f12f}", 13), text(" Back").size(12)]
|
||||
.align_y(Alignment::Center).spacing(4),
|
||||
@@ -1626,10 +1633,10 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
||||
column![
|
||||
text("Version").size(12).style(|_: &Theme| text::Style { color: Some(DIM) }),
|
||||
proton_version_picker,
|
||||
Space::new(0, 4),
|
||||
Space::new().height(4),
|
||||
text("Compat directory").size(12).style(|_: &Theme| text::Style { color: Some(DIM) }),
|
||||
row![compat_dir_input, browse_compat_btn].spacing(8).align_y(Alignment::Center),
|
||||
Space::new(0, 4),
|
||||
Space::new().height(4),
|
||||
container(save_btn).width(Length::Fill).align_x(Alignment::End),
|
||||
].spacing(6).into(),
|
||||
);
|
||||
@@ -1677,7 +1684,7 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
||||
text(format!(" {svc_status_text}")).size(12).style(move |_: &Theme| text::Style {
|
||||
color: Some(svc_status_color),
|
||||
}),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
svc_install_btn,
|
||||
svc_uninstall_btn,
|
||||
].align_y(Alignment::Center).spacing(8),
|
||||
@@ -1706,12 +1713,12 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
let body = column![
|
||||
header,
|
||||
Space::new(0, 6),
|
||||
Space::new().height(6),
|
||||
proton_section,
|
||||
tools_section,
|
||||
autostart_section,
|
||||
@@ -1731,21 +1738,26 @@ pub fn run(config: &Config) -> Result<CloseAction> {
|
||||
let config = config.clone();
|
||||
let close_action: Arc<Mutex<Option<CloseAction>>> = Arc::new(Mutex::new(None));
|
||||
let ca = close_action.clone();
|
||||
iced::application(|_: &Dashboard| String::from("umutray"), update, view)
|
||||
.subscription(subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.window(iced::window::Settings {
|
||||
size: iced::Size::new(640.0, 600.0),
|
||||
min_size: Some(iced::Size::new(480.0, 400.0)),
|
||||
exit_on_close_request: false,
|
||||
..Default::default()
|
||||
})
|
||||
.run_with(move || {
|
||||
iced::application(
|
||||
move || {
|
||||
let cfg = config.clone();
|
||||
let load_font = iced::font::load(std::borrow::Cow::Borrowed(iced_fonts::BOOTSTRAP_FONT_BYTES)).map(|_| Message::FontLoaded);
|
||||
let load_font = iced::font::load(iced_fonts::BOOTSTRAP_FONT_BYTES).map(|_| Message::FontLoaded);
|
||||
(Dashboard::new(cfg, ca.clone()), load_font)
|
||||
})
|
||||
.map_err(|e| anyhow::anyhow!("iced: {e}"))?;
|
||||
},
|
||||
update,
|
||||
view,
|
||||
)
|
||||
.title(|_: &Dashboard| String::from("umutray"))
|
||||
.subscription(subscription)
|
||||
.theme(Theme::Dark)
|
||||
.window(iced::window::Settings {
|
||||
size: iced::Size::new(640.0, 600.0),
|
||||
min_size: Some(iced::Size::new(480.0, 400.0)),
|
||||
exit_on_close_request: false,
|
||||
..Default::default()
|
||||
})
|
||||
.run()
|
||||
.map_err(|e| anyhow::anyhow!("iced: {e}"))?;
|
||||
|
||||
let action = close_action.lock().unwrap().unwrap_or(CloseAction::Quit);
|
||||
Ok(action)
|
||||
|
||||
+46
-35
@@ -498,7 +498,7 @@ fn view_picking(state: &State) -> Element<'_, Message> {
|
||||
.padding(Padding::from([6, 0]))
|
||||
.into()
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
// ── Next button ───────────────────────────────────────────────────────────
|
||||
@@ -620,7 +620,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
.padding([6, 14])
|
||||
.into()
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
// ── Header ────────────────────────────────────────────────────────────────
|
||||
@@ -632,7 +632,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
}),
|
||||
]
|
||||
.spacing(0),
|
||||
iced::widget::horizontal_space(),
|
||||
Space::new().width(Length::Fill),
|
||||
back_el,
|
||||
]
|
||||
.align_y(Alignment::Start);
|
||||
@@ -651,7 +651,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
.into();
|
||||
section_card(inner)
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
// ── Status card ───────────────────────────────────────────────────────────
|
||||
@@ -670,7 +670,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
StatusKind::Neutral,
|
||||
)
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
// ── Progress bar ──────────────────────────────────────────────────────────
|
||||
@@ -690,7 +690,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
.spacing(6),
|
||||
)
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
// ── Finished banner ───────────────────────────────────────────────────────
|
||||
@@ -721,7 +721,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
// ── Log toggle + pane ─────────────────────────────────────────────────────
|
||||
@@ -757,7 +757,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
toggle_btn.into()
|
||||
}
|
||||
} else {
|
||||
Space::new(0, 0).into()
|
||||
Space::new().into()
|
||||
};
|
||||
|
||||
// ── Action button ─────────────────────────────────────────────────────────
|
||||
@@ -832,7 +832,7 @@ fn view_install(state: &State) -> Element<'_, Message> {
|
||||
.align_x(Alignment::End)
|
||||
.into()
|
||||
}
|
||||
Stage::Picking => Space::new(0, 0).into(),
|
||||
Stage::Picking => Space::new().into(),
|
||||
};
|
||||
|
||||
// ── Assembly ──────────────────────────────────────────────────────────────
|
||||
@@ -859,21 +859,15 @@ pub fn run(config: &Config, launcher: &Launcher) -> Result<()> {
|
||||
let config = config.clone();
|
||||
let launcher = launcher.clone();
|
||||
let title = format!("umutray — {}", launcher.display);
|
||||
iced::application(move |_: &State| title.clone(), update, view)
|
||||
.subscription(subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.window(iced::window::Settings {
|
||||
size: iced::Size::new(520.0, 440.0),
|
||||
resizable: false,
|
||||
..Default::default()
|
||||
})
|
||||
.run_with(move || {
|
||||
let has_url = launcher.installer_url.is_some();
|
||||
let has_url = launcher.installer_url.is_some();
|
||||
let url = launcher.installer_url.clone().unwrap_or_default();
|
||||
iced::application(
|
||||
move || {
|
||||
let mut state = State::new_install(config.clone(), launcher.clone());
|
||||
if has_url {
|
||||
state.source = launcher.installer_url.unwrap_or_default();
|
||||
state.source = url.clone();
|
||||
}
|
||||
let load_font = iced::font::load(std::borrow::Cow::Borrowed(iced_fonts::BOOTSTRAP_FONT_BYTES))
|
||||
let load_font = iced::font::load(iced_fonts::BOOTSTRAP_FONT_BYTES)
|
||||
.map(|_| Message::FontLoaded);
|
||||
let init_task = if has_url {
|
||||
Task::batch([load_font, Task::done(Message::AutoDownload)])
|
||||
@@ -881,26 +875,43 @@ pub fn run(config: &Config, launcher: &Launcher) -> Result<()> {
|
||||
load_font
|
||||
};
|
||||
(state, init_task)
|
||||
})
|
||||
.map_err(|e| anyhow::anyhow!("iced: {e}"))
|
||||
},
|
||||
update,
|
||||
view,
|
||||
)
|
||||
.title(move |_: &State| title.clone())
|
||||
.subscription(subscription)
|
||||
.theme(Theme::Dark)
|
||||
.window(iced::window::Settings {
|
||||
size: iced::Size::new(520.0, 440.0),
|
||||
resizable: false,
|
||||
..Default::default()
|
||||
})
|
||||
.run()
|
||||
.map_err(|e| anyhow::anyhow!("iced: {e}"))
|
||||
}
|
||||
|
||||
pub fn run_new(config: &Config) -> Result<()> {
|
||||
let config = config.clone();
|
||||
iced::application(|_: &State| "umutray — Add Launcher".to_string(), update, view)
|
||||
.subscription(subscription)
|
||||
.theme(|_| Theme::Dark)
|
||||
.window(iced::window::Settings {
|
||||
size: iced::Size::new(520.0, 440.0),
|
||||
resizable: false,
|
||||
..Default::default()
|
||||
})
|
||||
.run_with(move || {
|
||||
let load_font = iced::font::load(std::borrow::Cow::Borrowed(iced_fonts::BOOTSTRAP_FONT_BYTES))
|
||||
iced::application(
|
||||
move || {
|
||||
let load_font = iced::font::load(iced_fonts::BOOTSTRAP_FONT_BYTES)
|
||||
.map(|_| Message::FontLoaded);
|
||||
(State::new_picking(config.clone()), load_font)
|
||||
})
|
||||
.map_err(|e| anyhow::anyhow!("iced: {e}"))
|
||||
},
|
||||
update,
|
||||
view,
|
||||
)
|
||||
.title(|_: &State| "umutray — Add Launcher".to_string())
|
||||
.subscription(subscription)
|
||||
.theme(Theme::Dark)
|
||||
.window(iced::window::Settings {
|
||||
size: iced::Size::new(520.0, 440.0),
|
||||
resizable: false,
|
||||
..Default::default()
|
||||
})
|
||||
.run()
|
||||
.map_err(|e| anyhow::anyhow!("iced: {e}"))
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
-1
@@ -55,7 +55,7 @@ pub const BORDER_CLR: Color = Color {
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Bootstrap-icon helper — keeps call sites tidy.
|
||||
pub fn icon(codepoint: &str, size: u16) -> iced::widget::Text<'static> {
|
||||
pub fn icon(codepoint: &str, size: u32) -> iced::widget::Text<'static> {
|
||||
text(codepoint.to_owned())
|
||||
.font(iced::Font::with_name("bootstrap-icons"))
|
||||
.size(size)
|
||||
@@ -93,6 +93,7 @@ pub fn btn_accent(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
radius: 8.0.into(),
|
||||
},
|
||||
shadow: NO_SHADOW,
|
||||
snap: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +133,7 @@ pub fn btn_ghost(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
radius: 8.0.into(),
|
||||
},
|
||||
shadow: NO_SHADOW,
|
||||
snap: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +159,7 @@ pub fn btn_danger(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
radius: 8.0.into(),
|
||||
},
|
||||
shadow: NO_SHADOW,
|
||||
snap: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -87,7 +87,7 @@ pub struct UmuTray {
|
||||
pub running: HashMap<String, bool>,
|
||||
/// Set after the tray spawns so Quit can shut down the SNI item
|
||||
/// cleanly instead of yanking it off the bus via exit().
|
||||
pub handle: Option<ksni::Handle<UmuTray>>,
|
||||
pub handle: Option<ksni::blocking::Handle<UmuTray>>,
|
||||
}
|
||||
|
||||
impl ksni::Tray for UmuTray {
|
||||
@@ -312,7 +312,7 @@ pub fn run(config: &Config) -> Result<()> {
|
||||
|
||||
/// A handle that can shut down the tray from another thread.
|
||||
pub struct TrayHandle {
|
||||
inner: ksni::Handle<UmuTray>,
|
||||
inner: ksni::blocking::Handle<UmuTray>,
|
||||
}
|
||||
|
||||
impl TrayHandle {
|
||||
@@ -335,9 +335,10 @@ pub fn spawn(config: &Config) -> TrayHandle {
|
||||
handle: None,
|
||||
};
|
||||
|
||||
let service = ksni::TrayService::new(tray);
|
||||
let handle = service.handle();
|
||||
service.spawn();
|
||||
let handle = {
|
||||
use ksni::blocking::TrayMethods;
|
||||
tray.spawn().expect("Failed to spawn tray service")
|
||||
};
|
||||
|
||||
// Hand the tray a clone of its own handle so Quit can shut down cleanly.
|
||||
let handle_for_self = handle.clone();
|
||||
|
||||
Reference in New Issue
Block a user