wintf 0.0.3

Windows Tategaki Framework - Rust UI library with Japanese vertical text support
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle};
use windows::Win32::System::Threading::{
    GetCurrentProcess, GetCurrentThread, GetCurrentThreadId, GetProcessTimes, GetThreadTimes,
    THREAD_QUERY_LIMITED_INFORMATION,
};
use windows::Win32::{Foundation::*, UI::WindowsAndMessaging::*};
use windows::core::*;

/// GetWindowLongPtrWのラッパー
#[inline(always)]
pub(crate) fn get_window_long_ptr(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> Result<isize> {
    unsafe {
        SetLastError(ERROR_SUCCESS);
        let res = GetWindowLongPtrW(hwnd, index);
        let err = Error::from_thread();
        if err.code() != S_OK {
            return Err(err);
        }
        Ok(res)
    }
}

/// SetWindowLongPtrWのラッパー
#[inline(always)]
pub(crate) fn set_window_long_ptr(
    hwnd: HWND,
    index: WINDOW_LONG_PTR_INDEX,
    value: isize,
) -> Result<isize> {
    unsafe {
        SetLastError(ERROR_SUCCESS);
        let res = SetWindowLongPtrW(hwnd, index, value);
        if res == 0 {
            let err = Error::from_thread();
            if err.code() != S_OK {
                return Err(err);
            }
        }
        Ok(res)
    }
}

/// GetWindow で z オーダー上の隣接窓を得る共通実装。
///
/// 隣接窓が無いこと(z オーダーの端に居る)は**失敗ではなく正常な不在**であり、
/// `Ok(None)` として返す。`windows` crate の `GetWindow` は戻り値 HWND が NULL の
/// 時点で一律 `Err(Error::from_thread())` にするため、そのままでは不在と真の失敗を
/// 区別できない。呼び出し前に `SetLastError(ERROR_SUCCESS)` でスレッドの残留エラーを
/// 消し、`Err` のコードが `S_OK`(=API がエラーを立てていない=隣が無いだけ)なら
/// `Ok(None)`、それ以外は失敗として `Err` を呼び出し側へ返す(握り潰さない)。
#[inline(always)]
fn get_window_relative(hwnd: HWND, ucmd: GET_WINDOW_CMD) -> Result<Option<HWND>> {
    unsafe {
        SetLastError(ERROR_SUCCESS);
        match GetWindow(hwnd, ucmd) {
            Ok(found) => Ok(Some(found)),
            Err(err) if err.code() == S_OK => Ok(None),
            Err(err) => Err(err),
        }
    }
}

/// 指定窓の 1 つ手前(z オーダー上でより前面側)にある窓を返す。
///
/// Win32 の z オーダー語彙では `GW_HWNDPREV` が「前面側の隣」を、`GW_HWNDNEXT` が
/// 「背面側の隣」を指す("PREV/NEXT" は列挙順であって前後関係の直感とは逆になる)。
/// 「バルーンはキャラのすぐ手前」という本フィーチャーの不変条件判定はこの対応関係の
/// 上に立つため、名前と定数の対応をここで固定する。
///
/// 最前面でこれ以上手前が無い場合は `Ok(None)`。失敗は `Err` で返し、記録は呼び出し側が行う。
#[inline(always)]
pub(crate) fn get_window_above(hwnd: HWND) -> Result<Option<HWND>> {
    get_window_relative(hwnd, GW_HWNDPREV)
}

/// 指定窓の 1 つ背後(z オーダー上でより背面側)にある窓を返す。
///
/// 対応関係は [`get_window_above`] の doc を参照(背面側=`GW_HWNDNEXT`)。
/// 最背面でこれ以上背後が無い場合は `Ok(None)`。失敗は `Err` で返す。
#[inline(always)]
pub(crate) fn get_window_below(hwnd: HWND) -> Result<Option<HWND>> {
    get_window_relative(hwnd, GW_HWNDNEXT)
}

/// 現在の前面窓(フォアグラウンドウィンドウ)を返す。
///
/// 前面窓が無い状態——他プロセスが前面を手放した直後や、前面窓が別のデスクトップに居る場合
/// ——では OS が NULL を返す。これは**失敗ではなく正常な不在**であり `None` になる。
/// `Result` で包まないのは、`GetForegroundWindow` が失敗そのものを持たない API だからである
/// (不在と失敗の区別が存在しないため、`Err` を作ると呼び出し側が偽の失敗を見ることになる)。
///
/// 読み取り専用であり、前面窓を変えることは決してない(要件 4.1/4.3)。
#[inline(always)]
pub(crate) fn get_foreground_window() -> Option<HWND> {
    // SAFETY: Win32 境界。前面窓の識別子を読むだけで、窓の状態は一切変えない。
    let hwnd = unsafe { GetForegroundWindow() };
    if hwnd.is_invalid() { None } else { Some(hwnd) }
}

/// 指定窓が可視かを返す(`IsWindowVisible`)。
///
/// 「可視」は `WS_VISIBLE` が当該窓**および祖先のすべて**に立っていることを意味する
/// (親が隠れていれば子は可視ではない)。窓が画面外に居るか、他の窓に完全に覆われているか、
/// 幅も高さも 0 かは問わない——**表示状態のスタイルだけ**の判定である。
///
/// `Result` で包まないのは、`GetForegroundWindow` と同じく失敗そのものを持たない API だから
/// である。無効なハンドルに対しても OS は「可視ではない」(`FALSE`)を返すのであり、
/// ここで `Err` を作ると呼び出し側が偽の失敗を見ることになる。
///
/// 読み取り専用であり、窓の表示状態を変えることは決してない。
#[inline(always)]
pub(crate) fn is_window_visible(hwnd: HWND) -> bool {
    // SAFETY: Win32 境界。表示状態のスタイルを読むだけで、窓の状態は一切変えない。
    unsafe { IsWindowVisible(hwnd) }.as_bool()
}

/// 指定窓が**常時最前面の帯**(`WS_EX_TOPMOST`)に居るかを返す。
///
/// Windows はトップレベル窓を 2 つの帯に分けて重ねる——常時最前面の一群が、そうでない
/// 一群より必ず手前に来る。`SetWindowPos` の `hWndInsertAfter` に帯の中の窓を渡すと、
/// 対象窓自身にも `WS_EX_TOPMOST` が**付いて帯へ移る**(実測で確定した OS の性質)。
/// ゴースト窓を常時最前面にしない(要件 8.1)ためには、挿入位置に選ぼうとしている窓が
/// 帯の中に居るかを**指令を出す前に**知る必要があり、その読み取りが本関数である。
///
/// `is_window_visible` と違って `Result` を返すのは、`GetWindowLongPtrW` が失敗しうる
/// (窓が消えた等)一方で、失敗を `false` へ潰すと「帯に居ない」と読めてしまい、
/// 要件 8.1 を毀損する側へ倒れるためである。倒し方は呼び出し側が決める。
#[inline(always)]
pub(crate) fn is_window_always_on_top(hwnd: HWND) -> Result<bool> {
    let ex_style = get_window_long_ptr(hwnd, GWL_EXSTYLE)?;
    Ok((ex_style as u32) & WS_EX_TOPMOST.0 != 0)
}

/// 指定窓の owner を `owner` に設定する(`SetWindowLongPtrW(GWLP_HWNDPARENT)`)。
///
/// トップレベル窓に対する `GWLP_HWNDPARENT` の書込は親子化(`SetParent`)ではなく
/// **owner の付け替え**である。owner を張ると OS が owner 窓の浮上にあわせて被 owner 窓を
/// 前面へ連れて上がる(要件 1.1 の確立手段)。
/// 失敗は握り潰さず `Err` で返す(記録は呼び出し側・要件 6.2)。
#[inline(always)]
pub(crate) fn set_window_owner(hwnd: HWND, owner: HWND) -> Result<()> {
    set_window_long_ptr(hwnd, GWLP_HWNDPARENT, owner.0 as isize).map(|_| ())
}

/// 指定窓の owner を外す(`GWLP_HWNDPARENT` へ 0 を書く)。
///
/// owner を張ったままペアの一方を破棄すると OS の破棄カスケードがもう一方を巻き込み、
/// 後発の破棄が無効 HWND を撃つ。破棄に先立って本関数で切り離すことで独立破棄の
/// semantics へ戻し、要件 5.9(破棄が重複しても異常終了しない)を構造的に満たす。
/// 失敗は握り潰さず `Err` で返す。
#[inline(always)]
pub(crate) fn clear_window_owner(hwnd: HWND) -> Result<()> {
    set_window_long_ptr(hwnd, GWLP_HWNDPARENT, 0).map(|_| ())
}

// ============================================================
// CPU 時間の読み取り(スレッド名簿 `ecs::world::thread_registry` と
// スレッド別 CPU 報告器のための Win32 ラッパ)
//
// 呼び出せる API は既存 feature(`Win32_Foundation`・`Win32_System_Threading`)の
// 範囲に限る。スレッドの**列挙**(ToolHelp)は feature が無く `Cargo.toml` 非接触の
// 制約(要件 8.6)と両立しないため使わない——列挙の代わりに、生成側が自分を名簿へ
// 登録する。
// ============================================================

/// CPU 時間(カーネル/ユーザーの 2 本立て・単位は 100ns)。
///
/// Win32 の `GetThreadTimes`/`GetProcessTimes` が返す `FILETIME` は「1601 年からの
/// 絶対時刻」ではなく**経過した CPU 時間そのもの**(100ns 単位の量)である。生成時刻と
/// 終了時刻は本構造体に持たない——負荷の帰属に使うのはカーネルとユーザーの 2 本だけで、
/// 持たない値は誤用されないため。
///
/// 壁時計(経過時間)とは別物である(要件 2.6)。待ち時間はここに現れない。
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CpuTimes {
    /// カーネルモードで消費した CPU 時間(100ns 単位)。
    pub kernel_100ns: u64,
    /// ユーザーモードで消費した CPU 時間(100ns 単位)。
    pub user_100ns: u64,
}

impl CpuTimes {
    /// カーネルとユーザーの合計(100ns 単位)。
    pub const fn total_100ns(&self) -> u64 {
        self.kernel_100ns + self.user_100ns
    }

    /// カーネルモードの CPU 時間(µs・切り捨て)。
    pub const fn kernel_us(&self) -> u64 {
        self.kernel_100ns / 10
    }

    /// ユーザーモードの CPU 時間(µs・切り捨て)。
    pub const fn user_us(&self) -> u64 {
        self.user_100ns / 10
    }

    /// 合計の CPU 時間(µs・切り捨て)。
    pub const fn total_us(&self) -> u64 {
        self.total_100ns() / 10
    }
}

/// `FILETIME`(上位 32bit + 下位 32bit)を 100ns 単位の u64 へ畳む。
#[inline(always)]
const fn filetime_to_100ns(ft: FILETIME) -> u64 {
    ((ft.dwHighDateTime as u64) << 32) | ft.dwLowDateTime as u64
}

/// 呼び出しスレッドのスレッド ID(TID)を返す。
///
/// 失敗を持たない API であり `Result` では包まない。
#[inline(always)]
pub fn get_current_thread_id() -> u32 {
    // SAFETY: Win32 境界。自スレッドの識別子を読むだけで、状態は一切変えない。
    unsafe { GetCurrentThreadId() }
}

/// 呼び出しスレッドのハンドルを**複製して所有権つきで**返す。
///
/// `GetCurrentThread` が返すのは擬似ハンドル(「今このコードを走らせているスレッド」を
/// 指す定数)であり、他スレッドへ渡すと**渡した先の**スレッドを指してしまう。名簿は
/// 別スレッドから読むので、生成側で `DuplicateHandle` して実ハンドルへ変換する必要が
/// ある。複製したハンドルはスレッドが終了した後も有効で、終了時点までの CPU 時間を
/// 読み出せる。
///
/// 権限は `THREAD_QUERY_LIMITED_INFORMATION` だけを与える(`GetThreadTimes` にはこれで
/// 足りる)。所有権は [`OwnedHandle`] が持ち、drop で `CloseHandle` される。
///
/// 失敗は握り潰さず `Err` で返す。
pub fn duplicate_current_thread_handle() -> Result<OwnedHandle> {
    // SAFETY: Win32 境界。自プロセス内で自スレッドのハンドルを複製するだけで、
    // スレッドの実行状態は一切変えない。複製先の生ハンドルは直後に OwnedHandle へ
    // 委ね、以後の解放は Drop が行う(複製の成功時のみ from_raw_handle へ渡す)。
    unsafe {
        let process = GetCurrentProcess();
        let mut duplicated = HANDLE::default();
        DuplicateHandle(
            process,
            GetCurrentThread(),
            process,
            &mut duplicated,
            THREAD_QUERY_LIMITED_INFORMATION.0,
            false,
            DUPLICATE_HANDLE_OPTIONS(0),
        )?;
        Ok(OwnedHandle::from_raw_handle(duplicated.0 as RawHandle))
    }
}

/// 指定スレッドの CPU 時間を返す(`GetThreadTimes`)。
///
/// 引数は [`duplicate_current_thread_handle`] が返す所有権つきハンドル——擬似ハンドルや
/// 出所不明の生ハンドルを渡せない形にしてある。終了済みのスレッドでも、ハンドルが開いて
/// いる限り最終値を読める。
///
/// 失敗は握り潰さず `Err` で返す。
pub fn get_thread_times(thread: &OwnedHandle) -> Result<CpuTimes> {
    let handle = HANDLE(thread.as_raw_handle() as _);
    let mut creation = FILETIME::default();
    let mut exit = FILETIME::default();
    let mut kernel = FILETIME::default();
    let mut user = FILETIME::default();
    // SAFETY: Win32 境界。読み取り専用で、4 つの出力先はいずれもスタック上の実体。
    unsafe {
        GetThreadTimes(handle, &mut creation, &mut exit, &mut kernel, &mut user)?;
    }
    Ok(CpuTimes {
        kernel_100ns: filetime_to_100ns(kernel),
        user_100ns: filetime_to_100ns(user),
    })
}

/// 自プロセス全体の CPU 時間を返す(`GetProcessTimes`)。
///
/// 名簿に載っているスレッドの合計との差が「名簿外の残り」(タスクプール等)であり、
/// 報告器はその差を 1 行で出す(`unregistered_rest`)。
///
/// 失敗は握り潰さず `Err` で返す。
pub fn get_process_times() -> Result<CpuTimes> {
    let mut creation = FILETIME::default();
    let mut exit = FILETIME::default();
    let mut kernel = FILETIME::default();
    let mut user = FILETIME::default();
    // SAFETY: Win32 境界。自プロセスの擬似ハンドルに対する読み取り専用の呼び出し。
    unsafe {
        GetProcessTimes(
            GetCurrentProcess(),
            &mut creation,
            &mut exit,
            &mut kernel,
            &mut user,
        )?;
    }
    Ok(CpuTimes {
        kernel_100ns: filetime_to_100ns(kernel),
        user_100ns: filetime_to_100ns(user),
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    // set_window_long_ptr の成功経路は自プロセス所有の実在 HWND を要するため GUI 非依存の
    // ユニットテストでは検証不能(統合的な検証はウィンドウ生成を伴う examples / 実機動作に
    // 委ねる)。get_window_long_ptr の成功経路はウィンドウ生成不要の GetDesktopWindow()
    // (常に実在する読み取り可能な HWND)で検証する。null HWND による決定的なエラー経路
    // (SetLastError → Error::from_thread のエラー変換ロジック)も併せて固定する。

    #[test]
    fn get_window_long_ptr_with_null_hwnd_returns_invalid_window_handle() {
        let err = get_window_long_ptr(HWND::default(), GWL_STYLE).unwrap_err();
        assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
    }

    #[test]
    fn set_window_long_ptr_with_null_hwnd_returns_invalid_window_handle() {
        let err = set_window_long_ptr(HWND::default(), GWL_EXSTYLE, 0).unwrap_err();
        assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
    }

    #[test]
    fn get_window_long_ptr_succeeds_on_desktop_window() {
        // GetDesktopWindow はウィンドウ生成不要で常に有効な HWND を返す(GUI 非依存)。
        // デスクトップウィンドウは常に WS_VISIBLE を持つためスタイル値は非 0。
        let hwnd = unsafe { GetDesktopWindow() };
        let style =
            get_window_long_ptr(hwnd, GWL_STYLE).expect("desktop window style must be readable");
        assert_ne!(style, 0);
    }

    #[test]
    fn get_window_long_ptr_clears_stale_last_error_before_call() {
        // 特性化: ラッパー冒頭の SetLastError(ERROR_SUCCESS) により、呼び出し前に
        // スレッドへ残留していた無関係なエラーコードが成功判定を汚染しない。
        // (このクリアがなければ残留エラーが Err として誤報告される)
        let hwnd = unsafe { GetDesktopWindow() };
        unsafe { SetLastError(ERROR_ACCESS_DENIED) };
        let res = get_window_long_ptr(hwnd, GWL_STYLE);
        assert!(res.is_ok());
    }

    // ---- z オーダー/owner ラッパー -------------------------------------------------
    //
    // これらの成功経路は「2 つの実窓の前後関係」そのものが検証対象であり、
    // GetDesktopWindow() のような単独 HWND では前後関係を作れない。したがって
    // 自プロセス所有の実窓を 2 枚生成し、SetWindowPos で既知の重なり順を作ってから
    // 実測する(本リポジトリでは実窓を素の #[test] 内で生成する方式が既定・
    // `ecs/clickthrough/controller_tests.rs` に先例)。
    //
    // ただし `GetWindow(GW_HWNDPREV/GW_HWNDNEXT)` が辿るのは**同じ親を持つ兄弟の列**で
    // あり、トップレベル窓の兄弟列はデスクトップ全体で共有される。同一バイナリの他テスト
    // (clickthrough など)や他プロセスが窓を生成・移動すると 2 枚の間へ割り込みうるため、
    // トップレベル窓では隣接を決定論的に固定できない(実測で再現する不安定を確認済み)。
    // そこで z オーダーの検証はテスト専用の親窓を 1 枚立て、その**子窓 2 枚**で行う。
    // 兄弟列がこの 2 枚だけに閉じるため、隣接も「隣が無いこと」も決定論的に確定する。

    /// 実所有のテスト HWND(非表示ポップアップ "Static")を生成する。
    /// `ecs/clickthrough/controller_tests.rs` の生成レシピと同一。呼び出し側が破棄する。
    fn create_test_hwnd(title: PCWSTR) -> HWND {
        create_test_window(title, WINDOW_STYLE(WS_POPUP.0), None)
    }

    /// テスト専用親窓の子として非表示の実窓を生成する(兄弟列を閉じるため)。
    fn create_test_child_hwnd(parent: HWND, title: PCWSTR) -> HWND {
        create_test_window(title, WINDOW_STYLE(WS_CHILD.0), Some(parent))
    }

    fn create_test_window(title: PCWSTR, style: WINDOW_STYLE, parent: Option<HWND>) -> HWND {
        use windows::Win32::System::LibraryLoader::GetModuleHandleW;
        // SAFETY: Win32 境界。定義済 "Static" クラスで非表示ウィンドウを生成する。
        unsafe {
            let hinstance = GetModuleHandleW(None).expect("GetModuleHandleW");
            CreateWindowExW(
                WINDOW_EX_STYLE(0),
                w!("Static"),
                title,
                style,
                0,
                0,
                0,
                0,
                parent,
                None,
                Some(hinstance.into()),
                None,
            )
            .expect("CreateWindowExW should create a hidden test window")
        }
    }

    /// テスト HWND を破棄する(後始末)。
    fn destroy_test_hwnd(hwnd: HWND) {
        // SAFETY: Win32 境界。生成した所有ウィンドウを破棄する。
        unsafe {
            let _ = DestroyWindow(hwnd);
        }
    }

    /// `hwnd` を `after` のすぐ背後(1 つ背面側)へ移す。
    ///
    /// `SetWindowPos` の `hWndInsertAfter` は「位置決め対象の 1 つ手前に来る窓」であり、
    /// 対象はその**背面側**へ入る("insert after" は z オーダー列挙上の後ろ=背面)。
    fn insert_after(hwnd: HWND, after: HWND) {
        // SAFETY: Win32 境界。自プロセス所有の実 HWND に対する z オーダーのみの変更。
        unsafe {
            SetWindowPos(
                hwnd,
                Some(after),
                0,
                0,
                0,
                0,
                SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
            )
            .expect("SetWindowPos should reorder self-owned test windows");
        }
    }

    #[test]
    fn get_window_above_and_below_report_real_two_window_zorder() {
        let parent = create_test_hwnd(w!("wintf-zorder-test-parent"));
        // 兄弟列はこの 2 枚だけ。**生成直後の重なりには依存しない**——配置は直後の
        // `insert_after` で明示的に作る。実測すると子窓は**先に作ったものほど手前**であり
        // (新しい子窓は兄弟列の背面側へ入る)、生成順から前後関係を読むと逆になる。
        let first = create_test_child_hwnd(parent, w!("wintf-zorder-test-first"));
        let second = create_test_child_hwnd(parent, w!("wintf-zorder-test-second"));

        // 配置 A: first を手前・second を背後にする(second を first のすぐ背後へ落とす)。
        insert_after(second, first);
        assert_eq!(
            get_window_above(second).expect("GetWindow(GW_HWNDPREV) must succeed"),
            Some(first),
            "配置 A: second の 1 つ手前は first のはず"
        );
        assert_eq!(
            get_window_below(first).expect("GetWindow(GW_HWNDNEXT) must succeed"),
            Some(second),
            "配置 A: first の 1 つ背後は second のはず"
        );
        // 兄弟が 2 枚しか居ないので、最前面/最背面の外側は「隣が無い」。
        // 失敗ではなく正常な不在なので Ok(None) で返る。
        assert_eq!(
            get_window_above(first).expect("GetWindow(GW_HWNDPREV) must succeed"),
            None,
            "配置 A: first より手前には誰も居ないはず"
        );
        assert_eq!(
            get_window_below(second).expect("GetWindow(GW_HWNDNEXT) must succeed"),
            None,
            "配置 A: second より背後には誰も居ないはず"
        );

        // 配置 B(対照): 前後を入れ替えると、両ラッパーの答えも入れ替わる。
        // above/below の対応付け(GW_HWNDPREV=手前/GW_HWNDNEXT=背後)が取り違えられて
        // いれば、配置 A と配置 B のどちらかが必ず落ちる。
        insert_after(first, second);
        assert_eq!(
            get_window_above(first).expect("GetWindow(GW_HWNDPREV) must succeed"),
            Some(second),
            "配置 B: first の 1 つ手前は second のはず"
        );
        assert_eq!(
            get_window_below(second).expect("GetWindow(GW_HWNDNEXT) must succeed"),
            Some(first),
            "配置 B: second の 1 つ背後は first のはず"
        );
        assert_eq!(
            get_window_above(second).expect("GetWindow(GW_HWNDPREV) must succeed"),
            None,
            "配置 B: second より手前には誰も居ないはず"
        );
        assert_eq!(
            get_window_below(first).expect("GetWindow(GW_HWNDNEXT) must succeed"),
            None,
            "配置 B: first より背後には誰も居ないはず"
        );

        destroy_test_hwnd(second);
        destroy_test_hwnd(first);
        destroy_test_hwnd(parent);
    }

    #[test]
    fn get_window_above_and_below_report_absence_as_ok_none() {
        // デスクトップウィンドウは兄弟を持たないため、前後いずれの隣接窓も存在しない。
        // 「隣が無い」は失敗ではなく正常な不在であり Ok(None) で返る。
        // 対照は get_window_above_and_below_report_real_two_window_zorder(隣が在れば
        // Some が返る)と null HWND のエラー経路(真の失敗は Err で返る)の 2 本。
        let desktop = unsafe { GetDesktopWindow() };
        assert_eq!(get_window_above(desktop).unwrap(), None);
        assert_eq!(get_window_below(desktop).unwrap(), None);
    }

    #[test]
    fn get_window_above_with_null_hwnd_returns_invalid_window_handle() {
        let err = get_window_above(HWND::default()).unwrap_err();
        assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
    }

    #[test]
    fn get_window_below_with_null_hwnd_returns_invalid_window_handle() {
        let err = get_window_below(HWND::default()).unwrap_err();
        assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
    }

    #[test]
    fn set_window_owner_with_null_hwnd_returns_invalid_window_handle() {
        let err = set_window_owner(HWND::default(), unsafe { GetDesktopWindow() }).unwrap_err();
        assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
    }

    #[test]
    fn clear_window_owner_with_null_hwnd_returns_invalid_window_handle() {
        let err = clear_window_owner(HWND::default()).unwrap_err();
        assert_eq!(err.code(), ERROR_INVALID_WINDOW_HANDLE.to_hresult());
    }

    #[test]
    fn get_foreground_window_returns_absence_or_a_real_window() {
        // 何が前面に居るかはテスト環境で決まらない(他プロセスの窓が前面なのが普通)。
        // 決定論的に主張できるのは「不在(None)か、実在する窓のいずれかである」ことだけで
        // ある——NULL を実在ハンドルとして返してしまう取り違えはここで落ちる。
        // 値そのものを使った判定の網羅は純関数 `is_behind_foreground` 側で行う。
        match get_foreground_window() {
            None => {}
            Some(hwnd) => {
                assert!(
                    !hwnd.is_invalid(),
                    "不在は None であって NULL ハンドルではない"
                );
                assert!(
                    unsafe { IsWindow(Some(hwnd)) }.as_bool(),
                    "返るのは実在する窓のはず"
                );
            }
        }
    }

    #[test]
    fn is_window_visible_reads_the_style_and_the_whole_ancestor_chain() {
        // 「可視」は当該窓と**祖先すべて**の WS_VISIBLE で決まる。隣接の実測はこの判定で
        // 窓を読み飛ばすため、3 通り(親が隠れている/自分に WS_VISIBLE が無い/両方立つ)が
        // それぞれ別の答えになることを固定する。
        let parent = create_test_hwnd(w!("wintf-visible-test-parent"));
        let shown = create_test_window(
            w!("wintf-visible-test-shown"),
            WINDOW_STYLE(WS_CHILD.0 | WS_VISIBLE.0),
            Some(parent),
        );
        let hidden = create_test_child_hwnd(parent, w!("wintf-visible-test-hidden"));

        // 親が隠れている間は、WS_VISIBLE を持つ子でも可視ではない。
        assert!(!is_window_visible(parent), "親窓はまだ表示していない");
        assert!(
            !is_window_visible(shown),
            "祖先が隠れている子は WS_VISIBLE を持っていても可視ではない"
        );

        // SAFETY: Win32 境界。自プロセス所有の窓を活性化せずに表示する。
        unsafe {
            let _ = ShowWindow(parent, SW_SHOWNA);
        }
        assert!(is_window_visible(parent), "表示した親窓は可視");
        assert!(
            is_window_visible(shown),
            "親が可視になれば WS_VISIBLE の子も可視になる"
        );
        // 対照: 同じ親の下でも WS_VISIBLE を持たない子は不可視のまま
        // (「常に真」を返す実装はここで落ちる)。
        assert!(
            !is_window_visible(hidden),
            "WS_VISIBLE を持たない子は親が可視でも不可視"
        );

        // 無効なハンドルは失敗ではなく「可視ではない」。
        assert!(!is_window_visible(HWND::default()));

        destroy_test_hwnd(hidden);
        destroy_test_hwnd(shown);
        destroy_test_hwnd(parent);
    }

    #[test]
    fn set_then_clear_window_owner_round_trips_on_real_windows() {
        let owner = create_test_hwnd(w!("wintf-owner-test-owner"));
        let owned = create_test_hwnd(w!("wintf-owner-test-owned"));

        // 初期状態: owner 無し(対照。ここが最初から owner 有りなら以降の assert は無意味)。
        assert_eq!(
            get_window_long_ptr(owned, GWLP_HWNDPARENT).unwrap(),
            0,
            "生成直後の owner は未設定のはず"
        );

        set_window_owner(owned, owner).expect("set_window_owner must succeed");
        assert_eq!(
            get_window_long_ptr(owned, GWLP_HWNDPARENT).unwrap(),
            owner.0 as isize,
            "GWLP_HWNDPARENT に owner の HWND が入るはず"
        );
        assert_eq!(
            unsafe { GetWindow(owned, GW_OWNER) }.ok(),
            Some(owner),
            "GetWindow(GW_OWNER) からも owner が読めるはず"
        );

        clear_window_owner(owned).expect("clear_window_owner must succeed");
        assert_eq!(
            get_window_long_ptr(owned, GWLP_HWNDPARENT).unwrap(),
            0,
            "切離し後の GWLP_HWNDPARENT は 0 のはず"
        );
        assert_eq!(
            unsafe { GetWindow(owned, GW_OWNER) }.ok(),
            None,
            "切離し後は GetWindow(GW_OWNER) も不在を返すはず"
        );

        destroy_test_hwnd(owned);
        destroy_test_hwnd(owner);
    }
}

#[cfg(test)]
#[path = "api_owner_chain_probe_tests.rs"]
mod api_owner_chain_probe_tests;

#[cfg(test)]
#[path = "api_owner_chain_nudge_probe_tests.rs"]
mod api_owner_chain_nudge_probe_tests;