Written and Maintained by Gregory Nacu


Last modified: Apr 26, 2019

Mouse and Keyboard

Input Module KERNAL Calls


C → Set = Skip load, just enable

Loads current system mouse cursor from disk.
Starts tracking mouse movements and events.


Turns off mouse cursor.
Stops tracking mouse movements and events.


Turns off mouse cursor.
Mouse cursor is automatically unhidden when the mouse moves.


A → Mouse Event Flags
X → X–Position (Pixels)
Y → Y–Position (Pixels)
A ← Mouse Event Flags
X ← X–Position (Text Column)
Y ← Y–Position (Text Row)

Takes a standard A/X/Y mouse event, and converts the X and Y from pixel precision to text column and row precision.


C ← Set on empty mouse queue
A ← Current Mouse Event Flags
X ← Current Mouse Event X–Position (Pixels)
Y ← Current Mouse Event Y–Position (Pixels)

Gets the current mouse event in the standard A/X/Y format.


C → Clear for normal dequeue

Dequeues the first mouse event and shifts the remaining buffer down one.

C → Set to rewrite the current mouse event
A → Mouse Event Flags
X → Mouse Event X–Position (Pixels)
Y → Mouse Event Y–Position (Pixels)

Updates the current mouse event with the values of A/X/Y.


C ← Set on empty key command queue
Y ← Key Event Flags

Gets the current key command event.


Dequeues the first key command event and shifts the remaining buffer down one.


C ← Set on empty printable key queue

Copies memory page A to memory page X.


Dequeues the first printable key event and shifts the remaining buffer down one.


Called automatically by the interrupt service routine.
Reads the mouse and updates the mouse cursor position and enqueues mouse events.
Reads the keyboard and enqueues key command and printable key events.

About C64 OS Input

The input module implements a 1351 compatible mouse proportional mouse driver. It's based on a modification of the code found in the back of the 1351 manual, which is also available from codebase64. The code in this example is very simple, and just outlines how to read the SID pots and use them to change the coordinates of a sprite.

C64 OS's mouse driver drives a two-sprite cursor, binds the cursor to the screen edges and implements a basic acceleration curve which can be end–user customized. The mouse driver also reads the mouse buttons using an exclusion technique similar to what is found in the keyboard key rollover routine. If the left button is depressed, activity on the right button is ignored, and vice versa. If the left button is depressed, then the right button, then the left is raised, and then the right is raised, the final right up is also ignored.

The input module also re-implements the keyboard driver, a key scanning routine that replaces the one built into the KERNAL. It's based on the 3-key rollover routine by Craig Bruce, which is also available from codebase64. Bruce's routine is designed to be a drop–in replacement and to remain compatible with software that makes use of the KERNAL's default key handling routines.

C64 OS's keyboard driver changes the way the modifier keys, Control, Commodore, Left Shift and Right Shift are handled. And divides keyboard activity into two broad types, key commands and printable characters (including some unmodified PETSCII control sequences, such as cursor keys, tab and carriage return.) Only PETSCII characters that can be readily mapped to 7-bit ASCII can be produced by C64 OS's keyboard driver. PETSCII graphic symbols cannot be generated by key presses, nor can colors be changed, reverse on/off be toggled nor character sets changed.

The input module is responsible for generating mouse and keyboard events, queueing and dequeueing those events.

For a technical deep dive into how C64 OS handles the mouse and keyboard, you can read in much more detail about how it works here.


Initmouse initializes and turns on the mouse cursor and sets a flag to enable the mouse driver to start tracking mouse movements, producing events and updating the position of the mouse cursor.

During the initmouse routine the mouse cursor's sprites are read in from one of several files in the pointers folder in the C64 OS system folder. The specific pointer file can be customized by the user and the setting is stored in the mouse settings file. The C64 OS loader automatically initializes the mouse everytime any application is loaded. You only need to explicitly initialize the mouse if your application has previously disabled it.

