Skip to content

Commit 6b59298

Browse files
authored
Merge pull request #121 from lgou2w/develop
v1.1.6
2 parents 429cb3a + 8ee6b9b commit 6b59298

File tree

22 files changed

+940
-682
lines changed

22 files changed

+940
-682
lines changed

Cargo.lock

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

package.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "hoyo_gacha",
33
"displayName": "HoYo.Gacha",
4-
"version": "1.1.5",
4+
"version": "1.1.6",
55
"description": "An unofficial tool for managing and analyzing your miHoYo gacha records",
66
"author": "lgou2w <lgou2w@hotmail.com>",
77
"homepage": "https://github.com/lgou2w/HoYo.Gacha#readme",
@@ -39,11 +39,11 @@
3939
},
4040
"dependencies": {
4141
"@fluentui/react-components": "^9.70.0",
42-
"@fluentui/react-icons": "^2.0.310",
42+
"@fluentui/react-icons": "^2.0.311",
4343
"@fluentui/react-motion-components-preview": "^0.10.0",
4444
"@tanstack/react-query": "^5.90.2",
4545
"@tanstack/react-query-devtools": "^5.90.2",
46-
"@tanstack/react-router": "^1.131.50",
46+
"@tanstack/react-router": "^1.132.23",
4747
"@tauri-apps/api": "^2.8.0",
4848
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
4949
"@tauri-apps/plugin-process": "^2.3.0",
@@ -56,16 +56,16 @@
5656
"react": "^18.3.1",
5757
"react-dom": "^18.3.1",
5858
"react-hook-form": "^7.63.0",
59-
"react-i18next": "^15.7.3",
60-
"react-virtuoso": "^4.14.0",
59+
"react-i18next": "^16.0.0",
60+
"react-virtuoso": "^4.14.1",
6161
"use-immer": "^0.11.0"
6262
},
6363
"devDependencies": {
6464
"@babel/preset-react": "^7.27.1",
6565
"@babel/preset-typescript": "^7.27.1",
6666
"@griffel/tag-processor": "^1.0.13",
6767
"@griffel/vite-plugin": "^0.1.13",
68-
"@tanstack/eslint-plugin-query": "^5.90.1",
68+
"@tanstack/eslint-plugin-query": "^5.91.0",
6969
"@tauri-apps/cli": "^2.8.4",
7070
"@testing-library/jest-dom": "^6.8.0",
7171
"@testing-library/react": "^16.3.0",
@@ -90,11 +90,11 @@
9090
"eslint-plugin-promise": "^7.2.1",
9191
"eslint-plugin-react": "^7.37.5",
9292
"eslint-plugin-react-hooks": "^5.2.0",
93-
"eslint-plugin-react-refresh": "^0.4.21",
94-
"eslint-plugin-testing-library": "^7.9.1",
93+
"eslint-plugin-react-refresh": "^0.4.22",
94+
"eslint-plugin-testing-library": "^7.10.0",
9595
"husky": "^9.1.7",
96-
"jest": "^30.1.3",
97-
"jest-environment-jsdom": "^30.1.2",
96+
"jest": "^30.2.0",
97+
"jest-environment-jsdom": "^30.2.0",
9898
"rimraf": "^6.0.1",
9999
"ts-jest": "^29.4.4",
100100
"typescript": "^5.9.2",

pnpm-lock.yaml

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

src-tauri/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hoyo_gacha"
3-
version = "1.1.5"
3+
version = "1.1.6"
44
edition = "2024"
55
rust-version = "1.85"
66
authors = ["lgou2w <lgou2w@hotmail.com>"]
@@ -33,18 +33,18 @@ num_enum = "0.7.4"
3333
os_info = "3.12.0"
3434
paste = "1.0.15"
3535
raw-window-handle = "0.6.2"
36-
regex = "1.11.2"
36+
regex = "1.11.3"
3737
reqwest = { version = "0.12.23", features = ["json"] }
3838
rfd = { version = "0.15.4", default-features = false }
3939
sha1 = "0.10.6"
40-
serde = { version = "1.0.226", features = ["derive"] }
40+
serde = { version = "1.0.228", features = ["derive"] }
4141
serde_json = "1.0.145"
4242
serde_repr = "0.1.20"
4343
sys-locale = "0.3.2"
4444
tauri-plugin-shell = "2.3.1"
4545
tauri-plugin-clipboard-manager = "2.3.0"
4646
tauri-plugin-process = "2.3.0"
47-
thiserror = "2.0.16"
47+
thiserror = "2.0.17"
4848
time = { version = "0.3.44", features = ["local-offset", "macros", "serde-human-readable"] }
4949
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
5050
tracing = "0.1.41"
@@ -76,7 +76,7 @@ features = [
7676

7777
[target.'cfg(windows)'.dependencies]
7878
windows-registry = "0.6.0"
79-
windows-version = "0.1.5"
79+
windows-version = "0.1.6"
8080
webview2-com = "0.38.0"
8181

8282
[target.'cfg(windows)'.dependencies.windows]
@@ -89,6 +89,7 @@ features = [
8989
"Win32_Security",
9090
"Win32_System_Com",
9191
"Win32_System_Console",
92+
"Win32_System_Registry",
9293
"Win32_System_Threading",
9394
"Win32_UI",
9495
"Win32_UI_Controls",

src-tauri/Tauri.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# identifier = "com.lgou2w.hoyo.gacha"
33
identifier = "com.lgou2w.hoyo.gacha.v1"
44
product-name = "HoYo.Gacha"
5-
version = "1.1.5"
5+
version = "1.1.6"
66

77
[build]
88
# See: package.json - scripts - dev & build

src-tauri/src/bootstrap/ffi/windows.rs

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
use std::env;
22
use std::mem::{self, MaybeUninit};
3+
use std::sync::Arc;
4+
use std::sync::atomic::{AtomicBool, Ordering};
5+
use std::thread;
36

47
use tauri::{Theme, WebviewWindow};
58
use webview2_com::Microsoft::Web::WebView2::Win32::{
69
COREWEBVIEW2_PREFERRED_COLOR_SCHEME_DARK, COREWEBVIEW2_PREFERRED_COLOR_SCHEME_LIGHT,
710
ICoreWebView2Settings4,
811
};
912
use webview2_com::Microsoft::Web::WebView2::Win32::{ICoreWebView2_2, ICoreWebView2_13};
10-
use windows::Win32::Foundation::{FALSE, HANDLE, TRUE};
13+
use windows::Win32::Foundation::{
14+
CloseHandle, FALSE, HANDLE, TRUE, WAIT_EVENT, WAIT_FAILED, WAIT_OBJECT_0,
15+
};
1116
use windows::Win32::Graphics::DirectWrite::{
1217
DWRITE_FACTORY_TYPE_SHARED, DWriteCreateFactory, IDWriteFactory,
1318
};
@@ -21,10 +26,15 @@ use windows::Win32::System::Com::{
2126
CoInitialize, CoInitializeEx, CoUninitialize, IPersistFile,
2227
};
2328
use windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};
29+
use windows::Win32::System::Registry::{
30+
HKEY, HKEY_CURRENT_USER, KEY_NOTIFY, REG_NOTIFY_CHANGE_LAST_SET, RegCloseKey,
31+
RegNotifyChangeKeyValue, RegOpenKeyExA,
32+
};
33+
use windows::Win32::System::Threading::{CreateEventA, INFINITE, SetEvent, WaitForMultipleObjects};
2434
use windows::Win32::UI::Controls::MARGINS;
2535
use windows::Win32::UI::Shell::{IShellLinkW, ShellLink};
2636
use windows::Win32::UI::WindowsAndMessaging::SetPropW;
27-
use windows::core::{BOOL, Interface, PCWSTR, PWSTR, w};
37+
use windows::core::{BOOL, Interface, PCWSTR, PWSTR, s, w};
2838

2939
use crate::consts;
3040

@@ -287,3 +297,116 @@ pub fn system_fonts() -> Result<Vec<String>, windows::core::Error> {
287297
Ok(fonts)
288298
}
289299
}
300+
301+
#[derive(Default)]
302+
pub struct AppsUseThemeMonitor {
303+
is_running: Arc<AtomicBool>,
304+
hshutdown: HANDLE,
305+
hthread: Option<thread::JoinHandle<()>>,
306+
}
307+
308+
impl AppsUseThemeMonitor {
309+
pub fn start<F>(&mut self, callback: F)
310+
where
311+
F: Fn(Theme) + Send + 'static,
312+
{
313+
if self.is_running.load(Ordering::Relaxed) {
314+
return;
315+
}
316+
317+
self.is_running.store(true, Ordering::Relaxed);
318+
self.hshutdown =
319+
unsafe { CreateEventA(None, true, false, None) }.expect("Shutdown event create failed");
320+
321+
let initial_theme = apps_use_theme();
322+
let mut last_theme = initial_theme;
323+
324+
let callback = Box::new(callback);
325+
let is_running = Arc::clone(&self.is_running);
326+
327+
// https://github.com/microsoft/windows-rs/issues/3169#issuecomment-2489378071
328+
let hshutdown = self.hshutdown.0 as isize;
329+
let hthread = thread::spawn(move || {
330+
use tracing::error;
331+
332+
let mut hkey = HKEY::default();
333+
if let Err(error) = unsafe {
334+
RegOpenKeyExA(
335+
HKEY_CURRENT_USER,
336+
s!("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"),
337+
None,
338+
KEY_NOTIFY,
339+
&mut hkey,
340+
)
341+
}
342+
.ok()
343+
{
344+
error!(message = "Open registry key failed", ?error);
345+
return;
346+
}
347+
348+
'monitor: while is_running.load(Ordering::Relaxed) {
349+
let hevent = match unsafe { CreateEventA(None, true, false, None) } {
350+
Ok(hevent) => hevent,
351+
Err(error) => {
352+
error!("Create event failed: {error:?}");
353+
break;
354+
}
355+
};
356+
357+
if let Err(error) = unsafe {
358+
RegNotifyChangeKeyValue(hkey, false, REG_NOTIFY_CHANGE_LAST_SET, Some(hevent), true)
359+
}
360+
.ok()
361+
{
362+
error!("RegNotifyChangeKeyValue failed: {error:?}");
363+
let _ = unsafe { CloseHandle(hevent) };
364+
break;
365+
}
366+
367+
let handles = [hevent, HANDLE(hshutdown as _)];
368+
let wait_event = unsafe { WaitForMultipleObjects(&handles, false, INFINITE) };
369+
let _ = unsafe { CloseHandle(hevent) };
370+
371+
match wait_event {
372+
WAIT_OBJECT_0 => {
373+
let new_theme = apps_use_theme();
374+
if new_theme != last_theme {
375+
last_theme = new_theme;
376+
callback(new_theme);
377+
}
378+
}
379+
WAIT_EVENT(1) => break 'monitor,
380+
WAIT_FAILED => {
381+
error!("WaitForMultipleObjects failed: {wait_event:?}");
382+
break 'monitor;
383+
}
384+
_ => {
385+
error!("WaitForMultipleObjects unexpected result: {wait_event:?}");
386+
break 'monitor;
387+
}
388+
}
389+
}
390+
391+
let _ = unsafe { RegCloseKey(hkey) };
392+
});
393+
394+
self.hthread = Some(hthread);
395+
}
396+
397+
pub fn stop(&mut self) {
398+
if !self.is_running.load(Ordering::Relaxed) {
399+
return;
400+
}
401+
402+
self.is_running.store(false, Ordering::Relaxed);
403+
404+
let _ = unsafe { SetEvent(self.hshutdown) };
405+
406+
if let Some(hthread) = self.hthread.take() {
407+
hthread.join().expect("Thread join failed");
408+
}
409+
410+
let _ = unsafe { CloseHandle(self.hshutdown) };
411+
}
412+
}

