MilliKeys - Programmer's Notes

Marketing descriptions...

This X-Master extension allows your Graffiti area to become a keyboard. Just print a picture of the keyboard on a sticker and slap it on your graffiti area. Please see the home page for more information.

This X-Master extension allows your Graffiti area to become a keyboard. Just print a picture of the keyboard on a sticker and slap it on your graffiti area. Each key is larger than those on the on-screen keyboard, yet you can input any letter with one tap, and input numbers, symbols and capital letters with one stroke. The software is also highly customizable; you can create and edit your own custom key layouts on the handheld itself.

Docs for void EvtGetPen (Int16 *pScreenX, Int16 *pScreenY, Boolean *pPenDown) say:
<- pScreenX x location relative to display

In fact the x coordinate appears to be relative to the top-left corner of the
current Form. Thus, with MilliKeys not adjusting the coordinates, modal dialogs
effectively move the nongraphic keyboard down and in the case of say, the Find
box, out of reach.

The penDownEvent documentation actually does say the coordinates of penDownEvent
are relative to the window, but I hadn't bothered to read that one...

Anyway, it seems there's at least two "windows", the draw and active windows,
which can be gotten from WinGetActiveWindow() and WinGetDrawWindow(). I will
assume for now that the DRAW window is the one we need to use, simply because
it's a lot easier to convert coordinates from the draw window rather than the
active window.


Order of average frequency of english letters--based on some kinda 'british corpus':


WriteToMemo(char* szText)
DmSearchStateType searchState;
UInt16 iCardNo;
LocalID memoDBID;
DmOpenRef memoDB;
Err error;
UInt16 iRecNum;
MemHandle record;
UInt16 iSize;
char *pRecord;

szText = FldGetTextPtr(pField);

error = DmGetNextDatabaseByTypeCreator (true, &searchState, 'DATA',
sysFileCMemo, true, &iCardNo, &memoDBID);
memoDB = DmOpenDatabase(iCardNo, memoDBID, dmModeReadWrite);

iSize = StrLen(szText)+1;
iRecNum = dmMaxRecordIndex;
record = DmNewRecord(memoDB, &iRecNum, iSize);
pRecord = MemHandleLock(record);
DmWrite(pRecord, 0, szText, iSize);
DmReleaseRecord(memoDB, iRecNum, true);


The patch for multi-segment debugging may come in handy:


This project implements a virtual keyboard on the silkscreen area of a PalmOS device. It supports multiple key layouts that are editable on the handheld itself; previewing on the screen; and macros to start programs or input keys. A 10x4 Qwerty layout is built in.


This project will implement a virtual keyboard on the silkscreen area of a PalmOS device. This requires that the program be a 'Hack', in order to intercept system calls, but as I'm having difficulty figuring out how to make it a hack, at the moment it is merely a standalone application, pre-alpha stage.

Anyway, its basic function works. You can input a layout in the Edit screen, then render it and use it on the Test screen. You can have multiple layouts; layouts can be duplicated, erased, and switched between. It has a "macro" feature; each macro can either input a series of keys or run a program, although the latter is not yet implemented.

A layout consists of up to five rows of keys; each row can have a different setting for height, key width, and alignment (width of the leftmost key).

Mind you, there's already a little program for getting keys on your silkscreen area, and it's called DotHack. [it's based on a previous GPL program, but for some reason source is not available...oh well] So why make another program? As well as being able to use the entire silkscreen area, not just the graffiti area--thus allowing an entire Qwerty keyboard to fit--MilliKeys supports "short strokes". A short stroke (as opposed to "big strokes", which I'll get to later) is a stroke where you press the pen down on a key, then move it in one of eight directions (up, right, up-right, etc.) and let go. This feature alone allows up to nine characters or actions to be represented by a single key. Additionally, when the program is finished it will support a shift and a caps lock, providing access to a predefined set of capital letter mappings, and a user-defined "extra" layout, which is kind of a user-defined shift/caps lock.

