Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] ExitRequested not fired on macOS #9198

Open
lockieluke opened this issue Mar 16, 2024 · 4 comments
Open

[bug] ExitRequested not fired on macOS #9198

lockieluke opened this issue Mar 16, 2024 · 4 comments
Labels
platform: macOS status: needs triage This issue needs to triage, applied to new issues type: bug

Comments

@lockieluke
Copy link

Describe the bug

// Prevents additipub(crate)onal console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;

use tauri::{Manager, RunEvent, WindowEvent};
use tokio::runtime::Runtime;

use crate::download_helper::{get_download_client, pause_all_downloads, start_download};
use crate::hardware::{get_cpu_architecture, get_disk_usage, get_friendly_os_name, get_os_type};
use crate::window_ext::WindowExt;

mod window_ext;
mod hardware;
mod download_helper;
mod utils;

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
async fn show_main_window(window: tauri::Window) {
    window.show().expect("Failed to show main window");
}

#[tokio::main]
async fn main() {
    tauri::async_runtime::set(tokio::runtime::Handle::current());

    let app = tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![
            show_main_window,
            get_cpu_architecture,
            get_os_type,
            get_friendly_os_name,
            get_disk_usage,
            pause_all_downloads,
            start_download
        ])
        .on_window_event(|e| {
            let apply_offset = || {
                let win = e.window();
                win.position_traffic_lights(20., 20.);
            };

            match e.event() {
                WindowEvent::Resized(..) => apply_offset(),
                WindowEvent::ThemeChanged(..) => apply_offset(),
                _ => {}
            }
        })
        .build(tauri::generate_context!())
        .expect("error while running tauri application");

    app.run(|app, event| match event {
        RunEvent::ExitRequested {api, ..} => {
            api.prevent_exit();

            println!("HELLO");

            tauri::async_runtime::block_on(async {
                pause_all_downloads().await;
            });
        },
        RunEvent::Ready { .. } => {
            let win = app.get_window("main").unwrap();
            win.set_transparent_titlebar(true);
            win.position_traffic_lights(20., 20.);
        },
        _ => {}
    });
}

nothing gets run when exit is requested

Reproduction

No response

Expected behavior

No response

Full tauri info output

[✔] Environment
    - OS: Mac OS 14.2.1 X64
    ✔ Xcode Command Line Tools: installed
    ✔ rustc: 1.78.0-nightly (0ecbd0605 2024-02-25)
    ✔ cargo: 1.78.0-nightly (194a60b29 2024-02-21)
    ✔ rustup: 1.26.0 (5af9b9484 2023-04-05)
    ✔ Rust toolchain: nightly-aarch64-apple-darwin (default)
    - node: 21.1.0
    - pnpm: 8.15.4
    - yarn: 4.0.2
    - npm: 9.1.2
    - bun: 1.0.30

[-] Packages
    - tauri [RUST]: 1.6.1
    - tauri-build [RUST]: 1.5.1
    - wry [RUST]: 0.24.7
    - tao [RUST]: 0.16.7
    - tauri-cli [RUST]: 1.5.10
    - @tauri-apps/api [NPM]: 1.5.3
    - @tauri-apps/cli [NPM]: 1.5.11

[-] App
    - build-type: bundle
    - CSP: unset
    - distDir: ../dist
    - devPath: http://localhost:1420/
    - framework: SolidJS
    - bundler: Vite


### Stack trace

_No response_

### Additional context

_No response_
@lockieluke lockieluke added status: needs triage This issue needs to triage, applied to new issues type: bug labels Mar 16, 2024
@lockieluke
Copy link
Author

For those who are looking for a way to reliably run async functions on exit in a Tauri app, this is how I managed to do it:

First make an exit lock, this is responsible for blocking your Tauri app from exiting, I made a Lazy global variable for this, just to make it nice and simple. You'll need once_cell.

use once_cell::sync::Lazy;

pub static EXIT_LOCKED: Lazy<std::sync::Mutex<bool>> = Lazy::new(|| {
    std::sync::Mutex::new(true)
});

Then, make an on_exit function that takes in an AppHandle. This function runs your cleanup actions and blocks the main thread using a loop until they have been run, no matter sync or async. You'll need the futures crate for this one.

