Move device list refresh to a separate function
[pmount-gui.git] / main.c
1 /* Required for strdup, snprintf, getopt */
2 #define _XOPEN_SOURCE 500
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <fcntl.h>
8 #include <dirent.h>
9 #include <mntent.h>
10 #include <errno.h>
11 #include <sys/stat.h>
12 #include <sys/wait.h>
13 #include <sys/select.h>
14 #include <gtk/gtk.h>
15 #include <gdk/gdkkeysyms.h>
16
17 typedef struct sProperty
18 {
19         char *name;
20         char *value;
21 } Property;
22
23 typedef struct sDevice
24 {
25         char *node;
26         char *devname;
27         char *label;
28         char *description;
29         char *mount_point;
30         time_t time;
31 } Device;
32
33 typedef struct sGuiContext
34 {
35         int manager;
36         Device *devices;
37         GtkWidget *list;
38         GtkWidget *button;
39         char *post_mount_command;
40 } GuiContext;
41
42 int verbosity = 0;
43
44 /**
45 Parses a string of the form name=value and places the components in a Property
46 structure.  Returns 0 on success, or -1 if the string wasn't a valid property.
47 */
48 int parse_property(char *str, int size, Property *prop)
49 {
50         int equals = -1;
51         int i;
52
53         for(i=0; (equals<0 && i<size); ++i)
54                 if(str[i]=='=')
55                         equals = i;
56
57         if(equals<0)
58                 return -1;
59
60         prop->name = malloc(equals+1);
61         strncpy(prop->name, str, equals);
62         prop->name[equals] = 0;
63
64         prop->value = malloc(size-equals);
65         strncpy(prop->value, str+equals+1, size-equals-1);
66         prop->value[size-equals-1] = 0;
67
68         return 0;
69 }
70
71 /**
72 Retrieves all properties associated with a /dev node.  The returned array is
73 terminated with an entry containing NULL values.  Use free_properties to free
74 the array.
75 */
76 Property *get_device_properties(char *node)
77 {
78         int pid;
79         int pipe_fd[2];
80         char *buf;
81         int bufsize;
82         int pos = 0;
83         int eof = 0;
84         Property *props = NULL;
85         int n_props = 0;
86
87         pipe(pipe_fd);
88
89         pid = fork();
90         if(pid==0)
91         {
92                 /* Child process */
93                 if(verbosity>=2)
94                         printf("Running udevadm info -q property -n \"%s\"\n", node);
95
96                 close(pipe_fd[0]);
97                 dup2(pipe_fd[1], 1);
98
99                 execl("/sbin/udevadm", "udevadm", "info", "-q", "property", "-n", node, NULL);
100                 _exit(1);
101         }
102         else if(pid<0)
103         {
104                 close(pipe_fd[0]);
105                 close(pipe_fd[1]);
106
107                 return NULL;
108         }
109
110         /* Parent process */
111         close(pipe_fd[1]);
112
113         bufsize = 256;
114         buf = (char *)malloc(bufsize);
115
116         while(1)
117         {
118                 int newline;
119                 int i;
120                 Property prop;
121
122                 if(!eof)
123                 {
124                         int len;
125
126                         len = read(pipe_fd[0], buf+pos, bufsize-pos);
127                         if(len==0)
128                                 eof = 1;
129                         else if(len==-1)
130                                 break;
131                         pos += len;
132                 }
133
134                 newline = -1;
135                 for(i=0; (newline<0 && i<pos); ++i)
136                         if(buf[i]=='\n')
137                                 newline = i;
138
139                 if(newline<0)
140                 {
141                         if(eof)
142                                 break;
143
144                         /* There was no newline in the buffer but there is more output to
145                         be read.  Try again with a larger buffer. */
146                         bufsize *= 2;
147                         buf = (char *)realloc(buf, bufsize);
148                         continue;
149                 }
150
151                 if(parse_property(buf, newline, &prop)==0)
152                 {
153                         /* Reserve space for a sentinel value as well. */
154                         props = (Property *)realloc(props, (n_props+2)*sizeof(Property));
155                         props[n_props] = prop;
156                         ++n_props;
157
158                         memmove(buf, buf+newline+1, pos-newline-1);
159                         pos -= newline+1;
160                 }
161                 else
162                         break;
163         }
164
165         free(buf);
166
167         if(props)
168         {
169                 /* Terminate the array with NULL pointers. */
170                 props[n_props].name = NULL;
171                 props[n_props].value = NULL;
172         }
173
174         waitpid(pid, NULL, 0);
175         close(pipe_fd[0]);
176
177         return props;
178 }
179
180 /**
181 Looks for a property in an array of properties and returns its value.  Returns
182 NULL if the property was not found.
183 */
184 char *get_property_value(Property *props, char *name)
185 {
186         int i;
187         for(i=0; props[i].name; ++i)
188                 if(strcmp(props[i].name, name)==0)
189                         return props[i].value;
190         return NULL;
191 }
192
193 /**
194 Checks if a property has a specific value.  A NULL value is matched if the
195 property does not exist.
196 */
197 int match_property_value(Property *props, char *name, char *value)
198 {
199         char *v = get_property_value(props, name);
200         if(!v)
201                 return value==NULL;
202         return strcmp(v, value)==0;
203 }
204
205 /**
206 Frees an array of properties and all strings contained in it.
207 */
208 void free_properties(Property *props)
209 {
210         int i;
211         if(!props)
212                 return;
213         for(i=0; props[i].name; ++i)
214         {
215                 free(props[i].name);
216                 free(props[i].value);
217         }
218         free(props);
219 }
220
221 /**
222 Returns an array of user-mountable devices listed in fstab.
223 */
224 char **get_fstab_devices(void)
225 {
226         FILE *file;
227         struct mntent *me;
228         char **devices = NULL;
229         int n_devices = 0;
230
231         file = setmntent("/etc/fstab", "r");
232         if(!file)
233                 return NULL;
234
235         while((me = getmntent(file)))
236                 if(hasmntopt(me, "user")!=NULL)
237                 {
238                         devices = (char **)realloc(devices, (n_devices+2)*sizeof(char *));
239                         devices[n_devices] = strdup(me->mnt_fsname);
240                         ++n_devices;
241                 }
242
243         endmntent(file);
244         if(devices)
245                 devices[n_devices] = NULL;
246
247         return devices;
248 }
249
250 /**
251 Checks if an array of strings contains the specified string.
252 */
253 int is_in_array(char **array, char *str)
254 {
255         int i;
256         if(!array || !str)
257                 return 0;
258         for(i=0; array[i]; ++i)
259                 if(!strcmp(str, array[i]))
260                         return 1;
261         return 0;
262 }
263
264 /**
265 Frees an array of strings.
266 */
267 void free_string_array(char **array)
268 {
269         int i;
270         if(!array)
271                 return;
272         for(i=0; array[i]; ++i)
273                 free(array[i]);
274         free(array);
275 }
276
277 /**
278 Checks if a partition identified by a sysfs path is on a removable device.
279 */
280 int is_removable(char *devpath)
281 {
282         char fnbuf[256];
283         int len;
284         char *ptr;
285         int fd;
286
287         len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
288         /* Default to not removable if the path was too long. */
289         if(len+10>=(int)sizeof(fnbuf))
290                 return 0;
291
292         /* We got a partition as a parameter, but the removable property is on the
293         disk.  Replace the last component with "removable". */
294         for(ptr=fnbuf+len; (ptr>fnbuf && *ptr!='/'); --ptr) ;
295         strcpy(ptr, "/removable");
296
297         fd = open(fnbuf, O_RDONLY);
298         if(fd!=-1)
299         {
300                 char c;
301                 read(fd, &c, 1);
302                 close(fd);
303                 if(c=='1')
304                 {
305                         if(verbosity>=2)
306                                 printf("  Removable\n");
307                         return 1;
308                 }
309                 if(verbosity>=2)
310                         printf("  Not removable\n");
311         }
312
313         return 0;
314 }
315
316 /**
317 Checks if a partition's disk or any of its parent devices are connected to any
318 of a set of buses.  The device is identified by a sysfs path.  The bus array
319 must be terminated with a NULL entry.
320 */
321 int check_buses(char *devpath, char **buses)
322 {
323         char fnbuf[256];
324         char *ptr;
325         int len;
326
327         len = snprintf(fnbuf, sizeof(fnbuf), "/sys%s", devpath);
328         /* Default to no match if the path was too long. */
329         if(len+10>=(int)sizeof(fnbuf))
330                 return 0;
331
332         for(ptr=fnbuf+len; ptr>fnbuf+12; --ptr)
333                 if(*ptr=='/')
334                 {
335                         char linkbuf[256];
336                         /* Replace the last component with "subsystem". */
337                         strcpy(ptr, "/subsystem");
338                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
339
340                         if(len!=-1)
341                         {
342                                 linkbuf[len] = 0;
343                                 /* Extract the last component of the subsystem symlink. */
344                                 for(; (len>0 && linkbuf[len-1]!='/'); --len) ;
345
346                                 if(verbosity>=2)
347                                 {
348                                         *ptr = 0;
349                                         printf("  Subsystem of %s is %s\n", fnbuf, linkbuf+len);
350                                 }
351
352                                 if(is_in_array(buses, linkbuf+len))
353                                         return 1;
354                         }
355                 }
356
357         return 0;
358 }
359
360 /**
361 Check if an array of properties describes a device that can be mounted.  An
362 array of explicitly allowed devices can be passed in as well.  Both arrays must
363 be terminated by a NULL entry.
364 */
365 int can_mount(Property *props, char **allowed)
366 {
367         static char *removable_buses[] = { "usb", "firewire", 0 };
368         char *devname;
369         char *devpath;
370         char *bus;
371
372         devname = get_property_value(props, "DEVNAME");
373         if(is_in_array(allowed, devname))
374                 return 1;
375
376         /* Special case for CD devices, since they are not partitions.  Only allow
377         mounting if media is inserted. */
378         if(match_property_value(props, "ID_TYPE", "cd") && match_property_value(props, "ID_CDROM_MEDIA", "1"))
379                 return 1;
380
381         /* Only allow mounting partitions. */
382         if(!match_property_value(props, "DEVTYPE", "partition"))
383                 return 0;
384
385         devpath = get_property_value(props, "DEVPATH");
386         if(is_removable(devpath))
387                 return 1;
388
389         /* Certain buses are removable by nature, but devices only advertise
390         themselves as removable if they support removable media, e.g. memory card
391         readers. */
392         bus = get_property_value(props, "ID_BUS");
393         if(is_in_array(removable_buses, bus))
394                 return 1;
395
396         return check_buses(devpath, removable_buses);
397 }
398
399 /**
400 Returns an array of all device nodes in a directory.  Symbolic links are
401 dereferenced.
402 */
403 char **get_device_nodes(char *dirname)
404 {
405         DIR *dir;
406         struct dirent *de;
407         char fnbuf[256];
408         char linkbuf[256];
409         struct stat st;
410         char **nodes = NULL;
411         int n_nodes = 0;
412         char **checked = NULL;
413         int n_checked = 0;
414         int i;
415
416         dir = opendir(dirname);
417         if(!dir)
418                 return NULL;
419
420         while((de = readdir(dir)))
421         {
422                 char *node;
423                 int duplicate = 0;
424
425                 /* Ignore . and .. entries. */
426                 if(de->d_name[0]=='.' && (de->d_name[1]==0 || (de->d_name[1]=='.' && de->d_name[2]==0)))
427                         continue;
428
429                 snprintf(fnbuf, sizeof(fnbuf), "%s/%s", dirname, de->d_name);
430
431                 node = fnbuf;
432                 lstat(fnbuf, &st);
433                 if(S_ISLNK(st.st_mode))
434                 {
435                         int len;
436                         len = readlink(fnbuf, linkbuf, sizeof(linkbuf)-1);
437                         if(len!=-1)
438                         {
439                                 linkbuf[len] = 0;
440                                 node = linkbuf;
441                         }
442                 }
443
444                 /* There may be multiple symlinks to the same device.  Only include each
445                 device once in the returned array. */
446                 if(checked)
447                 {
448                         for(i=0; (!duplicate && i<n_checked); ++i)
449                                 if(strcmp(node, checked[i])==0)
450                                         duplicate = 1;
451                 }
452                 if(duplicate)
453                 {
454                         if(verbosity>=2)
455                                 printf("Device %s is a duplicate\n", fnbuf);
456                         continue;
457                 }
458
459                 checked = (char **)realloc(checked, (n_checked+1)*sizeof(char *));
460                 checked[n_checked] = strdup(node);
461                 ++n_checked;
462
463                 nodes = (char **)realloc(nodes, (n_nodes+2)*sizeof(char *));
464                 nodes[n_nodes] = strdup(fnbuf);
465                 ++n_nodes;
466         }
467
468         closedir(dir);
469         if(checked)
470         {
471                 for(i=0; i<n_checked; ++i)
472                         free(checked[i]);
473                 free(checked);
474         }
475
476         if(nodes)
477                 nodes[n_nodes] = NULL;
478
479         return nodes;
480 }
481
482 /**
483 Reads the list of mounted devices from /etc/mtab and records the mount points.
484 */
485 void check_mounts(Device *devices)
486 {
487         FILE *file;
488         struct mntent *me;
489
490         file = setmntent("/etc/mtab", "r");
491         if(!file)
492                 return;
493
494         while((me = getmntent(file)))
495         {
496                 int i;
497
498                 for(i=0; devices[i].node; ++i)
499                         if(!strcmp(devices[i].devname, me->mnt_fsname))
500                         {
501                                 devices[i].mount_point = strdup(me->mnt_dir);
502
503                                 if(verbosity>=1)
504                                         printf("Device %s is mounted on %s\n", devices[i].node, devices[i].mount_point);
505                         }
506         }
507
508         endmntent(file);
509 }
510
511 /**
512 Returns an array of all mountable devices.
513 */
514 Device *get_devices(void)
515 {
516         char **nodes = NULL;
517         Device *devices = NULL;
518         int n_devices = 0;
519         char **mounted = NULL;
520         char **fstab = NULL;
521         int i;
522
523         nodes = get_device_nodes("/dev/disk/by-id");
524         fstab = get_fstab_devices();
525
526         for(i=0; nodes[i]; ++i)
527         {
528                 Property *props;
529
530                 if(verbosity>=1)
531                         printf("Examining device %s\n", nodes[i]);
532
533                 props = get_device_properties(nodes[i]);
534                 if(!props)
535                 {
536                         if(verbosity>=2)
537                                 printf("  No properties\n");
538                         continue;
539                 }
540
541                 if(verbosity>=2)
542                 {
543                         int j;
544                         for(j=0; props[j].name; ++j)
545                                 printf("  %s = %s\n", props[j].name, props[j].value);
546                 }
547
548                 if(can_mount(props, fstab))
549                 {
550                         char *devname;
551                         char *label;
552                         char *vendor;
553                         char *model;
554                         char buf[256];
555                         char pos;
556                         struct stat st;
557
558                         if(verbosity>=1)
559                                 printf("  Using device\n");
560
561                         devname = get_property_value(props, "DEVNAME");
562
563                         /* Get a human-readable label for the device.  Use filesystem label,
564                         filesystem UUID or device node name in order of preference. */
565                         label = get_property_value(props, "ID_FS_LABEL");
566                         if(!label)
567                                 label = get_property_value(props, "ID_FS_UUID");
568                         if(!label)
569                         {
570                                 char *ptr;
571
572                                 label = devname;
573                                 for(ptr=label; *ptr; ++ptr)
574                                         if(*ptr=='/')
575                                                 label = ptr+1;
576                         }
577
578                         vendor = get_property_value(props, "ID_VENDOR");
579                         model = get_property_value(props, "ID_MODEL");
580
581                         pos = snprintf(buf, sizeof(buf), "%s", label);
582                         if(vendor && model)
583                                 pos += snprintf(buf+pos, sizeof(buf)-pos, " (%s %s)", vendor, model);
584
585                         stat(nodes[i], &st);
586
587                         /* Reserve space for a sentinel entry. */
588                         devices = (Device *)realloc(devices, (n_devices+2)*sizeof(Device));
589                         devices[n_devices].node = nodes[i];
590                         devices[n_devices].devname = strdup(devname);
591                         devices[n_devices].label = strdup(label);
592                         devices[n_devices].description = strdup(buf);
593                         devices[n_devices].mount_point = NULL;
594                         devices[n_devices].time = st.st_mtime;
595                         ++n_devices;
596                 }
597                 else
598                         free(nodes[i]);
599                 free_properties(props);
600         }
601
602         free(nodes);
603         free_string_array(mounted);
604
605         if(devices)
606         {
607                 /* Terminate the array with NULL pointers. */
608                 devices[n_devices].node = NULL;
609                 devices[n_devices].devname = NULL;
610                 devices[n_devices].label = NULL;
611                 devices[n_devices].description = NULL;
612                 devices[n_devices].mount_point = NULL;
613
614                 check_mounts(devices);
615         }
616
617         return devices;
618 }
619
620 /**
621 Frees an array of devices and all strings contained in it.
622 */
623 void free_devices(Device *devices)
624 {
625         int i;
626         if(!devices)
627                 return;
628         for(i=0; devices[i].node; ++i)
629         {
630                 free(devices[i].node);
631                 free(devices[i].devname);
632                 free(devices[i].label);
633                 free(devices[i].description);
634                 if(devices[i].mount_point)
635                         free(devices[i].mount_point);
636         }
637         free(devices);
638 }
639
640 /**
641 Mounts a device if it was not mounted, or unmounts if it was.
642 */
643 int toggle_device(Device *device, char *out_buf, int out_size)
644 {
645         int umount = !!device->mount_point;
646         char mount_point[1024];
647         int pos = 0;
648         int status = 0;
649         fd_set fds;
650         struct timeval timeout;
651         int pid;
652         int pipe_fd[2];
653         char suffix = 0;
654
655         out_buf[0] = 0;
656
657         /* Find a mount point that does not exist yet. */
658         while(1)
659         {
660                 int len;
661
662                 len = snprintf(mount_point, sizeof(mount_point), "/media/%s", device->label);
663                 if(len+2>=(int)sizeof(mount_point))
664                         return -1;
665
666                 if(suffix)
667                         len += snprintf(mount_point+len, sizeof(mount_point)-len, "_%c", suffix);
668
669                 if(access(mount_point, F_OK)<0 && errno==ENOENT)
670                         break;
671
672                 if(suffix==0)
673                         suffix = '1';
674                 else if(suffix<'9')
675                         ++suffix;
676                 else
677                         return -1;
678         }
679
680         pipe(pipe_fd);
681
682         pid = fork();
683         if(pid==0)
684         {
685                 /* Child process */
686                 if(verbosity>=1)
687                 {
688                         if(umount)
689                                 printf("Running pumount %s\n", device->node);
690                         else
691                                 printf("Running pmount %s %s\n", device->node, mount_point+7);
692                 }
693
694                 close(pipe_fd[0]);
695                 dup2(pipe_fd[1], 1);
696                 dup2(pipe_fd[1], 2);
697
698                 if(umount)
699                         execl("/usr/bin/pumount", "pumount", device->node, NULL);
700                 else
701                         execl("/usr/bin/pmount", "pmount", device->node, mount_point+7, NULL);
702                 _exit(1);
703         }
704         else if(pid<0)
705                 return -1;
706
707         /* Parent process */
708
709         close(pipe_fd[1]);
710         FD_ZERO(&fds);
711         FD_SET(pipe_fd[0], &fds);
712         timeout.tv_sec = 0;
713         timeout.tv_usec = 200000;
714
715         while(1)
716         {
717                 /* The write fd for the pipe may be inherited by a fuse server
718                 process and stay open indefinitely. */
719                 if(select(pipe_fd[0]+1, &fds, NULL, NULL, &timeout))
720                 {
721                         int len;
722
723                         len = read(pipe_fd[0], out_buf+pos, out_size-pos-1);
724                         if(len<=0)
725                                 break;
726                         pos += len;
727                 }
728                 else if(waitpid(pid, &status, 0))
729                 {
730                         pid = 0;
731                         break;
732                 }
733         }
734
735         close(pipe_fd[0]);
736         if(pid)
737                 waitpid(pid, &status, 0);
738
739         out_buf[pos] = 0;
740
741         if(verbosity>=1)
742         {
743                 if(WIFEXITED(status))
744                 {
745                         if(WEXITSTATUS(status))
746                                 printf("Command exited successfully\n");
747                         else
748                                 printf("Command exited with status %d\n", WEXITSTATUS(status));
749                 }
750                 else if(WIFSIGNALED(status))
751                         printf("Command terminated with signal %d\n", WTERMSIG(status));
752                 else
753                         printf("Command exited with unknown result %04X\n", status);
754         }
755
756         if(!WIFEXITED(status) || WEXITSTATUS(status))
757                 return -1;
758
759         if(umount)
760         {
761                 free(device->mount_point);
762                 device->mount_point = NULL;
763         }
764         else
765                 device->mount_point = strdup(mount_point);
766
767         return 0;
768 }
769
770 /**
771 Refreshes both the internal devices array and the GUI list.
772 */
773 int refresh_devices(GuiContext *context, int umount)
774 {
775         GtkListStore *store;
776         GtkTreeSelection *selection;
777         int n_listed;
778         time_t latest;
779         int i;
780         GtkTreeIter iter;
781
782         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(context->list)));
783         gtk_list_store_clear(store);
784
785         free_devices(context->devices);
786
787         context->devices = get_devices();
788         if(!context->devices)
789                 return 0;
790
791         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(context->list));
792
793         /* Populate the list with devices in appropriate state. */
794         n_listed = 0;
795         latest = 0;
796         for(i=0; context->devices[i].node; ++i)
797         {
798                 Device *dev;
799
800                 dev = &context->devices[i];
801                 if(umount<0 || !dev->mount_point==!umount)
802                 {
803                         gtk_list_store_append(store, &iter);
804                         gtk_list_store_set(store, &iter,
805                                 0, dev->description,
806                                 1, dev,
807                                 2, !!dev->mount_point,
808                                 3, dev->mount_point,
809                                 -1);
810                         if(dev->time>latest)
811                         {
812                                 /* Pre-select the device that appeared on the system most recently. */
813                                 latest = dev->time;
814                                 gtk_tree_selection_select_iter(selection, &iter);
815                         }
816
817                         ++n_listed;
818                 }
819         }
820
821         return n_listed;
822 }
823
824 /**
825 Callback for selection in the device list changing.  Updates the action button
826 label according to device status.
827 */
828 void selection_changed(GtkTreeSelection *selection, gpointer user_data)
829 {
830         GuiContext *context = (GuiContext *)user_data;
831         GtkTreeIter iter;
832         GtkTreeModel *model;
833         Device *device;
834
835         if(!gtk_tree_selection_get_selected(selection, &model, &iter))
836                 return;
837
838         gtk_tree_model_get(model, &iter, 1, &device, -1);
839         gtk_button_set_label(GTK_BUTTON(context->button), (device->mount_point ? "Unmount" : "Mount"));
840 }
841
842 /**
843 Callback for activating a row in the device list.  Mounts or unmounts the
844 device depending on operating mode.
845 */
846 void row_activated(GtkTreeView *list, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
847 {
848         GuiContext *context = (GuiContext *)user_data;
849         GtkTreeModel *model;
850         GtkTreeIter iter;
851         Device *device;
852         int ret;
853         char output[1024];
854         int pid;
855
856         model = gtk_tree_view_get_model(list);
857
858         if(!gtk_tree_model_get_iter(model, &iter, path))
859                 return;
860
861         gtk_tree_model_get(model, &iter, 1, &device, -1);
862         ret = toggle_device(device, output, sizeof(output));
863         if(ret)
864         {
865                 GtkWidget *dialog;
866
867                 /* Pmount terminated with nonzero status or a signal.  Display an
868                 error to the user. */
869                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", output);
870                 if(context->manager)
871                         g_signal_connect(dialog, "response", G_CALLBACK(&gtk_widget_destroy), dialog);
872                 else
873                         g_signal_connect(dialog, "response", G_CALLBACK(&gtk_main_quit), NULL);
874                 gtk_widget_show_all(dialog);
875                 return;
876         }
877
878         if(context->manager)
879         {
880                 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
881                         2, !!device->mount_point,
882                         3, device->mount_point,
883                         -1);
884
885                 gtk_button_set_label(GTK_BUTTON(context->button), (device->mount_point ? "Unmount" : "Mount"));
886         }
887         else
888                 gtk_main_quit();
889
890         if(context->post_mount_command && device->mount_point)
891         {
892                 if(verbosity>=1)
893                         printf("Running %s in %s\n", context->post_mount_command, device->mount_point);
894
895                 pid = fork();
896                 if(pid==0)
897                 {
898                         chdir(device->mount_point);
899                         execlp(context->post_mount_command, context->post_mount_command, NULL);
900                         _exit(1);
901                 }
902         }
903
904         (void)column;
905         (void)user_data;
906 }
907
908 /**
909 Callback for the mount/unmount button.  Causes the selected row in the device
910 list to be activated.
911 */
912 void button_clicked(GtkButton *button, gpointer user_data)
913 {
914         GuiContext *context = (GuiContext *)user_data;
915         GtkTreeSelection *selection;
916         GtkTreeIter iter;
917         GtkTreeModel *model;
918         GtkTreePath *path;
919         GtkTreeViewColumn *column;
920
921         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(context->list));
922         gtk_tree_selection_get_selected(selection, &model, &iter);
923         path = gtk_tree_model_get_path(model, &iter);
924         column = gtk_tree_view_get_column(GTK_TREE_VIEW(context->list), 0);
925         gtk_tree_view_row_activated(GTK_TREE_VIEW(context->list), path, column);
926         gtk_tree_path_free(path);
927
928         (void)button;
929 }
930
931 /**
932 Global key press callback for the window.
933 */
934 gboolean key_press(GtkWidget *widget, GdkEvent *event, gpointer user_data)
935 {
936         if(event->key.keyval==GDK_KEY_Escape)
937         {
938                 gtk_main_quit();
939                 return TRUE;
940         }
941
942         (void)widget;
943         (void)user_data;
944
945         return FALSE;
946 }
947
948 void show_help(void)
949 {
950         printf("pmount-gui\n"
951                 "Copyright (c) 2011-2015 Mikko Rasa, Mikkosoft Productions\n\n"
952                 "Usage: pmount-gui [-v] [-u] [-r <command>] [-h]\n\n"
953                 "Options:\n"
954                 "  -v  Increase verbosity\n"
955                 "  -u  Unmount a device (default is mount)\n"
956                 "  -r  Run a command after mounting\n"
957                 "  -m  Start a persistent mount manager\n"
958                 "  -h  Display this help\n");
959 }
960
961 int main(int argc, char **argv)
962 {
963         GuiContext context;
964         GtkWidget *window;
965         GtkWidget *box;
966         GtkWidget *viewport;
967         GtkListStore *store;
968         GtkTreeSelection *selection;
969         GtkCellRenderer *mounted_toggle;
970         int opt;
971         int umount = 0;
972         int n_listed;
973
974         context.manager = 0;
975         context.devices = NULL;
976         context.post_mount_command = NULL;
977
978         gtk_init(&argc, &argv);
979
980         while((opt = getopt(argc, argv, "vur:mh"))!=-1) switch(opt)
981         {
982         case 'v':
983                 ++verbosity;
984                 break;
985         case 'u':
986                 umount = 1;
987                 break;
988         case 'r':
989                 context.post_mount_command = optarg;
990                 break;
991         case 'm':
992                 context.manager = 1;
993                 break;
994         case 'h':
995                 show_help();
996                 return 0;
997         }
998
999         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1000         gtk_container_set_border_width(GTK_CONTAINER(window), 5);
1001         g_signal_connect(window, "destroy", G_CALLBACK(&gtk_main_quit), NULL);
1002         g_signal_connect(window, "key-press-event", G_CALLBACK(&key_press), NULL);
1003
1004         box = gtk_vbox_new(FALSE, 5);
1005         gtk_container_add(GTK_CONTAINER(window), box);
1006
1007         viewport = gtk_viewport_new(NULL, NULL);
1008         gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_IN);
1009         gtk_box_pack_start(GTK_BOX(box), viewport, TRUE, TRUE, 0);
1010
1011         context.list = gtk_tree_view_new();
1012         gtk_container_add(GTK_CONTAINER(viewport), context.list);
1013         g_signal_connect(context.list, "row-activated", G_CALLBACK(&row_activated), &context);
1014
1015         store = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN, G_TYPE_STRING);
1016         gtk_tree_view_set_model(GTK_TREE_VIEW(context.list), GTK_TREE_MODEL(store));
1017         gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(context.list),
1018                 -1, "Device", gtk_cell_renderer_text_new(), "text", 0, NULL);
1019
1020         if(context.manager)
1021         {
1022                 GtkTreeViewColumn *mounted_column;
1023                 GtkCellRenderer *mount_point_renderer;
1024
1025                 mounted_column = gtk_tree_view_column_new();
1026                 gtk_tree_view_column_set_title(mounted_column, "Mounted");
1027
1028                 mounted_toggle = gtk_cell_renderer_toggle_new();
1029                 gtk_tree_view_column_pack_start(mounted_column, mounted_toggle, FALSE);
1030                 gtk_tree_view_column_add_attribute(mounted_column, mounted_toggle, "active", 2);
1031
1032                 mount_point_renderer = gtk_cell_renderer_text_new();
1033                 gtk_tree_view_column_pack_start(mounted_column, mount_point_renderer, TRUE);
1034                 gtk_tree_view_column_add_attribute(mounted_column, mount_point_renderer, "text", 3);
1035
1036                 gtk_tree_view_insert_column(GTK_TREE_VIEW(context.list), mounted_column, -1);
1037         }
1038
1039         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(context.list));
1040         g_signal_connect(selection, "changed", G_CALLBACK(&selection_changed), &context);
1041
1042         context.button = gtk_button_new_with_label(umount ? "Unmount" : "Mount");
1043         g_signal_connect(context.button, "clicked", G_CALLBACK(&button_clicked), &context);
1044         gtk_box_pack_start(GTK_BOX(box), context.button, FALSE, TRUE, 0);
1045
1046         n_listed = refresh_devices(&context, umount);
1047
1048         if(n_listed || context.manager)
1049                 gtk_widget_show_all(window);
1050         else
1051         {
1052                 GtkWidget *dialog;
1053
1054                 /* Don't show the main window if no devices were listed. */
1055                 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
1056                         "No devices to %s", (umount ? "unmount" : "mount"));
1057                 g_signal_connect(dialog, "response", G_CALLBACK(&gtk_main_quit), NULL);
1058                 gtk_widget_show_all(dialog);
1059         }
1060
1061         gtk_main();
1062
1063         free_devices(context.devices);
1064
1065         return 0;
1066 }