# 通知設計書 63-SignalCursorChanged

## 概要

本ドキュメントは、Windows Terminal における SignalCursorChanged 通知の設計仕様を記載する。この通知は、ターミナル上のカーソル位置が変更されたことを UI Automation (UIA) クライアントに通知するためのイベントである。

### 本通知の処理概要

SignalCursorChanged は、ターミナルコントロール内でカーソルが移動した際に、スクリーンリーダーなどの支援技術にその変更を伝達する UIA 通知である。

**業務上の目的・背景**：視覚障害を持つユーザーがスクリーンリーダー（NVDA、JAWS、ナレーターなど）を使用してターミナルを操作する場合、カーソル位置の変更をリアルタイムに把握する必要がある。コマンド入力中のカーソル移動、プログラム出力によるカーソル移動、行頭・行末への移動などにおいて、この通知により UIA クライアントがカーソル位置の変更を検知し、現在のカーソル位置をユーザーに音声等で伝達できるようになる。Microsoft の UIA アクセシビリティフレームワークにおけるテキストコントロールの標準的な動作に準拠するために必要な通知である。

**通知の送信タイミング**：レンダラーの描画サイクル（Paint cycle）において、カーソル位置の変更が検出された場合に送信される。具体的には、`UiaEngine::InvalidateCursor()` が呼ばれてカーソル領域の前回値（`_prevCursorRegion`）との差分が検出された場合に `_cursorChanged` フラグが true に設定され、`UiaEngine::Present()` メソッドの実行時に `IUiaEventDispatcher::SignalCursorChanged()` が呼び出される。

**通知の受信者**：UIA クライアント（スクリーンリーダー、自動テストフレームワークなど）が受信者である。XAML ベースの Windows Terminal では `TermControlAutomationPeer` が、WPF/HwndTerminal ベースでは `HwndTerminalAutomationPeer` が UIA イベントを発火し、OS の UIA インフラストラクチャを通じてクライアントに配信される。

**通知内容の概要**：`AutomationEvents::TextPatternOnTextSelectionChanged`（UIA_Text_TextSelectionChangedEventId）イベントが発火される。カーソル移動をテキスト選択変更イベントとして通知する設計となっている。これは UIA の仕様上、カーソル位置の変更は選択範囲（キャレット位置）の変更として扱われるためである。`TextEditTextChanged` イベントは意図的に使用されていない（スクリーンリーダーのクラッシュを防止するため）。

**期待されるアクション**：UIA クライアント（スクリーンリーダー等）は、このイベントを受信した後、現在のカーソル位置（キャレット位置）を再取得し、ユーザーにカーソルの新しい位置を音声やブライユ点字で伝達する。

## 通知種別

アプリ内通知（UI Automation イベント）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（`Dispatcher.RunAsync` による UIスレッドへのディスパッチ） |
| 優先度 | Normal（`CoreDispatcherPriority::Normal`） |
| リトライ | 無し |

### 送信先決定ロジック

送信先は OS の UIA インフラストラクチャにより自動的に決定される。UIA イベントをリッスンしているすべてのクライアント（スクリーンリーダー、自動テストツール等）に対してブロードキャストされる。TermControl が UI ツリーから削除されている場合（`_termControl` が null の場合）はイベントの発火をスキップする。

## 通知テンプレート

### UIA イベント情報

| 項目 | 内容 |
|-----|------|
| イベント種別 | `AutomationEvents::TextPatternOnTextSelectionChanged` |
| UIA イベントID | `UIA_Text_TextSelectionChangedEventId` |
| 形式 | UIA イベント通知（データペイロードなし） |

### 本文テンプレート

