1 /* gtkmouse.c: GTK routines for emulating Spectrum mice
2    Copyright (c) 2004 Darren Salt
3    Copyright (c) 2015 Sergio Baldoví
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License along
16    with this program; if not, write to the Free Software Foundation, Inc.,
17    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 
19    Author contact information:
20 
21    E-mail: linux@youmustbejoking.demon.co.uk
22 
23 */
24 
25 #include <config.h>
26 
27 #include <gtk/gtk.h>
28 #include <gdk/gdk.h>
29 
30 #include "gtkinternals.h"
31 #include "ui/ui.h"
32 
33 #ifdef GDK_WINDOWING_WAYLAND
34 #include <gdk/gdkwayland.h>
35 #endif
36 
37 #ifdef GDK_WINDOWING_X11
38 /* For XWarpPointer *only* - see below */
39 #include <gdk/gdkx.h>
40 #include <X11/Xlib.h>
41 #endif
42 
43 static GdkCursor *nullpointer = NULL;
44 
45 /* The widget we base our events, grabs, warping etc on */
46 static GtkWidget *mouse_widget = NULL;
47 
48 /* Translate absolute pointer coordinate to relative movement */
49 static void (*mouse_motion_fn)( gdouble x, gdouble y, int *rel_x, int *rel_y );
50 
51 #if defined GDK_WINDOWING_WAYLAND || defined GDK_WINDOWING_WIN32
52 
53 /* On Wayland we can't warp the pointer so we keep the last position */
54 static gdouble last_pos_x = 0;
55 static gdouble last_pos_y = 0;
56 static int have_last_position = 0;
57 
58 static void
mouse_motion_relative(gdouble x,gdouble y,int * rel_x,int * rel_y)59 mouse_motion_relative( gdouble x, gdouble y, int *rel_x, int *rel_y )
60 {
61    if( have_last_position ) {
62      *rel_x = x - last_pos_x;
63      *rel_y = y - last_pos_y;
64    } else {
65      *rel_x = 0;
66      *rel_y = 0;
67      have_last_position = 1;
68    }
69 
70    last_pos_x = x;
71    last_pos_y = y;
72 }
73 
74 #endif                /* if defined GDK_WINDOWING_WAYLAND ||
75                             defined GDK_WINDOWING_WIN32 */
76 
77 #ifdef GDK_WINDOWING_X11
78 
79 static void
mouse_motion_x11(gdouble x,gdouble y,int * rel_x,int * rel_y)80 mouse_motion_x11( gdouble x, gdouble y, int *rel_x, int *rel_y )
81 {
82   *rel_x = x - 128;
83   *rel_y = y - 128;
84 
85   if( x != 128 || y != 128 ) {
86     GdkWindow *window = gtk_widget_get_window( mouse_widget );
87     XWarpPointer( GDK_WINDOW_XDISPLAY( window ), None,
88                   GDK_WINDOW_XID( window ), 0, 0, 0, 0, 128, 128 );
89   }
90 }
91 
92 #endif                /* #ifdef GDK_WINDOWING_X11 */
93 
94 #ifdef GDK_WINDOWING_WIN32
95 
96 static void
mouse_motion_win32(gdouble x,gdouble y,int * rel_x,int * rel_y)97 mouse_motion_win32( gdouble x, gdouble y, int *rel_x, int *rel_y )
98 {
99   mouse_motion_relative( x, y, rel_x, rel_y );
100 
101   /* Keep pointer hidden */
102   SetCursor( NULL );
103 }
104 
105 #endif                /* #ifdef GDK_WINDOWING_WIN32 */
106 
107 static void
gtkmouse_reset_pointer(void)108 gtkmouse_reset_pointer( void )
109 {
110   /* Ugh. GDK doesn't have its own move-pointer function :-|
111 
112      The logic here is a bit hairy:
113 
114      * On GTK 2.x, we warp relative to the drawing area
115      * On GTK 3.x on X11, we warp relative to the top-level window
116      * On GTK 3.x on Wayland, we don't warp at all because it causes a
117        segfault (see bug #435)
118      * On GTK 3.x on win32, we don't warp at all
119    */
120 
121 #ifdef GDK_WINDOWING_WAYLAND
122 
123   GdkDisplay *display = gdk_display_get_default();
124   if( GDK_IS_WAYLAND_DISPLAY( display ) ) {
125     mouse_motion_fn = mouse_motion_relative;
126     have_last_position = 0;
127     return;
128   }
129 
130 #endif                /* #ifdef GDK_WINDOWING_WAYLAND */
131 
132 #ifdef GDK_WINDOWING_X11
133 
134   mouse_motion_fn = mouse_motion_x11;
135 
136   /* Force initial position */
137   GdkWindow *window = gtk_widget_get_window( mouse_widget );
138   XWarpPointer( GDK_WINDOW_XDISPLAY( window ), None,
139                 GDK_WINDOW_XID( window ), 0, 0, 0, 0, 128, 128 );
140   return;
141 
142 #endif                /* #ifdef GDK_WINDOWING_WAYLAND */
143 
144 #ifdef GDK_WINDOWING_WIN32
145 
146   mouse_motion_fn = mouse_motion_win32;
147   have_last_position = 0;
148   return;
149 
150 #endif                /* #ifdef GDK_WINDOWING_WIN32 */
151 
152 }
153 
154 static gboolean
motion_event(GtkWidget * widget GCC_UNUSED,GdkEventMotion * event,gpointer data GCC_UNUSED)155 motion_event( GtkWidget *widget GCC_UNUSED, GdkEventMotion *event,
156               gpointer data GCC_UNUSED )
157 {
158   int rel_x, rel_y;
159 
160   if( !ui_mouse_grabbed ) return FALSE;
161 
162   /* Get relative movement from last position */
163   (*mouse_motion_fn)( event->x, event->y, &rel_x, &rel_y );
164   ui_mouse_motion( rel_x, rel_y );
165 
166   return FALSE;
167 }
168 
169 static gboolean
button_event(GtkWidget * widget GCC_UNUSED,GdkEventButton * event,gpointer data GCC_UNUSED)170 button_event( GtkWidget *widget GCC_UNUSED, GdkEventButton *event,
171 	      gpointer data GCC_UNUSED )
172 {
173   if( event->type == GDK_BUTTON_PRESS || event->type == GDK_2BUTTON_PRESS
174       || event->type == GDK_3BUTTON_PRESS )
175     ui_mouse_button( event->button, 1 );
176   else
177     ui_mouse_button( event->button, 0 );
178 
179   /* Stop other handlers only if we've grabbed the mouse */
180   return ui_mouse_grabbed? TRUE : FALSE;
181 }
182 
183 void
gtkmouse_init(void)184 gtkmouse_init( void )
185 {
186 #if GTK_CHECK_VERSION( 3, 0, 0 )
187   mouse_widget = gtkui_window;
188 #else                 /* #if GTK_CHECK_VERSION( 3, 0, 0 ) */
189   mouse_widget = gtkui_drawing_area;
190 #endif                /* #if GTK_CHECK_VERSION( 3, 0, 0 ) */
191 
192   gtk_widget_add_events( GTK_WIDGET( mouse_widget ),
193     GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK );
194   g_signal_connect( G_OBJECT( mouse_widget ), "motion-notify-event",
195 		    G_CALLBACK( motion_event ), NULL );
196   g_signal_connect( G_OBJECT( mouse_widget ), "button-press-event",
197 		    G_CALLBACK( button_event ), NULL );
198   g_signal_connect( G_OBJECT( mouse_widget ), "button-release-event",
199 		    G_CALLBACK( button_event ), NULL );
200 }
201 
202 int
ui_mouse_grab(int startup)203 ui_mouse_grab( int startup )
204 {
205   GdkWindow *window;
206   GdkGrabStatus status;
207 
208   if( startup ) return 0;
209 
210   window = gtk_widget_get_window( mouse_widget );
211 
212 #if !GTK_CHECK_VERSION( 3, 20, 0 )
213 
214   if( !nullpointer ) {
215     nullpointer = gdk_cursor_new( GDK_BLANK_CURSOR );
216   }
217 
218   status = gdk_pointer_grab( window, FALSE,
219                              GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
220                              GDK_BUTTON_RELEASE_MASK,
221                              window, nullpointer, GDK_CURRENT_TIME );
222 
223 #else
224 
225   GdkDisplay *display;
226   GdkSeat *seat;
227 
228   display = gdk_window_get_display( window );
229 
230   if( !nullpointer ) {
231     nullpointer = gdk_cursor_new_for_display( display, GDK_BLANK_CURSOR );
232   }
233 
234   seat = gdk_display_get_default_seat( display );
235   status = gdk_seat_grab( seat, window, GDK_SEAT_CAPABILITY_ALL_POINTING,
236                           FALSE, nullpointer, NULL, NULL, NULL );
237 
238 #endif                /* #if !GTK_CHECK_VERSION( 3, 20, 0 ) */
239 
240   if( status == GDK_GRAB_SUCCESS ) {
241     gtkmouse_reset_pointer();
242     ui_statusbar_update( UI_STATUSBAR_ITEM_MOUSE, UI_STATUSBAR_STATE_ACTIVE );
243     return 1;
244   }
245 
246   ui_error( UI_ERROR_WARNING, "Mouse grab failed" );
247   return 0;
248 }
249 
250 int
ui_mouse_release(int suspend GCC_UNUSED)251 ui_mouse_release( int suspend GCC_UNUSED )
252 {
253 #if !GTK_CHECK_VERSION( 3, 20, 0 )
254 
255   gdk_pointer_ungrab( GDK_CURRENT_TIME );
256 
257 #else
258 
259   GdkDisplay *display;
260   GdkSeat *seat;
261 
262   display = gtk_widget_get_display( mouse_widget );
263   seat = gdk_display_get_default_seat( display );
264   gdk_seat_ungrab( seat );
265 
266 #endif                /* #if !GTK_CHECK_VERSION( 3, 20, 0 ) */
267 
268   ui_statusbar_update( UI_STATUSBAR_ITEM_MOUSE, UI_STATUSBAR_STATE_INACTIVE );
269   return 0;
270 }
271