Skip to content

Pari's blog

Windowsの仮想デスクトップを自由に移動したい

Windowsの仮想デスクトップを自由に移動したい

最終更新: 2024-03-10

TL; DR

  • Windowsでカスタムキーマップを使う場合は AutoHotkeyで

    • レジストリの変更が必要なキーもある
  • 内容

    • 音量上げ下げ
    • 仮想デスクトップの移動
    • Alacritty + Vimで発生するペーストの問題

前情報

私は現在Linux → Windowsへの移行中である。

Linuxでは気軽に設定できた内容もWindowsでは難しいことも多い。

この辺はおそらくOSの設計思想がよく現れていて、整理するとこんな感じ

  • Linux:

    • すべてはファイル
    • つまり設定は平文で保存される
    • ユーザーが変更可能で、変更に対する責任もユーザーが負う
  • Windows:

    • 色々な管理方式が混在
      • ファイル・レジストリ・ポリシーなど
    • ファイル以外はバイナリ(Windowsから読めるデータベース)
    • ユーザーが気軽に変更することは想定されない
      • 変更には専用のインターフェイス(つまりアプリケーション)が必要

つまりWindowsはクソ

「ユーザーが安易に変更できない」というのはITリテラシーの低い職場などで、規格を統一する際などは便利なのかもしれない。 Linuxの方が圧倒的にスクリプトキディに悪用され易いし(防ぐ方法はあってもかなり面倒そう)。

何がしたいか

次のような挙動をさせたい

  • Windows Key + L: 右の仮想デスクトップへ移動
  • Windows Key + Shift + L: ウィンドウを右の仮想デスクトップへ移動
  • Windows Key + H: 左の仮想デスクトップへ移動
  • Windows Key + Shift + H: ウィンドウを左の仮想デスクトップへ移動

Window Switch

「Win3 - 🐱🐱🐱」と 「Win4 - 🐱🐱🐱🐱」を行き来している。

前準備 - “Win + L”の解除

今回 Win + L に割当を追加する。元々はWindowのLock機能が割り当てられており、割当を解除するには、レジストリを編集する必要がある。 レジストリの以下を変更すれば良い。

  • キー: \HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\System\DisableLockWorkstation
  • 値: 1

System フォルダは存在しなかったので新規に作成する必要があった。

AutoHotkeyの基本設定 - 音量上げ下げを例に

Windowsにおいて、キーマッピングを変更するには AutoHotkey を使うものらしい。

例えば、次のようなマッピングを行うには

  • F4: 音量を下げる
  • F5: 音量を上げる

こうする。 SoundBeepはフィードバック音

#Requires AutoHotkey v2.0+
#SingleInstance Force

; Volume Up/Down
F5::
{
    SoundSetVolume "+5"
    SoundBeep 825, 200
}

F4::
{
    SoundSetVolume "-5"
    SoundBeep 660, 200
}

F3::{
  IsMuted := SoundGetMute()
  if IsMuted {
    SoundSetMute false
    SoundBeep 660, 200
  } else {
    SoundSetMute true
  }
}

※ バージョンが1系と2系でかなり文法が違うので注意

※ AutoHotkeyがインストールされていればダブルクリックで起動できるはず

例えば、次の場所に配置するとスタートアップ時に読み込んでくれる。

/mnt/c/Users/{ユーザ名}/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/default.ahk

AutoHotkeyでDLLを読む - 仮想デスクトップの移動

今回はウィンドウの移動を実現したいのだが、“現在のウィンドウを横に移動させる機能”は元々存在しない。

運良く、DLL1を開発してくれている人がいるので、それに乗っかる。

https://github.com/Ciantic/VirtualDesktopAccessor (幸いAHKの2系にも対応している)

ということで

#Requires AutoHotkey v2.0+
#SingleInstance Force ; The script will Reload if launched while already running

SetWorkingDir(A_ScriptDir)

; Path to the DLL, relative to the script
VDA_PATH := A_AppData . "\AutoHotkey\VirtualDesktopAccessor.dll"

hVirtualDesktopAccessor             := DllCall("LoadLibrary",    "Str", VDA_PATH,                "Ptr")
GetDesktopCountProc                 := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetDesktopCount", "Ptr")
GoToDesktopNumberProc               := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GoToDesktopNumber", "Ptr")
GetCurrentDesktopNumberProc         := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetCurrentDesktopNumber", "Ptr")
IsWindowOnCurrentVirtualDesktopProc := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "IsWindowOnCurrentVirtualDesktop", "Ptr")
IsWindowOnDesktopNumberProc         := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "IsWindowOnDesktopNumber", "Ptr")
MoveWindowToDesktopNumberProc       := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "MoveWindowToDesktopNumber", "Ptr")
IsPinnedWindowProc                  := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "IsPinnedWindow", "Ptr")
GetDesktopNameProc                  := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "GetDesktopName", "Ptr")
SetDesktopNameProc                  := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "SetDesktopName", "Ptr")
CreateDesktopProc                   := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "CreateDesktop", "Ptr")
RemoveDesktopProc                   := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "RemoveDesktop", "Ptr")