```
(テンプレートなし - UIA イベントはペイロードを持たない純粋なシグナルイベント)
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| （なし） | 本通知はペイロードを持たないイベント通知のため、テンプレート変数は存在しない | - | - |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| レンダラー内部イベント | `InvalidateCursor()` 呼び出し | `_isEnabled == true` かつ `_cursorChanged == true` | カーソル領域（`til::rect`）が前回値と異なる場合に `_cursorChanged` フラグを設定 |
| 描画サイクル完了 | `UiaEngine::Present()` 実行 | `_isEnabled == true` かつ `_isPainting == true` | 描画サイクル終了時にフラグを確認してイベントをディスパッチする |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| `_isEnabled == false` | `UiaEngine::Disable()` が呼ばれ、エンジンが無効化されている場合、イベントは発火されない |
| `_cursorChanged == false` | カーソル位置に変更がない場合、イベントは発火されない |
| カーソル領域が前回と同一 | `*psrRegion == _prevCursorRegion` の場合、`_cursorChanged` フラグは設定されない |
| psrRegion が null | `InvalidateCursor()` にnullが渡された場合、`E_INVALIDARG` が返され処理されない |
| TermControl が UI ツリーから削除済み | `_termControl` の weak_ref が null を返す場合、`RaiseAutomationEvent` は呼び出されない |
| Dispatcher が null | XAML Dispatcher が利用できない場合、イベントはスキップされる |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[カーソル位置変更の検出] --> B[InvalidateCursor呼び出し]
    B --> C{psrRegion != null?}
    C -->|No| D[E_INVALIDARG 返却]
    C -->|Yes| E{*psrRegion != _prevCursorRegion?}
    E -->|No| F[変更なし - スキップ]
    E -->|Yes| G[_prevCursorRegion = *psrRegion]
    G --> H[_cursorChanged = true]
    H --> I[StartPaint - 描画サイクル開始]
    I -->|somethingToDo == true| J[EndPaint - 描画サイクル終了]
    I -->|somethingToDo == false| K[S_FALSE - スキップ]
    J --> L[Present実行]
    L --> M{_cursorChanged?}
    M -->|true| N[dispatcher->SignalCursorChanged]
    M -->|false| O[スキップ]
    N --> P[InteractivityAutomationPeer::SignalCursorChanged]
    P --> Q[CursorChanged.raise - イベント発火]
    Q --> R[TermControlAutomationPeer::SignalCursorChanged]
    R --> S[UiaTracing::Signal::CursorChanged - トレース記録]
    S --> T[Dispatcher.RunAsync - UIスレッドへディスパッチ]
    T --> U{_termControl有効?}
    U -->|Yes| V[RaiseAutomationEvent - TextPatternOnTextSelectionChanged]
    U -->|No| W[処理終了]
    V --> X[_cursorChanged = false - フラグリセット]
    X --> Y[終了]
    D --> Y
    F --> Y
    K --> Y
    O --> Y
    W --> Y
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（UIA通知はデータベースを使用しない）

### 更新テーブル一覧

該当なし（UIA通知はデータベースを使用しない）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 引数不正（E_INVALIDARG） | `InvalidateCursor()` に null ポインタが渡された | `RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion)` でエラーを返す |
| Dispatcher取得失敗 | XAML UIスレッドの Dispatcher が null | イベント発火をスキップ（早期リターン） |
| TermControl参照失敗 | weak_ref からの TermControl 取得失敗 | イベント発火をスキップ |
| RaiseAutomationEvent例外 | UIA イベント発火時の例外 | CATCH_LOG() でログ記録後、処理を継続（UiaRenderer.cpp） |
| UiaRaiseAutomationEvent失敗（HWND版） | Win32 UIA API 呼び出し失敗 | `LOG_IF_FAILED` でログ記録 |
| InvalidateCursor内部例外 | カーソル比較処理中の例外 | `CATCH_RETURN()` マクロでキャッチし HRESULT を返す |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（レンダラーの描画サイクルに依存） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし。アプリケーション実行中は常時送信可能。ただし、`UiaEngine` が `Disable()` されている場合はイベントが抑止される。複数の TermControl が存在する場合、フォーカスを持つコントロールのみが `Enable()` 状態となり、他は `Disable()` される。

## セキュリティ考慮事項

- 本通知はカーソル位置変更という事実のみを通知し、カーソル位置の座標やテキスト内容は含まない。そのため、通知自体に個人情報や機密情報は含まれない。
- UIA クライアントが `ITextProvider` のメソッドを通じてカーソル位置やテキスト内容を取得する際は、OS の UIA セキュリティモデル（UIPI: User Interface Privilege Isolation）に従い、適切な権限を持つプロセスのみがアクセス可能。

## 備考

- **重要な設計判断**: カーソル変更時に `TextPatternOnTextSelectionChanged` を発火する。`TextEditTextChanged` は意図的に使用していない。コメント（TermControlAutomationPeer.cpp 198-201行目）によれば、`TextEditTextChanged` を単独で発火すると、受信側のアプリケーションが追加情報を期待するため nullptr 例外が発生し、スクリーンリーダーがクラッシュする可能性があるためである。
- `HwndTerminalAutomationPeer`（WPF/HWND版）でも同様に `UIA_Text_TextSelectionChangedEventId` を使用する（`UIA_Text_TextChangedEventId` ではない）。
- `InvalidateCursor()` では、前回のカーソル領域（`_prevCursorRegion`）と比較して差分がある場合のみフラグを設定する。同じ位置でカーソルが点滅するだけの場合はイベントが発火されない。
- `UiaTracing::Signal::CursorChanged()` によりETW（Event Tracing for Windows）にトレースログが記録される（WINEVENT_LEVEL_VERBOSE レベル）。
- `InvalidateCursor()` は `try/CATCH_RETURN()` パターンで例外を捕捉する。これは `Invalidate()` や `InvalidateSelection()` にはない例外処理であり、カーソル領域の比較処理が例外を発生させる可能性があるための対策である。

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

IUiaEventDispatcher インターフェースが定義するメソッドシグネチャと、カーソル領域を表す `til::rect` 型を理解することが出発点となる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | IUiaEventDispatcher.h | `src/types/IUiaEventDispatcher.h` | 26行目: `SignalCursorChanged()` の純粋仮想関数宣言。引数なし・戻り値なしのインターフェース |
| 1-2 | UiaRenderer.hpp | `src/renderer/uia/UiaRenderer.hpp` | 77行目: `_prevCursorRegion` の型が `til::rect` であることを確認。カーソル位置の前回値を保持する |

**読解のコツ**: `til::rect` は Windows Terminal 独自の矩形型で、カーソルの占有領域を文字セルグリッド上の座標で表現する。`_prevCursorRegion` との比較により差分検出が行われる。

#### Step 2: エントリーポイントを理解する

UiaEngine がレンダラーとしてカーソル位置変更を検出し、イベントをディスパッチする流れを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | UiaRenderer.hpp | `src/renderer/uia/UiaRenderer.hpp` | 71行目: `_cursorChanged` フラグの定義。77行目: `_prevCursorRegion` の定義 |
| 2-2 | UiaRenderer.cpp | `src/renderer/uia/UiaRenderer.cpp` | 78-91行目: `InvalidateCursor()` でカーソル位置の差分検出と `_cursorChanged = true` の設定。240-247行目: `Present()` 内で `_dispatcher->SignalCursorChanged()` を呼び出し |

**主要処理フロー**:
1. **78行目**: `InvalidateCursor()` - カーソル領域パラメータの受け取り
2. **81行目**: `RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion)` - null チェック
3. **84行目**: `*psrRegion != _prevCursorRegion` - 前回値との差分比較
4. **86行目**: `_prevCursorRegion = *psrRegion` - 前回値の更新
5. **87行目**: `_cursorChanged = true` - フラグ設定
6. **170行目**: `StartPaint()` - `_cursorChanged` を含む `somethingToDo` チェック
7. **219行目**: `Present()` - フラグ確認とディスパッチャ呼び出し
8. **240-247行目**: `SignalCursorChanged()` の呼び出し
9. **264行目**: `_cursorChanged = false` でフラグリセット

#### Step 3: InteractivityAutomationPeer の中継処理を理解する

InteractivityAutomationPeer が UiaEngine からのシグナルを受け取り、XAML イベントとして中継する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | InteractivityAutomationPeer.h | `src/cascadia/TerminalControl/InteractivityAutomationPeer.h` | 51行目: `SignalCursorChanged()` のオーバーライド宣言。75行目: `CursorChanged` イベントの定義 |
| 3-2 | InteractivityAutomationPeer.cpp | `src/cascadia/TerminalControl/InteractivityAutomationPeer.cpp` | 95-98行目: `SignalCursorChanged()` の実装 - `CursorChanged.raise()` でイベントを発火 |

**主要処理フロー**:
- **97行目**: `CursorChanged.raise(*this, nullptr)` - til::typed_event を使ってイベントを発火

#### Step 4: TermControlAutomationPeer の UIA イベント発火を理解する

TermControlAutomationPeer が最終的に XAML の AutomationEvent を発火する。ここが本通知の最も重要な設計判断ポイントである。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | TermControlAutomationPeer.h | `src/cascadia/TerminalControl/TermControlAutomationPeer.h` | 69行目: `SignalCursorChanged()` のオーバーライド宣言 |
| 4-2 | TermControlAutomationPeer.cpp | `src/cascadia/TerminalControl/TermControlAutomationPeer.cpp` | 83行目: コンストラクタでの `CursorChanged` イベントハンドラ登録。184-207行目: `SignalCursorChanged()` の実装。**198-201行目のコメントが重要**: TextEditTextChanged を使わない理由の説明 |

**主要処理フロー**:
- **83行目**: `_contentAutomationPeer.CursorChanged([this](auto&&, auto&&) { SignalCursorChanged(); })` - イベントハンドラ登録
- **186行目**: `UiaTracing::Signal::CursorChanged()` - ETWトレース記録
- **192行目**: `dispatcher.RunAsync(...)` - UIスレッドへの非同期ディスパッチ
- **203行目**: `RaiseAutomationEvent(AutomationEvents::TextPatternOnTextSelectionChanged)` - UIA イベント発火（TextSelectionChanged として発火する点に注意）

#### Step 5: HwndTerminalAutomationPeer の代替実装を理解する

WPF/HWND ベースの代替実装パスを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | HwndTerminalAutomationPeer.hpp | `src/cascadia/TerminalControl/HwndTerminalAutomationPeer.hpp` | 37行目: `SignalCursorChanged()` のオーバーライド宣言 |
| 5-2 | HwndTerminalAutomationPeer.cpp | `src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp` | 128-132行目: Win32 UIA API を直接呼び出す実装。131行目: `UIA_Text_TextSelectionChangedEventId` を使用（XAML版と同一のイベントID） |

**主要処理フロー**:
- **130行目**: `UiaTracing::Signal::CursorChanged()` - ETWトレース
- **131行目**: `UiaRaiseAutomationEvent(this, UIA_Text_TextSelectionChangedEventId)` - Win32 UIA API直接呼び出し

#### Step 6: トレーシング処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | UiaTracing.h | `src/types/UiaTracing.h` | 88行目: `Signal::CursorChanged()` の宣言 |
| 6-2 | UiaTracing.cpp | `src/types/UiaTracing.cpp` | 746-757行目: ETW `TraceLoggingWrite` による `"Signal::CursorChanged"` トレース出力 |

### プログラム呼び出し階層図

```
Renderer (描画サイクル)
    │
    ├─ UiaEngine::InvalidateCursor()              [UiaRenderer.cpp:78]
    │      ├─ null チェック
    │      ├─ _prevCursorRegion との差分比較
    │      ├─ _prevCursorRegion 更新
    │      └─ _cursorChanged = true
    │
    ├─ UiaEngine::StartPaint()                    [UiaRenderer.cpp:170]
    │      └─ somethingToDo チェック
    │
    ├─ UiaEngine::EndPaint()                      [UiaRenderer.cpp:190]
    │
    └─ UiaEngine::Present()                       [UiaRenderer.cpp:219]
           │
           └─ _dispatcher->SignalCursorChanged()
                  │
                  ├─[XAML版] InteractivityAutomationPeer::SignalCursorChanged()
                  │      └─ CursorChanged.raise()
                  │             └─ TermControlAutomationPeer::SignalCursorChanged()
                  │                    ├─ UiaTracing::Signal::CursorChanged()
                  │                    └─ Dispatcher.RunAsync()
                  │                           └─ RaiseAutomationEvent(TextPatternOnTextSelectionChanged)
                  │                              ※ TextEditTextChangedは使用しない（スクリーンリーダークラッシュ防止）
                  │
                  └─[HWND版] HwndTerminalAutomationPeer::SignalCursorChanged()
                         ├─ UiaTracing::Signal::CursorChanged()
                         └─ UiaRaiseAutomationEvent(UIA_Text_TextSelectionChangedEventId)