In the built-in layout the user can tap for lowercase keys; stroke up for uppercase; stroke down on the top row for numbers; stroke horizontally on the top row for punctuation (!@#$ instead of 1234); stroke down, left, or right on the second row for much more punctuation including extended characters; and stroke left anywhere on the bottom two rows for backspace. Finally, the "extra" layout contains accented characters. I have created a bitmap depicting this layout, which users can print out and tape on their PalmPilots.

It supports "big strokes"--strokes from the lower left or lower right to some other corner of the screen. The capability of a big stroke is the same as a macro.

Eventually I plan to implement a smart Graffiti passthrough feature where the program will detect irregular strokes and let Graffiti handle it.


const PenBtnInfoType *EvtGetPenBtnList (UInt16 *numButtons)
may come in handy
typedef struct PenBtnInfoType {
RectangleType boundsR;
WChar asciiCode;
UInt16 keyCode;
UInt16 modifiers;
} PenBtnInfoType;

void GsiSetShiftState (const UInt16 lockFlags, const UInt16 tempShift)
lockFlags glfCapsLock or glfNumLock.
tempShift The current temporary shift.
This function affects only the state of the UI element, not the underlying Graffiti engine.


I have found experimentally that:
The values for tempShift are 0 for none, 1 for dot (punctuation), 2 for backslash (as when you graffiti an X), and 3 for shift.
if tempShift is 1 to 3, lockFlags is basically ignored.
glfNumLock looks odd, the left side of the icon looks wrong. Hrm..

The SDK docs are very sparse, so I ran some little experiments on the pen queue. I did this in the emulator so results might not exactly match a real device. I found:
- You don't have to do anything to get points in the pen queue. As long as you track the pen's movement with EvtGetPen(), points will appear in the Pen queue. I noticed in SysEvtMgr.h in the comment for EvtEnqueuePenPoint:
// Append a point to the pen queue. Passing -1 for x and y means
// pen-up (terminate the current stroke). Called by digitizer interrupt routine
So apparently, the pen queue is a hardware queue, which explains why you don't need to put points in it...points just appear.
- I made the same stroke (I drew an imaginary circle with my mouse in about 2.5 sec) with a number of different pen tracking loops. Following the tracking loop, I put a loop to retrieve the pen stroke points:
EvtDequeuePenStrokeInfo (&SP, &EP);
TRACEL ("SP %hd %hd EP %hd %hd", SP.x, SP.y, EP.x, EP.y);
do {
EvtDequeuePenPoint (&P);
TRACEL ("PP %hd %hd", P.x, P.y);
} while (P.x != -1);
- Typical tracking loop.
while (IsPenDown) {
SysTaskDelay(SysTicksPerSecond() / 20);
EvtGetPen(&X, &Y, &IsPenDown);
TRACEL("%hd %hd", X, Y);
- Weird tracking Loop 1 (long delay)
while (IsPenDown) {
EvtGetPen(&X, &Y, &IsPenDown);
TRACEL("%hd %hd", X, Y);
- Weird tracking Loop 2 (many short delays)
while (IsPenDown) {
for (int n = 0; n < 20; n++)
SysTaskDelay(SysTicksPerSecond() / 20);
EvtGetPen(&X, &Y, &IsPenDown);
TRACEL("%hd %hd", X, Y);
- Weird tracking Loop 3 (two medium delays)
while (IsPenDown) {
for (int n = 0; n < 2; n++)
SysTaskDelay(SysTicksPerSecond() / 2);
EvtGetPen(&X, &Y, &IsPenDown);
TRACEL("%hd %hd", X, Y);
- Weird tracking Loop 4 (no delay)
while (IsPenDown) {
EvtGetPen(&X, &Y, &IsPenDown);
TRACEL("%hd %hd", X, Y);
- First, I found that the number of points obtained depends on the delay time.

Delay time Points in loop Points after loop
---------- -------------- -----------------
1000 msec  3              3
500 msec   5              5
50 msec    22             22
No delay   Tons!!         64

- The maximum number of points after the loop is 64: the pen queue never grows. In fact EvtPenQueueSize() always returns 256.
- If you have less than 64 points and some kind of delay in the loop, the points output in the loop are the same as the points output after the loop. One caveat: in the tracking loop you don't get the start point but you get the last point twice. Thus the number of points when tracking and the number of points afterward remain equal.
- If you run several delays between each EvtGetPen, EvtGetPen still claims the pen is down long after it is up. Basically, one point is put in the pen queue with each delay, and EvtGetPen insists on returning every available point. So for example, suppose:
- I delay for 4 seconds using 100ms delays without calling EvtGetPen.
- The user takes 2 seconds to make a stroke.
Then, there will be about 20 points in the pen queue. When you call EvtGetPen the first time, the user has already lifted the pen but EvtGenPen will return all 20 points before it lets you know the pen is up. This indicates EvtGetPen is getting the points from the queue and not, in fact, polling the hardware.
- If you use a normal tracking loop but your delay is too short (e.g. SysTaskDelay(1)), the pen queue will empty and EvtGetPen will return the same point multiple times before you get a new point. It would be good to find some optimal delay where you get a new point for each delay but you don't miss any points. (On a PC I would err on the side of too short a delay, but on a Palm a longer delay means power saving.) Unfortunately I don't know how to get this optimal delay, but I think I read in an SDK doc that the digitizer sampling rate was about 20ms.
- You cannot obtain a stroke without EvtGetPen(). You have to keep calling EvtGetPen() until it tells you the pen is up, otherwise (debug ROMs anyway), when EvtDequeuePenStrokeInfo is called, the OS will halt your program complaining that the "pen q out of sync". It will do this regardless of whether the pen is up or still down.
- If you call EvtDequeuePenStrokeInfo after your tracking loop, you will not get a penUpEvent.
- After retrieving points from the pen queue with EvtDequeuePenPoint, EvtFlushPenQueue must be called, even if you have already dequeued all the points. Otherwise, the next time the user taps the screen, a penDownEvent will fail to be generated. (However, penMoveEvents and a penUpEvent are still generated.)


More PalmOS documentation craptasticness!

Documentation for void WinGetBounds (WinHandle winH, RectangleType *rP) is confusing...

"This function returns in rP the bounds of the window represented by winH. This corresponds to the convention used by WinSetBounds, because it takes a window handle as an argument.

"Prior to Palm OS 4.0, WinGetBounds returned the bounds of the draw window, and did not take a window handle as an argument. If an application needed to determine the bounds of an arbitrary window, the application would call WinSetDrawWindow to temporarily set the draw window to the desired window, then WinGetBounds would be called to get the bounds of the draw window, then WinSetDrawWindow would be called again to restore the draw window. This is no longer necessary."

"Implemented only if 4.0 New Feature Set is NOT present. As of Palm OS 4.0, applications should use WinGetDrawWindowBounds."

Here we have it saying that WinGetBounds() has a new argument in 4.0, but paradoxically that it is not present in 4.0. I think what this may mean is that WinGetBounds as found in SDK 4 is new in OS 4.0, and that WinGetDrawWindowBounds, despite being labelled as "New!", is actually the original WinGetBounds with its name changed. Thus the ORIGINAL WinGetBounds is, in a sense, not present in 4.0. Meanwhile, the "New" function WinGetDrawWindowBounds is the only function of the two that is actually present in previous PalmOS versions.

I suspect I'll be seeing more of this error in time: error: initialization to `const char **' from `char **' adds cv-quals without intervening `const'

>> The code compiles anyway, but I`d like to understand what gcc is
>> trying to tell me, because I can`t see what`s wrong.
>> In function `void test()`:
>> warning: initialization to `const char **` from `char
>> **` adds cv-quals without intervening `const`
>> [line 479] char* init[] = {"This is", "a multiline", "string", 0};
>> [line 480] const char** i = init;
>> What`s wrong with that? init (itself changeable) points to a
>> changeable array of changeable strings. Then out of this I make i,
>> which points to an array of unchangeable strings. So what? I`m just
>> saying that I won`t change these strings via i. What`s the problem gcc
>> sees? And what are the "cv-quals" that are being added? What code
>> would express the same thing but make gcc shut up?

>Cv-quals are const/void qualifications, such as the const in your
>declaration of i.

>Adding cv-quals in the way that you have done allows unsafe code.
>For example, if the conversion you`d like (char** to const char**)
>were allowed, you could silently change constants:

> const char c = `c`;
> char* pc;
> const char** pcc = & pc; // conversion in question
> *pcc = & c;
> *pc = `?`;
> // What`s the value of c now?

>Gcc is right to warn you about this.

>What _is_ safe is to const-qualify _all_ intermediate levels of
>indirection. In your code,
> const char * const * i = init;
>should suffice.

>For a precise specification of this, see the latest Committee Draft
>for the forthcoming ISO C++ standard at
>(paragraph #4). Just beware of the unusual formatting, the HTML
>having been converted from something else.