# 通知設計書 60-NotifyNewOutput

## 概要

本ドキュメントは、Windows Terminal における NotifyNewOutput イベント通知の設計仕様を記述する。ターミナルに新しい出力が発生した際にUIオートメーション（UIA）を通じてスクリーンリーダー等の支援技術に通知するためのUIA通知である。

### 本通知の処理概要

NotifyNewOutput は、IUiaEventDispatcher インターフェースで定義された仮想メソッドであり、ターミナルバッファに新しい出力が書き込まれた際に呼び出される。UiaRenderer がレンダリングパイプラインの EndPaint フェーズで蓄積された出力テキストを、InteractivityAutomationPeer と TermControlAutomationPeer を経由してUIオートメーションの通知イベント（RaiseNotificationEvent）として発火する。これにより、NVDA や Narrator 等のスクリーンリーダーがターミナル出力をリアルタイムで読み上げることが可能となる。

**業務上の目的・背景**：アクセシビリティ対応として、視覚障害のあるユーザーがターミナル出力をスクリーンリーダーで確認できるようにする仕組みである。ターミナル出力はリアルタイムに更新されるため、新しい出力が発生するたびにスクリーンリーダーに通知することが不可欠である。ただし、ユーザーのキー入力のエコーバック（入力した文字がそのまま表示される出力）は抑制し、ノイズを減らす最適化が施されている。

**通知の送信タイミング**：UiaRenderer::EndPaint メソッド内で、レンダリングサイクルの完了時に _queuedOutput の内容がまとめて通知される。出力テキストは SAPI（Speech API）の制限に従って1000文字ずつに分割されて送信される。

**通知の受信者**：InteractivityAutomationPeer が IUiaEventDispatcher の実装として受信し、NewOutput イベントを発火する。TermControlAutomationPeer がこの NewOutput イベントを購読し、最終的に UIオートメーションの RaiseNotificationEvent を呼び出す。

**通知内容の概要**：新しく出力されたテキスト（std::wstring_view）が渡される。1000文字を超える出力は分割して送信される。

**期待されるアクション**：TermControlAutomationPeer がテキストをサニタイズし、キー入力のエコーバックを除去した上で、RaiseNotificationEvent（AutomationNotificationKind::ActionCompleted）を呼び出す。スクリーンリーダーはこの通知を受けてテキストを読み上げる。

## 通知種別

UIオートメーション通知（UIA RaiseNotificationEvent）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（UIスレッドへのディスパッチ） |
| 優先度 | 中 |
| リトライ | 無 |

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

UiaRenderer が IUiaEventDispatcher インターフェースを通じて InteractivityAutomationPeer に通知する。InteractivityAutomationPeer は NewOutput イベントを発火し、TermControlAutomationPeer が購読する。

## 通知テンプレート

### イベント引数

| 項目 | 内容 |
|-----|------|
| インターフェース | `IUiaEventDispatcher::NotifyNewOutput(std::wstring_view newOutput)` |
| 定義元 | `IUiaEventDispatcher.h` L27 |
| プロパティ | newOutput (std::wstring_view) |

### イベントデータ構造

