Log Off at Shutdown

One of the standard commands in PinballY is "Power Off" (you can find this command on the Exit menu), which sends a request to Windows to shut down the system and turn off power. This makes it possible to go through a complete session (power on, play some games, power off) without having to interact with the Windows desktop at any point, which is nice for pin cab users, since it helps maintain the illusion that the pin cab is an arcade machine instead of a Windows PC.

But Windows being Windows, everyone has a unique system configuration with unique operating requirements, and this can sometimes affect the process you have to use to shut down your system. Indeed, someone raised this very point on the forums, reporting that his system is set up in such a way that he has to explicitly log off before shutting down. That makes the "Power Off" command problematic for him.

Using Javascript, we can change the way the "Power Off" command works to make it friendlier to this special situation. In particular, we can change it so that the "Power Off" command doesn't actually execute the shutdown, but simply logs off the current user. That will leave it to the user to press the power button to complete the shutdown after the logoff succeeds.

If you've read through any of the other examples, you've seen the basic approach for changing the way any command works: we write a listener for the command event, check the ID in the event object to see if it's the Command ID we want to override, and if so, carry out our special function. We can then call the preventDefault() method of the event object to cancel the normal system processing.

We should mention a couple of special features of our custom "log off" command before we get into the coding.

The first is that the standard Power Off command has a two-step process in the UI. The nominal "Power Off" command (command.PowerOff) doesn't actually carry out the operation: its only function is to bring up a second menu to confirm that the user really wants to shut down. I like the two-step process because I really hate it when I accidentally trigger this command - it takes so long to get the system back up and running if you do. So the real shutdown action doesn't happen on the initial PowerOff command, but on the confirming command from the second menu, command.PowerOffConfirm. That's the command we actually need to override in our event handler.

The second special feature of this customization is that we'll need to talk directly to Windows to carry out the logoff command. PinballY doesn't have its own built-in version of this command that we can call, so we have to negotiate this directly through the Windows APIs. Fortunately, PinballY's Javascript has a provision for this, which you can read all about in Calling Native DLLs. Even more fortunately, the Windows API for logging off is very simple, just a single function call.

One extra step that we should perform is to save any changes to the settings before initiating the Windows log-off. When we call the Windows log-off API, Windows immediately starts shutting down the current user session, which requires terminating all of the programs running under that session. Windows is supposed to give running programs a chance to shut down cleanly, but in practice this doesn't always happen; Windows sometimes just kills all of the running programs without any warning. In PinballY's case, this can be problematic, because it can interrupt PinballY in the middle of writing out any unsaved changes to the settings file. That can leave the settings file only partially written to disk, which is obviously bad. To avoid this risk, we can explicitly save settings updates before we even start the log-off procedure. That'll write everything safely to disk, and then mark the in-memory copy as in sync with the disk copy, so that PinballY won't try to write it again at program exit.

Here's the full code to override the Power Off command and make it perform a Log Off command instead.

// To log off, we just need to call ExitWindowsEx(0, 0) in the Windows API. // Bind the DLL function so that we can call it from Javascript when the // time arrives. let User32 = dllImport.bind("User32.dll", ` BOOL WINAPI ExitWindowsEx(UINT uFlags, DWORD dwReason); `); // Take over the Power Off Confirm command mainWindow.on("command", ev => { if (ev.id == command.PowerOffConfirm) { // save any settings changes if (optionSettings.isDirty()) optionSettings.save(); // log off Windows const EWX_LOGOFF = 0; const SHTDN_REASON_FLAG_PLANNED = 0x80000000; User32.ExitWindowsEx(EWX_LOGOFF, SHTDN_REASON_FLAG_PLANNED); // quit out of PinballY mainWindow.doCommand(command.Quit); // skip the normal Power Off command processing ev.preventDefault(); } });

Note that we only took over the execution of the command, without changing the way the command appears on menus in the UI. If you wanted to fix up the menu labels to match the new function, that's fairly straightforward. We just have to listen for the menuopen event and make some edits:

mainWindow.on("menuopen", ev => { ev.items.forEach(ele => { if (ele.cmd == command.PowerOff) { ele.title = "Log Off"; ev.menuUpdated = true; } else if (ele.cmd == command.PowerOffConfirm) { ele.title = "Confirm Log Off"; ev.menuUpdated = true; } }); });