11use tauri:: { AppHandle , EventTarget , Manager , Runtime } ;
2+ use tauri_plugin_listener:: ListenerPluginExt ;
23use tauri_plugin_windows:: WindowImpl ;
34use tauri_specta:: Event ;
45
5- use crate :: { DetectEvent , SharedState , dnd} ;
6-
7- pub ( crate ) fn default_ignored_bundle_ids ( ) -> Vec < String > {
8- let hyprnote = [
9- "com.hyprnote.dev" ,
10- "com.hyprnote.stable" ,
11- "com.hyprnote.nightly" ,
12- "com.hyprnote.staging" ,
13- ] ;
14-
15- let dictation_apps = [
16- "com.electron.wispr-flow" ,
17- "com.seewillow.WillowMac" ,
18- "com.superduper.superwhisper" ,
19- "com.prakashjoshipax.VoiceInk" ,
20- "com.goodsnooze.macwhisper" ,
21- "com.descript.beachcube" ,
22- "com.apple.VoiceMemos" ,
23- "com.electron.aqua-voice" ,
24- ] ;
25-
26- let ides = [
27- "dev.warp.Warp-Stable" ,
28- "com.exafunction.windsurf" ,
29- "com.microsoft.VSCode" ,
30- "com.todesktop.230313mzl4w4u92" ,
31- ] ;
32-
33- let screen_recording = [
34- "so.cap.desktop" ,
35- "com.timpler.screenstudio" ,
36- "com.loom.desktop" ,
37- "com.obsproject.obs-studio" ,
38- ] ;
39-
40- let ai_assistants = [ "com.openai.chat" , "com.anthropic.claudefordesktop" ] ;
41-
42- let other = [
43- "com.raycast.macos" ,
44- "com.apple.garageband10" ,
45- "com.apple.Sound-Settings.extension" ,
46- ] ;
47-
48- dictation_apps
49- . into_iter ( )
50- . chain ( hyprnote)
51- . chain ( ides)
52- . chain ( screen_recording)
53- . chain ( ai_assistants)
54- . chain ( other)
55- . map ( String :: from)
56- . collect ( )
57- }
6+ use crate :: {
7+ DetectEvent , SharedState , dnd,
8+ policy:: { MicEventType , PolicyContext } ,
9+ } ;
5810
5911pub async fn setup < R : Runtime > ( app : & AppHandle < R > ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
6012 let app_handle = app. app_handle ( ) . clone ( ) ;
6113 let callback = hypr_detect:: new_callback ( move |event| {
62- let state = app_handle. state :: < SharedState > ( ) ;
14+ let app_handle_clone = app_handle. clone ( ) ;
6315
6416 match event {
6517 hypr_detect:: DetectEvent :: MicStarted ( apps) => {
66- let state_guard = state. blocking_lock ( ) ;
67-
68- if state_guard. respect_do_not_disturb && dnd:: is_do_not_disturb ( ) {
69- tracing:: info!( reason = "respect_do_not_disturb" , "skip_notification" ) ;
70- return ;
71- }
72-
73- let filtered_apps = filter_apps ( apps, & state_guard. ignored_bundle_ids ) ;
74- drop ( state_guard) ;
75-
76- if filtered_apps. is_empty ( ) {
77- tracing:: info!( reason = "all_apps_filtered" , "skip_notification" ) ;
78- return ;
79- }
80-
81- emit_to_main (
82- & app_handle,
83- DetectEvent :: MicStarted {
84- key : uuid:: Uuid :: new_v4 ( ) . to_string ( ) ,
85- apps : filtered_apps,
86- } ,
87- ) ;
18+ tauri:: async_runtime:: spawn ( async move {
19+ handle_mic_started ( & app_handle_clone, apps) . await ;
20+ } ) ;
8821 }
8922 hypr_detect:: DetectEvent :: MicStopped ( apps) => {
90- let state_guard = state. blocking_lock ( ) ;
91-
92- if state_guard. respect_do_not_disturb && dnd:: is_do_not_disturb ( ) {
93- tracing:: info!( reason = "respect_do_not_disturb" , "skip_mic_stopped" ) ;
94- return ;
95- }
96-
97- let filtered_apps = filter_apps ( apps, & state_guard. ignored_bundle_ids ) ;
98- drop ( state_guard) ;
99-
100- if filtered_apps. is_empty ( ) {
101- tracing:: info!( reason = "all_apps_filtered" , "skip_mic_stopped" ) ;
102- return ;
103- }
104-
105- emit_to_main (
106- & app_handle,
107- DetectEvent :: MicStopped {
108- apps : filtered_apps,
109- } ,
110- ) ;
23+ tauri:: async_runtime:: spawn ( async move {
24+ handle_mic_stopped ( & app_handle_clone, apps) . await ;
25+ } ) ;
11126 }
11227 #[ cfg( all( target_os = "macos" , feature = "zoom" ) ) ]
11328 hypr_detect:: DetectEvent :: ZoomMuteStateChanged { value } => {
@@ -128,15 +43,87 @@ pub async fn setup<R: Runtime>(app: &AppHandle<R>) -> Result<(), Box<dyn std::er
12843 Ok ( ( ) )
12944}
13045
131- fn filter_apps (
46+ async fn handle_mic_started < R : Runtime > (
47+ app_handle : & AppHandle < R > ,
48+ apps : Vec < hypr_detect:: InstalledApp > ,
49+ ) {
50+ let is_listening = {
51+ let listener_state = app_handle. listener ( ) . get_state ( ) . await ;
52+ matches ! (
53+ listener_state,
54+ tauri_plugin_listener:: fsm:: State :: Active
55+ | tauri_plugin_listener:: fsm:: State :: Finalizing
56+ )
57+ } ;
58+
59+ let state = app_handle. state :: < SharedState > ( ) ;
60+ let state_guard = state. lock ( ) . await ;
61+
62+ let is_dnd = dnd:: is_do_not_disturb ( ) ;
63+
64+ let ctx = PolicyContext {
65+ apps : & apps,
66+ is_listening,
67+ is_dnd,
68+ event_type : MicEventType :: Started ,
69+ } ;
70+
71+ match state_guard. policy . evaluate ( & ctx) {
72+ Ok ( result) => {
73+ drop ( state_guard) ;
74+ emit_to_main (
75+ app_handle,
76+ DetectEvent :: MicStarted {
77+ key : result. dedup_key ,
78+ apps : result. filtered_apps ,
79+ } ,
80+ ) ;
81+ }
82+ Err ( reason) => {
83+ tracing:: info!( ?reason, "skip_notification" ) ;
84+ }
85+ }
86+ }
87+
88+ async fn handle_mic_stopped < R : Runtime > (
89+ app_handle : & AppHandle < R > ,
13290 apps : Vec < hypr_detect:: InstalledApp > ,
133- ignored_bundle_ids : & [ String ] ,
134- ) -> Vec < hypr_detect:: InstalledApp > {
135- let default_ignored = default_ignored_bundle_ids ( ) ;
136- apps. into_iter ( )
137- . filter ( |app| !ignored_bundle_ids. contains ( & app. id ) )
138- . filter ( |app| !default_ignored. contains ( & app. id ) )
139- . collect ( )
91+ ) {
92+ let is_listening = {
93+ let listener_state = app_handle. listener ( ) . get_state ( ) . await ;
94+ matches ! (
95+ listener_state,
96+ tauri_plugin_listener:: fsm:: State :: Active
97+ | tauri_plugin_listener:: fsm:: State :: Finalizing
98+ )
99+ } ;
100+
101+ let state = app_handle. state :: < SharedState > ( ) ;
102+ let state_guard = state. lock ( ) . await ;
103+
104+ let is_dnd = dnd:: is_do_not_disturb ( ) ;
105+
106+ let ctx = PolicyContext {
107+ apps : & apps,
108+ is_listening,
109+ is_dnd,
110+ event_type : MicEventType :: Stopped ,
111+ } ;
112+
113+ match state_guard. policy . evaluate ( & ctx) {
114+ Ok ( result) => {
115+ drop ( state_guard) ;
116+ emit_to_main (
117+ app_handle,
118+ DetectEvent :: MicStopped {
119+ apps : result. filtered_apps ,
120+ } ,
121+ ) ;
122+ }
123+ Err ( reason) => {
124+ tracing:: info!( ?reason, "skip_mic_stopped" ) ;
125+ }
126+ }
140127}
141128
142129fn emit_to_main < R : Runtime > ( app_handle : & AppHandle < R > , event : DetectEvent ) {
0 commit comments