1 /*
2 Copyright (c) 1991, 1992, 1995 Simon Marshall
3
4 If you still end up late, don't blame me!
5
6 Permission to use, copy, modify, distribute, and sell this software and its
7 documentation for any purpose and without fee is hereby granted,
8 provided that the above copyright notice appear in all copies and that
9 both that copyright notice and this permission notice appear in
10 supporting documentation.
11
12 This software is provided AS IS with no warranties of any kind. The author
13 shall have no liability with respect to the infringement of copyrights,
14 trade secrets or any patents by this file or any part thereof. In no
15 event will the author be liable for any lost revenue or profits or
16 other special, indirect and consequential damages.
17 */
18
19 /*
20 * Just a few bits and bobs.
21 */
22
23
24 #include "xalarm.h"
25 #include "fns.h"
26
27 #if defined (USEGETPWUID)
28 #include <pwd.h>
29 #else
30 extern String getenv();
31 #endif
32
33 #if defined (USEUNAME)
34 #include <sys/utsname.h>
35 #endif
36
37
38 #define FILEOPTION1 "-file"
39 #define FILEOPTION2 "-f"
40 #define DAEMONOPTION1 "-daemon"
41 #define DAEMONOPTION2 "-demon"
42 #define RESTARTONLY1 "-restartonly"
43 #define RESTARTONLY2 "-restart"
44
45 #define COMMANDLIST "echo xalarms: `%s`"
46 #define COMMANDRESET "%s -USR1 `%s`"
47 #define COMMANDKILL "%s -USR2 `%s`"
48 #define DOWNCASE(ch) (((ch) >= 'A') and ((ch) <= 'Z') ? \
49 ((ch) - 'A' + 'a') : (ch))
50
51
52
53 String Concat(), ReplaceNewlines(), NextWord(), DisplayName(),
54 UserName(), HomeDirectory(), MachineName();
55 Boolean IsInteger();
56 Instance PreParseArgList();
57 XtIntervalId ResetTracker();
58 SIGRET KillAlarm();
59 void Initialise(), AddTimeOuts(), SetWMName(), StartDaemon(),
60 SetGeometry(), EnsureNotGrownOffScreen(), Audio(), Quit();
61 static void CentreWidgetUnderPointer(), ParseGeometry();
62 static SIGRET KillDaemon();
63 extern void DoAlarm(), Warning(), WakeUp(), PopupAndAsk();
64 extern FORKRET fork();
65 extern int ParseAlarmFile(), DaysTo();
66 extern long TimeToMilliSeconds();
67 extern unsigned long DateToMilliSeconds();
68 extern time_t time();
69 extern struct tm *localtime();
70
71
72
73 extern AlarmData xalarm;
74
75
76 /*
77 * Just give a bit of help etc.
78 * Fork off a child otherwise.
79 */
80
Initialise(proggie,data)81 void Initialise (proggie, data)
82 String proggie;
83 ApplData *data;
84 {
85 char getpids[TEXT], command[TEXT];
86
87 if (NIL (String, data->proggie))
88 data->proggie = proggie;
89
90 if (data->version)
91 (void) printf ("%s: using xalarm version %s\n", data->proggie, XALARMVERSION);
92
93 if (data->help) {
94 (void) printf ("Usage: %s [options] [text]\n\n", data->proggie);
95 (void) printf ("Default values for these resources/options are used by %s,\n\t",
96 data->proggie);
97 (void) printf ("but may be over-ruled.\n");
98 (void) printf ("Usual X options, plus:\n\t");
99 (void) printf ("-file +days|date\tSet alarms up to date from alarm file(s).\n\t");
100 (void) printf ("-daemon +days|date\tStart a daemon to look at alarm file(s).\n\t");
101 (void) printf ("-date +days|date\tDate at which to trigger %s.\n\t",
102 data->proggie+1);
103 (void) printf ("-time +time|time\tTime at which to trigger %s.\n\t",
104 data->proggie+1);
105 (void) printf ("-[no]confirm\t\t[Don't] ask for confirmation.\n\t");
106 (void) printf ("-warn time[,time...]\tTimes before %s for warnings.\n\t",
107 data->proggie+1);
108 (void) printf ("-warnwords number\tWords from %s message in warning.\n\t",
109 data->proggie+1);
110 (void) printf ("-list\t\t\tList xalarm process numbers (pids).\n\t");
111 (void) printf ("-reset pid|all\t\tReset xalarm process number pid/all xalarms.\n\t");
112 (void) printf ("-kill pid|all\t\tKill xalarm process number pid/all xalarms.\n\t");
113 (void) printf ("-restart\t\tOnly try to restart previous alarms.\n\t");
114 (void) printf ("-alarmaudio method\tWhat noise to make on the alarm.\n\t");
115 (void) printf ("-warningaudio method\tWhat noise to make on warnings.\n\t");
116 (void) printf ("-volume percentage\tPercentage volume for the terminal bell.\n\t");
117 (void) printf ("-quiet\t\t\tDon't make a sound.\n\t");
118 (void) printf ("-pester time\t\tTime after which %s re-triggers.\n\t",
119 data->proggie+1);
120 (void) printf ("-snooze time\t\tSet initial alarm snooze time value.\n\t");
121 (void) printf ("-nowarn\t\t\tNo warnings.\n\t");
122 (void) printf ("-nowarnwords\t\tNo words from %s message in warning.\n\t",
123 data->proggie+1);
124 (void) printf ("-nopester\t\tDon't persecute after %s triggers.\n",
125 data->proggie+1);
126 }
127
128 if (data->list) {
129 (void) sprintf (getpids, GETXALARMPIDS, getpid ());
130 (void) sprintf (command, COMMANDLIST, getpids);
131 if (system (command) != 0)
132 perror (data->proggie);
133 }
134
135 if (NONNIL (String, data->reset))
136 if (not STREQUAL (data->reset, ALL)) {
137 if (kill (atoi (data->reset), SIGUSR1) == -1)
138 perror (data->proggie);
139 } else {
140 (void) sprintf (getpids, GETXALARMPIDS, getpid ());
141 (void) sprintf (command, COMMANDRESET, KILLPATH, getpids);
142 if (system (command) != 0)
143 perror (data->proggie);
144 }
145
146 if (NONNIL (String, data->kill))
147 if (not STREQUAL (data->kill, ALL)) {
148 if (kill (atoi (data->kill), SIGUSR2) == -1)
149 perror (data->proggie);
150 } else {
151 (void) sprintf (getpids, GETXALARMPIDS, getpid ());
152 (void) sprintf (command, COMMANDKILL, KILLPATH, getpids);
153 if (system (command) != 0)
154 perror (data->proggie);
155 }
156
157 if ((data->list) or (data->version) or (data->help) or
158 (NONNIL (String, data->kill)) or (NONNIL (String, data->reset)))
159 exit (0);
160
161 /*
162 * Fork & exit the parent. If the fork fails, carry on anyway.
163 * Is this OK? Should we sleep a bit first?
164 */
165
166 switch ((int) fork ()) {
167 case -1:
168 perror (data->proggie);
169 case 0:
170 break;
171 default:
172 exit (0);
173 }
174 }
175
176
177
178 /*
179 * Check to see if we're to do the appointments thing, and copy out
180 * the arg list, then clear the original to prevent others snooping.
181 */
182
PreParseArgList(argv,argc,days,args)183 Instance PreParseArgList (argv, argc, days, args)
184 String *argv, **args;
185 int *argc, *days;
186 {
187 String ch;
188 Instance instance = Alarm;
189 int last = *argc, i;
190
191 *args = (String *) XtCalloc (*argc+4, sizeof (String *));
192 (*args)[0] = XtNewString (argv[0]);
193 *argc = 1;
194
195 for (i=1; i<last; i++)
196 if ((STREQUAL (argv[i], RESTARTONLY1)) or (STREQUAL (argv[i], RESTARTONLY2)))
197 instance = RestartOnly;
198 else if ((STREQUAL (argv[i], FILEOPTION1)) or
199 (STREQUAL (argv[i], FILEOPTION2))) {
200 if (++i == last) {
201 (void) fprintf (stderr, "No file date to parse alarm file with.\n");
202 exit (-1);
203 } else if (ISINVALID (*days = DaysTo (argv[i], instance = File))) {
204 (void) fprintf (stderr, "Can't use file date: %s", xalarm.errormessage);
205 exit (-1);
206 }
207 } else if ((STREQUAL (argv[i], DAEMONOPTION1)) or
208 (STREQUAL (argv[i], DAEMONOPTION2))) {
209 if (++i == last) {
210 (void) fprintf (stderr, "No daemon date to parse alarm file with.\n");
211 exit (-1);
212 } else if (ISINVALID (*days = DaysTo (argv[i], instance = Daemon))) {
213 (void) fprintf (stderr, "Can't use daemon date: %s", xalarm.errormessage);
214 exit (-1);
215 } else if (*days == 0) {
216 (void) fprintf (stderr, "Zero days means daemon would not sleep!\n");
217 exit (-1);
218 }
219 } else
220 /* Just copy the option.
221 */
222 (*args)[(*argc)++] = XtNewString (argv[i]);
223
224 for (i=1; i<last; i++) {
225 ch = argv[i];
226 while (*ch != '\0')
227 *(ch++) = ' ';
228 }
229
230 return (instance);
231 }
232
233
234
235 /*
236 * Fork off an xalarm process which will repeatedly parse the
237 * alarm file.
238 */
239
StartDaemon(days,argv,argc)240 void StartDaemon (days, argv, argc)
241 String *argv;
242 int days, argc;
243 {
244 Display *display;
245 String displayname = DisplayName (argv, argc);
246 int pid, kids;
247 time_t abstime;
248 struct tm *now;
249
250 /*
251 * Fork off the daemon.
252 */
253
254 switch (pid = (int) fork ()) {
255 case -1:
256 perror ("xalarm");
257 exit (-1);
258 case 0:
259 (void) signal (SIGUSR1, SIG_IGN);
260 (void) signal (SIGUSR2, (SIGRET (*)()) KillDaemon);
261 break;
262 default:
263 (void) printf ("xalarm: Daemon started (%d).\n", pid);
264 exit (0);
265 }
266
267 /*
268 * Open the display for the daemon and parse & sleep until we
269 * loose the connection.
270 */
271
272 if (NONNIL (Display *, (display = XOpenDisplay (displayname)))) {
273 (void) time (&abstime);
274 now = localtime (&abstime);
275
276 while (True) {
277 /*
278 * Start any alarms in the alarm file, then wait for them
279 * to finish.
280 */
281 if (ISWEEKLYD (days))
282 kids = ParseAlarmFile (7 - now->tm_wday, argv, argc);
283 else
284 kids = ParseAlarmFile (days - 1, argv, argc);
285
286 while (kids-- > 0)
287 (void) wait ((int *) NULL);
288
289 /*
290 * Sleep until the next time, then test the connection.
291 */
292 (void) time (&abstime);
293 now = localtime (&abstime);
294 (void) sleep ((unsigned)
295 (30 + ((ISWEEKLYD (days) ?
296 (days = 7) - now->tm_wday + 1 : days) * SECSIN1DAY) -
297 (now->tm_hour * SECSIN1HR) - (now->tm_min * SECSIN1MIN) - now->tm_sec));
298
299 XSync (display, False);
300 }
301 }
302 }
303
304
305
306 /*
307 * Signal handler for a daemon - it just kills the process.
308 */
309
KillDaemon(sig,code,scp,addr)310 static SIGRET KillDaemon (sig, code, scp, addr)
311 int sig, code;
312 struct sigcontext *scp;
313 char *addr;
314 {
315 (void) fprintf (stderr, "xalarm: Daemon killed (%d).\n", getpid ());
316 exit (0);
317 }
318
319
320
321 /*
322 * Signal handler for a normal alarm - it just kills the process.
323 */
324
KillAlarm(sig,code,scp,addr)325 SIGRET KillAlarm (sig, code, scp, addr)
326 int sig, code;
327 struct sigcontext *scp;
328 char *addr;
329 {
330 Quit ((Widget) NULL, (XtPointer) NULL, (XtPointer) NULL);
331 }
332
333
334
335 /*
336 * We add the time outs for the alarm & warnings.
337 * The warnings are added only if there is time enuf to go.
338 * ie. if "-time +10 -warn 15" you will get nowt.
339 * "-time +10 -warn 9" will, however, give you a warning in 1 min.
340 */
341
AddTimeOuts()342 void AddTimeOuts ()
343 {
344 unsigned long timeout = SUMTIMEOUTS (xalarm.dateout, xalarm.timeout);
345 int i;
346
347 xalarm.settime = (unsigned long) time ((time_t *) NULL);
348 xalarm.offtime = xalarm.settime + (timeout / MSECSIN1SEC);
349
350 for (i=0; i<xalarm.numwarnings; i++)
351 if (timeout > xalarm.warnings[i])
352 xalarm.timeoutid[i] =
353 XtAppAddTimeOut (xalarm.appcon,
354 TIMEOUT (timeout - xalarm.warnings[i]),
355 (XtTimerCallbackProc) Warning,
356 (XtPointer) (xalarm.warnings[i] / MSECSIN1MIN));
357 xalarm.timeoutid[xalarm.numwarnings] =
358 XtAppAddTimeOut (xalarm.appcon, TIMEOUT (timeout),
359 (XtTimerCallbackProc) WakeUp, (XtPointer) NULL);
360 }
361
362
363
364 /*
365 * Set the window manager name of the given widget.
366 */
367
SetWMName(widget,name)368 void SetWMName (widget, name)
369 Widget widget;
370 String name;
371 {
372 Display *display = XtDisplay (xalarm.toplevel);
373 String wmname[1];
374 XTextProperty property;
375
376 if (not XtIsRealized (widget))
377 XtRealizeWidget (widget);
378
379 wmname[0] = name;
380 if (XStringListToTextProperty (wmname, 1, &property)) {
381 XSetWMName (display, XtWindow (widget), &property);
382 XSetWMIconName (display, XtWindow (widget), &property);
383 XtFree (property.value);
384 }
385 }
386
387
388
389 /*
390 * Reset the tracker so it calls itself.
391 */
392
ResetTracker(tracker,clientdata,triggered)393 XtIntervalId ResetTracker (tracker, clientdata, triggered)
394 XtTimerCallbackProc tracker;
395 XtPointer clientdata;
396 int triggered;
397 {
398 time_t now;
399 struct tm *clock;
400
401 (void) time (&now);
402 clock = localtime (&now);
403 /*
404 * 60 seconds from when it triggered.
405 */
406 return (XtAppAddTimeOut (xalarm.appcon,
407 TIMEOUT (MSECSIN1SEC * (60 - clock->tm_sec + triggered)),
408 tracker, clientdata));
409 }
410
411
412
413 /*
414 * Set the geometry of the given widget.
415 */
416
SetGeometry(widget)417 void SetGeometry (widget)
418 Widget widget;
419 {
420 Dimension width, height, borderwidth;
421
422 if (not XtIsRealized (widget))
423 XtRealizeWidget (widget);
424
425 XtVaGetValues (widget, XtNwidth, &width, XtNheight, &height,
426 XtNborderWidth, &borderwidth, NULL);
427
428 if (STREQUAL (xalarm.geometry, NOGEOMETRY))
429 CentreWidgetUnderPointer (widget, width, height, borderwidth);
430 else
431 ParseGeometry (widget, width, height, borderwidth, xalarm.geometry);
432 }
433
434
435
436 /*
437 * Move the given widget so that it is directly underneath the pointer.
438 */
439
CentreWidgetUnderPointer(widget,width,height,borderwidth)440 static void CentreWidgetUnderPointer (widget, width, height, borderwidth)
441 Widget widget;
442 Dimension width, height, borderwidth;
443 {
444 Window root, child;
445 int x, y, dummy;
446 unsigned int mask;
447
448 width += (borderwidth * 2);
449 height += (borderwidth * 2);
450
451 if (XQueryPointer (XtDisplay (widget), XtWindow (widget),
452 &root, &child, &x, &y, &dummy, &dummy, &mask)) {
453 x = MAX (0, MIN (x - ((int) width / 2),
454 XWidthOfScreen (XtScreen (widget)) - (int) width));
455 y = MAX (0, MIN (y - ((int) height / 2),
456 XHeightOfScreen (XtScreen (widget)) - (int) height));
457 XtVaSetValues (widget, XtNx, (XtArgVal) x, XtNy, (XtArgVal) y, NULL);
458 }
459 }
460
461
462
463 /*
464 * Move +/ resize the given widget so that it has the given geometry.
465 */
466
ParseGeometry(widget,currentwidth,currentheight,borderwidth,geometry)467 static void ParseGeometry (widget, currentwidth, currentheight, borderwidth, geometry)
468 Widget widget;
469 Dimension currentwidth, currentheight, borderwidth;
470 String geometry;
471 {
472 unsigned int width = (unsigned int) currentwidth,
473 height = (unsigned int) currentheight;
474 int x = 0, y = 0, mask;
475
476 mask = XParseGeometry (geometry, &x, &y, &width, &height);
477
478 if (mask & WidthValue)
479 XtVaSetValues (widget, XtNwidth, (XtArgVal) width, NULL);
480 if (mask & HeightValue)
481 XtVaSetValues (widget, XtNheight, (XtArgVal) height, NULL);
482 if (mask & (WidthValue | HeightValue))
483 XtVaSetValues (widget, XtNallowShellResize, (XtArgVal) False, NULL);
484
485 if (not (mask & (XValue | YValue)))
486 CentreWidgetUnderPointer (widget, width, height, borderwidth);
487 else {
488 if (mask & XNegative)
489 x = x + XWidthOfScreen (XtScreen (widget)) - (int) width;
490 if (mask & YNegative)
491 y = y + XHeightOfScreen (XtScreen (widget)) - (int) height;
492 XtVaSetValues (widget, XtNx, (XtArgVal) x, XtNy, (XtArgVal) y, NULL);
493 }
494 }
495
496
497
498 /*
499 * Make sure that the widget's right side is visible.
500 * For resizing popups.
501 */
502
EnsureNotGrownOffScreen(widget)503 void EnsureNotGrownOffScreen (widget)
504 Widget widget;
505 {
506 Position x;
507 Dimension width, borderwidth, screenwidth = XWidthOfScreen (XtScreen (widget));
508
509 XtVaGetValues (widget, XtNx, &x, XtNwidth, &width, XtNborderWidth, &borderwidth,
510 NULL);
511 width += (borderwidth * 2);
512 if (screenwidth > width)
513 XtVaSetValues (widget, XtNx, (XtArgVal) MIN (x, screenwidth - width), NULL);
514 }
515
516
517
518 /*
519 * Pull out any display arg.
520 */
521
DisplayName(argv,argc)522 String DisplayName (argv, argc)
523 String *argv;
524 int argc;
525 {
526 String displayname = (String) NULL;
527 int i;
528
529 for (i=1; i<argc; i++)
530 if ((strcmp (argv[i], "-display") == 0) or (strcmp (argv[i], "-d") == 0))
531 if (i < argc-1)
532 displayname = argv[i+1];
533 else {
534 (void) printf ("xalarm: No display to open.\n");
535 exit (-1);
536 }
537
538 return (displayname);
539 }
540
541
542
543 /*
544 * Make a sound, if that's what's wanted.
545 */
Audio(sound)546 void Audio (sound)
547 String sound;
548 {
549 if ((STREQUAL (sound, BELL)) or (STREQUAL (sound, BEEP)))
550 XBell (XtDisplay (xalarm.toplevel), xalarm.volume);
551 else if (not STREQUAL (sound, QUIET))
552 if (system (sound) != 0)
553 perror (sound);
554 }
555
556
557
558 /*
559 * Returns a string concated from the given array of strings.
560 * Separates the strings with a newline.
561 *
562 * Hacked from various books on X & Xt.
563 */
564
Concat(strings,n)565 String Concat (strings, n)
566 String *strings;
567 int n;
568 {
569 String buffer;
570 unsigned int i, len = 0;
571
572 if (n <= 1)
573 return ((String) NULL);
574
575 for (i=1; i<n; i++)
576 len += strlen (strings[i]);
577 len += (n-1);
578
579 buffer = XtMalloc (len+1);
580 buffer[0] = '\0';
581 for (i=1; i<n; i++) {
582 if (i > 1)
583 (void) strcat (buffer, "\n");
584 (void) strcat (buffer, strings[i]);
585 }
586
587 return (buffer);
588 }
589
590
591
592 /*
593 * Replace each newline by a space. Returns the new string.
594 */
595
ReplaceNewlines(str)596 String ReplaceNewlines (str)
597 String str;
598 {
599 String s, newstr = XtNewString (str);
600
601 if (NONNIL (String, s = newstr))
602 do
603 if (*s == '\n')
604 *s = ' ';
605 while (*(s++) != '\0');
606
607 return (newstr);
608 }
609
610
611
612 /*
613 * Return the next word in str, starting at position chpos in the
614 * string. The returned word is in lower case.
615 */
616
NextWord(str,chpos)617 String NextWord (str, chpos)
618 String str;
619 int *chpos;
620 {
621 String word;
622 int start, i;
623
624 while (isspace (str[*chpos]))
625 (*chpos)++;
626
627 start = *chpos;
628 while ((not isspace (str[*chpos])) and (str[*chpos] != '\0'))
629 (*chpos)++;
630
631 if ((str[*chpos] == '\0') and (start == *chpos))
632 return ("");
633 else {
634 word = XtMalloc (*chpos - start + 1);
635 for (i=0; i<(*chpos-start); i++)
636 word[i] = DOWNCASE (str[start+i]);
637 word[*chpos-start] = '\0';
638
639 return (word);
640 }
641 }
642
643
644
645 /*
646 * Returns true iff the string contains a valid integer.
647 */
648
IsInteger(str)649 Boolean IsInteger (str)
650 String str;
651 {
652 if (*str == '+')
653 str++;
654 while (*str != '\0')
655 if (not isdigit (*str++))
656 return (False);
657 return (True);
658 }
659
660
661
662 /*
663 * Return the user, users' home directory & host names.
664 */
665
UserName()666 String UserName ()
667 {
668 static String name = (String) NULL;
669
670 if (NIL (String, name))
671 #if defined (USEGETLOGIN)
672 name = (String) getlogin ();
673 #elif defined (USECUSERID)
674 (void) cuserid (name = XtMalloc ((Cardinal) L_cuserid + 1));
675 #elif defined (USEGETPWUID)
676 name = getpwuid (getuid ())->pw_name;
677 name = XtNewString (name);
678 #else
679 name = getenv ("USER");
680 #endif
681
682 return ((NIL (String, name)) ? NOTKNOWN : name);
683 }
684
685
686
HomeDirectory()687 String HomeDirectory ()
688 {
689 static String name = (String) NULL;
690
691 if (NIL (String, name))
692 #if defined (USEGETPWUID)
693 name = getpwuid (getuid ())->pw_dir;
694 name = XtNewString (name);
695 #else
696 name = getenv ("HOME");
697 #endif
698
699 return ((NIL (String, name)) ? NOTKNOWN : name);
700 }
701
702
703
MachineName()704 String MachineName ()
705 {
706 #if defined (USEGETHOSTNAME)
707 static char name[TEXT];
708
709 return ((gethostname (name, TEXT) < 0) ? NOTKNOWN : name);
710 #elif defined (USEUNAME)
711 static struct utsname name;
712
713 return ((uname (&name) < 0) ? NOTKNOWN : name.nodename);
714 #else
715 return (NOTKNOWN);
716 #endif
717 }
718
719
720
721 /*
722 * This function generates a random number and stuffs it down the pipe.
723 */
724
Quit(widget,clientdata,calldata)725 void Quit (widget, clientdata, calldata)
726 Widget widget;
727 XtPointer clientdata, calldata;
728 {
729 XtDestroyApplicationContext (xalarm.appcon);
730 exit (0);
731 }
732