Как решить проблему “потери” окон программ, при использовании приложений удаленного доступа Sunshine и Moonlight

от автора

Рано или поздно любой “удалёнщик” открывает для себя по-настоящему крутую связку софта для удаленного управления из двух бесплатных приложений — Sunshine (серверная часть) и Moonlight (клиентская часть).

Что же такого крутого в этом софте — спросит любой давний поклонник этих Ваших “rdp,vnc, энидесков, тимвьюверов, замшелой удаленной консоли” и прочих типов удаленного доступа?

Если очень коротко, то суть именно этой “связки” софта в том, что она способна идеально “выжать” из возможностей Ваших видеокарты и процессора всё, на что они способны и реально выдать вплоть до 120FPS на 4K даже на не слишком “толстом” интернет-канале. А там где интернет-канал будет совсем “тонким”, эта «связка» софта способна “выжать” такие значения FPS, которые и не снились, ни rdp, ни прочим “хитрым” вариантам умной передачи экрана. Иными словами, Вы во многих ситуациях получите удаленную сессию управления, в которой будет абсолютно полное ощущение, что Вы работаете в конкретный момент не за удаленным рабочим столом, а просто локально. При нормальном интернете Вы даже сможете смотреть (или редактировать) 4K видео с 60FPS в удаленной сессии, если Вам это понадобится.

В этой статье я хотел бы рассказать, как я нестандартно решил проблему, которая неизбежно возникает у пользователей приложений Sunshine и Moonlight, в ситуации, если на серверной стороне к удаленному устройству подключено сразу несколько мониторов в режиме “расширить рабочее пространство сразу на несколько мониторов” + сама трансляция экрана в удаленную сессию настроена через виртуальный дисплей.

Отдельно стоит отметить, что использование виртуального рабочего дисплея с указанным софтом крайне рекомендовано по той причине, что с виртуальным рабочим дисплеем, установленным на серверной стороне, Вы можете задать любые разрешения удаленного экрана, с любой частотой обновления экрана, и Вы никак не будете ограничены реальными физическими параметрами физических мониторов (и их наличием вообще!), подключенных к удаленному устройству.

Кроме того, трансляция видеопотока с виртуального дисплея гораздо меньше “нагружает” серверную сторону и позволяет идеально оптимизировать сам поток данных.

Но с этими несомненными “плюсами” возникнет и та самая неизбежная проблема, о которой я напишу далее. Дело в том, что когда Вы подключитесь к удаленному виртуальному дисплею и начнете работать с различными приложениями, потом, когда Вы захотите вернуть “нормальный” режим работы на серверной стороне, Вы просто “потеряете” все окна приложений, которые ранее были запущены на виртуальном дисплее. И эти окна приложений просто перестанут отображаться на реальных физических дисплеях удаленного устройства, оставшись “жить” на виртуальном дисплее.

Как решить эту проблему? Очень просто!

Напишу на примере Windows:

  1. Создайте такой скрипт на PowerShell C:\Scripts\fix_windows.ps1.

