Skip to content

A much-improved special character palette

A while back, I created a pop-up character palette using Keyboard Maestro to allow easy insertion of the Mac's special characters (like , ⌘, ⌥, etc.). While this worked fine, I discovered a few major shortcomings:

  • I couldn't create more than one character without calling up the palette again.
  • I had to decide in advance if I wanted HTML entities or the actual characters.
  • Two palettes (HTML or character) meant two keyboard shortcuts to remember.
  • Adding characters to the palette was a real pain, because they had to be done twice.
  • I was out of digits for shortcuts, so I was going to have to change the palette structure.
  • It was slow: From calling up the palette to identifying which icon I wanted to use to selecting that icon, and then doing it all again for a second character was just really slow.

I set out to fix all of these issues, thinking I could use Keyboard Maestro's Custom HTML Prompt action, as I did for my iTunes song info window. And, in the end, that's what I used for the new-and-improved character palette:

This doesn't have to be used just for Mac special characters, of course. You could make yourself a customized pop-up for emoji, math symbols, whatever…

Read on for the how-to and download, if you'd like to put this to use…

To follow along, download the macro and open it in Keyboard Maestro. Before you use it, though, make sure you read the Don't use my code! section below, which explains how to replace my code with code that's much easier to customize.

The macro itself looks like this:

The first step—the semaphore lock—is there to prevent more than one palette from being activated at the same time. It's opposite, semaphore unlock, is the last step of the macro. The next two steps empty the variables that hold the HTML and characters, so the pop-up always appears blank. Step three sets the default return mode to HTML, because that's what I use most often—change it to Chars to change the default, or set it to %Delete% to not have a default.

Step five is the actual Custom HTML Prompt code, which I'll cover shortly. Step six is a conditional that loads the clipboard with either the HTML codes or the characters, depending on the chosen mode. After that, the clipboard is pasted, and the variables are cleared. I've been using this for a week or so, and it works really well.

Now, about the code…as with most of my coding projects, this one was a trial and error exercise, with multiple errors per trial. I had to learn a lot about trapping keystrokes in Javascript, and array variables, and other such details. But I did eventually get it working.

The first working version was particularly horrid, using tables for the button layout, which made updating the keys a major hassle. The second version, reproduced below and included with the downloaded macro, is better: Adding buttons no longer requires updating a table-based layout, but you still do have to tweak two sections of the code.

Which leads me to the next section, and my advice to please delete my code. (In the section on modifying the palette, the instructions assume you have, in fact, replaced my code.)

Don't use my code

Even though my code is reproduced below, and included in the downloaded macro, I recommend you do not use it. There's nothing wrong with it functionally—it works perfectly well. But my friend James took a look at my script, and did a little bit of work on it to make it much better. And by "did a little bit of work on it," I mean that he replaced 95% of my code.

The end result is a very easily-modified script that requires adding or changing only one line of code for each key you'd like to add or modify. So instead of using my macro as downloaded, you should download my macro, open it, then replace the contents of the Custom HTML Prompt step with James' code: [download better Custom HTML Prompt code]

So if James' code is better in every way, why did I include mine? Because if you're new to Javascript, my version (as I'm also new to Javascript) may be easier to follow as you learn. Once you master the basics (I'm not there yet), then you can move on to try to fully understand James' code (I'm definitely not there). So basically, my code is for learning what not do do, then you can look at James' code to see what to do.

My code and how it works

With that out of the way, here's the code in my version. This is included in the downloaded macro, and you don't need to read any further unless you're curious about how I made this work. (I'm writing this section primarily for my own use, so I'll understand what I did when I look back at it in the future.)

The CSS

Like Javascript, I know just enough CSS to be dangerous to myself; I'm certain my structure could be much cleaner than it is. Here are some of the bits that I'll be likely to forget if I don't document…

The shortcut keys that appear on mouseover

Each shortcut key is in a span tag, which defines the size, color, and position—absolutely positioned within each relatively-positioned button object, and set not to display by default:

button span {
  font-size:10px;
  position: absolute;
  top: -1px;
  left: -2px;
  color:red;
  display: none; }

