Очень быстрое переключение пользователей Windows

в 13:25, , рубрики: WinAPI, windows, Песочница, хоткеи, метки: , ,

Очень быстрое переключение пользователей WindowsХочу поделиться с вами лайфхаком, которым пользуюсь ежедневно уже на протяжении нескольких лет. Работает безупречно, сберегает время. Так повелось, что у нас с женой разные учетные записи на одном домашнем компьютере. Это удобно: у каждого свой рабочий стол, свои обои, предпочтения, настройки приложений, кукисы в браузере. Я даже не представляю сейчас, как можно работать под одной учеткой. Но (без этого “но” не было бы и статьи), есть одна маленькая проблема. Переключение пользователей. Как это делается обычно: Пуск –> некая кнопочка, в зависимости от системы -> сменить пользователя. Появляется экран выбора пользователя. Тыкаем в нужного пользователя. Да, есть сочетание клавиш Win+L. После которого опять надо ткнуть смену пользователя и иконку. Итого минимум 3 действия. В Windows 8 сделали заметное улучшение в этом плане. нажимаем Win + иконку пользователя и в списке кликаем на другого. Но это без учета, что на учетке есть пароль. Вот тут-то уже начинаются существенные задержки. Вводить пароль каждый раз при каждом переключении надоест очень быстро. А пароль на свою учетку мне пришлось поставить, так как нужен был удаленный доступ. Да, можно было для удаленного доступа сделать другую учетку, но мой лайфхак уже был готов к тому моменту, и прекрасно работал вне зависимости от того, есть пароли на учетках или нет.

А идея была такая. Сделать так, чтобы быстрое переключение пользователей происходило за одно действие. По нажатию одного хоткея. Поиск в интернете (напомню, было это года 3 назад) принес свои плоды, и подобные решения были найдены. Но, бесплатные либо глючили, либо требовали установки какого-то стороннего софта. А платная, качественная, нашлась одна, и работала одна очень хорошо, но, во-первых, была платной, во-вторых, содержала лишний функционал – по нажатию хоткея не сразу переключался пользователь, а отображалось окошко (по подобию Alt+Tab) с пользователями. Было решено написать свое решение. Самое простое, с минимумом функционала: хоткей – переключение.

Гугление выдало:

  • Для переключения сессий используйте функции wtsapi32.dll: WTSEnumerateSessions, WTSConnectSession, WTSDisconnectSession (Сейчас, когда смотрю описание этих функций, оно говорит что работает с удаленными рабочими сессиями, и честно-говоря, я в небольшом недоумении, но у меня работает локально, безупречно).
  • Для хоткеев, используйте функции user32.dll: RegisterHotKey, UnregisterHotKey. Тут все просто.

Сразу оговорюсь, и можете кидать в меня помидорами, но писал я это дело на c#, хотя на плюсах, было бы конечно лучше, нативнее и проч, проч, проч… Но, тогда я только начал осваивать c# и нужен был опыт, а когда решение было написано, переписывать его не было необходимости, хотя перенос его не займет больше одного вечера.

Итак, для начала было написано простое win32 приложение с кнопочкой, по нажатию которой выполнялся примерно такой код:

private void SwitchUser()
{
    IntPtr buffer = IntPtr.Zero;
    int count = 0;

    // получаем список сессий, в которых выполнен вход
    if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref buffer, ref count))
    {
        WTS_SESSION_INFO[] sessionInfo = new WTS_SESSION_INFO[count];

        // самая сложная часть:
        // аккуратно преобразовать неуправляемую память в управляемую
        for (int index = 0; index < count; index++)
            sessionInfo[index] = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)((int)buffer +
            (Marshal.SizeOf(new WTS_SESSION_INFO()) * index)), typeof(WTS_SESSION_INFO));
                
        int activeSessId = -1;
        int targetSessId = -1;

        // получаем Id активного, и неактивного сеанса
        // 0 пропускаем, там всегда "Services"
        for (int i = 1; i < count; i++)
        {                   
            if (sessionInfo[i].State == WTS_CONNECTSTATE_CLASS.WTSDisconnected)                    
                targetSessId = sessionInfo[i].SessionId;                                         
            else if (sessionInfo[i].State == WTS_CONNECTSTATE_CLASS.WTSActive)                   
                activeSessId = sessionInfo[i].SessionId;
        }
                
        if ((activeSessId > 0) && (targetSessId > 0))
        {
            // если есть неактивный сеанс, то переключаемся на него.
            WTSConnectSession(Convert.ToUInt64(targetSessId), Convert.ToUInt64(activeSessId), "", false);
        }
        else
        {
            // если неактивных нет. просто отключаемся (переходим на экран выбора пользователя)
            WTSDisconnectSession(WTS_CURRENT_SERVER_HANDLE, activeSessId, false);
        }
    }

    // обязательно чистим память
    WTSFreeMemory(buffer);
} 

