1 /* -*- mode: C; c-basic-offset: 4 -*-
2  * Drive Mount Applet
3  * Copyright (c) 2004 Canonical Ltd
4  * Copyright 2008 Pierre Ossman
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * Author:
21  *   James Henstridge <jamesh@canonical.com>
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include <gio/gio.h>
29 #include "drive-button.h"
30 #include <glib/gi18n.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <gio/gdesktopappinfo.h>
33 
34 #include <string.h>
35 
36 enum {
37     CMD_NONE,
38     CMD_MOUNT_OR_PLAY,
39     CMD_UNMOUNT,
40     CMD_EJECT
41 };
42 
43 /* type registration boilerplate code */
44 G_DEFINE_TYPE (DriveButton, drive_button, GTK_TYPE_BUTTON)
45 
46 static void     drive_button_set_volume   (DriveButton    *self,
47                                            GVolume        *volume);
48 static void     drive_button_set_mount    (DriveButton    *self,
49                                            GMount         *mount);
50 static void     drive_button_reset_popup  (DriveButton    *self);
51 static void     drive_button_ensure_popup (DriveButton    *self);
52 
53 static void     drive_button_dispose      (GObject        *object);
54 #if 0
55 static void     drive_button_unrealize    (GtkWidget      *widget);
56 #endif /* 0 */
57 static gboolean drive_button_button_press (GtkWidget      *widget,
58                                            GdkEventButton *event);
59 static gboolean drive_button_key_press    (GtkWidget      *widget,
60                                            GdkEventKey    *event);
61 static void drive_button_theme_change     (GtkIconTheme   *icon_theme,
62                                            gpointer        data);
63 
64 static void
drive_button_class_init(DriveButtonClass * class)65 drive_button_class_init (DriveButtonClass *class)
66 {
67     G_OBJECT_CLASS (class)->dispose = drive_button_dispose;
68     GTK_WIDGET_CLASS (class)->button_press_event = drive_button_button_press;
69     GTK_WIDGET_CLASS (class)->key_press_event = drive_button_key_press;
70 
71     GtkCssProvider *provider;
72 
73     provider = gtk_css_provider_new ();
74 
75     gtk_css_provider_load_from_data (provider,
76                                      "#drive-button {\n"
77                                      " border-width: 0px;\n"
78                                      " padding: 0px;\n"
79                                      " margin: 0px;\n"
80                                      "}",
81                                      -1, NULL);
82 
83     gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
84                                     GTK_STYLE_PROVIDER (provider),
85                                     GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
86     g_object_unref (provider);
87 }
88 
89 static void
drive_button_init(DriveButton * self)90 drive_button_init (DriveButton *self)
91 {
92     GtkWidget *image;
93 
94     image = gtk_image_new ();
95     gtk_container_add (GTK_CONTAINER (self), image);
96     gtk_widget_show (image);
97 
98     self->volume = NULL;
99     self->mount = NULL;
100     self->icon_size = 24;
101     self->update_tag = 0;
102 
103     self->popup_menu = NULL;
104 
105     gtk_widget_set_name (GTK_WIDGET (self), "drive-button");
106 }
107 
108 GtkWidget *
drive_button_new(GVolume * volume)109 drive_button_new (GVolume *volume)
110 {
111     DriveButton *self;
112 
113     self = g_object_new (DRIVE_TYPE_BUTTON, NULL);
114     if (volume != NULL) {
115       drive_button_set_volume (self, volume);
116 
117       g_signal_connect (gtk_icon_theme_get_default (), "changed",
118                         G_CALLBACK (drive_button_theme_change),
119                         self);
120     }
121 
122     return (GtkWidget *)self;
123 }
124 
125 GtkWidget *
drive_button_new_from_mount(GMount * mount)126 drive_button_new_from_mount (GMount *mount)
127 {
128     DriveButton *self;
129 
130     self = g_object_new (DRIVE_TYPE_BUTTON, NULL);
131     drive_button_set_mount (self, mount);
132 
133     g_signal_connect (gtk_icon_theme_get_default (), "changed",
134                       G_CALLBACK (drive_button_theme_change),
135                       self);
136 
137     return (GtkWidget *)self;
138 }
139 
140 static void
drive_button_dispose(GObject * object)141 drive_button_dispose (GObject *object)
142 {
143     DriveButton *self = DRIVE_BUTTON (object);
144 
145     drive_button_set_volume (self, NULL);
146 
147     if (self->update_tag)
148         g_source_remove (self->update_tag);
149     self->update_tag = 0;
150 
151     drive_button_reset_popup (self);
152 
153     if (G_OBJECT_CLASS (drive_button_parent_class)->dispose)
154         (* G_OBJECT_CLASS (drive_button_parent_class)->dispose) (object);
155 }
156 
157 static gboolean
drive_button_button_press(GtkWidget * widget,GdkEventButton * event)158 drive_button_button_press (GtkWidget      *widget,
159                            GdkEventButton *event)
160 {
161     DriveButton *self = DRIVE_BUTTON (widget);
162 
163     /* don't consume non-button1 presses */
164     if (event->button == 1) {
165         drive_button_ensure_popup (self);
166         if (self->popup_menu) {
167             gtk_menu_popup_at_widget (GTK_MENU (self->popup_menu),
168                                       widget,
169                                       GDK_GRAVITY_SOUTH_WEST,
170                                       GDK_GRAVITY_NORTH_WEST,
171                                       (const GdkEvent*) event);
172         }
173         return TRUE;
174     }
175     return FALSE;
176 }
177 
178 static gboolean
drive_button_key_press(GtkWidget * widget,GdkEventKey * event)179 drive_button_key_press (GtkWidget      *widget,
180                         GdkEventKey    *event)
181 {
182     DriveButton *self = DRIVE_BUTTON (widget);
183 
184     switch (event->keyval) {
185     case GDK_KEY_KP_Space:
186     case GDK_KEY_space:
187     case GDK_KEY_KP_Enter:
188     case GDK_KEY_Return:
189         drive_button_ensure_popup (self);
190         if (self->popup_menu) {
191             gtk_menu_popup_at_widget (GTK_MENU (self->popup_menu),
192                                       widget,
193                                       GDK_GRAVITY_SOUTH_WEST,
194                                       GDK_GRAVITY_NORTH_WEST,
195                                       (const GdkEvent*) event);
196         }
197         return TRUE;
198     }
199     return FALSE;
200 }
201 
202 static void
drive_button_theme_change(GtkIconTheme * icon_theme,gpointer data)203 drive_button_theme_change (GtkIconTheme *icon_theme,
204                            gpointer      data)
205 {
206     drive_button_queue_update (data);
207 }
208 
209 static void
drive_button_set_volume(DriveButton * self,GVolume * volume)210 drive_button_set_volume (DriveButton *self,
211                          GVolume     *volume)
212 {
213     g_return_if_fail (DRIVE_IS_BUTTON (self));
214 
215     if (self->volume) {
216         g_object_unref (self->volume);
217     }
218     self->volume = NULL;
219     if (self->mount) {
220         g_object_unref (self->mount);
221     }
222     self->mount = NULL;
223 
224     if (volume) {
225         self->volume = g_object_ref (volume);
226     }
227     drive_button_queue_update (self);
228 }
229 
230 static void
drive_button_set_mount(DriveButton * self,GMount * mount)231 drive_button_set_mount (DriveButton *self,
232                         GMount       *mount)
233 {
234     g_return_if_fail (DRIVE_IS_BUTTON (self));
235 
236     if (self->volume) {
237         g_object_unref (self->volume);
238     }
239     self->volume = NULL;
240     if (self->mount) {
241         g_object_unref (self->mount);
242     }
243     self->mount = NULL;
244 
245     if (mount) {
246         self->mount = g_object_ref (mount);
247     }
248     drive_button_queue_update (self);
249 }
250 
251 static gboolean
drive_button_update(gpointer user_data)252 drive_button_update (gpointer user_data)
253 {
254     DriveButton *self;
255     GdkScreen *screen;
256     GtkIconTheme *icon_theme;
257     GtkIconInfo *icon_info;
258     GIcon *icon;
259     int width, height, scale;
260     cairo_t *cr;
261     cairo_surface_t *surface = NULL;
262     cairo_surface_t *tmp_surface = NULL;
263     GtkRequisition button_req, image_req;
264     char *display_name, *tip;
265 
266     g_return_val_if_fail (DRIVE_IS_BUTTON (user_data), FALSE);
267     self = DRIVE_BUTTON (user_data);
268     self->update_tag = 0;
269 
270     /* base the icon size on the desired button size */
271     drive_button_reset_popup (self);
272     scale = gtk_widget_get_scale_factor (GTK_WIDGET (self));
273     gtk_widget_get_preferred_size (GTK_WIDGET (self), NULL, &button_req);
274     gtk_widget_get_preferred_size (gtk_bin_get_child (GTK_BIN (self)), NULL, &image_req);
275     width = (self->icon_size - (button_req.width - image_req.width)) / scale;
276     height = (self->icon_size - (button_req.height - image_req.height)) / scale;
277 
278     /* if no volume or mount, display general image */
279     if (!self->volume && !self->mount)
280     {
281         gtk_widget_set_tooltip_text (GTK_WIDGET (self), _("nothing to mount"));
282         screen = gtk_widget_get_screen (GTK_WIDGET (self));
283         icon_theme = gtk_icon_theme_get_for_screen (screen); //m
284         // note - other good icon would be emblem-unreadable
285         icon_info = gtk_icon_theme_lookup_icon_for_scale (icon_theme, "media-floppy",
286                                                           MIN (width, height), scale,
287                                                           GTK_ICON_LOOKUP_USE_BUILTIN);
288         if (icon_info) {
289             surface = gtk_icon_info_load_surface (icon_info, NULL, NULL);
290             g_object_unref (icon_info);
291         }
292 
293         if (!surface)
294             return FALSE;
295 
296         if (gtk_bin_get_child (GTK_BIN (self)) != NULL)
297             gtk_image_set_from_surface (GTK_IMAGE (gtk_bin_get_child (GTK_BIN (self))), surface);
298 
299         return FALSE;
300     }
301 
302     gboolean is_mounted = FALSE;
303 
304     if (self->volume)
305     {
306         GMount *mount;
307 
308         display_name = g_volume_get_name (self->volume);
309         mount = g_volume_get_mount (self->volume);
310 
311         if (mount)
312         {
313             is_mounted = TRUE;
314             tip = g_strdup_printf ("%s\n%s", display_name, _("(mounted)"));
315             icon = g_mount_get_icon (mount);
316             g_object_unref (mount);
317         }
318         else
319         {
320             is_mounted = FALSE;
321             tip = g_strdup_printf ("%s\n%s", display_name, _("(not mounted)"));
322             icon = g_volume_get_icon (self->volume);
323         }
324     } else
325     {
326         is_mounted = TRUE;
327         display_name = g_mount_get_name (self->mount);
328         tip = g_strdup_printf ("%s\n%s", display_name, _("(mounted)"));
329         icon = g_mount_get_icon (self->mount);
330     }
331 
332     gtk_widget_set_tooltip_text (GTK_WIDGET (self), tip);
333     g_free (tip);
334     g_free (display_name);
335 
336     screen = gtk_widget_get_screen (GTK_WIDGET (self));
337     icon_theme = gtk_icon_theme_get_for_screen (screen);
338     icon_info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme, icon,
339                                                           MIN (width, height), scale,
340                                                           GTK_ICON_LOOKUP_USE_BUILTIN);
341     if (icon_info)
342     {
343         surface = gtk_icon_info_load_surface (icon_info, NULL, NULL);
344         g_object_unref (icon_info);
345     }
346 
347     g_object_unref (icon);
348 
349     if (!surface)
350         return FALSE;
351 
352     // create a new surface because icon image can be shared by system
353     tmp_surface = cairo_surface_create_similar (surface,
354                                                 cairo_surface_get_content (surface),
355                                                 cairo_image_surface_get_width (surface) / scale,
356                                                 cairo_image_surface_get_height (surface) / scale);
357 
358     // if mounted, change icon
359     if (is_mounted)
360     {
361         int icon_width, icon_height, rowstride, n_channels, x, y;
362         guchar *pixels, *p;
363         gboolean has_alpha;
364 
365         has_alpha = cairo_surface_get_content (tmp_surface) != CAIRO_CONTENT_COLOR;
366         n_channels = 3;
367         if (has_alpha)
368             n_channels++;
369 
370         icon_width = cairo_image_surface_get_width (tmp_surface);
371         icon_height = cairo_image_surface_get_height (tmp_surface);
372 
373         rowstride = cairo_image_surface_get_stride (tmp_surface);
374         pixels = cairo_image_surface_get_data (tmp_surface);
375 
376         GdkRGBA color;
377         GSettings *settings;
378         settings = g_settings_new ("org.mate.drivemount");
379         gchar *color_string = g_settings_get_string (settings, "drivemount-checkmark-color");
380         if (!color_string)
381                 color_string = g_strdup ("#00ff00");
382         gdk_rgba_parse (&color, color_string);
383         g_free (color_string);
384         g_object_unref (settings);
385 
386         guchar red = color.red*255;
387         guchar green = color.green*255;
388         guchar blue = color.blue*255;
389 
390         const gdouble ratio = 0.65;
391         gdouble y_start = icon_height * ratio;
392         gdouble x_start = icon_height * (1 + ratio);
393 
394         for (y = y_start; y < icon_height; y++)
395             for (x = x_start - y; x < icon_width; x++)
396             {
397                 p = pixels + y * rowstride + x * n_channels;
398                 p[0] = red;
399                 p[1] = green;
400                 p[2] = blue;
401                 if (has_alpha)
402                     p[3] = 255;
403             }
404     }
405 
406     cr = cairo_create (tmp_surface);
407     cairo_set_operator (cr, CAIRO_OPERATOR_OVERLAY);
408     cairo_set_source_surface (cr, surface, 0, 0);
409     cairo_paint (cr);
410 
411     gtk_image_set_from_surface (GTK_IMAGE (gtk_bin_get_child (GTK_BIN (self))), tmp_surface);
412 
413     cairo_surface_destroy (surface);
414     cairo_surface_destroy (tmp_surface);
415 
416     gtk_widget_get_preferred_size (GTK_WIDGET (self), NULL, &button_req);
417 
418     return FALSE;
419 }
420 
421 void
drive_button_queue_update(DriveButton * self)422 drive_button_queue_update (DriveButton *self)
423 {
424     if (!self->update_tag) {
425         self->update_tag = g_idle_add (drive_button_update, self);
426     }
427 }
428 
429 void
drive_button_set_size(DriveButton * self,int icon_size)430 drive_button_set_size (DriveButton *self,
431                        int icon_size)
432 {
433     g_return_if_fail (DRIVE_IS_BUTTON (self));
434 
435     if (self->icon_size != icon_size) {
436         self->icon_size = icon_size;
437         drive_button_queue_update (self);
438     }
439 }
440 
441 void
drive_button_redraw(gpointer key,gpointer value,gpointer user_data)442 drive_button_redraw (gpointer key,
443                      gpointer value,
444                      gpointer user_data)
445 {
446     DriveButton *button = value;
447     drive_button_queue_update (button);
448 }
449 
450 int
drive_button_compare(DriveButton * button,DriveButton * other_button)451 drive_button_compare (DriveButton *button,
452                       DriveButton *other_button)
453 {
454     /* sort drives before driveless volumes volumes */
455     if (button->volume) {
456         if (other_button->volume) {
457             int cmp;
458             gchar *str1, *str2;
459 
460             str1 = g_volume_get_name (button->volume);
461             str2 = g_volume_get_name (other_button->volume);
462             cmp = g_utf8_collate (str1, str2);
463             g_free (str2);
464             g_free (str1);
465 
466             return cmp;
467         } else {
468             return -1;
469         }
470     } else {
471         if (other_button->volume) {
472             return 1;
473         } else {
474             int cmp;
475             gchar *str1, *str2;
476 
477             str1 = g_mount_get_name (button->mount);
478             str2 = g_mount_get_name (other_button->mount);
479             cmp = g_utf8_collate (str1, str2);
480             g_free (str2);
481             g_free (str1);
482 
483             return cmp;
484         }
485     }
486 }
487 
488 static void
drive_button_reset_popup(DriveButton * self)489 drive_button_reset_popup (DriveButton *self)
490 {
491     if (self->popup_menu)
492         gtk_widget_destroy (GTK_WIDGET (self->popup_menu));
493     self->popup_menu = NULL;
494 }
495 
496 #if 0
497 static void
498 popup_menu_detach (GtkWidget *attach_widget, GtkMenu *menu)
499 {
500     DRIVE_BUTTON (attach_widget)->popup_menu = NULL;
501 }
502 #endif /* 0 */
503 
504 static char *
escape_underscores(const char * str)505 escape_underscores (const char *str)
506 {
507     char *new_str;
508     int i, j, count;
509 
510     /* count up how many underscores are in the string */
511     count = 0;
512     for (i = 0; str[i] != '\0'; i++) {
513         if (str[i] == '_')
514             count++;
515     }
516     /* copy to new string, doubling up underscores */
517     new_str = g_new (char, i + count + 1);
518     for (i = j = 0; str[i] != '\0'; i++, j++) {
519         new_str[j] = str[i];
520         if (str[i] == '_')
521             new_str[++j] = '_';
522     }
523     new_str[j] = '\0';
524     return new_str;
525 }
526 static GtkWidget *
create_menu_item(DriveButton * self,const gchar * icon_name,const gchar * label,GCallback callback,gboolean sensitive)527 create_menu_item (DriveButton *self,
528                   const gchar *icon_name,
529                   const gchar *label,
530                   GCallback    callback,
531                   gboolean     sensitive)
532 {
533     GtkWidget *item, *image;
534 
535     item = gtk_image_menu_item_new_with_mnemonic (label);
536     if (icon_name) {
537         image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
538         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
539         gtk_widget_show (image);
540     }
541     if (callback)
542         g_signal_connect_object (item, "activate",
543                                  callback, self,
544                                  G_CONNECT_SWAPPED);
545     gtk_widget_set_sensitive (item, sensitive);
546     gtk_widget_show (item);
547     return item;
548 }
549 
550 static void
open_drive(DriveButton * self,GtkWidget * item)551 open_drive (DriveButton *self,
552             GtkWidget   *item)
553 {
554     GdkScreen *screen;
555     GtkWidget *dialog;
556     GError *error = NULL;
557     GFile *file = NULL;
558     GList *files = NULL;
559     GdkAppLaunchContext *launch_context;
560     GAppInfo *app_info;
561 
562     if (self->volume) {
563         GMount *mount;
564 
565         mount = g_volume_get_mount (self->volume);
566         if (mount) {
567             file = g_mount_get_root (mount);
568             g_object_unref (mount);
569         }
570     } else if (self->mount) {
571         file = g_mount_get_root (self->mount);
572     } else
573         g_return_if_reached ();
574 
575     app_info = g_app_info_get_default_for_type ("inode/directory", FALSE);
576     if (!app_info)
577       app_info = G_APP_INFO (g_desktop_app_info_new ("caja.desktop"));
578 
579     if (app_info) {
580         GdkDisplay *display = gtk_widget_get_display (item);
581         launch_context = gdk_display_get_app_launch_context (display);
582         screen = gtk_widget_get_screen (GTK_WIDGET (self));
583         gdk_app_launch_context_set_screen (launch_context, screen);
584         files = g_list_prepend (files, file);
585         g_app_info_launch (app_info,
586         files,
587         G_APP_LAUNCH_CONTEXT (launch_context),
588         &error);
589 
590         g_object_unref (launch_context);
591         g_list_free (files);
592     }
593 
594     if (!app_info || error) {
595         dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))),
596                                                      GTK_DIALOG_DESTROY_WITH_PARENT,
597                                                      GTK_MESSAGE_ERROR,
598                                                      GTK_BUTTONS_OK,
599                                                      _("Cannot execute Caja"));
600         if (error)
601             gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
602         else
603             gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "Could not find Caja");
604 
605         g_signal_connect (dialog, "response",
606                           G_CALLBACK (gtk_widget_destroy),
607                           NULL);
608 
609         gtk_widget_show (dialog);
610         g_error_free (error);
611     }
612 
613     g_object_unref (file);
614 }
615 
616 /* copied from mate-volume-manager/src/manager.c maybe there is a better way than
617  * duplicating this code? */
618 
619 /*
620  * gvm_run_command - run the given command, replacing %d with the device node
621  * and %m with the given path
622  */
623 static void
gvm_run_command(const char * device,const char * command,const char * path)624 gvm_run_command (const char *device,
625                  const char *command,
626                  const char *path)
627 {
628     char *argv[4];
629     gchar *new_command;
630     GError *error = NULL;
631     GString *exec = g_string_new (NULL);
632     char *p, *q;
633 
634     /* perform s/%d/device/ and s/%m/path/ */
635     new_command = g_strdup (command);
636     q = new_command;
637     p = new_command;
638     while ((p = strchr (p, '%')) != NULL) {
639         if (*(p + 1) == 'd') {
640             *p = '\0';
641             g_string_append (exec, q);
642             g_string_append (exec, device);
643             q = p + 2;
644             p = p + 2;
645         } else if (*(p + 1) == 'm') {
646             *p = '\0';
647             g_string_append (exec, q);
648             g_string_append (exec, path);
649             q = p + 2;
650             p = p + 2;
651         } else {
652             /* Ignore anything else. */
653             p++;
654         }
655     }
656     g_string_append (exec, q);
657 
658     argv[0] = "/bin/sh";
659     argv[1] = "-c";
660     argv[2] = exec->str;
661     argv[3] = NULL;
662 
663     g_spawn_async (g_get_home_dir (), argv, NULL, 0, NULL, NULL,
664                    NULL, &error);
665     if (error) {
666         g_warning ("failed to exec %s: %s\n", exec->str, error->message);
667         g_error_free (error);
668     }
669 
670     g_string_free (exec, TRUE);
671     g_free (new_command);
672 }
673 
674 /*
675  * gvm_check_dvd_only - is this a Video DVD?
676  *
677  * Returns TRUE if this was a Video DVD and FALSE otherwise.
678  * (the original in gvm was also running the autoplay action,
679  * I removed that code, so I renamed from gvm_check_dvd to
680  * gvm_check_dvd_only)
681  */
682 static gboolean
gvm_check_dvd_only(const char * udi,const char * device,const char * mount_point)683 gvm_check_dvd_only (const char *udi,
684                     const char *device,
685                     const char *mount_point)
686 {
687     char *path;
688     gboolean retval;
689 
690     path = g_build_path (G_DIR_SEPARATOR_S, mount_point, "video_ts", NULL);
691     retval = g_file_test (path, G_FILE_TEST_IS_DIR);
692     g_free (path);
693 
694     /* try the other name, if needed */
695     if (retval == FALSE) {
696         path = g_build_path (G_DIR_SEPARATOR_S, mount_point,
697                              "VIDEO_TS", NULL);
698         retval = g_file_test (path, G_FILE_TEST_IS_DIR);
699         g_free (path);
700     }
701 
702     return retval;
703 }
704 /* END copied from mate-volume-manager/src/manager.c */
705 
706 static gboolean
check_dvd_video(DriveButton * self)707 check_dvd_video (DriveButton *self)
708 {
709     GFile *file;
710     char *udi, *device_path, *mount_path;
711     gboolean result;
712     GMount *mount;
713 
714     if (!self->volume)
715         return FALSE;
716 
717     mount = g_volume_get_mount (self->volume);
718     if (!mount)
719         return FALSE;
720 
721     file = g_mount_get_root (mount);
722     g_object_unref (mount);
723 
724     if (!file)
725         return FALSE;
726 
727     mount_path = g_file_get_path (file);
728 
729     g_object_unref (file);
730 
731     device_path = g_volume_get_identifier (self->volume,
732                                            G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
733     udi = g_volume_get_identifier (self->volume,
734                                    G_VOLUME_IDENTIFIER_KIND_HAL_UDI);
735 
736     result = gvm_check_dvd_only (udi, device_path, mount_path);
737 
738     g_free (device_path);
739     g_free (udi);
740     g_free (mount_path);
741 
742     return result;
743 }
744 
745 static gboolean
check_audio_cd(DriveButton * self)746 check_audio_cd (DriveButton *self)
747 {
748     GFile *file;
749     char *activation_uri;
750     GMount *mount;
751 
752     if (!self->volume)
753         return FALSE;
754 
755     mount = g_volume_get_mount (self->volume);
756     if (!mount)
757         return FALSE;
758 
759     file = g_mount_get_root (mount);
760     g_object_unref (mount);
761 
762     if (!file)
763         return FALSE;
764 
765     activation_uri = g_file_get_uri (file);
766 
767     g_object_unref (file);
768 
769     /* we have an audioCD if the activation URI starts by 'cdda://' */
770     gboolean result = (strncmp ("cdda://", activation_uri, 7) == 0);
771     g_free (activation_uri);
772     return result;
773 }
774 
775 static void
run_command(DriveButton * self,const char * command)776 run_command (DriveButton *self,
777              const char  *command)
778 {
779     GFile *file;
780     char *mount_path, *device_path;
781     GMount *mount;
782 
783     if (!self->volume)
784         return;
785 
786     mount = g_volume_get_mount (self->volume);
787     if (!mount)
788         return;
789 
790     file = g_mount_get_root (mount);
791     g_object_unref (mount);
792 
793     g_assert (file);
794 
795     mount_path = g_file_get_path (file);
796 
797     g_object_unref (file);
798 
799     device_path = g_volume_get_identifier (self->volume,
800                                            G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
801 
802     gvm_run_command (device_path, command, mount_path);
803 
804     g_free (mount_path);
805     g_free (device_path);
806 }
807 
dummy_async_ready_callback(GObject * source_object,GAsyncResult * res,gpointer user_data)808 static void dummy_async_ready_callback (GObject      *source_object,
809                                         GAsyncResult *res,
810                                         gpointer      user_data)
811 {
812     /* do nothing */
813 }
814 
815 static void
mount_drive(DriveButton * self,GtkWidget * item)816 mount_drive (DriveButton *self,
817              GtkWidget   *item)
818 {
819     if (self->volume) {
820         GMountOperation *mount_op = gtk_mount_operation_new (NULL);
821         g_volume_mount (self->volume, G_MOUNT_MOUNT_NONE,
822                         mount_op, NULL, dummy_async_ready_callback, NULL);
823         g_object_unref (mount_op);
824     } else {
825         g_return_if_reached ();
826     }
827 }
828 
829 static void
unmount_drive(DriveButton * self,GtkWidget * item)830 unmount_drive (DriveButton *self,
831                GtkWidget   *item)
832 {
833     if (self->volume) {
834         GMount *mount;
835 
836         mount = g_volume_get_mount (self->volume);
837         if (mount) {
838             g_mount_unmount_with_operation (mount, G_MOUNT_UNMOUNT_NONE,
839                                             NULL, NULL, dummy_async_ready_callback, NULL);
840             g_object_unref (mount);
841         }
842     } else if (self->mount) {
843         g_mount_unmount_with_operation (self->mount, G_MOUNT_UNMOUNT_NONE,
844                                         NULL, NULL, dummy_async_ready_callback, NULL);
845     } else {
846         g_return_if_reached ();
847     }
848 }
849 
eject_finish(DriveButton * self,GAsyncResult * res,gpointer user_data)850 static void eject_finish (DriveButton  *self,
851                           GAsyncResult *res,
852                           gpointer      user_data)
853 {
854     /* Do nothing. We shouldn't need this according to the GIO
855      * docs, but the applet crashes without it using glib 2.18.0 */
856 }
857 
858 static void
eject_drive(DriveButton * self,GtkWidget * item)859 eject_drive (DriveButton *self,
860              GtkWidget   *item)
861 {
862     if (self->volume) {
863         g_volume_eject_with_operation (self->volume, G_MOUNT_UNMOUNT_NONE,
864                                        NULL, NULL,
865                                        (GAsyncReadyCallback) eject_finish,
866                                        NULL);
867     } else if (self->mount) {
868         g_mount_eject_with_operation (self->mount, G_MOUNT_UNMOUNT_NONE,
869                                       NULL, NULL,
870                                       (GAsyncReadyCallback) eject_finish,
871                                       NULL);
872     } else {
873         g_return_if_reached ();
874     }
875 }
876 static void
play_autoplay_media(DriveButton * self,const char * dflt)877 play_autoplay_media (DriveButton *self,
878                      const char  *dflt)
879 {
880     run_command (self, dflt);
881 }
882 
883 static void
play_dvd(DriveButton * self,GtkWidget * item)884 play_dvd (DriveButton *self,
885           GtkWidget   *item)
886 {
887     /* FIXME add an option to set this */
888     play_autoplay_media (self, "totem %d");
889 }
890 
891 static void
play_cda(DriveButton * self,GtkWidget * item)892 play_cda (DriveButton *self,
893           GtkWidget   *item)
894 {
895     /* FIXME add an option to set this */
896     play_autoplay_media (self, "sound-juicer -d %d");
897 }
898 
899 static void
drive_button_ensure_popup(DriveButton * self)900 drive_button_ensure_popup (DriveButton *self)
901 {
902     char *display_name, *tmp, *label;
903     GtkWidget *item;
904     gboolean mounted, ejectable;
905 
906     if (self->popup_menu) return;
907 
908     mounted = FALSE;
909 
910     if (self->volume) {
911         GMount *mount = NULL;
912 
913         display_name = g_volume_get_name (self->volume);
914         ejectable = g_volume_can_eject (self->volume);
915 
916         mount = g_volume_get_mount (self->volume);
917         if (mount) {
918             mounted = TRUE;
919             g_object_unref (mount);
920         }
921     } else {
922         if (!G_IS_MOUNT (self->volume))
923             return;
924         else {
925             display_name = g_mount_get_name (self->mount);
926             ejectable = g_mount_can_eject (self->mount);
927             mounted = TRUE;
928         }
929     }
930 
931     self->popup_menu = gtk_menu_new ();
932 
933     /* make sure the display name doesn't look like a mnemonic */
934     tmp = escape_underscores (display_name ? display_name : "(none)");
935     g_free (display_name);
936     display_name = tmp;
937 
938     if (check_dvd_video (self)) {
939         item = create_menu_item (self, "media-playback-start",
940                                  _("_Play DVD"), G_CALLBACK (play_dvd),
941                                  TRUE);
942     } else if (check_audio_cd (self)) {
943         item = create_menu_item (self, "media-playback-start",
944                                  _("_Play CD"), G_CALLBACK (play_cda),
945                                  TRUE);
946     } else {
947         label = g_strdup_printf (_("_Open %s"), display_name);
948         item = create_menu_item (self, "document-open", label,
949                                  G_CALLBACK (open_drive), mounted);
950         g_free (label);
951     }
952     gtk_container_add (GTK_CONTAINER (self->popup_menu), item);
953 
954     if (mounted) {
955         label = g_strdup_printf (_("Un_mount %s"), display_name);
956         item = create_menu_item (self, NULL, label,
957                                  G_CALLBACK (unmount_drive), TRUE);
958         g_free (label);
959         gtk_container_add (GTK_CONTAINER (self->popup_menu), item);
960     } else {
961         label = g_strdup_printf (_("_Mount %s"), display_name);
962         item = create_menu_item (self, NULL, label,
963                                  G_CALLBACK (mount_drive), TRUE);
964         g_free (label);
965         gtk_container_add (GTK_CONTAINER (self->popup_menu), item);
966     }
967 
968     if (ejectable) {
969         label = g_strdup_printf (_("_Eject %s"), display_name);
970         item = create_menu_item (self, "media-eject", label,
971                                  G_CALLBACK (eject_drive), TRUE);
972         g_free (label);
973         gtk_container_add (GTK_CONTAINER (self->popup_menu), item);
974     }
975 
976     /*Set up custom theme and transparency support */
977     GtkWidget *toplevel = gtk_widget_get_toplevel (self->popup_menu);
978     /* Fix any failures of compiz/other wm's to communicate with gtk for transparency */
979     GdkScreen *screen2 = gtk_widget_get_screen (GTK_WIDGET (toplevel));
980     GdkVisual *visual = gdk_screen_get_rgba_visual (screen2);
981     gtk_widget_set_visual (GTK_WIDGET (toplevel), visual);
982     /*set menu and it's toplevel window to follow panel theme */
983     GtkStyleContext *context;
984     context = gtk_widget_get_style_context (GTK_WIDGET (toplevel));
985     gtk_style_context_add_class (context,"gnome-panel-menu-bar");
986     gtk_style_context_add_class (context,"mate-panel-menu-bar");
987 }
988