To make them visible on mouseover, the body gains a hover behavior, but only for items in span tags—and the only thing on the form in span tags are the shortcut keys:

body:hover span { display:inline; }

The input fields

In order to handle both the HTML codes for the special characters and the special characters themselves, there are two input boxes on the form: The green visible box at the top, which displays the characters as you type, and an invisible box that holds the HTML code for each character.

<input disabled name="specialChars" class="input" type="text" id="sChars"/>
<input disabld name="specialHTML" class="input" type="text" id="sHtml"/>

Both input boxes are set to disabled, as they're not designed for user interaction. The hiding of the HTML field is handled by a very simple bit of CSS:

#sHtml { display: none; }

The remainder of the CSS is pretty basic stuff, just positioning the buttons and text.

The Javascript and HTML

The Javascript code traps keystrokes and then passes a click action to the button associated with a particular keystroke.

The my_keys array holds all the keyboard shortcuts; you can change these as you wish (though I'd suggest leaving Return and Escape alone). The values are the ASCII representations of each letter, and in ASCII, the "A" is character 65:

...
my_keys[67]="CmdKey"; // C
my_keys[75]="CtrlKey"; // K
my_keys[79]="OptKey"; // O
...

So if you wanted to use K for Command and C for control, you'd just swap the two values (67, 75) and that'd be that. (The double-slashes are comment markers, and I added the keys that are represented by the values; it's much easier to remember that way.)

window.addEventListener('keydown', checkKey);

This is the keyboard trap; it listens for key presses (not releases), and then runs a routine named checkKey to process the keys. That function is quite short (though it was much longer the first time I wrote this):

function checkKey(e) {
  event.preventDefault();
  if (typeof my_keys[e.keyCode] !== 'undefined') {
    document.getElementById(my_keys[e.keyCode]).click();
  }
}

The first line prevents the default action of the form. I honestly have no idea what that action is, but I know that if I don't do that, the form will beep every time you touch a key.

The second line checks the value of the trapped keystroke, and as long as it's defined in the array of my_keys, it proceeds to the document.getElement…. This step is the key to the code: document.getElementbyId() returns the element on the page identified by whatever's within the parentheses.

In this case, what's in the parentheses is the value associated with the matching key code from the my_keys array. In the case of the "C" key as shown above, that's "CmdKey." Once this command retrieves this element, it sends it a mouse click (the .click() bit at the end).

The code for the button (CmdKey in this example) looks like this:

<button id="CmdKey" onclick="pasteVals('?','&#x2318;');">
  ⌘<span>C</span>
</button>

The onclick bit is an HTML trigger that tells the button what to do when it gets a mouse click event. And what it does is call a Javascript function called pasteVals, passing it to the fields for the HTML representation of the character and the character itself:

function pasteVals(theChar, theHTML) {
  document.forms['myForm'].specialChars.value += theChar;
  document.forms['myForm'].specialHTML.value += theHTML;
}

These two lines are what populate the character and HTML fields, the += is a shorthand way of saying "This field = This field's existing contents + this new content," which is how the string of characters grows each time you "press" a button.

And that's really all there is to the code.

Modifying the palette

If you'd like to modify the palette, the first step is to make sure you're using James' much improved version. Seriously—modifying my code is feasible, but it's a fair bit of work if you're going to modify many of the buttons.

Assuming you've installed James' code, here's all you need to do customize it:

  1. If you're going to be adding another row (or two or three) of icons, you'll need to make the palette longer. In the CSS section of the code, modify this line:

    <body data-kmwindow="MOUSEX(),MOUSEY(),230,320">

    The last number (320) is the height of the window in pixels. Experiment to find a value that works for you.

  2. All of the symbols, their shortcut assignments, and their HTML and character representations are done in one line per symbol:

    ['83', ['⇧', '&#x21E7;', 'S']],

    The first number is the ASCII character code, where 65 is A. There are many sites that will display the codes as you type, if you don't want to do the math; here's one I've used.

    The next thing to modify is the symbol (⇧ in this example), and the third is the Unicode representation of that character (&#x21E7;).

    You can find this using the Emoji & Symbols viewer in macOS—the value appears below the character on the right side of the window, as seen at right. (If you don't care about the HTML values, you can just replace this bit with ''.)

    Finally, the last value is the shortcut key that you'll use to insert the symbol into the output area. This character must match the ASCII value you entered earlier—in this example, the "S" key has an ASCII value of 83.

To add more buttons, just copy and paste an existing row and edit as necessary. Buttons are added horizontally first, then vertically, based on the order they're listed in the script. And that's really all there is to it. Compared to editing my code, this is miles easier! So thanks, James, for the greatly-improved code!

As always, let me know if you have any questions.

11 thoughts on “A much-improved special character palette”

  1. Uh-oh... the link to James' much-improved version (in the 'Modifying the Palette' section) is broken :)

    1. It's working fine here—I wonder if you managed to load a version of the article that I edited, and Safari is showing the cached version. Can you empty the cache and reload?

      -rob.

    1. My bad! It was linked in two spots; the second one was wrong. I've fixed it now.

      Sorry about that!
      -rob.

  2. Sorry for my late answer. As you already said it's working now. Finally also thanks for this and the latest (great) posts.

  3. Giving up the ability to cancel the palette using the escape key is the only way to add "?" to the button list, right?

    1. Did you mean the actual question mark, as shown? Or did you mean the Escape key itself? If so, then right.

      -rob.

      1. Here's the AppleScript I've been using. You can select one using down arrow, and more than one by adding shift if they're consecutive. If not, you have to use the command key and the mouse or trackpad.

        tell application "System Events"
        set frontApp to name of first process whose frontmost is true
        end tell

        set theCommand to «data utxt2318» as Unicode text
        set theControl to «data utxt2303» as Unicode text
        set theOption to «data utxt2325» as Unicode text
        set theShift to «data utxt21E7» as Unicode text
        set theEscape to «data utxt238B» as Unicode text
        set theTab to «data utxt21E5» as Unicode text
        set theReturn to «data utxt21A9» as Unicode text
        set theEnter to «data utxt2324» as Unicode text
        set ForwardDelete to «data utxt2326» as Unicode text
        set BackSpaceDelete to «data utxt232B» as Unicode text
        set doubleTab to tab & tab

        tell application frontApp
        choose from list {theShift & doubleTab & "Shift", theControl & doubleTab & "Control", theOption & doubleTab & "Option", theCommand & doubleTab & "Command", theOption & theCommand & doubleTab & "Option+Command", theEscape & doubleTab & "Escape", theTab & doubleTab & "Tab", theReturn & doubleTab & "Return", theEnter & doubleTab & "Enter", ForwardDelete & doubleTab & "Forward Delete", BackSpaceDelete & doubleTab & "BackSpace"} with prompt "Pick the symbols you want:" OK button name "Insert" with multiple selections allowed
        end tell

        if result is not equal to false then
        set pickedSymbols to result as string
        set displaySymbols to ""
        if pickedSymbols contains "Shift" then
        set displaySymbols to displaySymbols & theShift
        end if

        if pickedSymbols contains "Control" then
        set displaySymbols to displaySymbols & theControl
        end if

        if pickedSymbols contains "Option" then
        set displaySymbols to displaySymbols & theOption
        end if

        if pickedSymbols contains "Command" then
        set displaySymbols to displaySymbols & theCommand
        end if

        if pickedSymbols contains "Escape" then
        set displaySymbols to displaySymbols & theEscape
        end if
        if pickedSymbols contains "Tab" then
        set displaySymbols to displaySymbols & theTab
        end if
        if pickedSymbols contains "Return" then
        set displaySymbols to displaySymbols & theReturn
        end if
        if pickedSymbols contains "Enter" then
        set displaySymbols to displaySymbols & theEnter
        end if
        if pickedSymbols contains "Forward Delete" then
        set displaySymbols to displaySymbols & ForwardDelete
        end if
        if pickedSymbols contains "BackSpace" then
        set displaySymbols to displaySymbols & BackSpaceDelete
        end if
        tell application "System Events"
        tell process frontApp
        set the clipboard to displaySymbols
        keystroke "v" using {command down}
        end tell
        end tell
        end if

Comments are closed.