ウチツクシ

ゲームをしたり作ったり

マルチディスプレイで解像度などの情報を取得する

追記:修正版をQiitaに書きました。


f:id:K-s:20160618182220j:plain
HSPの ginfo_dispx(y) はメインディスプレイの解像度しか得られないため、サブディスプレイで全画面ウィンドウを作るときなどには使えない。モニタを1つしか持ってないから気にならなかったのだけど、モニタに複数入力したらマルチできると気づき興味が出て調べた。リモコンで入力モード切り替える似非マルチ。

モニタの全ハンドルを取得するのは普通コールバックを使うらしいのだが、HSPは標準ではコールバック使えないから別の関数使って気合で取得した。EnumDisplayDevices とかは複数回呼ぶと全部取得できるのになんでこれはコールバックなんだ…
ただ、 コールバック用モジュールができました - HSPTV!掲示板 のモジュールを使えばコールバックもできるので使う場合は mod_clbk3.hsp で保存して1行目のコメント部分をアレしてください。

; #define USE_CALLBACK

#ifdef USE_CALLBACK
#include "mod_clbk3.hsp"
#endif
#include "user32.as"
#include "gdi32.as"

  ; 仮想画面の原点・サイズ取得
  GetSystemMetrics $4c ; SM_XVIRTUALSCREEN
  vsOriginX = stat
  GetSystemMetrics $4d ; SM_YVIRTUALSCREEN
  vsOriginY = stat
  GetSystemMetrics $4e ; SM_CXVIRTUALSCREEN
  vsSizeX = stat
  GetSystemMetrics $4f ; SM_CYVIRTUALSCREEN
  vsSizeY = stat

  dim hmoni
  moniNum = 0

#ifdef USE_CALLBACK
  ; コールバックを使ってモニタハンドルを列挙
  newclbk3 clbk, 4, *MonitorEnumProc
  EnumDisplayMonitors 0, 0, clbk, 0
  if 0 {
  *MonitorEnumProc
    clbkargprotect prm
    hmoni(moniNum) = prm(0)
    moniNum++
    return 1
  }
#else
  ; 仮想画面上の各点を調べてモニタハンドルを列挙
  sft = 100 ; 調べる各点の間隔
  repeat
    x = vsOriginX + cnt * sft \ vsSizeX
    y = vsOriginY + cnt * sft / vsSizeX * sft
    if (y > vsSizeY) : break
    MonitorFromPoint x, y, 0 ; MONITOR_DEFAULTTONULL
    h = stat
    if (h == 0) : continue
    f = 0
    if (moniNum) {
      ; ハンドルの重複チェック
      repeat moniNum
        if (h == hmoni(cnt)) : break
        if (cnt == moniNum - 1) : f = 1
      loop
    } else {
      f = 1
    }
    if (f) {
      hmoni(moniNum) = h
      moniNum++
    }
  loop
#endif

  ; 各ディスプレイの情報取得
  dim moniInfo, 10 + 8  ; MONITORINFOEX
      moniInfo(0) = 40 + 32
  dim devMode, 44 ; DEVMODE
  dim px, moniNum
  dim py, moniNum
  dim sx, moniNum
  dim sy, moniNum
  dim bit, moniNum
  dim hz, moniNum
  dim flag, moniNum
  sdim dn, 32, moniNum
  repeat moniNum
    GetMonitorInfo hmoni(cnt), varptr(moniInfo)
    px(cnt) = moniInfo(1) ; rcMonitor.left
    py(cnt) = moniInfo(2) ; rcMonitor.top
    flag(cnt) = moniInfo(9) ; dwFlags
    getstr dn(cnt), moniInfo, 40 ; szDevice
    EnumDisplaySettings dn(cnt), -1, varptr(devMode) ; ENUM_CURRENT_SETTINGS
    sx(cnt) = devMode(27) ; dmPelsWidth
    sy(cnt) = devMode(28) ; dmPelsHeight
    bit(cnt) = devMode(26) ; dmBitsPerPel
    hz(cnt) = devMode(30) ; dmDisplayFrequency
  loop
  
  ; デスクトップ全体のビットマップを取得
  buffer 1, vsSizeX, vsSizeY
  GetDC 0
  dc = stat
  BitBlt hdc, 0, 0, vsSizeX, vsSizeY, dc, vsOriginX, vsOriginY, $40CC0020 ; SRCCOPY | CAPTUREBLT
  ReleaseDC 0, dc
  celdiv 1, , , vsSizeX / 2, vsSizeY / 2
  
  ; 以下描画
  
  ; screen 0, 1024, 768
  gsel
  winsx = ginfo_sx
  winsy = ginfo_sy
  
  if (vsSizeX > vsSizeY * winsx / winsy) {
    rate = double(winsx - 40) / vsSizeX
    dox = 20
    doy = winsy / 2 - rate * vsSizeY / 2
  } else {
    rate = double(winsy - 40) / vsSizeY
    dox = winsx / 2 - rate * vsSizeX / 2
    doy = 20
  }
  
  font "arial", 12
  
  color 220, 220, 220
  box vsOriginX, vsOriginY, vsSizeX, vsSizeY
  pos ginfo_cx, ginfo_cy - 16
  color
  mes strf("pos(%d, %d), res(%d x %d)", vsOriginX, vsOriginY, vsSizeX, vsSizeY)
  repeat moniNum
    if (flag(cnt)) {
      color 220, 170, 170
    } else {
      color 150, 200, 200
    }
    box px(cnt), py(cnt), sx(cnt), sy(cnt)
    pos ginfo_cx + 6, ginfo_cy + 4
    color
    mes strf("pos(%d, %d)", px(cnt), py(cnt))
    mes strf("res(%d x %d)", sx(cnt), sy(cnt))
    mes strf("color %d bit", bit(cnt))
    mes strf("refresh %d hz", hz(cnt))
    mes dn(cnt)
  loop
  
  gmode 5, vsSizeX, vsSizeY, 40
  pos winsx / 2, winsy / 2
  celput 1, 0, rate, rate
  
  stop

#deffunc box int _x, int _y, int _w, int _h
  x1 = rate * (_x - vsOriginX) + dox
  y1 = rate * (_y - vsOriginY) + doy
  x2 = rate * (_x + _w - vsOriginX) + dox
  y2 = rate * (_y + _h - vsOriginY) + doy
  boxf x1, y1, x2 - 1, y2 - 1
  color ginfo_r + 20, ginfo_g + 20, ginfo_b + 20
  boxf x1 + 4, y1 + 4, x2 - 5, y2 - 5
  pos x1, y1
  return

GetMonitorInfo で得られた szDevice を EnumDisplaySettings に指定してそれで得た DEVMODE を ChangeDisplaySettings(6.19修正: ChangeDisplaySettingsEx じゃないとダメ)に渡せば特定のディスプレイの解像度変更なんかも多分できる。(→できた


今回調べてて分かったのは、

  • ソフトは起動すると基本的にメインディスプレイに表示される
  • ginfo_dispx(y) はメインディスプレイの解像度
  • MonitorFromPoint の引数は3つ(https://msdn.microsoft.com/ja-jp/library/cc410472.aspxだと2つに見える…初め配列のポインタ渡してて動かなかった、ずるい)