fn on_exit(app: &AppHandle, need_exiting: bool) {
    futures::executor::block_on(async move {
        tauri::async_runtime::spawn(async move {
			// Execute your cleanup actions here
            stop_all_downloads().await;
            *EXIT_LOCKED.lock().unwrap() = false;
        })
    });
    let win = app.get_window("main").unwrap();
    win.hide().unwrap();
    loop {
        if !*EXIT_LOCKED.lock().unwrap() {
            break;
        }
    };
    win.close().unwrap();

    if need_exiting {
        app.exit(0);
    }
}

Intercept window events to run cleanup actions when a window is closed, this implementation is for single-window apps. Make sure your storing your tauri::Builder to a variable or this won't work.

let app = tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![
	// Your handlers
    ])
   .on_window_event(|event| match event {
	    WindowEvent::CloseRequested { api, .. } => {
	        api.prevent_close();
	        on_exit(&event.window().app_handle(), true);
	    },
   		 _ => {}
    })
    .build(tauri::generate_context!())
    .expect("error while running tauri application");

Intercept events from the app lifecycle events(RunEvent) and run your cleanup actions also when Exit is triggered.

app.run(move |app, event| match event {
    RunEvent::Exit => {
		// Since RunEvent:Exit already executes exit, we'll pass false to on_exit so it won't exit twice
        on_exit(app, false);
    },
    _ => {}
});

This is a janky workaround but it works reliably enough, if there's a better way, please share. Hopefully, Tauri will handle pre-exit events reliably in v2 🤞

@FabianLars
Copy link
Member

How exactly are you exiting the app? Closing Windows via the red button for example should trigger ExitRequested once all windows are closed (even though that's not really how macos apps should behave). right click -> Quit on the dock item may be the same as #3084 if it's not working.

@lockieluke
Copy link
Author

Using Command+Q or Quit from menu bar

@cadomani
Copy link

For those who are looking for a way to reliably run async functions on exit in a Tauri app, this is how I managed to do it:

First make an exit lock, this is responsible for blocking your Tauri app from exiting, I made a Lazy global variable for this, just to make it nice and simple. You'll need once_cell.

use once_cell::sync::Lazy;

pub static EXIT_LOCKED: Lazy<std::sync::Mutex<bool>> = Lazy::new(|| {
    std::sync::Mutex::new(true)
});

Then, make an on_exit function that takes in an AppHandle. This function runs your cleanup actions and blocks the main thread using a loop until they have been run, no matter sync or async. You'll need the futures crate for this one.

fn on_exit(app: &AppHandle, need_exiting: bool) {
    futures::executor::block_on(async move {
        tauri::async_runtime::spawn(async move {
			// Execute your cleanup actions here
            stop_all_downloads().await;
            *EXIT_LOCKED.lock().unwrap() = false;
        })
    });
    let win = app.get_window("main").unwrap();
    win.hide().unwrap();
    loop {
        if !*EXIT_LOCKED.lock().unwrap() {
            break;
        }
    };
    win.close().unwrap();

    if need_exiting {
        app.exit(0);
    }
}

Intercept window events to run cleanup actions when a window is closed, this implementation is for single-window apps. Make sure your storing your tauri::Builder to a variable or this won't work.

let app = tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![
	// Your handlers
    ])
   .on_window_event(|event| match event {
	    WindowEvent::CloseRequested { api, .. } => {
	        api.prevent_close();
	        on_exit(&event.window().app_handle(), true);
	    },
   		 _ => {}
    })
    .build(tauri::generate_context!())
    .expect("error while running tauri application");

Intercept events from the app lifecycle events(RunEvent) and run your cleanup actions also when Exit is triggered.

app.run(move |app, event| match event {
    RunEvent::Exit => {
		// Since RunEvent:Exit already executes exit, we'll pass false to on_exit so it won't exit twice
        on_exit(app, false);
    },
    _ => {}
});

This is a janky workaround but it works reliably enough, if there's a better way, please share. Hopefully, Tauri will handle pre-exit events reliably in v2 🤞

Is this still currently the best way to do this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform: macOS status: needs triage This issue needs to triage, applied to new issues type: bug
Projects
None yet
Development

No branches or pull requests

3 participants