4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RustでWindows用キーボード入力表示ソフトを作るときに悩んだりしたこと

Posted at

前回:

ソフトの概要

設定が書かれたYAMLファイルを読み込み、キーボードの切り替えイベントを2/60秒以上3/60秒以下表示する。

  • 想定環境
    • OS: Windows 10 以上
    • ディスプレイ: リフレッシュレート60Hz以上
  • 設定項目
    • colors : Color3 に対応
    • fonts : IDWriteTextFormat に対応
    • window : ウィンドウの位置・サイズ・背景色を指定
    • defaultVkCode : キーの表示で使うデフォルトの幅・高さ・背景色・文字色・フォントを指定
    • vkCode : それぞれの vkCode に対応して表示するテキスト・表示位置・表示サイズなどを指定

WindowsもWin32APIもDirectXもスワップチェーンもフックも何も分からん

serge_yamlにマージ機能がない?

serge_yamlで順序付きマップ(IndexMap)はどうSerializeされるの?

Cargo.toml
[dependencies]
serde = {version = "1.0", features = ["derive"]}
serde_yaml = "0.8"
indexmap = { version = "1.9", features = ["serde"]}
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct MyStruct {
    mymap1: IndexMap<i32, String>,
    #[serde(with = "indexmap::serde_seq")]
    mymap2: IndexMap<i32, String>,
}

fn main() {
    let mut im_num_string = IndexMap::new();
    im_num_string.insert(5, "five".to_string());
    im_num_string.insert(1, "one".to_string());
    im_num_string.insert(10, "ten".to_string());
    println!("{}", serde_yaml::to_string(&im_num_string).unwrap());

    let my_struct = MyStruct{
        mymap1: im_num_string.clone(),
        mymap2: im_num_string.clone(),
    };
    println!("{}", serde_yaml::to_string(&my_struct).unwrap());
}
---
5: five
1: one
10: ten

---
mymap1:
  5: five
  1: one
  10: ten
mymap2:
  - - 5
    - five
  - - 1
    - one
  - - 10
    - ten

Arrayの中にvec!を書いて初期化できない

PS P:\program\rust\novcs> cargo build
   Compiling novcs v0.1.0 (P:\program\rust\novcs)
error[E0277]: the trait bound `Vec<bool>: Copy` is not satisfied
  --> src\main.rs:11:7
   |
11 |     [[vec![false]; 2]];
   |       ^^^^^^^^^^^ the trait `Copy` is not implemented for `Vec<bool>`
   |
   = note: the `Copy` trait is required because this value will be copied for each element of the array
   = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace 
for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `novcs` due to previous error
Cargo.toml
[dependencies]
array-macro = "2.1.5"
use array_macro::array;

fn main() {
    const SIZE: usize = 2;
    let arr: [Vec<bool>; SIZE] = array![vec![false]; SIZE];
    // べた書きでも可能ではある
    assert!([vec![false], vec![false]] == arr);
}

スタックオーバーフローする

PS P:\program\rust\zanei> cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target\debug\zanei.exe`

thread 'main' has overflowed its stack
error: process didn't exit successfully: `target\debug\zanei.exe` (exit code: 0xc00000fd, STATUS_STACK_OVERFLOW)
.cargo\config.toml
[build]
# 2 * (2 ** 20) byte
rustflags = ["-C", "link-args=/STACK:2097152"]
build.rs
fn main() {
    println!("cargo:rustc-link-arg=/STACK:{}", 2usize * 2usize.pow(20));
}

コマンドプロンプトが邪魔

src/main.rs
// 常時消す
#![windows_subsystem = "windows"]
// デバッグビルドでないなら消す
// #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

キーボード入力がラグくなる

  • 原因:フリップモデルのスワップチェーンで Present(1, 0) を実行すると次のvertical blankまでメインスレッドがブロックされ、その間、キーボード入力にフックしたコールバック関数が詰まる
  • 対応:レンダリングを別スレッドにする

実行ファイルにアイコンを設定したい

Cargo.toml
[build-dependencies]
winres = "0.1"
build.rs
fn main() {
  if cfg!(target_os = "windows") {
    let mut res = winres::WindowsResource::new();
    res.set_icon("test.ico");
    res.compile().unwrap();
  }
}

exe-ico.png

  • 左上:エクスプローラーのアイコン
  • 右上:ウィンドウアイコン
  • 左下:タスクバーのアイコン

どうやって60FPSで動くゲームと同期するの

  • 分からん
  • ディスプレイのリフレッシュレートが60Hzの場合は同期する認識
  • フリップモデルのスワップチェーンで Present(1, 0) 実行直後の時間をキーボード入力表示ソフトの基準時間にすることも考えましたが、ゲームと同期されるか分からないのでやめました
    • リフレッシュレートが120Hzのとき、ゲームは偶数フレーム更新、キーボード入力表示ソフトが奇数フレーム更新になりえる

感想

自分が必要とする機能だけあればいい

他の方が作って公開しているキーボード入力表示ソフトには ThoNohT/NohBoardApVpk がある。最初はこれらに実装されている機能を基本的に実装しようと思っていたけど、使わない機能を実装するモチベーションが湧かなかった。

  • 設定編集用GUI
  • アウトライン
  • 特殊キー
  • 画像

設定ファイルが面倒

どのキーを押したか分かるようにするために、キーごとに文字を設定するのが普通だと思う。その上でWindowsで文字を表示するために必要な引数が多すぎる(参考: ぶびびんぶろぐ: 大まかな概念:DirectWriteを使った文字列描画 )。レイアウト周りや D2D1_DRAW_TEXT_OPTIONS は固定値にした。

ENUMとして関数に渡される設定項目は、値と識別子のどちらを設定ファイルに記述させるか悩み、最終的に値にした。同じ値に複数の識別子が割り当てられてることがあるので、値のほうが検証の記述量を抑えられそうだった。DWRITE_FONT_WEIGHT (dwrite.h) - Win32 apps | Microsoft Docs が顕著。

typedef enum DWRITE_FONT_WEIGHT {
  DWRITE_FONT_WEIGHT_THIN = 100,
  DWRITE_FONT_WEIGHT_EXTRA_LIGHT = 200,
  DWRITE_FONT_WEIGHT_ULTRA_LIGHT = 200,
  DWRITE_FONT_WEIGHT_LIGHT = 300,
  DWRITE_FONT_WEIGHT_SEMI_LIGHT = 350,
  DWRITE_FONT_WEIGHT_NORMAL = 400,
  DWRITE_FONT_WEIGHT_REGULAR = 400,
  DWRITE_FONT_WEIGHT_MEDIUM = 500,
  DWRITE_FONT_WEIGHT_DEMI_BOLD = 600,
  DWRITE_FONT_WEIGHT_SEMI_BOLD = 600,
  DWRITE_FONT_WEIGHT_BOLD = 700,
  DWRITE_FONT_WEIGHT_EXTRA_BOLD = 800,
  DWRITE_FONT_WEIGHT_ULTRA_BOLD = 800,
  DWRITE_FONT_WEIGHT_BLACK = 900,
  DWRITE_FONT_WEIGHT_HEAVY = 900,
  DWRITE_FONT_WEIGHT_EXTRA_BLACK = 950,
  DWRITE_FONT_WEIGHT_ULTRA_BLACK = 950
} ;

MITライセンスでソースコードを公開すると思うとサボれる

「気になる所があったら、公開しているソースコードを使って好き勝手やってくれ!」と踏ん切りをつけられた。ただ自分が望む機能は実装済みなので、管理する気ないよアピールとしてアーカイブ化するべきかもしれない。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?