Skip to content

Instantly share code, notes, and snippets.

@lossyrob
Created May 26, 2026 19:31
Show Gist options
  • Select an option

  • Save lossyrob/a720ca2f3bd652165e778c0dce260e5e to your computer and use it in GitHub Desktop.

Select an option

Save lossyrob/a720ca2f3bd652165e778c0dce260e5e to your computer and use it in GitHub Desktop.
AutoHotkey Dev Box fullscreen switcher

Dev Box fullscreen switcher

AutoHotkey v2 script for cycling between full-screen Microsoft Dev Box / Cloud PC sessions launched by Windows App.

Hotkeys

Hotkey Action
Ctrl+Alt+Home Cycle to the next open Dev Box session
Ctrl+Alt+Shift+Home Minimize Dev Box sessions and return to the host

Ctrl+Alt+Home is also the native Remote Desktop connection-bar shortcut, so the connection bar may briefly appear. This script intentionally does not try to hide it because that can make switching flaky.

Install

  1. Install AutoHotkey v2, or let the installer script use winget.
  2. Download CycleDevBoxes.ahk and install.ps1 to the same folder.
  3. Run PowerShell:
Set-ExecutionPolicy -Scope Process Bypass -Force
.\install.ps1

The installer copies the script to %USERPROFILE%\Scripts, creates a Startup shortcut, and launches it.

How it works

  • It targets launched remote session windows (msrdc.exe, msrdcw.exe, mstsc.exe), not the Windows App launcher.
  • It filters for the actual RDP session window class (TscShellContainerClass) and ignores connection-bar/IME helper windows.
  • It remembers the last selected session by window title, then refreshes the open Dev Box window list on every hotkey press.
  • If sessions are opened, closed, or renamed, the next hotkey press updates the cycle set automatically.

Uninstall

Close AutoHotkey from the tray, then delete:

