# 通知設計書 17-CursorChanged

## 概要

カーソル位置や選択状態の変更をスクリーンリーダー等のアクセシビリティクライアントに通知するアクセシビリティイベントの設計書である。MSAAのEVENT_CONSOLE_CARETイベントとUIAのUIA_Text_TextSelectionChangedEventIdイベントを発火する。

### 本通知の処理概要

CursorChangedは、ターミナルのカーソル位置が移動した際や選択状態が変更された際に、MSAA（Microsoft Active Accessibility）とUIA（UI Automation）を通じてアクセシビリティクライアント（スクリーンリーダー等）に通知するイベントである。

**業務上の目的・背景**：視覚障害を持つユーザーがターミナルアプリケーションを利用するためには、カーソルの位置変更をスクリーンリーダーに適切に通知する必要がある。NVDAやNarratorなどのスクリーンリーダーは、EVENT_CONSOLE_CARETイベントを受信してカーソル位置の変更を検知し、ユーザーに音声で通知する。また、ConsoleControl(ConsoleSetCaretInfo)を通じてGetGUIThreadInfo()のrcCaretフィールドが更新され、カーソル矩形の情報がシステムレベルで利用可能になる。

**通知の送信タイミング**：テキストバッファ上のカーソル位置が変更された時点、または選択状態が変更された時点でトリガーされる。デバウンス処理（タイマーベース）により、高頻度の更新がバッチ処理される。

**通知の受信者**：MSAA/UIAを通じて、システムに登録されたアクセシビリティクライアント（スクリーンリーダー、画面拡大鏡など）。

**通知内容の概要**：カーソルの新しい位置（バッファ座標のx, y）、選択状態の有無を含むEVENT_CONSOLE_CARETイベント。加えて、ConsoleSetCaretInfoによりカーソル矩形（画面ピクセル座標）がシステムに通知される。UIAではUIA_Text_TextSelectionChangedEventIdが発火される。

**期待されるアクション**：スクリーンリーダーがカーソル位置の変更を検知し、現在のカーソル位置のテキストをユーザーに読み上げる。

## 通知種別

アクセシビリティ通知（MSAA EVENT_CONSOLE_CARET / UIA UIA_Text_TextSelectionChangedEventId）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（タイマーベースのデバウンス後、バックグラウンドスレッドで発火） |
| 優先度 | 中 |
| リトライ | 無し |

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

システムに登録された全てのアクセシビリティクライアントに対してブロードキャストされる。`NotifyWinEvent`APIおよび`UiaRaiseAutomationEvent`APIを通じてOSレベルで配信される。

## 通知テンプレート

### イベント情報

| 項目 | 内容 |
|-----|------|
| MSAA Event ID | EVENT_CONSOLE_CARET |
| MSAA flags | CONSOLE_CARET_SELECTION（選択中）/ CONSOLE_CARET_VISIBLE（通常） |
| MSAA lParam | MAKELONG(caretX, caretY)（バッファ座標） |
| UIA Event ID | UIA_Text_TextSelectionChangedEventId |
| ConsoleControl | ConsoleSetCaretInfo（カーソル矩形情報） |

### 本文テンプレート

```
（テキストメッセージは含まない。構造化されたイベントデータのみ。）
```

### 添付ファイル

該当なし。

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| position | カーソル位置（バッファ座標） | til::point | Yes |
| activeSelection | 選択操作中かどうか | bool | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| システム内部 | カーソル位置変更 | MSAAまたはUIAが有効 | テキスト出力、カーソル移動コマンド等 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| MSAAとUIAの両方が無効 | `_msaaEnabled == false`かつ`_uiaProvider == nullptr`の場合 |
| タイマーデバウンス期間中 | 既にタイマーがスケジュール済みの場合は状態が蓄積される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[カーソル位置変更] --> B[AccessibilityNotifier::CursorChanged]
    B --> C{MSAAまたはUIA有効?}
    C -->|No| D[スキップ]
    C -->|Yes| E[排他ロック取得]
    E --> F[_stateにカーソル情報を記録]
    F --> G{UIAプロバイダあり?}
    G -->|Yes| H[textSelectionChanged=true記録]
    G -->|No| I[_timerSet呼び出し]
    H --> I
    I --> J{_delay設定あり?}
    J -->|No| K[即座に_emitEvents実行]
    J -->|Yes| L{タイマースケジュール済み?}
    L -->|Yes| M[バッチに蓄積]
    L -->|No| N[SetThreadpoolTimerExでタイマー設定]
    N --> O[タイマー発火]
    O --> P[_timerEmitMSAA]
    P --> Q[_stateをコピーしてリセット]
    Q --> R[_emitEvents]
    R --> S[NotifyWinEvent EVENT_CONSOLE_CARET]
    R --> T[ConsoleControl ConsoleSetCaretInfo]
    R --> U[UiaRaiseAutomationEvent TextSelectionChanged]
