\datethis@i xview_types.w @* Introduction.This program provides an interface between the GNU Emacs editor and theOpenWindows environment, using the XView toolkit for interactive graphics.It is based on \.{emacstool}, a SunView interface written by Jeff Peckof 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 workwith OpenWindows Version~3 as distributed in 1992, using a Sun Type~4keyboard. GNU Emacs is a highly portable and versatile program, which does mostof the hard work involved in editing files. Our job is simply twofold:To get \.{emacs} started in an appropriate window, and to transmitkeyboard and mouse events as sequences of characters that \.{emacs}can happily consume. These simple tasks do, however, require us toget a lot of fussy details right. In fact, this program could not have beenwritten without a good deal more knowledge about XView than can be foundin the manuals. Fortunately Jeff Peck works for Sun, and hisinside knowledge has provided the necessary clues. (All complaints belowabout the XView documentation are based on the reference manuals andprogramming manuals available from Sun and from O'Reilly \AM\ Associates, inthe summer of 1992. Let us hope that such problems will not persist;computer programming should be fun, but some of the code below waswritten only after a bitter struggle!) The command-line arguments to \.{oemacs} are either standard XViewarguments, 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 thecommand line. An important note about using \.{xmodmap} to change the behavior ofcertain keys appears below. It makes \.{emacs} more powerful, unless youhave 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'veconfigured 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 improvedversions 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 bythe \.{system} command in \TEX/ (when the user has typed \.e in responseto an error message); without |exit(0)|, \TEX/ would complain of troubleexecuting this program, although we would simply be terminating the programwithout 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 otherbasic 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 willfind the OpenWindows header files. Some \UNIX/ systems define string functions in \.{<string.h>}, some in\.{<strings.h>}; the Sun documentation calls for \.{<string.h>}. Myexperiments indicate that Sun's compiler and loader work perfectlywell with string functions even when no header files are given, so theprogrammer doesn't really have to remember the right name. Similarly,\.{<stdio.h>} isn't really needed with Sun's \CEE/, unless certain macrosare used. I'll include \.{<string.h>} and \.{<stdio.h>} anyway, in thespirit 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 wellbegin with an easy task, in order to get warmed up. The other is thatif we specify the icon {\it after\/} creating the frame, we will unconditionallyoverride an icon that the user may have specified with the \.{-WI}option on the command line. (This is one of the little things aprogrammer has to turn by trial and error, since the XView documentationleaves much unsaid.) The colors used in the icon will be inherited from the frame, so they willbe reversed if the user asks for reverse video. I~prefer to have the iconalways 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 mygrayscale 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 offthe header line at the top of the frame. That gives us a chance to seetwo more lines of the file \.{emacs} is editing. However, we add alabel 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 theback cover of the GNU Emacs manual. (This accounts for my black-on-yellowpreference.) @<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 teletypeterminals 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 argumentsthat remain after generic XView arguments have been removed. We have to say |WIN_IS_CLIENT_PANE|, otherwise fonts specified on thecommand line will be ignored. (This cryptic instruction is mentioned brieflyin 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 terminalwindow (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 theprograms in its source file \.{lisp/term/sun.el}, where specialadaptations 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 partand a pseudo-terminal part. (The manual does mention briefly that textsubwindows have views---at the beginning of section 6.3, and indirectly inone of the examples in the chapter on color.) The view window of an emulated tty will receive keyboard andmouse events. We need to know its ``handle,'' because we want tomodify XView's default interpretation of many of those events. For example, we want to grab the keyboard focus (i.e., to begin receivingkeyboard input) as soon as the user moves the mouse into the \.{emacs}window. The normal OpenLook default requires a user to click in the windowfirst, 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 itmore visible against a black background. Changing the cursor is another undocumented reason why we need to knowabout the tty's view window; nothing changes if we try to attachthe 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 orderto convert mouse positions into coordinates that \.{emacs} will like.If the user has specified a font explicitly, the font will presumably havea fixed width for all characters; we can learn the relevant dimensionsby calling |xv_get|. But if the user has not specified a font, thesituation is trickier; |xv_get| will report the width of the default{\it non\/}-fixed-width font, and this will probably differ from the width ofthe 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 firstcall to |xv_create|---will therefore fail. Thus, we have to work around two distinct bugs in XView. The solutionis 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 whatfont will go into the tty window. The program below works correctlyeven if the command line specifies \.{-scale} \.{large}, say, instead ofspecifying a new font explicitly by something like \.{-font} \.{9x15}. While we are cataloguing peculiarities of XView, we might as well mentionthat the default character dimensions of the default font (Lucida) arereported to be $8\times13$ in the 12-point (medium) size, $8\times15$ in the14-point (large) size, and $12\times20$ in the 19-point (extralarge) size.The actual character dimensions in the window come, however, from thedefault fixed-width font (Lucida typewriter), and they are $7\times13$,$9\times15$, and $11\times23$ in the same three sizes. No logical progressionis 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 rightevent mask and the right cursor, and how to calculate the sizes of thecharacters it will contain. All that remains is for us to do theseoperations in the proper order, and to specify a filter routine thatwill monitor and edit all communications between the keyboard and theEmacs processor in the window. The new ingredient is the filter specification. We tell the XViewnotifier to call |filter| when a window event occurs, instead ofletting it call the tty subroutine event handler directly. The parameter|NOTIFY_SAFE| implies that the window's event handler ispart 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 andeither process it ourselves or pass it through (possibly modified)to its normal recipient. In the first case we return the codevalue |NOTIFY_DONE|, since we aren't reporting any errors;in the second case we call the normal event handler and return the valueit returns to us. An XView event is a data structure that has been partiallyinterpreted by the XView routines, which add semantic sugar tothe complex union type of X~Window events. We need not looktoo 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 keyboardfocus, we want to send a coded sequence of characters to \.{emacs}. \itemitem{(3)} When a key goes down, and we have the keyboard focus, wewant to send an appropriate sequence of characters to \.{emacs}. \itemitem{(4)} When the status of the Num Lock indicator light changes, wewill 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 orresize 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));Lines 1–500 of 896. Load more
\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));