%USERPROFILE%\Scripts\CycleDevBoxes.ahk
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\Cycle Dev Boxes.lnk
#Requires AutoHotkey v2.0
#SingleInstance Force
#UseHook True
#Warn
Persistent
SetWinDelay(0)
; Cycle full-screen Microsoft Dev Box / Cloud PC sessions from the local host.
; Targets launched remote session windows (msrdc.exe), not the Windows App launcher.
; Shortcuts:
; Ctrl+Alt+Home -> next open Dev Box
; Ctrl+Alt+Shift+Home -> minimize Dev Box sessions and return to host
Log("Started")
$^!+Home::{
KeyWait("Home")
Sleep(50)
GoToHost("Ctrl+Alt+Shift+Home")
}
$^!Home::{
KeyWait("Home")
Sleep(50)
CycleDevBox(1, "Ctrl+Alt+Home")
}
CycleDevBox(direction, source) {
windows := GetDevBoxWindows()
windows := ApplySavedOrder(windows)
WriteWindowOrder(windows)
Log(source " pressed; found " windows.Length " Dev Box windows")
if (windows.Length = 0) {
TrayTip("Dev Box switcher", "No Dev Box windows found.", 2)
return
}
activeTitle := ""
try activeTitle := WinGetTitle("A")
lastTitle := ReadLastTitle()
currentIndex := 0
for index, info in windows {
if (info.Title = activeTitle) {
currentIndex := index
break
}
}
if (currentIndex = 0 && lastTitle != "") {
for index, info in windows {
if (info.Title = lastTitle) {
currentIndex := index
break
}
}
}
if (currentIndex = 0) {
currentIndex := direction > 0 ? 0 : windows.Length + 1
}
targetIndex := currentIndex + direction
if (targetIndex > windows.Length) {
targetIndex := 1
} else if (targetIndex < 1) {
targetIndex := windows.Length
}
target := windows[targetIndex]
WriteLastTitle(target.Title)
Log("Activating " targetIndex "/" windows.Length ": " target.Title " (active='" activeTitle "', last='" lastTitle "')")
try WinRestore("ahk_id " target.Hwnd)
WinActivate("ahk_id " target.Hwnd)
if !WinWaitActive("ahk_id " target.Hwnd, , 0.5) {
DllCall("SetForegroundWindow", "ptr", target.Hwnd)
Log("Used SetForegroundWindow fallback")
}
}
GoToHost(source) {
windows := GetDevBoxWindows()
windows := ApplySavedOrder(windows)
WriteWindowOrder(windows)
Log(source " pressed; minimizing " windows.Length " Dev Box windows")
for info in windows {
try WinMinimize("ahk_id " info.Hwnd)
}
}
GetDevBoxWindows() {
filters := [
"ahk_exe msrdc.exe",
"ahk_exe msrdcw.exe",
"ahk_exe mstsc.exe"
]
windows := []
seen := Map()
for filter in filters {
for hwnd in WinGetList(filter) {
if (seen.Has(hwnd)) {
continue
}
try title := WinGetTitle("ahk_id " hwnd)
catch {
continue
}
if (title = "" || IsIgnoredWindowTitle(title)) {
continue
}
try className := WinGetClass("ahk_id " hwnd)
catch {
continue
}
if !IsDevBoxSessionClass(className) {
continue
}
try style := WinGetStyle("ahk_id " hwnd)
catch {
continue
}
if !(style & 0x10000000) {
continue
}
windows.Push({ Hwnd: hwnd, Title: title })
seen[hwnd] := true
}
}
return windows
}
IsIgnoredWindowTitle(title) {
ignoredTitles := Map(
"BBar", true,
"Default IME", true,
"MSCTFIME UI", true
)
return ignoredTitles.Has(title)
}
IsDevBoxSessionClass(className) {
return className = "TscShellContainerClass"
}
Log(message) {
FileAppend(FormatTime(, "yyyy-MM-dd HH:mm:ss") " " message "`n", A_ScriptDir "\CycleDevBoxes.log", "UTF-8")
}
ReadLastTitle() {
path := A_ScriptDir "\CycleDevBoxes.state"
if !FileExist(path) {
return ""
}
try return Trim(FileRead(path, "UTF-8"))
catch {
return ""
}
}
WriteLastTitle(title) {
path := A_ScriptDir "\CycleDevBoxes.state"
try FileDelete(path)
try FileAppend(title, path, "UTF-8")
}
ReadWindowOrder() {
path := A_ScriptDir "\CycleDevBoxes.order"
titles := []
if !FileExist(path) {
return titles
}
try text := FileRead(path, "UTF-8")
catch {
return titles
}
for title in StrSplit(text, "`n", "`r") {
title := Trim(title)
if (title != "") {
titles.Push(title)
}
}
return titles
}
ApplySavedOrder(windows) {
savedTitles := ReadWindowOrder()
ordered := []
used := Map()
for savedTitle in savedTitles {
for info in windows {
if (!used.Has(info.Title) && info.Title = savedTitle) {
ordered.Push(info)
used[info.Title] := true
break
}
}
}
for info in windows {
if !used.Has(info.Title) {
ordered.Push(info)
used[info.Title] := true
}
}
return ordered
}
WriteWindowOrder(windows) {
path := A_ScriptDir "\CycleDevBoxes.order"
lines := ""
for info in windows {
lines .= info.Title "`n"
}
try FileDelete(path)
try FileAppend(lines, path, "UTF-8")
}
$ErrorActionPreference = "Stop"
$scriptName = "CycleDevBoxes.ahk"
$installDir = Join-Path $HOME "Scripts"
$scriptPath = Join-Path $installDir $scriptName
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
Copy-Item -Path (Join-Path $PSScriptRoot $scriptName) -Destination $scriptPath -Force
$ahk = @(
"$env:LOCALAPPDATA\Programs\AutoHotkey\v2\AutoHotkey64.exe",
"$env:ProgramFiles\AutoHotkey\v2\AutoHotkey64.exe",
"$env:ProgramFiles\AutoHotkey\AutoHotkey64.exe"
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $ahk) {
if (Get-Command winget.exe -ErrorAction SilentlyContinue) {
winget install --id AutoHotkey.AutoHotkey --exact --source winget --accept-package-agreements --accept-source-agreements --scope user
} else {
throw "AutoHotkey v2 not found. Install it from https://www.autohotkey.com/ and rerun this script."
}
$ahk = @(
"$env:LOCALAPPDATA\Programs\AutoHotkey\v2\AutoHotkey64.exe",
"$env:ProgramFiles\AutoHotkey\v2\AutoHotkey64.exe",
"$env:ProgramFiles\AutoHotkey\AutoHotkey64.exe"
) | Where-Object { Test-Path $_ } | Select-Object -First 1
}
if (-not $ahk) {
throw "AutoHotkey64.exe not found after install."
}
$startupLink = Join-Path ([Environment]::GetFolderPath("Startup")) "Cycle Dev Boxes.lnk"
$wsh = New-Object -ComObject WScript.Shell
$link = $wsh.CreateShortcut($startupLink)
$link.TargetPath = $ahk
$link.Arguments = '"' + $scriptPath + '"'
$link.WorkingDirectory = $installDir
$link.Description = "Cycle Dev Boxes with Ctrl+Alt+Home; return to host with Ctrl+Alt+Shift+Home"
$link.Hotkey = ""
$link.Save()
Get-Process AutoHotkey64 -ErrorAction SilentlyContinue |
Where-Object { $_.Path -eq $ahk } |
ForEach-Object { Stop-Process -Id $_.Id -Force }
Start-Process -FilePath $ahk -ArgumentList @($scriptPath) -WorkingDirectory $installDir
Write-Host "Installed: $scriptPath"
Write-Host "Startup shortcut: $startupLink"
Write-Host "Hotkeys:"
Write-Host " Ctrl+Alt+Home cycle open Dev Boxes"
Write-Host " Ctrl+Alt+Shift+Home return to host"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment