「前回と同じディスプレイにウィンドウを出す」「サブモニターにプレゼン用ウィンドウを置く」「モニターごとの DPI に合わせて座標を補正する」には、接続中の各モニターの位置・解像度・スケール倍率が必要です。@tauri-apps/api/window の availableMonitors() で全モニター、primaryMonitor() でメイン、currentMonitor() で自ウィンドウが乗っているモニター、monitorFromPoint(x, y) で座標からモニターを引けます。Rust 側にも同名のメソッドがあります。
前提条件
プラグインは不要です。4 つの関数の権限(core:window:allow-available-monitors / allow-primary-monitor / allow-current-monitor / allow-monitor-from-point)はすべて core:window:default に含まれるので、core:default があれば追加なしで動きます。ウィンドウを移動する場合だけ core:window:allow-set-position を追加します。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": ["core:default", "core:window:allow-set-position"]
}
1. フロントエンドから実装する (TypeScript)
Monitor オブジェクトの中身
| プロパティ | 型 | 意味 |
|---|---|---|
name | string(取れなければ null) | OS の識別名。Windows は \\.\DISPLAY1 のような内部名 |
size | PhysicalSize | 解像度(物理ピクセル) |
position | PhysicalPosition | 仮想デスクトップ上の左上座標。プライマリが (0, 0)、左に置いたモニターは負の x |
scaleFactor | number | 拡大率。150% なら 1.5、Retina は 2 |
workArea | { position, size } | タスクバー / Dock を除いた作業領域(物理ピクセル) |
size は物理ピクセルです。CSS や LogicalSize と比べるときは scaleFactor で割ります。4K (3840x2160) を 150% で使うと論理サイズは 2560x1440 です。
import {
availableMonitors, primaryMonitor, currentMonitor, monitorFromPoint, cursorPosition,
} from '@tauri-apps/api/window';
export async function dumpMonitors() {
const monitors = await availableMonitors();
const primary = await primaryMonitor();
monitors.forEach((m, i) => {
const lw = Math.round(m.size.width / m.scaleFactor);
const lh = Math.round(m.size.height / m.scaleFactor);
console.log(
`#${i} ${m.name ?? '(no name)'}${m.name === primary?.name ? ' [primary]' : ''}`,
`${m.size.width}x${m.size.height} @ (${m.position.x}, ${m.position.y})`,
`scale ${m.scaleFactor} -> logical ${lw}x${lh}`,
`workArea ${m.workArea.size.width}x${m.workArea.size.height}`,
);
});
console.log('current:', (await currentMonitor())?.name);
// マウスカーソルがあるモニター(座標は物理ピクセル)
const cur = await cursorPosition();
console.log('under cursor:', (await monitorFromPoint(cur.x, cur.y))?.name);
}
画面の解像度を知りたいとき
「画面解像度」として多くの場合に欲しいのは、自分のウィンドウが表示されているモニターの値です。currentMonitor() の size がそれにあたり、物理ピクセルなので 4K モニターなら拡大率に関係なく 3840x2160 です。CSS や LogicalSize と同じ単位で比べるなら size.toLogical(scaleFactor) で論理ピクセルに直します。currentMonitor() はモニターを特定できないと null を返すので、primaryMonitor() に切り替えます。
import { currentMonitor, primaryMonitor } from '@tauri-apps/api/window';
// 自ウィンドウがあるモニターの解像度(物理・論理)を返す
export async function getScreenResolution() {
const m = (await currentMonitor()) ?? (await primaryMonitor());
if (!m) return null; // モニターを特定できなかった
const logical = m.size.toLogical(m.scaleFactor);
return {
physical: { width: m.size.width, height: m.size.height },
logical: { width: Math.round(logical.width), height: Math.round(logical.height) },
scaleFactor: m.scaleFactor,
};
}
拡大率 150% の 4K モニターなら physical は 3840x2160、logical は 2560x1440 です。ウィンドウを別のモニターへ動かすと値が変わるので、使う直前に取り直します。ウィンドウの大きさを画面に合わせる目的なら、タスクバーなどを除いた workArea を基準にします(ウィンドウのサイズを変更する)。Rust では、コマンドの引数に tauri::WebviewWindow を受け取って current_monitor()(None なら primary_monitor())を呼べば同じ値が取れます。
特定モニターにウィンドウを移動する
モニターの position を基準に配置を物理ピクセルで計算し、setPosition() に渡します。次の例は 2 台目のモニター中央に自ウィンドウを置きます。
import { availableMonitors, getCurrentWindow, PhysicalPosition } from '@tauri-apps/api/window';
export async function moveToMonitor(index: number) {
const target = (await availableMonitors())[index];
if (!target) throw new Error(`monitor #${index} not found`);
const win = getCurrentWindow();
const size = await win.outerSize(); // 物理ピクセル
await win.setPosition(new PhysicalPosition(
target.position.x + Math.floor((target.size.width - size.width) / 2),
target.position.y + Math.floor((target.size.height - size.height) / 2),
));
}
モニター間でスケール倍率が違うと移動後に物理サイズが変わるため、厳密に中央にしたいなら移動後に center() を呼びます。
2. バックエンドから実装する (Rust)
Window / WebviewWindow / AppHandle のいずれからも呼べます。Monitor のメソッドは name() -> Option<&String>、size() -> &PhysicalSize<u32>、position() -> &PhysicalPosition<i32>、scale_factor() -> f64、work_area() -> &PhysicalRect<i32, u32> です。
use tauri::Manager;
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct MonitorInfo {
name: Option<String>,
width: u32,
height: u32,
x: i32,
y: i32,
scale_factor: f64,
is_primary: bool,
}
#[tauri::command]
fn list_monitors(app: tauri::AppHandle) -> Result<Vec<MonitorInfo>, String> {
let primary = app.primary_monitor().map_err(|e| e.to_string())?
.and_then(|m| m.name().cloned());
let monitors = app.available_monitors().map_err(|e| e.to_string())?;
Ok(monitors.iter().map(|m| MonitorInfo {
name: m.name().cloned(),
width: m.size().width,
height: m.size().height,
x: m.position().x,
y: m.position().y,
scale_factor: m.scale_factor(),
is_primary: m.name().cloned() == primary,
}).collect())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![list_monitors])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';
const list = await invoke<{ name: string | null; width: number; isPrimary: boolean }[]>('list_monitors');
動作確認
npm run tauri dev で dumpMonitors() を実行すると、Windows のデュアルモニター(右に 4K を 150% で配置)では次のようになります。
#0 \\.\DISPLAY1 [primary] 1920x1080 @ (0, 0) scale 1 -> logical 1920x1080 workArea 1920x1032
#1 \\.\DISPLAY2 3840x2160 @ (1920, -540) scale 1.5 -> logical 2560x1440 workArea 3840x2160
current: \\.\DISPLAY1
moveToMonitor(1) を呼ぶとウィンドウが右のモニター中央へ移動し、currentMonitor() が DISPLAY2 になります。
よくあるエラーと対処法
primaryMonitor()/currentMonitor()がnull: リモートデスクトップや VM、モニター切替直後などで OS が特定できないケースです。availableMonitors()[0]へフォールバックしてください。monitorFromPointが常にnull: 引数は物理ピクセルです。event.screenXなどの論理値はscaleFactorを掛けてから渡します。window.set_position not allowed. Permissions associated with this command: core:window:allow-set-positionという趣旨のエラー: 移動には別権限が要ります。前提条件の JSON を確認してください。nameで同一判定したら別モニターが同名だった: Linux の一部環境ではnameがnullや重複になります。positionとsizeの組で識別する方が確実です。
OS ごとの違いと注意点
- Windows:
nameは\\.\DISPLAY1形式で、設定アプリの「ディスプレイ 1/2」の番号と一致しないことがあります。モニターごとに DPI が異なるのでscaleFactorは必ずモニター単位で見ます。 - macOS: Retina で
scaleFactorは2。「ディスプレイの配置」でメインを変えるとprimaryMonitor()も変わります。 - Linux: X11 では
nameがHDMI-1のような出力名になることが多く、Wayland ではpositionが(0, 0)固定になるコンポジターがあります。 - モニターの接続・切断を通知するイベントは Tauri にありません。必要なら数秒おきに
availableMonitors()を呼んで台数の変化を検知します。
