Pebbles PC

Programming Pebbles MultiCursor

From the Pittsburgh Pebbles PDA Project     

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.

Downloading the MultiCursor Support Software

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:

Support Files for Programming MultiCursor Applications

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.
  • 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. 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

    #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.
    Am_Handle_Pebbles_Event(hWnd, wParam, lParam);

The Am_Handle_Pebbles_Event code for pebblesdraw is in the 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.

Back to the main MultiCursor Page

Back to the Pebbles software Page

Back to the Pebbles main Page

Maintained by: Brad A. Myers