Search code examples
rustactix-webtauri

How to call tauri instance inside the actix web server to control window state?


I have tauri apps that also running a background http services. I want to make this app controllable via http on localhost, but it seems like tauri wont allow me to have that. Here is my main.rs code

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

use actix_web::web;
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
use std::sync::{Once, ONCE_INIT};
use tauri::Manager;
use serde::Deserialize;

static INIT: Once = ONCE_INIT;
static mut APP: Option<tauri::App> = None;

#[derive(Deserialize)]
struct AppSession {
    action: String,
}

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello world!")
}

#[get("/actions")]
async fn handle_actions(query: web::Query<AppSession>) -> impl Responder {
    let action = &query.action;
    unsafe {
        if let Some(app) = APP.as_ref() {
            match action.as_str() {
                "session_end" => {
                    app.get_window("main").unwrap().show().unwrap();
                    HttpResponse::Ok().body("Session ending, app shown")
                }
                "session_start" => {
                    app.get_window("main").unwrap().hide().unwrap();
                    HttpResponse::Ok().body("Session starting, app hidden")
                }
                _ => HttpResponse::Ok().body("Unknown action"),
            }
        } else {
            HttpResponse::InternalServerError().body("App instance not available")
        }
    }
}

fn main() {
    let app = tauri::Builder::default()
        .plugin(tauri_plugin_websocket::init())
        .setup(|app| {
            tauri::async_runtime::spawn(
                HttpServer::new(move || {
                    let app = app.clone();
                    App::new()
                        .service(hello)
                        .service(handle_actions)
                        .app_data(app)
                })
                .bind(("127.0.0.1", 8080))
                .expect("Failed to bind address")
                .run(),
            );

            Ok(())
        })
        .build(tauri::generate_context!())
        .expect("error while running tauri application");

    INIT.call_once(|| {
        // Initialize your tauri::App instance here
        unsafe {
            APP = Some(app);
        }
    });

    app.run(|_, _| {})
}

I tried to use Arc value so I can use the tauri app between thread but it seems not doable, since the code always errored that tauri:App doesn't implement the trait Send

static INIT: std::sync::Once = std::sync::Once::new();
static mut APP: Option<Arc<Mutex<Option<tauri::App>>>> = None;

Right now using the above full code, It still errored in line

let app = app.clone()

Saying that

no method named `clone` found for mutable reference `&mut tauri::App` in the current scope
method not found in `&mut App`

How to resolve this issue?


Solution

  • I ended up using OneLock with Window to initialize global variables. This will store the window reference and can be used to controls Tauri window instances. Here is the complete code :

    // Prevents additional console window on Windows in release, DO NOT REMOVE!!
    #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
    
    use std::sync::OnceLock;
    
    use actix_web::web;
    use actix_web::{App, HttpResponse, HttpServer, Responder};
    use serde::Deserialize;
    use tauri::{Manager, Window};
    
    static WINDOW: OnceLock<Window> = OnceLock::new();
    
    #[derive(Deserialize)]
    struct AppSession {
        event_type: String,
    }
    
    #[derive(Clone, serde::Serialize)]
    struct Payload {
      message: String,
    }
    
    fn main() {
        let app = tauri::Builder::default()
            .plugin(tauri_plugin_websocket::init())
            .setup(|app| {
                let window = app.get_window("main").unwrap();
    
                _ = WINDOW.set(window);
    
                async fn hello() -> impl Responder {
                    HttpResponse::Ok().body("Hello world!")
                }
    
                async fn handle_actions(query: web::Query<AppSession>) -> impl Responder {
                    let action = &query.event_type;
                    match action.as_str() {
                        "session_end" => {
                            WINDOW.get()
                                .expect("Window is available")
                                .emit("session_end", Payload { message: "Sesion End".into() })
                                .unwrap();
                            HttpResponse::Ok().body("Session ending, app shown")
                        }
                        _ => HttpResponse::Ok().body("Unknown action"),
                    }
                }
    
                tauri::async_runtime::spawn(
                    HttpServer::new(move || {
                        App::new()
                            .route("/", web::get().to(hello))
                            .route("/actions", web::get().to(handle_actions))
                    })
                    .bind(("127.0.0.1", 8081))
                    .expect("Failed to bind address")
                    .run(),
                );
    
                Ok(())
            })
            .build(tauri::generate_context!())
            .expect("error while running tauri application");
    
        app.run(|_, _| {})
    }