Open6

ImGuiでショートカットキーを使ってofSystemSaveDialogを使ったときの不具合[Mac]

Nariaki IwataniNariaki Iwatani

環境
macOS 10.15.5
OpenGL 4.1
openFrameworks 0.11.0
ofxImGui 330e1425a8

現象

bool IsKeyDown(int key_index, ImGuiKeyModFlags mod)
{
	auto &&io = ImGui::GetIO();
	return io.KeysDown[key_index] && io.KeyMods == mod;
}

if(IsKeyDown('S', ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift)) {
	auto result = ofSystemSaveDialog("", "Save as");
	if(result.bSuccess) {
		...
	}
}

こんな感じでダイアログを開くと、閉じた直後にimgui.cpp#7557にあるio.KeyModsのSanityCheckに失敗する。

const ImGuiKeyModFlags expected_key_mod_flags = GetMergedKeyModFlags();
IM_ASSERT(g.IO.KeyMods == expected_key_mod_flags && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods");
Nariaki IwataniNariaki Iwatani

デバッガで値をみたところ、以下だった。

  • GetMergedKeyModFlags()の結果は0
  • g.IO.KeyModsは10 (= ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift)

GetMergedKeyModFlags()が集計しているフラグはofxImGui/src/EngineGLFW.cppにあるEngineGLFW::onKeyReleasedで下ろされている。

そのタイミングをみてみると、ダイアログが開くときっぽい。

どうやらGLFWなのかmacOSなのか何かの動作で、OpenGL的にはここでキーを放したことになるらしい。

Nariaki IwataniNariaki Iwatani

時系列で現象をみると

  1. NewFrame()IO.KeyModsGetMergedKeyModFlags()によって設定される
  2. ダイアログが開き、IO.KeyModsはそのままでフラグ側だけが変更される
  3. ErrorCheckEndFrameSanityChecks()でミスマッチがみつかる
    ということになる。
Nariaki IwataniNariaki Iwatani

こんな感じでキーのキャッシュをして、ダイアログ前後で保存して戻すと、次以降のフレームでも(Releaseイベントが呼ばれないので)押されっぱなしになって失敗。

	auto &io = ImGui::GetIO();
	bool key_cache[512];
	auto storeKeyCache = [&]() {
		memcpy(key_cache, io.KeysDown, sizeof(io.KeysDown));
	};
	auto restoreKeyCache = [&]() {
		memcpy(io.KeysDown, key_cache, sizeof(io.KeysDown));
		io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL];
		io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT];
		io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT];
 		io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER];
	};
Nariaki IwataniNariaki Iwatani

これをダイアログ直後に呼んで解決した。
(#include "imgui_internal.h"が必要)

auto sanitizeKeyMods = [&]() {
	ImGui::GetIO().KeyMods = ImGui::GetMergedKeyModFlags();
};