Lately, the internet has been abuzz with the recent “Hour of Code” campaign. For those unaware, this is a campaign that aims to introduce people (largely children), to the world of programming in order to “jump-start their futures”. The campaign features well-renowned programmers such as Ashton Kutcher, Shakira, and Barack Obama. Now I’m not attempting make light of Obama’s apparent bubble sort knowledge, but it just feels far too superficial. Additionally, I’m not necessarily saying that these individuals don’t know or can’t learn how to program, but all of these campaigns over years have been the same. You grab a few celebrities who quite obviously aren’t interested in this sort of thing and try to sell it to the audience. It’s quite painful for me to watch.

A while back, Jeff Atwood wrote a nice articleon a subject very similar regarding CodeAcademy’s New Year’s resolution “Code Year”. That article is directly applicable to this situation as well for the most part.

Initially as a programmer, it’s easy to assume everybody should learn to code. After all, it’d make the world a better place right? Surely programming must be an essential skill set at this point that everybody should have?

No. It’s a valuable skill set, but not a truly essential one.

The real value of programming

Programming is many things, but at the end of the day it boils down to solving problems. It’s not about memorizing syntax and bashing out code, there’s a thought process behind it. Truly learning to program will give you insight on a whole new way to analyze and solve problems. In the process of learning to code, you will in turn gain a better understanding and respect for the various levels of technology upon which you are working with. It could help you more easily debug a problem you are having with say your computer or smart phone. Relatively mundane tasks can be simplified in day to day situations with just a little bit of programming knowledge.

While I can understand the thought process behind teaching everybody how to program, I am not convinced that it is necessary. I think schools shouldn’t necessarily be focusing on teaching children to program, but rather teach them some general computer/internet concepts  (typing classes do not count). That would be far more valuable to the average person compared to teaching them what a while loop is.

Growing up, I didn’t have any school courses that pertained to programming (or computers at all) although I was very interested in the subject. Had they been offered, I would’ve immediately signed up.

If you have even the slightest interest in learning to code, do it. You will need to have patience and be persistent about it. It’s not something that happens overnight. If you really feel as though it’s not for you, that very well may be the case. However, if it is something that are genuinely interested in, here are a few tips:

  • Be patient. Trial and error can be the best instructor at times
  • Try to work on something you are truly passionate about
  • Don’t obsess over the language, framework, or platform
  • Google is your friend, but don’t become too dependent

Programming is not for everybody. Don’t force people, especially children, to learn to code. If you don’t have a passion to code, it will be a long and strenuous process. By forcing them to learn it, it could very well turn them off from it entirely. Let them be exposed to it on their own volition by providing the necessary tools and guidance should they be interested.

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.

You can download Spadille 1.6 here.

Changelog:

  • Countless minor UI changes
  • Fixed a bunch of minor bugs
  • Improved memory usage
  • Improved threading
  • Rewrote screenshot hooking, caching and manager
  • Added dynamically populated server filters
  • Added share link
  • Added autocomplete to server browser
  • Added mumble link
  • Removed deprecated dependencies
  • Fixed support for Build and Shoot
  • 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.

Modifier Keys

We don’t want to limit the hooking to just basic keys, instead will allow optional modifier keys using the ModifierKeys enum.

[Flags]
public enum ModifierKeys : uint
{
    None = 0,
    Alt = 1,
    Control = 2,
    Shift = 4,
}

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.

[Flags]
var keyboard = new KeyboardHook();
keyboard.Hook(Keys.PrintScreen);
 
// do something
 
keyboard.Unhook(Keys.PrintScreen);

Internally, the hooked keys will be stored as KeyHook objects, which provide Keys and Modifiers properties.

private class KeyHook
{
    public KeyHook(Keys key, ModifierKeys modifiers, KeyHookDelegate pressed = null)
    {
        Key = key;
        Modifiers = modifiers;
        Pressed = pressed;
    }
 