The pointer file can be customized to change the appearence of the mouse cursor. The color of each sprite can by customized and are stored in mouse settings file. The cursor uses two overlapping hires sprites to give the cursor a sharp outline. The top left corner of the sprites is used to determine the coordinates of the cursor when an event is generated.

The initmouse routine can be called at any time to re-read in the pointer file. This may be done to update the appearence of the mouse cursor during runtime.

Initmouse has no input parameters and doesn't return anything.


There may be some applications for C64 OS that do not need to use the mouse, and may want to reclaim the sprites for some other purpose. Killmouse has no input parameters and doesn't return anything. It can be called at any time.

It turns off the sprites via the VIC-II registers, and unsets the mouse active flag in the mouse driver. This causes the mouse driver to stop reading the SID pots, and to stop updating the position of the mouse cursor, and stops reading the mouse buttons or producing mouse events.

After calling killmouse, call initmouse to restore normal mouse functionality and resume generating mouse events.


Hidemouse turns off the mouse cursor sprites but leaves all other mouse related functionality intact. The mouse cursor sprites are automatically reenabled when the mouse is moved.

Typically this routine is only called internally by other C64 OS routines. The most obvious candidates for hiding the mouse are the text input widgets in the toolkit module. After clicking on a text field to input text, the mouse cursor is inconveniently left exactly above where the user is about to edit text. When the user starts typing the toolkit input widget may call this routine to temporarily hide the mouse pointer.

Hidemouse has no input parameters and does not return anything.


See more details about mouse events below.

A mouse event stores the coordinates of the cursor in pixels. Because the horizontal resolution of the screen (320px) exceeds 8-bits (0-255), a 9th bit is required which is stored in one of the bits of the mouse event flags.

Many applications, and most of the toolkit, do not require pixel precision. This routine is called to convert a mouse event's X and Y pixel coordinates into column and row coordinates. Since there are only 40 and 25 of these respectively they do not overflow 8-bits and are often much easier to work with.

The input parameters to mouserc are the same as the return parameters from readmouse. After calling readmouse .X and .Y hold the pixel coordinates of where the mouse was when the event was generated, and .A holds the event flags, including the 9th horizontal position bit. After calling mouserc the .A flags byte is left unchanged, but .X and .Y have been converted to column and row coordinates.

readmouse and deqmouse

See more details about the event model below.

The interrupt service routine calls polldevices which causes the mouse driver to scan the SID pots and the mouse buttons and automatically generates mouse events which it adds to a FIFO queue. Your application does not need to do anything to make the mouse generate events. The only thing you must avoid is masking interrupts. If interrupts are masked for more than 1/60th of a second the mouse tracking will drop frames and mouse button presses may be missed.

Whenever a mouse event is on the queue the main event loop calls the mouse event handling routines assigned to the screen layers. Events propagate down the screen layers until they reach the screen layer that a belongs to your application.

If the application does not implement a mouse handling routine then it will not receive automatic notices of waiting mouse events. It is still possible for the application to manually poll for the presence of mouse events, but doing so would only make sense under unusual circumstances.

When the application's screen layer's mouse handling routine is called no parameters are passed to it. Instead the application forwards the notice by calling some other routine that ought to handle mouse events, typically this is toolkit's hit testing routine, however the application may inject custom behaviors before passing control to the toolkit, or the application may not make use of toolkit at all.

Whatever routine ultimately needs to have the event specifics calls readmouse. Readmouse returns in .A, .X and .Y the 3-byte mouse event that is at the top of the queue. Readmouse does not modify the queue and will likely be called many times as a normal part of processing this one event.

When the application's mouse handling routine finally returns, the system's main event loop automatically dequeues the event by calling deqmouse. Deqmouse shouldn't ever need to be called explicitly by the application. Deqmouse takes no input parameters and doesn't return anything. It just removes the first event from the mouse event queue, if there is one, and shifts the others, if there are others, one place in the queue.

