-
-
Save charrondev/43150e940bd2771b1ea88256d491c7a9 to your computer and use it in GitHub Desktop.
| use objc::{msg_send, sel, sel_impl}; | |
| use rand::{distributions::Alphanumeric, Rng}; | |
| use tauri::{ | |
| plugin::{Builder, TauriPlugin}, | |
| Manager, Runtime, Window, | |
| }; // 0.8 | |
| const WINDOW_CONTROL_PAD_X: f64 = 15.0; | |
| const WINDOW_CONTROL_PAD_Y: f64 = 23.0; | |
| struct UnsafeWindowHandle(*mut std::ffi::c_void); | |
| unsafe impl Send for UnsafeWindowHandle {} | |
| unsafe impl Sync for UnsafeWindowHandle {} | |
| pub fn init<R: Runtime>() -> TauriPlugin<R> { | |
| Builder::new("traffic_light_positioner") | |
| .on_window_ready(|window| { | |
| #[cfg(target_os = "macos")] | |
| 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<R: Runtime> { | |
| window: Window<R>, | |
| } | |
| #[cfg(target_os = "macos")] | |
| pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) { | |
| 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<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>( | |
| this: &Object, | |
| func: F, | |
| ) { | |
| let ptr = unsafe { | |
| let x: *mut c_void = *this.get_ivar("app_box"); | |
| &mut *(x as *mut WindowState<R>) | |
| }; | |
| 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<R: Runtime>(this: &Object, _cmd: Sel, notification: id) { | |
| unsafe { | |
| with_window_state(&*this, |state: &mut WindowState<R>| { | |
| 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<R: Runtime>( | |
| this: &Object, | |
| _cmd: Sel, | |
| notification: id, | |
| ) { | |
| unsafe { | |
| with_window_state(&*this, |state: &mut WindowState<R>| { | |
| 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<R: Runtime>( | |
| this: &Object, | |
| _cmd: Sel, | |
| notification: id, | |
| ) { | |
| unsafe { | |
| with_window_state(&*this, |state: &mut WindowState<R>| { | |
| 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<R: Runtime>( | |
| this: &Object, | |
| _cmd: Sel, | |
| notification: id, | |
| ) { | |
| unsafe { | |
| with_window_state(&*this, |state: &mut WindowState<R>| { | |
| 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<R: Runtime>( | |
| this: &Object, | |
| _cmd: Sel, | |
| notification: id, | |
| ) { | |
| unsafe { | |
| with_window_state(&*this, |state: &mut WindowState<R>| { | |
| 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::<R> 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::<R> as extern fn(&Object, Sel, id), | |
| (windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id), | |
| (windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id), | |
| (windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> 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) | |
| })) | |
| } | |
| } |
@liyasthomas Remove the & before the window_ and it should fix it!
@liyasthomas error says you cannot send this variable, so add .clone() to make a clone
Thank you, @morajabi, for the help. Here's the entire code worked for me:
tauri::Builder::default()
.setup(|app| {
let window = app.get_window("main").unwrap();
let window_ = window.clone();
window.on_window_event(move |event| {
if let tauri::WindowEvent::ThemeChanged(_theme) = event {
setup_traffic_light_positioner(window_.clone())
}
});
Ok(())
})Even tho, I got it slightly working with the above implementation, but the traffic lights seem to be flickering a bit in between theme change.
Demo: wyhaya/tauri-plugin-theme#14 (comment)
Edit: this code will only detect and reposition traffic lights when system theme is changed. This doesn't cover manually changing theme, eg. via tauri-plugin-theme. I've opened a thread to fix the flickering issue.
are there any examples of how to use this?
it would be great to see how things are called in main.rs / lib.rs
@morajabi is this still working for you?
I'm having a similar type mismatch when passing
window.clone() as an argument to setup_traffic_light_positioner,
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use tauri::Manager;
mod tlpos; // this is the gist originally posted by charrondev
#[cfg(target_os = "macos")]
#[macro_use]
extern crate cocoa;
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
fn main() {
tauri::Builder::default()
.setup(|app| {
let window = app.get_webview_window("main").unwrap();
window.on_window_event(move |event| {
tlpos::setup_traffic_light_positioner(window.clone())
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}@Fake-User Please post the error you're getting. Maybe also try pasting the code and the error into chatgpt.
For anyone interested, I made this into a Tauri v1 plugin:
https://github.com/itseeleeya/tauri-plugin-trafficlights-positioner/
Hi! Has anyone adapted the traffic light position setup for Tauri v2? I would appreciate any information or links to similar solutions!
Hi! Has anyone adapted the traffic light position setup for Tauri v2? I would appreciate any information or links to similar solutions!
It's already included since Tauri 2.4.0: https://tauri.app/release/@tauri-apps/cli/v2.4.0/#new-features
In order to use it, first update to latest Tauri version:
- Update to latest Tauri CLI: (
yarn/npm/pnpm install @tauri-apps/cli@latest @tauri-apps/api@latest). - Update Cargo Packages (
cd src-tauri && cargo update).
Then there are two ways to set-up the traffic lights position:
a. Directly in src-tauri/tauri.conf.json. For each window you need to set titleBarStyle, decorations and trafficLightPosition:
"app": {
"windows": [
{
"title": "Window title",
"titleBarStyle": "Overlay",
"decorations": true,
"trafficLightPosition": {
"x": 19,
"y": 24
}
}
]
}
It requires titleBarStyle: Overlay and decorations: true.
b. Using the JavaScript API:
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
new WebviewWindow(label, {
url,
title,
titleBarStyle: "overlay",
decorations: true,
trafficLightPosition: {
x: 19,
y: 24,
},
});

Thank you, @morajabi, but I'm having this below type mismatch issue.