Bug in VNC with NumLock key

Mickey Ferguson MFerguson "at" peinc.com
Wed, 08 Nov 2000 19:42:18 +0000


[apologies for upcoming long message]

James ''Wez'' Weatherall [mailto:jnw "at" uk.research.att.com] wrote:

>On Wed, 8 Nov 2000, Mickey Ferguson wrote:
> >I'm running WinVNC Version 3.3.3 R 7 on NT 4.0 with Service Pack 6 on
> >both server and client.  When I connect from to server, the NumLock
> >indicator (and functionality) gets turned off.  Worse yet, it doesn't
> >even get turned back on when I disconnect.

>WinVNC disables the NumLock setting on connect, as part of its attempt to
>set the keyboard to a known initial state.

> >Ideally I think the server's NumLock state should be set based on the
> >client's, and then when the connection is broken the state is
> >restored.

>This is tricky.  Reading the lock state is non-trivial because of the
>keyboard API and the messages from the client are all state _change_
>events - the server can't initially query the client for the state.

I'm not sure this is entirely correct.  Well, sort of, but sort of not, if
you know what I mean.  :)  When a user logs on, there is a setting in the
user portion of the registry that defines whether the NumLock key was turned
on or off when the user logged off previously, and the logon process
restores this setting.  (Well, it does it that way on NT 4.0, and no, I
don't know what the setting is, but it's got to be found somewhere under the
HKEY_CURRENT_USER tree.)  Perhaps you can look at that, because it's at
least a good guess.  And also, there has to be a way to query the state,
because somehow NT write whatever the current state is into the registry, so
it can be restored upon the next logon.  My guess would be that every time
the state is changed by the user, that registry entry is also changed, so
that by examining the registry value, you know the current state.  Granted,
this is conjecture, since I haven't looked into this in detail, but evidence
points in this direction.

>I can take out the numlock disabling code but I'm not sure that this would
>solve any more problems than it would cause.

OK, I decided to do a little hunting now, and I found this text in MSDN:
-------------------------------------------------------
KEYBOARD AND KEYBOARD LAYOUT ENTRIES FOR USERS
The Keyboard entry contains user preferences as defined by choosing the
Keyboard icon in Control Panel. Entries are found under this Registry path: 

   HKEY_CURRENT_USER\Control Panel\Keyboard

InitialKeyboardIndicators REG_SZ Number

Default: 0 

Specifies initial values for keys. 0 means that NUMLOCK is turned off after
the user logs on; 2 means NUMLOCK is turned on after the user logs on. This
value is set during log off or shutdown to preserve the state of the NUMLOCK
key at that time.

-------------------------------------------------------
And I also found this:
-------------------------------------------------------
To enable NUM LOCK before a user logs on, use the steps below. 
1. Run Registry Editor (Regedt32.exe). 
2. Navigate to HKEY_USERS\.Default\Control Panel\Keyboard. 
3. Change the value for InitialKeyboardIndicators from 0 to 2.
-------------------------------------------------------
And this:
-------------------------------------------------------
The numlock state is not saved if the user is not a member of the
administrators group. If the user is a member of the administrators group
the numlock state is saved on logoff. 

-------------------------------------------------------
This looks to be the most promising of all (search for the title in MSDN and
you get the entire article and its references):
-------------------------------------------------------
Keyboard Class: From Windows API Chaos to VBA Class Module Calm
By Ken Getz

Working with VBA, you grow accustomed to working with objects, and their
inherent properties and methods. In fact, it becomes difficult - even
painful - to program any other way. Unfortunately, there's still plenty of
functionality that's available only the old-fashioned, non-object-oriented
way. An outstanding example is that great sea of function calls, the Windows
API. One of the appealing features of working with class modules is that you
can wrap up such bodies of functionality into manageable objects with a set
of public interfaces. That is, you can hide the details of an interrelated
set of information, and present it as an object.

For example, imagine you find the need, in some of your VBA projects, to
interact with the user's keyboard. You might want to speed up or slow down
the cursor blink rate; find out how many function keys the user has; modify
the keyboard repeat speed and the delay time before repeating; or perhaps
retrieve and set the state of the toggle keys, i.e. n, c, and o. To do this
requires working with the Windows API, and that requires declarations,
external function calls, working with user-defined types and API-defined
constants - all sorts of unpleasantness.