The queue can buffer up to 3 mouse events. There is a special system flag for precision mouse tracking. Precision tracking is off by default. In this mode, a mouse tracking event generated while another mouse event is already on the queue will overwrite any previous mouse tracking event. This prevents the queue from getting clogged up if processing a tracking event is computationally expensive, at the cost that some intermediate tracking positions will be lost. Usually this is the right behavior and won't even be notice, it will just feel right and keep the system feeling responsive. However, there are cases where you do not want to drop tracking events, namely when you're using the mouse for pixel–precise drawing. In these special cases precision tracking can be turned on.

Precision tracking is automatically disabled when a new application is loaded.

readkcmd and deqkcmd

The C64's KERNAL ROM already has a keyboard driver. However, it reduces held modifier keys to just one and then uses that to select a key decoding table. The non–modifier key pressed is used as an index into that table to find a single byte in PETSCII. That byte is added to a 10-byte keyboard queue in workspace memory. This is not how C64 OS handles keyboard input.

The C64 OS keyboard driver scans the keyboard, using the rollover technique devised and described in great detail by Craig Bruce in Commodore Hacking Issue #6. From all of the simultaneously depressed keys C64 OS pulls out the modifier keys. If the Control or Commodore key is held down with any other non–modifier key a 2–byte keyboard command event is generated. The keyboard command event is added to the keyboard command event queue. One byte is the unmodified PETSCII character and the other byte is used as bit flags for the four discrete modifier keys: Control, Commodore, Left Shift and Right Shift.

Whenever there is a key command event in the queue the main event loop calls the key command handling routine of the top most screen layer. The event then propagates down the screen layers just as mouse events do. The top most screen layer is always the menu system. Thus the application's menu options are given the first opportunity to match any key command event. If there is a match the menu system issues a menu action to the application and the key command event is prevented from propagating to lower screen levels. The main event loop handles dequeuing key command events automatically.

Aside: There is only one exception to this screen layer stacking rule. Sometimes the application will push a screen layer that it wants (or needs) to be modal. But the menu layer is always on top. For example, when menus open, they always render above all other screen layers. The problem is that if the application wants a screen layer to be modal, it doesn't want the menu layer to intercept key commands first, and then bypass the intermediate screen layers and send a menu command directly to the application. To handle this situation, there is a system modal flag. When the flag is set, the menu layer passes key command events through without testing them against the menu options. (The mouse can still interact with the menu bar and open the menus, but all menu options are greyed out and cannot be triggered by the mouse.) Consequently the key command events will by handled first by the the topmost layer beneath the menu layer. This layer can choose which events to propagate and which not to, and thus can implement modal or partial modal behavior.

If no menu option matches the key command event the event propagates to the next screen layer and eventually to the application's main screen layer, calling that screen layer's key command handling routine. If the application does not implement a key command event handler then it will not receive automatic notices of key command events. It is still possible for the application to poll for their presence, as it can with mouse events, but it would only make sense to do so under unusual circumstances.

The application's key command routine can perform any custom behaviour that it wants. To get the specifics of the key command event it calls readkcmd, the PETSCII value and flags byte are returned in A and Y.

Typically, if the application's UI is based on the toolkit, the application will forward the event notice to the toolkit. Toolkit then dispatches the event to the view which has keyboard focus. The notice is then passed up the view hierarchy and each view is given an opportunity to respond to that event. Typically these behaviors are not something the application needs to worry about as the toolkit knows how to handle and process key command events all on its own. Forwarding notice of the presence of a key command event does not involve copying or sending the key command event data, the routine that eventually needs to know about the event details calls readkcmd. And readcmd may end up being called many times in the process of figuring everything that is affected by it.

See more details about this in the weblog post Command and Control.