try {    Add-Type @"    using System;    using System.Runtime.InteropServices;    public class Win32FinalComfortFix {        [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);        [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);        [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);        [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);        [DllImport("user32.dll")] public static extern bool SetProcessDpiAwarenessContext(IntPtr value);        public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }    }"@} catch {}try { [Win32FinalComfortFix]::SetProcessDpiAwarenessContext([IntPtr](-4)) } catch {}# 1. СПИСОК ПРОГРАММ$AppList = @(    "proxifier", "firefox", "firefoxportable", "happ",     "taskmgr", "bvssh", "idman", "sbiectrl",     "notepad", "totalcmd", "AkelPad", "VirtualBoxPortable", "VirtualBox", "swriter", "scalc", "msedge", "truecrypt", "miranda32", "element", "fancontrol", "ThrottleStop", "SamsungMagician", "cmd", "WindowsTerminal", "WNetWatcher", "WifiInfoView", "LM Studio", "explorer")Start-Sleep -Seconds 2# Делаем переменные координат глобальными для функции$script:offsetX = 200$script:offsetY = 200$windowWidth = 1920$windowHeight = 1080$uFlags = 0x0040        $SW_SHOWNORMAL = 1      # Функция перемещения окна (работает и для обычных программ, и для Проводника)function Move-TargetWindow($hwnd) {    $rect = New-Object Win32FinalComfortFix+RECT        if ([Win32FinalComfortFix]::GetWindowRect($hwnd, [ref]$rect)) {        # Если окно улетело на координаты Sunshine        if ($rect.Left -gt 6000 -or $rect.Left -lt -5000) {                        [Win32FinalComfortFix]::ShowWindowAsync($hwnd, $SW_SHOWNORMAL)            Start-Sleep -Milliseconds 60                          [Win32FinalComfortFix]::SetWindowPos($hwnd, [IntPtr]::Zero, $script:offsetX, $script:offsetY, $windowWidth, $windowHeight, $uFlags)            [Win32FinalComfortFix]::SetForegroundWindow($hwnd)                        # Сдвиг лесенки            $script:offsetX += 80            $script:offsetY += 50            if ($script:offsetX -gt 800) {                 $script:offsetX = 200                 $script:offsetY = 200             }        }    }}$shellApp = New-Object -ComObject Shell.Applicationforeach ($appName in $AppList) {    if ($appName -eq "explorer") {        # Специальный перебор окон Проводника        $explorerWindows = $shellApp.Windows() | Where-Object { $_.Name -eq "Проводник" -or $_.Name -eq "File Explorer" -or $_.FullName -like "*explorer.exe" }        foreach ($window in $explorerWindows) {            if ($window.HWND) {                Move-TargetWindow ([IntPtr]$window.HWND)            }        }        continue    }    # Стандартный перебор для остальных процессов    $processes = Get-Process -Name $appName -ErrorAction SilentlyContinue    foreach ($proc in $processes) {        $hwnd = $proc.MainWindowHandle        if ($hwnd -ne [IntPtr]::Zero) {            Move-TargetWindow $hwnd        }    }}

В этом скрипте Вам, по сути, нужно настроить только один блок кода:

1. СПИСОК ПРОГРАММ

$AppList = @( “proxifier”, “firefox”, “firefoxportable”, “happ”, “taskmgr”, “bvssh”, “idman”, “sbiectrl”, “notepad”, “totalcmd”, “AkelPad”, “VirtualBoxPortable”, “VirtualBox”, “swriter”, “scalc”, “msedge”, “truecrypt”, “miranda32”, “element”, “fancontrol”, “ThrottleStop”, “SamsungMagician”, “cmd”, “WindowsTerminal”, “WNetWatcher”, “WifiInfoView”, “LM Studio”, “explorer” )

Тут перечислены имена процессов приложений, окна которых Вы как раз и хотите “вернуть” на основной рабочий стол удаленного устройства, после завершения работы с Sunshine и Moonlight.
Разумеется, этот список можно дополнить любыми нужными Вам приложениями.

Теперь Вы можете либо вручную запускать этот скрипт после завершения удаленной сессии, либо Вы можете настроить автоматический запуск этого скрипта в Sunshine.

Сделать это можно так:

  1. Откройте браузер и перейдите в панель управления Sunshine: http://localhost:47990

  2. Перейдите во вкладку “Applications” в верхнем меню.

  3. Найдите в списке ваше приложение (обычно это “Desktop”) и нажмите кнопку “Edit” напротив него.

  4. Прокрутите страницу вниз до блока настроек команд запуска.

  5. Найдите поле “Undo Command” (команда, выполняемая после закрытия сессии Moonlight).

  6. Вставьте в это поле следующую строку: powershell.exe -ExecutionPolicy Bypass -File “C:\Scripts\fix_windows.ps1”

  7. Прокрутите страницу в самый низ и нажмите кнопку “Save”.

Как это работает в деталях?

Скрипт срабатывает через 2 секунды после закрытия сессии Moonlight (когда виртуальный экран уже отключился). Скрипт ищет окна программ только из белого списка ($AppList), поэтому, например, приложения, запущенные на внешних мониторах, которые Вы не добавили в скрипт, полностью в безопасности, и их окна не смещаются и не теряются, как при работе в удаленной сессии, так и при её завершении. Программы, оставшиеся в “пустоте” от виртуального дисплея, переносятся на основной экран в удобном крупном оконном разрешении и выстраиваются аккуратной лесенкой для быстрого переключения.

Конечно, описанный мной способ не идеальный в его реализации, но он совершенно рабочий. Рекомендую!

ссылка на оригинал статьи https://habr.com/ru/articles/1055686/