Wouldn't it be nice if you had a simple Keyboard object that supplied
properties to perform all the work for you? Of course it would! And, as
you've probably figured out, that's where this article is headed. The
Windows API provides a lot of information about the keyboard, but retrieving
it requires calling a number of functions, so you have to know which
functions to call, how to call them, what information they return, and more.
This article presents a VBA Keyboard class, with the properties described in
FIGURE 1. (One of the more inviting reasons to use class modules and the
objects they create is that you can instantiate multiple objects from the
same class. Although you'll never create multiple instances of the Keyboard
class in a single application, that's no reason not to use a class module.)

Property	Description	Allowable Values	Read/Write
KeyboardType	Determines the type (number of keys) of the keyboard.	N/A
R
FunctionKeys	Determines the number of function keys.	N/A	R
CapsLock	Retrieves or sets the state of the c toggle.	True/False
R/W
NumLock	Retrieves or sets the state of the n toggle.	True/False	R/W
ScrollLock	Retrieves or sets the state of the o toggle.	True/False
R/W
Delay	Retrieves or sets the keyboard repeat-delay setting.	0-3	R/W
Speed	Retrieves or sets the keyboard repeat speed.	0-31	R/W
CaretBlinkTime	Retrieves or sets the number of milliseconds between blinks
of the insertion caret.	200-1200 (generally in increments of 100)	R/W

FIGURE 1: Properties provided by the Keyboard class.

To try out Keyboard.cls (you'll find the code later in this article, and
it's available for download - see the end of the article for details),
you'll need to first create a class module named Keyboard, then get the code
into your project. Once you've got the Keyboard class module in your
project, you can use it like any other class. For example, to use the
Keyboard class, create a new instance of the class, then set or retrieve its
available properties. To retrieve and set the current keyboard delay
setting, for example, you could use code like this:

Dim okb As Keyboard

Set okb = New Keyboard
If okb.Delay < 3 Then
  okb.Delay = okb.Delay + 1
End If

Certainly a lot easier than calling a bunch of Windows API functions
directly!

Doing It the Hard Way

To do its work, the Keyboard class makes calls to the Windows API. This
section of the article delves into the specific API calls. If you're not
interested, just skip to the next section, "Don't Sweat the Details," which
describes how to use the class.

Keyboard type. Retrieving the type of keyboard and the number of function
keys is simple. Call the GetKeyboardType API function, passing it a value of
0 to obtain the keyboard type, or 2 to get the number of function keys. The
function returns keyboard types as shown in FIGURE 2, and the number of
function keys as shown in FIGURE 3.

Value	Keyboard Type
1	IBM PC/XT or compatible (83-key) 
2	Olivetti "ICO" (102-key) 
3	IBM PC/AT (84-key) or similar 
4	IBM enhanced (101- or 102-key) 
5	Nokia 1050 and similar 
6	Nokia 9140 and similar 
7	Japanese 

FIGURE 2: Keyboard type return values for GetKeyboardType.

Value	Number of Function Keys
1	10
2	12 (sometimes 18)
3	10
4	12
5	10
6	24
7	Hardware dependent; specified by the OEM

FIGURE 3. Function key return values for GetKeyboardType.

Of course, to use these functions, the class module requires the API
function's declaration, so it must include the following line of code in its
Declarations section:
Private Declare Function GetKeyboardType Lib "User32" _
  (ByVal lngTypeFlag As Long) As Long
To retrieve the keyboard type, or number of function keys (as just
described), the Keyboard class module contains the following procedures:
Property Get KeyboardType() As Long
  KeyboardType = GetKeyboardType(0)
End Property

Property Get FunctionKeys() As Long
  FunctionKeys = GetKeyboardType(2)
End Property
Keyboard toggles. To set a keyboard toggle, you must use the
GetKeyboardState and SetKeyboardState API functions. To retrieve the current
state of a toggle key, use the GetKeyState API function. These require the
following declarations:
Private Declare Function GetKeyState Lib "User32" _
  (ByVal lngVirtKey As Long) As Integer
Private Declare Function GetKeyboardState Lib "User32" _
  (bytKeyState As Byte) As Long
Private Declare Function SetKeyboardState Lib "User32" _
  (bytKeyState As Byte) As Long
To retrieve the state of a key, call GetKeyState, passing in the Windows
virtual key code for the keyboard key. Luckily, VBA provides constants for n
and c (vbKeyNumlock and vbKeyCapital). However, not all implementations of
VBA provide a similar key constant for o, so you may need to define the
constant yourself:
Private Const vbKeyScrollLock = 145
The GetKeyState function returns an integer containing information about the
key you've sent it, but the only piece of that information you need is the
lowest bit. To retrieve only the lowest bit, use the And operator and the
value 1 to strip off all the information except the lowest bit.
(For the bit-confused: In binary, the value 1 consists of a bunch of 0's
followed by a lonely 1. Each of these digits is called a "bit." The And
operator takes each bit of its two operands, and unless the bits in the same
position from both operands are 1, the result is a 0 bit. If they were both
1, the result is a 1 bit. That way, by using And 1 with the return value,
all the bits except the least significant bit are guaranteed to be 0 in the
output. The final bit will either be 1 or 0 in the output, depending on its
value in the return value from GetKeyState. That didn't help? It's OK - you
can get by for a long time in VBA not digging too deep into bitwise
arithmetic. You can also find plenty of help by searching on "And operator"
in the online help in the VBA IDE. Or, search using "Logical Operators" to
learn about the entire family of logical operators.)
To find out whether the o toggle is set, the Keyboard class module uses the
following property procedure:
Property Get ScrollLock() As Boolean
  ' Return the ScrollLock toggle.
  ScrollLock = CBool(GetKeyState(vbKeyScrollLock) And 1)
End Property
You'll find similar procedures to retrieve the state of the n and c toggles.
Setting the state of the toggle requires a bit more effort; you must follow
these steps:
Call an API function (GetKeyboardState) to retrieve the current state of the
entire keyboard as a series of bytes (one byte per key).
Change the state of the key (or keys) that interests you within the array of
bytes.
Call an API function (SetKeyboardState), sending the modified array of
bytes. This sets the state of the entire keyboard.
For each of the GetKeyboardState or SetKeyboardState functions, pass the
first element of an array of 256 bytes to the API function. The function
either fills the 256 elements of the array with information about the
keyboard, or uses the bytes to set the state of the keyboard. The Keyboard
class includes the following private procedure, which does all the work:
Private Sub SetKeyState(intKey As Integer, _
                        fTurnOn As Boolean)
  ' Retrieve the keyboard state, set the particular key in
  ' which you're interested; then set the entire keyboard
  ' state back the way it was, with the one key altered.
  Dim abytBuffer(0 To 255) As Byte

  Call GetKeyboardState(abytBuffer(0))
  abytBuffer(intKey) = CByte(Abs(fTurnOn))
  Call SetKeyboardState(abytBuffer(0))
End Sub
Code within the class module calls this procedure (passing key constants for
n, c, and o). The SetKeyState procedure follows the steps listed previously
to do its work. For example, the Property Let procedure, ScrollLock, looks
like this:
Property Let ScrollLock(Value As Boolean)
  ' Set the ScrollLock toggle.
  Call SetKeyState(vbKeyScrollLock, Value)
End Property
Repeat delay and speed. Setting the keyboard repeat speed, and the number of
milliseconds the keyboard waits before starting to repeat keys, is not
terribly difficult. These tasks require calling the SystemParametersInfo
function, requesting the necessary information, or setting the values.
SystemParametersInfo is a "grab bag" function; it can do many things, and
you indicate what you want it to do by passing it a constant. For example,
to set the keyboard delay to 3, you could make the following call to
SystemParametersInfo:
Call SystemParametersInfo(SPI_SETKEYBOARDDELAY, _
                          3, 0, SPIF_TELLALL)
In this case, the third parameter isn't used, so leave it as 0. The fourth
parameter is a constant defined in the class module, telling
SystemParametersInfo to update both the appropriate .ini file and the
Windows registry. The constants also instruct SystemParametersInfo to send a
message to all the running Windows applications, telling them you've changed
a setting. Setting the repeat speed is the same, except a different constant
is used.
To use SystemParametersInfo, you must declare it, as well as any constants
you'll be using. The Keyboard class module includes these declarations:
Private Const SPI_GETKEYBOARDDELAY = 22
Private Const SPI_SETKEYBOARDDELAY = 23
Private Const SPI_GETKEYBOARDSPEED = 10
Private Const SPI_SETKEYBOARDSPEED = 11

' SystemParametersInfo flags
Private Const SPIF_UPDATEINIFILE = &H1
Private Const SPIF_SENDWININICHANGE = &b

Private Declare Function SystemParametersInfo _
  Lib "User32" Alias "SystemParametersInfoA" ( _
  ByVal uAction As Long, ByVal uParam As Long, _
  lpvParam As Any, ByVal fuWinIni As Long) As Long

' This is a made-up constant.
Private Const SPIF_TELLALL = SPIF_UPDATEINIFILE Or _
                             SPIF_SENDWININICHANGE
Retrieving values using SystemParametersInfo requires a bit more effort. To
retrieve values, you must pass a variable as the third parameter, and
(normally) 0 as the fourth parameter. SystemParametersInfo fills the
variable with the requested value, like this fragment from the Keyboard
class module:
Property Get Speed() As Long
  ' Get the keyboard repeat-speed setting.
  Dim lngValue As Long

  Call SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, _
                            lngValue, 0)
  Speed = lngValue
End Property
Working with the caret. You can also set and retrieve the number of
milliseconds between blinks of the text insertion caret using Windows API
calls. The GetCaretBlinkTime function retrieves information about the caret,
and the SetCaretBlinkTime sets the blink time. To use these functions, you
must first declare them:
Private Declare Function GetCaretBlinkTime _
  Lib "User32" () As Long
Private Declare Function SetCaretBlinkTime _
  Lib "User32" (ByVal wMSeconds As Long) As Long
Once you've declared these functions, they're simple to use. For example, to
set the caret so that it blinks every 50 milliseconds (not a very good idea,
by the way), you'd write code like this:
Call SetCaretBlinkTime(50)
Be careful! The caret is a global resource, so if you change the blink time
for your application, it changes in all applications.
Don't Sweat the Details
Of course, once you've got all the API calls under your belt, you've done
most of the work required to create the class module. Given that you want to
expose the properties listed in FIGURE 1, it's only a matter of writing the
appropriate Property Let and Property Get procedures. Listing One on page XX
includes all the property procedures, neatly wrapping up the mess of Windows
API calls.
To use the Keyboard class module, treat it like any other class (except, as
noted earlier, that you'll never create more than one instance of the
class). For example, to set the caret blink time to 100 milliseconds and to
turn on the c and n toggles (preserving the original states of all the
settings), you could write code as shown in FIGURE 4.
Dim fCapsLock As Boolean
Dim fNumLock As Boolean
Dim intBlinkTime As Integer

Dim okb As Keyboard

Set okb = New Keyboard
intBlinkTime = okb.CaretBlinkTime
fCapsLock = okb.Capslock
fNumLock = okb.Numlock
okb.CaretBlinkTime = 100
okb.Capslock = True
okb.Numlock = True

' Later, to reset the states:
okb.CaretBlinkTime = intBlinkTime
okb.Capslock = fCapsLock
okb.Numlock = fNumLock
FIGURE 4: Using the Keyboard class to set the caret blink rate and c and n
toggles.
Certainly, no one could disagree that using properties of a simple object,
such as this one, is far easier than calling the Windows API directly. Once
you've got this class module completed (and that should be easy: Simply copy
and paste the code from the sample file - see the end of the article for
download details), you can use it in any VBA application. What's more,
because of IntelliSense, you don't even need to remember the names of the
properties. (I admit, when I find myself required to revert to previous
versions of Office or VB, I usually write the code in the current version,
taking advantage of IntelliSense, then copy and paste the code into the
older version when I'm done. Talk about lazy!) 
Give It a Try
Take the Keyboard class, add it to a project, and use it. You'll see that it
couldn't be easier. And you don't need to know or remember how those pesky
API calls work. As this column progresses, I'm sure we'll present several
more API wrapper classes. Mike Gilbert and I have written a number of these
for various projects, and they're incredibly rewarding to use in production
code. Imagine being able to pull out a class that wraps up some complex API
work, and use it without digging into obscure function calls and tons of
constants. You shouldn't need to sweat the details more than once, and
that's where class modules come in handy.
Begin Listing One 
Property Get KeyboardType() As Long
  ' Determine the type of keyboard on the system.
  ' 1  IBM PC/XT or compatible (83-key) keyboard
  ' 2  Olivetti "ICO" (102-key) keyboard
  ' 3  IBM PC/AT (84-key) or similar keyboard
  ' 4  IBM enhanced (101- or 102-key) keyboard
  ' 5  Nokia 1050 and similar keyboards
  ' 6  Nokia 9140 and similar keyboards
  ' 7  Japanese keyboard
  KeyboardType = GetKeyboardType(0)
End Property

Property Get FunctionKeys() As Long
  ' Determine the number of function keys on the keyboard.
  ' 1  10
  ' 2  12 (sometimes 18)
  ' 3  10
  ' 4  12
  ' 5  10
  ' 6  24
  ' 7  Hardware dependent and specified by the OEM
  FunctionKeys = GetKeyboardType(2)
End Property

Property Get Capslock() As Boolean
  ' Return the Capslock toggle.
  Capslock = CBool(GetKeyState(vbKeyCapital) And 1)
End Property

Property Get Numlock() As Boolean
  ' Return the Numlock toggle.
  Numlock = CBool(GetKeyState(vbKeyNumlock) And 1)
End Property

Property Get ScrollLock() As Boolean
  ' Return the ScrollLock toggle.
  ' ScrollLock = CBool(GetKeyState(vbKeyScrollLock) And 1)
End Property

Property Let Capslock(Value As Boolean)
  ' Set the Capslock toggle.
  Call SetKeyState(vbKeyCapital, Value)
End Property

Property Let Numlock(Value As Boolean)
  ' Set the Numlock toggle.
  Call SetKeyState(vbKeyNumlock, Value)
End Property

Property Let ScrollLock(Value As Boolean)
  ' Set the ScrollLock toggle.
  Call SetKeyState(vbKeyScrollLock, Value)
End Property

Private Sub SetKeyState(intKey As Integer, _
                        fTurnOn As Boolean)
  ' Retrieve the keyboard state, set the particular
  ' key in which you're interested, and then set
  ' the entire keyboard state back the way it
  ' was, with the one key altered.
  Dim abytBuffer(0 To 255) As Byte

  Call GetKeyboardState(abytBuffer(0))
  abytBuffer(intKey) = CByte(Abs(fTurnOn))
  Call SetKeyboardState(abytBuffer(0))
End Sub

Property Let Delay(Value As Long)
  ' Sets the keyboard repeat-delay setting.
  ' Only values 0 through 3 are acceptable. Others will be
  ' set back to 0.
  Call SystemParametersInfo(SPI_SETKEYBOARDDELAY, Value, _
                            0, SPIF_TELLALL)
End Property

Property Get Delay() As Long
  Dim lngValue As Long

  Call SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, _
                            lngValue, 0)
  Delay = lngValue
