diff options
Diffstat (limited to 'Build/source/texk/web2c/cwebdir/examples/oemacs.w')
-rw-r--r-- | Build/source/texk/web2c/cwebdir/examples/oemacs.w | 896 |
1 files changed, 896 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/cwebdir/examples/oemacs.w b/Build/source/texk/web2c/cwebdir/examples/oemacs.w new file mode 100644 index 00000000000..5810b0e7b26 --- /dev/null +++ b/Build/source/texk/web2c/cwebdir/examples/oemacs.w @@ -0,0 +1,896 @@ +\datethis +@i xview_types.w + +@* Introduction. +This program provides an interface between the GNU Emacs editor and the +OpenWindows environment, using the XView toolkit for interactive graphics. +It is based on \.{emacstool}, a SunView interface written by Jeff Peck +of Sun Microsystems in 1986 and adapted by him in 1988 to \.{xvetool}, +an early XView interface. The present code, by Don Knuth, is designed to work +with OpenWindows Version~3 as distributed in 1992, using a Sun Type~4 +keyboard. + +GNU Emacs is a highly portable and versatile program, which does most +of the hard work involved in editing files. Our job is simply twofold: +To get \.{emacs} started in an appropriate window, and to transmit +keyboard and mouse events as sequences of characters that \.{emacs} +can happily consume. These simple tasks do, however, require us to +get a lot of fussy details right. In fact, this program could not have been +written without a good deal more knowledge about XView than can be found +in the manuals. Fortunately Jeff Peck works for Sun, and his +inside knowledge has provided the necessary clues. (All complaints below +about the XView documentation are based on the reference manuals and +programming manuals available from Sun and from O'Reilly \AM\ Associates, in +the summer of 1992. Let us hope that such problems will not persist; +computer programming should be fun, but some of the code below was +written only after a bitter struggle!) + +The command-line arguments to \.{oemacs} are either standard XView +arguments, which specify the font and the size and position of the window, +the icon, the colors, etc.; or they are standard arguments of \.{emacs}, +which typically specify the file to be edited and a position in that file. +If I decide later to make certain things in \.{oemacs} less hardwired, +I will look for options in the X resource database instead of on the +command line. + +An important note about using \.{xmodmap} to change the behavior of +certain keys appears below. It makes \.{emacs} more powerful, unless you +have other good uses for those keys. (See the entry for \.{xmodmap} +in the index.) + +Before using \.{oemacs}, you must compile \.{emacs} with the +\.{sunfns} module (which the \.{Makefile} will do for you if you've +configured it correctly), and you should include the lines +$$\vbox{\halign{\.{#}\hfil\cr +(load-library "sun-mouse")\cr +(load-library "sun-fns")\cr}}$$ +in Emacs's file \.{lisp/site-init.el}. + +Caution: This program was developed and tested with Peck's improved +versions of sun-mouse and sun-fns; these are available from +\.{peck@@sun.com} if not yet part of the GNU distribution. + +@ We follow the traditional structure of XView applications. The |exit| +statement at the end is important, because \.{oemacs} can be invoked by +the \.{system} command in \TEX/ (when the user has typed \.e in response +to an error message); without |exit(0)|, \TEX/ would complain of trouble +executing this program, although we would simply be terminating the program +without returning any particular value. + +@c +@<Include header files@>@; +@# +Frame frame; /* the base frame where we will live */ +@<Global variables@>@; /* additional globals besides |frame| */ +@# +@<Event-handling procedures@>@; +@# +main(argc,argv) + int argc;@+char *argv[]; /* typical \UNIX/ setup */ +{ + @<Special initialization@>; + @<Install the components and controls of |frame|@>; + xv_main_loop(frame); + exit(0); +} + +@ Including the header file \.{<xview/xview.h>} also causes other +basic header files like \.{<xview/frame.h>}, \.{<xview/icon.h>}, etc., +to be loaded. We must call the \CEE/ compiler with the flag +\.{-I\$(OPENWINHOME)/include} so that the \CEE/ preprocessor will +find the OpenWindows header files. + +Some \UNIX/ systems define string functions in \.{<string.h>}, some in +\.{<strings.h>}; the Sun documentation calls for \.{<string.h>}. My +experiments indicate that Sun's compiler and loader work perfectly +well with string functions even when no header files are given, so the +programmer doesn't really have to remember the right name. Similarly, +\.{<stdio.h>} isn't really needed with Sun's \CEE/, unless certain macros +are used. I'll include \.{<string.h>} and \.{<stdio.h>} anyway, in the +spirit of being obedient to the stated rules. + +@<Include...@>= +#include <string.h> +#include <stdio.h> +#include <xview/xview.h> + +@* The icon and frame. +First we attach an icon that will appear if the user closes the \.{oemacs} +window later. + +There are two reasons for doing this first. One is that we might as well +begin with an easy task, in order to get warmed up. The other is that +if we specify the icon {\it after\/} creating the frame, we will unconditionally +override an icon that the user may have specified with the \.{-WI} +option on the command line. (This is one of the little things a +programmer has to turn by trial and error, since the XView documentation +leaves much unsaid.) + +The colors used in the icon will be inherited from the frame, so they will +be reversed if the user asks for reverse video. I~prefer to have the icon +always in black on a yellow background, so I'm making the color explicit. +(I hope this will work on monochrome displays; it works fine on my +grayscale monitor.) + +@<Create a frame with the gnu icon@>= +{@+Server_image icon_image=(Server_image)xv_create(NULL,SERVER_IMAGE,@| + XV_WIDTH,64,XV_HEIGHT,64,SERVER_IMAGE_BITS,icon_bits,NULL); + Server_image mask_image=(Server_image)xv_create(NULL,SERVER_IMAGE,@| + XV_WIDTH,64,XV_HEIGHT,64,SERVER_IMAGE_BITS,mask_bits,NULL); + Cms cms=(Cms)xv_create(NULL,CMS,CMS_SIZE,2,@| + CMS_NAMED_COLORS,"yellow","black",NULL,NULL); + Icon icon=(Icon)xv_create(NULL,ICON,@| + ICON_IMAGE,icon_image,ICON_MASK_IMAGE,mask_image,@| + WIN_CMS,cms,NULL); + frame=xv_create(NULL,FRAME,FRAME_ICON,icon,NULL); +} + +@ @<Include...@>= +#include <xview/cms.h> + +@ If the user hasn't specified a label with the \.{-Wl} option, we turn off +the header line at the top of the frame. That gives us a chance to see +two more lines of the file \.{emacs} is editing. However, we add a +label of our own; it will show up in the virtual window manager display. + +@<Remove the frame header, unless the user has specifically requested it@>= +if (xv_get(frame,XV_LABEL)==NULL) /* no label specified */ + xv_set(frame,FRAME_SHOW_HEADER,FALSE,XV_LABEL,"OEMACS",NULL); + +@ The following icon and mask are derived from the ``what's gnu'' image on the +back cover of the GNU Emacs manual. (This accounts for my black-on-yellow +preference.) + +@<Global...@>= +unsigned short icon_bits[]={@| + 0x0000, 0x0000, 0x0000, 0x1E00, + 0x0000, 0x0000, 0x0000, 0x0900,@| + 0x001E, 0x0000, 0x0000, 0x0880, + 0x0064, 0x0000, 0x0000, 0x0440,@| + 0x0088, 0x0000, 0x0000, 0x0420, + 0x0110, 0x0000, 0x0000, 0x0210,@| + 0x0220, 0x0000, 0x0000, 0x0210, + 0x0420, 0x0FCF, 0x01C0, 0x0108,@| + 0x0840, 0x1030, 0x8620, 0x0088, + 0x1080, 0x00C0, 0x5810, 0x0084,@| + 0x1080, 0x1F00, 0x2008, 0x0044, + 0x2100, 0xE200, 0x1004, 0x0044,@| + 0x4103, 0x0400, 0x0002, 0x0042, + 0x4204, 0x080E, 0x0001, 0x0042,@| + 0x8200, 0x7830, 0x0020, 0x8082, + 0x8203, 0x9040, 0x0018, 0x4102,@| + 0x8204, 0x2080, 0x07C6, 0x3E04, + 0x8108, 0x410C, 0x0021, 0x8004,@| + 0x8080, 0x8210, 0x03D0, 0x6008, + 0x4041, 0x0420, 0x0008, 0x1810,@| + 0x403E, 0x0820, 0x0FFC, 0x0620, + 0x2000, 0x1040, 0x0002, 0x01C0,@| + 0x1000, 0x608C, 0x0FFF, 0x0060, + 0x0801, 0x8110, 0x0080, 0x8118,@| + 0x0406, 0x0220, 0x1FFF, 0x66E0, + 0x0238, 0x044F, 0x0000, 0xD800,@| + 0x01C0, 0x0890, 0x8FFF, 0x4000, + 0x0300, 0x10A6, 0x4041, 0x6000,@| + 0x1C00, 0x2026, 0x4FFF, 0x6000, + 0x60CC, 0x4026, 0x4001, 0x6000,@| + 0x1F33, 0x8010, 0x8FFF, 0x4000, + 0x0012, 0x000F, 0x0040, 0xC000,@| + 0x0022, 0x4000, 0x07FF, 0x4000, + 0x0024, 0x4000, 0x0000, 0x2000,@| + 0x0024, 0x4818, 0x8FFF, 0xE000, + 0x0024, 0x4907, 0x0040, 0x2000,@| + 0x0044, 0x4900, 0x1FFF, 0xE000, + 0x0044, 0x4900, 0x0000, 0x2000,@| + 0x0044, 0x4900, 0x07FF, 0xE000, + 0x0044, 0x4880, 0x0020, 0x2000,@| + 0x0044, 0x4880, 0x07FF, 0xE000, + 0x0044, 0x4840, 0x0000, 0x2000,@| + 0x0044, 0x2A20, 0x07FF, 0xE000, + 0x0044, 0x2410, 0x0020, 0x2000,@| + 0x0042, 0x2448, 0x0FFF, 0xE000, + 0x0042, 0x2948, 0x0000, 0x2000,@| + 0x0041, 0x1144, 0x07FF, 0xA000, + 0x0041, 0x1144, 0x2010, 0x1000,@| + 0x0021, 0x1126, 0x20FA, 0x1000, + 0x0024, 0x8925, 0x2600, 0x1000,@| + 0x0014, 0x8924, 0xA138, 0x7000, + 0x0016, 0x88A4, 0x9090, 0x6000,@| + 0x000A, 0x44A4, 0x4880, 0xA000, + 0x0002, 0x44A2, 0x4401, 0x2000,@| + 0x0003, 0x4492, 0x2001, 0x4000, + 0x0001, 0x2451, 0x3002, 0x8000,@| + 0x0000, 0xA251, 0x1E05, 0x0000, + 0x0000, 0x2248, 0xA1F9, 0x8000,@| + 0x0000, 0x1648, 0x9002, 0x8000, + 0x0000, 0x1A28, 0x4C02, 0x8000,@| + 0x0000, 0x1220, 0x43FC, 0x8000, + 0x0000, 0x0120, 0x2000, 0x8000,@| + 0x0000, 0x0120, 0x2003, 0x0000, + 0x0000, 0x0150, 0x1FFC, 0x0000 +}; +unsigned short mask_bits[]={@| + 0x0000, 0x0000, 0x0000, 0x1E00, + 0x0000, 0x0000, 0x0000, 0x0F00,@| + 0x001E, 0x0000, 0x0000, 0x0F80, + 0x007C, 0x0000, 0x0000, 0x07C0,@| + 0x00F8, 0x0000, 0x0000, 0x07E0, + 0x01F0, 0x0000, 0x0000, 0x03F0,@| + 0x03E0, 0x0000, 0x0000, 0x03F0, + 0x07E0, 0x0FCF, 0x01C0, 0x01F8,@| + 0x0FC0, 0x103F, 0x87F0, 0x00F8, + 0x1F80, 0x00FF, 0xDFF0, 0x00FC,@| + 0x1F80, 0x1FFF, 0xFFF8, 0x007C, + 0x3F00, 0xE3FF, 0xFFFC, 0x007C,@| + 0x7F03, 0x07FF, 0xFFFE, 0x007E, + 0x7E04, 0x0FFF, 0xFFFF, 0x007E,@| + 0xFE00, 0x7FFF, 0xFFFF, 0x80FE, + 0xFE03, 0x9FFF, 0xFFFF, 0xC1FE,@| + 0xFE04, 0x3FFF, 0xFFFF, 0xFFFC, + 0xFF08, 0x7FFF, 0xFFFF, 0xFFFC,@| + 0xFF80, 0xFFFF, 0xFFFF, 0xFFF8, + 0x7FC1, 0xFFFF, 0xFFFF, 0xFFF0,@| + 0x7FFF, 0xFFFF, 0xFFFF, 0xFFE0, + 0x3FFF, 0xFFFF, 0xFFFF, 0xFFC0,@| + 0x1FFF, 0xFFFF, 0xFFFF, 0xFFE0, + 0x0FFF, 0xFFFF, 0xFFFF, 0xFFF8,@| + 0x07FF, 0xFFFF, 0xFFFF, 0xFEE0, + 0x03FF, 0xFFFF, 0xFFFF, 0xF800,@| + 0x01FF, 0xFFFF, 0xFFFF, 0xE000, + 0x03FF, 0xFFFF, 0xFFFF, 0xE000,@| + 0x1FFF, 0xFFFF, 0xFFFF, 0xE000, + 0x7FFF, 0xFFFF, 0xFFFF, 0xE000,@| + 0x1F7F, 0xFFFF, 0xFFFF, 0xC000, + 0x001F, 0xFFFF, 0xFFFF, 0xC000,@| + 0x003F, 0xFFFF, 0xFFFF, 0xC000, + 0x003F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x003F, 0xFFFF, 0xFFFF, 0xE000, + 0x003F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xF000,@| + 0x003F, 0xFFFF, 0xFFFF, 0xF000, + 0x003F, 0xFFFF, 0xFFFF, 0xF000,@| + 0x001F, 0xFFFF, 0xFFFF, 0xF000, + 0x001F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x000B, 0xFFFF, 0xFFFF, 0xE000, + 0x0003, 0xFFFF, 0xFFFF, 0xE000,@| + 0x0003, 0xFFFF, 0xFFFF, 0xC000, + 0x0001, 0xFFFF, 0xFFFF, 0x8000,@| + 0x0000, 0xBFF1, 0xFFFF, 0x0000, + 0x0000, 0x3FF8, 0xFFFF, 0x8000,@| + 0x0000, 0x1FF8, 0xFFFF, 0x8000, + 0x0000, 0x1FF8, 0x7FFF, 0x8000,@| + 0x0000, 0x13E0, 0x7FFF, 0x8000, + 0x0000, 0x01E0, 0x3FFF, 0x8000,@| + 0x0000, 0x01E0, 0x3FFF, 0x0000, + 0x0000, 0x0150, 0x1FFC, 0x0000 +}; + +@* Emulating a terminal. +We will run \.{emacs} in a ``tty subwindow,'' named after the teletype +terminals of ancient yore. + +The |argv| array will be a command line that invokes +\.{emacs} with all arguments not removed by |xv_init|, i.e., all arguments +that remain after generic XView arguments have been removed. + +We have to say |WIN_IS_CLIENT_PANE|, otherwise fonts specified on the +command line will be ignored. (This cryptic instruction is mentioned briefly +in the XView reference manual, but not in the XView programming manual; +I~would never have discovered it without Jeff Peck's help.) + +We also have to set |TTY_QUIT_ON_CHILD_DEATH| to |TRUE|. Otherwise when +\.{emacs} exits (via control-X, control-C) there still will be a terminal +window (into which we can type but not access the shell). + +Before starting \.{emacs} we set the environment variable +\.{TERM} equal to \.{sun}. +This will tell \.{emacs} to initialize itself with the +programs in its source file \.{lisp/term/sun.el}, where special +adaptations for Sun-style terminals have been recorded. + +@<Put a tty subwindow into the frame@>= +argv[0]="emacs"; +putenv("TERM=sun"); +tty=(Tty)xv_create(frame,TTY,WIN_IS_CLIENT_PANE,@| + TTY_QUIT_ON_CHILD_DEATH,TRUE,@| + TTY_ARGV,argv,NULL); + +@ @<Global...@>= +Tty tty; + +@ @<Include...@>= +#include <xview/tty.h> + +@ The XView manual doesn't tell us that tty subwindows have a view part +and a pseudo-terminal part. (The manual does mention briefly that text +subwindows have views---at the beginning of section 6.3, and indirectly in +one of the examples in the chapter on color.) + +The view window of an emulated tty will receive keyboard and +mouse events. We need to know its ``handle,'' because we want to +modify XView's default interpretation of many of those events. + +For example, we want to grab the keyboard focus (i.e., to begin receiving +keyboard input) as soon as the user moves the mouse into the \.{emacs} +window. The normal OpenLook default requires a user to click in the window +first, but that is inefficient in a production book-writing environment. +Us \.{emacs} users would rather type than point. + +A secret incantation makes the view window accessible. Dear reader, +would you have guessed how to do this, from reading the manuals only, +without looking at Jeff Peck's code? Be honest now. + +We don't have to enable the notification of other kinds of events; +tty subwindows already do that. + +@<Prepare to be notified when the mouse enters the window@>= +window=(Xv_window)xv_get(tty,OPENWIN_NTH_VIEW,0); +xv_set(window,WIN_CONSUME_EVENT,LOC_WINENTER,NULL); + +@ @<Global...@>= +Xv_window window; /* the view window of |tty| */ + +@ If the user has specified reverse video with the \.{-rv} option, +we will reverse black and white in the mouse cursor. This will make it +more visible against a black background. + +Changing the cursor is another undocumented reason why we need to know +about the tty's view window; nothing changes if we try to attach +the new cursor to |frame| or to |tty| instead of to |window|. + +@<Change the cursor, to avoid black-on-black@>= +if (rv) {Xv_singlecolor white,black; + Xv_cursor cursor; + white.red=white.green=white.blue=255; + black.red=black.green=black.blue=0; + cursor=(Xv_cursor)xv_create(NULL,CURSOR,@| + CURSOR_SRC_CHAR,OLC_BASIC_PTR,CURSOR_MASK_CHAR,OLC_BASIC_MASK_PTR,@| + CURSOR_FOREGROUND_COLOR,&white,CURSOR_BACKGROUND_COLOR,&black,NULL); + xv_set(window,WIN_CURSOR,cursor,NULL); +} + +@ @<Include...@>= +#include <xview/cursor.h> /* we're using the cursor package */ + +@ What is the variable |rv| that was tested in the code above? Good question. +We have to scan for \.{-rv} before |xv_init| looks at the command arguments. + +@<Scan the command line, setting |rv| nonzero if \.{-rv} is present@>= +rv=0; +{@+int k=argc; + while (--k>0) if (strcmp(argv[k],"-rv")==0 || + strcmp(argv[k],"-reverse")==0) rv=1; +} + +@ @<Global...@>= +int rv; + +@ We need to know the height and width of characters in the font, in order +to convert mouse positions into coordinates that \.{emacs} will like. +If the user has specified a font explicitly, the font will presumably have +a fixed width for all characters; we can learn the relevant dimensions +by calling |xv_get|. But if the user has not specified a font, the +situation is trickier; |xv_get| will report the width of the default +{\it non\/}-fixed-width font, and this will probably differ from the width of +the actual fixed-width font the tty subwindow will choose. + +Curiously, if we call |xv_find(NULL,FONT,FONT_FAMILY, +FONT_FAMILY_DEFAULT_FIXEDWIDTH,NULL)| {\it before\/} calling |xv_init|, +without even doing anything with the result returned by |xv_find|, +|xv_init| will not install any fonts specified on the command line. +The trick we used for icons---installing the default gnu icon on the first +call to |xv_create|---will therefore fail. + +Thus, we have to work around two distinct bugs in XView. The solution +is to discover the effects of the user's command line after |xv_init| +has acted and the frame has been set up; we can determine by brute force what +font will go into the tty window. The program below works correctly +even if the command line specifies \.{-scale} \.{large}, say, instead of +specifying a new font explicitly by something like \.{-font} \.{9x15}. + +While we are cataloguing peculiarities of XView, we might as well mention +that the default character dimensions of the default font (Lucida) are +reported to be $8\times13$ in the 12-point (medium) size, $8\times15$ in the +14-point (large) size, and $12\times20$ in the 19-point (extralarge) size. +The actual character dimensions in the window come, however, from the +default fixed-width font (Lucida typewriter), and they are $7\times13$, +$9\times15$, and $11\times23$ in the same three sizes. No logical progression +is evident in either the variable-width or the fixed-width dimensions, +although Lucida is a ``scalable font family.'' + +@<Compute the height and width of characters in the font@>= +{ + Xv_font font=(Xv_font)xv_get(frame,XV_FONT); + Xv_font dfont=(Xv_font)xv_find(NULL,FONT,FONT_FAMILY, + FONT_FAMILY_DEFAULT,NULL); + if (strcmp((char*)xv_get(font,FONT_NAME), + (char*)xv_get(dfont,FONT_NAME))==0) { + /* the user didn't specify a new font by name */ + dfont=(Xv_font)xv_find(NULL,FONT,FONT_FAMILY, + FONT_FAMILY_DEFAULT_FIXEDWIDTH,NULL); + /* this one will be used by the tty window */ + } else dfont=font; + char_width=(int)xv_get(dfont,FONT_DEFAULT_CHAR_WIDTH); + char_height=(int)xv_get(dfont,FONT_DEFAULT_CHAR_HEIGHT); +} + +@ @<Global...@>= +int char_width, char_height; /* character dimensions in the font */ + +@ @<Include...@>= +#include <xview/font.h> /* header for the font package */ + +@ OK, we've figured out how to install a tty subwindow with the right +event mask and the right cursor, and how to calculate the sizes of the +characters it will contain. All that remains is for us to do these +operations in the proper order, and to specify a filter routine that +will monitor and edit all communications between the keyboard and the +Emacs processor in the window. + +The new ingredient is the filter specification. We tell the XView +notifier to call |filter| when a window event occurs, instead of +letting it call the tty subroutine event handler directly. The parameter +|NOTIFY_SAFE| implies that the window's event handler is +part of XView, not an alien routine. + +@<Install the components...@>= +@<Scan the command line, setting |rv| nonzero if \.{-rv} is present@>; +xv_init(XV_INIT_ARGC_PTR_ARGV,&argc,argv,NULL); + /* start XViewing; strip and remember the OpenWin arguments */ +@<Create a frame with the gnu icon@>; +@<Remove the frame header...@>; +@<Put a tty subwindow into the frame@>; +@<Prepare to be notified when the mouse enters the window@>; +@<Change the cursor, to avoid black-on-black@>; +@<Compute the height and width of characters in the font@>; +notify_interpose_event_func(window,filter,NOTIFY_SAFE); + +@* Keyboard events. +The job of an interposed filter function is to look at an event and +either process it ourselves or pass it through (possibly modified) +to its normal recipient. In the first case we return the code +value |NOTIFY_DONE|, since we aren't reporting any errors; +in the second case we call the normal event handler and return the value +it returns to us. + +An XView event is a data structure that has been partially +interpreted by the XView routines, which add semantic sugar to +the complex union type of X~Window events. We need not look +too far inside an event structure to do the things that concern us. + +And what is our job? We are looking for three different kinds of events: + +\smallskip +\itemitem{(1)} When the mouse enters the window, +we want to grab the keyboard focus. + +\itemitem{(2)} When a mouse button goes up or down, and we have the keyboard +focus, we want to send a coded sequence of characters to \.{emacs}. + +\itemitem{(3)} When a key goes down, and we have the keyboard focus, we +want to send an appropriate sequence of characters to \.{emacs}. + +\itemitem{(4)} When the status of the Num Lock indicator light changes, we +will send emacs the command {\tt turn-numlock-on} or {\tt turn-numlock-off}, +for reasons described below. + +\smallskip\noindent Other events, like instructions to repaint or +resize the window, will be passed through without change to the tty window. + +@<Event-handling...@>= +Notify_value filter(window,event,arg,type) + Xv_window window; /* the ``client'' on whom we interposed */ + Event *event; /* the data we are filtering */ + Notify_arg arg; /* optional communication parameter between clients */ + Notify_event_type type; /* |NOTIFY_SAFE| or |NOTIFY_IMMEDIATE| */ +{@+register int id=event_id(event); +#ifdef DEBUG + printf("event %d%s, action %d, shift %x, mouse(%d,%d)\n", + event_id(event),event_is_up(event)?"UP":"DOWN",event->action, + event_shiftmask(event),event_x(event),event_y(event)); +#endif + @<Update the Num Lock status@>; + if (id==LOC_WINENTER) @<Grab the keyboard focus and return@>; + if (event_is_button(event)) @<Translate a mouse event and return@>; + if (event_is_up(event)) return NOTIFY_DONE; /* key has been released */ + @<Translate a function key into a special escape sequence@>; + @<Sanitize a keypress event so that unusual semantic actions are removed@>; + return notify_next_event_func(window,event,arg,type); /* pass it through */ +} + +@ It's easy to take charge of the keyboard and mouse, as soon as the mouse +enters our territory. + +@<Grab...@>= +{ + win_set_kbd_focus(window,xv_get(window,XV_XID)); + return NOTIFY_DONE; +} + +@ If the event is not related to mouse buttons or special function keys, +we want to pass it on unchanged, unless its |event_id| is less than 128. +In that case, it represents a character code, and we want to nuke any +semantic ``keyboard acceleration'' actions it might have been assigned +by OpenWindows. + +We also make the Meta key add 128 here. An undocumented macro +called |event_set_id|, found in \.{<xview/win\_input.h>}, +clears the |action| code as desired. + +@<Sanitize...@>= +if (id<128) + if (event_meta_is_down(event)) event_set_id(event,id+128); + else event_set_action(event,ACTION_NULL_EVENT); + +@* Function keys. +The Sun Type 4 keyboard has lots of keys, and these can be bound to lots +of useful functions when we are \.{emacs}ing to the max. Besides the +letters and punctuation marks of a normal typewriter, there are ten +``left'' function keys, L1--L10; twelve ``top'' function keys, F1--F12; +fifteen ``right'' function keys, R1--R15; and eight additional keys +labeled Help, Alt, AltGraph, Ins, Del, Enter, $+$, $-$, which we will +pretend have been labeled B1--B8. + +The L5 key, also known as Front, is controlled by the Open Look +window manager; it makes a window rise above any that might overlap it, +or shoves the window underneath in case it already was on top. + +The L7 key, aka Open, is likewise preempted by the +window manager. It closes a frame to an icon, or opens an icon. + +The window manager traps the R2 key and calls it the ``language'' key; +but I have no use for that feature. So I have remapped R2 to the comparatively +useless character $3\over4$, and I will translate it back to R2 below. (The +\.{xmodmap} program allows users to reassign the interpretation of key codes.) +I could have recovered the L5 and L7 keys in the same way, but I like +their functions as they stand. (L5 and L7 still come through if a +Shift, Control, and/or Meta key is down.) + +I can never remember the difference between Delete and BackSpace, so I +have mapped them both into control-?, ASCII code 127. + +There are two Shift keys, one at the left and one at the right, which +are best kept indistinguishable from each other. Similarly, the left +and right Meta keys (`\.{\char27}') are essentially identical. There's a +Control key too. These three types of modifier keys generate keypress +events, but we ignore them; the only thing that matters to us is whether +they are currently up or down, when other keys are pressed. + +\font\ttit=cmitt10 +There also are three special keys that do not generate events, so we +never see them. The CapsLock key toggles the Caps Lock light and +changes lowercase letters to uppercase when the light is on. The +NumLock key toggles the Num Lock light and changes the interpretation +of R4--R15 and B4--B8 when that light is on. The Compose key turns the +Compose light on until you type two characters, then it produces a +special symbol if those two characters match a pattern. For example, +when Compose is followed by either \.{a"} or \.{"a} you get the 8-bit +ISO code for {\tt \"a}; when Compose is followed by either \.{th} or +\.{ht} you get the Icelandic thorn; when it's followed by \.{??} you get {\tt +?`}; \.{c/} and \.{L-} give \rlap{\./}\.c and {\ttit\char`\$} +and so on. (A list of all composition patterns +appears in \.{<X11/Suncompose.h>}, but not in any of the manuals +I've seen.) The light goes off after two characters have been +typed, or if your first character is not composable, or if +you hit Compose again prematurely. If no proper two-character pattern +was specified, only ``up'' events (key releases) are transmitted, and +nothing will pass through to \.{emacs}. + +One other feature is worth noting: The key between F12 and Delete +produces a backslash `\.\\', or a vertical line `\.{\char125}' when +shifted. Everybody knows that, but even more is true. If you hold the +AltGraph key down and press this key, it turns out that you get the +broken-bar character `{\tt\hbox to1em{\hss\vbox{\hrule width 1pt height +3pt\vskip1.5pt\hrule height2pt depth1pt}\hss}}'. This is the only key that the +engineers chose to endow with three different graphic symbols. + +A few other anomalies occur; for example, AltGraph-R1 gives ASCII null, +while AltGraph does not affect R4. But I didn't discover any additional +combinations that are either useful or harmful. + +Once upon a time the Caps Lock key might have affected the |event_shiftmask| +field of an event, but it has no effect now. The shiftmask is always an +even number, contrary to the implications of \.{<xview/win\_input.h>}. + +@ The function keys will be translated into a four-character code. +First comes control-X; then an asterisk; then a letter, \.{a}--\.{o} +for function numbers 1--15, respectively; and then another letter, +identifying left, right, top, or bottom. The final letter is +ordinarily `\.l', `\.r', `\.t', or `\.b', respectively. But it is `\.L', `\.R', +`\.T', or `\.B' if a Shift key is down. Furthermore the Control key +subtracts 64 from the ASCII code, so you get `\.,', `\.2', `\.4', or +`\."' with Control and no Shift. With both Control and Shift you get +\.{\\C-L}, \.{\\C-R}, \.{\\C-T}, \.{\\C-B}. A Meta key adds another 128 +to the code. Thus, each function key leads to eight possibilities. + +For example, if F4 is pressed when Control and Shift are down, but not +Meta, the four-letter code is \.{\\C-X*d\\C-T}. The user could type +that sequence of four characters and get the same effect. + +Shifted function keys sometimes have a nice mnemonic significance. +For example, key R14, also labeled PgDn, is usually bound to the Emacs +operation `\.{scroll-up}', which moves the window down [sic] by one +screenful; we can then bind Shift-R14 to \.{forward-page}, which advances +down to the next page boundary. In \.{cweb-mode}, the next page boundary +is the next \.{@@*}, beginning a major part of the program. Similarly, +it's convenient to bind B7, the keypad `\.+' key, to \.{forward-paragraph}. +Then in \.{cweb-mode}, Shift-B7 goes to the next module (the next +\.{@@\ } or \.{@@*}). + +A Composed character will be preceded by \.{\\C-Q}, the Emacs `quote' +character, to distinguish it from a character that was generated with the +Meta key down. This also applies to the broken-bar character, which +will incidentally be preceded by AltGraph, which is B3; you'll probably +want to bind B3 to a no-op if broken bars are important to you. + +@ This program assumes that several key codes have been rebound from +their normal values. Namely, the commands +$$\vbox{\halign{\.{#}\hfil\cr +keysym R2 = threequarters\cr +keysym KP\_Subtract = onehalf\cr +keysym KP\_Add = onequarter\cr +keysym KP\_Enter = threesuperior\cr +keysym KP\_Decimal = twosuperior\cr +keysym KP\_0 = onesuperior\cr}}$$ +should be executed by \.{xmodmap}, preferably in the user's \.{.xinitrc} file. +This makes the keys act as $3\over4$, $1\over2$, $1\over4$, $^3$, $^2$, and +$^1$, respectively. The corresponding 8-bit codes are respectively +190, 189, 188, 179, 178, 185. (By the way, can anybody explain why the ISO +LATIN-1 code has $^0$, $^2$, and $^3$ in the positions of meta-0, meta-2, +and meta-3, while $^1$ is in the position of meta-9?) +@.xmodmap@> + +We haven't actually bound the keys this way to use them in editing. +We did it to provide linguistically unimportant codes that OpenWindows +wouldn't mess up; its normal conventions make those valuable keys +unusable for editing, except as duplicates for other existing keys. + +We send \.{turn-numlock-on/off} commands so that \.{emacs} can keep in +synch with the keyboard state. Namely, it will rebind the function +keys B4--B8 to their numeric-keypad equivalents while the Num Lock light is on. + +On the other hand, our remapping does make the Num Lock +feature useless in other (non-Emacs) applications. If you don't +rebind the keys as stated, you lose the functionality of R2 and B4--B8, +but \.{oemacs} will still work. + +The Help key is another special case. We don't want to remap it, +because it gives useful help information with other OpenWindows +applications. If Help is pressed without the shift or control key, +the |event_id| is zero and the |event_action| is |ACTION_HELP|. +Control-Help is similar, but with |ACTION_TEXT_HELP|. Shift-Help is +more complicated; it invokes `helpopen: starting new Viewer', after +generating an event that has |event_action=WIN_VISIBILITY_NOTIFY|. The +program below considers the Help key B1 to be characterized by any +event with |event_id=0| and either |event_action!=0| or +|event_shiftmask!=CTRLMASK|. + +@<Translate a function key into a special escape sequence@>= +{@+register int bank='b'; /* |'l'|, |'r'|, |'t'|, or |'b'| */ + register int n; /* function key serial number, |1<=n<=15| */ + if (id>=KEY_LEFT(1)) @<Translate an ordinary function key@>@; + else if (id>=256) @<Look for Alt or AltGraph@>@; + else if (id>=128) + @<Translate a special function key or composed character@>@; + else if (id>0 || + (event_action(event)==0 && event_shiftmask(event)==CTRLMASK)) + goto non_function; + else n=1; /* Help key */ +emit_function_key:@<Emit the code for a function key and |return|@>; +non_function:; +} + +@ I'm assuming here that the event id codes occur in the order left, right, +top, bottom, and that no higher event codes exist. + +@<Translate an ordinary function key@>= +{ + if (id<KEY_RIGHT(1)) { /* on the left bank */ + bank='l';@+n=id-KEY_LEFT(0); + } else if (id<KEY_TOP(1)) { /* on the right bank */ + bank='r';@+n=id-KEY_RIGHT(0); + } else if (id<KEY_BOTTOM(1)) { + bank='t';@+n=id-KEY_TOP(0); + } else n=id-KEY_BOTTOM(0); + goto emit_function_key; +} + +@ The event codes examined here appear in \.{<xview/win\_event.h>} +but not in the XView reference manual. + +@<Look for Alt or AltGraph@>= +if (id==SHIFT_ALT) { + n=2;@+goto emit_function_key; +} else if (id==SHIFT_ALTG) { + n=3;@+goto emit_function_key; +} else goto non_function; + +@ The |ttysw_input| routine sends text to a tty's view window. +The second parameter is a string, not necessarily terminated by +|'\0'| or anything else; the third parameter is the string length. + +@<Emit the code for a function key and |return|@>= +{ + if (event_shift_is_down(event)) bank-=32; + if (event_ctrl_is_down(event)) bank-=64; + if (event_meta_is_down(event)) bank+=128; + buf[2]=n+'a'-1; + buf[3]=bank; + ttysw_input(window,buf,4); + return NOTIFY_DONE; +} + +@ @<Global...@>= +char buf[]="\030*??\021"; /* |030| and |021| give control-X, control-Q */ + +@ @<Translate a special function key or composed character@>= +switch (id) { +case 190: bank='r';@+n=2;@+goto emit_function_key; +case 189: n=8;@+goto emit_function_key; +case 188: n=7;@+goto emit_function_key; +case 179: n=6;@+goto emit_function_key; +case 178: n=5;@+goto emit_function_key; +case 185: n=4;@+goto emit_function_key; +default: buf[5]=id; /* composed character or broken-bar */ + ttysw_input(window,buf+4,2); + return NOTIFY_DONE; +} + +@* The NumLock key. +The global variable |num_lock_state| will be 0 if the Num Lock indicator +light is off, 1 if it is on. Whenever an event occurs, we check to see +if |num_lock_state| should change; if so, we change it and send an +appropriate command to \.{emacs}. + +To read the state of the keyboard LED indicator lights, we need an I/O +control command called the |KIOCGLED| ioctl, described on the +man page for \.{kb(4m)}. + +@<Global...@>= +int num_lock_state; +char turnon[]="\370turn-numlock-on\r", turnoff[]="\370turn-numlock-off\r"; +int keyboard; /* file descriptor of \.{/dev/kbd} */ + +@ @<Include...@>= +#include <sys/file.h> /* definition of |O_RDWR| for |open| */ +#include <sundev/kbio.h> /* definition of |KIOCGLED| for |ioctl| */ + +@ @d LED_NUM_LOCK 0x1 /* the official definition is in \.{<server/sunevq.h>}, + but that header file includes huge gobs of other stuff */ + +@<Update the Num Lock status@>= +{@+char leds; /* binary encoding of LED lights */ + ioctl(keyboard,KIOCGLED,&leds); + if ((leds&LED_NUM_LOCK)!=num_lock_state) { + num_lock_state=leds&LED_NUM_LOCK; + if (num_lock_state) ttysw_input(window,turnon,17); + else ttysw_input(window,turnoff,18); + } +} + +@ Any ordinary user can apparently open the keyboard as a file. I would +have tried read-only access if read-write had failed; but read-write access +gives a sense of power even though I won't be writing anything. + +@<Special initialization@>= +keyboard=open("/dev/kbd",O_RDWR); +if (keyboard<0) { + fprintf(stderr,"%s: Can't open /dev/kbd!\n",argv[0]); + exit(1); +} + +@* Mouse events. +When a mouse button is pressed or released, we send \.{emacs} the +codes control-X and ASCII null, followed by a parenthesized list +of four numbers and carriage-return. +For example, as I was typing this paragraph, I +clicked the left mouse button on the screen just for fun; \.{emacs} +received the characters +$$\.{\\030\\0(1 18 28 9999)\\r\\030\\0(129 18 28 141)\\r}$$ +as a result (according to `\.{view-lossage}'). I would have received +the same response if I had typed these characters myself, instead +of clicking the mouse. + +The first of the four numbers identifies the mouse button itself +as the code number 1, 2, or 4 (for left, middle, right), plus 8 if +a Shift key is down, plus 16 if the Control key is down, plus 32 +if a Meta key is down, plus 128 if the mouse key is being released +instead of pressed. + +The second number is the row number in the frame, the top row being +considered row~0. + +The third number is the column number in the frame, the left column being +considered column~0. + +The fourth number is the elapsed time between this mouse event and the +previous one, in milliseconds. If the elapsed time was 10 seconds or +more, 9999 is substituted. + +Macros inside \.{emacs} can use the second and third numbers to +position the cursor. The fourth number can be used to determine if the +user is ``double clicking'' or using ``chords.'' Examples of such +macros appear in the Emacs source file \.{lisp/sun-mouse.el}. + +Incidentally, the ASCII null character in mouse sequence makes us happy that +the string parameter to |ttysw_input| is not null-terminated. + +@<Translate a mouse event...@>= +{@+register int button_code,elapsed_time; + button_code=(id==MS_LEFT? 1: id==MS_MIDDLE? 2: 4); + if (event_shift_is_down(event)) button_code += 8; + if (event_ctrl_is_down(event)) button_code += 16; + if (event_meta_is_down(event)) button_code += 32; + if (event_is_up(event)) button_code += 128; + @<Compute the time elapsed since the previous mouse event@>; + sprintf(mouse_buf+2,"(%d %d %d %d)\r",button_code,@| + event_x(event)/char_width, event_y(event)/char_height,@| + elapsed_time); + ttysw_input(window,mouse_buf,12+strlen(mouse_buf+12)); /* length is at least 12 */ + return NOTIFY_DONE; +} + +@ @<Global...@>= +char mouse_buf[24]="\030"; + +@ XView's event structure includes |event_time(event)|, which has +type |struct timeval|; this data type is declared in \.{<sys/time.h>}, +which is one of the files included automatically as a result of +including \.{<xview/xview.h>}. +A |timeval| structure consists of two |long| integers, |tv_sec| and |tv_usec|, +denoting clock time in seconds and microseconds, respectively. + +@<Compute the time...@>= +{@+struct timeval now; /* current time */ + long delta_sec, delta_usec; /* difference between current and + previous time */ + now=event_time(event); + delta_sec=now.tv_sec-prev_mouse_time.tv_sec; + delta_usec=now.tv_usec-prev_mouse_time.tv_usec; + if (delta_usec<0) delta_usec+=1000000,delta_sec--; + if (delta_sec>=10) elapsed_time=9999; /* infinity (or close enough) */ + else elapsed_time=(delta_sec*1000)+(delta_usec/1000); + prev_mouse_time=now; +} + +@ @<Global...@>= +struct timeval prev_mouse_time; + +@* Remaining problems. There's a terribly unfortunate bug in the +present implementation of XView, causing characters of tty subwindows +to be badly painted at crucial times; the rightmost column of pixels +in a character is often clobbered. If I could figure out how to +generate repaint events for the tty subwindow, I might build a mechanism +into \.{oemacs} that does this after the keyboard has been idle for 10 +seconds, say. This would blink the screen; maybe I'll get used to that, +or maybe I'll prefer to refresh the window manually by binding +\.{redraw-display} to the L2 and R1 keys. In any case a lot of screen +refreshing is necessary at the moment, alas. + +(Note added later: I doubt if I'll get used to blinking, and the present +method of manual refresh is tolerable so I won't pursue the 10-second +timer idea. I have meanwhile noticed a procedure |wmgr_refreshwindow(window)| +mentioned in \.{<xview/wmgr.h>}; it will presumably refresh any +given window. + +Another bug, much less serious, occurs when the window is resized. +If the window gets smaller, \.{emacs} isn't told to correct its +assumptions; so it puts information in strange places or offscreen. +(Internally, emacs uses the \.{TIOCGWINSZ} or \.{TIOCSWINSZ} ioctl, +described in the man page for \.{termio}.) +You can work around this by first making the window very small, then +making it large. + +@* Index. + + |