summaryrefslogtreecommitdiff
path: root/web/c_cpp/cweb/examples/oemacs.w
blob: 5810b0e7b26081fd18d44370e356de53571aed80 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
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.