ネイティブ API
現時点で、Murasaki のネイティブサーフェスからアプリに公開されているもの。
Murasaki のネイティブウィンドウ、メニュー、OS 統合は
@murasakijs/native —
自社製の Rust バインディング(tao/wry/muda)— によって支えられているため、Rust を書く
ことは一切ありません。このページでは、あなたの React アプリから実際に到達可能なも
のを説明します。
ウィンドウとネイティブメニューバー
windowのshape(サイズ、title、route、表示、permission、macOS vibrancy)は
murasaki.config.ts で宣言します。window はprimaryのmain、windows はlabel付き
secondaryです。全体像はウィンドウと権限を参照してください。
macOS では、標準的な App/Edit/Window メニューバー(About、Services、Hide、Quit、
Edit、Window)が自動的に生成され、config.locales からローカライズされます — コード
は不要です。
コンテキストメニュー
右クリックのコンテキストメニューは、マークアップではなくフックである
useContextMenu で宣言し、Rust 側に投げられて macOS / Windows では本物の
NSMenu / HMENU がポップアップします。詳しくは専用の
コンテキストメニューガイドを参照してください。
ダイアログ、クリップボード、通知、shell、ウィンドウ操作
レンダラーから安全に使えるネイティブ機能は murasaki/native から import します。
すべて Promise ベースで、リクエストID付きIPCをRustホストが直接処理します。
'use client'
import { app, appWindow, clipboard, dialog, notification, shell, systemPermission, tray, windows } from 'murasaki/native'
const files = await dialog.openFile({
multiple: true,
filters: [{ name: 'Images', extensions: ['png', 'jpg'] }],
})
await clipboard.writeText(files.join('\n'))
await notification.show({ title: '選択完了', body: `${files.length} files` })
await appWindow.setTitle('Import complete')
console.log(await appWindow.getLabel())
await windows.open('preview')
await shell.showItemInFolder(files[0])| API | 操作 |
|---|---|
app | quit(gracefulなapplication shutdown) |
dialog | openFile, openDirectory, saveFile |
clipboard | readText, writeText |
notification | show |
shell | openExternal(安全なURLスキームのみ)、showItemInFolder |
systemPermission | macOSのcamera / microphone / screen recording / accessibility向けstatus, request |
appWindow | getLabel, setTitle, setSize, minimize, toggleMaximize, show, hide, focus, close, setAlwaysOnTop、状態取得 |
windows | 宣言label向けopen, list, show, hide, focus, close |
tray | create, remove, setTooltip, setIcon, setMenu, onClick, onMenuItem |
app.quit() とrootの quit() helperはいずれも app:quit が必要です。権限のない
secondary rendererからapplication全体を終了することはできません。
0.50 migration: 以前のquit()はpermissionなしのraw IPCを送っていました。
既存のnon-updater appで使用しているwindowにはapp:quitを追加してください。
built-in updaterを有効にした場合だけ、検証済みinstall / restart handshakeの後方互換性を
保つためprimary windowへ自動付与されます。
これらは src/main.ts から直接呼ぶAPIではなく、信頼済みレンダラー向けです。
ブリッジはアプリと完全一致するoriginだけを受け付け、固定allowlistのコマンドだけを
公開します。各rendererが使うcommandだけを window.capabilities /
windows[label].capabilities で許可してください。argument / path / URL scopeは
計画中なので、権限を持つwindowに信頼できないremote contentを読み込まないでください。
トレイアイコン
client componentから1つのsystem tray iconを作成できます。既定では
config.icon を使い、明示指定する場合は8-bit RGB/RGBA PNGを渡します。
操作ごとに独立したpermissionが必要です。
import { tray } from 'murasaki/native'
await tray.create({
tooltip: '同期中',
template: true,
menu: [
{ id: 'open', label: 'Murasakiを開く' },
{ separator: true },
{ id: 'quit', label: '終了' },
],
menuOnLeftClick: navigator.userAgent.includes('Mac OS X'),
menuOnRightClick: true,
})
const unsubscribe = tray.onClick(({ button, double }) => {
console.log({ button, double })
})
const unsubscribeMenu = tray.onMenuItem(async (id) => {
if (id === 'open') await appWindow.show()
if (id === 'quit') await app.quit()
})
await tray.setTooltip('同期完了')
await tray.setIcon('/absolute/path/to/synced.png')
await tray.setMenu([{ id: 'open', label: 'Murasakiを開く' }])
// 後で: unsubscribe(); unsubscribeMenu(); await tray.remove()export default defineConfig({
// ...
capabilities: [
'tray:create',
'tray:setTooltip',
'tray:setIcon',
'tray:setMenu',
'tray:remove',
'window:show',
'app:quit',
],
})template はmacOS専用です。もう一度 create すると既存アイコンを置き換えます。
tray menuはevent駆動で、click可能な項目には一意なidが必要です。quitなどのprivileged
actionは、それぞれ本来のnative capabilityを通過します。process-wide iconを作成した
rendererを閉じるとiconも削除されます。global shortcutとLinux trayは未対応です。
システム権限
OSの同意とMurasaki renderer capabilityは別物です。packaged macOS appでは、用途説明と
任意の起動時requestをconfigに宣言できます。用途説明はInfo.plistへ書き込まれ、camera /
microphoneの説明が欠けた状態でpromptしてappがcrashすることを防ぎます。
export default defineConfig({
// ...
systemPermissions: {
macOS: {
camera: {
usageDescription: 'ビデオ通話でカメラを使用します。',
requestOnLaunch: true,
},
microphone: {
usageDescription: '音声通話でマイクを使用します。',
},
screenRecording: { requestOnLaunch: false },
accessibility: { requestOnLaunch: false },
},
},
capabilities: [
'systemPermission:status',
'systemPermission:request',
],
})client componentから文脈に合わせて要求する場合:
const status = await systemPermission.status('microphone')
if (status === 'notDetermined') {
await systemPermission.request('microphone')
}camera / microphoneのOS promptは非同期で完了するため、request()が最初は
notDeterminedを返す場合があります。protected featureを有効にする前に、appが再び
focusされた時点でstatus()を再確認してください。
requestOnLaunchはpackaged macOS appに適用されます。developmentはterminal / Node hostの
identityで動くため、TCCの実動作はpackage buildから確認してください。Windowsのunpackaged
desktop appではcamera / microphoneを利用するdevice API側が同意を要求し、汎用的な起動時
promptはありません。そのためMurasakiは権限取得を装わずunsupportedを返します。macOSの
screen recording / accessibilityは初回とdenyを区別できない場合notGrantedです。
組み込みのメニューアクション
項目の action は、関数の代わりに組み込みの <Action.*/> 要素の1つにできます —
これらは(OS 自身が処理する)ネイティブロール、または小さなクライアントサイドの挙動を
実行します:
| Action | 挙動 |
|---|---|
<Action.Copy />, <Action.Paste />, <Action.Cut />, <Action.SelectAll />, <Action.Undo />, <Action.Redo /> | ネイティブ OS の編集ロール |
<Action.Quit /> | ネイティブの終了ロール |
<Action.Reload /> | ウィンドウをリロードします(location.reload()) |
<Action.Navigate to="/path" /> | ルーター経由のクライアントサイドナビゲーション |
<Action.Run action={fn} /> | 通常の関数を実行します(関数を直接渡すのと同じです) |
自動更新
useUpdate() は、更新の確認・ダウンロード・インストールを行います — その確認/ダウン
ロードのロジックは Node 上で動作し、コンテキストメニューやアプリメニューが使う IPC
ブリッジではなく、アプリの他の部分を配信しているのと同じローカル HTTP サーバー(Server
Actions や API Routes と同じ仕組み)経由でアクセスされます。<UpdateButton />
(こちらも murasaki から、@murasakijs/ui でスタイリング)は、それをラップした
すぐに使えるボタンです:
import { useUpdate } from 'murasaki'
const { status, latest, check, download, install } = useUpdate()status は idle → checking → available → downloading → ready(または
not-available / error)と遷移します。セットアップはコマンド2つだけです — マニフェ
ストの形式、セキュリティモデル、GitHub Actions でのリリースワークフローについては、専
用の自動更新ガイドを参照してください。
global shortcut、argument / path単位のcapability ruleはまだ公開されていません。 設計前に プラットフォーム機能状況を確認してください。