The file C:\Users\MikeSwanson\claude-projects\gururmm\agent\src\tray_launcher.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
     1→//! Tray Launcher - Launches the tray application in user sessions
     2→//!
     3→//! This module handles launching the GuruRMM tray application in each
     4→//! logged-in user's session. The agent service runs as SYSTEM, so we need
     5→//! to use CreateProcessAsUser to launch the tray in the user's context.
     6→
     7→#[cfg(windows)]
     8→pub mod windows {
     9→    use std::collections::HashMap;
    10→    use std::path::PathBuf;
    11→    use tokio::sync::RwLock;
    12→    use tracing::{debug, error, info, warn};
    13→
    14→    use windows::Win32::Foundation::{CloseHandle, HANDLE};
    15→    use windows::Win32::Security::{
    16→        DuplicateTokenEx, SecurityIdentification, TOKEN_ALL_ACCESS, TokenPrimary,
    17→    };
    18→    use windows::Win32::System::RemoteDesktop::{
    19→        WTSActive, WTSEnumerateSessionsW, WTSFreeMemory, WTSQueryUserToken, WTS_SESSION_INFOW,
    20→    };
    21→    use windows::Win32::System::Threading::{
    22→        CreateProcessAsUserW, GetExitCodeProcess, TerminateProcess, PROCESS_CREATION_FLAGS,
    23→        PROCESS_INFORMATION, STARTUPINFOW,
    24→    };
    25→
    26→    use crate::ipc::TrayPolicy;
    27→
    28→    /// Exit code indicating process is still running
    29→    const STATUS_PENDING: u32 = 259; // Same as STILL_ACTIVE
    30→
    31→    /// Information about a running tray process
    32→    #[derive(Debug)]
    33→    struct TrayProcess {
    34→        session_id: u32,
    35→        process_handle: HANDLE,
    36→        process_id: u32,
    37→    }
    38→
    39→    /// Manages tray application instances across user sessions
    40→    pub struct TrayLauncher {
    41→        /// Path to the tray executable
    42→        tray_exe_path: PathBuf,
    43→
    44→        /// Currently running tray processes (session_id -> process info)
    45→        running: RwLock<HashMap<u32, TrayProcess>>,
    46→
    47→        /// Current policy
    48→        policy: RwLock<TrayPolicy>,
    49→    }
    50→
    51→    impl TrayLauncher {
    52→        /// Create a new tray launcher
    53→        pub fn new(tray_exe_path: PathBuf) -> Self {
    54→            Self {
    55→                tray_exe_path,
    56→                running: RwLock::new(HashMap::new()),
    57→                policy: RwLock::new(TrayPolicy::default_permissive()),
    58→            }
    59→        }
    60→
    61→        /// Update the tray policy
    62→        pub async fn update_policy(&self, policy: TrayPolicy) {
    63→            let was_enabled = self.policy.read().await.enabled;
    64→            *self.policy.write().await = policy.clone();
    65→
    66→            if policy.enabled && !was_enabled {
    67→                // Policy just enabled - launch in all sessions
    68→                info!("Tray policy enabled, launching in user sessions");
    69→                self.ensure_running().await;
    70→            } else if !policy.enabled && was_enabled {
    71→                // Policy just disabled - terminate all
    72→                info!("Tray policy disabled, terminating tray processes");
    73→                self.terminate_all().await;
    74→            }
    75→        }
    76→
    77→        /// Ensure tray is running in all active user sessions
    78→        pub async fn ensure_running(&self) {
    79→            let policy = self.policy.read().await;
    80→            if !policy.enabled {
    81→                debug!("Tray policy disabled, skipping launch");
    82→                return;
    83→            }
    84→            drop(policy);
    85→
    86→            if !self.tray_exe_path.exists() {
    87→                warn!("Tray executable not found: {:?}", self.tray_exe_path);
    88→                return;
    89→            }
    90→
    91→            // Get active sessions
    92→            let sessions = match self.enumerate_sessions() {
    93→                Ok(s) => s,
    94→                Err(e) => {
    95→                    error!("Failed to enumerate sessions: {}", e);
    96→                    return;
    97→                }
    98→            };
    99→
   100→            let mut running = self.running.write().await;
   101→
   102→            // Clean up dead processes
   103→            running.retain(|session_id, proc| {
   104→                if !is_process_running(proc.process_handle) {
   105→                    debug!("Tray process in session {} has exited", session_id);
   106→                    unsafe {
   107→                        let _ = CloseHandle(proc.process_handle);
   108→                    }
   109→                    false
   110→                } else {
   111→                    true
   112→                }
   113→            });
   114→
   115→            // Launch in sessions that don't have tray running
   116→            for session_id in sessions {
   117→                if running.contains_key(&session_id) {
   118→                    continue;
   119→                }
   120→
   121→                match self.launch_in_session(session_id) {
   122→                    Ok(proc) => {
   123→                        info!(
   124→                            "Launched tray in session {} (PID {})",
   125→                            session_id, proc.process_id
   126→                        );
   127→                        running.insert(session_id, proc);
   128→                    }
   129→                    Err(e) => {
   130→                        warn!("Failed to launch tray in session {}: {}", session_id, e);
   131→                    }
   132→                }
   133→            }
   134→        }
   135→
   136→        /// Terminate all running tray processes
   137→        pub async fn terminate_all(&self) {
   138→            let mut running = self.running.write().await;
   139→
   140→            for (session_id, proc) in running.drain() {
   141→                info!(
   142→                    "Terminating tray in session {} (PID {})",
   143→                    session_id, proc.process_id
   144→                );
   145→                terminate_process(proc.process_handle);
   146→                unsafe {
   147→                    let _ = CloseHandle(proc.process_handle);
   148→                }
   149→            }
   150→        }
   151→
   152→        /// Handle a session change event
   153→        pub async fn on_session_change(&self, session_id: u32, is_logon: bool) {
   154→            if is_logon {
   155→                info!("User logged on to session {}", session_id);
   156→                // Small delay to let the session fully initialize
   157→                tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
   158→                self.ensure_running().await;
   159→            } else {
   160→                info!("User logged off from session {}", session_id);
   161→                // Clean up the process entry (process likely already dead)
   162→                let mut running = self.running.write().await;
   163→                if let Some(proc) = running.remove(&session_id) {
   164→                    unsafe {
   165→                        let _ = CloseHandle(proc.process_handle);
   166→                    }
   167→                }
   168→            }
   169→        }
   170→
   171→        /// Enumerate active user sessions
   172→        fn enumerate_sessions(&self) -> anyhow::Result<Vec<u32>> {
   173→            let mut sessions = Vec::new();
   174→
   175→            unsafe {
   176→                let mut session_info: *mut WTS_SESSION_INFOW = std::ptr::null_mut();
   177→                let mut count: u32 = 0;
   178→
   179→                // Enumerate sessions on the local server (None = local)
   180→                WTSEnumerateSessionsW(None, 0, 1, &mut session_info, &mut count).map_err(|e| {
   181→                    anyhow::anyhow!("WTSEnumerateSessionsW failed: {:?}", e)
   182→                })?;
   183→
   184→                // Iterate through sessions
   185→                let session_slice = std::slice::from_raw_parts(session_info, count as usize);
   186→                for session in session_slice {
   187→                    // Only consider active sessions (user logged in and at desktop)
   188→                    if session.State == WTSActive && session.SessionId != 0 {
   189→                        debug!(
   190→                            "Found active session: ID={}, State={:?}",
   191→                            session.SessionId, session.State
   192→                        );
   193→                        sessions.push(session.SessionId);
   194→                    }
   195→                }
   196→
   197→                // Free the memory allocated by WTS
   198→                WTSFreeMemory(session_info as *mut _);
   199→            }
   200→
   201→            Ok(sessions)
   202→        }
   203→
   204→        /// Launch the tray application in a specific user session
   205→        fn launch_in_session(&self, session_id: u32) -> anyhow::Result<TrayProcess> {
   206→            unsafe {
   207→                // Get the user token for this session
   208→                let mut user_token = HANDLE::default();
   209→                WTSQueryUserToken(session_id, &mut user_token).map_err(|e| {
   210→                    anyhow::anyhow!("WTSQueryUserToken failed: {:?}", e)
   211→                })?;
   212→
   213→                // Duplicate the token to get a primary token we can use
   214→                let mut primary_token = HANDLE::default();
   215→                let dup_result = DuplicateTokenEx(
   216→                    user_token,
   217→                    TOKEN_ALL_ACCESS,
   218→                    None,
   219→                    SecurityIdentification,
   220→                    TokenPrimary,
   221→                    &mut primary_token,
   222→                );
   223→
   224→                // Close the original token
   225→                let _ = CloseHandle(user_token);
   226→
   227→                dup_result.map_err(|e| {
   228→                    anyhow::anyhow!("DuplicateTokenEx failed: {:?}", e)
   229→                })?;
   230→
   231→                // Build the command line
   232→                let exe_path = self.tray_exe_path.to_string_lossy();
   233→                let mut command_line: Vec<u16> = format!("\"{}\"", exe_path)
   234→                    .encode_utf16()
   235→                    .chain(std::iter::once(0))
   236→                    .collect();
   237→
   238→                // Set up startup info
   239→                let mut startup_info = STARTUPINFOW::default();
   240→                startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
   241→                // Use the user's desktop
   242→                let mut desktop: Vec<u16> = "winsta0\\default"
   243→                    .encode_utf16()
   244→                    .chain(std::iter::once(0))
   245→                    .collect();
   246→                startup_info.lpDesktop = windows::core::PWSTR(desktop.as_mut_ptr());
   247→
   248→                let mut process_info = PROCESS_INFORMATION::default();
   249→
   250→                // Create the process in the user's session
   251→                let create_result = CreateProcessAsUserW(
   252→                    primary_token,
   253→                    None,
   254→                    windows::core::PWSTR(command_line.as_mut_ptr()),
   255→                    None,
   256→                    None,
   257→                    false,
   258→                    PROCESS_CREATION_FLAGS(0),
   259→                    None,
   260→                    None,
   261→                    &startup_info,
   262→                    &mut process_info,
   263→                );
   264→
   265→                // Close the duplicated token
   266→                let _ = CloseHandle(primary_token);
   267→
   268→                create_result.map_err(|e| {
   269→                    anyhow::anyhow!("CreateProcessAsUserW failed: {:?}", e)
   270→                })?;
   271→
   272→                // Close the thread handle (we don't need it)
   273→                let _ = CloseHandle(process_info.hThread);
   274→
   275→                Ok(TrayProcess {
   276→                    session_id,
   277→                    process_handle: process_info.hProcess,
   278→                    process_id: process_info.dwProcessId,
   279→                })
   280→            }
   281→        }
   282→    }
   283→
   284→    impl Drop for TrayLauncher {
   285→        fn drop(&mut self) {
   286→            // Clean up process handles
   287→            if let Ok(mut running) = self.running.try_write() {
   288→                for (_, proc) in running.drain() {
   289→                    unsafe {
   290→                        let _ = CloseHandle(proc.process_handle);
   291→                    }
   292→                }
   293→            }
   294→        }
   295→    }
   296→
   297→    /// Check if a process is still running
   298→    fn is_process_running(handle: HANDLE) -> bool {
   299→        unsafe {
   300→            let mut exit_code: u32 = 0;
   301→            if GetExitCodeProcess(handle, &mut exit_code).is_ok() {
   302→                exit_code == STATUS_PENDING
   303→            } else {
   304→                false
   305→            }
   306→        }
   307→    }
   308→
   309→    /// Terminate a process
   310→    fn terminate_process(handle: HANDLE) {
   311→        unsafe {
   312→            let _ = TerminateProcess(handle, 0);
   313→        }
   314→    }
   315→}
   316→
   317→#[cfg(unix)]
   318→pub mod unix {
   319→    use std::path::PathBuf;
   320→    use tracing::info;
   321→
   322→    use crate::ipc::TrayPolicy;
   323→
   324→    /// Placeholder for Unix tray launcher
   325→    pub struct TrayLauncher {
   326→        #[allow(dead_code)]
   327→        tray_exe_path: PathBuf,
   328→    }
   329→
   330→    impl TrayLauncher {
   331→        pub fn new(tray_exe_path: PathBuf) -> Self {
   332→            Self { tray_exe_path }
   333→        }
   334→
   335→        pub async fn update_policy(&self, _policy: TrayPolicy) {
   336→            // TODO: Implement for Unix (XDG autostart or user systemd service)
   337→            info!("Unix tray launcher not yet implemented");
   338→        }
   339→
   340→        pub async fn ensure_running(&self) {
   341→            info!("Unix tray launcher not yet implemented");
   342→        }
   343→
   344→        pub async fn terminate_all(&self) {
   345→            info!("Unix tray launcher not yet implemented");
   346→        }
   347→
   348→        pub async fn on_session_change(&self, _session_id: u32, _is_logon: bool) {
   349→            // No-op on Unix for now
   350→        }
   351→    }
   352→}
   353→
   354→#[cfg(windows)]
   355→pub use windows::TrayLauncher;
   356→
   357→#[cfg(unix)]
   358→pub use unix::TrayLauncher;
   359→

