1 #include <fcntl.h>
2 #include <math.h>
3 #include <pthread.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <time.h>
7 #include <unistd.h>
8 
9 #include <sys/wait.h>
10 #include <sys/param.h>
11 #include <sys/types.h>
12 
13 #include <X11/Xlib.h>
14 #include <X11/xpm.h>
15 #include <X11/extensions/shape.h>
16 #include <gtk/gtk.h>
17 
18 #include "../wmgeneral/wmgeneral.h"
19 #include "../wmgeneral/misc.h"
20 
21 #include "wmtimer.xpm"
22 
23 #define CHAR_WIDTH 5
24 #define CHAR_HEIGHT 7
25 #define VERSION "2.92"
26 #define _MULTI_THREADED
27 
28 typedef enum {NONE, ALARM, TIMER, TIMER_PAUSED, TIMER_DONE, CHRONO, CHRONO_PAUSED} modeType;
29 typedef struct {int bell, command;} actionType;
30 typedef enum {OUT, IN, RETURN} configState;
31 
32 /*******************************************************************************
33  * Functions
34  ******************************************************************************/
35 // Misc functions
36 void execAct();
37 void parseArgs(int argc, char *argv[]);
38 void processEvent(XEvent *event);
39 void usage();
40 
41 // Timer updates
42 void decrementTimer();
43 void incrementTimer();
44 
45 // X11 Screen updates
46 void blitNum(int num, int x, int y);
47 void blitString(char *name, int x, int y);
48 void updateACT();
49 void updateClock(int clockHour, int clockMin, int clockSec);
50 void updateMain();
51 
52 // GTK GUI functions
53 void *configure(void *);
54 void callback(GtkWidget *widget, gpointer data);
55 int delete_event(GtkWidget *widget, GdkEvent *event, gpointer data);
56 void destroy(GtkWidget *widget, gpointer data);
57 
58 // Functions to avoid 'implicit declaration' warnings
59 int atoi();
60 char toupper();
61 
62 
63 /*******************************************************************************
64  * Globals
65  ******************************************************************************/
66 static GtkWidget *entry;
67 static GtkWidget *spinner1;
68 static GtkWidget *spinner2;
69 static GtkWidget *spinner3;
70 
71 int timeSetToZero = 0;
72 int buttonStatus = -1;
73 int hour = 0, min = 0, sec = 0;
74 char *myName;
75 char command[256];
76 modeType mode, tmpMode;
77 actionType action, tmpAction;
78 configState configSt;
79 
80 
81 /*******************************************************************************
82  * main
83  ******************************************************************************/
main(int argc,char * argv[])84 int main(int argc, char *argv[])
85 {
86   int prevSec = 0;
87   long now;
88   struct tm *thisTime;
89   int wminet_mask_width = 64;
90   int wminet_mask_height = 64;
91   char wminet_mask_bits[64 * 64];
92   XEvent Event;
93 
94   parseArgs(argc, argv);
95   gtk_init (&argc, &argv);
96 
97   createXBMfromXPM(wminet_mask_bits, wmtimer_xpm,
98       wminet_mask_width, wminet_mask_height);
99 
100   openXwindow(argc, argv, wmtimer_xpm, wminet_mask_bits,
101       wminet_mask_width, wminet_mask_height);
102 
103   // setMaskXY(-64, 0);
104 
105   AddMouseRegion(0, 18, 49, 45, 59);	/* middle button */
106   AddMouseRegion(1, 5, 49, 17, 59);	/* left button   */
107   AddMouseRegion(2, 46, 49, 59, 59);	/* right button  */
108   AddMouseRegion(3, 2, 2, 58, 47);	/* main area     */
109   //  AddMouseRegion(3, 6, 2, 60, 18);   	/* first bar     */
110   //  AddMouseRegion(4, 6, 20, 60, 34);  	/* second bar    */
111   //  AddMouseRegion(5, 6, 37, 60, 48);  	/* third bar     */
112 
113   updateMain();
114   updateACT();
115 
116   //  if (hour == 0 && min == 0 && sec == 0)
117   //    timeSetToZero = 1;
118 
119   while (1)
120   {
121     now = time(0);
122     waitpid(0, NULL, WNOHANG);
123     thisTime = localtime(&now);
124 
125     updateClock(thisTime->tm_hour, thisTime->tm_min, thisTime->tm_sec);
126     RedrawWindow();
127 
128     switch (mode)
129     {
130       case TIMER:
131 	if (prevSec < thisTime->tm_sec)
132 	{
133 	  decrementTimer();
134 	  updateACT();
135 	  if (hour == 0 && min == 0 && sec == 0 && !timeSetToZero)
136 	      execAct();
137 	}
138 	prevSec = thisTime->tm_sec;
139 	break;
140       case CHRONO:
141 	if (prevSec < thisTime->tm_sec)
142 	{
143 	  incrementTimer();
144 	  updateACT();
145 	}
146 	prevSec = thisTime->tm_sec;
147 	break;
148       case ALARM:
149 	if (hour == thisTime->tm_hour &&
150 	    min == thisTime->tm_min &&
151 	    sec == thisTime->tm_sec)
152 	  execAct();
153 	break;
154       case NONE:
155       case TIMER_DONE:
156       case TIMER_PAUSED:
157       case CHRONO_PAUSED:
158 	break;
159     }
160 
161     while (XPending(display)) 			// Handle X Events
162     {
163       XNextEvent(display, &Event);
164       processEvent(&Event);
165     }
166 
167     // Since we have multi-thread, need to detect return from configure
168     if (configSt == RETURN)
169     {
170       configSt = OUT;
171       updateMain();
172       updateACT();
173     }
174 
175     usleep(100000L);
176   }
177 
178 return 0;
179 }
180 
181 
182 /*******************************************************************************
183  * execAct
184  ******************************************************************************/
execAct()185 void execAct()
186 {
187   if (action.command && strlen(command))
188     execCommand(command);
189   if (action.bell)
190   {
191     printf("\07");
192     fflush(stdout);
193   }
194 
195   if (mode == TIMER)
196     mode = TIMER_DONE;		// Don't want to keep doing the Timer event
197   else
198     mode = NONE;
199 
200 }
201 
202 
203 /*******************************************************************************
204  * parseArgs
205  ******************************************************************************/
parseArgs(int argc,char * argv[])206 void parseArgs(int argc, char *argv[])
207 {
208   int argIndex;
209   int timeDelim = 0;
210   int timeParts[] = {0,0,0};
211   char *charPtr;
212 
213   command[0] = '\0';
214   myName = argv[0];
215 
216   for (argIndex = 1; argIndex < argc; argIndex++)
217   {
218     char *arg = argv[argIndex];
219 
220     // Allow for the options that wmgeneral.c accepts
221     if (!strcmp(arg, "-color") ||
222 	!strcmp(arg, "-display") ||
223 	!strcmp(arg, "-geometry")) {
224     }
225     else if (*arg == '-')
226     {
227       switch (arg[1])
228       {
229 	case 'a':
230 	  mode = ALARM;
231 	  break;
232 	case 'c':
233 	  mode = TIMER;
234 	  break;
235 	case 'r':
236 	  mode = CHRONO;
237 	  break;
238 	case 'b':
239 	  action.bell = 1;
240 	  break;
241 	case 'e':
242 	  strcpy(command, argv[argIndex+1]);
243 	  action.command = 1;
244 	  break;
245 	case 't':
246 	  if (argv[argIndex+1])
247 	  {
248 	    // Check time argument for errors, dont want to segfault on strtok()
249 	    for (charPtr = argv[argIndex+1]; *charPtr; charPtr++)
250 	    {
251 	      if (*charPtr == ':')
252 		timeDelim++;
253 	      else
254 	      {
255 		if (timeDelim == 0)
256 		  timeParts[0]++;
257 		else if (timeDelim == 1)
258 		  timeParts[1]++;
259 		else if (timeDelim == 2)
260 		  timeParts[2]++;
261 	      }
262 	    }
263 
264 	    // Need to have 2 :'s as time delimiter
265 	    // Need to have 1 or 2 digits for each part
266 	    if (timeDelim != 2 ||
267 		!(timeParts[0] == 1 || timeParts[0] == 2) ||
268 		!(timeParts[1] == 1 || timeParts[1] == 2) ||
269 		!(timeParts[2] == 1 || timeParts[2] == 2) )
270 	      usage();
271 
272 	    hour = atoi(strtok(argv[argIndex+1], ":"));
273 	    min = atoi(strtok(NULL, ":"));
274 	    sec = atoi(strtok(NULL, ":"));
275 
276 	    if (hour == 0 && min == 0 && sec == 0)
277 	      timeSetToZero = 1;
278 	  }
279 	  else
280 	    usage();
281 	  break;
282 	case 'v':
283 	  printf("WMTimer Version: %s\n", VERSION);
284 	  _exit(0);
285 	  break;
286 	default:
287 	  usage();
288 	  break;
289       }
290     }
291   }
292 
293 }
294 
295 
296 /*******************************************************************************
297  * processEvent
298  ******************************************************************************/
processEvent(XEvent * event)299 void processEvent(XEvent *event)
300 {
301 
302   int tmpButtonStatus;
303   int threadId;
304   pthread_t  thread;
305 
306   switch (event->type)
307   {
308     case Expose:
309       RedrawWindow();
310       break;
311     case DestroyNotify:
312       XCloseDisplay(display);
313       _exit(0);
314       break;
315     case ButtonPress:
316       tmpButtonStatus = CheckMouseRegion(event->xbutton.x, event->xbutton.y);
317       buttonStatus = tmpButtonStatus;
318       if (buttonStatus == tmpButtonStatus && buttonStatus >= 0)
319       {
320 	switch (buttonStatus)
321 	{
322 	  case 0:					// center button
323 	    break;
324 	  case 1:					// left arrow button
325 	    break;
326 	  case 2:					// right arrow button
327 	    break;
328 	  case 3:					// main area
329 	    break;
330 	  default:
331 	    break;
332 	}
333       }
334       break;
335     case ButtonRelease:
336       tmpButtonStatus = CheckMouseRegion(event->xbutton.x, event->xbutton.y);
337       if (buttonStatus == tmpButtonStatus && buttonStatus >= 0)
338       {
339 	switch (buttonStatus)
340 	{
341 	  case 0:					// center button
342 	    if (mode == ALARM)
343 	    {
344 	      hour = min = sec = 0;
345 	      updateACT();
346 	    }
347 	    if (mode == CHRONO)
348 	      mode = CHRONO_PAUSED;
349 	    else if (mode == TIMER)
350 	      mode = TIMER_PAUSED;
351 	    updateMain();
352 	    break;
353 	  case 1:					// left arrow button
354 	    if (mode == CHRONO)
355 	      mode = CHRONO_PAUSED;
356 	    else if (mode == TIMER)
357 	      mode = TIMER_PAUSED;
358 	    hour = min = sec = 0;
359 	    updateACT();
360 	    updateMain();
361 	    break;
362 	  case 2:					// right arrow button
363 	    if (mode == ALARM)
364 	    {
365 	      hour = min = sec = 0;
366 	      updateACT();
367 	      timeSetToZero = 0;
368 	      mode = CHRONO;
369 	    }
370 	    else if (mode == TIMER || mode == TIMER_PAUSED)
371 	      mode = TIMER;
372 	    else
373 	      mode = CHRONO;
374 
375 	    updateMain();
376 	    break;
377 	  case 3:					// main area
378 	    if (configSt != IN) 	// Dont want to spawn multiple config threads
379 	    {
380 	      threadId = pthread_create(&thread, NULL, configure, NULL);
381 	      configSt = IN;
382 	    }
383 	    break;
384 	  default:
385 	    break;
386 	}
387       }
388       buttonStatus = -1;
389       break;
390   }
391 }
392 
393 
394 /*******************************************************************************
395  * usage
396  ******************************************************************************/
usage(void)397 void usage(void)
398 {
399   fprintf(stderr, "\nWMTimer - Josh King <wmtimer@darkops.net>\n\n");
400 
401   fprintf(stderr, "usage: %s -[a|c|r] -t <hh:mm:ss> -e <command>\n\n", myName);
402 
403   fprintf(stderr, "    -a     alarm mode, wmtimer will beep/exec command\n");
404   fprintf(stderr, "             at specified time\n");
405   fprintf(stderr, "    -c     countdowntimer mode, wmtimer will beep/exec\n");
406   fprintf(stderr, "	         command when specified time reaches 0 \n");
407   fprintf(stderr, "    --color <color> as a word or as rgb:RR/GG/BB\n");
408   fprintf(stderr, "    -b     beep\n");
409   fprintf(stderr, "    -e     <command> exec command\n");
410   fprintf(stderr, "    -r     start in chronograph mode\n");
411   fprintf(stderr, "    -t     <hh:mm:ss>\n");
412   fprintf(stderr, "    -h     this help screen\n");
413   fprintf(stderr, "    -v     print the version number\n");
414   fprintf(stderr, "\n");
415 
416   _exit(0);
417 }
418 
419 
420 /*******************************************************************************
421  * decrementTimer
422  ******************************************************************************/
decrementTimer()423 void decrementTimer()
424 {
425   if (!(hour == 0 && min == 0 && sec == 0)) 	// Don't want to go past 0:0:0
426     sec--;
427   if (sec == -1)
428   {
429     sec = 59;
430     min--;
431     if (min == -1)
432     {
433       min = 59;
434       hour--;
435     }
436   }
437 }
438 
439 
440 /*******************************************************************************
441  * incrementTimer
442  ******************************************************************************/
incrementTimer()443 void incrementTimer()
444 {
445   sec++;
446   if (sec == 60)
447   {
448     sec = 0;
449     min++;
450     if (min == 60)
451     {
452       min = 0;
453       hour++;
454     }
455   }
456 }
457 
458 
459 /*******************************************************************************
460  * blitNum Blits a number at given co-ordinates
461  ******************************************************************************/
blitNum(int num,int x,int y)462 void blitNum(int num, int x, int y)
463 {
464   char buf[1024];
465   int newx = x;
466 
467   if (num > 99)
468     newx -= CHAR_WIDTH;
469 
470   if (num > 999)
471     newx -= CHAR_WIDTH;
472 
473   sprintf(buf, "%02i", num);
474   blitString(buf, newx, y);
475 }
476 
477 
478 /*******************************************************************************
479  * blitString Blits a string at given co-ordinates
480  ******************************************************************************/
blitString(char * name,int x,int y)481 void blitString(char *name, int x, int y)
482 {
483   // copyXPMArea(x_get_pos, y_get_pos, x_dist_from_x_pos, y_dist_from_y_pos,
484   //     x_placement_pos, y_placement_pos);
485   // each char/num is 6u wide & 8u high, nums are 64u down, chars are 74u down
486 
487   int i;
488   int c;
489   int k;
490   k = x;
491 
492   for (i = 0; name[i]; i++)
493   {
494     c = toupper(name[i]);
495     if (c >= 'A' && c <= 'Z')			// its a letter
496     {
497       c -= 'A';
498       copyXPMArea(c * 6, 74, 6, 8, k, y);
499       k += 6;
500     }
501     else 					// its a number or symbol
502     {
503       c -= '0';
504       copyXPMArea(c * 6, 64, 6, 8, k, y);
505       k += 6;
506     }
507   }
508 
509 }
510 
511 
512 /*******************************************************************************
513  * updateACT  (AlarmChronoTimer)
514  ******************************************************************************/
updateACT()515 void updateACT()
516 {
517   blitNum(hour, 7, 36);
518   blitString(":", 20, 36);
519   blitNum(min, 25, 36);
520   blitString(":", 38, 36);
521   blitNum(sec, 43, 36);
522 }
523 
524 
525 /*******************************************************************************
526  * updateClock
527  ******************************************************************************/
updateClock(int clockHour,int clockMin,int clockSec)528 void updateClock(int clockHour, int clockMin, int clockSec)
529 {
530   blitNum(clockHour, 7, 5);
531   blitString(":", 20, 5);
532   blitNum(clockMin, 25, 5);
533   blitString(":", 38, 5);
534   blitNum(clockSec, 43, 5);
535 
536 }
537 
538 
539 /*******************************************************************************
540  * updateMain
541  ******************************************************************************/
updateMain()542 void updateMain()
543 {
544   copyXPMArea(13 * 6, 64, 8 * 6, 8, 6, 21);
545 
546   switch (mode)
547   {
548     case ALARM:
549       blitString("ALARM:", 13, 21);
550       break;
551     case TIMER_PAUSED:
552     case TIMER:
553       blitString("TIMER:", 13, 21);
554       break;
555     case CHRONO_PAUSED:
556     case CHRONO:
557       blitString("CHRONO:", 12, 21);
558       break;
559     default:
560       blitString("WMTIMER", 10, 21);
561       break;
562   }
563 }
564 
565 
566 /*******************************************************************************
567  * callback
568  ******************************************************************************/
callback(GtkWidget * widget,gpointer data)569 void callback(GtkWidget * widget, gpointer data)
570 {
571   if ((char *) data == "alarm_button")
572     tmpMode = ALARM;
573   else if ((char *) data == "timer_button")
574     tmpMode = TIMER;
575   else if ((char *) data == "chrono_button")
576     tmpMode = CHRONO;
577   else if ((char *) data == "bell_button")
578   {
579     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
580       tmpAction.bell = 1;
581     else
582       tmpAction.bell = 0;
583   }
584   else if ((char *) data == "command_button")
585   {
586     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
587     {
588       tmpAction.command = 1;
589       gtk_entry_set_editable(GTK_ENTRY (entry), TRUE);
590       gtk_entry_set_text(GTK_ENTRY (entry), command);
591     }
592     else
593     {
594       tmpAction.command = 0;
595       gtk_entry_set_text(GTK_ENTRY (entry), "");
596       gtk_entry_set_editable(GTK_ENTRY (entry), FALSE);
597     }
598   }
599   else if ((char *) data == "ok")
600   {
601     if (tmpAction.command)
602       strcpy(command, gtk_entry_get_text(GTK_ENTRY (entry)));
603 
604     hour = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON (spinner1));
605     min = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON (spinner2));
606     sec = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON (spinner3));
607     timeSetToZero = 0;
608 
609     // If users presses 'ok' and we have not explicitly set the mode,
610     // then it was left on ALARM
611     if (!tmpMode)
612       tmpMode = ALARM;
613 
614     mode = tmpMode;
615     // need to reset so that it can default to ALARM if timer not specified
616     tmpMode = NONE;
617     action.bell = tmpAction.bell;
618     action.command = tmpAction.command;
619     configSt = RETURN;
620   }
621   else if (!strcmp((char *) data, "clear"))
622   {
623     command[0] = '\0';
624     gtk_spin_button_set_value(GTK_SPIN_BUTTON (spinner1), 0);
625     gtk_spin_button_set_value(GTK_SPIN_BUTTON (spinner2), 0);
626     gtk_spin_button_set_value(GTK_SPIN_BUTTON (spinner3), 0);
627     gtk_entry_set_text(GTK_ENTRY (entry), command);
628   }
629   else if (!strcmp ((char *) data, "cancel"))
630     configSt = OUT;
631 }
632 
633 
634 /*******************************************************************************
635  * delete_event This callback quits the program
636  ******************************************************************************/
delete_event(GtkWidget * widget,GdkEvent * event,gpointer data)637 int delete_event(GtkWidget * widget, GdkEvent * event, gpointer data)
638 {
639   return(FALSE);
640 }
641 
642 
643 /*******************************************************************************
644  * destroy destroys the window
645  ******************************************************************************/
destroy(GtkWidget * widget,gpointer data)646 void destroy(GtkWidget * widget, gpointer data)
647 {
648   gtk_main_quit();
649 }
650 
651 
652 /*******************************************************************************
653  * configure
654  ******************************************************************************/
configure(void * arg)655 void *configure(void *arg)
656 {
657   GtkWidget *window;
658   GtkWidget *frame;
659   GtkWidget *button;
660   GtkWidget *alarm_button;
661   GtkWidget *timer_button;
662   GtkWidget *chrono_button;
663   GtkWidget *box1;
664   GtkWidget *box2;
665   GtkWidget *sub_vbox;
666   GtkWidget *label;
667   GtkAdjustment *adj;
668 
669 
670   // Create a new window
671   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
672   gtk_window_set_title (GTK_WINDOW (window), "Configure");
673   gtk_window_set_wmclass (GTK_WINDOW (window), "wmtimerconf", "");
674 
675   // Set a handler for delete_event that immediately exits GTK.
676   gtk_signal_connect (GTK_OBJECT (window), "destroy",
677       GTK_SIGNAL_FUNC (destroy), NULL);
678 
679   // Sets the border width of the window.
680   gtk_container_set_border_width (GTK_CONTAINER (window), 10);
681 
682   // Create Vertical box
683   box1 = gtk_vbox_new (FALSE, 0);
684   // Add vertical box to main window
685   gtk_container_add (GTK_CONTAINER (window), box1);
686   gtk_widget_show (box1);
687 
688   frame = gtk_frame_new ("Mode");
689   gtk_box_pack_start (GTK_BOX (box1), frame, TRUE, TRUE, 2);
690   gtk_widget_show (frame);
691 
692   box2 = gtk_hbox_new (FALSE, 0);
693   gtk_container_add (GTK_CONTAINER (frame), box2);
694 
695   // Create Alarm radio button
696   alarm_button = gtk_radio_button_new_with_label (NULL, "Alarm");
697   gtk_signal_connect (GTK_OBJECT (alarm_button), "clicked",
698       GTK_SIGNAL_FUNC (callback), (gpointer) "alarm_button");
699   gtk_box_pack_start (GTK_BOX (box2), alarm_button, FALSE, FALSE, 2);
700   gtk_widget_show (alarm_button);
701 
702   // Create Timer radio button
703   timer_button = gtk_radio_button_new_with_label (gtk_radio_button_group
704       (GTK_RADIO_BUTTON (alarm_button)), "Timer");
705   gtk_signal_connect (GTK_OBJECT (timer_button), "clicked",
706       GTK_SIGNAL_FUNC (callback), (gpointer) "timer_button");
707   gtk_box_pack_start (GTK_BOX (box2), timer_button, FALSE, FALSE, 2);
708   gtk_widget_show (timer_button);
709   gtk_widget_show (box2);
710 
711   // Create Chrono radio button
712   chrono_button = gtk_radio_button_new_with_label (gtk_radio_button_group
713       (GTK_RADIO_BUTTON (alarm_button)), "Chrono");
714   gtk_signal_connect (GTK_OBJECT (chrono_button), "clicked",
715       GTK_SIGNAL_FUNC (callback), (gpointer) "chrono_button");
716   gtk_box_pack_start (GTK_BOX (box2), chrono_button, FALSE, FALSE, 2);
717   gtk_widget_show (chrono_button);
718   gtk_widget_show (box2);
719 
720   // If we are in timer mode, set toggle accordingly else default to alarm
721   // Set the button corresponding to the current mode
722   if (mode == TIMER || mode == TIMER_PAUSED || mode == TIMER_DONE)
723     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (timer_button), TRUE);
724   else if (mode == CHRONO || mode == CHRONO_PAUSED)
725     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chrono_button), TRUE);
726   else
727     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (alarm_button), TRUE);
728 
729   // Create frame for the time
730   frame = gtk_frame_new ("Time");
731   gtk_box_pack_start (GTK_BOX (box1), frame, TRUE, TRUE, 2);
732   gtk_widget_show (frame);
733 
734   box2 = gtk_hbox_new (FALSE, 0);
735   gtk_container_add (GTK_CONTAINER (frame), box2);
736 
737   // Create hours spinner
738   adj = (GtkAdjustment *) gtk_adjustment_new (hour, 0.0, 23.0, 1.0, 2.0, 0.0);
739   spinner1 = gtk_spin_button_new (adj, 0, 0);
740   gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner1), TRUE);
741   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinner1), TRUE);
742   gtk_box_pack_start (GTK_BOX (box2), spinner1, FALSE, FALSE, 2);
743   gtk_widget_show (spinner1);
744 
745   // Create separator label
746   label = gtk_label_new (" : ");
747   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
748   gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 2);
749   gtk_widget_show (label);
750 
751   // Create minutes spinner
752   adj = (GtkAdjustment *) gtk_adjustment_new (min, 0.0, 59.0, 1.0, 5.0, 0.0);
753   spinner2 = gtk_spin_button_new (adj, 0, 0);
754   gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner2), TRUE);
755   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinner2), TRUE);
756   gtk_box_pack_start (GTK_BOX (box2), spinner2, FALSE, FALSE, 2);
757   gtk_widget_show (spinner2);
758 
759   // Create separator label
760   label = gtk_label_new (" : ");
761   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
762   gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 2);
763   gtk_widget_show (label);
764 
765   // Create seconds spinner
766   adj = (GtkAdjustment *) gtk_adjustment_new (sec, 0.0, 59.0, 1.0, 5.0, 0.0);
767   spinner3 = gtk_spin_button_new (adj, 0, 0);
768   gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (spinner3), TRUE);
769   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinner3), TRUE);
770   gtk_box_pack_start (GTK_BOX (box2), spinner3, FALSE, FALSE, 2);
771   gtk_widget_show (spinner3);
772   gtk_widget_show (box2);
773 
774   // Create frame for 2 buttons and text entry box
775   frame = gtk_frame_new ("Action");
776   gtk_box_pack_start (GTK_BOX (box1), frame, TRUE, TRUE, 2);
777   gtk_widget_show (frame);
778 
779   // Create vertical box
780   sub_vbox = gtk_vbox_new (FALSE, 0);
781   gtk_container_add (GTK_CONTAINER (frame), sub_vbox);
782   gtk_widget_show (sub_vbox);
783 
784   // Create horizontal box
785   box2 = gtk_hbox_new (FALSE, 0);
786   gtk_box_pack_start (GTK_BOX (sub_vbox), box2, TRUE, TRUE, 2);
787 
788   // Create Bell check button
789   button = gtk_check_button_new_with_label ("System Bell");
790   gtk_signal_connect (GTK_OBJECT (button), "clicked",
791       GTK_SIGNAL_FUNC (callback), (gpointer) "bell_button");
792   gtk_box_pack_start (GTK_BOX (box2), button, FALSE, FALSE, 2);
793   gtk_widget_show (button);
794 
795   // If bell mode is active, toggle the button
796   if (action.bell)
797     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
798 
799   // Create Command check button
800   button = gtk_check_button_new_with_label ("Command");
801   gtk_signal_connect (GTK_OBJECT (button), "clicked",
802       GTK_SIGNAL_FUNC (callback), (gpointer) "command_button");
803   gtk_box_pack_start (GTK_BOX (box2), button, FALSE, FALSE, 2);
804   gtk_widget_show (button);
805   gtk_widget_show (box2);
806 
807   // Create horizontal box
808   box2 = gtk_hbox_new (FALSE, 0);
809   gtk_box_pack_start (GTK_BOX (sub_vbox), box2, TRUE, TRUE, 2);
810 
811 
812   // Create label for text entry box
813   label = gtk_label_new ("Command: ");
814   gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
815   gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 2);
816   gtk_widget_show (label);
817 
818   // Create "Command" text entry area
819   entry = gtk_entry_new_with_max_length (100);
820   gtk_entry_set_editable (GTK_ENTRY (entry), FALSE);
821   gtk_signal_connect (GTK_OBJECT (entry), "activate",
822       GTK_SIGNAL_FUNC (callback), entry);
823   gtk_box_pack_start (GTK_BOX (box2), entry, FALSE, FALSE, 2);
824   gtk_widget_show (entry);
825   gtk_widget_show (box2);
826 
827   // If command mode is active, toggle the button and allow user to enter a
828   // command
829   if (action.command)
830     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
831 
832   box2 = gtk_hbox_new (FALSE, 0);
833   gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 2);
834 
835   // Create "Cancel" button
836   button = gtk_button_new_with_label ("Cancel");
837   gtk_signal_connect (GTK_OBJECT (button), "clicked",
838       GTK_SIGNAL_FUNC (callback), "cancel");
839   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
840       GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window));
841   gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 2);
842   gtk_widget_show (button);
843 
844   // Create "Clear" button
845   button = gtk_button_new_with_label ("Clear");
846   gtk_signal_connect (GTK_OBJECT (button), "clicked",
847       GTK_SIGNAL_FUNC (callback), "clear");
848   gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 2);
849   gtk_widget_show (button);
850 
851   // Create "Ok" button
852   button = gtk_button_new_with_label ("Ok");
853   gtk_signal_connect (GTK_OBJECT (button), "clicked",
854       GTK_SIGNAL_FUNC (callback), "ok");
855   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
856       GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window));
857   gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 2);
858   gtk_widget_show (button);
859 
860   gtk_widget_show (box2);
861 
862   gtk_widget_show (window);
863 
864   gtk_main();
865 
866   return 0;
867 }
868 
869