```

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

### 参照テーブル一覧

該当なし。本通知はメモリ上のテキストバッファ情報のみを参照する。

### 更新テーブル一覧

該当なし。

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| NotifyWinEvent失敗 | システムエラー | エラーは無視される（高速パス） |
| UiaRaiseAutomationEvent失敗 | UIAプロバイダのエラー | `LOG_IF_FAILED`でログ記録 |
| ConsoleControl失敗 | カーネルエラー | エラーは伝播するが処理は継続 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| デバウンス | タイマーベースのデバウンス。遅延値はInitialize()時に設定されるmsaaDelay/uiaDelayパラメータで制御 |
| バッチ処理 | 複数のカーソル変更イベントが蓄積され、タイマー発火時にまとめて処理される |

### 配信時間帯

制限なし。

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

カーソル位置情報はアクセシビリティAPIを通じてシステム全体に公開される。この情報を悪用してキー入力を推測する攻撃（カーソルトラッキング攻撃）が理論的には可能であるが、これはOSレベルのアクセシビリティAPIの一般的な制約である。

## 備考

- `_emitEvents`内でコンソールロックが取得される（カーソル矩形の計算に必要）。パフォーマンス上の理由から、イベントのコピーとロックの取得は分離されている。
- MSAAのCONSOLE_CARET_SELECTIONとCONSOLE_CARET_VISIBLEはビットフラグとして設計されているが、実際のコード（Microsoftの公式例を含む）では列挙として扱われている。カーソルは常にVISIBLEとして報告される。
- ScrollBufferイベントが発生した場合、既にプライムされたカーソル位置のY座標がスクロールデルタ分だけ調整される（AccessibilityNotifier.cpp 行262-263）。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AccessibilityNotifier.h | `src/host/AccessibilityNotifier.h` | 行13-99: AccessibilityNotifier構造体全体。特にState構造体（行41-68）でeventConsoleCaretの各フィールドを理解する |

**読解のコツ**: State構造体は`memset(0)`で初期化可能な設計。各イベントの「primed」フラグが発火判定に使われる。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | 行138-172: `CursorChanged`メソッド。positionとactiveSelectionを受け取り、_stateに記録する |

**主要処理フロー**:
1. **行149**: UIAプロバイダの有効性チェック
2. **行153-163**: MSAAが有効な場合、カーソル位置と選択状態を_stateに記録
3. **行165-168**: UIAが有効な場合、textSelectionChangedをtrueに設定
4. **行170**: `_timerSet()`でタイマー処理を開始

#### Step 3: タイマー・デバウンス機構を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | 行329-340: `_timerSet`メソッド。_delay有無でインライン発火 or タイマー設定 |
| 3-2 | AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | 行342-369: `_timerEmitMSAA`コールバック。_stateをコピーしてリセット後、_emitEventsを呼び出す |

#### Step 4: イベント発火処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | 行371-543: `_emitEvents`メソッド。カーソル情報の準備（行428-457）とイベント発火（行497-518） |

**主要処理フロー**:
- **行428-457**: カーソル位置からスクリーン座標への変換（ConsoleSetCaretInfo用）
- **行497-518**: EVENT_CONSOLE_CARETの発火、flags判定（CONSOLE_CARET_SELECTION/CONSOLE_CARET_VISIBLE）
- **行520-524**: UIA_Text_TextSelectionChangedEventIdの発火

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

```
AccessibilityNotifier::CursorChanged (AccessibilityNotifier.cpp:147)
    |
    +-- _state更新（排他ロック下）
    |       +-- eventConsoleCaretPositionX/Y 設定
    |       +-- eventConsoleCaretSelecting 設定
    |       +-- eventConsoleCaretPrimed = true
    |       +-- textSelectionChanged = true（UIA有効時）
    |
    +-- _timerSet()
            |
            +-- [_delay==nullptr] _emitEvents(_state) 即座に実行
            +-- [_delay!=nullptr] SetThreadpoolTimerEx() でスケジュール
                    |
                    +-- _timerEmitMSAA (バックグラウンドスレッド)
                            |
                            +-- _state コピー & リセット
                            +-- _emitEvents(state)
                                    |
                                    +-- ConsoleControl(ConsoleSetCaretInfo)
                                    +-- NotifyWinEvent(EVENT_CONSOLE_CARET)
                                    +-- UiaRaiseAutomationEvent(TextSelectionChanged)
```

### データフロー図

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

カーソル移動           AccessibilityNotifier
(position, selection)   CursorChanged()
        |                    |
        v               _state更新                    [MSAA]
    til::point          (排他ロック下)                 EVENT_CONSOLE_CARET
    bool                     |                        + ConsoleSetCaretInfo
                        _timerSet()
                             |                        [UIA]
                        _emitEvents()                 UIA_Text_TextSelectionChangedEventId
                             |
                        コンソールロック取得
                        バッファ座標 -> スクリーン座標変換
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AccessibilityNotifier.h | `src/host/AccessibilityNotifier.h` | ヘッダー | AccessibilityNotifier構造体・State定義 |
| AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | ソース | CursorChanged・_emitEvents実装 |
| globals.h | `src/host/globals.h` | ヘッダー | AccessibilityNotifierインスタンスの保持 |