//! Tray Launcher - Launches the tray application in user sessions
//!
//! This module handles launching the GuruRMM tray application in each
//! logged-in user's session. The agent service runs as SYSTEM, so we need
//! to use CreateProcessAsUser to launch the tray in the user's context.

#[cfg(windows)]
pub mod windows {
    use std::collections::HashMap;
    use std::path::PathBuf;
    use tokio::sync::RwLock;
    use tracing::{debug, error, info, warn};

    use windows::Win32::Foundation::{CloseHandle, HANDLE};
    use windows::Win32::Security::{
        DuplicateTokenEx, SecurityIdentification, TOKEN_ALL_ACCESS, TokenPrimary,
    };
    use windows::Win32::System::RemoteDesktop::{
        WTSActive, WTSEnumerateSessionsW, WTSFreeMemory, WTSQueryUserToken, WTS_SESSION_INFOW,
    };
    use windows::Win32::System::Threading::{
        CreateProcessAsUserW, GetExitCodeProcess, TerminateProcess, PROCESS_CREATION_FLAGS,
        PROCESS_INFORMATION, STARTUPINFOW,
    };

    use crate::ipc::TrayPolicy;

    /// Exit code indicating process is still running
    const STATUS_PENDING: u32 = 259; // Same as STILL_ACTIVE

