ユーザーのホームディレクトリパスを取得する

homeDir() と Rust の home_dir() でホームディレクトリのパスを得る。~ が展開されない点、ドキュメントなどは documentDir() を使う理由、$HOME の権限を広げすぎない書き方も示す。

ファイルシステム 対象: Tauri 2.x 更新日: 読了目安: 約7分 fs-020
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. パスを「~」付きで短く表示する
  4. ~ はホームとして解釈されない
  5. ドキュメントやダウンロードは専用の関数で
  6. 2. バックエンドから実装する (Rust)
  7. 動作確認
  8. よくあるエラーと対処法
  9. OS ごとの違いと注意点
  10. 関連レシピ

ホームディレクトリのパスは、フロントエンドの homeDir()、Rust の app.path().home_dir() で得られます。よく使うのは、パスを ~/Documents/report.pdf のように短く表示するとき、~/.gitconfig のような既存の設定ファイルを読むとき、ダイアログの初期フォルダーにするときです。一方で、アプリのデータをホーム直下に置くのは避け、ドキュメントやダウンロードの場所も専用の関数で取ります。OS ごとの実際の場所は アプリ専用のデータ保存フォルダパスを取得する の表を参照してください。

前提条件

homeDir() は core:default に含まれる core:path:default で呼べます。ホームの中のファイルを読むときだけ File System プラグインを追加します。

npm run tauri add fs

公式の fs:allow-home-read-recursive は、~/.ssh の鍵なども含めてホーム全体を読めるようにしてしまうので、読むファイルだけを範囲にします。readTextFile() と exists() のコマンドは fs:default に含まれていますが、fs:default の範囲はアプリ用フォルダーだけなので、コマンドごとに範囲を付けて足します。BaseDirectory.Home が範囲の $HOME に当たります(変数の一覧は ファイルやディレクトリを削除する)。

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "fs:default",
    {
      "identifier": "fs:allow-read-text-file",
      "allow": [{ "path": "$HOME/.gitconfig" }]
    },
    {
      "identifier": "fs:allow-exists",
      "allow": [{ "path": "$HOME/.gitconfig" }]
    }
  ]
}

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

パスを「~」付きで短く表示する

長いパスは、ホームの部分を ~ に置き換えると読みやすくなります。home + sep() と比べるのは、/home/me2 のような別の利用者のフォルダーを誤って縮めないためです。置き換えた文字列は表示専用で、fs の API には元のパスを渡します。

import { homeDir, sep } from '@tauri-apps/api/path';

const home = await homeDir();

export function toDisplayPath(path: string): string {
  const inHome = path === home || path.startsWith(home + sep());
  return inHome ? '~' + path.slice(home.length) : path;
}

console.log(toDisplayPath(`${home}${sep()}Documents${sep()}report.pdf`));

~ はホームとして解釈されない

fs プラグインも path API も、~ をホームに展開しません。readTextFile('~/.gitconfig') は「~ という名前のフォルダー」を指す相対パスになり、範囲の外として拒否されます。ホームからの相対パスは baseDir: BaseDirectory.Home と組み合わせます。

import { BaseDirectory, exists, readTextFile } from '@tauri-apps/plugin-fs';

// ~/.gitconfig の中身を返す。無ければ null
export async function readGitConfig(): Promise<string | null> {
  const opts = { baseDir: BaseDirectory.Home };
  if (!(await exists('.gitconfig', opts))) return null;
  return readTextFile('.gitconfig', opts);
}

ドキュメントやダウンロードは専用の関数で

join(await homeDir(), 'Documents') のように組み立てると、実在しない場所になることがあります。Windows ではドキュメントやデスクトップを OneDrive の中などへ移せ、Linux ではフォルダー名が xdg-user-dirs の設定で決まる(日本語の環境では ~/ドキュメント など)ためです。documentDir()・downloadDir()・desktopDir() などを使えば、OS の設定どおりの場所が返ります。保存ダイアログの初期フォルダーにするなら、この値を defaultPath に渡します(ファイルを保存する場所を選ばせる)。

