Хочу поделиться с вами лайфхаком, которым пользуюсь ежедневно уже на протяжении нескольких лет. Работает безупречно, сберегает время. Так повелось, что у нас с женой разные учетные записи на одном домашнем компьютере. Это удобно: у каждого свой рабочий стол, свои обои, предпочтения, настройки приложений, кукисы в браузере. Я даже не представляю сейчас, как можно работать под одной учеткой. Но (без этого “но” не было бы и статьи), есть одна маленькая проблема. Переключение пользователей. Как это делается обычно: Пуск –> некая кнопочка, в зависимости от системы -> сменить пользователя. Появляется экран выбора пользователя. Тыкаем в нужного пользователя. Да, есть сочетание клавиш 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 вы можете ознакомиться с исходниками. Либо можете скачать весь проект и скомпилированные и готовые к работе исполняемые файлы.