    /// Information about a running tray process
    #[derive(Debug)]
    struct TrayProcess {
        session_id: u32,
        process_handle: HANDLE,
        process_id: u32,
    }

    /// Manages tray application instances across user sessions
    pub struct TrayLauncher {
        /// Path to the tray executable
        tray_exe_path: PathBuf,

        /// Currently running tray processes (session_id -> process info)
        running: RwLock<HashMap<u32, TrayProcess>>,

        /// Current policy
        policy: RwLock<TrayPolicy>,
    }

    impl TrayLauncher {
        /// Create a new tray launcher
        pub fn new(tray_exe_path: PathBuf) -> Self {
            Self {
                tray_exe_path,
                running: RwLock::new(HashMap::new()),
                policy: RwLock::new(TrayPolicy::default_permissive()),
            }
        }

        /// Update the tray policy
        pub async fn update_policy(&self, policy: TrayPolicy) {
            let was_enabled = self.policy.read().await.enabled;
            *self.policy.write().await = policy.clone();

            if policy.enabled && !was_enabled {
                // Policy just enabled - launch in all sessions
                info!("Tray policy enabled, launching in user sessions");
                self.ensure_running().await;
            } else if !policy.enabled && was_enabled {
                // Policy just disabled - terminate all
                info!("Tray policy disabled, terminating tray processes");
                self.terminate_all().await;
            }
        }

        /// Ensure tray is running in all active user sessions
        pub async fn ensure_running(&self) {
            let policy = self.policy.read().await;
            if !policy.enabled {
                debug!("Tray policy disabled, skipping launch");
                return;
            }
            drop(policy);

            if !self.tray_exe_path.exists() {
                warn!("Tray executable not found: {:?}", self.tray_exe_path);
                return;
            }

            // Get active sessions
            let sessions = match self.enumerate_sessions() {
                Ok(s) => s,
                Err(e) => {
                    error!("Failed to enumerate sessions: {}", e);
                    return;
                }
            };

            let mut running = self.running.write().await;

            // Clean up dead processes
            running.retain(|session_id, proc| {
                if !is_process_running(proc.process_handle) {
                    debug!("Tray process in session {} has exited", session_id);
                    unsafe {
                        let _ = CloseHandle(proc.process_handle);
                    }
                    false
                } else {
                    true
                }
            });

            // Launch in sessions that don't have tray running
            for session_id in sessions {
                if running.contains_key(&session_id) {
                    continue;
                }

                match self.launch_in_session(session_id) {
                    Ok(proc) => {
                        info!(
                            "Launched tray in session {} (PID {})",
                            session_id, proc.process_id
                        );
                        running.insert(session_id, proc);
                    }
                    Err(e) => {
                        warn!("Failed to launch tray in session {}: {}", session_id, e);
                    }
                }
            }
        }

        /// Terminate all running tray processes
        pub async fn terminate_all(&self) {
            let mut running = self.running.write().await;

            for (session_id, proc) in running.drain() {
                info!(
                    "Terminating tray in session {} (PID {})",
                    session_id, proc.process_id
                );
                terminate_process(proc.process_handle);
                unsafe {
                    let _ = CloseHandle(proc.process_handle);
                }
            }
        }

        /// Handle a session change event
        pub async fn on_session_change(&self, session_id: u32, is_logon: bool) {
            if is_logon {
                info!("User logged on to session {}", session_id);
                // Small delay to let the session fully initialize
                tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
                self.ensure_running().await;
            } else {
                info!("User logged off from session {}", session_id);
                // Clean up the process entry (process likely already dead)
                let mut running = self.running.write().await;
                if let Some(proc) = running.remove(&session_id) {
                    unsafe {
                        let _ = CloseHandle(proc.process_handle);
                    }
                }
            }
        }

        /// Enumerate active user sessions
        fn enumerate_sessions(&self) -> anyhow::Result<Vec<u32>> {
            let mut sessions = Vec::new();

            unsafe {
                let mut session_info: *mut WTS_SESSION_INFOW = std::ptr::null_mut();
                let mut count: u32 = 0;

                // Enumerate sessions on the local server (None = local)
                WTSEnumerateSessionsW(None, 0, 1, &mut session_info, &mut count).map_err(|e| {
                    anyhow::anyhow!("WTSEnumerateSessionsW failed: {:?}", e)
                })?;

                // Iterate through sessions
                let session_slice = std::slice::from_raw_parts(session_info, count as usize);
                for session in session_slice {
                    // Only consider active sessions (user logged in and at desktop)
                    if session.State == WTSActive && session.SessionId != 0 {
                        debug!(
                            "Found active session: ID={}, State={:?}",
                            session.SessionId, session.State
                        );
                        sessions.push(session.SessionId);
                    }
                }

                // Free the memory allocated by WTS
                WTSFreeMemory(session_info as *mut _);
            }

            Ok(sessions)
        }

        /// Launch the tray application in a specific user session
        fn launch_in_session(&self, session_id: u32) -> anyhow::Result<TrayProcess> {
            unsafe {
                // Get the user token for this session
                let mut user_token = HANDLE::default();
                WTSQueryUserToken(session_id, &mut user_token).map_err(|e| {
                    anyhow::anyhow!("WTSQueryUserToken failed: {:?}", e)
                })?;

                // Duplicate the token to get a primary token we can use
                let mut primary_token = HANDLE::default();
                let dup_result = DuplicateTokenEx(
                    user_token,
                    TOKEN_ALL_ACCESS,
                    None,
                    SecurityIdentification,
                    TokenPrimary,
                    &mut primary_token,
                );

                // Close the original token
                let _ = CloseHandle(user_token);

                dup_result.map_err(|e| {
                    anyhow::anyhow!("DuplicateTokenEx failed: {:?}", e)
                })?;

                // Build the command line
                let exe_path = self.tray_exe_path.to_string_lossy();
                let mut command_line: Vec<u16> = format!("\"{}\"", exe_path)
                    .encode_utf16()
                    .chain(std::iter::once(0))
                    .collect();

                // Set up startup info
                let mut startup_info = STARTUPINFOW::default();
                startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
                // Use the user's desktop
                let mut desktop: Vec<u16> = "winsta0\\default"
                    .encode_utf16()
                    .chain(std::iter::once(0))
                    .collect();
                startup_info.lpDesktop = windows::core::PWSTR(desktop.as_mut_ptr());

                let mut process_info = PROCESS_INFORMATION::default();

                // Create the process in the user's session
                let create_result = CreateProcessAsUserW(
                    primary_token,
                    None,
                    windows::core::PWSTR(command_line.as_mut_ptr()),
                    None,
                    None,
                    false,
                    PROCESS_CREATION_FLAGS(0),
                    None,
                    None,
                    &startup_info,
                    &mut process_info,
                );

                // Close the duplicated token
                let _ = CloseHandle(primary_token);

                create_result.map_err(|e| {
                    anyhow::anyhow!("CreateProcessAsUserW failed: {:?}", e)
                })?;

                // Close the thread handle (we don't need it)
                let _ = CloseHandle(process_info.hThread);

                Ok(TrayProcess {
                    session_id,
                    process_handle: process_info.hProcess,
                    process_id: process_info.dwProcessId,
                })
            }
        }
    }

