/* GPM (General purpose mouse) functions Copyright (C) 1997 William M. Perry Copyright (C) 1999 Free Software Foundation, Inc. This file is part of XEmacs. XEmacs is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. XEmacs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with XEmacs; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Synched up with: Not in FSF. */ /* Authors: William Perry */ #include #include "lisp.h" #include "console.h" #include "console-tty.h" #include "device.h" #include "events.h" #include "events-mod.h" #include "sysdep.h" #include "commands.h" #include "lstream.h" #include "sysproc.h" /* for MAXDESC */ #include "process.h" #ifdef HAVE_GPM #include "gpmevent.h" #include #if (!defined(__linux__)) /* possible under xterm */ #define KG_SHIFT 0 #define KG_CTRL 2 #define KG_ALT 3 #else #include #endif extern int gpm_tried; extern void *gpm_stack; static int (*orig_event_pending_p) (int); static void (*orig_next_event_cb) (Lisp_Event *); static Lisp_Object gpm_event_queue; static Lisp_Object gpm_event_queue_tail; struct __gpm_state { int gpm_tried; int gpm_flag; void *gpm_stack; }; static struct __gpm_state gpm_state_information[MAXDESC]; static void store_gpm_state (int fd) { gpm_state_information[fd].gpm_tried = gpm_tried; gpm_state_information[fd].gpm_flag = gpm_flag; gpm_state_information[fd].gpm_stack = gpm_stack; } static void restore_gpm_state (int fd) { gpm_tried = gpm_state_information[fd].gpm_tried; gpm_flag = gpm_state_information[fd].gpm_flag; gpm_stack = gpm_state_information[fd].gpm_stack; gpm_consolefd = gpm_fd = fd; } static void clear_gpm_state (int fd) { if (fd >= 0) { memset(&gpm_state_information[fd], '\0', sizeof(struct __gpm_state)); } gpm_tried = gpm_flag = 1; gpm_fd = gpm_consolefd = -1; gpm_stack = NULL; } static int get_process_infd (Lisp_Process *p) { Lisp_Object instr, outstr; get_process_streams (p, &instr, &outstr); assert (!NILP (instr)); return filedesc_stream_fd (XLSTREAM (instr)); } DEFUN ("receive-gpm-event", Freceive_gpm_event, 0, 2, 0, /* Run GPM_GetEvent(). This function is the process handler for the GPM connection. */ (process, string)) { Gpm_Event ev; int modifiers = 0; int button = 1; Lisp_Object fake_event = Qnil; Lisp_Event *event = NULL; struct gcpro gcpro1; static int num_events; CHECK_PROCESS (process); restore_gpm_state (get_process_infd (XPROCESS (process))); if (!Gpm_GetEvent(&ev)) { warn_when_safe (Qnil, Qcritical, "Gpm_GetEvent failed - %d", gpm_fd); return(Qzero); } GCPRO1(fake_event); num_events++; fake_event = Fmake_event (Qnil, Qnil); event = XEVENT(fake_event); event->timestamp = 0; event->channel = Fselected_frame (Qnil); /* CONSOLE_SELECTED_FRAME (con); */ /* Whow, wouldn't named defines be NICE!?!?! */ modifiers = 0; if (ev.modifiers & 1) modifiers |= XEMACS_MOD_SHIFT; if (ev.modifiers & 2) modifiers |= XEMACS_MOD_META; if (ev.modifiers & 4) modifiers |= XEMACS_MOD_CONTROL; if (ev.modifiers & 8) modifiers |= XEMACS_MOD_META; if (ev.buttons & GPM_B_LEFT) { button = 1; } else if (ev.buttons & GPM_B_MIDDLE) { button = 2; } else if (ev.buttons & GPM_B_RIGHT) { button = 3; } switch (GPM_BARE_EVENTS(ev.type)) { case GPM_DOWN: case GPM_UP: event->event_type = (ev.type & GPM_DOWN) ? button_press_event : button_release_event; event->event.button.x = ev.x; event->event.button.y = ev.y; event->event.button.button = button; event->event.button.modifiers = modifiers; break; case GPM_MOVE: case GPM_DRAG: event->event_type = pointer_motion_event; event->event.motion.x = ev.x; event->event.motion.y = ev.y; event->event.motion.modifiers = modifiers; default: /* This will never happen */ break; } /* Handle the event */ enqueue_event (fake_event, &gpm_event_queue, &gpm_event_queue_tail); UNGCPRO; return (Qzero); } static void turn_off_gpm (char *process_name) { Lisp_Object process = Fget_process (build_string (process_name)); int fd = -1; if (NILP (process)) { /* Something happened to our GPM process - fail silently */ return; } fd = get_process_infd (XPROCESS (process)); restore_gpm_state (fd); Gpm_Close(); clear_gpm_state (fd); Fdelete_process (build_string (process_name)); } #ifdef TIOCLINUX static Lisp_Object tty_get_foreign_selection (Lisp_Object selection_symbol, Lisp_Object target_type) { /* This function can GC */ struct device *d = decode_device (Qnil); int fd = DEVICE_INFD (d); char c = 3; Lisp_Object output_stream = Qnil; Lisp_Object terminal_stream = Qnil ; Lisp_Object output_string = Qnil; struct gcpro gcpro1,gcpro2,gcpro3; GCPRO3(output_stream,terminal_stream,output_string); /* The ioctl() to paste actually puts things in the input queue of ** the virtual console, so we need to trap that data, since we are ** supposed to return the actual string selection from this ** function. */ /* I really hate doing this, but it doesn't seem to cause any ** problems, and it makes the Lstream_read stuff further down ** error out correctly instead of trying to indefinitely read from ** the console. ** ** There is no set_descriptor_blocking() function call, but in my ** testing under linux, it has not proved fatal to leave the ** descriptor in non-blocking mode. ** ** William Perry Nov 5, 1999 */ set_descriptor_non_blocking (fd); /* We need two streams, one for reading from the selected device, ** and one to write the data into. There is no writable version ** of the lisp-string lstream, so we make do with a resizing ** buffer stream, and make a string out of it after we are ** done. */ output_stream = make_resizing_buffer_output_stream (); terminal_stream = make_filedesc_input_stream (fd, 0, -1, LSTR_BLOCKED_OK); output_string = Qnil; /* #### We should arguably use a specbind() and an unwind routine here, ** #### but I don't care that much right now. */ if (NILP (output_stream) || NILP (terminal_stream)) { /* Should we signal an error here? */ goto out; } if (ioctl (fd, TIOCLINUX, &c) < 0) { /* Could not get the selection - eek */ UNGCPRO; return (Qnil); } while (1) { Bufbyte tempbuf[1024]; /* some random amount */ Lstream_data_count i; Lstream_data_count size_in_bytes = Lstream_read (XLSTREAM (terminal_stream), tempbuf, sizeof (tempbuf)); if (size_in_bytes <= 0) { /* end of the stream */ break; } /* convert CR->LF */ for (i = 0; i < size_in_bytes; i++) { if (tempbuf[i] == '\r') { tempbuf[i] = '\n'; } } Lstream_write (XLSTREAM (output_stream), tempbuf, size_in_bytes); } Lstream_flush (XLSTREAM (output_stream)); output_string = make_string (resizing_buffer_stream_ptr (XLSTREAM (output_stream)), Lstream_byte_count (XLSTREAM (output_stream))); Lstream_delete (XLSTREAM (output_stream)); Lstream_delete (XLSTREAM (terminal_stream)); out: UNGCPRO; return (output_string); } static Lisp_Object tty_selection_exists_p (Lisp_Object selection, Lisp_Object selection_type) { return (Qt); } #endif /* TIOCLINUX */ #if 0 static Lisp_Object tty_own_selection (Lisp_Object selection_name, Lisp_Object selection_value, Lisp_Object how_to_add, Lisp_Object selection_type) { /* There is no way to do this cleanly - the GPM selection ** 'protocol' (actually the TIOCLINUX ioctl) requires a start and ** end position on the _screen_, not a string to stick in there. ** Lame. ** ** William Perry Nov 4, 1999 */ } #endif /* This function appears to work once in a blue moon. I'm not sure ** exactly why either. *sigh* ** ** William Perry Nov 4, 1999 ** ** Apparently, this is the way (mouse-position) is supposed to work, ** and I was just expecting something else. (mouse-pixel-position) ** works just fine. ** ** William Perry Nov 7, 1999 */ static int tty_get_mouse_position (struct device *d, Lisp_Object *frame, int *x, int *y) { Gpm_Event ev; int num_buttons; memset(&ev,'\0',sizeof(ev)); num_buttons = Gpm_GetSnapshot(&ev); if (!num_buttons) { /* This means there are events pending... */ /* #### In theory, we should drain the events pending, stick ** #### them in the queue, and return the mouse position ** #### anyway. */ return(-1); } *x = ev.x; *y = ev.y; *frame = DEVICE_SELECTED_FRAME (d); return (1); } static void tty_set_mouse_position (struct window *w, int x, int y) { /* #### I couldn't find any GPM functions that set the mouse position. #### Mr. Perry had left this function empty; that must be why. #### karlheg */ } static int gpm_event_pending_p (int user_p) { Lisp_Object event; EVENT_CHAIN_LOOP (event, gpm_event_queue) { if (!user_p || command_event_p (event)) { return (1); } } return (orig_event_pending_p (user_p)); } static void gpm_next_event_cb (Lisp_Event *event) { /* #### It would be nice to preserve some sort of ordering of the ** #### different types of events, but that would be quite a bit ** #### of work, and would more than likely break the abstraction ** #### between the other event loops and this one. */ if (!NILP (gpm_event_queue)) { Lisp_Object queued_event = dequeue_event (&gpm_event_queue, &gpm_event_queue_tail); *event = *(XEVENT (queued_event)); if (event->event_type == pointer_motion_event) { struct device *d = decode_device (event->channel); int fd = DEVICE_INFD (d); /* Ok, now this is just freaky. Bear with me though. ** ** If you run gnuclient and attach to a XEmacs running in ** X or on another TTY, the mouse cursor does not get ** drawn correctly. This is because the ioctl() fails ** with EPERM because the TTY specified is not our ** controlling terminal. If you are the superuser, it ** will work just spiffy. The appropriate source file (at ** least in linux 2.2.x) is ** .../linux/drivers/char/console.c in the function ** tioclinux(). The following bit of code is brutal to ** us: ** ** if (current->tty != tty && !suser()) ** return -EPERM; ** ** I even tried setting us as a process leader, removing ** our controlling terminal, and then using the TIOCSCTTY ** to set up a new controlling terminal, all with no luck. ** ** What is even weirder is if you run XEmacs in a VC, and ** attach to it from another VC with gnuclient, go back to ** the original VC and hit a key, the mouse pointer ** displays (in BOTH VCs), until you hit a key in the ** second VC, after which it does not display in EITHER ** VC. Bizarre, no? ** ** All I can say is thank god Linux comes with source code ** or I would have been completely confused. Well, ok, ** I'm still completely confused. I don't see why they ** don't just check the permissions on the device ** (actually, if you have enough access to it to get the ** console's file descriptor, you should be able to do ** with it as you wish, but maybe that is just me). ** ** William M. Perry - Nov 9, 1999 */ Gpm_DrawPointer (event->event.motion.x,event->event.motion.y, fd); } return; } orig_next_event_cb (event); } static void hook_event_callbacks_once (void) { static int hooker; if (!hooker) { orig_event_pending_p = event_stream->event_pending_p; orig_next_event_cb = event_stream->next_event_cb; event_stream->event_pending_p = gpm_event_pending_p; event_stream->next_event_cb = gpm_next_event_cb; hooker = 1; } } static void hook_console_methods_once (void) { static int hooker; if (!hooker) { /* Install the mouse position methods for the TTY console type */ CONSOLE_HAS_METHOD (tty, get_mouse_position); CONSOLE_HAS_METHOD (tty, set_mouse_position); CONSOLE_HAS_METHOD (tty, get_foreign_selection); CONSOLE_HAS_METHOD (tty, selection_exists_p); #if 0 CONSOLE_HAS_METHOD (tty, own_selection); #endif } } DEFUN ("gpm-enabled-p", Fgpm_enabled_p, 0, 1, 0, /* Return non-nil if GPM mouse support is currently enabled on DEVICE. */ (device)) { char *console_name = ttyname (DEVICE_INFD (decode_device (device))); char process_name[1024]; Lisp_Object proc; if (!console_name) { return (Qnil); } memset (process_name, '\0', sizeof(process_name)); snprintf (process_name, sizeof(process_name) - 1, "gpm for %s", console_name); proc = Fget_process (build_string (process_name)); if (NILP (proc)) { return (Qnil); } if (1) /* (PROCESS_LIVE_P (proc)) */ { return (Qt); } return (Qnil); } DEFUN ("gpm-enable", Fgpm_enable, 0, 2, 0, /* Toggle accepting of GPM mouse events. */ (device, arg)) { Gpm_Connect conn; int rval; Lisp_Object gpm_process; Lisp_Object gpm_filter; struct device *d = decode_device (device); int fd = DEVICE_INFD (d); char *console_name = ttyname (fd); char process_name[1024]; hook_event_callbacks_once (); hook_console_methods_once (); if (noninteractive) { error ("Can't connect to GPM in batch mode."); } if (!console_name) { /* Something seriously wrong here... */ return (Qnil); } memset (process_name, '\0', sizeof(process_name)); snprintf (process_name, sizeof(process_name) - 1, "gpm for %s", console_name); if (NILP (arg)) { turn_off_gpm (process_name); return (Qnil); } /* DANGER DANGER. ** Though shalt not call (gpm-enable t) after we have already ** started, or stuff blows up. */ if (!NILP (Fgpm_enabled_p (device))) { error ("GPM already enabled for this console."); } conn.eventMask = GPM_DOWN|GPM_UP|GPM_MOVE|GPM_DRAG; conn.defaultMask = GPM_MOVE; conn.minMod = 0; conn.maxMod = ((1<