Very fast Windows user switching

And the idea was like that. Make quick user switching happen in one go. By pressing one hotkey. A search on the Internet (I recall it was about 3 years ago) bore fruit, and similar solutions were found. But, free either buggy, or required the installation of some third-party software. But paid, high-quality, one was found, and one worked very well, but, firstly, it was paid, and secondly, it contained extra functionality - the user did not immediately switch by pressing the hotkey, but a window was displayed (similar to Alt + Tab) with by users. It was decided to write your decision. The simplest, with a minimum of functionality: hotkey - switching.
Googling issued:
- To switch sessions, use the wtsapi32.dll functions: WTSEnumerateSessions , WTSConnectSession , WTSDisconnectSession (Now, when I look at the description of these functions, it says that it works with remote working sessions, and to be honest, I'm at a loss, but it works locally, perfectly) .
- For hotkeys, use the user32.dll functions: RegisterHotKey , UnregisterHotKey . Everything is simple here.
I’ll make a reservation right away, and you can throw tomatoes at me, but I wrote this thing in c #, although on the pros, it would certainly be better, more native, and so on, and so on, but ... But then I just started to learn c # and needed experience, and when the decision was written; there was no need to rewrite it, although it would not take more than one evening to transfer it.
So, for starters, a simple win32 application with a button was written, by clicking on which something like this code was executed:
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);
}
In two sessions, sessionInfo will have 3 elements: a services session, a 1st user session, a 2nd user session. Accordingly, targetSessId and activeSessId are uniquely determined. For sessions over 2, the switch will occur between the active and the last inactive.
But here I was struck by a small setback. Some could already guess that this would not work. At the time the WTSConnectSession is run from the application, the active user is disconnected, but the second user is not enabled. Those. simply put, the application of one user cannot initiate the entry of another user. But the service can do it! Yes, it's a pity, but without a system service, we won’t succeed. Well, create a system service into which we will drop this code. This is where C # and .Net come in handy, since writing a service on these technologies is very , very simple.. Now the following problem arises: the service does not have a user interface, i.e. the user cannot directly affect the operation of the service, and the service cannot hear the user's actions. You cannot hang a hotkey on a service.
So, here is our solution: The
user application listens to the user, and when a hotkey is detected, sends a signal to the system service, which performs the switch.
There is very little left, but here I can find something to show you. For example, what we need is a desktop application, without windows, but for it to accept hotkeys. This can be done as everyone does: Hide the main application window and do not show. But there is a better solution. Write your ApplicationContext .
For example this:
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());
}
Here I use the MovablePython.Hotkey interface found on the Internet over user32.dll functions RegisterHotKey, UnregisterHotKey.
And a couple of lines about the service itself.
protected override void OnCustomCommand(int command)
{
base.OnCustomCommand(command);
if (command == SWITCH_USER_COMMAND)
{
SwitchUser();
}
}
We redefine the OnCustomCommand event, and upon receiving our command, we execute the function we already know.
It remains to register and start the service, and put the application into startup at each user.
All. Now, after the first user has logged in after starting the computer and pressing Win + A, his session is disconnected and the user selection window appears. The second user enters, presses Win + A - the session of the first user appears. Etc.
On github you can familiarize yourself with the source . Or you can download the entire project and compiled and ready-to-work executable files.