use std::{
cell::RefCell,
collections::HashMap,
ffi::CString,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
mpsc::sync_channel,
},
thread::{self, ThreadId},
};
use serde::de::DeserializeOwned;
use tauri::{AppHandle, Manager, Resource, ResourceId, Runtime, plugin::PluginApi};
use crate::Error;
use crate::PermissionFlowExt;
use crate::models::*;
use permission_flow::{
AppPath, Permission as NativePermission, PermissionFlowController, StartFlowOptions,
};
thread_local! {
static CONTROLLERS: RefCell<HashMap<u64, PermissionFlowController>> = RefCell::new(HashMap::new());
}
pub fn init<R: Runtime, C: DeserializeOwned>(
app: &AppHandle<R>,
_api: PluginApi<R, C>,
) -> crate::Result<PermissionFlowPlugin<R>> {
Ok(PermissionFlowPlugin {
app: app.clone(),
main_thread_id: thread::current().id(),
next_controller_id: AtomicU64::new(1),
})
}
pub struct PermissionFlowPlugin<R: Runtime> {
app: AppHandle<R>,
main_thread_id: ThreadId,
next_controller_id: AtomicU64,
}
impl<R: Runtime> PermissionFlowPlugin<R> {
pub fn create(&self) -> crate::Result<ResourceId> {
let controller_id = self.next_controller_id.fetch_add(1, Ordering::Relaxed);
self.run_on_main_thread(move || {
CONTROLLERS.with(|controllers| {
let mut controllers = controllers.borrow_mut();
controllers.insert(controller_id, PermissionFlowController::new()?);
Ok(())
})
})?;
let mut resources_table = self.app.resources_table();
let rid = resources_table.add(PermissionFlowHandle {
app: self.app.clone(),
controller_id,
});
Ok(rid)
}
pub fn start_flow(&self, controller_id: u64, payload: StartFlowRequest) -> crate::Result<()> {
self.run_on_main_thread(move || {
CONTROLLERS.with(|controllers| {
let controllers = controllers.borrow();
let controller = controllers.get(&controller_id).ok_or(Error::DriverClosed)?;
let app_path = CString::new(payload.app_path)?;
let mut options =
StartFlowOptions::new(permission_to_native(payload.permission), app_path);
if !payload.use_click_source_frame {
options = options.without_click_source_frame();
}
controller.start_flow(options)?;
Ok(())
})
})
}
pub fn stop_current_flow(&self, controller_id: u64) -> crate::Result<()> {
self.run_on_main_thread(move || {
CONTROLLERS.with(|controllers| {
let controllers = controllers.borrow();
let controller = controllers.get(&controller_id).ok_or(Error::DriverClosed)?;
controller.stop_current_flow()?;
Ok(())
})
})
}
pub fn authorization_state(
&self,
permission: Permission,
) -> crate::Result<PermissionAuthorizationState> {
Ok(permission_to_native(permission)
.authorization_state()?
.into())
}
pub fn suggested_host_app_path(&self) -> crate::Result<Option<String>> {
Ok(
AppPath::suggested_host_app()
.map(|path| path.as_c_str().to_string_lossy().into_owned()),
)
}
fn drop_controller(&self, controller_id: u64) {
let _ = self.run_on_main_thread(move || {
CONTROLLERS.with(|controllers| {
let mut controllers = controllers.borrow_mut();
controllers.remove(&controller_id);
});
Ok(())
});
}
fn run_on_main_thread<T, F>(&self, action: F) -> crate::Result<T>
where
T: Send + 'static,
F: FnOnce() -> crate::Result<T> + Send + 'static,
{
if thread::current().id() == self.main_thread_id {
return action();
}
let (sender, receiver) = sync_channel(1);
self.app.run_on_main_thread(move || {
let _ = sender.send(action());
})?;
receiver
.recv()
.map_err(|_| Error::MainThreadChannelClosed)?
}
}
pub struct PermissionFlowHandle<R: Runtime> {
app: AppHandle<R>,
controller_id: u64,
}
impl<R: Runtime> PermissionFlowHandle<R> {
pub fn controller_id(&self) -> u64 {
self.controller_id
}
}
impl<R: Runtime> Resource for PermissionFlowHandle<R> {
fn close(self: Arc<Self>) {
self.app
.permission_flow()
.drop_controller(self.controller_id);
}
}
impl From<permission_flow::PermissionAuthorizationState> for PermissionAuthorizationState {
fn from(value: permission_flow::PermissionAuthorizationState) -> Self {
match value {
permission_flow::PermissionAuthorizationState::Granted => Self::Granted,
permission_flow::PermissionAuthorizationState::NotGranted => Self::NotGranted,
permission_flow::PermissionAuthorizationState::Unknown => Self::Unknown,
permission_flow::PermissionAuthorizationState::Checking => Self::Checking,
}
}
}
fn permission_to_native(permission: Permission) -> NativePermission {
match permission {
Permission::Accessibility => NativePermission::ACCESSIBILITY,
Permission::InputMonitoring => NativePermission::INPUT_MONITORING,
Permission::ScreenRecording => NativePermission::SCREEN_RECORDING,
Permission::AppManagement => NativePermission::APP_MANAGEMENT,
Permission::Bluetooth => NativePermission::BLUETOOTH,
Permission::DeveloperTools => NativePermission::DEVELOPER_TOOLS,
Permission::FullDiskAccess => NativePermission::FULL_DISK_ACCESS,
Permission::MediaAppleMusic => NativePermission::MEDIA_APPLE_MUSIC,
}
}