Tauri 实现快捷呼出窗口到当前光标所在屏幕

436 阅读3分钟

实现类似 macOS 中聚焦、uTools 类似的工具通过快捷键唤起窗口到鼠标所在位置,或者鼠标所在屏幕中。

通常可以使用 Wrt / Tao 中内部获取鼠标位置,但相关代码非开放使用需要自行实现。

实现获取鼠标位置

#[cfg(target_os = "macos")]
pub fn get_mouse_coordinates() -> (i32, i32) {
    use core_graphics::event::CGEvent;
    use core_graphics::event_source::{CGEventSource, CGEventSourceRef, CGEventSourceStateID};

    let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState)
        .expect("failed to create event source");
    let event = CGEvent::new(source).expect("failed to create event");

    (event.location().x as i32, event.location().y as i32)
}

#[cfg(target_os = "windows")]
pub fn get_mouse_coordinates() -> (i32, i32) {
    use std::mem;
    use winapi::shared::windef::POINT;
    use winapi::um::winuser::GetCursorPos;

    unsafe {
        let mut point: POINT = mem::zeroed();
        GetCursorPos(&mut point);

        (point.x, point.y)
    }
}

#[cfg(target_os = "linux")]
pub fn get_mouse_coordinates() -> (i32, i32) {
    use std::os::raw::{c_int, c_uint};
    use std::os::unix::ffi::OsStringExt;
    use std::ptr;

    extern "C" {
        fn XOpenDisplay(display_name: *const u8) -> *mut std::ffi::c_void;
        fn XDefaultRootWindow(display: *mut std::ffi::c_void) -> c_uint;
        fn XQueryPointer(
            display: *mut std::ffi::c_void,
            window: c_uint,
            root_return: *mut c_uint,
            child_return: *mut c_uint,
            root_x_return: *mut c_int,
            root_y_return: *mut c_int,
            win_x_return: *mut c_int,
            win_y_return: *mut c_int,
            mask_return: *mut c_uint,
        ) -> c_int;
        fn XCloseDisplay(display: *mut std::ffi::c_void) -> c_int;
    }

    unsafe {
        let display_name = std::ffi::CString::new(ptr::null())?;
        let display = XOpenDisplay(display_name.as_ptr());

        let root_window = XDefaultRootWindow(display);
        let mut root_x = 0;
        let mut root_y = 0;
        let mut win_x = 0;
        let mut win_y = 0;
        let mut mask = 0;

        XQueryPointer(
            display,
            root_window,
            ptr::null_mut(),
            ptr::null_mut(),
            &mut root_x,
            &mut root_y,
            &mut win_x,
            &mut win_y,
            &mut mask,
        );

        XCloseDisplay(display);

        (root_x, root_y)
    }
}

窗口管理

注意存在物理坐标和逻辑坐标的区别,我们需要将所有获取到的物理坐标配合 DPI 转换为逻辑坐标,否则你在不同 DPI 窗口之间移动会导致坐标不正确。

// 按快捷键的时候将窗口移动到当前鼠标位置,方便用户操作
pub fn show_window_to_current_position(window: &Window<Wry>) -> bool {
    let is_visible = window.is_visible().unwrap();
    if is_visible {
        window.hide().unwrap();
        return false;
    } else {
        window.show().unwrap();
    }

    // 系统 api 调度获取到是逻辑坐标
    let mouse_position = get_mouse_coordinates();
    let mouse_position_x = mouse_position.0 as f64;
    let mouse_position_y = mouse_position.1 as f64;

    let window_dpi = window.scale_factor().unwrap();
    // 获取当前窗口大小,需要还原 dpi 将坐标还原为逻辑坐标
    let window_size = window.inner_size().unwrap().to_logical::<f64>(window_dpi);
    let window_width = window_size.width;
    let window_height = window_size.height;

    let all_monitor = window.available_monitors().unwrap();

    let current_monitor = all_monitor
        .iter()
        .find(|monitor| {
            let monitor_dpi = monitor.scale_factor();
            let monitor_position = monitor.position().to_logical::<f64>(monitor_dpi);
            let monitor_position_x = monitor_position.x;
            let monitor_position_y = monitor_position.y;
            let monitor_size = monitor.size().to_logical::<f64>(monitor_dpi);
            let monitor_width = monitor_size.width;
            let monitor_height = monitor_size.height;

            // 判断鼠标位置是否在当前显示器内
            mouse_position_x >= monitor_position_x
                && mouse_position_x <= monitor_position_x + monitor_width
                && mouse_position_y >= monitor_position_y
                && mouse_position_y <= monitor_position_y + monitor_height
        })
        .unwrap();

    let current_monitor_dpi = current_monitor.scale_factor();

    // 设置窗口位置到当前鼠标位置
    let current_monitor_position = current_monitor
        .position()
        .to_logical::<f64>(current_monitor_dpi);
    let current_monitor_position_x = current_monitor_position.x;
    let current_monitor_position_y = current_monitor_position.y;
    let current_monitor_size = current_monitor
        .size()
        .to_logical::<f64>(current_monitor_dpi);
    let current_monitor_width = current_monitor_size.width;
    let current_monitor_height = current_monitor_size.height;

    // 计算窗口位置
    let window_position_x = mouse_position_x - window_width / 2.0;
    let window_position_y = mouse_position_y - window_height / 2.0;

    // 判断窗口位置是否超出当前显示器
    let window_position_x = if window_position_x < current_monitor_position_x {
        current_monitor_position_x
    } else if window_position_x + window_width > current_monitor_position_x + current_monitor_width
    {
        current_monitor_position_x + current_monitor_width - window_width
    } else {
        window_position_x
    };

    let window_position_y = if window_position_y < current_monitor_position_y {
        current_monitor_position_y
    } else if window_position_y + window_height
        > current_monitor_position_y + current_monitor_height
    {
        current_monitor_position_y + current_monitor_height - window_height
    } else {
        window_position_y
    };

    let current_temp = LogicalPosition::new(window_position_x, window_position_y);

    window.set_position(current_temp).expect("无法设置窗口位置");

    true
}