現在のマウスカーソル座標を取得する

cursorPosition() と Rust の cursor_position() で、画面全体でのマウスカーソルの座標を取る。物理座標と論理座標の変換、ウィンドウ外やマルチモニターでの値も示す。

システム情報 対象: Tauri 2.x 更新日: 読了目安: 約5分 sys-020
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. 2. バックエンドから実装する (Rust)
  4. 動作確認
  5. よくあるエラーと対処法
  6. OS ごとの違いと注意点
  7. 関連レシピ

DOM の mousemove で取れるのはウィンドウ内の座標だけで、カーソルがウィンドウの外にある間は何も分かりません。スクリーンショットツールやカーソル追従型のオーバーレイには画面全体の座標が必要です。Tauri では @tauri-apps/api/window の cursorPosition() と Rust の Window::cursor_position() がそれを返します。どちらも 物理ピクセル なので、DPI スケーリング環境での換算も説明します。

前提条件

プラグインは不要です。権限 core:window:allow-cursor-position は core:window:default に含まれているため、core:default を有効にしていればそのまま使えます。個別指定にしている場合だけ追加します。

{
  "permissions": [
    "core:window:allow-cursor-position",
    "core:window:allow-scale-factor",
    "core:window:allow-outer-position"
  ]
}

1. フロントエンドから実装する (TypeScript)

import { cursorPosition, getCurrentWindow } from '@tauri-apps/api/window';

const win = getCurrentWindow();

async function readCursor() {
  const phys = await cursorPosition();          // 物理ピクセル。デスクトップ左上が原点。ウィンドウ外でも取れる
  const logical = phys.toLogical(await win.scaleFactor()); // 論理ピクセル (CSS px 相当)
  const outer = await win.outerPosition();      // これも物理ピクセル
  const rel = { x: phys.x - outer.x, y: phys.y - outer.y }; // ウィンドウ左上からの相対 (物理)
  return { phys, logical, rel };
}

// mousemove はウィンドウ外で止まるので、ポーリングで追う
setInterval(async () => {
  const { phys, logical } = await readCursor();
  document.getElementById('pos')!.textContent =
    `physical (${phys.x}, ${phys.y}) / logical (${logical.x.toFixed(0)}, ${logical.y.toFixed(0)})`;
}, 200);

cursorPosition() は PhysicalPosition を返し、toLogical(scaleFactor) で LogicalPosition に変換できます。引き算するときに物理と論理を混ぜないよう注意してください。

2. バックエンドから実装する (Rust)

Window / WebviewWindow の cursor_position() が Result<PhysicalPosition<f64>> を返します。Rust 側で定期的に取ってイベントで流すと、フロントは listen するだけで済み、IPC の往復も減ります。

use serde::Serialize;
use std::time::Duration;
use tauri::{Emitter, Manager, WebviewWindow};

#[derive(Serialize, Clone)]
struct Cursor { x: f64, y: f64, lx: f64, ly: f64 }

fn read(window: &WebviewWindow) -> tauri::Result<Cursor> {
    let p = window.cursor_position()?;                    // 物理座標
    let l = p.to_logical::<f64>(window.scale_factor()?);  // 論理座標
    Ok(Cursor { x: p.x, y: p.y, lx: l.x, ly: l.y })
}

#[tauri::command]
fn cursor_now(window: WebviewWindow) -> Result<Cursor, String> {
    read(&window).map_err(|e| e.to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            let window = app.get_webview_window("main").unwrap();
            std::thread::spawn(move || loop {
                if let Ok(c) = read(&window) {
                    let _ = window.emit("cursor", c);
                }
                std::thread::sleep(Duration::from_millis(100));
            });
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![cursor_now])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { listen } from '@tauri-apps/api/event';
type Cursor = { x: number; y: number; lx: number; ly: number };
await listen<Cursor>('cursor', (e) => console.log(e.payload));

動作確認

npm run tauri dev で起動し、カーソルをウィンドウの外へ出しても数値が更新され続ければ成功です。150% スケーリングの Windows では physical (1500, 900) / logical (1000, 600) のように物理が論理の 1.5 倍になります。モニターを 2 台つないで左側のモニターにカーソルを移すと x が負になります。

よくあるエラーと対処法

  • window.cursor_position not allowed. Permissions associated with this command: core:window:allow-cursor-position, ... という趣旨のエラー: core:window:default を外して個別指定にしている場合に起きます。上記の権限を追加します。
  • DOM の座標と一致しない: cursorPosition() は物理ピクセルでデスクトップ原点、MouseEvent.clientX は論理ピクセルでビューポート原点です。toLogical() と innerPosition()(タイトルバーを除いた原点)で揃えます。
  • Android / iOS で失敗する: このコマンドはデスクトップ専用で、モバイルでは Err になります。
  • CPU 使用率が上がる: 100ms 未満の間隔で IPC を回すと目に見えて重くなります。Rust 側でループさせ、値が変わったときだけ emit すると軽くなります。

OS ごとの違いと注意点

  • Windows: モニターごとにスケーリングが違う構成では、どのモニター上にいるかで論理換算の係数が変わります。win.scaleFactor() は「ウィンドウがあるモニター」の係数なので、他モニター上のカーソルは monitorFromPoint(x, y) でそのモニターの scaleFactor を使います。
  • macOS: OS 標準は論理ピクセル・左下原点ですが、Tauri は物理ピクセル・左上原点に統一して返します。Retina では 2 倍の値です。
  • Linux: X11 では Windows と同様に動きます。Wayland はセキュリティ設計上、アプリが自分のウィンドウ外のポインタ位置を取得できず、ウィンドウ外では最後にウィンドウ内にいた座標が返るか、エラーになります。Wayland を主対象にするならウィンドウ内の mousemove で代替してください。
  • マルチモニター: 原点はデスクトップ全体の左上(通常はプライマリモニターの左上)で、プライマリより左や上のモニターでは負の値になります。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する