```

### データフロー図

```
[入力]                          [処理]                              [出力]

カーソル位置変更      ───▶  UiaEngine::InvalidateCursor()      ───▶  差分比較
  (til::rect)                    │                                    (*psrRegion != _prevCursorRegion)
                                 ▼
                           差分あり                              ───▶  _prevCursorRegion 更新
                                 │                                    + _cursorChanged = true
                                 ▼
                           UiaEngine::Present()                 ───▶  IUiaEventDispatcher
                                 │                                    ::SignalCursorChanged()
                                 ▼
                           AutomationPeer                       ───▶  UIA イベント
                           (XAML or HWND)                              (TextPatternOnTextSelectionChanged)
                                 │                                    ※ TextEditTextChanged ではない
                                 ▼
                           OS UIA Infrastructure                ───▶  スクリーンリーダー等の
                                                                       UIA クライアント
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| IUiaEventDispatcher.h | `src/types/IUiaEventDispatcher.h` | ソース（ヘッダー） | UIA イベントディスパッチャのインターフェース定義 |
| UiaRenderer.hpp | `src/renderer/uia/UiaRenderer.hpp` | ソース（ヘッダー） | UIA レンダラーエンジンのクラス定義（_prevCursorRegion含む） |
| UiaRenderer.cpp | `src/renderer/uia/UiaRenderer.cpp` | ソース | UIA レンダラーエンジンの実装（InvalidateCursor, Present） |
| InteractivityAutomationPeer.h | `src/cascadia/TerminalControl/InteractivityAutomationPeer.h` | ソース（ヘッダー） | Interactivity 層の AutomationPeer 定義 |
| InteractivityAutomationPeer.cpp | `src/cascadia/TerminalControl/InteractivityAutomationPeer.cpp` | ソース | Interactivity 層の SignalCursorChanged 実装 |
| TermControlAutomationPeer.h | `src/cascadia/TerminalControl/TermControlAutomationPeer.h` | ソース（ヘッダー） | TermControl の AutomationPeer 定義 |
| TermControlAutomationPeer.cpp | `src/cascadia/TerminalControl/TermControlAutomationPeer.cpp` | ソース | XAML UIA イベント発火の実装（TextEditTextChanged非使用の設計判断コメント含む） |
| HwndTerminalAutomationPeer.hpp | `src/cascadia/TerminalControl/HwndTerminalAutomationPeer.hpp` | ソース（ヘッダー） | HWND 版 AutomationPeer 定義 |
| HwndTerminalAutomationPeer.cpp | `src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp` | ソース | HWND 版の Win32 UIA イベント発火実装 |
| UiaTracing.h | `src/types/UiaTracing.h` | ソース（ヘッダー） | UIA トレーシングクラス定義 |
| UiaTracing.cpp | `src/types/UiaTracing.cpp` | ソース | ETW トレースログ出力の実装 |
