さんだすメモ

さメモ

技術ブログでは、ない・・・

AutoHotkey で DPI スケーリングの違う環境でウィンドウの移動をする

DPI の違うモニターで思うようにウィンドウを動かせない問題について。

github.com

PowerToys の FancyZones をものすごく参考にしました。

Windows 10、AutoHotkey v2 です。

DwmGetWindowAttribute

size := 16
buf := Buffer(size)
DWMWA_EXTENDED_FRAME_BOUNDS := 9
DllCall(
  "Dwmapi.dll\DwmGetWindowAttribute",
  "Ptr", hWindow,
  "UInt", DWMWA_EXTENDED_FRAME_BOUNDS,
  "Ptr", buf.Ptr,
  "Int", size
)
left := NumGet(buf, 0, "Int")
top := NumGet(buf, 4, "Int")
right := NumGet(buf, 8, "Int")
bottom := NumGet(buf, 12, "Int")

ウィンドウの周りの影の部分を除いた、見たままの枠を返してくれます。

ただし、DPI スケーリングを考慮しません。

SetThreadDpiAwarenessContext

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 := -4
DllCall(
  "SetThreadDpiAwarenessContext",
  "Int", DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
)

あまり理解できていませんが、DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 にしました。

スケーリング前の座標を取得・使用するようになっており、DwmGetWindowAttribute と相性が良いです。
また、Google Chrome などを思い通りに設置できるようになりました。

一方で、DPI Awareness が進んでいないプロセスのウィンドウについては、DPI の違うモニターで思い通りに移動できなくなります。
FancyZones では、DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE 未満のプロセスのウィンドウは境界内部に収まるように位置決めしているようでした。

一時的に DPI Awareness Context を変更する

あまり試せていないません。
適用できないものがあったら更新します・・・。

  • DwmGetWindowAttributeGetWindowRect で影分のマージンを取得
    • DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
  • ウィンドウの移動
    • 対象が DPI_AWARENESS_CONTEXT_PER_MONITOR 以上のとき
      • 座標を指定して移動
    • 対象が DPI_AWARENESS_CONTEXT_SYSTEM_AWARE 以下のとき
      • DPI_AWARENESS_CONTEXT_SYSTEM_AWARE に落とす
      • GetScaleFactorForMonitor などで得られる scale factor を使って影分のマージンをスケーリング(200% -> 0.5 倍)
      • 座標を指定して移動
      • DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 に戻す

最後に

FancyZones 様様です。
機能も増えてますね。
こちらの関数が使えたりするといいなあと思うのですが、よくわからないので AutoHotkeyスクリプトを書いています。