The main event loop automatically dequeues key command events by calling deqkcmd. Deqkcmd shouldn't ever need to be called explicitly by the application. Deqkcmd takes no input parameters and doesn't return anything. It just removes the first key command event from the queue, if there is one, and shifts the others, if there are others, down one place in the queue. The queue can buffer up to three keyboard command events.

Unmodified Key Commands and Modifier Keys

There are some subtleties to how the modifier keys are used. The left and right shift keys are handled independently and behave differently.

The reason is because the C64 has only 2 cursor keys, down and right. The C64 requires the use of shift to select cursor up and cursor left respectively. However, in modern operating systems, shift can be combined with cursor keys to change text selections.

Typically the shift keys, used without Commodore or Control, do not produce key command events but just the shifted PETSCII values. If the right shift key is combined with the cursor keys they will produce the PETSCII codes for cursor up and cursor left. However, there is a special case. If the left shift key is combined with a cursor key, rather than changing the direction of the cursor key, it will produce a key command event instead.

These key command events are handled by text editing widgets in the toolit to modify text selection ranges. Left and right shift keys are used independently. Only the right shift key can be used to change the cursor direction. The left shift key can then be combined with any cursor direction (Cursor-Down, Right-Shift-Cursor-Up, Cursor-Right, Right-Shift-Cursor-Left) to generate a left-shifted cursor key event in all four directions.

The F1 through F8 keys are also exceptional. They always produce key command events, even if they are pressed without any modifier keys. If a function key is pressed with other modifier keys, the modifier key flags are set in the key command event along with the function key as the value. This greatly extends the number of possible functions that the 4 function keys can perform. Commodore + F1, for example, is different than Control + F1, and both are different again from Commodore + Control + F1.

readkprnt and deqkprnt

Following on the description of key command events, when a key is pressed, if neither the Control key nor the Commodore key are held down, (and the left shift and cursor keys are not being used in concert nor an F-key,) then a printable key event will be generated.

When the cursor keys are not being used, the left and right shift keys are treated the same and select between the normal and shifted KERNAL key decode tables. The PETSCII character retrieved from the decode table is the complete 1-byte event, and is added to the event queue, which is the KERNAL's standard 10-byte keyboard buffer.

Whenever an event is in the printable key event queue the system's main event loop calls printable key event handling routines of the topmost screen layers. Similar with mouse and key command events, the event propagates down the screen layers. If the application's main screen layer does not implement this handling routine it will not receive automatic notice that they are there, but can still poll for their presence by periodically calling readkprnt.

If the event is allowed to propagate to the application's main screen layer, then that key event key event routine may handle the event manually to perform any given task. Typically, though, if the application's UI is based on the toolkit, the application will forward the printable key event notice to the toolkit. As with the other event types, passing notice does not involve copying or sending the event. The event handling routines are not passed any arguments. Whichever routine eventually needs to know the details of the current printable key event, it may call readkprnt.

Readkprnt does not modify the queue and will likely be called many times as different parts of the system handle the event. Readkprnt takes no input parameters and returns the single printable character byte in A.

When notice is given to toolkit, toolkit forwards the notice to the view with keyboard focus. Toolkit views know how to pass the notice up the view hierarchy. If a view in that hierarchy handles printable key events, it may use the event, for example, to insert a character into its back-stored string. The application does not need to play any roll in making this work as toolkit knows how to handle printable key events.

When the application's printable key event handling routine eventually returns, the main event loop automatically dequeues the printable key event by calling deqkprnt. The application should not ever have to call deqkprnt explicitly. Deqkprnt does not take any input parameters and it doesn't return anything. When it is called it removes the first printable key event from the queue, if there is one, and shifts the remaining printable key events, if there are any, one place in the queue.


Polldevices is what makes the mouse and keyboard drivers tick. It has to be called with some regularity in order for the mouse cursor to be responsive to physical mouse movements, and for characters typed on the keyboard to be noticed. Calling this routine represents one tick of the drivers. The drivers generate the three above–described types of events automatically as long as this routine continues to be called with regularity.