import { desktopDir, documentDir, downloadDir, homeDir, join } from '@tauri-apps/api/path';

const guessed = await join(await homeDir(), 'Documents'); // 実在するとは限らない
const docs = await documentDir().catch(() => homeDir()); // 場所が決まらない環境ではホームで代用

console.log('guessed ', guessed);
console.log('document', docs);
console.log('download', await downloadDir());
console.log('desktop ', await desktopDir());

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

home_dir() は tauri::Result<PathBuf> を返します。Rust の std::fs は capability の範囲に縛られないので、JS から任意のパスを受け取って読むコマンドは作らず、読む場所は Rust の中で決めます。次の例は ~/.gitconfig の [user] にある name を読み、作者名の初期値に使うものです。JS 側に fs の権限は要りません。

use std::io::ErrorKind;
use tauri::Manager;

/// ~/.gitconfig の [user] name を返す。ファイルや項目が無ければ None
#[tauri::command]
fn git_user_name(app: tauri::AppHandle) -> Result<Option<String>, String> {
    let path = app.path().home_dir().map_err(|e| e.to_string())?.join(".gitconfig");
    let text = match std::fs::read_to_string(&path) {
        Ok(t) => t,
        Err(e) if e.kind() == ErrorKind::NotFound => return Ok(None),
        Err(e) => return Err(format!("{}: {e}", path.display())),
    };
    let mut in_user = false;
    for line in text.lines().map(str::trim) {
        if line.starts_with('[') {
            in_user = line == "[user]";
        } else if let (true, Some((key, value))) = (in_user, line.split_once('=')) {
            if key.trim() == "name" {
                return Ok(Some(value.trim().to_string()));
            }
        }
    }
    Ok(None)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![git_user_name])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';

const name = await invoke<string | null>('git_user_name');
console.log(name ?? '(未設定)');

動作確認

npm run tauri dev で起動し、1 章のコードを実行すると、Windows では次のように出ます(ユーザー名は環境によって変わります)。ドキュメントを OneDrive に移している PC では、document と desktop が OneDrive の中を指し、guessed と食い違います。

~\Documents\report.pdf
guessed  C:\Users\me\Documents
document C:\Users\me\Documents
download C:\Users\me\Downloads
desktop  C:\Users\me\Desktop

git_user_name は、Git で user.name を設定していればその名前を、していなければ null を返します。

よくあるエラーと対処法

  • 「forbidden path: ~/.gitconfig, …」: ~ が展開されず、相対パスのまま判定されています。baseDir: BaseDirectory.Home と .gitconfig を渡します。
  • 「forbidden path: <パス>, maybe it is not allowed on the scope for allow-read-text-file permission in your capability file」: 範囲に書いていないファイルです。必要なファイルだけを $HOME/… で足します(リリースビルドでは「forbidden path: <パス>」だけになります)。
  • 「unknown path」: 場所を特定できない環境です。Linux では xdg-user-dirs の設定が無い最小構成の環境で、documentDir() などがこのエラーになることがあります。上の例のように catch で代わりの場所を使います。
  • Windows で toDisplayPath() が縮めない: Windows ではパスの大文字小文字が違っても同じ場所です。Windows で使うなら、比べる前に両方を小文字にします。

OS ごとの違いと注意点

  • Windows: ユーザーのプロファイルフォルダー(C:\Users\<ユーザー名>)が返り、環境変数 HOME は使われません。ユーザー名に日本語や空白が入っていることがあるので、パスを外部コマンドに渡すときは文字列をつないでコマンドにせず、引数として分けて渡します。
  • macOS / Linux: 環境変数 HOME の値がそのまま返ります。HOME を変えて起動すれば、その場所になります。
  • iOS: ホームには直接書き込めません。アプリ用のフォルダーを使います。
  • 共通: ホーム直下にアプリ用のフォルダー(~/.myapp など)を作るのは、CLI ツールと共有するといった理由があるときだけにし、通常は appDataDir() などを使います。

関連レシピ

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

Web Ninja

この記事を書いた人

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

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

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

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