1 /*
2 * Copyright ?? 2012 Red Hat, Inc.
3 *
4 * Permission to use, copy, modify, distribute, and sell this software
5 * and its documentation for any purpose is hereby granted without
6 * fee, provided that the above copyright notice appear in all copies
7 * and that both that copyright notice and this permission notice
8 * appear in supporting documentation, and that the name of Red Hat
9 * not be used in advertising or publicity pertaining to distribution
10 * of the software without specific, written prior permission. Red
11 * Hat makes no representations about the suitability of this software
12 * for any purpose. It is provided "as is" without express or implied
13 * warranty.
14 *
15 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
17 * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
19 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
20 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 *
23 * Authors:
24 * Olivier Fourdan <ofourdan@redhat.com>
25 */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <glib.h>
36 #include <gdk/gdkx.h>
37 #include <gtk/gtk.h>
38 #include <cairo.h>
39 #include <librsvg/rsvg.h>
40 #include "libwacom.h"
41
42 #ifndef LIBRSVG_CHECK_VERSION
43 #include <librsvg/librsvg-features.h>
44 #endif
45 #if !LIBRSVG_CHECK_VERSION(2,36,2)
46 #include <librsvg/rsvg-cairo.h>
47 #endif
48
49 #define INACTIVE_COLOR "#ededed"
50 #define ACTIVE_COLOR "#729fcf"
51 #define STROKE_COLOR "#000000"
52 #define DARK_COLOR "#535353"
53 #define BACK_COLOR "#000000"
54
55 /* Convenient struct to store our stuff around */
56 typedef struct
57 {
58 RsvgHandle *handle;
59 GtkWidget *widget;
60 guint timeout;
61 WacomDevice *device;
62 GdkRectangle area;
63 char active_button;
64 int num_buttons;
65 } Tablet;
66
67 static gboolean
get_sub_location(cairo_t * cairo_context,RsvgHandle * handle,const char * sub,double * x,double * y,double * width,double * height)68 get_sub_location (cairo_t *cairo_context, RsvgHandle *handle, const char *sub, double *x, double *y, double *width, double *height)
69 {
70 if (x || y) {
71 RsvgPositionData position;
72 double tx, ty;
73
74 if (!rsvg_handle_get_position_sub (handle, &position, sub)) {
75 g_warning ("Failed to retrieve '%s' position", sub);
76 return FALSE;
77 }
78
79 tx = (double) position.x;
80 ty = (double) position.y;
81 cairo_user_to_device (cairo_context, &tx, &ty);
82
83 if (x)
84 *x = tx;
85 if (y)
86 *y = ty;
87 }
88
89 if (width || height) {
90 RsvgDimensionData dimensions;
91 double twidth, theight;
92
93 if (!rsvg_handle_get_dimensions_sub (handle, &dimensions, sub)) {
94 g_warning ("Failed to retrieve '%s' dimension", sub);
95 return FALSE;
96 }
97
98 twidth = (double) dimensions.width;
99 theight = (double) dimensions.height;
100 cairo_user_to_device_distance (cairo_context, &twidth, &theight);
101
102 if (width)
103 *width = twidth;
104 if (height)
105 *height = theight;
106 }
107
108 return TRUE;
109 }
110
111 static void
print_label(cairo_t * cairo_context,Tablet * tablet,const char * sub,const char * markup,WacomButtonFlags flags)112 print_label (cairo_t *cairo_context, Tablet *tablet, const char *sub, const char *markup, WacomButtonFlags flags)
113 {
114 GtkWidget *widget;
115 GtkAllocation allocation;
116 GtkStyle *style;
117 PangoContext *pango_context;
118 PangoLayout *pango_layout;
119 PangoRectangle pango_rect;
120 double label_x, label_y;
121 int x, y;
122
123 if (!get_sub_location (cairo_context, tablet->handle, sub, &label_x, &label_y, NULL, NULL))
124 return;
125
126 widget = GTK_WIDGET(tablet->widget);
127 gtk_widget_get_allocation(widget, &allocation);
128 style = gtk_widget_get_style (widget);
129 pango_context = gtk_widget_get_pango_context (widget);
130 pango_layout = pango_layout_new (pango_context);
131
132 pango_layout_set_markup (pango_layout, markup, -1);
133 pango_layout_get_pixel_extents (pango_layout, NULL, &pango_rect);
134
135 if (flags & WACOM_BUTTON_POSITION_LEFT) {
136 pango_layout_set_alignment (pango_layout, PANGO_ALIGN_LEFT);
137 x = (int) label_x + pango_rect.x;
138 y = (int) label_y + pango_rect.y - pango_rect.height / 2;
139 } else if (flags & WACOM_BUTTON_POSITION_RIGHT) {
140 pango_layout_set_alignment (pango_layout, PANGO_ALIGN_RIGHT);
141 x = (int) label_x + pango_rect.x - pango_rect.width;
142 y = (int) label_y + pango_rect.y - pango_rect.height / 2;
143 } else {
144 pango_layout_set_alignment (pango_layout, PANGO_ALIGN_CENTER);
145 x = (int) label_x + pango_rect.x - pango_rect.width / 2;
146 y = (int) label_y + pango_rect.y;
147 }
148
149 gtk_paint_layout (style,
150 gtk_widget_get_window (widget),
151 0,
152 TRUE,
153 &allocation,
154 widget,
155 NULL,
156 x,
157 y,
158 pango_layout);
159
160 g_object_unref (pango_layout);
161 }
162
163 static void
print_button_labels(cairo_t * cairo_context,Tablet * tablet)164 print_button_labels (cairo_t *cairo_context, Tablet *tablet)
165 {
166 char button;
167
168 for (button = 'A'; button < 'A' + tablet->num_buttons; button++) {
169 WacomButtonFlags flags;
170 gchar *sub;
171 gchar *label;
172
173 flags = libwacom_get_button_flag(tablet->device, button);
174 sub = g_strdup_printf ("#Label%c", button);
175 label = g_strdup_printf ("<span foreground=\"%s\" >Button %c</span>",
176 (button == tablet->active_button) ? ACTIVE_COLOR : INACTIVE_COLOR,
177 button);
178 print_label (cairo_context, tablet, sub, label, flags);
179 g_free (label);
180 g_free (sub);
181 }
182
183 /* Touch rings */
184 if (libwacom_has_ring(tablet->device)) {
185 print_label (cairo_context, tablet, "#LabelRingCCW", "<span foreground=\"" INACTIVE_COLOR "\" >Ring Counter Clockwise</span>", WACOM_BUTTON_POSITION_LEFT);
186 print_label (cairo_context, tablet, "#LabelRingCW", "<span foreground=\"" INACTIVE_COLOR "\" >Ring Clockwise</span>", WACOM_BUTTON_POSITION_LEFT);
187 }
188 if (libwacom_has_ring2(tablet->device)) {
189 print_label (cairo_context, tablet, "#LabelRing2CCW", "<span foreground=\"" INACTIVE_COLOR "\" >2nd Ring Counter Clockwise</span>", WACOM_BUTTON_POSITION_RIGHT);
190 print_label (cairo_context, tablet, "#LabelRing2CW", "<span foreground=\"" INACTIVE_COLOR "\" >2nd Ring Clockwise</span>", WACOM_BUTTON_POSITION_RIGHT);
191 }
192 /* Touch strips */
193 if (libwacom_get_num_strips(tablet->device) > 0) {
194 print_label (cairo_context, tablet, "#LabelStripUp", "<span foreground=\"" INACTIVE_COLOR "\" >Strip Up</span>", WACOM_BUTTON_POSITION_LEFT);
195 print_label (cairo_context, tablet, "#LabelStripDown", "<span foreground=\"" INACTIVE_COLOR "\" >Strip Down</span>", WACOM_BUTTON_POSITION_LEFT);
196 }
197 if (libwacom_get_num_strips(tablet->device) > 1) {
198 print_label (cairo_context, tablet, "#LabelStrip2Up", "<span foreground=\"" INACTIVE_COLOR "\" >2nd Strip Up</span>", WACOM_BUTTON_POSITION_RIGHT);
199 print_label (cairo_context, tablet, "#LabelStrip2Down", "<span foreground=\"" INACTIVE_COLOR "\" >2nd Strip Down</span>", WACOM_BUTTON_POSITION_RIGHT);
200 }
201 }
202
203 static void
update_tablet(Tablet * tablet)204 update_tablet (Tablet *tablet)
205 {
206 char *width, *height;
207 char button;
208 GError *error;
209 gchar *file_data, *escaped_file_data, *data;
210 gsize file_len;
211
212 error = NULL;
213
214 if (tablet->handle)
215 g_object_unref (tablet->handle);
216
217 if (!g_file_get_contents (libwacom_get_layout_filename (tablet->device), &file_data, &file_len, NULL))
218 return;
219 escaped_file_data = g_base64_encode ((guchar*) file_data, file_len);
220
221 width = g_strdup_printf ("%d", tablet->area.width);
222 height = g_strdup_printf ("%d", tablet->area.height);
223
224 data = g_strconcat ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
225 "<svg version=\"1.1\"\n"
226 " xmlns=\"http://www.w3.org/2000/svg\"\n"
227 " xmlns:xi=\"http://www.w3.org/2001/XInclude\"\n"
228 " width=\"", width, "\"\n"
229 " height=\"", height, "\">\n"
230 " <style type=\"text/css\">\n",
231 " .Leader {\n"
232 " stroke-width: .5 !important;\n"
233 " stroke: ", INACTIVE_COLOR, ";\n"
234 " fill: none !important;\n"
235 " }\n",
236 " .Button {\n"
237 " stroke-width: 0.25;\n"
238 " stroke: ", INACTIVE_COLOR, ";\n"
239 " fill: ", INACTIVE_COLOR, ";\n"
240 " }\n",
241 NULL);
242 g_free (width);
243 g_free (height);
244
245 for (button = 'A'; button < 'A' + tablet->num_buttons; button++) {
246 gchar class[] = {button, '\0'};
247 if (button == tablet->active_button) {
248 data = g_strconcat (data,
249 " .", class, " {\n"
250 " stroke: ", ACTIVE_COLOR, " !important;\n"
251 " fill: ", ACTIVE_COLOR, " !important;\n"
252 " }\n",
253 NULL);
254 }
255 }
256
257 data = g_strconcat (data,
258 " .Leader {\n"
259 " fill: none !important;\n"
260 " }\n",
261 " .Label {\n"
262 " stroke: none !important;\n"
263 " stroke-width: 0.1 !important;\n"
264 " opacity: .0 !important;\n"
265 " font-size: .1 !important;\n"
266 " fill: ", BACK_COLOR, " !important;\n"
267 " }\n",
268 " .TouchStrip,.TouchRing {\n"
269 " stroke-width: 0.1 !important;\n"
270 " stroke: ", INACTIVE_COLOR," !important;\n"
271 " fill: ", DARK_COLOR, " !important;\n"
272 " }\n",
273 " </style>\n"
274 " <xi:include href=\"data:text/xml;base64,", escaped_file_data, "\"/>\n"
275 "</svg>",
276 NULL);
277
278 tablet->handle = rsvg_handle_new_from_data ((guint8 *) data, strlen(data), &error);
279 g_free (data);
280 g_free (escaped_file_data);
281 g_free (file_data);
282 }
283
284 static void
on_expose_cb(GtkWidget * widget,GdkEvent * event,Tablet * tablet)285 on_expose_cb (GtkWidget *widget, GdkEvent *event, Tablet *tablet)
286 {
287 GtkAllocation allocation;
288 cairo_t *cairo_context;
289 float scale;
290 double twidth, theight;
291
292 if (tablet->handle == NULL)
293 update_tablet (tablet);
294
295 /* Create a Cairo for the widget */
296 cairo_context = gdk_cairo_create (gtk_widget_get_window (widget));
297 cairo_set_operator (cairo_context, CAIRO_OPERATOR_CLEAR);
298 cairo_paint (cairo_context);
299 cairo_set_operator (cairo_context, CAIRO_OPERATOR_OVER);
300
301 /* Scale to fit in window */
302 gtk_widget_get_allocation(widget, &allocation);
303 scale = MIN ((float) allocation.width / tablet->area.width,
304 (float) allocation.height / tablet->area.height);
305 cairo_scale (cairo_context, scale, scale);
306
307 /* Center the result in window */
308 twidth = (double) tablet->area.width;
309 theight = (double) tablet->area.height;
310 cairo_user_to_device_distance (cairo_context, &twidth, &theight);
311 twidth = ((double) allocation.width - twidth) / 2.0;
312 theight = ((double) allocation.height - theight) / 2.0;
313 cairo_device_to_user_distance (cairo_context, &twidth, &theight);
314 cairo_translate (cairo_context, twidth, theight);
315
316 /* And render the tablet layout */
317 rsvg_handle_render_cairo (tablet->handle, cairo_context);
318 print_button_labels (cairo_context, tablet);
319 cairo_destroy (cairo_context);
320 }
321
322 static gboolean
on_timer_cb(Tablet * tablet)323 on_timer_cb (Tablet *tablet)
324 {
325 GtkAllocation allocation;
326 int num_buttons;
327
328 tablet->active_button++;
329 num_buttons = libwacom_get_num_buttons (tablet->device);
330 if (tablet->active_button >= 'A' + num_buttons)
331 tablet->active_button = 'A';
332 update_tablet (tablet);
333 gtk_widget_get_allocation (GTK_WIDGET(tablet->widget), &allocation);
334 gdk_window_invalidate_rect(gtk_widget_get_window (tablet->widget), &allocation, FALSE);
335
336 return TRUE;
337 }
338
339 static gboolean
on_delete_cb(GtkWidget * widget,GdkEvent * event,Tablet * tablet)340 on_delete_cb (GtkWidget *widget, GdkEvent *event, Tablet *tablet)
341 {
342 gtk_main_quit ();
343
344 return TRUE;
345 }
346
347 int
main(int argc,char ** argv)348 main (int argc, char **argv)
349 {
350 GOptionContext *context;
351 RsvgHandle *handle;
352 RsvgDimensionData dimensions;
353 GError *error;
354 WacomDeviceDatabase *db;
355 WacomDevice *device;
356 Tablet *tablet;
357 GdkWindow *gdk_win;
358 GdkColor black;
359 char *tabletname;
360 const char *filename;
361 GOptionEntry
362 options[] = {
363 {"tablet", 't', 0, G_OPTION_ARG_STRING, &tabletname, "Name of the tablet to show", "<string>"},
364 {NULL}
365 };
366
367 handle = NULL;
368 error = NULL;
369 tabletname = NULL;
370
371 context = g_option_context_new ("- libwacom tablet viewer");
372 g_option_context_add_main_entries (context, options, NULL);
373 g_option_context_add_group (context, gtk_get_option_group (TRUE));
374 g_option_context_set_help_enabled (context, TRUE);
375 g_option_context_parse (context, &argc, &argv, NULL);
376 g_option_context_free (context);
377
378 gtk_init (&argc, &argv);
379 if (tabletname == NULL) {
380 g_warning ("No tablet name provided, exiting");
381 return 1;
382 }
383 db = libwacom_database_new_for_path(TOPSRCDIR"/data");
384 if (!db) {
385 g_warning ("Failed to load libwacom database, exiting");
386 return 1;
387 }
388 device = libwacom_new_from_name(db, tabletname, NULL);
389 if (!device) {
390 g_warning ("Device '%s' not found in libwacom database, exiting", tabletname);
391 return 1;
392 }
393
394 filename = libwacom_get_layout_filename(device);
395 if (filename == NULL) {
396 g_warning ("Device '%s' has no layout available, exiting", tabletname);
397 return 1;
398 }
399 handle = rsvg_handle_new_from_file (filename, &error);
400 if (error || handle == NULL)
401 return 1;
402 rsvg_handle_get_dimensions (handle, &dimensions);
403 g_object_unref (handle);
404
405 tablet = g_new0 (Tablet, 1);
406 tablet->device = device;
407 tablet->area.width = dimensions.width;
408 tablet->area.height = dimensions.height;
409 tablet->handle = NULL;
410 tablet->active_button = 'A';
411 tablet->num_buttons = libwacom_get_num_buttons (device);
412 tablet->widget = gtk_window_new (GTK_WINDOW_TOPLEVEL);
413
414 gtk_widget_set_app_paintable (tablet->widget, TRUE);
415 gtk_widget_realize (tablet->widget);
416 gdk_win = gtk_widget_get_window (tablet->widget);
417 gdk_color_parse (BACK_COLOR, &black);
418 gdk_window_set_background (gdk_win, &black);
419 gtk_window_set_default_size (GTK_WINDOW (tablet->widget), 800, 600);
420 gtk_window_set_title (GTK_WINDOW (tablet->widget), libwacom_get_name (device));
421
422 g_signal_connect (tablet->widget, "expose-event", G_CALLBACK(on_expose_cb), tablet);
423 g_signal_connect (tablet->widget, "delete-event", G_CALLBACK(on_delete_cb), tablet);
424 tablet->timeout = g_timeout_add(750 /* ms */, (GSourceFunc) on_timer_cb, tablet);
425
426 gtk_widget_show (tablet->widget);
427
428 gtk_main();
429
430 libwacom_destroy(device);
431 libwacom_database_destroy(db);
432
433 return 0;
434 }
435