Well, over a year and a half later, I suppose I should release Spadille 1.6 eh?
Spadille dropped off the grid a bit after being put on the back burner a while ago. I stopped working on it during the middle of refactoring…which is a bad idea. It’s been ready for release for a long time now, but I just never released it. Why do today what you can put off until tomorrow? This version does not support OpenSpades yet, but I will release a quick patch shortly for it.
Abstracted Spadille client and SpadilleDotNet library
Added “Last Played” column
Added intermediate dialog when joining server
I’ll likely be releasing my library that Spadille is based on (along with a few other projects), SpadilleDotNet. It’s a C#/.NET (2.0 compliant) library that provides a lot of functionality for working with Ace of Spades Classic (Voxlap/OpenSpades), Build and Shoot, Ace of Spades 1.0, and more.
Additionally, I have a few more tools that I’ll be releasing here soon as well, as I start to dig through old projects that I’ve not released, so keep an eye out.
Here’s a quick little class for implementing global hotkeys in C#/.NET 2.0+.
As a bit of a preface, you could easily use [RegisterHotKey]http://msdn.microsoft.com/en-us/library/windows/desktop/ms646309(v=vs.85).aspx) and [UnregisterHotKey]http://msdn.microsoft.com/en-us/library/windows/desktop/ms646327(v=vs.85).aspx) respectively to accomplish something like this, but this has a few caveats:
You can’t register a key that has already been previously registered.
Some keys are reserved and cannot be registered, such as F12.
Although hacks exist, it’s not very ideal to easily implement with console applications due to the lack of a proper window handle.
Alternatively, we can use another WinAPI function: [GetAsyncKeyState]http://msdn.microsoft.com/en-us/library/windows/desktop/ms646293(v=vs.85).aspx). It requires a bit more manual work, but it’s simple in the end.
Note: Without getting into semantics about what constitutes a proper “hook”, I’m just going to refer to the process of monitoring/polling key events via GetAsyncKeyState() as “hooking”.
To keep things simple, the actually hooking process will use the [Keys Enumeration]http://msdn.microsoft.com/en-us/library/system.windows.forms.keys.aspx) and will marshall their underlying integral type during the P/Invoke. When a key is pressed, we will trigger the KeyPressed event.
We don’t want to limit the hooking to just basic keys, instead will allow optional modifier keys using the ModifierKeys enum.
Hooking & Unhooking
To hook a key, call the Hook() method, supplying the Keys value as well as any optional modifier keys. Additionally, you can provide a delegate to use for a callback for when the key is pressed.
To unhook a key, simple call the Unhook() method with the appropriate parameters and it will no longer be polled.
Internally, the hooked keys will be stored as KeyHook objects, which provide Keys and Modifiers properties.
For simple hotkeys, there won't be any collisions. However, when you start mixing and matching modifier keys, things can get a little messy. To alleviate this issue, the hooked keys are sorted internally using a custom [IComparer<T>](http://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx):
Basically, it just compares hooked keys based on the following:
To sort the list, we use a simple lambda expression with our comparer:
The underlying polling is based on a [SystemTimer.Timer]http://msdn.microsoft.com/en-us/library/system.timers.timer.aspx). According to [official Microsoft sources]http://msdn.microsoft.com/en-us/windows/hardware/gg463266.aspx), this has a resolution of 15.6ms:
The default timer resolution on Windows 7 is 15.6 milliseconds (ms). Some applications reduce this to 1 ms, which reduces the battery run time on mobile systems by as much as 25 percent.
I’m not about to perform a case steady on how fast a human can realistically type versus the timer interval, configure the interval as necessary. The polling itself can be enabled/disabled via the Enabled property. Additionally, polling is suppressed when hooking/unhooking keys.
During each tick, the key states are polled via PollKeyStates():
When using GetAsyncKeyState() you need to pay attention to the most and least significant bits:
If the function succeeds, the return value specifies whether the key was pressed since the last call to GetAsyncKeyState, and whether the key is currently up or down. If the most significant bit is set, the key is down, and if the least significant bit is set, the key was pressed after the previous call to GetAsyncKeyState. However, you should not rely on this last behavior.
Normally when polling like these, we would end up triggering multiple KeyPressed events within a few milliseconds apart from each other. We can use the return value to get around this.
A temporary List is created which contains all hooked keys which are currently pressed. This way we don't need to call GetAsyncKeyState() while iterating over the hooked keys and we can prevent hook collisions. This is where the key sorting from earlier comes into play. We don't want to prematurely trigger a KeyPressed event for a different hooked key than expected.
This isn’t necessarily a perfect solution but it works and is simple and flexible.
Random update time! I have a bad habit of not updating my applications for long periods of time…
A few weeks ago, I received a pingback from AddictiveTips where they wrote an articleon Tabster. I’m still not sure why I just now received the PingBack when the article was published on March 29th of this year. This isn’t the first time Tabster has been reviewed by any means (and has also been used in schools), but it brought the neglected project to my attention.
Well, much re-factoring later and Tabster 1.6 is ready.