ウィンドウの配置を自動で変えるやつ

ディスプレイを1つ追加するとケーブルを抜き差しするたびにウィンドウ配置が1画面用/2画面用でぐちゃぐちゃに移動してしまう。

都度手動で配置を変えるのも面倒なので、PowerShellで実行すれば勝手に配置してくれるプログラムを組むことにした。

# C#
Add-Type @"
    using System;
    using System.Runtime.InteropServices;

    public class Win32Api {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
    }
"@

# アプリケーション名
$name_c = "chrome"
# X座標
$x1_c = 1274
# Y座標
$y1_c = 0
# 幅
$x2_c = 2336
# 高さ
$y2_c = 692

Get-Process -Name $name_c | ? { $_.MainWindowTitle -ne "" } | % {
    [Win32Api]::MoveWindow($_.MainWindowHandle, $x1_c, $y1_c, $x2_c - $x1_c ,$y2_c - $y1_c, $true)
} | Out-Null

Get-Process -Name $name_c | ? { $_.MainWindowTitle -ne "" } | % {
    [Win32Api]::MoveWindow($_.MainWindowHandle, $x1_c, $y1_c, $x2_c - $x1_c ,$y2_c - $y1_c, $true)
} | Out-Null

とはいえ大体拾いコードを書き換えたもの。

四隅のX,Y座標を指定してウィンドウの配置を変えます。

MoveWindowがウィンドウを移動させるコマンドなのだが、ディスプレイ間を移動させると座標ずれが起きることがあるので全く同じものを2回実行している。

 

四隅のX,Y座標を取得するには以下を使う。

# C#ソースコードを変数に保存
$src = @"

using System;
using System.Runtime.InteropServices;

namespace Win32Api {

  public struct RECT {
    public int left;
    public int top;
    public int right;
    public int bottom;
  }
 
  public class Helper {

    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

    public static RECT GetForegroundWindowRect(IntPtr hwnd1) {
      IntPtr hwnd = hwnd1;
      RECT rect = new RECT();
      GetWindowRect(hwnd, out rect);
     
      return rect;
    }
  }
}
"@

# ソースコードからセッションにクラスを追加
Add-Type -TypeDefinition $src

$name = "chrome"

# $rectにアクティブウィンドウの4隅のスクリーン座標が格納される
#$rect = [Win32Api.Helper]::GetForegroundWindowRect($_.MainWindowHandle)
#$rect
Get-Process -Name $name | ? { $_.MainWindowTitle -ne "" } | % {
    $rect = [Win32Api.Helper]::GetForegroundWindowRect($_.MainWindowHandle)
} | Out-Null

Write-Output $rect
Read-Host "Enterで結果を見る"
Read-Host "Enterで終了する"

ただし、これで出てきた座標をそのまま入力してもうまく移動しないアプリケーションがある。それこそChromeとか。

これはWindows10の仕様らしいのだが、GetForegroundWindowRectで取得すると、ウィンドウサイズが実際よりも大きい値を取ってしまうらしい。

MoveWindowは指定された値の通りにウィンドウを配置するので、取得時の配置よりも大きいウィンドウが配置されてしまう。

別のコマンドを使えば正しい大きさが取れるらしいのだが、まあ面倒なので手動で調整するようにした。

# C#
Add-Type @"
    using System;
    using System.Runtime.InteropServices;

    public class Win32Api {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
    }
"@
# C#ソースコードを変数に保存
$src = @"
using System;
using System.Runtime.InteropServices;
 
namespace Win32Api {

  public struct RECT {
    public int left;
    public int top;
    public int right;
    public int bottom;
  }
 
  public class Helper {

    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
   
    public static RECT GetForegroundWindowRect(IntPtr hwnd1) {
      IntPtr hwnd = hwnd1;
      RECT rect = new RECT();
      GetWindowRect(hwnd, out rect);
     
      return rect;
    }
  }
}
"@
# ソースコードからセッションにクラスを追加
Add-Type -TypeDefinition $src

# アプリケーション名
$name_c = "chrome"
echo $name_c

Get-Process -Name $name_c | ? { $_.MainWindowTitle -ne "" } | % {
    $rect = [Win32Api.Helper]::GetForegroundWindowRect($_.MainWindowHandle)
} | Out-Null

echo $rect
Read-Host "press Enter for input new params"
# X座標
$x1_c = Read-Host "left"
# Y座標
$y1_c = Read-Host "top"
# 幅
$x2_c = Read-Host "right"
# 高さ
$y2_c = Read-Host "bottom"

echo "$x1_c $x2_c" "$y1_c $y2_c"

Get-Process -Name $name_c | ? { $_.MainWindowTitle -ne "" } | % {
    [Win32Api]::MoveWindow($_.MainWindowHandle, $x1_c, $y1_c, $x2_c - $x1_c ,$y2_c - $y1_c, $true)
} | Out-Null

Get-Process -Name $name_c | ? { $_.MainWindowTitle -ne "" } | % {
    $rect1_c = [Win32Api.Helper]::GetForegroundWindowRect($_.MainWindowHandle)
} | Out-Null

echo $rect1_c
Read-Host "press Enter"

Read-Host "Enterで終了する"

ということで、最初に四隅のX,Y座標を取ったら、ずれているウィンドウに対して手入力版を起動して微調整、任意の配置になったら自動配置用の変数を変えて……いや面倒くさいですよこれ。

 

まあ気が向いたらまた調整するかもしれません。