1 /*
2  * Copyright (C) 2012 Red Hat, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street - Suite 500, Boston, MA 02110-1335, USA.
17  *
18  * Author: Olivier Fourdan <ofourdan@redhat.com>
19  *
20  */
21 
22 #include "config.h"
23 
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <glib/gi18n.h>
27 #include <gtk/gtk.h>
28 #include <cairo.h>
29 #include <librsvg/rsvg.h>
30 
31 #include "csd-wacom-osd-window.h"
32 #include "csd-wacom-device.h"
33 #include "csd-enums.h"
34 
35 #define ROTATION_KEY                "rotation"
36 #define ACTION_TYPE_KEY             "action-type"
37 #define CUSTOM_ACTION_KEY           "custom-action"
38 #define CUSTOM_ELEVATOR_ACTION_KEY  "custom-elevator-action"
39 #define RES_PATH                    "/org/cinnamon/settings-daemon/plugins/wacom/"
40 
41 #define BACK_OPACITY		0.8
42 #define INACTIVE_COLOR		"#ededed"
43 #define ACTIVE_COLOR		"#729fcf"
44 #define STROKE_COLOR		"#000000"
45 #define DARK_COLOR		"#535353"
46 #define BACK_COLOR		"#000000"
47 
48 #define ELEVATOR_TIMEOUT	250 /* ms */
49 
50 static struct {
51 	const gchar     *color_name;
52 	const gchar     *color_value;
53 } css_color_table[] = {
54 	{ "inactive_color", INACTIVE_COLOR },
55 	{ "active_color",   ACTIVE_COLOR   },
56 	{ "stroke_color",   STROKE_COLOR   },
57 	{ "dark_color",     DARK_COLOR     },
58 	{ "back_color",     BACK_COLOR     }
59 };
60 
61 static gchar *
replace_string(gchar ** string,const gchar * search,const char * replacement)62 replace_string (gchar **string, const gchar *search, const char *replacement)
63 {
64 	GRegex *regex;
65 	gchar *res;
66 
67 	g_return_val_if_fail (*string != NULL, NULL);
68 	g_return_val_if_fail (string != NULL, NULL);
69 	g_return_val_if_fail (search != NULL, *string);
70 	g_return_val_if_fail (replacement != NULL, *string);
71 
72 	regex = g_regex_new (search, 0, 0, NULL);
73 	res = g_regex_replace_literal (regex, *string, -1, 0, replacement, 0, NULL);
74 	g_regex_unref (regex);
75 	/* The given string is freed and replaced by the resulting replacement */
76 	g_free (*string);
77 	*string = res;
78 
79 	return res;
80 }
81 
82 static gchar
get_last_char(gchar * string)83 get_last_char (gchar *string)
84 {
85 	size_t pos;
86 
87 	g_return_val_if_fail (string != NULL, '\0');
88 	pos = strlen (string);
89 	g_return_val_if_fail (pos > 0, '\0');
90 
91 	return string[pos - 1];
92 }
93 
94 static double
get_rotation_in_radian(CsdWacomRotation rotation)95 get_rotation_in_radian (CsdWacomRotation rotation)
96 {
97 	switch (rotation) {
98 	case CSD_WACOM_ROTATION_NONE:
99 		return 0.0;
100 		break;
101 	case CSD_WACOM_ROTATION_HALF:
102 		return G_PI;
103 		break;
104 	/* We only support left-handed/right-handed */
105 	case CSD_WACOM_ROTATION_CCW:
106 	case CSD_WACOM_ROTATION_CW:
107 	default:
108 		break;
109 	}
110 
111 	/* Fallback */
112 	return 0.0;
113 }
114 
115 static gboolean
get_sub_location(RsvgHandle * handle,const char * sub,cairo_t * cr,double * x,double * y)116 get_sub_location (RsvgHandle *handle,
117                   const char *sub,
118                   cairo_t    *cr,
119                   double     *x,
120                   double     *y)
121 {
122 	RsvgPositionData  position;
123 	double tx, ty;
124 
125 	if (!rsvg_handle_get_position_sub (handle, &position, sub)) {
126 		g_warning ("Failed to retrieve '%s' position", sub);
127 		return FALSE;
128 	}
129 
130 	tx = (double) position.x;
131 	ty = (double) position.y;
132 	cairo_user_to_device (cr, &tx, &ty);
133 
134 	if (x)
135 		*x = tx;
136 	if (y)
137 		*y = ty;
138 
139 	return TRUE;
140 }
141 
142 static gboolean
get_image_size(const char * filename,int * width,int * height)143 get_image_size (const char *filename, int *width, int *height)
144 {
145 	RsvgHandle       *handle;
146 	RsvgDimensionData dimensions;
147 	GError* error = NULL;
148 
149 	if (filename == NULL)
150 		return FALSE;
151 
152 	handle = rsvg_handle_new_from_file (filename, &error);
153 	if (error != NULL) {
154 		g_printerr ("%s\n", error->message);
155 		g_error_free (error);
156 	}
157 	if (handle == NULL)
158 		return FALSE;
159 
160 	/* Compute image size */
161 	rsvg_handle_get_dimensions (handle, &dimensions);
162 	g_object_unref (handle);
163 
164 	if (dimensions.width == 0 || dimensions.height == 0)
165 		return FALSE;
166 
167 	if (width)
168 		*width = dimensions.width;
169 
170 	if (height)
171 		*height = dimensions.height;
172 
173 	return TRUE;
174 }
175 
176 static int
get_pango_vertical_offset(PangoLayout * layout)177 get_pango_vertical_offset (PangoLayout *layout)
178 {
179 	const PangoFontDescription *desc;
180 	PangoContext               *context;
181 	PangoLanguage              *language;
182 	PangoFontMetrics           *metrics;
183 	int                         baseline;
184 	int                         strikethrough;
185 	int                         thickness;
186 
187 	context = pango_layout_get_context (layout);
188 	language = pango_language_get_default ();
189 	desc = pango_layout_get_font_description (layout);
190 	metrics = pango_context_get_metrics (context, desc, language);
191 
192 	baseline = pango_layout_get_baseline (layout);
193 	strikethrough =  pango_font_metrics_get_strikethrough_position (metrics);
194 	thickness =  pango_font_metrics_get_underline_thickness (metrics);
195 
196 	return PANGO_PIXELS (baseline - strikethrough - thickness / 2);
197 }
198 
199 #define CSD_TYPE_WACOM_OSD_BUTTON         (csd_wacom_osd_button_get_type ())
200 #define CSD_WACOM_OSD_BUTTON(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CSD_TYPE_WACOM_OSD_BUTTON, CsdWacomOSDButton))
201 #define CSD_WACOM_OSD_BUTTON_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CSD_TYPE_WACOM_OSD_BUTTON, CsdWacomOSDButtonClass))
202 #define CSD_IS_WACOM_OSD_BUTTON(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CSD_TYPE_WACOM_OSD_BUTTON))
203 #define CSD_IS_WACOM_OSD_BUTTON_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CSD_TYPE_WACOM_OSD_BUTTON))
204 #define CSD_WACOM_OSD_BUTTON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CSD_TYPE_WACOM_OSD_BUTTON, CsdWacomOSDButtonClass))
205 
206 typedef struct CsdWacomOSDButtonPrivate CsdWacomOSDButtonPrivate;
207 
208 typedef struct {
209         GObject                   parent;
210         CsdWacomOSDButtonPrivate *priv;
211 } CsdWacomOSDButton;
212 
213 typedef struct {
214         GObjectClass              parent_class;
215 } CsdWacomOSDButtonClass;
216 
217 GType                     csd_wacom_osd_button_get_type        (void) G_GNUC_CONST;
218 
219 enum {
220 	PROP_OSD_BUTTON_0,
221 	PROP_OSD_BUTTON_ID,
222 	PROP_OSD_BUTTON_CLASS,
223 	PROP_OSD_BUTTON_LABEL,
224 	PROP_OSD_BUTTON_ACTIVE,
225 	PROP_OSD_BUTTON_VISIBLE,
226 	PROP_OSD_BUTTON_AUTO_OFF
227 };
228 
229 #define CSD_WACOM_OSD_BUTTON_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
230 					     CSD_TYPE_WACOM_OSD_BUTTON, \
231 					     CsdWacomOSDButtonPrivate))
232 #define MATCH_ID(b,s) (g_strcmp0 (b->priv->id, s) == 0)
233 
234 struct CsdWacomOSDButtonPrivate {
235 	GtkWidget                *widget;
236 	char                     *id;
237 	char                     *class;
238 	char                     *label;
239 	double                    label_x;
240 	double                    label_y;
241 	CsdWacomTabletButtonType  type;
242 	CsdWacomTabletButtonPos   position;
243 	gboolean                  active;
244 	gboolean                  visible;
245 	guint                     auto_off;
246 	guint                     timeout;
247 };
248 
249 static void     csd_wacom_osd_button_class_init  (CsdWacomOSDButtonClass *klass);
250 static void     csd_wacom_osd_button_init        (CsdWacomOSDButton      *osd_button);
251 static void     csd_wacom_osd_button_finalize    (GObject                *object);
252 
G_DEFINE_TYPE(CsdWacomOSDButton,csd_wacom_osd_button,G_TYPE_OBJECT)253 G_DEFINE_TYPE (CsdWacomOSDButton, csd_wacom_osd_button, G_TYPE_OBJECT)
254 
255 static void
256 csd_wacom_osd_button_set_id (CsdWacomOSDButton *osd_button,
257 			     const gchar       *id)
258 {
259 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
260 
261 	osd_button->priv->id = g_strdup (id);
262 }
263 
264 static void
csd_wacom_osd_button_set_class(CsdWacomOSDButton * osd_button,const gchar * class)265 csd_wacom_osd_button_set_class (CsdWacomOSDButton *osd_button,
266 			        const gchar       *class)
267 {
268 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
269 
270 	osd_button->priv->class = g_strdup (class);
271 }
272 
273 static gchar*
csd_wacom_osd_button_get_label_class(CsdWacomOSDButton * osd_button)274 csd_wacom_osd_button_get_label_class (CsdWacomOSDButton *osd_button)
275 {
276 	gchar *label_class;
277 
278 	label_class = g_strconcat ("#Label", osd_button->priv->class, NULL);
279 
280 	return (label_class);
281 }
282 
283 static void
csd_wacom_osd_button_set_label(CsdWacomOSDButton * osd_button,const gchar * str)284 csd_wacom_osd_button_set_label (CsdWacomOSDButton *osd_button,
285 				const gchar       *str)
286 {
287 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
288 
289 	g_free (osd_button->priv->label);
290 	osd_button->priv->label = g_strdup (str ? str : "");
291 }
292 
293 static void
csd_wacom_osd_button_set_button_type(CsdWacomOSDButton * osd_button,CsdWacomTabletButtonType type)294 csd_wacom_osd_button_set_button_type (CsdWacomOSDButton        *osd_button,
295 				      CsdWacomTabletButtonType  type)
296 {
297 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
298 
299 	osd_button->priv->type = type;
300 }
301 
302 static void
csd_wacom_osd_button_set_position(CsdWacomOSDButton * osd_button,CsdWacomTabletButtonPos position)303 csd_wacom_osd_button_set_position (CsdWacomOSDButton        *osd_button,
304 				   CsdWacomTabletButtonPos   position)
305 {
306 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
307 
308 	osd_button->priv->position = position;
309 }
310 
311 static void
csd_wacom_osd_button_set_location(CsdWacomOSDButton * osd_button,double x,double y)312 csd_wacom_osd_button_set_location (CsdWacomOSDButton        *osd_button,
313 				   double                    x,
314 				   double                    y)
315 {
316 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
317 
318 	osd_button->priv->label_x = x;
319 	osd_button->priv->label_y = y;
320 }
321 
322 static void
csd_wacom_osd_button_set_auto_off(CsdWacomOSDButton * osd_button,guint timeout)323 csd_wacom_osd_button_set_auto_off (CsdWacomOSDButton        *osd_button,
324 				   guint                     timeout)
325 {
326 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
327 
328 	osd_button->priv->auto_off = timeout;
329 }
330 
331 static void
csd_wacom_osd_button_redraw(CsdWacomOSDButton * osd_button)332 csd_wacom_osd_button_redraw (CsdWacomOSDButton *osd_button)
333 {
334 	GdkWindow *window;
335 
336 	g_return_if_fail (GTK_IS_WIDGET (osd_button->priv->widget));
337 
338 	window = gtk_widget_get_window (GTK_WIDGET (osd_button->priv->widget));
339 	gdk_window_invalidate_rect (window, NULL, FALSE);
340 }
341 
342 static gboolean
csd_wacom_osd_button_timer(CsdWacomOSDButton * osd_button)343 csd_wacom_osd_button_timer (CsdWacomOSDButton *osd_button)
344 {
345 	/* Auto de-activate the button */
346 	osd_button->priv->active = FALSE;
347 	csd_wacom_osd_button_redraw (osd_button);
348 
349 	return FALSE;
350 }
351 
352 static void
csd_wacom_osd_button_set_active(CsdWacomOSDButton * osd_button,gboolean active)353 csd_wacom_osd_button_set_active (CsdWacomOSDButton *osd_button,
354 				 gboolean           active)
355 {
356 	gboolean previous_state;
357 
358 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
359 
360 	previous_state = osd_button->priv->active;
361 	if (osd_button->priv->auto_off > 0) {
362 		/* For auto-off buttons, apply only if active, de-activation is done in the timeout */
363 		if (active == TRUE)
364 			osd_button->priv->active = active;
365 
366 		if (osd_button->priv->timeout) {
367 			g_source_remove (osd_button->priv->timeout);
368 			osd_button->priv->timeout = 0;
369 		}
370 		osd_button->priv->timeout = g_timeout_add (osd_button->priv->auto_off,
371 		                                           (GSourceFunc) csd_wacom_osd_button_timer,
372 		                                           osd_button);
373 	} else {
374 		/* Whereas for other buttons, apply the change straight away */
375 		osd_button->priv->active = active;
376 	}
377 
378 	if (previous_state != osd_button->priv->active)
379 		csd_wacom_osd_button_redraw (osd_button);
380 }
381 
382 static void
csd_wacom_osd_button_set_visible(CsdWacomOSDButton * osd_button,gboolean visible)383 csd_wacom_osd_button_set_visible (CsdWacomOSDButton *osd_button,
384 				  gboolean           visible)
385 {
386 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
387 
388 	osd_button->priv->visible = visible;
389 }
390 
391 static CsdWacomOSDButton *
csd_wacom_osd_button_new(GtkWidget * widget,gchar * id)392 csd_wacom_osd_button_new (GtkWidget *widget,
393                           gchar *id)
394 {
395 	CsdWacomOSDButton *osd_button;
396 
397 	osd_button = CSD_WACOM_OSD_BUTTON (g_object_new (CSD_TYPE_WACOM_OSD_BUTTON,
398 	                                                 "id", id,
399 	                                                 NULL));
400 	osd_button->priv->widget = widget;
401 
402 	return osd_button;
403 }
404 
405 static void
csd_wacom_osd_button_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)406 csd_wacom_osd_button_set_property (GObject        *object,
407 				   guint           prop_id,
408 				   const GValue   *value,
409 				   GParamSpec     *pspec)
410 {
411 	CsdWacomOSDButton *osd_button;
412 
413 	osd_button = CSD_WACOM_OSD_BUTTON (object);
414 
415 	switch (prop_id) {
416 	case PROP_OSD_BUTTON_ID:
417 		csd_wacom_osd_button_set_id (osd_button, g_value_get_string (value));
418 		break;
419 	case PROP_OSD_BUTTON_CLASS:
420 		csd_wacom_osd_button_set_class (osd_button, g_value_get_string (value));
421 		break;
422 	case PROP_OSD_BUTTON_LABEL:
423 		csd_wacom_osd_button_set_label (osd_button, g_value_get_string (value));
424 		break;
425 	case PROP_OSD_BUTTON_ACTIVE:
426 		csd_wacom_osd_button_set_active (osd_button, g_value_get_boolean (value));
427 		break;
428 	case PROP_OSD_BUTTON_VISIBLE:
429 		csd_wacom_osd_button_set_visible (osd_button, g_value_get_boolean (value));
430 		break;
431 	case PROP_OSD_BUTTON_AUTO_OFF:
432 		csd_wacom_osd_button_set_auto_off (osd_button, g_value_get_uint (value));
433 		break;
434 	default:
435 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
436 		break;
437 	}
438 }
439 
440 static void
csd_wacom_osd_button_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)441 csd_wacom_osd_button_get_property (GObject        *object,
442 				   guint           prop_id,
443 				   GValue         *value,
444 				   GParamSpec     *pspec)
445 {
446 	CsdWacomOSDButton *osd_button;
447 
448 	osd_button = CSD_WACOM_OSD_BUTTON (object);
449 
450 	switch (prop_id) {
451 	case PROP_OSD_BUTTON_ID:
452 		g_value_set_string (value, osd_button->priv->id);
453 		break;
454 	case PROP_OSD_BUTTON_CLASS:
455 		g_value_set_string (value, osd_button->priv->class);
456 		break;
457 	case PROP_OSD_BUTTON_LABEL:
458 		g_value_set_string (value, osd_button->priv->label);
459 		break;
460 	case PROP_OSD_BUTTON_ACTIVE:
461 		g_value_set_boolean (value, osd_button->priv->active);
462 		break;
463 	case PROP_OSD_BUTTON_VISIBLE:
464 		g_value_set_boolean (value, osd_button->priv->visible);
465 		break;
466 	case PROP_OSD_BUTTON_AUTO_OFF:
467 		g_value_set_uint (value, osd_button->priv->auto_off);
468 		break;
469 	default:
470 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
471 		break;
472 	}
473 }
474 
475 static void
csd_wacom_osd_button_class_init(CsdWacomOSDButtonClass * klass)476 csd_wacom_osd_button_class_init (CsdWacomOSDButtonClass *klass)
477 {
478 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
479 
480 	object_class->set_property = csd_wacom_osd_button_set_property;
481 	object_class->get_property = csd_wacom_osd_button_get_property;
482 	object_class->finalize = csd_wacom_osd_button_finalize;
483 
484 	g_object_class_install_property (object_class,
485 	                                 PROP_OSD_BUTTON_ID,
486 	                                 g_param_spec_string ("id",
487 	                                                      "Button Id",
488 	                                                      "The Wacom Button ID",
489 	                                                      "",
490 	                                                      G_PARAM_READWRITE));
491 	g_object_class_install_property (object_class,
492 	                                 PROP_OSD_BUTTON_CLASS,
493 	                                 g_param_spec_string ("class",
494 	                                                      "Button Class",
495 	                                                      "The Wacom Button Class",
496 	                                                      "",
497 	                                                      G_PARAM_READWRITE));
498 	g_object_class_install_property (object_class,
499 	                                 PROP_OSD_BUTTON_LABEL,
500 	                                 g_param_spec_string ("label",
501 	                                                      "Label",
502 	                                                      "The button label",
503 	                                                      "",
504 	                                                      G_PARAM_READWRITE));
505 	g_object_class_install_property (object_class,
506 	                                 PROP_OSD_BUTTON_ACTIVE,
507 	                                 g_param_spec_boolean ("active",
508 	                                                       "Active",
509 	                                                       "Whether the button is active",
510 	                                                       FALSE,
511 	                                                       G_PARAM_READWRITE));
512 	g_object_class_install_property (object_class,
513 	                                 PROP_OSD_BUTTON_VISIBLE,
514 	                                 g_param_spec_boolean ("visible",
515 	                                                       "Visible",
516 	                                                       "Whether the button is visible",
517 	                                                       TRUE,
518 	                                                       G_PARAM_READWRITE));
519 	g_object_class_install_property (object_class,
520 	                                 PROP_OSD_BUTTON_AUTO_OFF,
521 	                                 g_param_spec_uint    ("auto-off",
522 	                                                       "Auto Off",
523 	                                                       "Timeout before button disables itself automatically",
524 	                                                       0,
525 	                                                       G_MAXUINT,
526 	                                                       0, /* disabled by default */
527 	                                                       G_PARAM_READWRITE));
528 
529 	g_type_class_add_private (klass, sizeof (CsdWacomOSDButtonPrivate));
530 }
531 
532 static void
csd_wacom_osd_button_init(CsdWacomOSDButton * osd_button)533 csd_wacom_osd_button_init (CsdWacomOSDButton *osd_button)
534 {
535 	osd_button->priv = CSD_WACOM_OSD_BUTTON_GET_PRIVATE (osd_button);
536 }
537 
538 static void
csd_wacom_osd_button_finalize(GObject * object)539 csd_wacom_osd_button_finalize (GObject *object)
540 {
541 	CsdWacomOSDButton *osd_button;
542 	CsdWacomOSDButtonPrivate *priv;
543 
544 	g_return_if_fail (object != NULL);
545 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (object));
546 
547 	osd_button = CSD_WACOM_OSD_BUTTON (object);
548 
549 	g_return_if_fail (osd_button->priv != NULL);
550 
551 	priv = osd_button->priv;
552 
553 	if (priv->timeout > 0) {
554 		g_source_remove (priv->timeout);
555 		priv->timeout = 0;
556 	}
557 	g_clear_pointer (&priv->id, g_free);
558 	g_clear_pointer (&priv->class, g_free);
559 	g_clear_pointer (&priv->label, g_free);
560 
561 	G_OBJECT_CLASS (csd_wacom_osd_button_parent_class)->finalize (object);
562 }
563 
564 /* Compute the new actual position once rotation is applied */
565 static CsdWacomTabletButtonPos
get_actual_position(CsdWacomTabletButtonPos position,CsdWacomRotation rotation)566 get_actual_position (CsdWacomTabletButtonPos position,
567 		     CsdWacomRotation        rotation)
568 {
569 	switch (rotation) {
570 	case CSD_WACOM_ROTATION_NONE:
571 		return position;
572 		break;
573 	case CSD_WACOM_ROTATION_HALF:
574 		if (position == WACOM_TABLET_BUTTON_POS_LEFT)
575 			return WACOM_TABLET_BUTTON_POS_RIGHT;
576 		if (position == WACOM_TABLET_BUTTON_POS_RIGHT)
577 			return WACOM_TABLET_BUTTON_POS_LEFT;
578 		if (position == WACOM_TABLET_BUTTON_POS_TOP)
579 			return WACOM_TABLET_BUTTON_POS_BOTTOM;
580 		if (position == WACOM_TABLET_BUTTON_POS_BOTTOM)
581 			return WACOM_TABLET_BUTTON_POS_TOP;
582 		break;
583 	/* We only support left-handed/right-handed */
584 	case CSD_WACOM_ROTATION_CCW:
585 	case CSD_WACOM_ROTATION_CW:
586 	default:
587 		break;
588 	}
589 	/* fallback */
590 	return position;
591 }
592 
593 static void
csd_wacom_osd_button_draw_label(CsdWacomOSDButton * osd_button,GtkStyleContext * style_context,PangoContext * pango_context,cairo_t * cr,CsdWacomRotation rotation)594 csd_wacom_osd_button_draw_label (CsdWacomOSDButton *osd_button,
595 			         GtkStyleContext   *style_context,
596 			         PangoContext      *pango_context,
597 			         cairo_t           *cr,
598 			         CsdWacomRotation   rotation)
599 {
600 	CsdWacomOSDButtonPrivate *priv;
601 	PangoLayout              *layout;
602 	PangoRectangle            logical_rect;
603 	CsdWacomTabletButtonPos   actual_position;
604 	double                    lx, ly;
605 	gchar                    *markup;
606 
607 	g_return_if_fail (CSD_IS_WACOM_OSD_BUTTON (osd_button));
608 
609 	priv = osd_button->priv;
610 	if (priv->visible == FALSE)
611 		return;
612 
613 	actual_position = get_actual_position (priv->position, rotation);
614 	layout = pango_layout_new (pango_context);
615 	if (priv->active)
616 		markup = g_strdup_printf ("<span foreground=\"" ACTIVE_COLOR "\" weight=\"normal\">%s</span>", priv->label);
617 	else
618 		markup = g_strdup_printf ("<span foreground=\"" INACTIVE_COLOR "\" weight=\"normal\">%s</span>", priv->label);
619 	pango_layout_set_markup (layout, markup, -1);
620 	g_free (markup);
621 
622 	pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
623 	switch (actual_position) {
624 	case WACOM_TABLET_BUTTON_POS_LEFT:
625 		pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
626 		lx = priv->label_x + logical_rect.x;
627 		ly = priv->label_y + logical_rect.y - get_pango_vertical_offset (layout);
628 		break;
629 	case WACOM_TABLET_BUTTON_POS_RIGHT:
630 		pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
631 		lx = priv->label_x + logical_rect.x - logical_rect.width;
632 		ly = priv->label_y + logical_rect.y - get_pango_vertical_offset (layout);
633 		break;
634 	case WACOM_TABLET_BUTTON_POS_TOP:
635 		pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
636 		lx = priv->label_x + logical_rect.x - logical_rect.width / 2;
637 		ly = priv->label_y + logical_rect.y;
638 		break;
639 	case WACOM_TABLET_BUTTON_POS_BOTTOM:
640 		pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
641 		lx = priv->label_x + logical_rect.x - logical_rect.width / 2;
642 		ly = priv->label_y + logical_rect.y - logical_rect.height;
643 		break;
644 	default:
645 		g_warning ("Unhandled button position");
646 		pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
647 		lx = priv->label_x + logical_rect.x - logical_rect.width / 2;
648 		ly = priv->label_y + logical_rect.y - logical_rect.height / 2;
649 		break;
650 	}
651 	gtk_render_layout (style_context, cr, lx, ly, layout);
652 	g_object_unref (layout);
653 }
654 
655 enum {
656   PROP_OSD_WINDOW_0,
657   PROP_OSD_WINDOW_MESSAGE,
658   PROP_OSD_WINDOW_CSD_WACOM_DEVICE
659 };
660 
661 #define CSD_WACOM_OSD_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
662 					     CSD_TYPE_WACOM_OSD_WINDOW, \
663 					     CsdWacomOSDWindowPrivate))
664 
665 struct CsdWacomOSDWindowPrivate
666 {
667 	RsvgHandle               *handle;
668 	CsdWacomDevice           *pad;
669 	CsdWacomRotation          rotation;
670 	GdkRectangle              screen_area;
671 	GdkRectangle              monitor_area;
672 	GdkRectangle              tablet_area;
673 	char                     *message;
674 	GList                    *buttons;
675 };
676 
677 static void     csd_wacom_osd_window_class_init  (CsdWacomOSDWindowClass *klass);
678 static void     csd_wacom_osd_window_init        (CsdWacomOSDWindow      *osd_window);
679 static void     csd_wacom_osd_window_finalize    (GObject                *object);
680 
G_DEFINE_TYPE(CsdWacomOSDWindow,csd_wacom_osd_window,GTK_TYPE_WINDOW)681 G_DEFINE_TYPE (CsdWacomOSDWindow, csd_wacom_osd_window, GTK_TYPE_WINDOW)
682 
683 static RsvgHandle *
684 load_rsvg_with_base (const char  *css_string,
685 		     const char  *original_layout_path,
686 		     GError     **error)
687 {
688 	RsvgHandle *handle;
689 	char *dirname;
690 
691 	handle = rsvg_handle_new ();
692 
693 	dirname = g_path_get_dirname (original_layout_path);
694 	rsvg_handle_set_base_uri (handle, dirname);
695 	g_free (dirname);
696 
697 	if (!rsvg_handle_write (handle,
698 				(guint8 *) css_string,
699 				strlen (css_string),
700 				error)) {
701 		g_object_unref (handle);
702 		return NULL;
703 	}
704 	if (!rsvg_handle_close (handle, error)) {
705 		g_object_unref (handle);
706 		return NULL;
707 	}
708 
709 	return handle;
710 }
711 
712 static void
csd_wacom_osd_window_update(CsdWacomOSDWindow * osd_window)713 csd_wacom_osd_window_update (CsdWacomOSDWindow *osd_window)
714 {
715 	GError      *error = NULL;
716 	gchar       *width, *height;
717 	gchar       *buttons_section;
718 	gchar       *css_string;
719 	const gchar *layout_file;
720 	GBytes      *css_data;
721         guint i;
722 	GList *l;
723 
724 	g_return_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window));
725 	g_return_if_fail (CSD_IS_WACOM_DEVICE (osd_window->priv->pad));
726 
727 	css_data = g_resources_lookup_data (RES_PATH "tablet-layout.css", 0, &error);
728 	if (error != NULL) {
729 		g_printerr ("GResource error: %s\n", error->message);
730 		g_clear_pointer (&error, g_error_free);
731 	}
732 	if (css_data == NULL)
733 		return;
734 	css_string = g_strdup ((gchar *) g_bytes_get_data (css_data, NULL));
735 	g_bytes_unref(css_data);
736 
737 	width = g_strdup_printf ("%d", osd_window->priv->tablet_area.width);
738 	replace_string (&css_string, "layout_width", width);
739 	g_free (width);
740 
741 	height = g_strdup_printf ("%d", osd_window->priv->tablet_area.height);
742 	replace_string (&css_string, "layout_height", height);
743 	g_free (height);
744 
745 	/* Build the buttons section */
746 	buttons_section = g_strdup ("");
747 	for (l = osd_window->priv->buttons; l != NULL; l = l->next) {
748 		CsdWacomOSDButton *osd_button = l->data;
749 
750 		if (osd_button->priv->visible == FALSE)
751 			continue;
752 
753 		if (osd_button->priv->active) {
754 			buttons_section = g_strconcat (buttons_section,
755 			                               ".", osd_button->priv->class, " {\n"
756 			                               "      stroke:   active_color !important;\n"
757 			                               "      fill:     active_color !important;\n"
758 			                               "    }\n",
759 			                               NULL);
760 		}
761 	}
762 	replace_string (&css_string, "buttons_section", buttons_section);
763 	g_free (buttons_section);
764 
765         for (i = 0; i < G_N_ELEMENTS (css_color_table); i++)
766 		replace_string (&css_string,
767 		                css_color_table[i].color_name,
768 		                css_color_table[i].color_value);
769 
770 	layout_file = csd_wacom_device_get_layout_path (osd_window->priv->pad);
771 	replace_string (&css_string, "layout_file", layout_file);
772 
773 	/* Render the SVG with the CSS applied */
774 	g_clear_object (&osd_window->priv->handle);
775 	osd_window->priv->handle = load_rsvg_with_base (css_string, layout_file, &error);
776 	if (osd_window->priv->handle == NULL) {
777 		g_debug ("CSS applied:\n%s\n", css_string);
778 		g_printerr ("RSVG error: %s\n", error->message);
779 		g_clear_error (&error);
780 	}
781 	g_free (css_string);
782 }
783 
784 static void
csd_wacom_osd_window_draw_message(CsdWacomOSDWindow * osd_window,GtkStyleContext * style_context,PangoContext * pango_context,cairo_t * cr)785 csd_wacom_osd_window_draw_message (CsdWacomOSDWindow   *osd_window,
786 				   GtkStyleContext     *style_context,
787 				   PangoContext        *pango_context,
788 				   cairo_t             *cr)
789 {
790 	GdkRectangle  *monitor_area = &osd_window->priv->monitor_area;
791 	PangoRectangle logical_rect;
792 	PangoLayout *layout;
793 	char *markup;
794 	double x;
795 	double y;
796 
797 	if (osd_window->priv->message == NULL)
798 		return;
799 
800 	layout = pango_layout_new (pango_context);
801 	pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
802 
803 	markup = g_strdup_printf ("<span foreground=\"white\">%s</span>", osd_window->priv->message);
804 	pango_layout_set_markup (layout, markup, -1);
805 	g_free (markup);
806 
807 	pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
808 	x = (monitor_area->width - logical_rect.width) / 2 + logical_rect.x;
809 	y = (monitor_area->height - logical_rect.height) / 2 + logical_rect.y;
810 
811 	gtk_render_layout (style_context, cr, x, y, layout);
812 	g_object_unref (layout);
813 }
814 
815 static void
csd_wacom_osd_window_draw_labels(CsdWacomOSDWindow * osd_window,GtkStyleContext * style_context,PangoContext * pango_context,cairo_t * cr)816 csd_wacom_osd_window_draw_labels (CsdWacomOSDWindow   *osd_window,
817 				  GtkStyleContext     *style_context,
818 				  PangoContext        *pango_context,
819 				  cairo_t             *cr)
820 {
821 	GList *l;
822 
823 	for (l = osd_window->priv->buttons; l != NULL; l = l->next) {
824 		CsdWacomOSDButton *osd_button = l->data;
825 
826 		if (osd_button->priv->visible == FALSE)
827 			continue;
828 
829 		csd_wacom_osd_button_draw_label (osd_button,
830 			                         style_context,
831 			                         pango_context,
832 			                         cr,
833 			                         osd_window->priv->rotation);
834 	}
835 }
836 
837 static void
csd_wacom_osd_window_place_buttons(CsdWacomOSDWindow * osd_window,cairo_t * cr)838 csd_wacom_osd_window_place_buttons (CsdWacomOSDWindow *osd_window,
839 				    cairo_t           *cr)
840 {
841 	GList            *l;
842 
843 	g_return_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window));
844 
845 	for (l = osd_window->priv->buttons; l != NULL; l = l->next) {
846 		CsdWacomOSDButton *osd_button = l->data;
847 		double             label_x, label_y;
848 		gchar             *sub;
849 
850 		sub = csd_wacom_osd_button_get_label_class (osd_button);
851 		if (!get_sub_location (osd_window->priv->handle, sub, cr, &label_x, &label_y)) {
852 			g_warning ("Failed to retrieve %s position", sub);
853 			g_free (sub);
854 			continue;
855 		}
856 		g_free (sub);
857 		csd_wacom_osd_button_set_location (osd_button, label_x, label_y);
858 	}
859 }
860 
861 /* Note: this function does modify the given cairo context */
862 static void
csd_wacom_osd_window_adjust_cairo(CsdWacomOSDWindow * osd_window,cairo_t * cr)863 csd_wacom_osd_window_adjust_cairo (CsdWacomOSDWindow *osd_window,
864                                    cairo_t           *cr)
865 {
866 	double         scale, twidth, theight;
867 	GdkRectangle  *tablet_area  = &osd_window->priv->tablet_area;
868 	GdkRectangle  *screen_area  = &osd_window->priv->screen_area;
869 	GdkRectangle  *monitor_area = &osd_window->priv->monitor_area;
870 
871 	/* Rotate */
872 	cairo_rotate (cr, get_rotation_in_radian (osd_window->priv->rotation));
873 
874 	/* Scale to fit in window */
875 	scale = MIN ((double) monitor_area->width / tablet_area->width,
876 	             (double) monitor_area->height / tablet_area->height);
877 	cairo_scale (cr, scale, scale);
878 
879 	/* Center the result in window */
880 	twidth = (double) tablet_area->width;
881 	theight = (double) tablet_area->height;
882 	cairo_user_to_device_distance (cr, &twidth, &theight);
883 
884 	twidth = ((double) monitor_area->width - twidth) / 2.0;
885 	theight = ((double) monitor_area->height - theight) / 2.0;
886 	cairo_device_to_user_distance (cr, &twidth, &theight);
887 
888 	twidth = twidth + (double) (monitor_area->x - screen_area->x);
889 	theight = theight + (double) (monitor_area->y - screen_area->y);
890 
891 	cairo_translate (cr, twidth, theight);
892 }
893 
894 static gboolean
csd_wacom_osd_window_draw(GtkWidget * widget,cairo_t * cr)895 csd_wacom_osd_window_draw (GtkWidget *widget,
896 			   cairo_t   *cr)
897 {
898 	CsdWacomOSDWindow *osd_window = CSD_WACOM_OSD_WINDOW (widget);
899 
900 	g_return_val_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window), FALSE);
901 	g_return_val_if_fail (CSD_IS_WACOM_DEVICE (osd_window->priv->pad), FALSE);
902 
903 	if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget))) {
904 		GtkStyleContext     *style_context;
905 		PangoContext        *pango_context;
906 
907 		style_context = gtk_widget_get_style_context (widget);
908 		pango_context = gtk_widget_get_pango_context (widget);
909 
910 		cairo_set_source_rgba (cr, 0, 0, 0, BACK_OPACITY);
911 		cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
912 		cairo_paint (cr);
913 		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
914 
915 		/* Save original matrix */
916 		cairo_save (cr);
917 
918 		/* Apply new cairo transformation matrix */
919 		csd_wacom_osd_window_adjust_cairo (osd_window, cr);
920 
921 		/* And render the tablet layout */
922 		csd_wacom_osd_window_update (osd_window);
923 		rsvg_handle_render_cairo (osd_window->priv->handle, cr);
924 
925 		csd_wacom_osd_window_place_buttons (osd_window, cr);
926 
927 		/* Reset to original matrix */
928 		cairo_restore (cr);
929 
930 		/* Draw button labels and message */
931 		csd_wacom_osd_window_draw_labels (osd_window,
932 		                                  style_context,
933 		                                  pango_context,
934 		                                  cr);
935 		csd_wacom_osd_window_draw_message (osd_window,
936 		                                   style_context,
937 		                                   pango_context,
938 		                                   cr);
939 	}
940 
941 	return FALSE;
942 }
943 
944 static gchar *
get_escaped_accel_shortcut(const gchar * accel)945 get_escaped_accel_shortcut (const gchar *accel)
946 {
947 	guint keyval;
948 	GdkModifierType mask;
949 	gchar *str, *label;
950 
951 	if (accel == NULL || accel[0] == '\0')
952 		return g_strdup (_("Do Nothing"));
953 
954 	gtk_accelerator_parse (accel, &keyval, &mask);
955 
956 	str = gtk_accelerator_get_label (keyval, mask);
957 	label = g_markup_printf_escaped (_("Send Keystroke %s"), str);
958 	g_free (str);
959 
960 	return label;
961 }
962 
963 static gchar *
get_tablet_button_label_normal(CsdWacomDevice * device,CsdWacomTabletButton * button)964 get_tablet_button_label_normal (CsdWacomDevice       *device,
965 				CsdWacomTabletButton *button)
966 {
967 	CsdWacomActionType type;
968 	gchar *name, *str;
969 
970 	type = g_settings_get_enum (button->settings, ACTION_TYPE_KEY);
971 	if (type == CSD_WACOM_ACTION_TYPE_NONE)
972 		return g_strdup (_("Do Nothing"));
973 
974 	if (type == CSD_WACOM_ACTION_TYPE_HELP)
975 		return g_strdup (_("Show On-Screen Help"));
976 
977 	if (type == CSD_WACOM_ACTION_TYPE_SWITCH_MONITOR)
978 		return g_strdup (_("Switch Monitor"));
979 
980 	str = g_settings_get_string (button->settings, CUSTOM_ACTION_KEY);
981 	if (str == NULL || *str == '\0') {
982 		g_free (str);
983 		return g_strdup (_("Do Nothing"));
984 	}
985 
986 	name = get_escaped_accel_shortcut (str);
987 	g_free (str);
988 
989 	return name;
990 }
991 
992 static gchar *
get_tablet_button_label_touch(CsdWacomDevice * device,CsdWacomTabletButton * button,GtkDirectionType dir)993 get_tablet_button_label_touch  (CsdWacomDevice       *device,
994 				CsdWacomTabletButton *button,
995 				GtkDirectionType      dir)
996 {
997 	char **strv, *name, *str;
998 
999 	strv = g_settings_get_strv (button->settings, CUSTOM_ELEVATOR_ACTION_KEY);
1000 	name = NULL;
1001 
1002 	if (strv) {
1003 		if (g_strv_length (strv) >= 1 && dir == GTK_DIR_UP)
1004 			name = g_strdup (strv[0]);
1005 		else if (g_strv_length (strv) >= 2 && dir == GTK_DIR_DOWN)
1006 			name = g_strdup (strv[1]);
1007 		g_strfreev (strv);
1008 	}
1009 
1010 	str = get_escaped_accel_shortcut (name);
1011 	g_free (name);
1012 	name = str;
1013 
1014 	/* With multiple modes, also show the current mode for that action */
1015 	if (csd_wacom_device_get_num_modes (device, button->group_id) > 1) {
1016 		name = g_strdup_printf (_("Mode %d: %s"), button->idx + 1, str);
1017 		g_free (str);
1018 	}
1019 
1020 	return name;
1021 }
1022 
1023 static gchar *
get_tablet_button_label(CsdWacomDevice * device,CsdWacomTabletButton * button,GtkDirectionType dir)1024 get_tablet_button_label (CsdWacomDevice       *device,
1025 	                 CsdWacomTabletButton *button,
1026 	                 GtkDirectionType      dir)
1027 {
1028 	g_return_val_if_fail (button, NULL);
1029 
1030 	if (!button->settings)
1031 		goto out;
1032 
1033 	switch (button->type) {
1034 	case WACOM_TABLET_BUTTON_TYPE_NORMAL:
1035 		return get_tablet_button_label_normal (device, button);
1036 		break;
1037 	case WACOM_TABLET_BUTTON_TYPE_RING:
1038 	case WACOM_TABLET_BUTTON_TYPE_STRIP:
1039 		return get_tablet_button_label_touch (device, button, dir);
1040 		break;
1041 	case WACOM_TABLET_BUTTON_TYPE_HARDCODED:
1042 	default:
1043 		break;
1044 	}
1045 out:
1046 	return g_strdup (button->name);
1047 }
1048 
1049 static gchar*
get_tablet_button_class_name(CsdWacomTabletButton * tablet_button,GtkDirectionType dir)1050 get_tablet_button_class_name (CsdWacomTabletButton *tablet_button,
1051                               GtkDirectionType      dir)
1052 {
1053 	gchar *id;
1054 	gchar  c;
1055 
1056 	id = tablet_button->id;
1057 	switch (tablet_button->type) {
1058 	case WACOM_TABLET_BUTTON_TYPE_RING:
1059 		if (id[0] == 'l') /* left-ring */
1060 			return g_strdup_printf ("Ring%s", (dir == GTK_DIR_UP ? "CCW" : "CW"));
1061 		if (id[0] == 'r') /* right-ring */
1062 			return g_strdup_printf ("Ring2%s", (dir == GTK_DIR_UP ? "CCW" : "CW"));
1063 		g_warning ("Unknown ring type '%s'", id);
1064 		return NULL;
1065 		break;
1066 	case WACOM_TABLET_BUTTON_TYPE_STRIP:
1067 		if (id[0] == 'l') /* left-strip */
1068 			return g_strdup_printf ("Strip%s", (dir == GTK_DIR_UP ? "Up" : "Down"));
1069 		if (id[0] == 'r') /* right-strip */
1070 			return g_strdup_printf ("Strip2%s", (dir == GTK_DIR_UP ? "Up" : "Down"));
1071 		g_warning ("Unknown strip type '%s'", id);
1072 		return NULL;
1073 		break;
1074 	case WACOM_TABLET_BUTTON_TYPE_NORMAL:
1075 	case WACOM_TABLET_BUTTON_TYPE_HARDCODED:
1076 		c = get_last_char (id);
1077 		return g_strdup_printf ("%c", g_ascii_toupper (c));
1078 		break;
1079 	default:
1080 		g_warning ("Unknown button type '%s'", id);
1081 		break;
1082 	}
1083 
1084 	return NULL;
1085 }
1086 
1087 static gchar*
get_tablet_button_id_name(CsdWacomTabletButton * tablet_button,GtkDirectionType dir)1088 get_tablet_button_id_name (CsdWacomTabletButton *tablet_button,
1089                            GtkDirectionType      dir)
1090 {
1091 	gchar *id;
1092 	gchar  c;
1093 
1094 	id = tablet_button->id;
1095 	switch (tablet_button->type) {
1096 	case WACOM_TABLET_BUTTON_TYPE_RING:
1097 		return g_strconcat (id, (dir == GTK_DIR_UP ? "-ccw" : "-cw"), NULL);
1098 		break;
1099 	case WACOM_TABLET_BUTTON_TYPE_STRIP:
1100 		return g_strconcat (id, (dir == GTK_DIR_UP ? "-up" : "-down"), NULL);
1101 		break;
1102 	case WACOM_TABLET_BUTTON_TYPE_NORMAL:
1103 	case WACOM_TABLET_BUTTON_TYPE_HARDCODED:
1104 		c = get_last_char (id);
1105 		return g_strdup_printf ("%c", g_ascii_toupper (c));
1106 		break;
1107 	default:
1108 		g_warning ("Unknown button type '%s'", id);
1109 		break;
1110 	}
1111 
1112 	return NULL;
1113 }
1114 
1115 static gint
get_elevator_current_mode(CsdWacomOSDWindow * osd_window,CsdWacomTabletButton * elevator_button)1116 get_elevator_current_mode (CsdWacomOSDWindow    *osd_window,
1117                            CsdWacomTabletButton *elevator_button)
1118 {
1119 	GList *list, *l;
1120 	gint   mode;
1121 
1122 	mode = 1;
1123 	/* Search in the list of buttons the corresponding
1124 	 * mode-switch button and get the current mode
1125 	 */
1126 	list = csd_wacom_device_get_buttons (osd_window->priv->pad);
1127 	for (l = list; l != NULL; l = l->next) {
1128 		CsdWacomTabletButton *tablet_button = l->data;
1129 
1130 		if (tablet_button->type != WACOM_TABLET_BUTTON_TYPE_HARDCODED)
1131 			continue;
1132 		if (elevator_button->group_id != tablet_button->group_id)
1133 			continue;
1134 		mode = csd_wacom_device_get_current_mode (osd_window->priv->pad,
1135 		                                          tablet_button->group_id);
1136 		break;
1137 	}
1138 	g_list_free (list);
1139 
1140 	return mode;
1141 }
1142 
1143 static CsdWacomOSDButton *
csd_wacom_osd_window_add_button_with_dir(CsdWacomOSDWindow * osd_window,CsdWacomTabletButton * tablet_button,guint timeout,GtkDirectionType dir)1144 csd_wacom_osd_window_add_button_with_dir (CsdWacomOSDWindow    *osd_window,
1145                                           CsdWacomTabletButton *tablet_button,
1146                                           guint                 timeout,
1147                                           GtkDirectionType      dir)
1148 {
1149 	CsdWacomOSDButton    *osd_button;
1150 	gchar                *str;
1151 
1152 	str = get_tablet_button_id_name (tablet_button, dir);
1153 	osd_button = csd_wacom_osd_button_new (GTK_WIDGET (osd_window), str);
1154 	g_free (str);
1155 
1156 	str = get_tablet_button_class_name (tablet_button, dir);
1157 	csd_wacom_osd_button_set_class (osd_button, str);
1158 	g_free (str);
1159 
1160 	str = get_tablet_button_label (osd_window->priv->pad, tablet_button, dir);
1161 	csd_wacom_osd_button_set_label (osd_button, str);
1162 	g_free (str);
1163 
1164 	csd_wacom_osd_button_set_button_type (osd_button, tablet_button->type);
1165 	csd_wacom_osd_button_set_position (osd_button, tablet_button->pos);
1166 	csd_wacom_osd_button_set_auto_off (osd_button, timeout);
1167 	osd_window->priv->buttons = g_list_append (osd_window->priv->buttons, osd_button);
1168 
1169 	return osd_button;
1170 }
1171 
1172 static void
csd_wacom_osd_window_add_tablet_button(CsdWacomOSDWindow * osd_window,CsdWacomTabletButton * tablet_button)1173 csd_wacom_osd_window_add_tablet_button (CsdWacomOSDWindow    *osd_window,
1174                                         CsdWacomTabletButton *tablet_button)
1175 {
1176 	CsdWacomOSDButton    *osd_button;
1177 	gint                  mode;
1178 
1179 	switch (tablet_button->type) {
1180 	case WACOM_TABLET_BUTTON_TYPE_NORMAL:
1181 	case WACOM_TABLET_BUTTON_TYPE_HARDCODED:
1182 		osd_button = csd_wacom_osd_window_add_button_with_dir (osd_window,
1183 		                                                       tablet_button,
1184 		                                                       0,
1185 		                                                       0);
1186 		csd_wacom_osd_button_set_visible (osd_button, TRUE);
1187 		break;
1188 	case WACOM_TABLET_BUTTON_TYPE_RING:
1189 	case WACOM_TABLET_BUTTON_TYPE_STRIP:
1190 		mode = get_elevator_current_mode (osd_window, tablet_button) - 1;
1191 
1192 		/* Add 2 buttons per elevator, one "Up"... */
1193 		osd_button = csd_wacom_osd_window_add_button_with_dir (osd_window,
1194 		                                                       tablet_button,
1195 		                                                       ELEVATOR_TIMEOUT,
1196 		                                                       GTK_DIR_UP);
1197 		csd_wacom_osd_button_set_visible (osd_button, tablet_button->idx == mode);
1198 
1199 		/* ... and one "Down" */
1200 		osd_button = csd_wacom_osd_window_add_button_with_dir (osd_window,
1201 		                                                       tablet_button,
1202 		                                                       ELEVATOR_TIMEOUT,
1203 		                                                       GTK_DIR_DOWN);
1204 		csd_wacom_osd_button_set_visible (osd_button, tablet_button->idx == mode);
1205 
1206 		break;
1207 	default:
1208 		g_warning ("Unknown button type");
1209 		break;
1210 	}
1211 }
1212 
1213 /*
1214  * Returns the rotation to apply a device to get a representation relative to
1215  * the current rotation of the output.
1216  * (This function is _not_ the same as in csd-wacom-manager.c)
1217  */
1218 static CsdWacomRotation
display_relative_rotation(CsdWacomRotation device_rotation,CsdWacomRotation output_rotation)1219 display_relative_rotation (CsdWacomRotation device_rotation,
1220 			   CsdWacomRotation output_rotation)
1221 {
1222 	CsdWacomRotation rotations[] = { CSD_WACOM_ROTATION_HALF,
1223 	                                 CSD_WACOM_ROTATION_CW,
1224 	                                 CSD_WACOM_ROTATION_NONE,
1225 	                                 CSD_WACOM_ROTATION_CCW };
1226 	guint i;
1227 
1228 	if (device_rotation == output_rotation)
1229 		return CSD_WACOM_ROTATION_NONE;
1230 
1231 	if (output_rotation == CSD_WACOM_ROTATION_NONE)
1232 		return device_rotation;
1233 
1234 	for (i = 0; i < G_N_ELEMENTS (rotations); i++) {
1235 		if (device_rotation == rotations[i])
1236 			break;
1237 	}
1238 
1239 	if (output_rotation == CSD_WACOM_ROTATION_HALF)
1240 		return rotations[(i + G_N_ELEMENTS (rotations) - 2) % G_N_ELEMENTS (rotations)];
1241 
1242 	if (output_rotation == CSD_WACOM_ROTATION_CW)
1243 		return rotations[(i + 1) % G_N_ELEMENTS (rotations)];
1244 
1245 	if (output_rotation == CSD_WACOM_ROTATION_CCW)
1246 		return rotations[(i + G_N_ELEMENTS (rotations) - 1) % G_N_ELEMENTS (rotations)];
1247 
1248 	/* fallback */
1249 	return CSD_WACOM_ROTATION_NONE;
1250 }
1251 
1252 static void
csd_wacom_osd_window_mapped(GtkWidget * widget,gpointer data)1253 csd_wacom_osd_window_mapped (GtkWidget *widget,
1254                              gpointer   data)
1255 {
1256 	CsdWacomOSDWindow *osd_window = CSD_WACOM_OSD_WINDOW (widget);
1257 
1258 	g_return_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window));
1259 
1260 	/* Position the window at its expected position before moving
1261 	 * to fullscreen, so the window will be on the right monitor.
1262 	 */
1263 	gtk_window_move (GTK_WINDOW (osd_window),
1264 	                 osd_window->priv->screen_area.x,
1265 	                 osd_window->priv->screen_area.y);
1266 
1267 	gtk_window_fullscreen (GTK_WINDOW (osd_window));
1268 	gtk_window_set_keep_above (GTK_WINDOW (osd_window), TRUE);
1269 }
1270 
1271 static void
csd_wacom_osd_window_realized(GtkWidget * widget,gpointer data)1272 csd_wacom_osd_window_realized (GtkWidget *widget,
1273                                gpointer   data)
1274 {
1275 	CsdWacomOSDWindow *osd_window = CSD_WACOM_OSD_WINDOW (widget);
1276 	GdkWindow         *gdk_window;
1277 	GdkRGBA            transparent;
1278 	GdkScreen         *screen;
1279 	GdkCursor         *cursor;
1280 	gint               monitor;
1281 	gboolean           status;
1282 
1283 	g_return_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window));
1284 	g_return_if_fail (CSD_IS_WACOM_DEVICE (osd_window->priv->pad));
1285 
1286 	if (!gtk_widget_get_realized (widget))
1287 		return;
1288 
1289 	screen = gtk_widget_get_screen (widget);
1290 	gdk_window = gtk_widget_get_window (widget);
1291 
1292 	transparent.red = transparent.green = transparent.blue = 0.0;
1293 	transparent.alpha = BACK_OPACITY;
1294 	gdk_window_set_background_rgba (gdk_window, &transparent);
1295 
1296 	cursor = gdk_cursor_new (GDK_BLANK_CURSOR);
1297 	gdk_window_set_cursor (gdk_window, cursor);
1298 	g_object_unref (cursor);
1299 
1300 	/* Determine the monitor for that device and set appropriate fullscreen mode*/
1301 	monitor = csd_wacom_device_get_display_monitor (osd_window->priv->pad);
1302 	if (monitor == CSD_WACOM_SET_ALL_MONITORS) {
1303 		/* Covers the entire screen */
1304 		osd_window->priv->screen_area.x = 0;
1305 		osd_window->priv->screen_area.y = 0;
1306 		osd_window->priv->screen_area.width = gdk_screen_get_width (screen);
1307 		osd_window->priv->screen_area.height = gdk_screen_get_height (screen);
1308 		gdk_screen_get_monitor_geometry (screen, 0, &osd_window->priv->monitor_area);
1309 		gdk_window_set_fullscreen_mode (gdk_window, GDK_FULLSCREEN_ON_ALL_MONITORS);
1310 	} else {
1311 		gdk_screen_get_monitor_geometry (screen, monitor, &osd_window->priv->screen_area);
1312 		osd_window->priv->monitor_area = osd_window->priv->screen_area;
1313 		gdk_window_set_fullscreen_mode (gdk_window, GDK_FULLSCREEN_ON_CURRENT_MONITOR);
1314 	}
1315 
1316 	gtk_window_set_default_size (GTK_WINDOW (osd_window),
1317 	                             osd_window->priv->screen_area.width,
1318 	                             osd_window->priv->screen_area.height);
1319 
1320 	status = get_image_size (csd_wacom_device_get_layout_path (osd_window->priv->pad),
1321 	                         &osd_window->priv->tablet_area.width,
1322 	                         &osd_window->priv->tablet_area.height);
1323 	if (status == FALSE)
1324 		osd_window->priv->tablet_area = osd_window->priv->monitor_area;
1325 }
1326 
1327 static void
csd_wacom_osd_window_set_device(CsdWacomOSDWindow * osd_window,CsdWacomDevice * device)1328 csd_wacom_osd_window_set_device (CsdWacomOSDWindow *osd_window,
1329 				 CsdWacomDevice    *device)
1330 {
1331 	CsdWacomRotation  device_rotation;
1332 	CsdWacomRotation  output_rotation;
1333 	GSettings        *settings;
1334 	GList            *list, *l;
1335 
1336 	g_return_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window));
1337 	g_return_if_fail (CSD_IS_WACOM_DEVICE (device));
1338 
1339 	/* If we had a layout previously handled, get rid of it */
1340 	if (osd_window->priv->handle)
1341 		g_object_unref (osd_window->priv->handle);
1342 	osd_window->priv->handle = NULL;
1343 
1344 	/* Bind the device with the OSD window */
1345 	if (osd_window->priv->pad)
1346 		g_object_weak_unref (G_OBJECT(osd_window->priv->pad),
1347 		                     (GWeakNotify) gtk_widget_destroy,
1348 		                     osd_window);
1349 	osd_window->priv->pad = device;
1350 	g_object_weak_ref (G_OBJECT(osd_window->priv->pad),
1351 	                   (GWeakNotify) gtk_widget_destroy,
1352 	                   osd_window);
1353 
1354 	/* Capture current rotation, we do not update that later, OSD window is meant to be short lived */
1355 	settings = csd_wacom_device_get_settings (osd_window->priv->pad);
1356 	device_rotation = g_settings_get_enum (settings, ROTATION_KEY);
1357 	output_rotation = csd_wacom_device_get_display_rotation (osd_window->priv->pad);
1358 	osd_window->priv->rotation = display_relative_rotation (device_rotation, output_rotation);
1359 
1360 	/* Create the buttons */
1361 	list = csd_wacom_device_get_buttons (device);
1362 	for (l = list; l != NULL; l = l->next) {
1363 		CsdWacomTabletButton *tablet_button = l->data;
1364 
1365 		csd_wacom_osd_window_add_tablet_button (osd_window, tablet_button);
1366 	}
1367 	g_list_free (list);
1368 }
1369 
1370 CsdWacomDevice *
csd_wacom_osd_window_get_device(CsdWacomOSDWindow * osd_window)1371 csd_wacom_osd_window_get_device (CsdWacomOSDWindow *osd_window)
1372 {
1373 	g_return_val_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window), NULL);
1374 
1375 	return osd_window->priv->pad;
1376 }
1377 
1378 void
csd_wacom_osd_window_set_message(CsdWacomOSDWindow * osd_window,const gchar * str)1379 csd_wacom_osd_window_set_message (CsdWacomOSDWindow *osd_window,
1380 				  const gchar       *str)
1381 {
1382 	g_return_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window));
1383 
1384 	g_free (osd_window->priv->message);
1385 	osd_window->priv->message = g_strdup (str);
1386 }
1387 
1388 const char *
csd_wacom_osd_window_get_message(CsdWacomOSDWindow * osd_window)1389 csd_wacom_osd_window_get_message (CsdWacomOSDWindow *osd_window)
1390 {
1391 	g_return_val_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window), NULL);
1392 
1393 	return osd_window->priv->message;
1394 }
1395 
1396 static void
csd_wacom_osd_window_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1397 csd_wacom_osd_window_set_property (GObject        *object,
1398 				   guint           prop_id,
1399 				   const GValue   *value,
1400 				   GParamSpec     *pspec)
1401 {
1402 	CsdWacomOSDWindow *osd_window;
1403 
1404 	osd_window = CSD_WACOM_OSD_WINDOW (object);
1405 
1406 	switch (prop_id) {
1407 	case PROP_OSD_WINDOW_MESSAGE:
1408 		csd_wacom_osd_window_set_message (osd_window, g_value_get_string (value));
1409 		break;
1410 	case PROP_OSD_WINDOW_CSD_WACOM_DEVICE:
1411 		csd_wacom_osd_window_set_device (osd_window, g_value_get_object (value));
1412 		break;
1413 	default:
1414 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1415 		break;
1416 	}
1417 }
1418 
1419 static void
csd_wacom_osd_window_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1420 csd_wacom_osd_window_get_property (GObject        *object,
1421 				   guint           prop_id,
1422 				   GValue         *value,
1423 				   GParamSpec     *pspec)
1424 {
1425 	CsdWacomOSDWindow *osd_window;
1426 
1427 	osd_window = CSD_WACOM_OSD_WINDOW (object);
1428 
1429 	switch (prop_id) {
1430 	case PROP_OSD_WINDOW_MESSAGE:
1431 		g_value_set_string (value, osd_window->priv->message);
1432 		break;
1433 	case PROP_OSD_WINDOW_CSD_WACOM_DEVICE:
1434 		g_value_set_object (value, (GObject*) osd_window->priv->pad);
1435 		break;
1436 	default:
1437 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1438 		break;
1439 	}
1440 }
1441 
1442 void
csd_wacom_osd_window_set_active(CsdWacomOSDWindow * osd_window,CsdWacomTabletButton * button,GtkDirectionType dir,gboolean active)1443 csd_wacom_osd_window_set_active (CsdWacomOSDWindow    *osd_window,
1444 				 CsdWacomTabletButton *button,
1445 				 GtkDirectionType      dir,
1446 				 gboolean              active)
1447 {
1448 	GList     *l;
1449 	gchar     *id;
1450 
1451 	g_return_if_fail (CSD_IS_WACOM_OSD_WINDOW (osd_window));
1452 	g_return_if_fail (button != NULL);
1453 
1454 	id = get_tablet_button_id_name (button, dir);
1455 	for (l = osd_window->priv->buttons; l != NULL; l = l->next) {
1456 		CsdWacomOSDButton *osd_button = l->data;
1457 		if (MATCH_ID (osd_button, id))
1458 			csd_wacom_osd_button_set_active (osd_button, active);
1459 	}
1460 	g_free (id);
1461 }
1462 
1463 void
csd_wacom_osd_window_set_mode(CsdWacomOSDWindow * osd_window,gint group_id,gint mode)1464 csd_wacom_osd_window_set_mode (CsdWacomOSDWindow    *osd_window,
1465                                gint                  group_id,
1466                                gint                  mode)
1467 {
1468 	GList                *list, *l;
1469 
1470 	list = csd_wacom_device_get_buttons (osd_window->priv->pad);
1471 	for (l = list; l != NULL; l = l->next) {
1472 		CsdWacomTabletButton *tablet_button = l->data;
1473 		GList                *l2;
1474 		gchar                *id_up, *id_down;
1475 
1476 		if (tablet_button->type != WACOM_TABLET_BUTTON_TYPE_STRIP &&
1477 		    tablet_button->type != WACOM_TABLET_BUTTON_TYPE_RING)
1478 			continue;
1479 		if (tablet_button->group_id != group_id)
1480 			continue;
1481 
1482 		id_up = get_tablet_button_id_name (tablet_button, GTK_DIR_UP);
1483 		id_down = get_tablet_button_id_name (tablet_button, GTK_DIR_DOWN);
1484 
1485 		for (l2 = osd_window->priv->buttons; l2 != NULL; l2 = l2->next) {
1486 			CsdWacomOSDButton *osd_button = l2->data;
1487 			gboolean           visible = (tablet_button->idx == mode - 1);
1488 
1489 			if (MATCH_ID (osd_button, id_up) || MATCH_ID (osd_button, id_down))
1490 				csd_wacom_osd_button_set_visible (osd_button, visible);
1491 		}
1492 
1493 		g_free (id_up);
1494 		g_free (id_down);
1495 
1496 	}
1497 	g_list_free (list);
1498 }
1499 
1500 GtkWidget *
csd_wacom_osd_window_new(CsdWacomDevice * pad,const gchar * message)1501 csd_wacom_osd_window_new (CsdWacomDevice       *pad,
1502                           const gchar          *message)
1503 {
1504 	CsdWacomOSDWindow *osd_window;
1505 	GdkScreen         *screen;
1506 	GdkVisual         *visual;
1507 
1508 	osd_window = CSD_WACOM_OSD_WINDOW (g_object_new (CSD_TYPE_WACOM_OSD_WINDOW,
1509 	                                                 "type",              GTK_WINDOW_TOPLEVEL,
1510 	                                                 "skip-pager-hint",   TRUE,
1511 	                                                 "skip-taskbar-hint", TRUE,
1512 	                                                 "focus-on-map",      TRUE,
1513 	                                                 "decorated",         FALSE,
1514 	                                                 "deletable",         FALSE,
1515 	                                                 "accept-focus",      TRUE,
1516 	                                                 "wacom-device",      pad,
1517 	                                                 "message",           message,
1518 	                                                 NULL));
1519 
1520 	/* Must set the visual before realizing the window */
1521 	gtk_widget_set_app_paintable (GTK_WIDGET (osd_window), TRUE);
1522 	screen = gdk_screen_get_default ();
1523 	visual = gdk_screen_get_rgba_visual (screen);
1524 	if (visual == NULL)
1525 		visual = gdk_screen_get_system_visual (screen);
1526 	gtk_widget_set_visual (GTK_WIDGET (osd_window), visual);
1527 
1528 	g_signal_connect (GTK_WIDGET (osd_window), "realize",
1529 	                  G_CALLBACK (csd_wacom_osd_window_realized),
1530 	                  NULL);
1531 	g_signal_connect (GTK_WIDGET (osd_window), "map",
1532 	                  G_CALLBACK (csd_wacom_osd_window_mapped),
1533 	                  NULL);
1534 
1535 	return GTK_WIDGET (osd_window);
1536 }
1537 
1538 static void
csd_wacom_osd_window_class_init(CsdWacomOSDWindowClass * klass)1539 csd_wacom_osd_window_class_init (CsdWacomOSDWindowClass *klass)
1540 {
1541 	GObjectClass *gobject_class;
1542 	GtkWidgetClass *widget_class;
1543 
1544 	gobject_class = G_OBJECT_CLASS (klass);
1545 	widget_class  = GTK_WIDGET_CLASS (klass);
1546 
1547 	gobject_class->set_property = csd_wacom_osd_window_set_property;
1548 	gobject_class->get_property = csd_wacom_osd_window_get_property;
1549 	gobject_class->finalize     = csd_wacom_osd_window_finalize;
1550 	widget_class->draw          = csd_wacom_osd_window_draw;
1551 
1552 	g_object_class_install_property (gobject_class,
1553 	                                 PROP_OSD_WINDOW_MESSAGE,
1554 	                                 g_param_spec_string ("message",
1555 	                                                      "Window message",
1556 	                                                      "The message shown in the OSD window",
1557 	                                                      "",
1558 	                                                      G_PARAM_READWRITE));
1559 	g_object_class_install_property (gobject_class,
1560 	                                 PROP_OSD_WINDOW_CSD_WACOM_DEVICE,
1561 	                                 g_param_spec_object ("wacom-device",
1562 	                                                      "Wacom device",
1563 	                                                      "The Wacom device represented by the OSD window",
1564 	                                                      CSD_TYPE_WACOM_DEVICE,
1565 	                                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1566 
1567 	g_type_class_add_private (klass, sizeof (CsdWacomOSDWindowPrivate));
1568 }
1569 
1570 static void
csd_wacom_osd_window_init(CsdWacomOSDWindow * osd_window)1571 csd_wacom_osd_window_init (CsdWacomOSDWindow *osd_window)
1572 {
1573 	osd_window->priv = CSD_WACOM_OSD_WINDOW_GET_PRIVATE (osd_window);
1574 }
1575 
1576 static void
csd_wacom_osd_window_finalize(GObject * object)1577 csd_wacom_osd_window_finalize (GObject *object)
1578 {
1579 	CsdWacomOSDWindow *osd_window;
1580 	CsdWacomOSDWindowPrivate *priv;
1581 
1582 	g_return_if_fail (object != NULL);
1583 	g_return_if_fail (CSD_IS_WACOM_OSD_WINDOW (object));
1584 
1585 	osd_window = CSD_WACOM_OSD_WINDOW (object);
1586 	g_return_if_fail (osd_window->priv != NULL);
1587 
1588 	priv = osd_window->priv;
1589 	g_clear_object (&priv->handle);
1590 	g_clear_pointer (&priv->message, g_free);
1591 	if (priv->buttons) {
1592 		g_list_free_full (priv->buttons, g_object_unref);
1593 		priv->buttons = NULL;
1594 	}
1595 
1596 	G_OBJECT_CLASS (csd_wacom_osd_window_parent_class)->finalize (object);
1597 }
1598