C64 OS's standard interrupt service routine automatically handles calling polldevices. The application should never have to call polldevices manually.

If the application masks interrupts, the mouse and keyboard will become unresponsive until interrupts are restored. This should be avoided unless it's absolutely necessary. Interrupts should never be masked for longer than 1/60th of a second or the user interface will feel choppy, sluggish or unresponsive.

Unlike the mouse driver, the keyboard driver cannot be disabled, however it can very efficiently detect that no keys are held down.

Mouse Events

An in–depth discussion of mouse events can be found here under the Mouse Events heading.

A mouse event consists of three bytes, X position, Y position and flags. The flags byte consists of an event type in the low 4-bits, 3-bits to represent the modifier keys, Left Shift, Control and Commodore. Bit 7, the last bit, is used to make the 8th bit of X position resolution.

A mouse event's coordinates can be converted to rows and columns by calling mouserc. See above for a description of mouserc.

Mouse events carry modifier key flags with them, this allows for clicking with a modifier key or key combination to be programmatically handled differently than an unmodified click.

Note: Right Shift is not available as a mouse event modifier key. The Right Shift key collides with the mouse buttons in the commodore key matrix. If the mouse button is depressed, it is fundamentally impossible to determine the state of the Right Shift key.

Key Events

An in–depth discussion of key events can be found here under the Keyboard Command Events heading, and further below under Printable Keyboard Events, plus additional notes on special key commands and cursor control.

Key Command Events

A key command event consists of two bytes, the PETSCII character value, and a modifier key flags byte. The modifier key flags are similar to the flags used by the KERNAL, however, C64 OS breaks out the Right Shift key as a separate flag.

The PETSCII value is extracted from the KERNAL key decode tables, standard and shifted, depending on whether a shift key is used. [TODO: confirm this. The PETSCII value might be always coming from the unshifted table.]

Function keys are always encoded as key command events. The PETSCII value of such an event is the F1 through F8 PETSCII codes ($85 through $8C). These can also be combined with modifier keys. However, it is impossible for F2, F4, F6 or F8 to not also be combined with at least one shift key, due to the fact that the C64 keyboard has only four physical F-Keys.

Cursor keys used in combination with the Left Shift key are encoded as key commands. The PETSCII value is the cursor key PETSCII values ($11/$91 down/up, $1D/$9D right/left).

Printable Key Events

An in–depth discussion of printable key events can be found here under the Printable Keyboard Events heading.

Key strokes which do not produce key command events produce printable key events. This consists mostly of standard and shifted alphanumeric characters and printable symbols.

Some single-byte PETSCII control sequences are handled as printable key events. Such as carriage return, home, insert, and cursors left, up, right and down. However, other sequences that are typically handled by the KERNAL's screen editor, such as changes of color, reverse on/off, changes of character set, and function keys are not queued as printable key events.

Some standard ASCII characters, most in wide use on the internet today, are not represented on the C64 Keybaord. And the PETSCII itself is actually missing these characters, replacing them instead with some custom graphics or alternative symbols. But, if C64 OS is to interact with the internet it needs a way to type these characters.

In the C64 KERNAL ROM's keyboard driver, there are 6 keys, three in the upper row and three in the row below that, which when combined with shift produce PETSCII graphic symbols:

plus, minus, pound
@, asterisk, up arrow

In C64 OS there is special logic which, combined with a custom characterset, map these shifted keys to the common ASCII characters we need. The rational for all of these and a more can be found in the in–depth discussion under the Printable Keyboard Events heading here.

Shift @ { (open brace)
Shift * } (close brace)
Shift Up Arrow ~ (tilde)
Shift £ | (pipe)
Shift - _ (underscore)
Shift + ` (backtick)

Next Section: Strings and Charsets

Table of Contents

This is a document in progress.
Check back again soon.