End Property

Property Let Speed(Value As Long)
  ' Sets the keyboard repeat-speed setting.
  ' Only values 0 through 31 are acceptable. Others will
  ' be set back to 0.
  Call SystemParametersInfo(SPI_SETKEYBOARDSPEED, Value, _
                            0, SPIF_TELLALL)
End Property

Property Get Speed() As Long
  ' Get the keyboard repeat-speed setting.
  Dim lngValue As Long

  Call SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, _
                            lngValue, 0)
  Speed = lngValue
End Property

Property Get CaretBlinkTime() As Long
  ' Retrieve the number of milliseconds
  ' between blinks of the caret.
  ' SYSTEM RESOURCE. Change this with care.
  CaretBlinkTime = GetCaretBlinkTime()
End Property

Property Let CaretBlinkTime(Value As Long)
  ' Set the number of milliseconds
  ' between blinks of the caret.
  ' SYSTEM RESOURCE. Change this with care.
  ' Allowable values: 200 to 1200 (multiples of 100)
  Call SetCaretBlinkTime(Value)
End Property
End Listing One


The files referenced in this article are available for download from the
Informant Web site at http://www.informant.com/mod/modnewupl.htm. File name:
MOD9712KG.ZIP.

Ken Getz and Mike Gilbert are Senior Consultants with MCW Technologies, a
Microsoft Solution Provider focusing on Visual Basic and the Office and
BackOffice suites. They've recently completed VBA Developer's Handbook and
Access 97 Developer's Handbook (co-authored with Paul Litwin), both for
SYBEX.
---------------------------------------------------------------------
To unsubscribe, send a message with the line: unsubscribe vnc-list
to majordomo "at" uk.research.att.com
See also: http://www.uk.research.att.com/vnc/intouch.html
---------------------------------------------------------------------