Initial git import
[sxemacs] / src / ui / TTY / gpmevent.c
1 /* GPM (General purpose mouse) functions
2    Copyright (C) 1997 William M. Perry <wmperry@gnu.org>
3    Copyright (C) 1999 Free Software Foundation, Inc.
4
5 This file is part of SXEmacs
6
7 SXEmacs is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 SXEmacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
19
20
21 /* Synched up with: Not in FSF. */
22
23 /* Authors: William Perry */
24
25 #include <config.h>
26 #include "lisp.h"
27 #include "ui/console.h"
28 #include "console-tty.h"
29 #include "ui/device.h"
30 #define INCLUDE_EVENTS_H_PRIVATE_SPHERE
31 #include "events/events.h"
32 #include "events/events-mod.h"
33 #include "sysdep.h"
34 #include "commands.h"
35 #include "lstream.h"
36 #include "sysproc.h"            /* for MAXDESC */
37 #include "process.h"
38
39 #ifdef HAVE_GPM
40 #include "gpmevent.h"
41 #include <gpm.h>
42
43 #if (!defined(__linux__))       /* possible under xterm */
44 #define KG_SHIFT        0
45 #define KG_CTRL         2
46 #define KG_ALT          3
47 #else
48 #include <linux/keyboard.h>
49 #endif
50
51 extern int gpm_tried;
52 extern void *gpm_stack;
53
54 static int (*orig_event_pending_p) (int);
55 static void (*orig_next_event_cb) (Lisp_Event *);
56
57 static Lisp_Object gpm_event_queue;
58 static Lisp_Object gpm_event_queue_tail;
59
60 struct __gpm_state {
61         int gpm_tried;
62         int gpm_flag;
63         void *gpm_stack;
64 };
65
66 static struct __gpm_state gpm_state_information[MAXDESC];
67
68 static void store_gpm_state(int fd)
69 {
70         gpm_state_information[fd].gpm_tried = gpm_tried;
71         gpm_state_information[fd].gpm_flag = gpm_flag;
72         gpm_state_information[fd].gpm_stack = gpm_stack;
73 }
74
75 static void restore_gpm_state(int fd)
76 {
77         gpm_tried = gpm_state_information[fd].gpm_tried;
78         gpm_flag = gpm_state_information[fd].gpm_flag;
79         gpm_stack = gpm_state_information[fd].gpm_stack;
80         gpm_consolefd = gpm_fd = fd;
81 }
82
83 static void clear_gpm_state(int fd)
84 {
85         if (fd >= 0) {
86                 memset(&gpm_state_information[fd], '\0',
87                        sizeof(struct __gpm_state));
88         }
89         gpm_tried = gpm_flag = 1;
90         gpm_fd = gpm_consolefd = -1;
91         gpm_stack = NULL;
92 }
93
94 static int get_process_infd(Lisp_Process * p)
95 {
96         Lisp_Object instr, outstr;
97         get_process_streams(p, &instr, &outstr);
98         assert(!NILP(instr));
99         return Lstream_get_fd(XLSTREAM(instr));
100 }
101
102 DEFUN("receive-gpm-event", Freceive_gpm_event, 0, 2, 0, /*
103 Run GPM_GetEvent().
104 This function is the process handler for the GPM connection.
105 */
106       (process, string))
107 {
108         Gpm_Event ev;
109         int modifiers = 0;
110         int button = 1;
111         Lisp_Object fake_event = Qnil;
112         Lisp_Event *event = NULL;
113         struct gcpro gcpro1;
114         static int num_events;
115
116         CHECK_PROCESS(process);
117
118         restore_gpm_state(get_process_infd(XPROCESS(process)));
119
120         if (!Gpm_GetEvent(&ev)) {
121                 warn_when_safe(Qnil, Qcritical, "Gpm_GetEvent failed - %d",
122                                gpm_fd);
123                 return (Qzero);
124         }
125
126         GCPRO1(fake_event);
127
128         num_events++;
129
130         fake_event = Fmake_event(Qnil, Qnil);
131         event = XEVENT(fake_event);
132
133         event->timestamp = 0;
134         event->channel = Fselected_frame(Qnil); /* CONSOLE_SELECTED_FRAME (con); */
135
136         /* Whow, wouldn't named defines be NICE!?!?! */
137         modifiers = 0;
138
139         if (ev.modifiers & 1)
140                 modifiers |= XEMACS_MOD_SHIFT;
141         if (ev.modifiers & 2)
142                 modifiers |= XEMACS_MOD_META;
143         if (ev.modifiers & 4)
144                 modifiers |= XEMACS_MOD_CONTROL;
145         if (ev.modifiers & 8)
146                 modifiers |= XEMACS_MOD_META;
147
148         if (ev.buttons & GPM_B_LEFT) {
149                 button = 1;
150         } else if (ev.buttons & GPM_B_MIDDLE) {
151                 button = 2;
152         } else if (ev.buttons & GPM_B_RIGHT) {
153                 button = 3;
154         }
155
156         switch (GPM_BARE_EVENTS(ev.type)) {
157         case GPM_DOWN:
158         case GPM_UP:
159                 event->event_type =
160                     (ev.
161                      type & GPM_DOWN) ? button_press_event :
162                     button_release_event;
163                 event->event.button.x = ev.x;
164                 event->event.button.y = ev.y;
165                 event->event.button.button = button;
166                 event->event.button.modifiers = modifiers;
167                 break;
168         case GPM_MOVE:
169         case GPM_DRAG:
170                 event->event_type = pointer_motion_event;
171                 event->event.motion.x = ev.x;
172                 event->event.motion.y = ev.y;
173                 event->event.motion.modifiers = modifiers;
174         default:
175                 /* This will never happen */
176                 break;
177         }
178
179         /* Handle the event */
180         enqueue_event(fake_event, &gpm_event_queue, &gpm_event_queue_tail);
181
182         UNGCPRO;
183
184         return (Qzero);
185 }
186
187 static void turn_off_gpm(char *process_name)
188 {
189         Lisp_Object process = Fget_process(build_string(process_name));
190         int fd = -1;
191
192         if (NILP(process)) {
193                 /* Something happened to our GPM process - fail silently */
194                 return;
195         }
196
197         fd = get_process_infd(XPROCESS(process));
198
199         restore_gpm_state(fd);
200
201         Gpm_Close();
202
203         clear_gpm_state(fd);
204
205         Fdelete_process(build_string(process_name));
206 }
207
208 #ifdef TIOCLINUX
209 static Lisp_Object
210 tty_get_foreign_selection(Lisp_Object selection_symbol, Lisp_Object target_type)
211 {
212         /* This function can GC */
213         struct device *d = decode_device(Qnil);
214         int fd = DEVICE_INFD(d);
215         char c = 3;
216         Lisp_Object output_stream = Qnil;
217         Lisp_Object terminal_stream = Qnil;
218         Lisp_Object output_string = Qnil;
219         struct gcpro gcpro1, gcpro2, gcpro3;
220
221         GCPRO3(output_stream, terminal_stream, output_string);
222
223         /* The ioctl() to paste actually puts things in the input queue of
224          ** the virtual console, so we need to trap that data, since we are
225          ** supposed to return the actual string selection from this
226          ** function.
227          */
228
229         /* I really hate doing this, but it doesn't seem to cause any
230          ** problems, and it makes the Lstream_read stuff further down
231          ** error out correctly instead of trying to indefinitely read from
232          ** the console.
233          **
234          ** There is no set_descriptor_blocking() function call, but in my
235          ** testing under linux, it has not proved fatal to leave the
236          ** descriptor in non-blocking mode.
237          **
238          ** William Perry Nov 5, 1999
239          */
240         set_descriptor_non_blocking(fd);
241
242         /* We need two streams, one for reading from the selected device,
243          ** and one to write the data into.  There is no writable version
244          ** of the lisp-string lstream, so we make do with a resizing
245          ** buffer stream, and make a string out of it after we are
246          ** done.
247          */
248         output_stream = make_resizing_buffer_output_stream();
249         terminal_stream =
250             make_filedesc_input_stream(fd, 0, -1, LSTR_BLOCKED_OK);
251         output_string = Qnil;
252
253         /* #### We should arguably use a specbind() and an unwind routine here,
254          ** #### but I don't care that much right now.
255          */
256         if (NILP(output_stream) || NILP(terminal_stream)) {
257                 /* Should we signal an error here? */
258                 goto out;
259         }
260
261         if (ioctl(fd, TIOCLINUX, &c) < 0) {
262                 /* Could not get the selection - eek */
263                 UNGCPRO;
264                 return (Qnil);
265         }
266
267         while (1) {
268                 Bufbyte tempbuf[1024];  /* some random amount */
269                 Lstream_data_count i;
270                 Lstream_data_count size_in_bytes =
271                     Lstream_read(XLSTREAM(terminal_stream),
272                                  tempbuf, sizeof(tempbuf));
273
274                 if (size_in_bytes <= 0) {
275                         /* end of the stream */
276                         break;
277                 }
278
279                 /* convert CR->LF */
280                 for (i = 0; i < size_in_bytes; i++) {
281                         if (tempbuf[i] == '\r') {
282                                 tempbuf[i] = '\n';
283                         }
284                 }
285
286                 Lstream_write(XLSTREAM(output_stream), tempbuf, size_in_bytes);
287         }
288
289         Lstream_flush(XLSTREAM(output_stream));
290
291         output_string =
292             make_string(resizing_buffer_stream_ptr(XLSTREAM(output_stream)),
293                         Lstream_byte_count(XLSTREAM(output_stream)));
294
295         Lstream_delete(XLSTREAM(output_stream));
296         Lstream_delete(XLSTREAM(terminal_stream));
297
298       out:
299         UNGCPRO;
300         return (output_string);
301 }
302
303 static Lisp_Object
304 tty_selection_exists_p(Lisp_Object selection, Lisp_Object selection_type)
305 {
306         return (Qt);
307 }
308 #endif                          /* TIOCLINUX */
309
310 #if 0
311 static Lisp_Object
312 tty_own_selection(Lisp_Object selection_name, Lisp_Object selection_value,
313                   Lisp_Object how_to_add, Lisp_Object selection_type)
314 {
315         /* There is no way to do this cleanly - the GPM selection
316          ** 'protocol' (actually the TIOCLINUX ioctl) requires a start and
317          ** end position on the _screen_, not a string to stick in there.
318          ** Lame.
319          **
320          ** William Perry Nov 4, 1999
321          */
322 }
323 #endif
324
325 /* This function appears to work once in a blue moon.  I'm not sure
326 ** exactly why either.  *sigh*
327 **
328 ** William Perry Nov 4, 1999
329 **
330 ** Apparently, this is the way (mouse-position) is supposed to work,
331 ** and I was just expecting something else.  (mouse-pixel-position)
332 ** works just fine.
333 **
334 ** William Perry Nov 7, 1999
335 */
336 static int
337 tty_get_mouse_position(struct device *d, Lisp_Object * frame, int *x, int *y)
338 {
339         Gpm_Event ev;
340         int num_buttons;
341
342         memset(&ev, '\0', sizeof(ev));
343
344         num_buttons = Gpm_GetSnapshot(&ev);
345
346         if (!num_buttons) {
347                 /* This means there are events pending... */
348
349                 /* #### In theory, we should drain the events pending, stick
350                  ** #### them in the queue, and return the mouse position
351                  ** #### anyway.
352                  */
353                 return (-1);
354         }
355         *x = ev.x;
356         *y = ev.y;
357         *frame = DEVICE_SELECTED_FRAME(d);
358         return (1);
359 }
360
361 static void tty_set_mouse_position(struct window *w, int x, int y)
362 {
363         /*
364            #### I couldn't find any GPM functions that set the mouse position.
365            #### Mr. Perry had left this function empty; that must be why.
366            #### karlheg
367          */
368 }
369
370 static int gpm_event_pending_p(int user_p)
371 {
372         Lisp_Object event;
373
374         EVENT_CHAIN_LOOP(event, gpm_event_queue) {
375                 if (!user_p || command_event_p(event)) {
376                         return (1);
377                 }
378         }
379         return (orig_event_pending_p(user_p));
380 }
381
382 static void gpm_next_event_cb(Lisp_Event * event)
383 {
384         /* #### It would be nice to preserve some sort of ordering of the
385          ** #### different types of events, but that would be quite a bit
386          ** #### of work, and would more than likely break the abstraction
387          ** #### between the other event loops and this one.
388          */
389
390         if (!NILP(gpm_event_queue)) {
391                 Lisp_Object queued_event =
392                     dequeue_event(&gpm_event_queue, &gpm_event_queue_tail);
393                 *event = *(XEVENT(queued_event));
394
395                 if (event->event_type == pointer_motion_event) {
396                         struct device *d = decode_device(event->channel);
397                         int fd = DEVICE_INFD(d);
398
399                         /* Ok, now this is just freaky.  Bear with me though.
400                          **
401                          ** If you run gnuclient and attach to a SXEmacs running in
402                          ** X or on another TTY, the mouse cursor does not get
403                          ** drawn correctly.  This is because the ioctl() fails
404                          ** with EPERM because the TTY specified is not our
405                          ** controlling terminal.  If you are the superuser, it
406                          ** will work just spiffy.  The appropriate source file (at
407                          ** least in linux 2.2.x) is
408                          ** .../linux/drivers/char/console.c in the function
409                          ** tioclinux().  The following bit of code is brutal to
410                          ** us:
411                          **
412                          ** if (current->tty != tty && !suser())
413                          **    return -EPERM;
414                          **
415                          ** I even tried setting us as a process leader, removing
416                          ** our controlling terminal, and then using the TIOCSCTTY
417                          ** to set up a new controlling terminal, all with no luck.
418                          **
419                          ** What is even weirder is if you run SXEmacs in a VC, and
420                          ** attach to it from another VC with gnuclient, go back to
421                          ** the original VC and hit a key, the mouse pointer
422                          ** displays (in BOTH VCs), until you hit a key in the
423                          ** second VC, after which it does not display in EITHER
424                          ** VC.  Bizarre, no?
425                          **
426                          ** All I can say is thank god Linux comes with source code
427                          ** or I would have been completely confused.  Well, ok,
428                          ** I'm still completely confused.  I don't see why they
429                          ** don't just check the permissions on the device
430                          ** (actually, if you have enough access to it to get the
431                          ** console's file descriptor, you should be able to do
432                          ** with it as you wish, but maybe that is just me).
433                          **
434                          ** William M. Perry - Nov 9, 1999
435                          */
436
437                         Gpm_DrawPointer(event->event.motion.x,
438                                         event->event.motion.y, fd);
439                 }
440
441                 return;
442         }
443
444         orig_next_event_cb(event);
445 }
446
447 static void hook_event_callbacks_once(void)
448 {
449         static int hooker;
450
451         if (!hooker) {
452                 orig_event_pending_p = event_stream->event_pending_p;
453                 orig_next_event_cb = event_stream->next_event_cb;
454                 event_stream->event_pending_p = gpm_event_pending_p;
455                 event_stream->next_event_cb = gpm_next_event_cb;
456                 hooker = 1;
457         }
458 }
459
460 static void hook_console_methods_once(void)
461 {
462         static int hooker;
463
464         if (!hooker) {
465                 /* Install the mouse position methods for the TTY console type */
466                 CONSOLE_HAS_METHOD(tty, get_mouse_position);
467                 CONSOLE_HAS_METHOD(tty, set_mouse_position);
468                 CONSOLE_HAS_METHOD(tty, get_foreign_selection);
469                 CONSOLE_HAS_METHOD(tty, selection_exists_p);
470 #if 0
471                 CONSOLE_HAS_METHOD(tty, own_selection);
472 #endif
473         }
474 }
475
476 DEFUN("gpm-enabled-p", Fgpm_enabled_p, 0, 1, 0, /*
477 Return non-nil if GPM mouse support is currently enabled on DEVICE.
478 */
479       (device))
480 {
481         char *console_name = ttyname(DEVICE_INFD(decode_device(device)));
482         char process_name[1024];
483         Lisp_Object proc;
484
485         if (!console_name) {
486                 return (Qnil);
487         }
488
489         memset(process_name, '\0', sizeof(process_name));
490         snprintf(process_name, sizeof(process_name) - 1, "gpm for %s",
491                  console_name);
492
493         proc = Fget_process(build_string(process_name));
494
495         if (NILP(proc)) {
496                 return (Qnil);
497         }
498
499         if (1) {                /* (PROCESS_LIVE_P (proc)) */
500                 return (Qt);
501         }
502         return (Qnil);
503 }
504
505 DEFUN("gpm-enable", Fgpm_enable, 0, 2, 0,       /*
506 Toggle accepting of GPM mouse events.
507 */
508       (device, arg))
509 {
510         Gpm_Connect conn;
511         int rval;
512         Lisp_Object gpm_process;
513         Lisp_Object gpm_filter;
514         struct device *d = decode_device(device);
515         int fd = DEVICE_INFD(d);
516         char *console_name = ttyname(fd);
517         char process_name[1024];
518
519         hook_event_callbacks_once();
520         hook_console_methods_once();
521
522         if (noninteractive) {
523                 error("Can't connect to GPM in batch mode.");
524         }
525
526         if (!console_name) {
527                 /* Something seriously wrong here... */
528                 return (Qnil);
529         }
530
531         memset(process_name, '\0', sizeof(process_name));
532         snprintf(process_name, sizeof(process_name) - 1, "gpm for %s",
533                  console_name);
534
535         if (NILP(arg)) {
536                 turn_off_gpm(process_name);
537                 return (Qnil);
538         }
539
540         /* DANGER DANGER.
541          ** Though shalt not call (gpm-enable t) after we have already
542          ** started, or stuff blows up.
543          */
544         if (!NILP(Fgpm_enabled_p(device))) {
545                 error("GPM already enabled for this console.");
546         }
547
548         conn.eventMask = GPM_DOWN | GPM_UP | GPM_MOVE | GPM_DRAG;
549         conn.defaultMask = GPM_MOVE;
550         conn.minMod = 0;
551         conn.maxMod = ((1 << KG_SHIFT) | (1 << KG_ALT) | (1 << KG_CTRL));
552
553         /* Reset some silly static variables so that multiple Gpm_Open()
554          ** calls have even a slight chance of working
555          */
556         gpm_tried = 0;
557         gpm_flag = 0;
558         gpm_stack = NULL;
559
560         /* Make sure Gpm_Open() does ioctl() on the correct
561          ** descriptor, or it can get the wrong terminal sizes, etc.
562          */
563         gpm_consolefd = fd;
564
565         /* We have to pass the virtual console manually, otherwise if you
566          ** use 'gnuclient -nw' to connect to an SXEmacs that is running in
567          ** X, Gpm_Open() tries to use ttyname(0 | 1 | 2) to find out which
568          ** console you are using, which is of course not correct for the
569          ** new tty device.
570          */
571         if (strncmp(console_name, "/dev/tty", 8) || !isdigit(console_name[8])) {
572                 /* Urk, something really wrong */
573                 return (Qnil);
574         }
575
576         rval = Gpm_Open(&conn, atoi(console_name + 8));
577
578         switch (rval) {
579         case -1:                /* General failure */
580                 break;
581         case -2:                /* We are running under an XTerm */
582                 Gpm_Close();
583                 break;
584         default:
585                 /* Is this really necessary? */
586                 set_descriptor_non_blocking(gpm_fd);
587                 store_gpm_state(gpm_fd);
588                 gpm_process =
589                     connect_to_file_descriptor(build_string(process_name), Qnil,
590                                                make_int(gpm_fd),
591                                                make_int(gpm_fd));
592
593                 if (!NILP(gpm_process)) {
594                         rval = 0;
595                         Fprocess_kill_without_query(gpm_process, Qnil);
596                         XSETSUBR(gpm_filter, &SFreceive_gpm_event);
597                         set_process_filter(gpm_process, gpm_filter, 1);
598
599                         /* Keep track of the device for later */
600                         /* Fput (gpm_process, intern ("gpm-device"), device); */
601                 } else {
602                         Gpm_Close();
603                         rval = -1;
604                 }
605         }
606
607         return (rval ? Qnil : Qt);
608 }
609
610 void vars_of_gpmevent(void)
611 {
612         gpm_event_queue = Qnil;
613         gpm_event_queue_tail = Qnil;
614         staticpro(&gpm_event_queue);
615         staticpro(&gpm_event_queue_tail);
616         dump_add_root_object(&gpm_event_queue);
617         dump_add_root_object(&gpm_event_queue_tail);
618 }
619
620 void syms_of_gpmevent(void)
621 {
622         DEFSUBR(Freceive_gpm_event);
623         DEFSUBR(Fgpm_enable);
624         DEFSUBR(Fgpm_enabled_p);
625 }
626
627 #endif                          /* HAVE_GPM */