Programming Pebbles
MultiCursor
by Brad Myers

MultiCursor is designed to investigate issues when each PDA user has their
own cursor. The PDA side of MultiCursor looks and works just
like Pebbles Remote Commander, but the PC side, instead of having all users
share the same cursor as in Remote Commander, with MultiCursor,
each user
has their own separate cursor. Special applications are required to accept
the multiple cursor messages, since the PC really has only a single cursor.
One such special application is
PebblesDraw, which we created using
the Amulet user interface toolkit. This page describes (to some extent) how
you can create your own multicursor application using the Pebbles multicursor
applications.
The MultiCursor PDA side and DLL files are downloaded with the rest of the Pebbles
applications in one big zip file. See
the main
downloading instructions.
However, this does not include the software support for developing your own
applications. That is in a separate file:
The zip file will expand to provide the following files:
-
Pebbles.h: The main Pebbles include file. This file mostly defines
the protocol between the PDA side and the PC side, so few of the definitions
in this file are directly needed.
-
pebblesmulticursor.h: The main include file for writing multicursor
applications. This defines the initialization procedure,
Init_Pebbles_MultiCursor, which should be called to register the Windows
window to which messages are sent, and two support procedures for finding
out the names of the users who are currently attached. It also defines the
Windows message type, PEBBLESMULTICURSORMESSAGE which is used to send all
Pebbles messages.
-
RemoteControlMessages.h: Defines all the codes used for the messages
from the PDA to the PC.
-
gemW_pebbles.cc: This is the file containing the code used by the
PebblesDraw application to interpret Pebbles events. It is included to give
you a guide and sample from which to create your own applications.
gemW_pebbles.cc will not compile or work as is for you since it depends on
the entire Amulet library. Instead, you will have to cut and paste the parts
that are useful.
Overview of MultiCursor Applications
In the RemoteCommander application, all the PDA messages are inserted into
the regular Windows event stream as if they were regular mouse and keyboard
events, but this doesn't allow support for multiple streams of input.
Therefore, in MultiCursor applications, the PDA messages are inserted into
the Windows stream as a special type of message, which the application
can then handle as appropriate.
To get this started, the application must register with the MultiCursor dll
the Windows window to which the messages should be sent. Since the dll is
being used by two different processes: the PebblesPC process to handle events
from the PDA, and your application, you must use a special mechanism to get
the process started. The following code should be used (taken from
find_dll_and_set_pebbles from gemW_pebbles.cc):
#define DLL_NAME "MultiCursor"
#define ENTRY_POINT "Init_Pebbles_MultiCursor"
typedef void (CALLBACK*EntryPoint) (HWND);
HWND hwnd = * main application window * ;
HINSTANCE hDLL = LoadLibrary (DLL_NAME);
if (!hDLL)
cerr << "Can't find dll library " << DLL_NAME <<endl;
EntryPoint proc = (EntryPoint)GetProcAddress(hDLL, ENTRY_POINT);
if (!proc) cerr << "Can't find procedure " << ENTRY_POINT
<< " in dll library " << DLL_NAME <<endl << flush;
proc (hwnd);
One of the kinds of messages that arrive announce when a new user has attached
to MultiCursor. You might want to find out what name this user was assigned.
The Get_Pebbles_User_Name procedure does this. To get this procedure's handle,
you would use the following:
#define PEBBLES_USER_NAME "Get_Pebbles_User_Name"
typedef char* (CALLBACK*GetPebblesUserNameType) (int);
GetPebblesUserNameType GetPebblesUserName;
GetPebblesUserName = (GetPebblesUserNameType)GetProcAddress(hDLL, PEBBLES_USER_NAME);
if (!GetPebblesUserName)
cerr << "Can't find procedure " << PEBBLES_USER_NAME
<< " in dll library " << DLL_NAME << endl << flush;
Get_Pebbles_User_Name takes a user_id which is numbered from 0, and returns the
user name if that user has ever run multicursor, or NULL if there is no such
valid user. If the user gives no value in the name field when the register
their COM port, then the name will be the empty string "", which is different
from NULL. The user_ids correspond to the position of the user in the PebblesPC
user list window, with 0 being the top item. The maximum user_id supported
by MultiCursor is MAX_MULTICURSOR_USERS which is 20 (even though PebblesPC
in general can support any number of users). For example, the following checks
for all currently registered users, and might be done immediately after the
initialization above:
for (user_index = 0; user_index < MAX_MULTICURSOR_USERS; user_index++) {
name = GetPebblesUserName(user_index);
if (name) ** then this user is valid
}
When a user runs MultiCursor on the PDA and sends a mouse or keyboard event,
the MultiCursor dll will send this event to the window hWnd registered in
the initialization call as a message of type PEBBLESMULTICURSORMESSAGE. This
might be handled as follows:
#pragma warning(disable : 4759) //'segment lost' warning off
LRESULT CALLBACK __export MainWndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam) {
switch (message) {
case WM_CREATE: //window create code
case WM_LBUTTONDOWN: // window left button down code
case ... // etc.
case PEBBLESMULTICURSORMESSAGE:
Am_Handle_Pebbles_Event(hWnd, wParam, lParam);
break;
}
The Am_Handle_Pebbles_Event code for pebblesdraw is in the gemW_pebbles.cc
sample file.
In general, the lParam and wParam encode the contents of the message, as
follows:
-
wParam is the user_id of the user who issued this event. This will be between
0 and MAX_MULTICURSOR_USERS.
-
lParam is the content of the message from the PDA. It is a 32 bit
word divided into 4 bytes of which 3 bytes are used:
-
The least significant byte is the message type, as defined in the file
RemoteControlMessages.h. Remember that it is unsigned:
unsigned char message_type = lParam & 0x000000FF; // lsb is command type
-
The next 2 bytes encode parameters of some of the commands. For example,
the CMD_MOVE and CMD_PEN_DOWN commands use these as the coordinates of the
stylus on the PDA screen. Remember that these are unsigned.
unsigned char x,y;
x = (lParam & 0x0000FF00) >> 8;
y = (lParam & 0x00FF0000) >> 16;
-
The CMD_KEYDOWN command uses the first byte to encode which key was hit:
char chr = (lParam & 0x0000FF00) >> 8;
When the type is CMD_NEW_USER, this signifies that there has been a change
of users: a user has started or stopped running the multicursor application
on the PDA. Often, you can ignore when users stop running multicursor, since
they will often switch back and forth among lots of applications on the PDA.
Which user has changed status is in the wParam value, and whether the user
has come or gone can be determined using GetPebblesUserName.
Maintained by: Brad A.
Myers