; On change listeners
RegisterPostMessageHookProc         := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "RegisterPostMessageHook", "Ptr")
UnregisterPostMessageHookProc       := DllCall("GetProcAddress", "Ptr", hVirtualDesktopAccessor, "AStr", "UnregisterPostMessageHook", "Ptr")

; Util Func
GetDesktopCount() {
    global GetDesktopCountProc
    count := DllCall(GetDesktopCountProc, "Int")
    return count
}

MoveCurrentWindowToDesktop(number) {
    global MoveWindowToDesktopNumberProc, GoToDesktopNumberProc
    activeHwnd := WinGetID("A")
    DllCall(MoveWindowToDesktopNumberProc, "Ptr", activeHwnd, "Int", number, "Int")
}

SetDesktopName(num, name) {
    global SetDesktopNameProc
    OutputDebug(name)
    name_utf8 := Buffer(1024, 0)
    StrPut(name, name_utf8, "UTF-8")
    ran := DllCall(SetDesktopNameProc, "Int", num, "Ptr", name_utf8, "Int")
    return ran
}

; Main Func
MoveCurrentWindowToPrevDesktop() {
    global GetCurrentDesktopNumberProc, GoToDesktopNumberProc
    current := DllCall(GetCurrentDesktopNumberProc, "Int")
    last_desktop := GetDesktopCount() - 1
    ; If current desktop is 0, do nothing
    if (current != 0) {
        MoveCurrentWindowToDesktop(current - 1)
    }
    return
}

MoveCurrentWindowToNextDesktop() {
    global GetCurrentDesktopNumberProc, GoToDesktopNumberProc
    current := DllCall(GetCurrentDesktopNumberProc, "Int")
    last_desktop := GetDesktopCount() - 1
    ; If current desktop is last, do nothing
    if (current != last_desktop) {
        MoveCurrentWindowToDesktop(current + 1)
    }
    return
}

SetDesktopName(0, "Win1 - 🐱")
SetDesktopName(1, "Win2 - 🐱🐱")
SetDesktopName(2, "Win3 - 🐱🐱🐱")
SetDesktopName(3, "Win4 - 🐱🐱🐱🐱")

; “Win + H”を“Win + Ctrl + LeftArrow”にマッピング
#h::#^Left

; “Win + L”を“Win + Ctrl + RightArrow”にマッピング
#l::#^Right

; “Win + Shift + H”を“現在のウィンドウを左のデスクトップに移動した上で、左のデスクトップに移動”にマッピング
#+h::
{
    Send "#^{Left}"
    MoveCurrentWindowToPrevDesktop()
}

; “Win + Shift + L”を“現在のウィンドウを右のデスクトップに移動した上で、右のデスクトップに移動”にマッピング
#+l::
{
    Send "#^{Right}"
    MoveCurrentWindowToNextDesktop()
}
  • VirtualDesktopAccessorのDLL本体は ユーザーディレクトリ/AppData/Roaming/AutoHotkey以下に配置する想定

    VDA_PATH := A_AppData . "\AutoHotkey\VirtualDesktopAccessor.dll"
    
  • コマンドの特殊キーとかの割当てはこんな感じ

    #	Windowsキー
    ^	Ctrl
    +	Shift
    !	Alt
    F1~F24	ファンクションキー
    Space	スペース
    Tab	Tab
    Enter	Enter
    BS	Backspace
    Del	Delete
    Left,Right,Up,Down	←→↑↓の矢印キー
    
  • 以下の2つの関数は現在のアクティブウィンドウを移動する関数なので、アクティブウィンドウが存在しない場合はエラーが出る (まぁ別に良いか)。

    • MoveCurrentWindowToPrevDesktop
    • MoveCurrentWindowToNextDesktop

Alacritty + Vimで発生するペーストの問題

改行を含むテキストをWindows側でクリップボードにコピーして、WSL内のAlacritty内のVimに貼り付けると、余計な改行が発生する。

解消方法は https://github.com/alacritty/alacritty/issues/2324#issuecomment-1672990131 にある通り、

元のテキスト)

a
b
c

貼り付けた場合のテキスト)

a

b

c

これを解消する際にAutoHotkeyが使える。利便性のためにalacritty.tomlに次を追加

[window]
title = "alacritty"
dynamic_title = false

AutoHotkeyに次を追加。

#HotIf WinActive("alacritty")
^+v::
{
    A_Clipboard := StrReplace(A_Clipboard, "`r`n", "`n")
    Send("^+v")
}
return
#HotIf

Alacrittyの場合のみクリップボードを編集してCLRFをLFに変換している。

脚注

1

DLL (Dynamic Link Libarary) というのはWindowsにおける外部プログラムであり、何でもできる。