1 /*
2  * Gromit-MPX -- a program for painting on the screen
3  *
4  * Gromit Copyright (C) 2000 Simon Budig <Simon.Budig@unix-ag.org>
5  *
6  * Gromit-MPX Copyright (C) 2009,2010 Christian Beier <dontmind@freeshell.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21  *
22  */
23 
24 #include <string.h>
25 #include <stdlib.h>
26 #include <stdint.h>
27 #include "gromit-mpx.h"
28 #include "input.h"
29 #include "callbacks.h"
30 #include "config.h"
31 #include "build-config.h"
32 
33 
on_expose(GtkWidget * widget,cairo_t * cr,gpointer user_data)34 gboolean on_expose (GtkWidget *widget,
35 		    cairo_t* cr,
36 		    gpointer user_data)
37 {
38   GromitData *data = (GromitData *) user_data;
39 
40   if(data->debug)
41     g_printerr("DEBUG: got draw event\n");
42 
43   cairo_save (cr);
44   cairo_set_source_surface (cr, data->backbuffer, 0, 0);
45   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
46   cairo_paint (cr);
47   cairo_restore (cr);
48 
49   return TRUE;
50 }
51 
52 
53 
54 
on_configure(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)55 gboolean on_configure (GtkWidget *widget,
56 		       GdkEventExpose *event,
57 		       gpointer user_data)
58 {
59   GromitData *data = (GromitData *) user_data;
60 
61   if(data->debug)
62     g_printerr("DEBUG: got configure event\n");
63 
64   return TRUE;
65 }
66 
67 
68 
on_screen_changed(GtkWidget * widget,GdkScreen * previous_screen,gpointer user_data)69 void on_screen_changed(GtkWidget *widget,
70 		       GdkScreen *previous_screen,
71 		       gpointer   user_data)
72 {
73   GromitData *data = (GromitData *) user_data;
74 
75   if(data->debug)
76     g_printerr("DEBUG: got screen-changed event\n");
77 
78   GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET (widget));
79   GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
80   if (visual == NULL)
81     visual = gdk_screen_get_system_visual (screen);
82 
83   gtk_widget_set_visual (widget, visual);
84 }
85 
86 
87 
on_monitors_changed(GdkScreen * screen,gpointer user_data)88 void on_monitors_changed ( GdkScreen *screen,
89 			   gpointer   user_data)
90 {
91   GromitData *data = (GromitData *) user_data;
92 
93   // get new sizes
94   data->width = gdk_screen_get_width (data->screen);
95   data->height = gdk_screen_get_height (data->screen);
96 
97   if(data->debug)
98     g_printerr("DEBUG: screen size changed to %d x %d!\n", data->width, data->height);
99 
100   // change size
101   gtk_widget_set_size_request(GTK_WIDGET(data->win), data->width, data->height);
102   // try to set transparent for input
103   cairo_region_t* r =  cairo_region_create();
104   gtk_widget_input_shape_combine_region(data->win, r);
105   cairo_region_destroy(r);
106 
107   /* recreate the shape surface */
108   cairo_surface_t *new_shape = cairo_image_surface_create(CAIRO_FORMAT_ARGB32 ,data->width, data->height);
109   cairo_t *cr = cairo_create (new_shape);
110   cairo_set_source_surface (cr, data->backbuffer, 0, 0);
111   cairo_paint (cr);
112   cairo_destroy (cr);
113   cairo_surface_destroy(data->backbuffer);
114   data->backbuffer = new_shape;
115 
116   /*
117      these depend on the shape surface
118   */
119   GHashTableIter it;
120   gpointer value;
121   g_hash_table_iter_init (&it, data->tool_config);
122   while (g_hash_table_iter_next (&it, NULL, &value))
123     paint_context_free(value);
124   g_hash_table_remove_all(data->tool_config);
125 
126 
127   parse_config(data); // also calls paint_context_new() :-(
128 
129 
130   data->default_pen = paint_context_new (data, GROMIT_PEN,
131 					 data->red, 7, 0, 1);
132   data->default_eraser = paint_context_new (data, GROMIT_ERASER,
133 					    data->red, 75, 0, 1);
134 
135   if(!data->composited) // set shape
136     {
137       cairo_region_t* r = gdk_cairo_region_create_from_surface(data->backbuffer);
138       gtk_widget_shape_combine_region(data->win, r);
139       cairo_region_destroy(r);
140     }
141 
142   setup_input_devices(data);
143 
144 
145   gtk_widget_show_all (data->win);
146 }
147 
148 
149 
on_composited_changed(GdkScreen * screen,gpointer user_data)150 void on_composited_changed ( GdkScreen *screen,
151 			   gpointer   user_data)
152 {
153   GromitData *data = (GromitData *) user_data;
154 
155   if(data->debug)
156     g_printerr("DEBUG: got composited-changed event\n");
157 
158   data->composited = gdk_screen_is_composited (data->screen);
159 
160   if(data->composited)
161     {
162       // undo shape
163       gtk_widget_shape_combine_region(data->win, NULL);
164       // re-apply transparency
165       gtk_widget_set_opacity(data->win, 0.75);
166     }
167 
168   // set anti-aliasing
169   GHashTableIter it;
170   gpointer value;
171   g_hash_table_iter_init (&it, data->tool_config);
172   while (g_hash_table_iter_next (&it, NULL, &value))
173     {
174       GromitPaintContext *context = value;
175       cairo_set_antialias(context->paint_ctx, data->composited ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
176     }
177 
178 
179   GdkRectangle rect = {0, 0, data->width, data->height};
180   gdk_window_invalidate_rect(gtk_widget_get_window(data->win), &rect, 0);
181 }
182 
183 
184 
on_clientapp_selection_get(GtkWidget * widget,GtkSelectionData * selection_data,guint info,guint time,gpointer user_data)185 void on_clientapp_selection_get (GtkWidget          *widget,
186 				 GtkSelectionData   *selection_data,
187 				 guint               info,
188 				 guint               time,
189 				 gpointer            user_data)
190 {
191   GromitData *data = (GromitData *) user_data;
192 
193   gchar *ans = "";
194 
195   if(data->debug)
196     g_printerr("DEBUG: clientapp received request.\n");
197 
198   if (gtk_selection_data_get_target(selection_data) == GA_TOGGLEDATA)
199     {
200       ans = data->clientdata;
201     }
202 
203   gtk_selection_data_set (selection_data,
204                           gtk_selection_data_get_target(selection_data),
205                           8, (guchar*)ans, strlen (ans));
206 }
207 
208 
on_clientapp_selection_received(GtkWidget * widget,GtkSelectionData * selection_data,guint time,gpointer user_data)209 void on_clientapp_selection_received (GtkWidget *widget,
210 				      GtkSelectionData *selection_data,
211 				      guint time,
212 				      gpointer user_data)
213 {
214   GromitData *data = (GromitData *) user_data;
215 
216   /* If someone has a selection for us, Gromit is already running. */
217 
218   if(gtk_selection_data_get_data_type(selection_data) == GDK_NONE)
219     data->client = 0;
220   else
221     data->client = 1;
222 
223   gtk_main_quit ();
224 }
225 
226 
227 
228 static float line_thickener = 0;
229 
230 
on_buttonpress(GtkWidget * win,GdkEventButton * ev,gpointer user_data)231 gboolean on_buttonpress (GtkWidget *win,
232 			 GdkEventButton *ev,
233 			 gpointer user_data)
234 {
235   GromitData *data = (GromitData *) user_data;
236   gdouble pressure = 1;
237 
238   /* get the data for this device */
239   GromitDeviceData *devdata = g_hash_table_lookup(data->devdatatable, ev->device);
240 
241   if(data->debug)
242     g_printerr("DEBUG: Device '%s': Button %i Down State %d at (x,y)=(%.2f : %.2f)\n",
243 	       gdk_device_get_name(ev->device), ev->button, ev->state, ev->x, ev->y);
244 
245   if (!devdata->is_grabbed)
246     return FALSE;
247 
248   if (gdk_device_get_source(gdk_event_get_source_device((GdkEvent *)ev)) == GDK_SOURCE_PEN) {
249       /* Do not drop unprocessed motion events. Smoother drawing for pens of tablets. */
250       gdk_window_set_event_compression(gtk_widget_get_window(data->win), FALSE);
251   } else {
252       /* For all other source types, set back to default. Otherwise, lines were only
253 	 fully drawn to the end on button release. */
254       gdk_window_set_event_compression(gtk_widget_get_window(data->win), TRUE);
255   }
256 
257   /* See GdkModifierType. Am I fixing a Gtk misbehaviour???  */
258   ev->state |= 1 << (ev->button + 7);
259 
260 
261   if (ev->state != devdata->state ||
262       devdata->lastslave != gdk_event_get_source_device ((GdkEvent *) ev))
263     select_tool (data, ev->device, gdk_event_get_source_device ((GdkEvent *) ev), ev->state);
264 
265   devdata->lastx = ev->x;
266   devdata->lasty = ev->y;
267   devdata->motion_time = ev->time;
268 
269   snap_undo_state (data);
270 
271   gdk_event_get_axis ((GdkEvent *) ev, GDK_AXIS_PRESSURE, &pressure);
272   data->maxwidth = (CLAMP (pressure + line_thickener, 0, 1) *
273 		    (double) (devdata->cur_context->width -
274 			      devdata->cur_context->minwidth) +
275 		    devdata->cur_context->minwidth);
276 
277   if (ev->button <= 5)
278     draw_line (data, ev->device, ev->x, ev->y, ev->x, ev->y);
279 
280   coord_list_prepend (data, ev->device, ev->x, ev->y, data->maxwidth);
281 
282   return TRUE;
283 }
284 
285 
on_motion(GtkWidget * win,GdkEventMotion * ev,gpointer user_data)286 gboolean on_motion (GtkWidget *win,
287 		    GdkEventMotion *ev,
288 		    gpointer user_data)
289 {
290   GromitData *data = (GromitData *) user_data;
291   GdkTimeCoord **coords = NULL;
292   gint nevents;
293   int i;
294   gdouble pressure = 1;
295   /* get the data for this device */
296   GromitDeviceData *devdata = g_hash_table_lookup(data->devdatatable, ev->device);
297 
298   if (!devdata->is_grabbed)
299     return FALSE;
300 
301   if(data->debug)
302       g_printerr("DEBUG: Device '%s': motion to (x,y)=(%.2f : %.2f)\n", gdk_device_get_name(ev->device), ev->x, ev->y);
303 
304   if (ev->state != devdata->state ||
305       devdata->lastslave != gdk_event_get_source_device ((GdkEvent *) ev))
306     select_tool (data, ev->device, gdk_event_get_source_device ((GdkEvent *) ev), ev->state);
307 
308   gdk_device_get_history (ev->device, ev->window,
309 			  devdata->motion_time, ev->time,
310 			  &coords, &nevents);
311 
312   if(!data->xinerama && nevents > 0)
313     {
314       for (i=0; i < nevents; i++)
315         {
316           gdouble x, y;
317 
318           gdk_device_get_axis (ev->device, coords[i]->axes,
319                                GDK_AXIS_PRESSURE, &pressure);
320           if (pressure > 0)
321             {
322 	      data->maxwidth = (CLAMP (pressure + line_thickener, 0, 1) *
323 				(double) (devdata->cur_context->width -
324 					  devdata->cur_context->minwidth) +
325 				devdata->cur_context->minwidth);
326 
327               gdk_device_get_axis(ev->device, coords[i]->axes,
328                                   GDK_AXIS_X, &x);
329               gdk_device_get_axis(ev->device, coords[i]->axes,
330                                   GDK_AXIS_Y, &y);
331 
332 	      draw_line (data, ev->device, devdata->lastx, devdata->lasty, x, y);
333 
334               coord_list_prepend (data, ev->device, x, y, data->maxwidth);
335               devdata->lastx = x;
336               devdata->lasty = y;
337             }
338         }
339 
340       devdata->motion_time = coords[nevents-1]->time;
341       g_free (coords);
342     }
343 
344   /* always paint to the current event coordinate. */
345   gdk_event_get_axis ((GdkEvent *) ev, GDK_AXIS_PRESSURE, &pressure);
346 
347   if (pressure > 0)
348     {
349       data->maxwidth = (CLAMP (pressure + line_thickener, 0, 1) *
350 			(double) (devdata->cur_context->width -
351 				  devdata->cur_context->minwidth) +
352 			devdata->cur_context->minwidth);
353 
354       if(devdata->motion_time > 0)
355 	{
356 	  draw_line (data, ev->device, devdata->lastx, devdata->lasty, ev->x, ev->y);
357 	  coord_list_prepend (data, ev->device, ev->x, ev->y, data->maxwidth);
358 	}
359     }
360 
361   devdata->lastx = ev->x;
362   devdata->lasty = ev->y;
363   devdata->motion_time = ev->time;
364 
365   return TRUE;
366 }
367 
368 
on_buttonrelease(GtkWidget * win,GdkEventButton * ev,gpointer user_data)369 gboolean on_buttonrelease (GtkWidget *win,
370 			   GdkEventButton *ev,
371 			   gpointer user_data)
372 {
373   GromitData *data = (GromitData *) user_data;
374   /* get the device data for this event */
375   GromitDeviceData *devdata = g_hash_table_lookup(data->devdatatable, ev->device);
376 
377   gfloat direction = 0;
378   gint width = 0;
379   if(devdata->cur_context)
380     width = devdata->cur_context->arrowsize * devdata->cur_context->width / 2;
381 
382 
383   if ((ev->x != devdata->lastx) ||
384       (ev->y != devdata->lasty))
385     on_motion(win, (GdkEventMotion *) ev, user_data);
386 
387   if (!devdata->is_grabbed)
388     return FALSE;
389 
390   if (devdata->cur_context->arrowsize != 0 &&
391       coord_list_get_arrow_param (data, ev->device, width * 3,
392 				  &width, &direction))
393     draw_arrow (data, ev->device, ev->x, ev->y, width, direction);
394 
395   coord_list_free (data, ev->device);
396 
397   return TRUE;
398 }
399 
400 /* Remote control */
on_mainapp_selection_get(GtkWidget * widget,GtkSelectionData * selection_data,guint info,guint time,gpointer user_data)401 void on_mainapp_selection_get (GtkWidget          *widget,
402 			       GtkSelectionData   *selection_data,
403 			       guint               info,
404 			       guint               time,
405 			       gpointer            user_data)
406 {
407   GromitData *data = (GromitData *) user_data;
408 
409   gchar *uri = "OK";
410   GdkAtom action = gtk_selection_data_get_target(selection_data);
411 
412   if(action == GA_TOGGLE)
413     {
414       /* ask back client for device id */
415       gtk_selection_convert (data->win, GA_DATA,
416                              GA_TOGGLEDATA, time);
417       gtk_main(); /* Wait for the response */
418     }
419   else if (action == GA_VISIBILITY)
420     toggle_visibility (data);
421   else if (action == GA_CLEAR)
422     clear_screen (data);
423   else if (action == GA_RELOAD)
424     setup_input_devices(data);
425   else if (action == GA_QUIT)
426     gtk_main_quit ();
427   else if (action == GA_UNDO)
428     undo_drawing (data);
429   else if (action == GA_REDO)
430     redo_drawing (data);
431   else
432     uri = "NOK";
433 
434 
435   gtk_selection_data_set (selection_data,
436                           gtk_selection_data_get_target(selection_data),
437                           8, (guchar*)uri, strlen (uri));
438 }
439 
440 
on_mainapp_selection_received(GtkWidget * widget,GtkSelectionData * selection_data,guint time,gpointer user_data)441 void on_mainapp_selection_received (GtkWidget *widget,
442 				    GtkSelectionData *selection_data,
443 				    guint time,
444 				    gpointer user_data)
445 {
446   GromitData *data = (GromitData *) user_data;
447 
448   if(gtk_selection_data_get_length(selection_data) < 0)
449     {
450       if(data->debug)
451         g_printerr("DEBUG: mainapp got no answer back from client.\n");
452     }
453   else
454     {
455       if(gtk_selection_data_get_target(selection_data) == GA_TOGGLEDATA )
456         {
457 	  intptr_t dev_nr = strtoull((gchar*)gtk_selection_data_get_data(selection_data), NULL, 10);
458 
459           if(data->debug)
460 	    g_printerr("DEBUG: mainapp got toggle id '%ld' back from client.\n", (long)dev_nr);
461 
462 	  if(dev_nr < 0)
463 	    toggle_grab(data, NULL); /* toggle all */
464 	  else
465 	    {
466 	      /* find dev numbered dev_nr */
467 	      GHashTableIter it;
468 	      gpointer value;
469 	      GromitDeviceData* devdata = NULL;
470 	      g_hash_table_iter_init (&it, data->devdatatable);
471 	      while (g_hash_table_iter_next (&it, NULL, &value))
472 		{
473 		  devdata = value;
474 		  if(devdata->index == dev_nr)
475 		    break;
476 		  else
477 		    devdata = NULL;
478 		}
479 
480 	      if(devdata)
481 		toggle_grab(data, devdata->device);
482 	      else
483 		g_printerr("ERROR: No device at index %ld.\n", (long)dev_nr);
484 	    }
485         }
486     }
487 
488   gtk_main_quit ();
489 }
490 
491 
on_device_removed(GdkDeviceManager * device_manager,GdkDevice * device,gpointer user_data)492 void on_device_removed (GdkDeviceManager *device_manager,
493 			GdkDevice        *device,
494 			gpointer          user_data)
495 {
496   GromitData *data = (GromitData *) user_data;
497 
498   if(!gdk_device_get_device_type(device) == GDK_DEVICE_TYPE_MASTER
499      || gdk_device_get_n_axes(device) < 2)
500     return;
501 
502   if(data->debug)
503     g_printerr("DEBUG: device '%s' removed\n", gdk_device_get_name(device));
504 
505   setup_input_devices(data);
506 }
507 
on_device_added(GdkDeviceManager * device_manager,GdkDevice * device,gpointer user_data)508 void on_device_added (GdkDeviceManager *device_manager,
509 		      GdkDevice        *device,
510 		      gpointer          user_data)
511 {
512   GromitData *data = (GromitData *) user_data;
513 
514   if(!gdk_device_get_device_type(device) == GDK_DEVICE_TYPE_MASTER
515      || gdk_device_get_n_axes(device) < 2)
516     return;
517 
518   if(data->debug)
519     g_printerr("DEBUG: device '%s' added\n", gdk_device_get_name(device));
520 
521   setup_input_devices(data);
522 }
523 
524 
525 
on_toggle_paint(GtkWidget * widget,GdkEventButton * ev,gpointer user_data)526 gboolean on_toggle_paint(GtkWidget *widget,
527 			 GdkEventButton  *ev,
528 			 gpointer   user_data)
529 {
530     GromitData *data = (GromitData *) user_data;
531 
532     if(data->debug)
533 	g_printerr("DEBUG: Device '%s': Button %i on_toggle_paint at (x,y)=(%.2f : %.2f)\n",
534 		   gdk_device_get_name(ev->device), ev->button, ev->x, ev->y);
535 
536     toggle_grab(data, ev->device);
537 
538     return TRUE;
539 }
540 
on_toggle_paint_all(GtkMenuItem * menuitem,gpointer user_data)541 void on_toggle_paint_all (GtkMenuItem *menuitem,
542 			  gpointer     user_data)
543 {
544   GromitData *data = (GromitData *) user_data;
545 
546   /*
547     on_toggle_paint_all() is called when toggle_paint_item in the menu
548     is clicked on KDE-like platforms. Under X11, at least
549     https://github.com/ubuntu/gnome-shell-extension-appindicator seems to
550     grab the pointer, preventing grabbing by Gromit-MPX.
551     Simply work around this by waiting :-/
552    */
553   char *xdg_session_type = getenv("XDG_SESSION_TYPE");
554   if (xdg_session_type && strcmp(xdg_session_type, "x11") == 0)
555       g_usleep(333*1000);
556 
557   toggle_grab(data, NULL);
558 }
559 
560 
on_clear(GtkMenuItem * menuitem,gpointer user_data)561 void on_clear (GtkMenuItem *menuitem,
562 	       gpointer     user_data)
563 {
564   GromitData *data = (GromitData *) user_data;
565   clear_screen(data);
566 }
567 
568 
on_toggle_vis(GtkMenuItem * menuitem,gpointer user_data)569 void on_toggle_vis(GtkMenuItem *menuitem,
570 		   gpointer     user_data)
571 {
572   GromitData *data = (GromitData *) user_data;
573   toggle_visibility(data);
574 }
575 
576 
on_thicker_lines(GtkMenuItem * menuitem,gpointer user_data)577 void on_thicker_lines(GtkMenuItem *menuitem,
578 		      gpointer     user_data)
579 {
580   line_thickener += 0.1;
581 }
582 
on_thinner_lines(GtkMenuItem * menuitem,gpointer user_data)583 void on_thinner_lines(GtkMenuItem *menuitem,
584 		      gpointer     user_data)
585 {
586   line_thickener -= 0.1;
587   if (line_thickener < -1)
588     line_thickener = -1;
589 }
590 
591 
on_opacity_bigger(GtkMenuItem * menuitem,gpointer user_data)592 void on_opacity_bigger(GtkMenuItem *menuitem,
593 		       gpointer     user_data)
594 {
595   GromitData *data = (GromitData *) user_data;
596   data->opacity += 0.1;
597   if(data->opacity>1.0)
598     data->opacity = 1.0;
599   gtk_widget_set_opacity(data->win, data->opacity);
600 }
601 
on_opacity_lesser(GtkMenuItem * menuitem,gpointer user_data)602 void on_opacity_lesser(GtkMenuItem *menuitem,
603 		       gpointer     user_data)
604 {
605   GromitData *data = (GromitData *) user_data;
606   data->opacity -= 0.1;
607   if(data->opacity<0.0)
608     data->opacity = 0.0;
609   gtk_widget_set_opacity(data->win, data->opacity);
610 }
611 
612 
on_undo(GtkMenuItem * menuitem,gpointer user_data)613 void on_undo(GtkMenuItem *menuitem,
614 	     gpointer     user_data)
615 {
616   GromitData *data = (GromitData *) user_data;
617   undo_drawing (data);
618 }
619 
on_redo(GtkMenuItem * menuitem,gpointer user_data)620 void on_redo(GtkMenuItem *menuitem,
621 	     gpointer     user_data)
622 {
623   GromitData *data = (GromitData *) user_data;
624   redo_drawing (data);
625 }
626 
627 
on_about(GtkMenuItem * menuitem,gpointer user_data)628 void on_about(GtkMenuItem *menuitem,
629 	      gpointer     user_data)
630 {
631     const gchar *authors [] = { "Christian Beier <dontmind@freeshell.org>",
632                                 "Simon Budig <Simon.Budig@unix-ag.org>",
633                                 "Barak A. Pearlmutter <barak+git@pearlmutter.net>",
634                                 "Nathan Whitehead <nwhitehe@gmail.com>",
635                                 "Lukáš Hermann <tuxilero@gmail.com>",
636                                 "Katie Holly <git@meo.ws>",
637                                 "Monty Montgomery <xiphmont@gmail.com>",
638                                 "AlisterH <alister.hood@gmail.com>",
639                                 "Mehmet Atif Ergun <mehmetaergun@users.noreply.github.com>",
640                                 "Russel Winder <russel@winder.org.uk>",
641                                 "Tao Klerks <tao@klerks.biz>",
642                                 "Tobias Schönberg <tobias47n9e@gmail.com>",
643                                 "Yuri D'Elia <yuri.delia@eurac.edu>",
644 				"Julián Unrrein <junrrein@gmail.com>",
645 				"Eshant Gupta <guptaeshant@gmail.com>",
646                                  NULL };
647     gtk_show_about_dialog (NULL,
648 			   "program-name", "Gromit-MPX",
649 			   "logo-icon-name", "net.christianbeier.Gromit-MPX",
650 			   "title", "About Gromit-MPX",
651 			   "comments", "Gromit-MPX (GRaphics Over MIscellaneous Things - Multi-Pointer-EXtension) is an on-screen annotation tool that works with any Unix desktop environment under X11 as well as Wayland.",
652 			   "version", VERSION,
653 			   "website", "https://github.com/bk138/gromit-mpx",
654 			   "authors", authors,
655 			   "copyright", "2009-2022 Christian Beier, Copyright 2000 Simon Budig",
656 			   "license-type", GTK_LICENSE_GPL_2_0,
657 			   NULL);
658 }
659 
660 
on_intro_show_again_button_toggled(GtkCheckButton * toggle,GromitData * data)661 static void on_intro_show_again_button_toggled(GtkCheckButton *toggle, GromitData *data)
662 {
663   data->show_intro_on_startup = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle));
664 }
665 
on_intro(GtkMenuItem * menuitem,gpointer user_data)666 void on_intro(GtkMenuItem *menuitem,
667 	      gpointer user_data)
668 {
669     GromitData *data = (GromitData *) user_data;
670 
671     // Create a new assistant widget with no pages.
672     GtkWidget *assistant = gtk_assistant_new ();
673     gtk_window_set_position (GTK_WINDOW(assistant), GTK_WIN_POS_CENTER);
674 
675     // set page one
676     GtkWidget *widgetOne = gtk_label_new ("Gromit-MPX (GRaphics Over MIscellaneous Things) is a small tool to make\n"
677 					  "annotations on the screen.\n\n"
678 					  "Its main use is for making presentations of some application. Normally,\n"
679 					  "you would have to move the mouse pointer around the point of interest\n"
680 					  "until hopefully everybody noticed it.  With Gromit-MPX, you can draw\n"
681 					  "everywhere onto the screen, highlighting some button or area.\n\n"
682                                           "If you happen to enjoy using Gromit-MPX, please consider supporting\n"
683 					  "its development by using one of the donation options on the project's\n"
684 					  "website or directly via the support options available from the tray menu.\n");
685     gtk_assistant_append_page (GTK_ASSISTANT (assistant), widgetOne);
686     gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), widgetOne, "Gromit-MPX - What is it?");
687     gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), widgetOne, GTK_ASSISTANT_PAGE_INTRO);
688     gtk_assistant_set_page_complete (GTK_ASSISTANT (assistant), widgetOne, TRUE);
689 
690     // set page two
691     GtkWidget *widgetTwo = gtk_label_new (NULL);
692     char widgetTwoBuf[4096];
693     snprintf(widgetTwoBuf, sizeof(widgetTwoBuf),
694 	     "You can operate Gromit-MPX using its tray icon (if your desktop environment\n"
695 	     "provides a sys tray), but since you typically want to use the program you are\n"
696 	     "demonstrating and highlighting something is a short interruption of your\n"
697 	     "workflow, Gromit-MPX can be toggled on and off on the fly via a hotkey:\n\n"
698 	     "It grabs the `%s` and `%s` keys, so that no other application can use them\n"
699 	     "and they are available to Gromit-MPX only.  The available commands are:\n\n<tt><b>"
700 	     "   toggle painting:         %s\n"
701 	     "   clear screen:            SHIFT-%s\n"
702 	     "   toggle visibility:       CTRL-%s\n"
703 	     "   quit:                    ALT-%s\n"
704 	     "   undo last stroke:        %s\n"
705 	     "   redo last undone stroke: SHIFT-%s</b></tt>",
706 	     data->hot_keyval, data->undo_keyval,
707 	     data->hot_keyval, data->hot_keyval, data->hot_keyval, data->hot_keyval, data->undo_keyval, data->undo_keyval);
708     gtk_label_set_markup (GTK_LABEL (widgetTwo), widgetTwoBuf);
709     gtk_assistant_append_page (GTK_ASSISTANT (assistant), widgetTwo);
710     gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), widgetTwo, "Gromit-MPX - How to use it");
711     gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), widgetTwo, GTK_ASSISTANT_PAGE_CONTENT);
712     gtk_assistant_set_page_complete (GTK_ASSISTANT (assistant), widgetTwo, TRUE);
713 
714     // set page three
715     GtkWidget *widgetThree = gtk_grid_new ();
716     GtkWidget *widgetThreeText = gtk_label_new ("Do you want to show this introduction again on the next start of Gromit-MPX?\n"
717 						"You can always access it again via the sys tray menu.\n");
718     GtkWidget *widgetThreeButton = gtk_check_button_new_with_label ("Show again on startup");
719     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widgetThreeButton), data->show_intro_on_startup);
720     gtk_grid_attach (GTK_GRID (widgetThree), widgetThreeText, 0, 0, 1, 1);
721     gtk_grid_attach_next_to (GTK_GRID (widgetThree), widgetThreeButton, widgetThreeText, GTK_POS_BOTTOM, 1, 1);
722     g_signal_connect (G_OBJECT (widgetThreeButton), "toggled",
723 		      G_CALLBACK (on_intro_show_again_button_toggled), data);
724     gtk_assistant_append_page (GTK_ASSISTANT (assistant), widgetThree);
725     gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), widgetThree, GTK_ASSISTANT_PAGE_CONFIRM);
726     gtk_assistant_set_page_complete (GTK_ASSISTANT (assistant), widgetThree, TRUE);
727 
728     // connect the close buttons
729     g_signal_connect (G_OBJECT (assistant), "cancel",
730 		      G_CALLBACK (gtk_widget_destroy), NULL);
731     g_signal_connect (G_OBJECT (assistant), "close",
732 		      G_CALLBACK (gtk_widget_destroy), NULL);
733 
734     // show
735     gtk_widget_show_all (assistant);
736 }
737 
on_support_liberapay(GtkMenuItem * menuitem,gpointer user_data)738 void on_support_liberapay(GtkMenuItem *menuitem, gpointer user_data)
739 {
740     gtk_show_uri_on_window (NULL,
741 			    "https://liberapay.com/bk138",
742 			    GDK_CURRENT_TIME,
743 			    NULL);
744 
745 }
746 
on_support_patreon(GtkMenuItem * menuitem,gpointer user_data)747 void on_support_patreon(GtkMenuItem *menuitem, gpointer user_data)
748 {
749     gtk_show_uri_on_window (NULL,
750 			    "https://patreon.com/bk138",
751 			    GDK_CURRENT_TIME,
752 			    NULL);
753 
754 }
755 
on_support_paypal(GtkMenuItem * menuitem,gpointer user_data)756 void on_support_paypal(GtkMenuItem *menuitem, gpointer user_data)
757 {
758     gtk_show_uri_on_window (NULL,
759 			    "https://www.paypal.com/donate?hosted_button_id=N7GSSPRPUSTPU",
760 			    GDK_CURRENT_TIME,
761 			    NULL);
762 
763 }
764