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(¬ify.mutex, NULL);
169 pthread_mutex_init(¬ify_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(¬ify.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(¬ify_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(¬ify_app.mutex);
298 pthread_mutex_unlock(¬ify.mutex);
299 notify_check_next_app(0);
300 return;
301 }
302 }
303 pthread_mutex_unlock(¬ify_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(¬ify.mutex);
312 }
313
314 static void
notify_main_thread_cleanup(void * arg)315 notify_main_thread_cleanup(void *arg)
316 {
317 pthread_mutex_trylock(¬ify.mutex);
318 pthread_mutex_unlock(¬ify.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(¬ify.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(¬ify.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(¬ify_app.mutex);
358 got_app = notify_app.got_app;
359 pthread_mutex_unlock(¬ify_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(¬ify_app.mutex);
437 notify_free_app();
438 pthread_mutex_unlock(¬ify_app.mutex);
439 } else {
440 if (force || !notify_same_item(tmp_app.time)) {
441 pthread_mutex_lock(¬ify_app.mutex);
442 notify_update_app(tmp_app.time, tmp_app.state,
443 tmp_app.txt);
444 pthread_mutex_unlock(¬ify_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(¬ify_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(¬ify_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(¬ify_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(¬ify_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(¬ify_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(¬ify_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(¬ify_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