Хочу поделиться с вами лайфхаком, которым пользуюсь ежедневно уже на протяжении нескольких лет. Работает безупречно, сберегает время. Так повелось, что у нас с женой разные учетные записи на одном домашнем компьютере. Это удобно: у каждого свой рабочий стол, свои обои, предпочтения, настройки приложений, кукисы в браузере. Я даже не представляю сейчас, как можно работать под одной учеткой. Но (без этого “но” не было бы и статьи), есть одна маленькая проблема. Переключение пользователей. Как это делается обычно: Пуск –> некая кнопочка, в зависимости от системы -> сменить пользователя. Появляется экран выбора пользователя. Тыкаем в нужного пользователя. Да, есть сочетание клавиш 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
Огромнейшее спасибо!
Спасибо за прогу! Очень удобно пользоваться! Но с выходом Win10 все поменялось – Win+A теперь занята. Как можно теперь переназначить переключение скажем на Win+Z?
Это круто!
Владимир, в этой строчке:
hk = new Hotkey(Keys.A, false, false, false, true);
можно менять хоткеи. Но при этом придётся перекомпилировать весь проект приложения (не сервиса).