You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Tauri跨平台桌面应用中用JavaScript追踪用户活跃与空闲时间

实现Tauri应用的用户活跃/空闲时间全面追踪

要完成全场景的用户活跃与空闲追踪,需要结合前端DOM交互监听和Tauri系统级API,覆盖应用前台、后台、最小化等所有状态,以下是具体实现方案:

核心场景拆分与对应策略

  • 前台活跃检测:通过监听用户鼠标、键盘等交互事件,实时标记活跃状态,无操作超过阈值则标记为空闲。
  • 后台/最小化状态:利用Tauri调用系统原生API获取系统级空闲时间,同时监听窗口状态切换,自动切换检测逻辑。
  • 系统级边界场景:捕获锁屏、睡眠、应用切换等事件,确保状态判断准确。

具体代码实现

1. 前端:前台交互监听

在你的JS文件中添加基础交互监听,维护活跃状态并触发状态回调:

let isUserActive = true;
let idleTimer = null;
const IDLE_THRESHOLD = 60000; // 自定义空闲阈值,示例为1分钟

// 监听所有用户交互事件
const activeTriggers = ['mousemove', 'keydown', 'click', 'scroll', 'touchstart'];
activeTriggers.forEach(event => {
  window.addEventListener(event, handleUserActive);
});

// 用户活跃时重置计时器并更新状态
function handleUserActive() {
  if (!isUserActive) {
    isUserActive = true;
    syncStateToBackend(true);
  }
  resetIdleTimer();
}

// 启动空闲计时器
function resetIdleTimer() {
  if (idleTimer) clearTimeout(idleTimer);
  idleTimer = setTimeout(() => {
    isUserActive = false;
    syncStateToBackend(false);
  }, IDLE_THRESHOLD);
}

// 将状态同步到Tauri后端
function syncStateToBackend(isActive) {
  window.__TAURI__.invoke('track_state', {
    is_active: isActive,
    timestamp: Date.now()
  });
}

2. Tauri后端:系统空闲检测与窗口监听

在src-tauri/src/main.rs中实现跨平台系统空闲时间获取,并监听窗口状态切换:

use tauri::{Manager, WindowEvent};
use std::time::Duration;

// 跨平台获取系统空闲时间(毫秒)
#[tauri::command]
fn get_system_idle() -> Result<u64, String> {
    #[cfg(target_os = "windows")]
    {
        use winapi::um::winuser::{GetLastInputInfo, LASTINPUTINFO};
        let mut input_info = LASTINPUTINFO {
            cbSize: std::mem::size_of::<LASTINPUTINFO>() as u32,
            dwTime: 0,
        };
        unsafe {
            if GetLastInputInfo(&mut input_info) != 0 {
                let tick = winapi::um::winbase::GetTickCount();
                Ok((tick - input_info.dwTime) as u64)
            } else {
                Err("Failed to fetch system idle time".into())
            }
        }
    }

    #[cfg(target_os = "macos")]
    {
        use core_foundation::base::TCFType;
        use core_foundation::date::CFDate;
        use core_graphics::event::CGEventSource;
        let source = CGEventSource::new(CGEventSource::STATE_SYSTEM).unwrap();
        let idle = source.get_last_event_time();
        let now = CFDate::now().abs_time();
        Ok((now - idle) as u64)
    }

    #[cfg(target_os = "linux")]
    {
        use libxss::XScreenSaverInfo;
        use x11_dl::xlib;
        let xlib = xlib::Xlib::open().unwrap();
        let display = unsafe { (xlib.XOpenDisplay)(std::ptr::null()) };
        let mut info = XScreenSaverInfo::new();
        unsafe {
            (xlib.XScreenSaverQueryInfo)(display, (xlib.XDefaultRootWindow)(display), &mut info);
        }
        Ok(info.idle as u64)
    }
}

// 接收前端状态并存储/处理
#[tauri::command]
fn track_state(is_active: bool, timestamp: u64) {
    // 这里可以将状态写入本地文件、数据库或进行实时统计
    println!("User state: {} | Timestamp: {}", is_active, timestamp);
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![get_system_idle, track_state])
        .setup(|app| {
            let main_window = app.get_window("main").unwrap();
            let window_clone = main_window.clone();

            // 监听窗口焦点/状态变化
            main_window.on_window_event(move |event| {
                match event {
                    WindowEvent::Focused(is_focused) => {
                        if !is_focused {
                            // 窗口失焦/最小化,启动系统空闲检测线程
                            std::thread::spawn(move || {
                                loop {
                                    match get_system_idle() {
                                        Ok(idle_ms) => {
                                            let is_active = idle_ms < 60000; // 同前端阈值
                                            window_clone.invoke("syncStateToBackend", &[is_active]).ok();
                                        }
                                        Err(e) => eprintln!("Idle check error: {}", e),
                                    }
                                    std::thread::sleep(Duration::from_secs(1));
                                }
                            });
                        } else {
                            // 窗口恢复焦点,重置前端计时器
                            window_clone.invoke("resetIdleTimer", &[]).ok();
                        }
                    }
                    _ => {}
                }
            });

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("Failed to run Tauri app");
}

3. 数据统计与整合

在前端维护状态历史,计算总活跃/空闲时长:

const stateHistory = [];

function syncStateToBackend(isActive) {
    const timestamp = Date.now();
    stateHistory.push({ isActive, timestamp });
    window.__TAURI__.invoke('track_state', { is_active: isActive, timestamp });
    calculateTotalDuration();
}

function calculateTotalDuration() {
    let totalActive = 0;
    let totalIdle = 0;

    for (let i = 1; i < stateHistory.length; i++) {
        const prev = stateHistory[i-1];
        const curr = stateHistory[i];
        const duration = curr.timestamp - prev.timestamp;

        if (prev.isActive) {
            totalActive += duration;
        } else {
            totalIdle += duration;
        }
    }

    console.log(`总活跃时长:${Math.floor(totalActive / 1000)}秒`);
    console.log(`总空闲时长:${Math.floor(totalIdle / 1000)}秒`);
}

关键注意事项

  • 跨平台适配:不同系统的空闲时间API差异较大,上述代码已覆盖Windows/macOS/Linux,需确保对应依赖已添加到Cargo.toml(比如Windows的winapi,macOS的core-foundation/core-graphics,Linux的libxss/x11-dl)。
  • 性能优化:后台检测线程每隔1秒执行一次即可,避免过高频率占用系统资源。
  • 隐私合规:若收集用户行为数据,需在应用中明确告知用户并获得同意,符合GDPR、CCPA等法规要求。
  • 边界处理:额外监听系统锁屏、睡眠事件(可通过Tauri的系统相关插件实现),确保状态判断无遗漏。

内容的提问来源于stack exchange,提问作者vasanth kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 05:12:15