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