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