```cpp
// IUiaEventDispatcher.h
class IUiaEventDispatcher
{
public:
    virtual ~IUiaEventDispatcher() = default;
    virtual void SignalSelectionChanged() = 0;
    virtual void SignalTextChanged() = 0;
    virtual void SignalCursorChanged() = 0;
    virtual void NotifyNewOutput(std::wstring_view newOutput) = 0;
};
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| newOutput | 新しく出力されたテキスト | UiaRenderer._queuedOutput | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| レンダリングサイクル | UiaRenderer::EndPaint | _queuedOutput が空でない | UiaRenderer.cpp L250-258: EndPaint 内で蓄積出力を1000文字ずつ送信 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 出力テキストが空 | _queuedOutput が空の場合は NotifyNewOutput は呼ばれない（ただしループが0回回転するだけ） |
| テキストが読み取り不可 | TermControlAutomationPeer::IsReadable チェックにより、制御文字のみの出力は抑制される |
| キー入力エコーバック | TermControlAutomationPeer でキー入力と一致する出力文字を除去し、残りがない場合は通知抑制 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ターミナル出力] --> B[UiaRenderer::_queuedOutput に蓄積]
    B --> C[UiaRenderer::EndPaint]
    C --> D{_queuedOutput 空?}
    D -->|空でない| E[1000文字ずつ分割]
    D -->|空| F[スキップ]
    E --> G[_dispatcher->NotifyNewOutput]
    G --> H[InteractivityAutomationPeer::NotifyNewOutput]
    H --> I[NewOutput.raise hstring newOutput]
    I --> J[TermControlAutomationPeer::NotifyNewOutput]
    J --> K[Sanitize テキスト]
    K --> L[キー入力エコーバック除去]
    L --> M{IsReadable?}
    M -->|読み取り可能| N[co_await Dispatcher RunAsync]
    M -->|読み取り不可| O[return]
    N --> P[RaiseNotificationEvent]
```

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

### 参照テーブル一覧

該当なし（データベースは使用しない）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ディスパッチャー未取得 | UIスレッドが利用不可 | TermControlAutomationPeer::NotifyNewOutput で null チェック（L244-248） |
| 通知イベント失敗 | UIA プロバイダ接続切断 | try-catch CATCH_LOG() で例外をログ出力（L266） |
| SAPI制限超過 | 出力テキストが1000文字超 | 1000文字ずつに分割して送信（L253-258） |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1レンダリングサイクルあたり | 1回（EndPaint で蓄積出力をまとめて送信） |
| 1通知あたり最大文字数 | 1000文字（SAPI 制限） |

### 配信時間帯

制限なし

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

- ターミナル出力にはパスワードなどの機密情報が含まれる場合がある。スクリーンリーダーがこれらを読み上げることに対する対策はターミナルアプリケーション側では行われない
- UIオートメーション通知は他のプロセスからも受信可能であるため、機密情報の漏洩に注意が必要

## 備考

- NotifyNewOutput は IUiaEventDispatcher の仮想メソッドとして定義されており、他の UIA 通知（SignalSelectionChanged、SignalTextChanged、SignalCursorChanged）と同じインターフェースに属する
- SAPI の 1000文字制限（sapiLimit）は UiaRenderer.cpp L253 で定義されている。これは音声合成 API の制約に基づくもの
- TermControlAutomationPeer のキー入力エコーバック除去ロジック（L214-235）は、_keyEvents キューと出力テキストを文字単位で比較し、一致する先頭文字を除去する最適化である
- RaiseNotificationEvent は AutomationNotificationProcessing::All を使用しており（L262）、キーボードイベントによって中断可能である
- AutomationNotificationKind::ActionCompleted と L"TerminalTextOutput" タグで通知される
- NVDA 等のスクリーンリーダーとの互換性のため、RunAsync(...).get() は使用しない（L253 のコメント参照）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | IUiaEventDispatcher.h | `src/types/IUiaEventDispatcher.h` | L20-28: IUiaEventDispatcher インターフェース全体の定義。4つの仮想メソッド（SignalSelectionChanged, SignalTextChanged, SignalCursorChanged, NotifyNewOutput） |

**読解のコツ**: IUiaEventDispatcher は純粋仮想クラス（インターフェース）であり、実装は InteractivityAutomationPeer が担当する。std::wstring_view で出力テキストを受け取る。

#### Step 2: 送信元（UiaRenderer）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | UiaRenderer.cpp | `src/renderer/uia/UiaRenderer.cpp` | L248-260: EndPaint 内の NotifyNewOutput 呼び出し。sapiLimit=1000 での分割処理 |

**主要処理フロー**:
1. **L253**: `static constexpr size_t sapiLimit{ 1000 }` で SAPI 制限定義
2. **L254**: `_queuedOutput` から出力テキストを取得
3. **L255-258**: 1000文字ずつに分割してループで `_dispatcher->NotifyNewOutput()` を呼び出し
4. **L266**: `_queuedOutput.clear()` で蓄積出力をクリア

