1 /*
2  * Calcurse - text-based organizer
3  *
4  * Copyright (c) 2004-2020 calcurse Development Team <misc@calcurse.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  *      - Redistributions of source code must retain the above
12  *        copyright notice, this list of conditions and the
13  *        following disclaimer.
14  *
15  *      - Redistributions in binary form must reproduce the above
16  *        copyright notice, this list of conditions and the
17  *        following disclaimer in the documentation and/or other
18  *        materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  *
32  * Send your feedback or comments to : misc@calcurse.org
33  * Calcurse home page : http://calcurse.org
34  *
35  */
36 
37 #include <sys/wait.h>
38 #include <time.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #include "calcurse.h"
44 
45 #define NOTIFY_FIELD_LENGTH	25
46 
47 struct notify_vars {
48 	WINDOW *win;
49 	char *apts_file;
50 	char time[NOTIFY_FIELD_LENGTH];
51 	char date[NOTIFY_FIELD_LENGTH];
52 	pthread_mutex_t mutex;
53 };
54 
55 static struct notify_vars notify;
56 static struct notify_app notify_app;
57 static pthread_attr_t detached_thread_attr;
58 
59 /*
60  * Return the number of seconds before next appointment
61  * (0 if no upcoming appointment).
62  */
notify_time_left(void)63 int notify_time_left(void)
64 {
65 	time_t ntimer;
66 	int left;
67 
68 	ntimer = time(NULL);
69 	left = notify_app.time - ntimer;
70 
71 	return left > 0 ? left : 0;
72 }
73 
notify_trigger(void)74 static unsigned notify_trigger(void)
75 {
76 	int flagged = notify_app.state & APOINT_NOTIFY;
77 
78 	if (!notify_app.got_app)
79 		return 0;
80 	if (nbar.notify_all == NOTIFY_ALL)
81 		return 1;
82 	if (nbar.notify_all == NOTIFY_UNFLAGGED_ONLY)
83 		flagged = !flagged;
84 	return flagged;
85 }
86 
87 /*
88  * Return 1 if the reminder was not sent already for the upcoming appointment.
89  */
notify_needs_reminder(void)90 unsigned notify_needs_reminder(void)
91 {
92 	if (notify_app.state & APOINT_NOTIFIED)
93 		return 0;
94 	return notify_trigger();
95 }
96 
97 /*
98  * This is used to update the notify_app structure.
99  * Note: the mutex associated with this structure must be locked by the
100  * caller!
101  */
notify_update_app(time_t start,char state,char * msg)102 void notify_update_app(time_t start, char state, char *msg)
103 {
104 	notify_free_app();
105 	notify_app.got_app = 1;
106 	notify_app.time = start;
107 	notify_app.state = state;
108 	notify_app.txt = mem_strdup(msg);
109 }
110 
111 /* Return 1 if we need to display the notify-bar, else 0. */
notify_bar(void)112 int notify_bar(void)
113 {
114 	int display_bar = 0;
115 
116 	pthread_mutex_lock(&nbar.mutex);
117 	display_bar = (nbar.show) ? 1 : 0;
118 	pthread_mutex_unlock(&nbar.mutex);
119 
120 	return display_bar;
121 }
122 
123 /* Initialize the nbar variable used to store notification options. */
notify_init_vars(void)124 void notify_init_vars(void)
125 {
126 	const char *time_format = "%T";
127 	const char *date_format = "%a %F";
128 	const char *cmd = "printf '\\a'";
129 
130 	pthread_mutex_init(&nbar.mutex, NULL);
131 	nbar.show = 1;
132 	nbar.cntdwn = 300;
133 	strncpy(nbar.datefmt, date_format, BUFSIZ);
134 	nbar.datefmt[BUFSIZ - 1] = '\0';
135 	strncpy(nbar.timefmt, time_format, BUFSIZ);
136 	nbar.timefmt[BUFSIZ - 1] = '\0';
137 	strncpy(nbar.cmd, cmd, BUFSIZ);
138 	nbar.cmd[BUFSIZ - 1] = '\0';
139 
140 	nbar.notify_all = 0;
141 
142 	pthread_attr_init(&detached_thread_attr);
143 	pthread_attr_setdetachstate(&detached_thread_attr,
144 				    PTHREAD_CREATE_DETACHED);
145 }
146 
147 /* Extract the appointment file name from the complete file path. */
extract_aptsfile(void)148 static void extract_aptsfile(void)
149 {
150 	char *file;
151 
152 	file = strrchr(path_apts, '/');
153 	if (!file) {
154 		notify.apts_file = path_apts;
155 	} else {
156 		notify.apts_file = file;
157 		notify.apts_file++;
158 	}
159 }
160 
161 /*
162  * Create the notification bar, by initializing all the variables and
163  * creating the notification window (l is the number of lines, c the
164  * number of columns, y and x are its coordinates).
165  */
notify_init_bar(void)166 void notify_init_bar(void)
167 {
168 	pthread_mutex_init(&notify.mutex, NULL);
169 	pthread_mutex_init(&notify_app.mutex, NULL);
170 	notify_app.got_app = 0;
171 	notify_app.txt = 0;
172 	notify.win =
173 	    newwin(win[NOT].h, win[NOT].w, win[NOT].y, win[NOT].x);
174 	extract_aptsfile();
175 }
176 
177 /*
178  * Free memory associated with the notify_app structure.
179  */
notify_free_app(void)180 void notify_free_app(void)
181 {
182 	notify_app.time = 0;
183 	notify_app.got_app = 0;
184 	notify_app.state = APOINT_NULL;
185 	if (notify_app.txt)
186 		mem_free(notify_app.txt);
187 	notify_app.txt = 0;
188 }
189 
190 /* Stop the notify-bar main thread. */
notify_stop_main_thread(void)191 void notify_stop_main_thread(void)
192 {
193 	/* Is the thread running? */
194 	if (pthread_equal(notify_t_main, pthread_self()))
195 		return;
196 
197 	pthread_cancel(notify_t_main);
198 	pthread_join(notify_t_main, NULL);
199 	notify_t_main = pthread_self();
200 }
201 
202 /*
203  * The calcurse window geometry has changed so we need to reset the
204  * notification window.
205  */
notify_reinit_bar(void)206 void notify_reinit_bar(void)
207 {
208 	delwin(notify.win);
209 	notify.win =
210 	    newwin(win[NOT].h, win[NOT].w, win[NOT].y, win[NOT].x);
211 }
212 
213 /* Launch user defined command as a notification. */
notify_launch_cmd(void)214 unsigned notify_launch_cmd(void)
215 {
216 	char const *arg[2] = { nbar.cmd, NULL };
217 	int pid, pin, pout, perr;
218 
219 	if (notify_app.state & APOINT_NOTIFIED)
220 		return 1;
221 
222 	notify_app.state |= APOINT_NOTIFIED;
223 
224 	if ((pid = shell_exec(&pin, &pout, &perr, 1, *arg, arg))) {
225 		close(pin);
226 		close(pout);
227 		close(perr);
228 	}
229 
230 	return 1;
231 }
232 
233 /*
234  * Update the notification bar. This is useful when changing color theme
235  * for example.
236  */
notify_update_bar(void)237 void notify_update_bar(void)
238 {
239 	const int space = 3;
240 	int file_pos, date_pos, app_pos, txt_max_len;
241 	int time_left, blinking;
242 
243 	date_pos = space;
244 	pthread_mutex_lock(&notify.mutex);
245 
246 	file_pos =
247 	    strlen(notify.date) + strlen(notify.time) + 7 + 2 * space;
248 	app_pos = file_pos + strlen(notify.apts_file) + 2 + space;
249 	txt_max_len = MAX(col - (app_pos + 12 + space), 3);
250 
251 	WINS_NBAR_LOCK;
252 	custom_apply_attr(notify.win, ATTR_HIGHEST);
253 	wattron(notify.win, A_UNDERLINE | A_REVERSE);
254 	mvwhline(notify.win, 0, 0, ACS_HLINE, col);
255 	mvwprintw(notify.win, 0, date_pos, "[ %s | %s ]", notify.date,
256 		  notify.time);
257 	mvwprintw(notify.win, 0, file_pos, "(%s)", notify.apts_file);
258 	WINS_NBAR_UNLOCK;
259 
260 	pthread_mutex_lock(&notify_app.mutex);
261 	if (notify_app.got_app) {
262 		char buf[txt_max_len * UTF8_MAXLEN];
263 
264 		strncpy(buf, notify_app.txt, txt_max_len * UTF8_MAXLEN);
265 		buf[sizeof(buf) - 1] = '\0';
266 		utf8_chop(buf, txt_max_len);
267 
268 		time_left = notify_time_left();
269 		if (time_left > 0) {
270 			int hours_left, minutes_left;
271 
272 			/* In minutes rounded up. */
273 			minutes_left = time_left / MININSEC +
274 				       (time_left % MININSEC ?  1 : 0);
275 
276 			hours_left = minutes_left / HOURINMIN;
277 			minutes_left = minutes_left % HOURINMIN;
278 
279 			pthread_mutex_lock(&nbar.mutex);
280 			blinking = time_left <= nbar.cntdwn && notify_trigger();
281 
282 			WINS_NBAR_LOCK;
283 			if (blinking)
284 				wattron(notify.win, A_BLINK);
285 			mvwprintw(notify.win, 0, app_pos,
286 				  "> %02d:%02d :: %s <", hours_left,
287 				  minutes_left, buf);
288 			if (blinking)
289 				wattroff(notify.win, A_BLINK);
290 			WINS_NBAR_UNLOCK;
291 
292 			if (blinking)
293 				notify_launch_cmd();
294 			pthread_mutex_unlock(&nbar.mutex);
295 		} else {
296 			notify_app.got_app = 0;
297 			pthread_mutex_unlock(&notify_app.mutex);
298 			pthread_mutex_unlock(&notify.mutex);
299 			notify_check_next_app(0);
300 			return;
301 		}
302 	}
303 	pthread_mutex_unlock(&notify_app.mutex);
304 
305 	WINS_NBAR_LOCK;
306 	wattroff(notify.win, A_UNDERLINE | A_REVERSE);
307 	custom_remove_attr(notify.win, ATTR_HIGHEST);
308 	WINS_NBAR_UNLOCK;
309 	wins_wrefresh(notify.win);
310 
311 	pthread_mutex_unlock(&notify.mutex);
312 }
313 
314 static void
notify_main_thread_cleanup(void * arg)315 notify_main_thread_cleanup(void *arg)
316 {
317 	pthread_mutex_trylock(&notify.mutex);
318 	pthread_mutex_unlock(&notify.mutex);
319 	pthread_mutex_trylock(&nbar.mutex);
320 	pthread_mutex_unlock(&nbar.mutex);
321 }
322 
323 /* Update the notication bar content */
324 /* ARGSUSED0 */
notify_main_thread(void * arg)325 static void *notify_main_thread(void *arg)
326 {
327 	const unsigned thread_sleep = 1;
328 	const unsigned check_app = MININSEC;
329 	int elapse = 0;
330 	int got_app;
331 	struct tm ntime;
332 	time_t ntimer;
333 
334 	elapse = 0;
335 
336 	pthread_cleanup_push(notify_main_thread_cleanup, NULL);
337 
338 	for (;;) {
339 		ntimer = time(NULL);
340 		localtime_r(&ntimer, &ntime);
341 		pthread_mutex_lock(&notify.mutex);
342 		pthread_mutex_lock(&nbar.mutex);
343 		strftime(notify.time, NOTIFY_FIELD_LENGTH, nbar.timefmt,
344 			 &ntime);
345 		strftime(notify.date, NOTIFY_FIELD_LENGTH, nbar.datefmt,
346 			 &ntime);
347 		pthread_mutex_unlock(&nbar.mutex);
348 		pthread_mutex_unlock(&notify.mutex);
349 		notify_update_bar();
350 		psleep(thread_sleep);
351 		/* Reap the user-defined notifications. */
352 		while (waitpid(0, NULL, WNOHANG) > 0)
353 			;
354 		elapse += thread_sleep;
355 		if (elapse >= check_app) {
356 			elapse = 0;
357 			pthread_mutex_lock(&notify_app.mutex);
358 			got_app = notify_app.got_app;
359 			pthread_mutex_unlock(&notify_app.mutex);
360 			if (!got_app)
361 				notify_check_next_app(0);
362 		}
363 	}
364 
365 	pthread_cleanup_pop(0);
366 	pthread_exit(NULL);
367 }
368 
369 /* Fill the given structure with information about next appointment. */
notify_get_next(struct notify_app * a)370 unsigned notify_get_next(struct notify_app *a)
371 {
372 	time_t current_time;
373 
374 	if (!a)
375 		return 0;
376 
377 	current_time = time(NULL);
378 
379 	a->time = current_time + DAYINSEC;
380 	a->got_app = 0;
381 	a->state = 0;
382 	a->txt = NULL;
383 	recur_apoint_check_next(a, current_time, get_today());
384 	apoint_check_next(a, current_time);
385 
386 	return 1;
387 }
388 
389 /*
390  * This is used for the daemon to check if we have an upcoming appointment or
391  * not.
392  */
notify_get_next_bkgd(void)393 unsigned notify_get_next_bkgd(void)
394 {
395 	struct notify_app a;
396 
397 	a.txt = NULL;
398 	if (!notify_get_next(&a))
399 		return 0;
400 
401 	if (!a.got_app) {
402 		/* No next appointment, reset the previous notified one. */
403 		notify_app.got_app = 0;
404 		return 1;
405 	} else {
406 		if (!notify_same_item(a.time))
407 			notify_update_app(a.time, a.state, a.txt);
408 	}
409 
410 	if (a.txt)
411 		mem_free(a.txt);
412 
413 	return 1;
414 }
415 
416 /* Return the description of next appointment to be notified. */
notify_app_txt(void)417 char *notify_app_txt(void)
418 {
419 	if (notify_app.got_app)
420 		return notify_app.txt;
421 	else
422 		return NULL;
423 }
424 
425 /* Look for the next appointment within the next 24 hours. */
426 /* ARGSUSED0 */
notify_thread_app(void * arg)427 static void *notify_thread_app(void *arg)
428 {
429 	struct notify_app tmp_app;
430 	int force = (arg ? 1 : 0);
431 
432 	if (!notify_get_next(&tmp_app))
433 		pthread_exit(NULL);
434 
435 	if (!tmp_app.got_app) {
436 		pthread_mutex_lock(&notify_app.mutex);
437 		notify_free_app();
438 		pthread_mutex_unlock(&notify_app.mutex);
439 	} else {
440 		if (force || !notify_same_item(tmp_app.time)) {
441 			pthread_mutex_lock(&notify_app.mutex);
442 			notify_update_app(tmp_app.time, tmp_app.state,
443 					  tmp_app.txt);
444 			pthread_mutex_unlock(&notify_app.mutex);
445 		}
446 	}
447 
448 	if (tmp_app.txt)
449 		mem_free(tmp_app.txt);
450 	notify_update_bar();
451 
452 	pthread_exit(NULL);
453 }
454 
455 /* Launch the thread notify_thread_app to look for next appointment. */
notify_check_next_app(int force)456 void notify_check_next_app(int force)
457 {
458 	pthread_t notify_t_app;
459 	void *arg = (force ? (void *)1 : NULL);
460 
461 	pthread_create(&notify_t_app, &detached_thread_attr,
462 		       notify_thread_app, arg);
463 	return;
464 }
465 
466 /* Check if the newly created appointment is to be notified. */
notify_check_added(char * mesg,time_t start,char state)467 void notify_check_added(char *mesg, time_t start, char state)
468 {
469 	time_t current_time;
470 	int update_notify = 0;
471 	long gap;
472 
473 	current_time = time(NULL);
474 	pthread_mutex_lock(&notify_app.mutex);
475 	if (!notify_app.got_app) {
476 		gap = start - current_time;
477 		if (gap >= 0 && gap <= DAYINSEC)
478 			update_notify = 1;
479 	} else if (start < notify_app.time && start >= current_time) {
480 		update_notify = 1;
481 	} else if (start == notify_app.time && state != notify_app.state) {
482 		update_notify = 1;
483 	}
484 
485 	if (update_notify) {
486 		notify_update_app(start, state, mesg);
487 	}
488 	pthread_mutex_unlock(&notify_app.mutex);
489 	notify_update_bar();
490 }
491 
492 /* Check if the newly repeated appointment is to be notified. */
notify_check_repeated(struct recur_apoint * i)493 void notify_check_repeated(struct recur_apoint *i)
494 {
495 	time_t current_time, real_app_time;
496 	int update_notify = 0;
497 
498 	current_time = time(NULL);
499 	pthread_mutex_lock(&notify_app.mutex);
500 	if (recur_item_find_occurrence
501 	    (i->start, i->dur, i->rpt, &i->exc, get_today(), &real_app_time)) {
502 		if (!notify_app.got_app) {
503 			if (real_app_time - current_time <= DAYINSEC)
504 				update_notify = 1;
505 		} else if (real_app_time < notify_app.time
506 			   && real_app_time >= current_time) {
507 			update_notify = 1;
508 		} else if (real_app_time == notify_app.time
509 			   && i->state != notify_app.state) {
510 			update_notify = 1;
511 		}
512 	}
513 	if (update_notify) {
514 		notify_update_app(real_app_time, i->state, i->mesg);
515 	}
516 	pthread_mutex_unlock(&notify_app.mutex);
517 	notify_update_bar();
518 }
519 
notify_same_item(time_t time)520 int notify_same_item(time_t time)
521 {
522 	int same = 0;
523 
524 	pthread_mutex_lock(&(notify_app.mutex));
525 	if (notify_app.got_app && notify_app.time == time)
526 		same = 1;
527 	pthread_mutex_unlock(&(notify_app.mutex));
528 
529 	return same;
530 }
531 
532 /*
533  * Check if an occurrence of a recurrent appointment is currently the "next
534  * upcoming appointment" in the notify bar.
535  */
notify_same_recur_item(struct recur_apoint * i)536 int notify_same_recur_item(struct recur_apoint *i)
537 {
538 	int same = 0;
539 	time_t item_start;
540 
541 	/* Tomorrow? */
542 	recur_item_find_occurrence(i->start, i->dur, i->rpt, &i->exc,
543 				   NEXTDAY(get_today()), &item_start);
544 	/* Today? */
545 	recur_item_find_occurrence(i->start, i->dur, i->rpt, &i->exc,
546 				   get_today(), &item_start);
547 	pthread_mutex_lock(&notify_app.mutex);
548 	if (notify_app.got_app && item_start == notify_app.time)
549 		same = 1;
550 	pthread_mutex_unlock(&(notify_app.mutex));
551 
552 	return same;
553 }
554 
555 /* Launch the notify-bar main thread. */
notify_start_main_thread(void)556 void notify_start_main_thread(void)
557 {
558         /* Avoid starting the notification bar thread twice. */
559 	notify_stop_main_thread();
560 
561 	pthread_create(&notify_t_main, NULL, notify_main_thread, NULL);
562 	notify_check_next_app(0);
563 }
564 
565 /*
566  * Print an option in the configuration menu.
567  * Specific treatment is needed depending on if the option is of type boolean
568  * (either YES or NO), or an option holding a string value.
569  */
570 static void
print_option(WINDOW * win,unsigned x,unsigned y,char * name,char * valstr,unsigned valbool,char * desc)571 print_option(WINDOW * win, unsigned x, unsigned y, char *name,
572 	     char *valstr, unsigned valbool, char *desc)
573 {
574 	const int MAXCOL = col - 3;
575 	int x_opt, len;
576 
577 	x_opt = x + strlen(name);
578 	mvwprintw(win, y, x, "%s", name);
579 	erase_window_part(win, x_opt, y, MAXCOL, y);
580 	if ((len = strlen(valstr)) != 0) {
581 		unsigned maxlen = MAX(MAXCOL - x_opt - 2, 3);
582 		char buf[maxlen * UTF8_MAXLEN];
583 
584 		strncpy(buf, valstr, maxlen * UTF8_MAXLEN);
585 		buf[maxlen * UTF8_MAXLEN - 1] = '\0';
586 		utf8_chop(buf, maxlen);
587 
588 		custom_apply_attr(win, ATTR_HIGHEST);
589 		mvwaddstr(win, y, x_opt, buf);
590 		custom_remove_attr(win, ATTR_HIGHEST);
591 	} else {
592 		print_bool_option_incolor(win, valbool, y, x_opt);
593 	}
594 	mvwaddstr(win, y + 1, x, desc);
595 }
596 
597 /* Print options related to the notify-bar. */
print_config_option(int i,WINDOW * win,int y,int hilt,void * cb_data)598 static void print_config_option(int i, WINDOW *win, int y, int hilt, void *cb_data)
599 {
600 	enum { SHOW, DATE, CLOCK, WARN, CMD, NOTIFYALL, DMON, DMON_LOG,
601 		    NB_OPT };
602 
603 	struct opt_s {
604 		char *name;
605 		char *desc;
606 		char valstr[BUFSIZ];
607 		unsigned valnum;
608 	} opt[NB_OPT];
609 
610 	opt[SHOW].name = "appearance.notifybar = ";
611 	opt[SHOW].desc =
612 	    _("(if set to YES, notify-bar will be displayed)");
613 
614 	opt[DATE].name = "format.notifydate = ";
615 	opt[DATE].desc =
616 	    _("(Format of the date to be displayed inside notify-bar)");
617 
618 	opt[CLOCK].name = "format.notifytime = ";
619 	opt[CLOCK].desc =
620 	    _("(Format of the time to be displayed inside notify-bar)");
621 
622 	opt[WARN].name = "notification.warning = ";
623 	opt[WARN].desc = _("(Warn user if an appointment is within next "
624 			   "'notify-bar_warning' seconds)");
625 
626 	opt[CMD].name = "notification.command = ";
627 	opt[CMD].desc =
628 	    _("(Command used to notify user of an upcoming appointment)");
629 
630 	opt[NOTIFYALL].name = "notification.notifyall = ";
631 	opt[NOTIFYALL].desc =
632 	    _("(Notify all appointments instead of flagged ones only)");
633 
634 	opt[DMON].name = "daemon.enable = ";
635 	opt[DMON].desc =
636 	    _("(Run in background to get notifications after exiting)");
637 
638 	opt[DMON_LOG].name = "daemon.log = ";
639 	opt[DMON_LOG].desc =
640 	    _("(Log activity when running in background)");
641 
642 	pthread_mutex_lock(&nbar.mutex);
643 
644 	/* String value options */
645 	strncpy(opt[DATE].valstr, nbar.datefmt, BUFSIZ);
646 	strncpy(opt[CLOCK].valstr, nbar.timefmt, BUFSIZ);
647 	snprintf(opt[WARN].valstr, BUFSIZ, "%d", nbar.cntdwn);
648 	strncpy(opt[CMD].valstr, nbar.cmd, BUFSIZ);
649 
650 	/* Boolean options */
651 	opt[SHOW].valnum = nbar.show;
652 	pthread_mutex_unlock(&nbar.mutex);
653 
654 	opt[DMON].valnum = dmon.enable;
655 	opt[DMON_LOG].valnum = dmon.log;
656 
657 	opt[SHOW].valstr[0] = opt[DMON].valstr[0] =
658 		opt[DMON_LOG].valstr[0] = '\0';
659 
660 	opt[NOTIFYALL].valnum = nbar.notify_all;
661 	if (opt[NOTIFYALL].valnum == NOTIFY_FLAGGED_ONLY)
662 		strcpy(opt[NOTIFYALL].valstr, "flagged-only");
663 	else if (opt[NOTIFYALL].valnum == NOTIFY_UNFLAGGED_ONLY)
664 		strcpy(opt[NOTIFYALL].valstr, "unflagged-only");
665 	else if (opt[NOTIFYALL].valnum == NOTIFY_ALL)
666 		strcpy(opt[NOTIFYALL].valstr, "all");
667 
668 	if (hilt)
669 		custom_apply_attr(win, ATTR_HIGHEST);
670 
671 	print_option(win, 1, y, opt[i].name, opt[i].valstr,
672 		     opt[i].valnum, opt[i].desc);
673 
674 	if (hilt)
675 		custom_remove_attr(win, ATTR_HIGHEST);
676 }
677 
config_option_row_type(int i,void * cb_data)678 static enum listbox_row_type config_option_row_type(int i, void *cb_data)
679 {
680 	return LISTBOX_ROW_TEXT;
681 }
682 
config_option_height(int i,void * cb_data)683 static int config_option_height(int i, void *cb_data)
684 {
685 	return 3;
686 }
687 
config_option_edit(int i)688 static void config_option_edit(int i)
689 {
690 	char *buf;
691 	const char *date_str =
692 	    _("Enter the date format (see 'man 3 strftime' for possible formats) ");
693 	const char *time_str =
694 	    _("Enter the time format (see 'man 3 strftime' for possible formats) ");
695 	const char *count_str =
696 	    _("Enter the number of seconds (0 not to be warned before an appointment)");
697 	const char *cmd_str = _("Enter the notification command ");
698 
699 	buf = mem_malloc(BUFSIZ);
700 	buf[0] = '\0';
701 
702 	switch (i) {
703 	case 0:
704 		pthread_mutex_lock(&nbar.mutex);
705 		nbar.show = !nbar.show;
706 		pthread_mutex_unlock(&nbar.mutex);
707 		if (notify_bar())
708 			notify_start_main_thread();
709 		else
710 			notify_stop_main_thread();
711 		resize = 1;
712 		break;
713 	case 1:
714 		status_mesg(date_str, "");
715 		pthread_mutex_lock(&nbar.mutex);
716 		strncpy(buf, nbar.datefmt, BUFSIZ);
717 		buf[BUFSIZ - 1] = '\0';
718 		pthread_mutex_unlock(&nbar.mutex);
719 		if (updatestring(win[STA].p, &buf, 0, 1) == 0) {
720 			pthread_mutex_lock(&nbar.mutex);
721 			strncpy(nbar.datefmt, buf, BUFSIZ);
722 			nbar.datefmt[BUFSIZ - 1] = '\0';
723 			pthread_mutex_unlock(&nbar.mutex);
724 		}
725 		break;
726 	case 2:
727 		status_mesg(time_str, "");
728 		pthread_mutex_lock(&nbar.mutex);
729 		strncpy(buf, nbar.timefmt, BUFSIZ);
730 		buf[BUFSIZ - 1] = '\0';
731 		pthread_mutex_unlock(&nbar.mutex);
732 		if (updatestring(win[STA].p, &buf, 0, 1) == 0) {
733 			pthread_mutex_lock(&nbar.mutex);
734 			strncpy(nbar.timefmt, buf, BUFSIZ);
735 			nbar.timefmt[BUFSIZ - 1] = '\0';
736 			pthread_mutex_unlock(&nbar.mutex);
737 		}
738 		break;
739 	case 3:
740 		status_mesg(count_str, "");
741 		pthread_mutex_lock(&nbar.mutex);
742 		snprintf(buf, BUFSIZ, "%d", nbar.cntdwn);
743 		pthread_mutex_unlock(&nbar.mutex);
744 		if (updatestring(win[STA].p, &buf, 0, 1) == 0 &&
745 		    is_all_digit(buf) && atoi(buf) >= 0
746 		    && atoi(buf) <= DAYINSEC) {
747 			pthread_mutex_lock(&nbar.mutex);
748 			nbar.cntdwn = atoi(buf);
749 			pthread_mutex_unlock(&nbar.mutex);
750 		}
751 		break;
752 	case 4:
753 		status_mesg(cmd_str, "");
754 		pthread_mutex_lock(&nbar.mutex);
755 		strncpy(buf, nbar.cmd, BUFSIZ);
756 		buf[BUFSIZ - 1] = '\0';
757 		pthread_mutex_unlock(&nbar.mutex);
758 		if (updatestring(win[STA].p, &buf, 0, 1) == 0) {
759 			pthread_mutex_lock(&nbar.mutex);
760 			strncpy(nbar.cmd, buf, BUFSIZ);
761 			nbar.cmd[BUFSIZ - 1] = '\0';
762 			pthread_mutex_unlock(&nbar.mutex);
763 		}
764 		break;
765 	case 5:
766 		pthread_mutex_lock(&nbar.mutex);
767 		nbar.notify_all = (nbar.notify_all + 1) % 3;
768 		pthread_mutex_unlock(&nbar.mutex);
769 		notify_check_next_app(1);
770 		break;
771 	case 6:
772 		dmon.enable = !dmon.enable;
773 		break;
774 	case 7:
775 		dmon.log = !dmon.log;
776 		break;
777 	}
778 
779 	mem_free(buf);
780 }
781 
782 /* Notify-bar configuration. */
notify_config_bar(void)783 void notify_config_bar(void)
784 {
785 	static int bindings[] = {
786 		KEY_GENERIC_QUIT, KEY_MOVE_UP, KEY_MOVE_DOWN, KEY_EDIT_ITEM
787 	};
788 	struct listbox lb;
789 	int key;
790 
791 	clear();
792 	listbox_init(&lb, 0, 0, notify_bar() ? row - 3 : row - 2, col,
793 		     _("notification options"), config_option_row_type,
794 		     config_option_height, print_config_option);
795 	listbox_load_items(&lb, 8);
796 	listbox_draw_deco(&lb, 0);
797 	listbox_display(&lb, NOHILT);
798 	wins_set_bindings(bindings, ARRAY_SIZE(bindings));
799 	wins_status_bar();
800 	wnoutrefresh(win[STA].p);
801 	wmove(win[STA].p, 0, 0);
802 	wins_doupdate();
803 
804 	while ((key = keys_get(win[KEY].p, NULL, NULL)) != KEY_GENERIC_QUIT) {
805 		switch (key) {
806 		case KEY_MOVE_DOWN:
807 			listbox_sel_move(&lb, 1);
808 			break;
809 		case KEY_MOVE_UP:
810 			listbox_sel_move(&lb, -1);
811 			break;
812 		case KEY_EDIT_ITEM:
813 			config_option_edit(listbox_get_sel(&lb));
814 			break;
815 		}
816 
817 		if (resize) {
818 			resize = 0;
819 			wins_get_config();
820 			wins_reset_noupdate();
821 			listbox_resize(&lb, 0, 0, notify_bar() ? row - 3 : row - 2, col);
822 			listbox_draw_deco(&lb, 0);
823 			delwin(win[STA].p);
824 			win[STA].p =
825 			    newwin(win[STA].h, win[STA].w, win[STA].y,
826 				   win[STA].x);
827 			keypad(win[STA].p, TRUE);
828 			if (notify_bar()) {
829 				notify_reinit_bar();
830 				notify_update_bar();
831 			}
832 			clearok(curscr, TRUE);
833 		}
834 
835 		listbox_display(&lb, NOHILT);
836 		wins_status_bar();
837 		wnoutrefresh(win[STA].p);
838 		wmove(win[STA].p, 0, 0);
839 		wins_doupdate();
840 	}
841 
842 	listbox_delete(&lb);
843 }
844