sjs@jcricket.ctt.bellcore.com (Stan Switzer) (10/26/88)
Thanks to a lot of good net feedback I have made a number
of improvements to the original KeySee.
Since KeySee is now about a month old and hasn't changed any in about
half of that time, I figured there'd be no harm in posting this
(hopefully final) newer version: KeySee 1.1.
New features:
1) You can change focus mode from cursor-follows-mouse to
click-to-type with a button.
2) Middle and right mouse buttons map to control and shift. A
trivial change allows you to substitute "meta" for one or the other.
3) Pressing L5 and L7 with your mouse now does the "expected" thing.
4) Eliminate certain key "pumping" problems.
5) If KeySee is too slow for your taste, look at the documentation
on "SpeedHacks".
6) Generous internal and functional documentation has been added.
Readers interested in more detail may enquire within.
Notes:
1) If somehow the keyboard gets stuck in Caps mode or Control mode,
remebmer that how can hit "L1" to clear the problem.
2) If keyboard input seems to be going to the wrong window, make
sure that you are'nt in click-to-type mode.
Enjoy,
Stan Switzer sjs@ctt.bellcore.com "Thou shalt not have no idea"
P.S.: Ignore bogus reply path, use signature address only.
----------------------------------------------------------
#!/usr/NeWS/bin/psh
%
% KeySee: Display the keyboard and simulate keyboard input.
%
% Copyright (C) 1988 by Stan Switzer. All rights reserved.
% This program is provided for unrestricted use, provided that this
% copyright message is preserved. There is no warranty, and no author
% or distributer accepts responsibility for any damage caused by this
% program.
%
% Mon KeySee, mon key do.
%
% Stan Switzer sjs@ctt.bellcore.com
% ---------------------------------------
% Sept. 29, 1988 Stan Switzer
%
% One good hack deserves another. Ever since Don Hopkins posted his
% MouSee I have been toying with the idea of writing something that
% does for the keyboard what MouSee does for the Mouse. This is the
% result.
%
% The original version of this program simply displayed the status of
% the keyboard. It soon occured to me that it could just as easily
% simulate keyboard input. Not long afterward I added the on/off switch
% and made it load "liteitem.ps" if it wasn't already loaded.
% Josh Siegel suggested binding control and shift to the other mouse
% keys, which turned out to be quite practical. Another suggestion was
% to display the keycaps according to the current bindings (and related
% ideas), which did not turn out to be practical. Perhaps someone else
% will take up the cause.
%
% Thanks to comp.windows.news readers who put up with (numerous) early
% versions of this program, and who provided valuable feedback for new
% features.
%
% The following features of the program may need some explanation:
% + KeySee assumes you have a Sun-3 style keyboard. This is true
% of all Sun-3 and Sun-4 "kb" devices, but not the 386i.
% It is relatively simple to accomodate other keyboard layouts.
% + The On/Off button determines whether keyboard monitoring is
% enabled.
% + The Cursor/Click button controls the keyboard focus mode:
% "cursor follows mouse" or "click to type." Note that the button
% changes even if you change focus mode by other means!
% + Ctl, Left Shift, and Left (meta) are "sticky." One press turns
% it on, another turns it off.
% + The Adjust button is "Ctl" and the Menu button is "Shift" except
% that they are not sticky.
% + When the monitor is "Off," the keyboard doesn't highlight mouse
% operations. This is a feature, not a bug. The cute thing here is
% when you press "A" with your mouse, it doesn't gray out because
% you pressed it (like a button would) but because it really thinks
% you pressed the "A" key. If the monitor is off, it just doesn't
% know. It would be easy to "fix" but I'm not sure it's broken.
% If enough people feel strongly about it I'll change it.
% + Note what happens if you press L5 or L7 with your mouse! For this
% to work it is important that simulated events have proper screen
% coordinates (/XLocation and /YLocation values).
% + Note what happens when you press the *real* L1 key. Apparently the
% kernel hides the L1 down in case you also hit "A" and then gives you
% *both* a down and up event when (if!) the key comes up again.
% + Keys will not simulate down or up events if they are already in the
% result state. This is to prevent key "pumping" where you manage to
% create more down events than up. This is particularly important in
% the case of shift keys.
% + Because KeySee operates as close to the raw keyboard as possible,
% simulated events look very real -- even to the extent that KeySee
% shifts work with real keys and real shifts work with KeySee key
% hits. About the only difference is that the /KeyState interest field
% isn't (cannot be) set. Apparently /KeyState isn't really used for
% much. Perhaps someone can explain SCANNER in user_edit_template(UI.ps)
% to me. Possibly /KeyState would be useful for mouse events so that
% you detect shifted mouse buttons, etc. Better would be to use
% the {shift|caps|meta|control}_keys_down entries in UI_private.
% + Serious NeWS hacks might want to look at some of the comments in here.
% There are some good tricks (many learned from Don Hopkins's code and
% quite a few learned the old-fashioned way) and important observations
% documented here. The following techniques are illustrated here:
% - Dictionaries as interest /Names; event handling.
% - Raw keyboard input handling.
% - Managing canvases with LOTS of items.
% - Playing games with the icon.
% - Tricking items into drawing themselves somewhere besides where they
% really live (i.e.: in the icon!)
% - Subclassing of items and windows. I strongly suggest subclassing
% windows rather than "defing" things in to a vanilla window.
% - How I learned to stop worrying and love polymorphism
% (Dr. Strangehack)
% + KeySee is a bit slow on a 3/50. Especially if you run two or more!
%
% Oct. 1, 1988 SJS
%
% + Simplified mouse menu and adjust button handling. Changing the menu
% key to the meta will now only require an extremely trival change.
% Search for "meta" below to find the code.
% + Despite precautions taken to avoid the situation it is sometimes
% possible to "pump" a key (it isn't easy, mind you, just possible)
% if you should find keys inexplicably stuck in caps or control mode,
% hit "L1" to reset the keystate.
% + An interesting and perplexing problem occurs when KeySee is run in
% color (non-retained) and it is partially obscured by another canvas.
% When you press the L5 button and the key comes up, it attempts to
% redraw itself to the "up" color, but the canvas is clipped to the
% damage path, rendering the redraw ineffective. There doesn't seem
% to be any good solution to this problem.
%
% Oct. 4, 1988 SJS
%
% + Stopped dummy (spacer) buttons from sending events. Previously, they
% sent bogus but apparently harmless events with "null" as the name.
%
% Oct. 6, 1988 SJS
%
% + Added "xor" style highlighting code. If you find KeySee too slow,
% find "/SpeedHacks? false def" and change "false" to "true".
%
% Oct. 11, 1988 SJS
% + Changed cycle items to use prevailing background and text colors
% to be sure that they can be seen when the background is black.
% There's no accounting for taste.
%
% Oct. 26, 1988 SJS
% + As there have been no changes to KeySee for nearly a month,
% it, in its present state, shall be called KeySee 1.1 and
% released for (I hope) one final time.
%
systemdict /Item known not { (NeWS/liteitem.ps) run } if
/KeyItem LabeledItem dictbegin
/ItemDownColor .5 .5 .5 rgbcolor def
/ItemRadius 4 def
/Station null def
/StickyKey? false def
/ItemValue false def
dictend classbegin
/new { % label canvas width height => instance
() /Center nullproc 6 3 roll /new super send begin
ItemLabel /OU eq { % over/under key label
/LowerLabel exch def
/UpperLabel exch def
} if
currentdict
end
} def
/SpeedHacks? false def % enable speed hacks?
/ItemLabelFont /Times-Roman findfont 10 scalefont def
/setvalue { % bool => - -- true == down, false == up
/ItemValue exch store
ItemValue ItemPaintedValue ne {
SpeedHacks? {
gsave ItemCanvas setcanvas HiLightKey grestore
/ItemPaintedValue ItemValue def
} {
/paint self send
} ifelse
} if
} def
/JustSetvalue { % bool => - -- paint but don't draw it
/ItemValue exch store
} def
/PaintIfNeeded { % - => - -- draw it if needed
ItemValue ItemPaintedValue ne {/paint self send} if
} def
/KS 2 def % Key Separation
/ItemShape { % bool => - -- shape of item (false->inset)
{ ItemRadius KS 2 idiv dup ItemWidth KS sub ItemHeight KS sub }
{ ItemRadius .5 sub KS 2 idiv .5 add dup
ItemWidth KS sub 1 sub ItemHeight KS sub 1 sub }
ifelse
rrectpath
} def
/PaintItem {
true ItemShape
gsave
ItemFillColor ItemValue null ne {
SpeedHacks? not ItemValue and { pop ItemDownColor } if
} if
setcolor fill
grestore
ItemBorderColor setcolor stroke
ShowLabel
SpeedHacks? ItemValue and { HiLightKey } if
} def
/HiLightKey {
gsave false ItemShape 0 setgray 5 setrasteropcode fill grestore
} def
/OU { % over-under proc
% if the Label is a sym, it is executed passing "true" to draw it
% and "false" to return the width and height.
% Hack: we pretend that the labels have no width and then
% cshow them.
{ % draw it
0 currentfont fontdescent 1 add 2 idiv rmoveto
gsave 0 currentfont fontheight rmoveto UpperLabel cshow grestore
LowerLabel cshow
} { % size it
0 currentfont fontheight 2 mul
} ifelse
} def
/reshape { % x y w h
/reshape super send
LabelSize % w h
ItemHeight exch sub 2 div /LabelY exch def
ItemWidth exch sub 2 div /LabelX exch def
} def
/SetStation { % stationcode => -
16#6F00 add % This is magic
/Station exch def
} def
/ClientDown { true FakeKey } def
/ClientUp { false FakeKey } def
/FakeKey { % bool(true==down) => - -- fake a key transition
StickyKey? {
{ ItemValue not ReallyFakeKey } if
} {
ReallyFakeKey
} ifelse
} def
/ReallyFakeKey { % bool(true==down) -- bypass sticky goo
dup ItemValue ne exch /ItemValue exch store
{
createevent dup begin
/Name Station def
/Action ItemValue /DownTransition /UpTransition ifelse def
gsave framebuffer setcanvas currentcursorlocation
/YLocation exch def /XLocation exch def
grestore
end sendevent
} if
} def
/SetSticky { /StickyKey? exch def } def
classend def
/DummyKeyItem KeyItem [ ] % just uses up space
classbegin
/new { % canvas width height => instance
() 4 1 roll
/new super send
} def
/ReallyFakeKey { pop } def % don't send key events
/PaintItem nullproc def
/SetStation { pop } def
classend def
% really a cycle item but reaponds to some "KeyItem" messages:
/CycleKey CycleItem dictbegin
/Station null def
dictend classbegin
/new { % choices notify can w h => instance
/cycle 6 1 roll /Right 5 1 roll
/new super send
} def
/SetStation { pop } def
/PaintIfNeeded nullproc def
classend def
/KeeSee DefaultWindow dictbegin
/Items null def
/ItemList null def
/TmpDict null def
/Watcher null def
/IconKey null def
/ItemProc null def
/EventProc null def
dictbegin
dictend classbegin
/new {
/new super send begin
/PaintClient {
ClientFillColor fillcanvas
ClientCanvas setcanvas
ItemList { paintitems } forall
} def
/TmpDict 20 dict def
currentdict
end
} def
/FrameLabel (Key See) def
/IconLabel FrameLabel def
/KeyWidth 24 def % Width (&Height) of Std Key
/Border 4 def % border around keyboard proper
/Key { % (Label) WidthFactor => item
KeyWidth mul
ClientCanvas exch KeyWidth /new KeyItem send
pause
} def
/Dummy { % WidthFactor => item
KeyWidth mul ClientCanvas exch KeyWidth /new DummyKeyItem send
} def
/Sticky { % item => item
true /SetSticky 2 index send
} def
/CreateClientCanvas {
/CreateClientCanvas super send
% various items:
/Items dictbegin
(A) 0 get 1 (Z) 0 get { % alpha keys
1 string dup 0 4 -1 roll put
dup 1 string copy cvn exch 1 Key def
} for
/D1 (!)(1)/OU 1 Key def
/D2 (@)(2)/OU 1 Key def
/D3 (#)(3)/OU 1 Key def
/D4 ($)(4)/OU 1 Key def
/D5 (%)(5)/OU 1 Key def
/D6 (^)(6)/OU 1 Key def
/D7 (&)(7)/OU 1 Key def
/D8 (*)(8)/OU 1 Key def
/D9 (\()(9)/OU 1 Key def
/D0 (\))(0)/OU 1 Key def
/Caps (Caps) 1.25 Key def
/Left (Left) 1.5 Key Sticky def
/Space () 9 Key def
/SPC (Space) 2.25 Key def 0 0 /move SPC send
/Right (Right) 1.5 Key def
/Alt (Alt) 1.75 Key def
/LShift (Shift) 2.25 Key Sticky def
/RShift (Shift) 1.75 Key def
/LF (LF) 1 Key def
/L-C (<)(,)/OU 1 Key def
/G-P (>)(.)/OU 1 Key def
/Q-S (?)(/)/OU 1 Key def
/Ctl (Ctl) 1.75 Key Sticky def
/C-S (:)(;)/OU 1 Key def
/Q-Q (")(')/OU 1 Key def
/Ret (Return) 2.25 Key def
/Tab (Tab) 1.5 Key def
/O-B ({)([)/OU 1 Key def
/C-B (})(])/OU 1 Key def
/Del (Del) 1.5 Key def
/Esc (Esc) 1 Key def
/U-D (_)(-)/OU 1 Key def
/P-E (+)(=)/OU 1 Key def
/V-B (|)(\\)/OU 1 Key def
/T-Q (~)(`)/OU 1 Key def
/F1 (F1) 1 Key def
/F2 (F2) 1 Key def
/F3 (F3) 2 Key def
/F4 (F4) 2 Key def
/F5 (F5) 2 Key def
/F6 (F6) 2 Key def
/F7 (F7) 2 Key def
/F8 (F8) 1 Key def
/F9 (F9) 1 Key def
/BS (BS) 1 Key def
/L1 (L1) 1 Key def /L2 (L2) 1 Key def /X1 .5 Dummy def
/L3 (L3) 1 Key def /L4 (L4) 1 Key def /X2 .5 Dummy def
/L5 (L5) 1 Key def /L6 (L6) 1 Key def /X3 .5 Dummy def
/L7 (L7) 1 Key def /L8 (L8) 1 Key def /X4 .5 Dummy def
/L9 (L9) 1 Key def /L10 (L10) 1 Key def /X5 .5 Dummy def
/X6 .5 Dummy def /R1 (R1) 1 Key def
/R2 (R2) 1 Key def /R3 (R3) 1 Key def
/X7 .5 Dummy def /R4 (R4) 1 Key def
/R5 (R5) 1 Key def /R6 (R6) 1 Key def
/X8 .5 Dummy def /R7 (R7) 1 Key def
/R8 (R8) 1 Key def /R9 (R9) 1 Key def
/X9 .5 Dummy def /R10 (R10) 1 Key def
/R11 (R11) 1 Key def /R12 (R12) 1 Key def
/X10 .5 Dummy def /R13 (R13) 1 Key def
/R14 (R14) 1 Key def /R15 (R15) 1 Key def
/X11 .5 Dummy def
/OnOff [ (On) (Off) ]
{ ItemValue 0 eq /watch /stopwatch ifelse ThisWindow send }
ClientCanvas KeyWidth dup 2.5 mul exch /new CycleKey send def
textcolor ClientFillColor {
/ItemFillColor exch def
/ItemTextColor exch def
} OnOff send
/Mode [ (Cursor) (Click) ]
{ ItemValue 0 eq /CursorFocus /ClickFocus ifelse setfocusmode }
ClientCanvas KeyWidth dup 3 mul exch /new CycleKey send def
textcolor ClientFillColor {
/ItemFillColor exch def
/ItemTextColor exch def
} Mode send
Mode /ItemValue
UI_private /FocusMode get /CursorFocus eq 0 1 ifelse put
dictend store
% Display order
/ItemList Items begin [ % Key rows from bottom to top
[ OnOff 119 Caps Left Space Right 19 Alt X11 Mode ] Station
[ 95 L9 97 L10 X5 99 LShift Z X C
V B N M L-C G-P Q-S RShift LF
X10 112 R13 R14 R15 ] Station
[ 72 L7 L8 X4 76 Ctl A S D F G H J K L C-S Q-Q 89 Ret
X9 91 R10 R11 R12 ] Station
[ 49 L5 51 L6 X3 53 Tab Q W E R T Y U I O P O-B C-B Del
X8 68 R7 R8 R9 ] Station
[ 25 L3 L4 X2 29 Esc D1 D2 D3 D4 D5 D6 D7 D8 D9 D0
U-D P-E 88 V-B 42 T-Q
X7 45 R4 R5 R6 ] Station
[ 1 L1 3 L2 X1 5 F1 F2 8 F3 10 F4 12 F5
14 F6 16 F7 F8 F9 43 BS
X6 21 R1 R2 R3 ] Station
] end store
/ItemProc Items forkitems store
} def
/IconWidth 64 def /IconHeight 48 def
/PaintIconKey { % paints IconKey centered in icon
% a tasty hack!
IconKey null ne {
{ ItemHeight ItemWidth } IconKey send
IconWidth exch sub 2 idiv exch
IconHeight exch sub 2 idiv 6 add
gsave translate /PaintItem IconKey send grestore
} if
} def
/PaintIcon { gsave IconCanvas setcanvas
IconFillColor fillcanvas IconBorderColor strokecanvas
IconTextColor setcolor
PaintIconKey
PaintIconLabel
grestore } def
/flipiconic {
/flipiconic super send
Iconic? {
painticon % update icon image
} { % we don't paint items when they are not displayed, so update.
ItemList { { /PaintIfNeeded exch send } forall } forall
} ifelse
} def
/SetIconKey { gsave IconCanvas setcanvas
IconKey null ne { % erase previous key image
{ ItemWidth ItemHeight } IconKey send
IconWidth 2 index sub 2 idiv
IconHeight 2 index sub 2 idiv 6 add
moveto rect
IconFillColor setshade fill
} if
JustSetIconKey
IconTextColor setcolor PaintIconKey
grestore } def
/JustSetIconKey {
Items begin
dup Space eq { % normal space bar is too big!
/ItemValue get SPC dup /ItemValue 4 -1 roll put
} if
end
/IconKey exch def
} def
/eventmonitor { % start eventmonitor
EventProc null ne { EventProc killprocess } if
/EventProc {
createevent dup begin
/Name /SetFocusPolicy def % focus mode change
/Priority 10 def
end expressinterest
createevent dup begin
/Name [ MenuButton AdjustButton ] def
/Action [ /UpTransition /DownTransition ] def
/Canvas ClientCanvas def
% evidence suggests that mouse events are exclusive whether
% you want it or not!
/Exclusivity true def
end expressinterest
{
awaitevent begin
% invoke name w/ Action as param:
% "0 pop" keeps "self send" from being expunged
Action Name self 0 pop send
end
} loop
} fork def
} def
/FakeKey { % action key => -
Items exch get % action keyitem
exch /DownTransition eq exch % bool keyitem
/ReallyFakeKey exch send % bypass sticky key handling
} def
AdjustButton { /Ctl FakeKey } def % action => -
MenuButton { /LShift FakeKey } def % action => - % if you prefer "shift"
% MenuButton { /Left FakeKey } def % action => - % if you prefer "meta"
/SetFocusPolicy { % action => -
% action is new mode
/CursorFocus eq 0 1 ifelse /setvalue Items /Mode get send
} def
/stopwatch { % stop event loop
Watcher null ne { Watcher killprocess } if
} def
/watch { % start event loop
stopwatch
/Watcher {
createevent dup begin
/Name dictbegin % dict: keycode => item
Items {
exch pop dup /Station get
dup null eq { pop pop } { exch def } ifelse
} forall
dictend def
/Priority 10 def
end expressinterest
{
awaitevent begin
% Note: Name is key item because of interest /Name dict
pause % perhaps this will let us do real work first
% "self /foo exch send" keeps the method compiler
% from removing self send. This is important to that
% when (Just)SetIconKey is invoked it will end up
% setting IconKey in the object, not in the event.
% failing to do this results in having the interest
% (which is apparently reused for all keyboard events)
% referencing an item and consequently its parent (the
% ClientCanvas), causing the canvas to just stick around
% forever. Probably defing bogus entries in events should
% be an error. This was no fun to find.
Action /DownTransition eq
Iconic? {
/JustSetvalue Name send
Name self /SetIconKey exch send
} {
/setvalue Name send
Name self /JustSetIconKey exch send
} ifelse
end
} loop
} fork def
} def
/Station { % [ KeyItems-and-indexes ] => [ KeyItems ]
% sets the station codes in the array's items.
mark exch 0 exch { % [ item item ... n currentitem
dup type /integertype eq {
exch pop
} {
2 copy /SetStation exch send
exch 1 add
} ifelse
} forall
pop ]
} def
/ShapeClientCanvas {
% This is a real good way to position items!
/ShapeClientCanvas super send
Recalc % recalc layout params
ClientCanvas setcanvas
% now, move the items to their rightful places
TmpDict begin
/SepX 0 def /SepY 0 def
/Y Border def
ItemList {
/X Border def
/MaxH 0 def
{
X Y /move 3 index send
/ItemHeight 1 index send dup MaxH gt
{ /MaxH exch def } { pop } ifelse
/ItemWidth exch send X add SepX add /X exch store
} forall
/Y Y MaxH SepY add add def
} forall
end
Watcher null eq { watch } if
EventProc null eq { eventmonitor } if
} def
/placeit { % one click placement and sizing
gsave fboverlay setcanvas getclick grestore
BorderLeft BorderRight add 21 KeyWidth mul add Border 2 mul add
BorderTop BorderBottom add 6 KeyWidth mul add Border 2 mul add
3 -1 roll 1 index sub 3 1 roll % % ulx uly w h => llx lly w h
reshape
} def
/Recalc { % - => - -- recalculates various layout parameters
% for when I decide to handle resizing!
} def
classend def
/win framebuffer /new KeeSee send def
/placeit win send
/map win send
% ----- Anything after this line is not part of the program -----