При двух сеансах sessionInfo будет иметь 3 элемента: сеанс служб, сеанс 1-го пользователя, сеанс 2-го пользователя. Соответственно targetSessId и activeSessId определятся однозначно. При сеансах более 2, переключение будет происходить между активным и последним неактивным.

Но тут меня постигла небольшая неудача. Некоторые уже могли догадаться, что так дело не пойдет. В момент выполнения WTSConnectSession из приложения, отключение активного пользователя происходит, а вот включение второго пользователя – нет. Т.е. проще говоря, приложение одного пользователя не может инициировать вход другого пользователя. Но это может сделать служба! Да, очень жаль, но без системной службы у нас ничего не получится. Хорошо, создадим системную службу в которую закинем этот код. Вот тут-то и пригодится C# и .Net, так как написать службу на этих технологиях очень и очень просто. Теперь возникает следующая проблема: служба не имеет пользовательского интерфейса, т.е. пользователь не может напрямую повлиять на работу службы, а служба не может услышать действия пользователя. Навесить хоткей на службу нельзя.

Итак, вот наше решение:
Пользовательское приложение слушает пользователя, и при обнаружении хоткея, посылает сигнал системной службе, которая и выполняет переключение.

Осталось совсем немного, но и тут мне найдется что вам показать. Например то, что нам нужно десктопное приложение, без окон, но чтобы оно принимало хоткеи. Это можно сделать так, как делают все: Скрыть главное окно приложения и не показывать. Но есть решение лучше. Написать свой ApplicationContext. С блэк
Например такой:

internal class SUApplicationContext: ApplicationContext
{
    private Hotkey hk;
    private Form form;
    private const int SWITCH_USER_COMMAND = 193;
    internal SUApplicationContext()
    {
        // только создаем форму, она все равно нужна
        // чтобы слушать хоткеи
        form = new Form();

        // создаем глобальный хоткей Win+A
        hk = new Hotkey(Keys.A, false, false, false, true);

        // делегируем обработчик события
        hk.Pressed += delegate { SendSwitchCommand(); };

        // регистрируем хоткей, если можем
        if (hk.GetCanRegister(form))
            hk.Register(form);

        // Вешаем событие на выход
        Application.ApplicationExit += Application_ApplicationExit;
    }

    private void SendSwitchCommand()
    {
        // Описываем нашу службу
        ServiceController sc = new ServiceController("Sus");
        try
        {
            // посылаем ей команду
            sc.ExecuteCommand(SWITCH_USER_COMMAND);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    void Application_ApplicationExit(object sender, EventArgs e)
    {
        // при выходе разрегистрируем хоткей 
        if (hk.Registered)
            hk.Unregister();
    }
} 

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new SUApplicationContext());
}

Здесь я использую найденный на просторах интернета интерфейс MovablePython.Hotkey над user32.dll функциями RegisterHotKey, UnregisterHotKey.

И пару строк о самой службе.

protected override void OnCustomCommand(int command)
{
    base.OnCustomCommand(command);
    if (command == SWITCH_USER_COMMAND)
    {
        SwitchUser();
    }
}

Переопределяем событие OnCustomCommand, и при получении нашей команды, выполняем уже известную нам функцию.

Осталось зарегистрировать и запустить службу, и поставить в автозагрузку каждому пользователю приложение.

Все. Теперь после того, как вошел первый пользователь после запуска компьютера и нажал Win+A, его сеанс отключается, и появляется окно выбора пользователя. Входит второй пользователь, нажимает Win+A – появляется сеанс первого пользователя. И т. д.

На github вы можете ознакомиться с исходниками. Либо можете скачать весь проект и скомпилированные и готовые к работе исполняемые файлы.

Автор: indomit

Источник


  1. Ден:

    Огромнейшее спасибо!

  2. Владимир:

    Спасибо за прогу! Очень удобно пользоваться! Но с выходом Win10 все поменялось – Win+A теперь занята. Как можно теперь переназначить переключение скажем на Win+Z?

  3. Vasiliy:

    Это круто!

  4. Мудрик:

    Владимир, в этой строчке:

    hk = new Hotkey(Keys.A, false, false, false, true);

    можно менять хоткеи. Но при этом придётся перекомпилировать весь проект приложения (не сервиса).

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js