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