前回:
ソフトの概要
設定が書かれたYAMLファイルを読み込み、キーボードの切り替えイベントを2/60秒以上3/60秒以下表示する。
- 想定環境
- OS: Windows 10 以上
- ディスプレイ: リフレッシュレート60Hz以上
- 設定項目
-
colors
:Color3
に対応 -
fonts
:IDWriteTextFormat
に対応 -
window
: ウィンドウの位置・サイズ・背景色を指定 -
defaultVkCode
: キーの表示で使うデフォルトの幅・高さ・背景色・文字色・フォントを指定 -
vkCode
: それぞれの vkCode に対応して表示するテキスト・表示位置・表示サイズなどを指定
-
WindowsもWin32APIもDirectXもスワップチェーンもフックも何も分からん
- 最初にしっかり目を通しておけば良かったなと思った資料
- 都度お世話になった資料
serge_yamlにマージ機能がない?
- ない
- マージ機能はYAML 1.1の仕様
- serge_yamlはYAML 1.2をサポート
- YAML Anchor, Aliases and Merge Keys
- YAML Merge key << not supported · Issue #163 · dtolnay/serde-yaml
- マージ機能を後付するクレートはある
serge_yamlで順序付きマップ(IndexMap)はどうSerializeされるの?
- そもそもYAMLはキーの順序を保証していない
- 順序を保証する形式にするマクロは用意されている
[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
- それを行うマクロを定義したクレートがいくつかある
[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)
-
標準のスタックサイズ(MSVCのリンカーの場合 1MB)だと足りないので指定すれば良い
-
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));
}
コマンドプロンプトが邪魔
- 消せる
// 常時消す
#![windows_subsystem = "windows"]
// デバッグビルドでないなら消す
// #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
キーボード入力がラグくなる
- 原因:フリップモデルのスワップチェーンで
Present(1, 0)
を実行すると次のvertical blankまでメインスレッドがブロックされ、その間、キーボード入力にフックしたコールバック関数が詰まる-
DXGI overview - Win32 apps | Microsoft Docs
-
Be careful that you never have the message-pump thread wait on the render thread when you use full-screen swap chains. For instance, calling IDXGISwapChain1::Present1 (from the render thread) may cause the render thread to wait on the message-pump thread.
-
-
Windowsのフックのはまりどころ備忘録 - Qiita
-
コールバック関数を実行するスレッドをブロックしてしまっている(未検証)
-
-
DXGI overview - Win32 apps | Microsoft Docs
- 対応:レンダリングを別スレッドにする
実行ファイルにアイコンを設定したい
-
楽に設定させてくれるクレートがある
-
winres
の場合- Windows SDK - Windows app development | Microsoft Developer から Windows SDK をインストール
- icoファイルを用意する(Cargoプロジェクトルートに
test.ico
を配置したとする) -
Cargo.toml
とbuild.rs
を設定する cargo build
[build-dependencies]
winres = "0.1"
fn main() {
if cfg!(target_os = "windows") {
let mut res = winres::WindowsResource::new();
res.set_icon("test.ico");
res.compile().unwrap();
}
}
- 左上:エクスプローラーのアイコン
- 右上:ウィンドウアイコン
- 左下:タスクバーのアイコン
どうやって60FPSで動くゲームと同期するの
- 分からん
- ディスプレイのリフレッシュレートが60Hzの場合は同期する認識
- フリップモデルのスワップチェーンで Present(1, 0) 実行直後の時間をキーボード入力表示ソフトの基準時間にすることも考えましたが、ゲームと同期されるか分からないのでやめました
- リフレッシュレートが120Hzのとき、ゲームは偶数フレーム更新、キーボード入力表示ソフトが奇数フレーム更新になりえる
感想
自分が必要とする機能だけあればいい
他の方が作って公開しているキーボード入力表示ソフトには ThoNohT/NohBoard や ApVpk がある。最初はこれらに実装されている機能を基本的に実装しようと思っていたけど、使わない機能を実装するモチベーションが湧かなかった。
- 設定編集用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ライセンスでソースコードを公開すると思うとサボれる
「気になる所があったら、公開しているソースコードを使って好き勝手やってくれ!」と踏ん切りをつけられた。ただ自分が望む機能は実装済みなので、管理する気ないよアピールとしてアーカイブ化するべきかもしれない。