Рано или поздно любой “удалёнщик” открывает для себя по-настоящему крутую связку софта для удаленного управления из двух бесплатных приложений - 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.Application

foreach ($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), поэтому, например, приложения, запущенные на внешних мониторах, которые Вы не добавили в скрипт, полностью в безопасности, и их окна не смещаются и не теряются, как при работе в удаленной сессии, так и при её завершении. Программы, оставшиеся в “пустоте” от виртуального дисплея, переносятся на основной экран в удобном крупном оконном разрешении и выстраиваются аккуратной лесенкой для быстрого переключения.

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

Комментарии (0)