diff --git a/apps/readest-app/src-tauri/Cargo.lock b/apps/readest-app/src-tauri/Cargo.lock index 28147cf7..582004e4 100644 --- a/apps/readest-app/src-tauri/Cargo.lock +++ b/apps/readest-app/src-tauri/Cargo.lock @@ -668,6 +668,22 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + [[package]] name = "cocoa" version = "0.26.0" @@ -676,14 +692,28 @@ checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" dependencies = [ "bitflags 2.6.0", "block", - "cocoa-foundation", + "cocoa-foundation 0.2.0", "core-foundation 0.10.0", - "core-graphics", + "core-graphics 0.24.0", "foreign-types", "libc", "objc", ] +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + [[package]] name = "cocoa-foundation" version = "0.2.0" @@ -693,7 +723,7 @@ dependencies = [ "bitflags 2.6.0", "block", "core-foundation 0.10.0", - "core-graphics-types", + "core-graphics-types 0.2.0", "libc", "objc", ] @@ -787,6 +817,19 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + [[package]] name = "core-graphics" version = "0.24.0" @@ -795,11 +838,22 @@ checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.6.0", "core-foundation 0.10.0", - "core-graphics-types", + "core-graphics-types 0.2.0", "foreign-types", "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + [[package]] name = "core-graphics-types" version = "0.2.0" @@ -3511,7 +3565,10 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" name = "readest" version = "0.1.0" dependencies = [ + "cocoa 0.25.0", "log", + "objc", + "rand 0.8.5", "serde", "serde_json", "tauri", @@ -4153,7 +4210,7 @@ checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "bytemuck", "cfg_aliases 0.2.1", - "core-graphics", + "core-graphics 0.24.0", "foreign-types", "js-sys", "log", @@ -4359,9 +4416,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63f1f6b2017cc33d7f6fc9c6186a2c0f5dfc985899a7b4fe9e64985c17533db3" dependencies = [ "bitflags 2.6.0", - "cocoa", + "cocoa 0.26.0", "core-foundation 0.10.0", - "core-graphics", + "core-graphics 0.24.0", "crossbeam-channel", "dispatch", "dlopen2", @@ -4568,7 +4625,7 @@ checksum = "8e5cd17faa36a826e5686bd0fda5bc3f4c903682263f00cd50f2f778fc4bb866" dependencies = [ "async-stream", "bytes", - "cocoa", + "cocoa 0.26.0", "colored", "devtools-core", "futures", @@ -4656,7 +4713,7 @@ checksum = "9a49f2c05d15e6375ab7f7e528b3049150ba4dfafdf61f85e5178d0aef18e3f5" dependencies = [ "android_logger", "byte-unit", - "cocoa", + "cocoa 0.26.0", "fern", "log", "objc", @@ -5228,7 +5285,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c92af36a182b46206723bdf8a7942e20838cde1cf062e5b97854d57eb01763b" dependencies = [ - "core-graphics", + "core-graphics 0.24.0", "crossbeam-channel", "dirs", "libappindicator", diff --git a/apps/readest-app/src-tauri/Cargo.toml b/apps/readest-app/src-tauri/Cargo.toml index 3ec9e747..0fe839f6 100644 --- a/apps/readest-app/src-tauri/Cargo.toml +++ b/apps/readest-app/src-tauri/Cargo.toml @@ -22,7 +22,7 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" # FIXME: remove the devtools feature in production -tauri = { version = "2.0.2", features = [ "macos-private-api", "protocol-asset", "devtools"] } +tauri = { version = "2.0.2", features = [ "protocol-asset", "devtools"] } tauri-plugin-log = "2.0.1" tauri-plugin-fs = "2.0.1" tauri-plugin-dialog = "2.0.1" @@ -33,3 +33,8 @@ tauri-plugin-devtools = "2.0.0" [patch.crates-io] tauri = { path = "../../../packages/tauri/crates/tauri" } wry = { path = "../../../packages/wry" } + +[target."cfg(target_os = \"macos\")".dependencies] +cocoa = "0.25" +objc = "0.2.7" +rand = "0.8" diff --git a/apps/readest-app/src-tauri/src/lib.rs b/apps/readest-app/src-tauri/src/lib.rs index f6ec8d60..41ceabf5 100644 --- a/apps/readest-app/src-tauri/src/lib.rs +++ b/apps/readest-app/src-tauri/src/lib.rs @@ -1,3 +1,16 @@ +#[cfg(target_os = "macos")] +#[macro_use] +extern crate cocoa; + +#[cfg(target_os = "macos")] +#[macro_use] +extern crate objc; + +#[cfg(target_os = "macos")] +mod tauri_traffic_light_positioner_plugin; + +use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder}; + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() @@ -5,6 +18,7 @@ pub fn run() { .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) + .plugin(tauri_traffic_light_positioner_plugin::init()) .setup(|app| { if cfg!(debug_assertions) { app.handle().plugin( @@ -13,6 +27,17 @@ pub fn run() { .build(), )?; } + let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default()) + .title("") + .inner_size(800.0, 600.0) + .resizable(true) + .maximized(true); + + #[cfg(target_os = "macos")] + let win_builder = win_builder.title_bar_style(TitleBarStyle::Overlay); + + let _ = win_builder.build().unwrap(); + Ok(()) }) .run(tauri::generate_context!()) diff --git a/apps/readest-app/src-tauri/src/tauri_traffic_light_positioner_plugin.rs b/apps/readest-app/src-tauri/src/tauri_traffic_light_positioner_plugin.rs new file mode 100644 index 00000000..dfd76255 --- /dev/null +++ b/apps/readest-app/src-tauri/src/tauri_traffic_light_positioner_plugin.rs @@ -0,0 +1,364 @@ +use objc::{msg_send, sel, sel_impl}; +use rand::{distributions::Alphanumeric, Rng}; +use tauri::Emitter; +use tauri::{ + plugin::{Builder, TauriPlugin}, + Runtime, Window, +}; // 0.8 + +const WINDOW_CONTROL_PAD_X: f64 = 10.0; +const WINDOW_CONTROL_PAD_Y: f64 = 21.0; + +struct UnsafeWindowHandle(*mut std::ffi::c_void); +unsafe impl Send for UnsafeWindowHandle {} +unsafe impl Sync for UnsafeWindowHandle {} + +pub fn init() -> TauriPlugin { + Builder::new("traffic_light_positioner") + .on_window_ready(|window| { + #[cfg(target_os = "macos")] + setup_traffic_light_positioner(window.clone()); + let window_clone = window.clone(); + window.on_window_event(move |event| { + let window = window_clone.clone(); + if let tauri::WindowEvent::ThemeChanged(_theme) = event { + setup_traffic_light_positioner(window); + } + }); + return; + }) + .build() +} + +#[cfg(target_os = "macos")] +fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) { + use cocoa::appkit::{NSView, NSWindow, NSWindowButton}; + use cocoa::foundation::NSRect; + let ns_window = ns_window_handle.0 as cocoa::base::id; + unsafe { + let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton); + let miniaturize = + ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton); + let zoom = ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton); + + let title_bar_container_view = close.superview().superview(); + + let close_rect: NSRect = msg_send![close, frame]; + let button_height = close_rect.size.height; + + let title_bar_frame_height = button_height + y; + let mut title_bar_rect = NSView::frame(title_bar_container_view); + title_bar_rect.size.height = title_bar_frame_height; + title_bar_rect.origin.y = NSView::frame(ns_window).size.height - title_bar_frame_height; + let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect]; + + let window_buttons = vec![close, miniaturize, zoom]; + let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x; + + for (i, button) in window_buttons.into_iter().enumerate() { + let mut rect: NSRect = NSView::frame(button); + rect.origin.x = x + (i as f64 * space_between); + button.setFrameOrigin(rect.origin); + } + } +} + +#[cfg(target_os = "macos")] +#[derive(Debug)] +struct WindowState { + window: Window, +} + +#[cfg(target_os = "macos")] +pub fn setup_traffic_light_positioner(window: Window) { + use cocoa::appkit::NSWindow; + use cocoa::base::{id, BOOL}; + use cocoa::foundation::NSUInteger; + use objc::runtime::{Object, Sel}; + use std::ffi::c_void; + + // Do the initial positioning + position_traffic_lights( + UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")), + WINDOW_CONTROL_PAD_X, + WINDOW_CONTROL_PAD_Y, + ); + + // Ensure they stay in place while resizing the window. + fn with_window_state) -> T, T>( + this: &Object, + func: F, + ) { + let ptr = unsafe { + let x: *mut c_void = *this.get_ivar("app_box"); + &mut *(x as *mut WindowState) + }; + func(ptr); + } + + unsafe { + let ns_win = window + .ns_window() + .expect("NS Window should exist to mount traffic light delegate.") + as id; + + let current_delegate: id = ns_win.delegate(); + + extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + msg_send![super_del, windowShouldClose: sender] + } + } + extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowWillClose: notification]; + } + } + extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) { + unsafe { + with_window_state(&*this, |state: &mut WindowState| { + let id = state + .window + .ns_window() + .expect("NS window should exist on state to handle resize") + as id; + + #[cfg(target_os = "macos")] + position_traffic_lights( + UnsafeWindowHandle(id as *mut std::ffi::c_void), + WINDOW_CONTROL_PAD_X, + WINDOW_CONTROL_PAD_Y, + ); + }); + + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowDidResize: notification]; + } + } + extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowDidMove: notification]; + } + } + extern "C" fn on_window_did_change_backing_properties( + this: &Object, + _cmd: Sel, + notification: id, + ) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification]; + } + } + extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowDidBecomeKey: notification]; + } + } + extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowDidResignKey: notification]; + } + } + extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + msg_send![super_del, draggingEntered: notification] + } + } + extern "C" fn on_prepare_for_drag_operation( + this: &Object, + _cmd: Sel, + notification: id, + ) -> BOOL { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + msg_send![super_del, prepareForDragOperation: notification] + } + } + extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + msg_send![super_del, performDragOperation: sender] + } + } + extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, concludeDragOperation: notification]; + } + } + extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, draggingExited: notification]; + } + } + extern "C" fn on_window_will_use_full_screen_presentation_options( + this: &Object, + _cmd: Sel, + window: id, + proposed_options: NSUInteger, + ) -> NSUInteger { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options] + } + } + extern "C" fn on_window_did_enter_full_screen( + this: &Object, + _cmd: Sel, + notification: id, + ) { + unsafe { + with_window_state(&*this, |state: &mut WindowState| { + state + .window + .emit("did-enter-fullscreen", ()) + .expect("Failed to emit event"); + }); + + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowDidEnterFullScreen: notification]; + } + } + extern "C" fn on_window_will_enter_full_screen( + this: &Object, + _cmd: Sel, + notification: id, + ) { + unsafe { + with_window_state(&*this, |state: &mut WindowState| { + state + .window + .emit("will-enter-fullscreen", ()) + .expect("Failed to emit event"); + }); + + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowWillEnterFullScreen: notification]; + } + } + extern "C" fn on_window_did_exit_full_screen( + this: &Object, + _cmd: Sel, + notification: id, + ) { + unsafe { + with_window_state(&*this, |state: &mut WindowState| { + state + .window + .emit("did-exit-fullscreen", ()) + .expect("Failed to emit event"); + + let id = state.window.ns_window().expect("Failed to emit event") as id; + position_traffic_lights( + UnsafeWindowHandle(id as *mut std::ffi::c_void), + WINDOW_CONTROL_PAD_X, + WINDOW_CONTROL_PAD_Y, + ); + }); + + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowDidExitFullScreen: notification]; + } + } + extern "C" fn on_window_will_exit_full_screen( + this: &Object, + _cmd: Sel, + notification: id, + ) { + unsafe { + with_window_state(&*this, |state: &mut WindowState| { + state + .window + .emit("will-exit-fullscreen", ()) + .expect("Failed to emit event"); + }); + + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowWillExitFullScreen: notification]; + } + } + extern "C" fn on_window_did_fail_to_enter_full_screen( + this: &Object, + _cmd: Sel, + window: id, + ) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window]; + } + } + extern "C" fn on_effective_appearance_did_change( + this: &Object, + _cmd: Sel, + notification: id, + ) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification]; + } + } + extern "C" fn on_effective_appearance_did_changed_on_main_thread( + this: &Object, + _cmd: Sel, + notification: id, + ) { + unsafe { + let super_del: id = *this.get_ivar("super_delegate"); + let _: () = msg_send![ + super_del, + effectiveAppearanceDidChangedOnMainThread: notification + ]; + } + } + + // Are we deallocing this properly ? (I miss safe Rust :( ) + let window_label = window.label().to_string(); + + let app_state = WindowState { window }; + let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void; + let random_str: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(20) + .map(char::from) + .collect(); + + // We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate + // delegate with the same name. + let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str); + + ns_win.setDelegate_(delegate!(&delegate_name, { + window: id = ns_win, + app_box: *mut c_void = app_box, + toolbar: id = cocoa::base::nil, + super_delegate: id = current_delegate, + (windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL, + (windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id), + (windowDidResize:) => on_window_did_resize:: as extern fn(&Object, Sel, id), + (windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id), + (windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id), + (windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id), + (windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id), + (draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL, + (prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL, + (performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL, + (concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id), + (draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id), + (window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger, + (windowDidEnterFullScreen:) => on_window_did_enter_full_screen:: as extern fn(&Object, Sel, id), + (windowWillEnterFullScreen:) => on_window_will_enter_full_screen:: as extern fn(&Object, Sel, id), + (windowDidExitFullScreen:) => on_window_did_exit_full_screen:: as extern fn(&Object, Sel, id), + (windowWillExitFullScreen:) => on_window_will_exit_full_screen:: as extern fn(&Object, Sel, id), + (windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id), + (effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id), + (effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id) + })) + } +} diff --git a/apps/readest-app/src-tauri/tauri.conf.json b/apps/readest-app/src-tauri/tauri.conf.json index 0752baaf..3faf9ce9 100644 --- a/apps/readest-app/src-tauri/tauri.conf.json +++ b/apps/readest-app/src-tauri/tauri.conf.json @@ -10,20 +10,7 @@ "beforeBuildCommand": "pnpm build" }, "app": { - "macOSPrivateApi": true, - "windows": [ - { - "title": "Readest", - "width": 800, - "height": 600, - "resizable": true, - "fullscreen": false, - "maximized": true, - "decorations": false, - "transparent": true, - "shadow": false - } - ], + "windows": [], "security": { "csp": { "default-src": "'self' 'unsafe-inline' blob: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://fonts.gstatic.com", diff --git a/apps/readest-app/src/app/library/components/LibraryHeader.tsx b/apps/readest-app/src/app/library/components/LibraryHeader.tsx index e6df47e0..c95948fa 100644 --- a/apps/readest-app/src/app/library/components/LibraryHeader.tsx +++ b/apps/readest-app/src/app/library/components/LibraryHeader.tsx @@ -1,8 +1,11 @@ +import clsx from 'clsx'; import React, { useEffect, useRef } from 'react'; import { FaSearch } from 'react-icons/fa'; import { PiPlus } from 'react-icons/pi'; import { PiSelectionAllDuotone } from 'react-icons/pi'; +import { useEnv } from '@/context/EnvContext'; +import useFullScreen from '@/hooks/useFullScreen'; import WindowButtons from '@/components/WindowButtons'; interface LibraryHeaderProps { @@ -16,7 +19,10 @@ const LibraryHeader: React.FC = ({ onImportBooks, onToggleSelectMode, }) => { + const { appService } = useEnv(); + const { isFullScreen } = useFullScreen(); const headerRef = useRef(null); + useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.ctrlKey || e.shiftKey) { @@ -45,10 +51,16 @@ const LibraryHeader: React.FC = ({ }; return ( -
+
-
- +
+ = ({
- + {!appService?.isNativeWindow && ( + + )}
); diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 2ecf522a..3d74521a 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -72,6 +72,10 @@ const LibraryPage = () => { setIsSelectMode(!isSelectMode); }; + if (!appService) { + return ; + } + return (
void; onOpenSplitView: () => void; handleTogglePin: () => void; -}> = ({ isPinned, onGoToLibrary, onOpenSplitView, handleTogglePin }) => ( -
-
- +}> = ({ isPinned, onGoToLibrary, onOpenSplitView, handleTogglePin }) => { + const { isFullScreen } = useFullScreen(); + return ( +
+
+ +
+
+ + } + > + + + +
-
- - } - > - - - -
-
-); + ); +}; export default SidebarHeader; diff --git a/apps/readest-app/src/hooks/useFullScreen.ts b/apps/readest-app/src/hooks/useFullScreen.ts new file mode 100644 index 00000000..e77b4fad --- /dev/null +++ b/apps/readest-app/src/hooks/useFullScreen.ts @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; + +const useFullScreen = () => { + const [isFullScreen, setIsFullScreen] = useState(false); + + const handleSwitchFullScreen = async () => { + const { getCurrentWindow } = await import('@tauri-apps/api/window'); + const currentWindow = getCurrentWindow(); + currentWindow.listen('will-enter-fullscreen', () => { + console.log('Window entered fullscreen'); + setIsFullScreen(true); + }); + + currentWindow.listen('will-exit-fullscreen', () => { + console.log('Window exited fullscreen'); + setIsFullScreen(false); + }); + }; + + useEffect(() => { + handleSwitchFullScreen(); + }, []); + + return { isFullScreen }; +}; + +export default useFullScreen; diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts index 1a2b488c..d1993129 100644 --- a/apps/readest-app/src/services/appService.ts +++ b/apps/readest-app/src/services/appService.ts @@ -26,6 +26,7 @@ import { export abstract class BaseAppService implements AppService { localBooksDir: string = ''; abstract isAppDataSandbox: boolean; + abstract isNativeWindow: boolean; abstract fs: FileSystem; abstract resolvePath(fp: string, base: BaseDir): { baseDir: number; base: BaseDir; fp: string }; diff --git a/apps/readest-app/src/services/nativeAppService.ts b/apps/readest-app/src/services/nativeAppService.ts index 6ccc5d27..5fa46c7e 100644 --- a/apps/readest-app/src/services/nativeAppService.ts +++ b/apps/readest-app/src/services/nativeAppService.ts @@ -115,6 +115,7 @@ export const nativeFileSystem: FileSystem = { export class NativeAppService extends BaseAppService { fs = nativeFileSystem; isAppDataSandbox = isMobile; + isNativeWindow = osType() === 'macos'; override resolvePath(fp: string, base: BaseDir): { baseDir: number; base: BaseDir; fp: string } { return resolvePath(fp, base); diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts index 86d086be..296afd64 100644 --- a/apps/readest-app/src/types/system.ts +++ b/apps/readest-app/src/types/system.ts @@ -18,6 +18,8 @@ export interface FileSystem { export interface AppService { fs: FileSystem; + isNativeWindow: boolean; + isAppDataSandbox: boolean; selectDirectory(title: string): Promise; selectFiles(name: string, extensions: string[]): Promise;