src-tauri/src/bootstrap/tauri.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::path::PathBuf;
2+
use std::sync::atomic::AtomicBool;
3+
use std::sync::atomic::Ordering;
24
use std::sync::{Arc, Mutex};
35
use std::time::Duration;
46
use std::{env, process};
@@ -16,6 +18,7 @@ use super::internals;
1618
use super::singleton::Singleton;
1719
use super::tracing::Tracing;
1820
use super::updater::{UpdatedKind, Updater};
21+
use crate::bootstrap::ffi::AppsUseThemeMonitor;
1922
use crate::business::GachaMetadata;
2023
use crate::database::{self, Database, KvMut};
2124
use crate::models::{ThemeData, WindowState};
@@ -32,6 +35,9 @@ pub async fn start(singleton: Singleton, tracing: Tracing, database: Database) {
3235
// Arc shared database to Tauri state
3336
let database = Arc::new(database);
3437

38+
#[cfg(windows)]
39+
let mut apps_use_theme_monitor = AppsUseThemeMonitor::default();
40+
3541
info!("Loading theme data...");
3642
let color_scheme = KvMut::from(&database, consts::KV_THEME_DATA)
3743
.try_read_val_json::<ThemeData>()
@@ -42,6 +48,7 @@ pub async fn start(singleton: Singleton, tracing: Tracing, database: Database) {
4248
.and_then(|theme_data| theme_data.color_scheme)
4349
.unwrap_or_else(|| {
4450
if cfg!(windows) {
51+
FLAG_AUTO_COLOR_SCHEME.store(true, Ordering::Relaxed);
4552
ffi::apps_use_theme()
4653
} else {
4754
Theme::Light
@@ -318,9 +325,30 @@ pub async fn start(singleton: Singleton, tracing: Tracing, database: Database) {
318325
.build(generate_context!())
319326
.expect("Error while building Tauri application");
320327

328+
#[cfg(windows)]
329+
{
330+
info!("Start AppsUseTheme monitor thread...");
331+
let app_handle = app.handle().clone();
332+
apps_use_theme_monitor.start(move |color_scheme| {
333+
info!("Detected system color scheme change: {color_scheme:?}");
334+
335+
if FLAG_AUTO_COLOR_SCHEME.load(Ordering::Relaxed)
336+
&& let Some(main_window) = app_handle.get_webview_window(consts::TAURI_MAIN_WINDOW_LABEL)
337+
{
338+
ffi::set_window_theme(&main_window, color_scheme);
339+
let _ = ffi::set_webview_theme(&main_window, color_scheme);
340+
let _ = main_window.emit(consts::EVENT_COLOR_SCHEME_CHANGED, color_scheme);
341+
}
342+
});
343+
}
344+
321345
let exit_code = app.run_return(|_app_handle, _event| {});
322346

323347
info!("Tauri exiting...");
348+
349+
#[cfg(windows)]
350+
apps_use_theme_monitor.stop();
351+
324352
database.close().await;
325353
tracing.close();
326354
drop(singleton);
@@ -406,12 +434,22 @@ fn core_is_supported_window_vibrancy() -> bool {
406434
ffi::is_supported_window_vibrancy()
407435
}
408436

437+
#[cfg(windows)]
438+
static FLAG_AUTO_COLOR_SCHEME: AtomicBool = AtomicBool::new(false);
439+
409440
#[tauri::command]
410-
fn core_change_theme(window: WebviewWindow, color_scheme: Theme) -> Result<(), tauri::Error> {
441+
fn core_change_theme(
442+
window: WebviewWindow,
443+
color_scheme: Option<Theme>,
444+
) -> Result<(), tauri::Error> {
445+
FLAG_AUTO_COLOR_SCHEME.store(color_scheme.is_none(), Ordering::Relaxed);
446+
447+
let color_scheme = color_scheme.unwrap_or(ffi::apps_use_theme());
411448
if !consts::TAURI_MAIN_WINDOW_DECORATIONS {
412449
ffi::set_window_theme(&window, color_scheme);
413450
ffi::set_webview_theme(&window, color_scheme)?;
414451
}
452+
415453
Ok(())
416454
}
417455

0 commit comments

Comments
 (0)