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