    impl Drop for TrayLauncher {
        fn drop(&mut self) {
            // Clean up process handles
            if let Ok(mut running) = self.running.try_write() {
                for (_, proc) in running.drain() {
                    unsafe {
                        let _ = CloseHandle(proc.process_handle);
                    }
                }
            }
        }
    }

    /// Check if a process is still running
    fn is_process_running(handle: HANDLE) -> bool {
        unsafe {
            let mut exit_code: u32 = 0;
            if GetExitCodeProcess(handle, &mut exit_code).is_ok() {
                exit_code == STATUS_PENDING
            } else {
                false
            }
        }
    }

    /// Terminate a process
    fn terminate_process(handle: HANDLE) {
        unsafe {
            let _ = TerminateProcess(handle, 0);
        }
    }
}

#[cfg(unix)]
pub mod unix {
    use std::path::PathBuf;
    use tracing::info;

    use crate::ipc::TrayPolicy;

    /// Placeholder for Unix tray launcher
    pub struct TrayLauncher {
        #[allow(dead_code)]
        tray_exe_path: PathBuf,
    }

    impl TrayLauncher {
        pub fn new(tray_exe_path: PathBuf) -> Self {
            Self { tray_exe_path }
        }

        pub async fn update_policy(&self, _policy: TrayPolicy) {
            // TODO: Implement for Unix (XDG autostart or user systemd service)
            info!("Unix tray launcher not yet implemented");
        }

        pub async fn ensure_running(&self) {
            info!("Unix tray launcher not yet implemented");
        }

        pub async fn terminate_all(&self) {
            info!("Unix tray launcher not yet implemented");
        }

        pub async fn on_session_change(&self, _session_id: u32, _is_logon: bool) {
            // No-op on Unix for now
        }
    }
}

#[cfg(windows)]
pub use windows::TrayLauncher;

#[cfg(unix)]
pub use unix::TrayLauncher;

-    use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID};

+    use windows::Win32::Foundation::{CloseHandle, HANDLE};

-        AdjustTokenPrivileges, DuplicateTokenEx, LookupPrivilegeValueW, SecurityIdentification,

-        LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED, TOKEN_ACCESS_MASK, TOKEN_ADJUST_PRIVILEGES,

-        TOKEN_ALL_ACCESS, TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_PRIVILEGES, TOKEN_QUERY,

+        DuplicateTokenEx, SecurityIdentification, TOKEN_ALL_ACCESS, TokenPrimary,

-        CreateProcessAsUserW, PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, STARTUPINFOW,

+        CreateProcessAsUserW, GetExitCodeProcess, TerminateProcess, PROCESS_CREATION_FLAGS,

+        PROCESS_INFORMATION, STARTUPINFOW,

+    /// Exit code indicating process is still running

+    const STATUS_PENDING: u32 = 259; // Same as STILL_ACTIVE

-                    unsafe { let _ = CloseHandle(proc.process_handle); }

+                        let _ = CloseHandle(proc.process_handle);

-                info!("Terminating tray in session {} (PID {})", session_id, proc.process_id);

+                    "Terminating tray in session {} (PID {})",

+                    session_id, proc.process_id