#### Step 3: InteractivityAutomationPeer を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | InteractivityAutomationPeer.cpp | `src/cascadia/TerminalControl/InteractivityAutomationPeer.cpp` | L100-103: NotifyNewOutput 実装。NewOutput イベントを発火 |

#### Step 4: TermControlAutomationPeer を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | TermControlAutomationPeer.cpp | `src/cascadia/TerminalControl/TermControlAutomationPeer.cpp` | L84: NewOutput イベントの購読（コンストラクタ内） |
| 4-2 | TermControlAutomationPeer.cpp | `src/cascadia/TerminalControl/TermControlAutomationPeer.cpp` | L209-270: NotifyNewOutput 実装の全体。Sanitize、キー入力エコーバック除去、IsReadable チェック、Dispatcher.RunAsync、RaiseNotificationEvent |

**主要処理フロー**:
1. **L211**: `Sanitize(newOutput)` でテキストをサニタイズ
2. **L215-235**: キー入力エコーバック除去ロジック。_keyEvents キューと出力を比較
3. **L239-241**: `IsReadable(sanitized)` チェック。読み取り不可なら return
4. **L244-248**: Dispatcher null チェック
5. **L254-264**: `RunAsync` でUIスレッドに切り替え、`RaiseNotificationEvent` を呼び出し

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

```
UiaRenderer::EndPaint
    |
    +-- _queuedOutput から出力テキスト取得
    +-- 1000文字ずつ分割ループ
    +-- _dispatcher->NotifyNewOutput(output.substr(...))
            |
            v
InteractivityAutomationPeer::NotifyNewOutput
    |
    +-- NewOutput.raise(*this, hstring{ newOutput })
            |
            v
TermControlAutomationPeer::NotifyNewOutput (lambda)
    |
    +-- Sanitize(newOutput)
    +-- キー入力エコーバック除去
    +-- IsReadable チェック
    +-- Dispatcher().RunAsync(...)
            |
            v
    RaiseNotificationEvent(
        ActionCompleted,
        All,
        sanitizedCopy,
        L"TerminalTextOutput"
    )
```

### データフロー図

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

ターミナル出力 -------> UiaRenderer                   スクリーンリーダー
(テキストバッファ)      _queuedOutput 蓄積            (NVDA/Narrator等)
                              |                            ^
                              v                            |
                        EndPaint                           |
                        (1000文字分割)                      |
                              |                            |
                              v                            |
                        InteractivityAutomationPeer        |
                              |                            |
                              v                            |
                        TermControlAutomationPeer          |
                        (Sanitize, エコー除去)              |
                              |                            |
                              v                            |
                        RaiseNotificationEvent ----------->|
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| IUiaEventDispatcher.h | `src/types/IUiaEventDispatcher.h` | ヘッダ | IUiaEventDispatcher インターフェース定義 |
| UiaRenderer.cpp | `src/renderer/uia/UiaRenderer.cpp` | ソース | EndPaint での NotifyNewOutput 呼び出し |
| InteractivityAutomationPeer.cpp | `src/cascadia/TerminalControl/InteractivityAutomationPeer.cpp` | ソース | NotifyNewOutput 実装・NewOutput イベント発火 |
| InteractivityAutomationPeer.h | `src/cascadia/TerminalControl/InteractivityAutomationPeer.h` | ヘッダ | クラス宣言 |
| TermControlAutomationPeer.cpp | `src/cascadia/TerminalControl/TermControlAutomationPeer.cpp` | ソース | 最終処理（サニタイズ・RaiseNotificationEvent） |
| TermControlAutomationPeer.h | `src/cascadia/TerminalControl/TermControlAutomationPeer.h` | ヘッダ | クラス宣言 |
| HwndTerminalAutomationPeer.cpp | `src/cascadia/TerminalControl/HwndTerminalAutomationPeer.cpp` | ソース | HWND ベースの AutomationPeer 実装 |