    public Keys Key { get; private set; }
    public ModifierKeys Modifiers { get; private set; }
    public KeyHookDelegate Pressed { get; private set; }
}

Prioritization

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):
private class HookComparer : IComparer<KeyHook>
{
    private static int GetModifierCount(ModifierKeys modifiers)
    {
        var iValue = (int) modifiers;
        var bitCount = 0;
 
        while (iValue != 0)
        {
            iValue = iValue & (iValue - 1);
            bitCount++;
        }
 
        return bitCount;
    }
 
    #region Implementation of IComparer<KeyHook>
 
    public int Compare(KeyHook x, KeyHook y)
    {
        var keyCompare = x.Key.CompareTo(y.Key);
        if (keyCompare != 0)
            return keyCompare;
 
        var modifierCount = GetModifierCount(y.Modifiers).CompareTo(GetModifierCount(x.Modifiers));
        if (modifierCount != 0)
            return modifierCount;
 
        return ((int) x.Modifiers).CompareTo((int) y.Modifiers);
    }
 
    #endregion
}

Basically, it just compares hooked keys based on the following:

  1. (Keys](http://msdn.microsoft.com/en-us/library/system.windows.forms.keys.aspx) enumeration
  2. Number of ModiferKeys set
  3. Underlying ModifierKeys integral type summation

To sort the list, we use a simple lambda expression with our comparer:

_keys.Sort((k1, k2) => new HookComparer().Compare(k1, k2));

Polling

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():

private void PollKeyStates()
{
    var altPressed = Convert.ToBoolean(GetAsyncKeyState(Keys.Menu));
    var controlPressed = Convert.ToBoolean(GetAsyncKeyState(Keys.ControlKey));
    var shiftPressed = Convert.ToBoolean(GetAsyncKeyState(Keys.ShiftKey));
 
    var pressedKeys = new List<Keys>();
 
    foreach (var key in _keys)
    {
        if ((GetAsyncKeyState(key.Key) == -32767))
            pressedKeys.Add(key.Key);
    }
 
    foreach (var key in _keys)
    {
        if (!pressedKeys.Contains(key.Key))
            continue;
 
        if ((key.Modifiers & ModifierKeys.None) == ModifierKeys.None)
        {
            if ((key.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt && !altPressed)
                continue;
            if ((key.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && !controlPressed)
                continue;
            if ((key.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift && !shiftPressed)
                continue;
        }
 
        pressedKeys.Remove(key.Key);
 
        if (KeyPressed != null)
            KeyPressed(this, new KeyPressedEventArgs(key.Modifiers, key.Key));
 
        if (key.Pressed != null)
            key.Pressed(this, EventArgs.Empty);
    }
}

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.

Finally, here’s the complete class:

/*
* KeyboardHook.cs by Nate Shoffner
* http://nateshoffner.com
*/
 
#region
 
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows.Forms;
using Timer = System.Timers.Timer;
 
#endregion
 
namespace GlobalHotkeys
{
    [Flags]
    public enum ModifierKeys : uint
    {
        None = 0,
        Alt = 1,
        Control = 2,
        Shift = 4,
    }
 
    public class KeyPressedEventArgs : EventArgs
    {
        internal KeyPressedEventArgs(ModifierKeys modifiers, Keys key)
        {
            Modifiers = modifiers;
            Key = key;
        }
 
        public Keys Key { get; private set; }
        public ModifierKeys Modifiers { get; private set; }
    }
 
    public class KeyboardHook : IDisposable
    {
        #region P/Invokes
 
        [DllImport("user32.dll")]
        private static extern short GetAsyncKeyState(Keys vKey);
 
        #endregion
 
        #region Delegates
 
        public delegate void KeyHookDelegate(object sender, EventArgs e);
 
        #endregion
 
        private readonly List<KeyHook> _keys = new List<KeyHook>();
        private readonly Timer _timer;
 
        public KeyboardHook()
        {
            _timer = new Timer {Interval = 75};
            _timer.Elapsed += _timer_Elapsed;
        }
 
        public bool Enabled
        {
            get { return _timer.Enabled; }
 
            set
            {
                if (value)
                    _timer.Start();
                else
                    _timer.Stop();
            }
        }
 
        public event EventHandler<KeyPressedEventArgs> KeyPressed;
 
        public bool Hook(Keys key, ModifierKeys modifiers = ModifierKeys.None, KeyHookDelegate pressed = null)
        {
            if (_timer.Enabled)
                _timer.Stop();
 
            var exists = _keys.Find(x => x.Key == key && x.Modifiers == modifiers) != null;
 
            if (!exists)
            {
                _keys.Add(new KeyHook(key, modifiers, pressed));
                _keys.Sort((k1, k2) => new HookComparer().Compare(k1, k2));
            }
 
            if (_keys.Count > 0)
                _timer.Start();
 
            return !exists;
        }
 
        public bool Unhook(Keys key, ModifierKeys modifiers = ModifierKeys.None)
        {
            if (_timer.Enabled)
                _timer.Stop();
 
            var i = _keys.FindIndex(x => x.Key == key && x.Modifiers == modifiers);
 
            if (i >= 0)
            {
                _keys.RemoveAt(i);
                return true;
            }
 
            if (_keys.Count > 0)
                _timer.Start();
 
            return false;
        }
 
        private void _timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            PollKeyStates();
        }
 
        private void PollKeyStates()
        {
            var altPressed = Convert.ToBoolean(GetAsyncKeyState(Keys.Menu));
            var controlPressed = Convert.ToBoolean(GetAsyncKeyState(Keys.ControlKey));
            var shiftPressed = Convert.ToBoolean(GetAsyncKeyState(Keys.ShiftKey));
 
            var pressedKeys = new List<Keys>();
 
            foreach (var key in _keys)
            {
                if ((GetAsyncKeyState(key.Key) == -32767))
                    pressedKeys.Add(key.Key);
            }
 
            foreach (var key in _keys)
            {
                if (!pressedKeys.Contains(key.Key))
                    continue;
 
                if ((key.Modifiers & ModifierKeys.None) == ModifierKeys.None)
                {
                    if ((key.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt && !altPressed)
                        continue;
                    if ((key.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && !controlPressed)
                        continue;
                    if ((key.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift && !shiftPressed)
                        continue;
                }
 
                pressedKeys.Remove(key.Key);
 
                if (KeyPressed != null)
                    KeyPressed(this, new KeyPressedEventArgs(key.Modifiers, key.Key));
            }
        }
 
        #region Nested type: HookComparer
 
        private class HookComparer : IComparer<KeyHook>
        {
            private static int GetModifierCount(ModifierKeys modifiers)
            {
                var iValue = (int) modifiers;
                var bitCount = 0;
 
                while (iValue != 0)
                {
                    iValue = iValue & (iValue - 1);
                    bitCount++;
                }
 
                return bitCount;
            }
 
            #region Implementation of IComparer<KeyHook>
 
            public int Compare(KeyHook x, KeyHook y)
            {
                var keyCompare = x.Key.CompareTo(y.Key);
                if (keyCompare != 0)
                    return keyCompare;
 
                var modifierCount = GetModifierCount(y.Modifiers).CompareTo(GetModifierCount(x.Modifiers));
                if (modifierCount != 0)
                    return modifierCount;
 
                return ((int) x.Modifiers).CompareTo((int) y.Modifiers);
            }
 
            #endregion
        }
 
        #endregion
 
        #region Implementation of IDisposable
 
        public void Dispose()
        {
            _timer.Stop();
            _timer.Dispose();
        }
 
        #endregion
 
        #region Nested type: KeyHook
 
        private class KeyHook
        {
            public KeyHook(Keys key, ModifierKeys modifiers)
            {
                Key = key;
                Modifiers = modifiers;
            }
 
            public Keys Key { get; private set; }
            public ModifierKeys Modifiers { get; private set; }
        }
 
        #endregion
    }
}
5