-                let result = WTSEnumerateSessionsW(

+                WTSEnumerateSessionsW(None, 0, 1, &mut session_info, &mut count).map_err(|e| {

+                    anyhow::anyhow!("WTSEnumerateSessionsW failed: {:?}", e)

-                    return Err(anyhow::anyhow!(

-                        "WTSEnumerateSessionsW failed: {:?}",

-                        windows::core::Error::from_win32()

-                if !WTSQueryUserToken(session_id, &mut user_token).as_bool() {

-                        "WTSQueryUserToken failed: {:?}",

+                WTSQueryUserToken(session_id, &mut user_token).map_err(|e| {

+                    anyhow::anyhow!("WTSQueryUserToken failed: {:?}", e)

-                let result = DuplicateTokenEx(

+                let dup_result = DuplicateTokenEx(

-                        "DuplicateTokenEx failed: {:?}",

+                dup_result.map_err(|e| {

+                    anyhow::anyhow!("DuplicateTokenEx failed: {:?}", e)

-                let command_line: Vec<u16> = format!("\"{}\"", exe_path)

+                let mut command_line: Vec<u16> = format!("\"{}\"", exe_path)

-                let desktop: Vec<u16> = "winsta0\\default"

+                let mut desktop: Vec<u16> = "winsta0\\default"

-                startup_info.lpDesktop = windows::core::PWSTR(desktop.as_ptr() as *mut u16);

+                startup_info.lpDesktop = windows::core::PWSTR(desktop.as_mut_ptr());

-                let result = CreateProcessAsUserW(

+                let create_result = CreateProcessAsUserW(

-                    windows::core::PWSTR(command_line.as_ptr() as *mut u16),

+                    windows::core::PWSTR(command_line.as_mut_ptr()),

-                    return Err(anyhow::anyhow!(

-                        "CreateProcessAsUserW failed: {:?}",

-                        windows::core::Error::from_win32()

+                create_result.map_err(|e| {

+                    anyhow::anyhow!("CreateProcessAsUserW failed: {:?}", e)

-        use windows::Win32::System::Threading::{GetExitCodeProcess, STILL_ACTIVE};

-            if GetExitCodeProcess(handle, &mut exit_code).as_bool() {

-                exit_code == STILL_ACTIVE.0 as u32

+            if GetExitCodeProcess(handle, &mut exit_code).is_ok() {

+                exit_code == STATUS_PENDING

-        use windows::Win32::System::Threading::TerminateProcess;

//! Tray Launcher - Launches the tray application in user sessions
//!
//! This module handles launching the GuruRMM tray application in each
//! logged-in user's session. The agent service runs as SYSTEM, so we need
//! to use CreateProcessAsUser to launch the tray in the user's context.

#[cfg(windows)]
pub mod windows {
    use std::collections::HashMap;
    use std::path::PathBuf;
    use std::sync::Arc;
    use tokio::sync::RwLock;
    use tracing::{debug, error, info, warn};

    use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID};
    use windows::Win32::Security::{
        AdjustTokenPrivileges, DuplicateTokenEx, LookupPrivilegeValueW, SecurityIdentification,
        LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED, TOKEN_ACCESS_MASK, TOKEN_ADJUST_PRIVILEGES,
        TOKEN_ALL_ACCESS, TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_PRIVILEGES, TOKEN_QUERY,
        TokenPrimary,
    };
    use windows::Win32::System::RemoteDesktop::{
        WTSActive, WTSEnumerateSessionsW, WTSFreeMemory, WTSQueryUserToken, WTS_SESSION_INFOW,
    };
    use windows::Win32::System::Threading::{
        CreateProcessAsUserW, PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, STARTUPINFOW,
    };
    use windows::core::PCWSTR;

    use crate::ipc::TrayPolicy;

    /// Information about a running tray process
    #[derive(Debug)]
    struct TrayProcess {
        session_id: u32,
        process_handle: HANDLE,
        process_id: u32,
    }

    /// Manages tray application instances across user sessions
    pub struct TrayLauncher {
        /// Path to the tray executable
        tray_exe_path: PathBuf,

        /// Currently running tray processes (session_id -> process info)
        running: RwLock<HashMap<u32, TrayProcess>>,

        /// Current policy
        policy: RwLock<TrayPolicy>,
    }

    impl TrayLauncher {
        /// Create a new tray launcher
        pub fn new(tray_exe_path: PathBuf) -> Self {
            Self {
                tray_exe_path,
                running: RwLock::new(HashMap::new()),
                policy: RwLock::new(TrayPolicy::default_permissive()),
            }
        }

        /// Update the tray policy
        pub async fn update_policy(&self, policy: TrayPolicy) {
            let was_enabled = self.policy.read().await.enabled;
            *self.policy.write().await = policy.clone();

            if policy.enabled && !was_enabled {
                // Policy just enabled - launch in all sessions
                info!("Tray policy enabled, launching in user sessions");
                self.ensure_running().await;
            } else if !policy.enabled && was_enabled {
                // Policy just disabled - terminate all
                info!("Tray policy disabled, terminating tray processes");
                self.terminate_all().await;
            }
        }

        /// Ensure tray is running in all active user sessions
        pub async fn ensure_running(&self) {
            let policy = self.policy.read().await;
            if !policy.enabled {
                debug!("Tray policy disabled, skipping launch");
                return;
            }
            drop(policy);

            if !self.tray_exe_path.exists() {
                warn!("Tray executable not found: {:?}", self.tray_exe_path);
                return;
            }

            // Get active sessions
            let sessions = match self.enumerate_sessions() {
                Ok(s) => s,
                Err(e) => {
                    error!("Failed to enumerate sessions: {}", e);
                    return;
                }
            };

            let mut running = self.running.write().await;

            // Clean up dead processes
            running.retain(|session_id, proc| {
                if !is_process_running(proc.process_handle) {
                    debug!("Tray process in session {} has exited", session_id);
                    unsafe { let _ = CloseHandle(proc.process_handle); }
                    false
                } else {
                    true
                }
            });

            // Launch in sessions that don't have tray running
            for session_id in sessions {
                if running.contains_key(&session_id) {
                    continue;
                }

                match self.launch_in_session(session_id) {
                    Ok(proc) => {
                        info!(
                            "Launched tray in session {} (PID {})",
                            session_id, proc.process_id
                        );
                        running.insert(session_id, proc);
                    }
                    Err(e) => {
                        warn!("Failed to launch tray in session {}: {}", session_id, e);
                    }
                }
            }
        }

        /// Terminate all running tray processes
        pub async fn terminate_all(&self) {
            let mut running = self.running.write().await;

            for (session_id, proc) in running.drain() {
                info!("Terminating tray in session {} (PID {})", session_id, proc.process_id);
                terminate_process(proc.process_handle);
                unsafe { let _ = CloseHandle(proc.process_handle); }
            }
        }

        /// Handle a session change event
        pub async fn on_session_change(&self, session_id: u32, is_logon: bool) {
            if is_logon {
                info!("User logged on to session {}", session_id);
                // Small delay to let the session fully initialize
                tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
                self.ensure_running().await;
            } else {
                info!("User logged off from session {}", session_id);
                // Clean up the process entry (process likely already dead)
                let mut running = self.running.write().await;
                if let Some(proc) = running.remove(&session_id) {
                    unsafe { let _ = CloseHandle(proc.process_handle); }
                }
            }
        }

        /// Enumerate active user sessions
        fn enumerate_sessions(&self) -> anyhow::Result<Vec<u32>> {
            let mut sessions = Vec::new();

            unsafe {
                let mut session_info: *mut WTS_SESSION_INFOW = std::ptr::null_mut();
                let mut count: u32 = 0;

                // Enumerate sessions on the local server (None = local)
                let result = WTSEnumerateSessionsW(
                    None,
                    0,
                    1,
                    &mut session_info,
                    &mut count,
                );

                if !result.as_bool() {
                    return Err(anyhow::anyhow!(
                        "WTSEnumerateSessionsW failed: {:?}",
                        windows::core::Error::from_win32()
                    ));
                }

                // Iterate through sessions
                let session_slice = std::slice::from_raw_parts(session_info, count as usize);
                for session in session_slice {
                    // Only consider active sessions (user logged in and at desktop)
                    if session.State == WTSActive && session.SessionId != 0 {
                        debug!(
                            "Found active session: ID={}, State={:?}",
                            session.SessionId, session.State
                        );
                        sessions.push(session.SessionId);
                    }
                }

                // Free the memory allocated by WTS
                WTSFreeMemory(session_info as *mut _);
            }

            Ok(sessions)
        }

        /// Launch the tray application in a specific user session
        fn launch_in_session(&self, session_id: u32) -> anyhow::Result<TrayProcess> {
            unsafe {
                // Get the user token for this session
                let mut user_token = HANDLE::default();
                if !WTSQueryUserToken(session_id, &mut user_token).as_bool() {
                    return Err(anyhow::anyhow!(
                        "WTSQueryUserToken failed: {:?}",
                        windows::core::Error::from_win32()
                    ));
                }

                // Duplicate the token to get a primary token we can use
                let mut primary_token = HANDLE::default();
                let result = DuplicateTokenEx(
                    user_token,
                    TOKEN_ALL_ACCESS,
                    None,
                    SecurityIdentification,
                    TokenPrimary,
                    &mut primary_token,
                );

                // Close the original token
                let _ = CloseHandle(user_token);

                if !result.as_bool() {
                    return Err(anyhow::anyhow!(
                        "DuplicateTokenEx failed: {:?}",
                        windows::core::Error::from_win32()
                    ));
                }

                // Build the command line
                let exe_path = self.tray_exe_path.to_string_lossy();
                let command_line: Vec<u16> = format!("\"{}\"", exe_path)
                    .encode_utf16()
                    .chain(std::iter::once(0))
                    .collect();

                // Set up startup info
                let mut startup_info = STARTUPINFOW::default();
                startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
                // Use the user's desktop
                let desktop: Vec<u16> = "winsta0\\default"
                    .encode_utf16()
                    .chain(std::iter::once(0))
                    .collect();
                startup_info.lpDesktop = windows::core::PWSTR(desktop.as_ptr() as *mut u16);

                let mut process_info = PROCESS_INFORMATION::default();

                // Create the process in the user's session
                let result = CreateProcessAsUserW(
                    primary_token,
                    None,
                    windows::core::PWSTR(command_line.as_ptr() as *mut u16),
                    None,
                    None,
                    false,
                    PROCESS_CREATION_FLAGS(0),
                    None,
                    None,
                    &startup_info,
                    &mut process_info,
                );

                // Close the duplicated token
                let _ = CloseHandle(primary_token);

                if !result.as_bool() {
                    return Err(anyhow::anyhow!(
                        "CreateProcessAsUserW failed: {:?}",
                        windows::core::Error::from_win32()
                    ));
                }

                // Close the thread handle (we don't need it)
                let _ = CloseHandle(process_info.hThread);

                Ok(TrayProcess {
                    session_id,
                    process_handle: process_info.hProcess,
                    process_id: process_info.dwProcessId,
                })
            }
        }
    }

    impl Drop for TrayLauncher {
        fn drop(&mut self) {
            // Clean up process handles
            if let Ok(mut running) = self.running.try_write() {
                for (_, proc) in running.drain() {
                    unsafe { let _ = CloseHandle(proc.process_handle); }
                }
            }
        }
    }

    /// Check if a process is still running
    fn is_process_running(handle: HANDLE) -> bool {
        use windows::Win32::System::Threading::{GetExitCodeProcess, STILL_ACTIVE};

        unsafe {
            let mut exit_code: u32 = 0;
            if GetExitCodeProcess(handle, &mut exit_code).as_bool() {
                exit_code == STILL_ACTIVE.0 as u32
            } else {
                false
            }
        }
    }

    /// Terminate a process
    fn terminate_process(handle: HANDLE) {
        use windows::Win32::System::Threading::TerminateProcess;

        unsafe {
            let _ = TerminateProcess(handle, 0);
        }
    }
}

#[cfg(unix)]
pub mod unix {
    use std::path::PathBuf;
    use tracing::info;

    use crate::ipc::TrayPolicy;

    /// Placeholder for Unix tray launcher
    pub struct TrayLauncher {
        tray_exe_path: PathBuf,
    }

    impl TrayLauncher {
        pub fn new(tray_exe_path: PathBuf) -> Self {
            Self { tray_exe_path }
        }

        pub async fn update_policy(&self, _policy: TrayPolicy) {
            // TODO: Implement for Unix (XDG autostart or user systemd service)
            info!("Unix tray launcher not yet implemented");
        }

        pub async fn ensure_running(&self) {
            info!("Unix tray launcher not yet implemented");
        }

        pub async fn terminate_all(&self) {
            info!("Unix tray launcher not yet implemented");
        }

        pub async fn on_session_change(&self, _session_id: u32, _is_logon: bool) {
            // No-op on Unix for now
        }
    }
}

#[cfg(windows)]
pub use windows::TrayLauncher;

#[cfg(unix)]
pub use unix::TrayLauncher;


Good, I rewrote the tray_launcher.rs with proper Result handling. Let me try building again.


Now let me try building again:


Bash

cd ~/claude-projects/gururmm/agent && cargo build --release 2>&1

Build agent with fixed tray launcher


Error: Exit code 101
   Compiling gururmm-agent v0.4.0 (C:\Users\MikeSwanson\claude-projects\gururmm\agent)
warning: unused import: `std::ffi::OsStr`
   --> src\ipc.rs:290:9
    |
290 |     use std::ffi::OsStr;
    |         ^^^^^^^^^^^^^^^
    |
    = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default

warning: unused import: `std::os::windows::ffi::OsStrExt`
   --> src\ipc.rs:291:9
    |
291 |     use std::os::windows::ffi::OsStrExt;
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: unused import: `warn`
  --> src\updater\mod.rs:16:35
   |
16 | use tracing::{debug, error, info, warn};
   |                                   ^^^^

warning: unused import: `Context`
  --> src\main.rs:15:14
   |
15 | use anyhow::{Context, Result};
   |              ^^^^^^^

warning: variable does not need to be mutable
   --> src\main.rs:147:28
    |
147 |     let (force_checkin_tx, mut force_checkin_rx) = tokio::sync::mpsc::channel::<()>(8);
    |                            ----^^^^^^^^^^^^^^^^
    |                            |
    |                            help: remove this `mut`
    |
    = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default

error[E0277]: `*mut c_void` cannot be sent between threads safely
     --> src\service.rs:188:13
      |
  188 | /             tokio::spawn(async move {
  189 | |                 // Small delay to let everything initialize
  190 | |                 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
  191 | |                 launcher_init.ensure_running().await;
  192 | |             });
      | |______________^ `*mut c_void` cannot be sent between threads safely
      |
      = help: within `(u32, TrayProcess)`, the trait `std::marker::Send` is not implemented for `*mut c_void`
note: required because it appears within the type `HANDLE`
     --> C:\Users\MikeSwanson\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\windows-0.58.0\src\Windows\Win32\Foundation\mod.rs:10643:12
      |
10643 | pub struct HANDLE(pub *mut core::ffi::c_void);
      |            ^^^^^^
note: required because it appears within the type `TrayProcess`
     --> src\tray_launcher.rs:33:12
      |
   33 |     struct TrayProcess {
      |            ^^^^^^^^^^^
      = note: required because it appears within the type `(u32, TrayProcess)`
      = note: required for `hashbrown::raw::RawTable<(u32, TrayProcess)>` to implement `std::marker::Send`
note: required because it appears within the type `hashbrown::map::HashMap<u32, TrayProcess, RandomState>`
     --> /rust/deps\hashbrown-0.15.5\src\map.rs:185:12
note: required because it appears within the type `HashMap<u32, TrayProcess>`
     --> /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234\library\std\src\collections\hash\map.rs:246:12
      = note: required for `tokio::sync::RwLock<HashMap<u32, TrayProcess>>` to implement `Sync`
note: required because it appears within the type `TrayLauncher`
     --> src\tray_launcher.rs:40:16
      |
   40 |     pub struct TrayLauncher {
      |                ^^^^^^^^^^^^
      = note: required for `std::sync::Arc<TrayLauncher>` to implement `std::marker::Send`
note: required because it's used within this `async` block
     --> src\service.rs:188:26
      |
  188 |             tokio::spawn(async move {
      |                          ^^^^^^^^^^
note: required by a bound in `tokio::spawn`
     --> C:\Users\MikeSwanson\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\tokio-1.48.0\src\task\spawn.rs:171:21
      |
  169 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
      |            ----- required by a bound in this function
  170 |     where
  171 |         F: Future + Send + 'static,
      |                     ^^^^ required by this bound in `spawn`

error[E0277]: `*mut c_void` cannot be shared between threads safely
     --> src\service.rs:188:13
      |
  188 | /             tokio::spawn(async move {
  189 | |                 // Small delay to let everything initialize
  190 | |                 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
  191 | |                 launcher_init.ensure_running().await;
  192 | |             });
      | |______________^ `*mut c_void` cannot be shared between threads safely
      |
      = help: within `(u32, TrayProcess)`, the trait `Sync` is not implemented for `*mut c_void`
note: required because it appears within the type `HANDLE`
     --> C:\Users\MikeSwanson\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\windows-0.58.0\src\Windows\Win32\Foundation\mod.rs:10643:12
      |
10643 | pub struct HANDLE(pub *mut core::ffi::c_void);
      |            ^^^^^^
note: required because it appears within the type `TrayProcess`
     --> src\tray_launcher.rs:33:12
      |
   33 |     struct TrayProcess {
      |            ^^^^^^^^^^^
      = note: required because it appears within the type `(u32, TrayProcess)`
      = note: required for `hashbrown::raw::RawTable<(u32, TrayProcess)>` to implement `Sync`
note: requi

... [2013 characters truncated] ...

204 | |             launcher_init.ensure_running().await;
  205 | |         });
      | |__________^ `*mut c_void` cannot be sent between threads safely
      |
      = help: within `(u32, TrayProcess)`, the trait `std::marker::Send` is not implemented for `*mut c_void`
note: required because it appears within the type `HANDLE`
     --> C:\Users\MikeSwanson\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\windows-0.58.0\src\Windows\Win32\Foundation\mod.rs:10643:12
      |
10643 | pub struct HANDLE(pub *mut core::ffi::c_void);
      |            ^^^^^^
note: required because it appears within the type `TrayProcess`
     --> src\tray_launcher.rs:33:12
      |
   33 |     struct TrayProcess {
      |            ^^^^^^^^^^^
      = note: required because it appears within the type `(u32, TrayProcess)`
      = note: required for `hashbrown::raw::RawTable<(u32, TrayProcess)>` to implement `std::marker::Send`
note: required because it appears within the type `hashbrown::map::HashMap<u32, TrayProcess, RandomState>`
     --> /rust/deps\hashbrown-0.15.5\src\map.rs:185:12
note: required because it appears within the type `HashMap<u32, TrayProcess>`
     --> /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234\library\std\src\collections\hash\map.rs:246:12
      = note: required for `tokio::sync::RwLock<HashMap<u32, TrayProcess>>` to implement `Sync`
note: required because it appears within the type `TrayLauncher`
     --> src\tray_launcher.rs:40:16
      |
   40 |     pub struct TrayLauncher {
      |                ^^^^^^^^^^^^
      = note: required for `std::sync::Arc<TrayLauncher>` to implement `std::marker::Send`
note: required because it's used within this `async` block
     --> src\main.rs:201:22
      |
  201 |         tokio::spawn(async move {
      |                      ^^^^^^^^^^
note: required by a bound in `tokio::spawn`
     --> C:\Users\MikeSwanson\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\tokio-1.48.0\src\task\spawn.rs:171:21
      |
  169 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
      |            ----- required by a bound in this function
  170 |     where
  171 |         F: Future + Send + 'static,
      |                     ^^^^ required by this bound in `spawn`

error[E0277]: `*mut c_void` cannot be shared between threads safely
     --> src\main.rs:201:9
      |
  201 | /         tokio::spawn(async move {
  202 | |             // Small delay to let IPC server fully start
  203 | |             tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
  204 | |             launcher_init.ensure_running().await;
  205 | |         });
      | |__________^ `*mut c_void` cannot be shared between threads safely
      |
      = help: within `(u32, TrayProcess)`, the trait `Sync` is not implemented for `*mut c_void`
note: required because it appears within the type `HANDLE`
     --> C:\Users\MikeSwanson\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\windows-0.58.0\src\Windows\Win32\Foundation\mod.rs:10643:12
      |
10643 | pub struct HANDLE(pub *mut core::ffi::c_void);
      |            ^^^^^^
note: required because it appears within the type `TrayProcess`
     --> src\tray_launcher.rs:33:12
      |
   33 |     struct TrayProcess {
      |            ^^^^^^^^^^^
      = note: required because it appears within the type `(u32, TrayProcess)`
      = note: required for `hashbrown::raw::RawTable<(u32, TrayProcess)>` to implement `Sync`
note: required because it appears within the type `hashbrown::map::HashMap<u32, TrayProcess, RandomState>`
     --> /rust/deps\hashbrown-0.15.5\src\map.rs:185:12
note: required because it appears within the type `HashMap<u32, TrayProcess>`
     --> /rustc/ded5c06cf21d2b93bffd5d884aa6e96934ee4234\library\std\src\collections\hash\map.rs:246:12
      = note: required for `tokio::sync::RwLock<HashMap<u32, TrayProcess>>` to implement `Sync`
note: required because it appears within the type `TrayLauncher`
     --> src\tray_launcher.rs:40:16
      |
   40 |     pub struct TrayLauncher {
      |                ^^^^^^^^^^^^
      = note: required for `std::sync::Arc<TrayLauncher>` to implement `std::marker::Send`
note: required because it's used within this `async` block
     --> src\main.rs:201:22
      |
  201 |         tokio::spawn(async move {
      |                      ^^^^^^^^^^
note: required by a bound in `tokio::spawn`
     --> C:\Users\MikeSwanson\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\tokio-1.48.0\src\task\spawn.rs:171:21
      |
  169 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
      |            ----- required by a bound in this function
  170 |     where
  171 |         F: Future + Send + 'static,
      |                     ^^^^ required by this bound in `spawn`

For more information about this error, try `rustc --explain E0277`.
warning: `gururmm-agent` (bin "gururmm-agent") generated 6 warnings
error: could not compile `gururmm-agent` (bin "gururmm-agent") due to 4 previous errors; 6 warnings emitted


The problem is that HANDLE is not Send/Sync because it's a raw pointer. I need to use a wrapper type that implements Send + Sync, or store just the process ID instead of the handle, and only open handles when needed.

The simplest fix is to store process IDs instead of handles, and use a simple wrapper for HANDLEs that uses `unsafe impl Send + Sync`. Let me refactor to store just the process IDs and manage handles differently.


The issue is that Windows HANDLE is not Send/Sync. I need to store process IDs instead and only open handles when needed:


Write

//! Tray Launcher - Launches the tray application in user sessions
//!
//! This module handles launching the GuruRMM tray application in each
//! logged-in user's session. The agent service runs as SYSTEM, so we need
//! to use CreateProcessAsUser to launch the tray in the user's context.

#[cfg(windows)]
pub mod windows {
    use std::collections::HashMap;
    use std::path::PathBuf;
    use std::sync::Mutex;
    use tracing::{debug, error, info, warn};

    use windows::Win32::Foundation::{CloseHandle, HANDLE};
    use windows::Win32::Security::{
        DuplicateTokenEx, SecurityIdentification, TOKEN_ALL_ACCESS, TokenPrimary,
    };
    use windows::Win32::System::RemoteDesktop::{
        WTSActive, WTSEnumerateSessionsW, WTSFreeMemory, WTSQueryUserToken, WTS_SESSION_INFOW,
    };
    use windows::Win32::System::Threading::{
        CreateProcessAsUserW, GetExitCodeProcess, OpenProcess, TerminateProcess,
        PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, PROCESS_QUERY_INFORMATION,
        PROCESS_TERMINATE, STARTUPINFOW,
    };

    use crate::ipc::TrayPolicy;

    /// Exit code indicating process is still running
    const STATUS_PENDING: u32 = 259;

    /// Information about a running tray process (stores only IDs, not handles)
    #[derive(Debug, Clone)]
    struct TrayProcessInfo {
        session_id: u32,
        process_id: u32,
    }

    /// Manages tray application instances across user sessions
    pub struct TrayLauncher {
        /// Path to the tray executable
        tray_exe_path: PathBuf,

        /// Currently running tray processes (session_id -> process info)
        /// Using std::sync::Mutex since we only need sync access, not async
        running: Mutex<HashMap<u32, TrayProcessInfo>>,

        /// Current policy
        policy: Mutex<TrayPolicy>,
    }

    // SAFETY: TrayLauncher only stores primitive types (u32) and PathBuf
    // The Mutex provides thread-safe access
    unsafe impl Send for TrayLauncher {}
    unsafe impl Sync for TrayLauncher {}

    impl TrayLauncher {
        /// Create a new tray launcher
        pub fn new(tray_exe_path: PathBuf) -> Self {
            Self {
                tray_exe_path,
                running: Mutex::new(HashMap::new()),
                policy: Mutex::new(TrayPolicy::default_permissive()),
            }
        }

        /// Update the tray policy
        pub async fn update_policy(&self, policy: TrayPolicy) {
            let was_enabled = {
                let p = self.policy.lock().unwrap();
                p.enabled
            };

            {
                let mut p = self.policy.lock().unwrap();
                *p = policy.clone();
            }

            if policy.enabled && !was_enabled {
                info!("Tray policy enabled, launching in user sessions");
                self.ensure_running().await;
            } else if !policy.enabled && was_enabled {
                info!("Tray policy disabled, terminating tray processes");
                self.terminate_all().await;
            }
        }

        /// Ensure tray is running in all active user sessions
        pub async fn ensure_running(&self) {
            let enabled = {
                let policy = self.policy.lock().unwrap();
                policy.enabled
            };

            if !enabled {
                debug!("Tray policy disabled, skipping launch");
                return;
            }

            if !self.tray_exe_path.exists() {
                debug!("Tray executable not found: {:?}", self.tray_exe_path);
                return;
            }

            // Get active sessions
            let sessions = match enumerate_sessions() {
                Ok(s) => s,
                Err(e) => {
                    error!("Failed to enumerate sessions: {}", e);
                    return;
                }
            };

            let mut running = self.running.lock().unwrap();

            // Clean up dead processes
            running.retain(|session_id, proc| {
                if !is_process_running(proc.process_id) {
                    debug!("Tray process in session {} has exited", session_id);
                    false
                } else {
                    true
                }
            });

            // Launch in sessions that don't have tray running
            for session_id in sessions {
                if running.contains_key(&session_id) {
                    continue;
                }

                match launch_in_session(&self.tray_exe_path, session_id) {
                    Ok(process_id) => {
                        info!(
                            "Launched tray in session {} (PID {})",
                            session_id, process_id
                        );
                        running.insert(
                            session_id,
                            TrayProcessInfo {
                                session_id,
                                process_id,
                            },
                        );
                    }
                    Err(e) => {
                        warn!("Failed to launch tray in session {}: {}", session_id, e);
                    }
                }
            }
        }

        /// Terminate all running tray processes
        pub async fn terminate_all(&self) {
            let mut running = self.running.lock().unwrap();

            for (session_id, proc) in running.drain() {
                info!(
                    "Terminating tray in session {} (PID {})",
                    session_id, proc.process_id
                );
                terminate_process(proc.process_id);
            }
        }

        /// Handle a session change event
        pub async fn on_session_change(&self, session_id: u32, is_logon: bool) {
            if is_logon {
                info!("User logged on to session {}", session_id);
                tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
                self.ensure_running().await;
            } else {
                info!("User logged off from session {}", session_id);
                let mut running = self.running.lock().unwrap();
                running.remove(&session_id);
            }
        }
    }

    /// Enumerate active user sessions
    fn enumerate_sessions() -> anyhow::Result<Vec<u32>> {
        let mut sessions = Vec::new();

        unsafe {
            let mut session_info: *mut WTS_SESSION_INFOW = std::ptr::null_mut();
            let mut count: u32 = 0;

            WTSEnumerateSessionsW(None, 0, 1, &mut session_info, &mut count)
                .map_err(|e| anyhow::anyhow!("WTSEnumerateSessionsW failed: {:?}", e))?;

            let session_slice = std::slice::from_raw_parts(session_info, count as usize);
            for session in session_slice {
                if session.State == WTSActive && session.SessionId != 0 {
                    debug!(
                        "Found active session: ID={}, State={:?}",
                        session.SessionId, session.State
                    );
                    sessions.push(session.SessionId);
                }
            }

            WTSFreeMemory(session_info as *mut _);
        }

        Ok(sessions)
    }

    /// Launch the tray application in a specific user session
    fn launch_in_session(tray_exe_path: &PathBuf, session_id: u32) -> anyhow::Result<u32> {
        unsafe {
            // Get the user token for this session
            let mut user_token = HANDLE::default();
            WTSQueryUserToken(session_id, &mut user_token)
                .map_err(|e| anyhow::anyhow!("WTSQueryUserToken failed: {:?}", e))?;

            // Duplicate the token to get a primary token
            let mut primary_token = HANDLE::default();
            let dup_result = DuplicateTokenEx(
                user_token,
                TOKEN_ALL_ACCESS,
                None,
                SecurityIdentification,
                TokenPrimary,
                &mut primary_token,
            );

            let _ = CloseHandle(user_token);
            dup_result.map_err(|e| anyhow::anyhow!("DuplicateTokenEx failed: {:?}", e))?;

            // Build the command line
            let exe_path = tray_exe_path.to_string_lossy();
            let mut command_line: Vec<u16> = format!("\"{}\"", exe_path)
                .encode_utf16()
                .chain(std::iter::once(0))
                .collect();

            // Set up startup info
            let mut startup_info = STARTUPINFOW::default();
            startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;
            let mut desktop: Vec<u16> = "winsta0\\default"
                .encode_utf16()
                .chain(std::iter::once(0))
                .collect();
            startup_info.lpDesktop = windows::core::PWSTR(desktop.as_mut_ptr());

            let mut process_info = PROCESS_INFORMATION::default();

            let create_result = CreateProcessAsUserW(
                primary_token,
                None,
                windows::core::PWSTR(command_line.as_mut_ptr()),
                None,
                None,
                false,
                PROCESS_CREATION_FLAGS(0),
                None,
                None,
                &startup_info,
                &mut process_info,
            );

            let _ = CloseHandle(primary_token);
            create_result.map_err(|e| anyhow::anyhow!("CreateProcessAsUserW failed: {:?}", e))?;

            // Close handles immediately - we only need the process ID
            let _ = CloseHandle(process_info.hThread);
            let _ = CloseHandle(process_info.hProcess);

            Ok(process_info.dwProcessId)
        }
    }

    /// Check if a process is still running by process ID
    fn is_process_running(process_id: u32) -> bool {
        unsafe {
            let handle = match OpenProcess(PROCESS_QUERY_INFORMATION, false, process_id) {
                Ok(h) => h,
                Err(_) => return false,
            };

            let mut exit_code: u32 = 0;
            let result = if GetExitCodeProcess(handle, &mut exit_code).is_ok() {
                exit_code == STATUS_PENDING
            } else {
                false
            };

            let _ = CloseHandle(handle);
            result
        }
    }

    /// Terminate a process by process ID
    fn terminate_process(process_id: u32) {
        unsafe {
            if let Ok(handle) = OpenProcess(PROCESS_TERMINATE, false, process_id) {
                let _ = TerminateProcess(handle, 0);
                let _ = CloseHandle(handle);
            }
        }
    }
}

#[cfg(unix)]
pub mod unix {
    use std::path::PathBuf;
    use tracing::info;

    use crate::ipc::TrayPolicy;

    /// Placeholder for Unix tray launcher
    pub struct TrayLauncher {
        #[allow(dead_code)]
        tray_exe_path: PathBuf,
    }

    impl TrayLauncher {
        pub fn new(tray_exe_path: PathBuf) -> Self {
            Self { tray_exe_path }
        }

        pub async fn update_policy(&self, _policy: TrayPolicy) {
            info!("Unix tray launcher not yet implemented");
        }

        pub async fn ensure_running(&self) {
            // No-op on Unix for now
        }

        pub async fn terminate_all(&self) {
            // No-op on Unix for now
        }

        pub async fn on_session_change(&self, _session_id: u32, _is_logon: bool) {
            // No-op on Unix for now
        }
    }
}

#[cfg(windows)]
pub use windows::TrayLauncher;

#[cfg(unix)]
pub use unix::TrayLauncher;