When adding a key already in use for another action, a warning box is displayed. The text length is limited by the window width through the use of strncpy(). If the limit is exceeded, the string will have no null termination, resulting in unpredictable behaviour. A similar problem in fatalbox() is fixed as well. Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk> Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
1832 lines
38 KiB
C
1832 lines
38 KiB
C
/*
|
|
* Calcurse - text-based organizer
|
|
*
|
|
* Copyright (c) 2004-2017 calcurse Development Team <misc@calcurse.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* - Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
*
|
|
* - Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* Send your feedback or comments to : misc@calcurse.org
|
|
* Calcurse home page : http://calcurse.org
|
|
*
|
|
*/
|
|
|
|
#include <time.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <termios.h>
|
|
|
|
#include "calcurse.h"
|
|
#include "sha1.h"
|
|
|
|
#define ISLEAP(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
|
|
|
|
#define FS_EXT_MAXLEN 64
|
|
|
|
enum format_specifier {
|
|
FS_STARTDATE,
|
|
FS_DURATION,
|
|
FS_ENDDATE,
|
|
FS_REMAINING,
|
|
FS_MESSAGE,
|
|
FS_NOTE,
|
|
FS_NOTEFILE,
|
|
FS_PRIORITY,
|
|
FS_RAW,
|
|
FS_HASH,
|
|
FS_PSIGN,
|
|
FS_EOF,
|
|
FS_UNKNOWN
|
|
};
|
|
|
|
/* General routine to exit calcurse properly. */
|
|
void exit_calcurse(int status)
|
|
{
|
|
int was_interactive;
|
|
|
|
ui_calendar_stop_date_thread();
|
|
io_stop_psave_thread();
|
|
|
|
if (ui_mode == UI_CURSES) {
|
|
notify_stop_main_thread();
|
|
clear();
|
|
wins_refresh();
|
|
endwin();
|
|
ui_mode = UI_CMDLINE;
|
|
was_interactive = 1;
|
|
} else {
|
|
was_interactive = 0;
|
|
}
|
|
|
|
free_user_data();
|
|
keys_free();
|
|
mem_stats();
|
|
|
|
if (was_interactive) {
|
|
if (unlink(path_cpid) != 0)
|
|
EXIT(_("Could not remove calcurse lock file: %s\n"),
|
|
strerror(errno));
|
|
if (dmon.enable)
|
|
dmon_start(status);
|
|
}
|
|
|
|
exit(status);
|
|
}
|
|
|
|
void free_user_data(void)
|
|
{
|
|
unsigned i;
|
|
|
|
day_free_vector();
|
|
event_llist_free();
|
|
apoint_llist_free();
|
|
recur_apoint_llist_free();
|
|
recur_event_llist_free();
|
|
for (i = 0; i <= 37; i++)
|
|
ui_day_item_cut_free(i);
|
|
todo_free_list();
|
|
notify_free_app();
|
|
}
|
|
|
|
/* Function to exit on internal error. */
|
|
void fatalbox(const char *errmsg)
|
|
{
|
|
WINDOW *errwin;
|
|
const char *label = _("/!\\ INTERNAL ERROR /!\\");
|
|
const char *reportmsg = _("Please report the following bug:");
|
|
const int WINROW = 10;
|
|
const int WINCOL = col - 2;
|
|
const int MSGLEN = WINCOL - 2;
|
|
char msg[MSGLEN];
|
|
|
|
if (errmsg == NULL)
|
|
return;
|
|
|
|
strncpy(msg, errmsg, MSGLEN);
|
|
msg[MSGLEN - 1] = '\0';
|
|
errwin =
|
|
newwin(WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2);
|
|
custom_apply_attr(errwin, ATTR_HIGHEST);
|
|
box(errwin, 0, 0);
|
|
wins_show(errwin, label);
|
|
mvwaddstr(errwin, 3, 1, reportmsg);
|
|
mvwaddstr(errwin, 5, (WINCOL - strlen(msg)) / 2, msg);
|
|
custom_remove_attr(errwin, ATTR_HIGHEST);
|
|
wins_wrefresh(errwin);
|
|
wgetch(errwin);
|
|
delwin(errwin);
|
|
wins_doupdate();
|
|
}
|
|
|
|
void warnbox(const char *msg)
|
|
{
|
|
WINDOW *warnwin;
|
|
const char *label = "/!\\";
|
|
const int WINROW = 10;
|
|
const int WINCOL = col - 2;
|
|
const int MSGLEN = WINCOL - 2;
|
|
char displmsg[MSGLEN];
|
|
|
|
if (msg == NULL)
|
|
return;
|
|
|
|
strncpy(displmsg, msg, MSGLEN);
|
|
displmsg[MSGLEN - 1] = '\0';
|
|
warnwin =
|
|
newwin(WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2);
|
|
custom_apply_attr(warnwin, ATTR_HIGHEST);
|
|
box(warnwin, 0, 0);
|
|
wins_show(warnwin, label);
|
|
mvwaddstr(warnwin, 5, (WINCOL - strlen(displmsg)) / 2, displmsg);
|
|
custom_remove_attr(warnwin, ATTR_HIGHEST);
|
|
wins_wrefresh(warnwin);
|
|
wgetch(warnwin);
|
|
delwin(warnwin);
|
|
wins_doupdate();
|
|
}
|
|
|
|
/*
|
|
* Print a message in the status bar.
|
|
* Message texts for first line and second line are to be provided.
|
|
*/
|
|
void status_mesg(const char *msg1, const char *msg2)
|
|
{
|
|
wins_erase_status_bar();
|
|
custom_apply_attr(win[STA].p, ATTR_HIGHEST);
|
|
mvwaddstr(win[STA].p, 0, 0, msg1);
|
|
mvwaddstr(win[STA].p, 1, 0, msg2);
|
|
custom_remove_attr(win[STA].p, ATTR_HIGHEST);
|
|
wins_wrefresh(win[STA].p);
|
|
}
|
|
|
|
/*
|
|
* Prompts the user to make a choice between named alternatives.
|
|
*
|
|
* The available choices are described by a string of the form
|
|
* "[ynp]". The first and last char are ignored (they are only here to
|
|
* make the translators' life easier), and every other char indicates
|
|
* a key the user is allowed to press.
|
|
*
|
|
* Returns the index of the key pressed by the user (starting from 1),
|
|
* or -1 if the user doesn't want to answer (e.g. by escaping).
|
|
*/
|
|
int status_ask_choice(const char *message, const char choice[],
|
|
int nb_choice)
|
|
{
|
|
/* "[4/2/f/t/w/.../Z] " */
|
|
char avail_choice[2 * nb_choice + 3];
|
|
int i, ch;
|
|
|
|
avail_choice[0] = '[';
|
|
for (i = 1; i <= nb_choice; i++) {
|
|
avail_choice[i * 2 - 1] = choice[i];
|
|
avail_choice[i * 2] = '/';
|
|
}
|
|
avail_choice[nb_choice * 2] = ']';
|
|
avail_choice[nb_choice * 2 + 1] = '\0';
|
|
|
|
status_mesg(message, avail_choice);
|
|
|
|
for (;;) {
|
|
ch = wgetch(win[KEY].p);
|
|
for (i = 1; i <= nb_choice; i++)
|
|
if (ch == choice[i])
|
|
return i;
|
|
if (ch == ESCAPE)
|
|
return (-1);
|
|
if (resize) {
|
|
resize = 0;
|
|
wins_reset();
|
|
status_mesg(message, avail_choice);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Prompts the user with a boolean question.
|
|
*
|
|
* Returns 1 if yes, 2 if no, and -1 otherwise
|
|
*/
|
|
int status_ask_bool(const char *msg)
|
|
{
|
|
return (status_ask_choice(msg, _("[yn]"), 2));
|
|
}
|
|
|
|
/*
|
|
* Prompts the user to make a choice between a number of alternatives.
|
|
*
|
|
* Returns the option chosen by the user (starting from 1), or -1 if
|
|
* the user doesn't want to answer.
|
|
*/
|
|
int
|
|
status_ask_simplechoice(const char *prefix, const char *choice[],
|
|
int nb_choice)
|
|
{
|
|
int i;
|
|
char *tmp;
|
|
/* "(1) Choice1, (2) Choice2, (3) Choice3?" */
|
|
char choicestr[BUFSIZ];
|
|
/* Holds the characters to choose from ('1', '2', etc) */
|
|
char char_choice[nb_choice + 2];
|
|
|
|
/* No need to initialize first and last char. */
|
|
for (i = 1; i <= nb_choice; i++)
|
|
char_choice[i] = '0' + i;
|
|
|
|
strcpy(choicestr, prefix);
|
|
|
|
for (i = 0; i < nb_choice; i++) {
|
|
asprintf(&tmp,
|
|
((i + 1) == nb_choice) ? "(%d) %s?" : "(%d) %s, ",
|
|
(i + 1), choice[i]);
|
|
strcat(choicestr, tmp);
|
|
mem_free(tmp);
|
|
}
|
|
|
|
return (status_ask_choice(choicestr, char_choice, nb_choice));
|
|
}
|
|
|
|
/* Erase part of a window. */
|
|
void
|
|
erase_window_part(WINDOW * win, int first_col, int first_row, int last_col,
|
|
int last_row)
|
|
{
|
|
int c, r;
|
|
|
|
for (r = first_row; r <= last_row; r++) {
|
|
for (c = first_col; c <= last_col; c++)
|
|
mvwaddstr(win, r, c, " ");
|
|
}
|
|
}
|
|
|
|
/* draws a popup window */
|
|
WINDOW *popup(int pop_row, int pop_col, int pop_y, int pop_x,
|
|
const char *title, const char *msg, int hint)
|
|
{
|
|
const char *any_key = _("Press any key to continue...");
|
|
WINDOW *popup_win;
|
|
const int MSGXPOS = 5;
|
|
|
|
popup_win = newwin(pop_row, pop_col, pop_y, pop_x);
|
|
keypad(popup_win, TRUE);
|
|
if (msg)
|
|
mvwaddstr(popup_win, MSGXPOS, (pop_col - strlen(msg)) / 2,
|
|
msg);
|
|
custom_apply_attr(popup_win, ATTR_HIGHEST);
|
|
box(popup_win, 0, 0);
|
|
wins_show(popup_win, title);
|
|
if (hint)
|
|
mvwaddstr(popup_win, pop_row - 2,
|
|
pop_col - (strlen(any_key) + 1), any_key);
|
|
custom_remove_attr(popup_win, ATTR_HIGHEST);
|
|
wins_wrefresh(popup_win);
|
|
|
|
return popup_win;
|
|
}
|
|
|
|
/* prints in middle of a panel */
|
|
void
|
|
print_in_middle(WINDOW * win, int starty, int startx, int width,
|
|
const char *string)
|
|
{
|
|
int len = strlen(string);
|
|
int x, y;
|
|
|
|
win = win ? win : stdscr;
|
|
getyx(win, y, x);
|
|
x = startx ? startx : x;
|
|
y = starty ? starty : y;
|
|
width = width ? width : 80;
|
|
|
|
x += (width - len) / 2;
|
|
|
|
custom_apply_attr(win, ATTR_HIGHEST);
|
|
mvwaddstr(win, y, x, string);
|
|
custom_remove_attr(win, ATTR_HIGHEST);
|
|
}
|
|
|
|
/* checks if a string is only made of digits */
|
|
int is_all_digit(const char *string)
|
|
{
|
|
for (; *string; string++) {
|
|
if (!isdigit((int)*string))
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Given an item date expressed in seconds, return its start time in seconds. */
|
|
long get_item_time(long date)
|
|
{
|
|
return (long)(get_item_hour(date) * HOURINSEC +
|
|
get_item_min(date) * MININSEC);
|
|
}
|
|
|
|
int get_item_hour(long date)
|
|
{
|
|
struct tm lt;
|
|
|
|
localtime_r((time_t *) & date, <);
|
|
return lt.tm_hour;
|
|
}
|
|
|
|
int get_item_min(long date)
|
|
{
|
|
struct tm lt;
|
|
|
|
localtime_r((time_t *) & date, <);
|
|
return lt.tm_min;
|
|
}
|
|
|
|
struct tm date2tm(struct date day, unsigned hour, unsigned min)
|
|
{
|
|
time_t t = now();
|
|
struct tm start;
|
|
|
|
localtime_r(&t, &start);
|
|
|
|
start.tm_mon = day.mm - 1;
|
|
start.tm_mday = day.dd;
|
|
start.tm_year = day.yyyy - 1900;
|
|
start.tm_hour = hour;
|
|
start.tm_min = min;
|
|
start.tm_sec = 0;
|
|
start.tm_isdst = -1;
|
|
|
|
return start;
|
|
}
|
|
|
|
time_t date2sec(struct date day, unsigned hour, unsigned min)
|
|
{
|
|
struct tm start = date2tm(day, hour, min);
|
|
time_t t = mktime(&start);
|
|
|
|
EXIT_IF(t == -1, _("failure in mktime"));
|
|
|
|
return t;
|
|
}
|
|
|
|
time_t utcdate2sec(struct date day, unsigned hour, unsigned min)
|
|
{
|
|
char *tz;
|
|
time_t t;
|
|
|
|
tz = getenv("TZ");
|
|
if (tz)
|
|
tz = mem_strdup(tz);
|
|
setenv("TZ", "", 1);
|
|
tzset();
|
|
|
|
t = date2sec(day, hour, min);
|
|
|
|
if (tz) {
|
|
setenv("TZ", tz, 1);
|
|
mem_free(tz);
|
|
} else {
|
|
unsetenv("TZ");
|
|
}
|
|
tzset();
|
|
|
|
return t;
|
|
}
|
|
|
|
/* Compare two dates (without comparing times). */
|
|
int date_cmp_day(time_t d1, time_t d2)
|
|
{
|
|
struct tm lt1, lt2;
|
|
|
|
localtime_r((time_t *)&d1, <1);
|
|
localtime_r((time_t *)&d2, <2);
|
|
|
|
if (lt1.tm_year < lt2.tm_year)
|
|
return -1;
|
|
if (lt1.tm_year > lt2.tm_year)
|
|
return 1;
|
|
if (lt1.tm_mon < lt2.tm_mon)
|
|
return -1;
|
|
if (lt1.tm_mon > lt2.tm_mon)
|
|
return 1;
|
|
if (lt1.tm_mday < lt2.tm_mday)
|
|
return -1;
|
|
if (lt1.tm_mday > lt2.tm_mday)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Return a string containing the date, given a date in seconds. */
|
|
char *date_sec2date_str(long sec, const char *datefmt)
|
|
{
|
|
struct tm lt;
|
|
char *datestr = (char *)mem_calloc(BUFSIZ, sizeof(char));
|
|
|
|
if (sec == 0) {
|
|
strncpy(datestr, "0", BUFSIZ);
|
|
} else {
|
|
localtime_r((time_t *) & sec, <);
|
|
strftime(datestr, BUFSIZ, datefmt, <);
|
|
}
|
|
|
|
return datestr;
|
|
}
|
|
|
|
/* Generic function to format date. */
|
|
void date_sec2date_fmt(long sec, const char *fmt, char *datef)
|
|
{
|
|
#if ENABLE_NLS
|
|
/* TODO: Find a better way to deal with localization and strftime(). */
|
|
char *locale_old = mem_strdup(setlocale(LC_ALL, NULL));
|
|
setlocale(LC_ALL, "C");
|
|
#endif
|
|
|
|
struct tm lt;
|
|
localtime_r((time_t *) & sec, <);
|
|
strftime(datef, BUFSIZ, fmt, <);
|
|
|
|
#if ENABLE_NLS
|
|
setlocale(LC_ALL, locale_old);
|
|
mem_free(locale_old);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Used to change date by adding a certain amount of days or weeks.
|
|
* Returns 0 on success, 1 otherwise.
|
|
*/
|
|
int date_change(struct tm *date, int delta_month, int delta_day)
|
|
{
|
|
struct tm t;
|
|
|
|
t = *date;
|
|
t.tm_mon += delta_month;
|
|
t.tm_mday += delta_day;
|
|
|
|
if (mktime(&t) == -1) {
|
|
return 1;
|
|
} else {
|
|
*date = t;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Used to change date by adding a certain amount of days or weeks.
|
|
*/
|
|
long date_sec_change(long date, int delta_month, int delta_day)
|
|
{
|
|
struct tm lt;
|
|
time_t t;
|
|
|
|
t = date;
|
|
localtime_r(&t, <);
|
|
lt.tm_mon += delta_month;
|
|
lt.tm_mday += delta_day;
|
|
lt.tm_isdst = -1;
|
|
t = mktime(<);
|
|
EXIT_IF(t == -1, _("failure in mktime"));
|
|
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
* Return a long containing the date which is updated taking into account
|
|
* the new time and date entered by the user.
|
|
*/
|
|
long update_time_in_date(long date, unsigned hr, unsigned mn)
|
|
{
|
|
struct tm lt;
|
|
time_t t, new_date;
|
|
|
|
t = date;
|
|
localtime_r(&t, <);
|
|
lt.tm_hour = hr;
|
|
lt.tm_min = mn;
|
|
lt.tm_sec = 0;
|
|
new_date = mktime(<);
|
|
EXIT_IF(new_date == -1, _("error in mktime"));
|
|
|
|
return new_date;
|
|
}
|
|
|
|
/*
|
|
* Returns the date in seconds from year 1900.
|
|
* If no date is entered, current date is chosen.
|
|
*/
|
|
time_t get_sec_date(struct date date)
|
|
{
|
|
struct tm ptrtime;
|
|
time_t timer;
|
|
char current_day[] = "dd ";
|
|
char current_month[] = "mm ";
|
|
char current_year[] = "yyyy ";
|
|
|
|
if (date.yyyy == 0 && date.mm == 0 && date.dd == 0) {
|
|
timer = time(NULL);
|
|
localtime_r(&timer, &ptrtime);
|
|
strftime(current_day, strlen(current_day), "%d", &ptrtime);
|
|
strftime(current_month, strlen(current_month), "%m", &ptrtime);
|
|
strftime(current_year, strlen(current_year), "%Y", &ptrtime);
|
|
date.mm = atoi(current_month);
|
|
date.dd = atoi(current_day);
|
|
date.yyyy = atoi(current_year);
|
|
}
|
|
|
|
return date2sec(date, 0, 0);
|
|
}
|
|
|
|
long min2sec(unsigned minutes)
|
|
{
|
|
return minutes * MININSEC;
|
|
}
|
|
|
|
/*
|
|
* Display a scroll bar when there are so many items that they
|
|
* can not be displayed inside the corresponding panel.
|
|
*/
|
|
void
|
|
draw_scrollbar(WINDOW * win, int y, int x, int length,
|
|
int bar_top, int bar_bottom, unsigned hilt)
|
|
{
|
|
mvwvline(win, bar_top, x, ACS_VLINE, bar_bottom - bar_top + 1);
|
|
if (hilt)
|
|
custom_apply_attr(win, ATTR_HIGHEST);
|
|
wattron(win, A_REVERSE);
|
|
mvwvline(win, y, x, ' ', length);
|
|
wattroff(win, A_REVERSE);
|
|
if (hilt)
|
|
custom_remove_attr(win, ATTR_HIGHEST);
|
|
}
|
|
|
|
/*
|
|
* Print an item (either an appointment, event, or todo) in a
|
|
* popup window. This is useful if an item description is too
|
|
* long to fit in its corresponding panel window.
|
|
*/
|
|
void
|
|
item_in_popup(const char *a_start, const char *a_end, const char *msg,
|
|
const char *pop_title)
|
|
{
|
|
WINDOW *popup_win, *pad;
|
|
const int margin_left = 4, margin_top = 4;
|
|
const int winl = row - 5, winw = col - margin_left;
|
|
const int padl = winl - 2, padw = winw - margin_left;
|
|
|
|
pad = newpad(padl, padw);
|
|
popup_win = popup(winl, winw, 1, 2, pop_title, NULL, 1);
|
|
if (a_start && a_end) {
|
|
mvwprintw(popup_win, margin_top, margin_left, "- %s -> %s",
|
|
a_start, a_end);
|
|
}
|
|
mvwaddstr(pad, 0, margin_left, msg);
|
|
wmove(win[STA].p, 0, 0);
|
|
pnoutrefresh(pad, 0, 0, margin_top + 2, margin_left, padl, winw);
|
|
wins_doupdate();
|
|
wgetch(popup_win);
|
|
delwin(pad);
|
|
delwin(popup_win);
|
|
}
|
|
|
|
/* Returns the beginning of current day in seconds from 1900. */
|
|
time_t get_today(void)
|
|
{
|
|
struct tm lt;
|
|
time_t current_time;
|
|
struct date day;
|
|
|
|
current_time = time(NULL);
|
|
localtime_r(¤t_time, <);
|
|
day.mm = lt.tm_mon + 1;
|
|
day.dd = lt.tm_mday;
|
|
day.yyyy = lt.tm_year + 1900;
|
|
|
|
return date2sec(day, 0, 0);
|
|
}
|
|
|
|
/* Returns the current time in seconds. */
|
|
long now(void)
|
|
{
|
|
return (long)time(NULL);
|
|
}
|
|
|
|
char *nowstr(void)
|
|
{
|
|
struct tm lt;
|
|
static char buf[BUFSIZ];
|
|
time_t t = now();
|
|
|
|
localtime_r(&t, <);
|
|
strftime(buf, sizeof buf, "%a %b %d %T %Y", <);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/* Print the given option value with appropriate color. */
|
|
void
|
|
print_bool_option_incolor(WINDOW * win, unsigned option, int pos_y,
|
|
int pos_x)
|
|
{
|
|
int color = 0;
|
|
const char *option_value;
|
|
|
|
if (option == 1) {
|
|
color = ATTR_TRUE;
|
|
option_value = _("yes");
|
|
} else if (option == 0) {
|
|
color = ATTR_FALSE;
|
|
option_value = _("no");
|
|
} else {
|
|
EXIT(_("option not defined"));
|
|
}
|
|
|
|
custom_apply_attr(win, color);
|
|
mvwaddstr(win, pos_y, pos_x, option_value);
|
|
custom_remove_attr(win, color);
|
|
wnoutrefresh(win);
|
|
wins_doupdate();
|
|
}
|
|
|
|
/*
|
|
* Get the name of the default directory for temporary files.
|
|
*/
|
|
const char *get_tempdir(void)
|
|
{
|
|
if (getenv("TMPDIR"))
|
|
return getenv("TMPDIR");
|
|
#ifdef P_tmpdir
|
|
else if (P_tmpdir)
|
|
return P_tmpdir;
|
|
#endif
|
|
else
|
|
return "/tmp";
|
|
}
|
|
|
|
/*
|
|
* Create a new unique file, and return a newly allocated string which contains
|
|
* the random part of the file name.
|
|
*/
|
|
char *new_tempfile(const char *prefix)
|
|
{
|
|
char *fullname;
|
|
int fd;
|
|
FILE *file;
|
|
|
|
if (prefix == NULL)
|
|
return NULL;
|
|
|
|
asprintf(&fullname, "%s.XXXXXX", prefix);
|
|
if ((fd = mkstemp(fullname)) == -1
|
|
|| (file = fdopen(fd, "w+")) == NULL) {
|
|
if (fd != -1) {
|
|
unlink(fullname);
|
|
close(fd);
|
|
}
|
|
ERROR_MSG(_("temporary file \"%s\" could not be created"),
|
|
fullname);
|
|
|
|
mem_free(fullname);
|
|
return NULL;
|
|
}
|
|
fclose(file);
|
|
|
|
return fullname;
|
|
}
|
|
|
|
static void get_ymd(int *year, int *month, int *day, time_t t)
|
|
{
|
|
struct tm tm;
|
|
|
|
localtime_r(&t, &tm);
|
|
*day = tm.tm_mday;
|
|
*month = tm.tm_mon + 1;
|
|
*year = tm.tm_year + 1900;
|
|
}
|
|
|
|
static void get_weekday_ymd(int *year, int *month, int *day, int weekday)
|
|
{
|
|
time_t t = get_today();
|
|
struct tm tm;
|
|
int delta;
|
|
|
|
localtime_r(&t, &tm);
|
|
delta = weekday - tm.tm_wday;
|
|
t = date_sec_change(t, 0, delta > 0 ? delta : 7);
|
|
|
|
localtime_r(&t, &tm);
|
|
*day = tm.tm_mday;
|
|
*month = tm.tm_mon + 1;
|
|
*year = tm.tm_year + 1900;
|
|
}
|
|
|
|
/*
|
|
* Check if a date is valid.
|
|
*/
|
|
int check_date(unsigned year, unsigned month, unsigned day)
|
|
{
|
|
return (year >= 1902 && month >= 1 && month <= 12 && day >= 1 &&
|
|
day <= days[month - 1] + (month == 2 && ISLEAP(year)) ? 1 : 0);
|
|
}
|
|
|
|
/*
|
|
* Convert a string containing a date into three integers containing the year,
|
|
* month and day.
|
|
*
|
|
* If a pointer to a date structure containing the current date is passed as
|
|
* last parameter ("slctd_date"), the function will accept several short forms,
|
|
* e.g. "26" for the 26th of the current month/year or "3/1" for Mar 01 (or Jan
|
|
* 03, depending on the date format) of the current year. If a null pointer is
|
|
* passed, short forms won't be accepted at all.
|
|
*
|
|
* Returns 1 if sucessfully converted or 0 if the string is an invalid date.
|
|
*/
|
|
int
|
|
parse_date(const char *date_string, enum datefmt datefmt, int *year,
|
|
int *month, int *day, struct date *slctd_date)
|
|
{
|
|
const char sep = (datefmt == DATEFMT_ISO) ? '-' : '/';
|
|
const char *p;
|
|
int in[3] = { 0, 0, 0 }, n = 0;
|
|
int d, m, y;
|
|
|
|
if (!date_string)
|
|
return 0;
|
|
|
|
if (!strcasecmp(date_string, "today")) {
|
|
get_ymd(year, month, day, get_today());
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "yesterday")) {
|
|
get_ymd(year, month, day, date_sec_change(get_today(), 0, -1));
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "tomorrow")) {
|
|
get_ymd(year, month, day, date_sec_change(get_today(), 0, 1));
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "now")) {
|
|
get_ymd(year, month, day, now());
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "sunday") ||
|
|
!strcasecmp(date_string, "sun")) {
|
|
get_weekday_ymd(year, month, day, 0);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "monday") ||
|
|
!strcasecmp(date_string, "mon")) {
|
|
get_weekday_ymd(year, month, day, 1);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "tuesday") ||
|
|
!strcasecmp(date_string, "tue")) {
|
|
get_weekday_ymd(year, month, day, 2);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "wednesday") ||
|
|
!strcasecmp(date_string, "wed")) {
|
|
get_weekday_ymd(year, month, day, 3);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "thursday") ||
|
|
!strcasecmp(date_string, "thu")) {
|
|
get_weekday_ymd(year, month, day, 4);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "friday") ||
|
|
!strcasecmp(date_string, "fri")) {
|
|
get_weekday_ymd(year, month, day, 5);
|
|
return 1;
|
|
} else if (!strcasecmp(date_string, "saturday") ||
|
|
!strcasecmp(date_string, "sat")) {
|
|
get_weekday_ymd(year, month, day, 6);
|
|
return 1;
|
|
}
|
|
|
|
/* parse string into in[], read up to three integers */
|
|
for (p = date_string; *p; p++) {
|
|
if (*p == sep) {
|
|
if ((++n) > 2)
|
|
return 0;
|
|
} else if ((*p >= '0') && (*p <= '9')) {
|
|
in[n] = in[n] * 10 + (int)(*p - '0');
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ((!slctd_date && n < 2) || in[n] == 0)
|
|
return 0;
|
|
|
|
/* convert into day, month and year, depending on the date format */
|
|
switch (datefmt) {
|
|
case DATEFMT_MMDDYYYY:
|
|
m = (n >= 1) ? in[0] : 0;
|
|
d = (n >= 1) ? in[1] : in[0];
|
|
y = in[2];
|
|
break;
|
|
case DATEFMT_DDMMYYYY:
|
|
d = in[0];
|
|
m = in[1];
|
|
y = in[2];
|
|
break;
|
|
case DATEFMT_YYYYMMDD:
|
|
case DATEFMT_ISO:
|
|
y = (n >= 2) ? in[n - 2] : 0;
|
|
m = (n >= 1) ? in[n - 1] : 0;
|
|
d = in[n];
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
if (slctd_date) {
|
|
if (y > 0 && y < 100) {
|
|
/* convert "YY" format into "YYYY" */
|
|
y += slctd_date->yyyy - slctd_date->yyyy % 100;
|
|
} else if (n < 2) {
|
|
/* set year and, optionally, month if short from is used */
|
|
y = slctd_date->yyyy;
|
|
if (n < 1)
|
|
m = slctd_date->mm;
|
|
}
|
|
}
|
|
|
|
/* check if date is valid, take leap years into account */
|
|
if (!check_date(y, m, d))
|
|
return 0;
|
|
|
|
if (year)
|
|
*year = y;
|
|
if (month)
|
|
*month = m;
|
|
if (day)
|
|
*day = d;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Convert a date duration string into a number of days.
|
|
*
|
|
* Returns 1 on success and 0 on failure.
|
|
*/
|
|
int parse_date_duration(const char *string, unsigned *days)
|
|
{
|
|
enum {
|
|
STATE_INITIAL,
|
|
STATE_WWDD_DD,
|
|
STATE_DONE
|
|
} state = STATE_INITIAL;
|
|
|
|
const char *p;
|
|
unsigned in = 0, frac = 0, denom = 1;
|
|
unsigned dur = 0;
|
|
|
|
if (!string || *string == '\0')
|
|
return 0;
|
|
|
|
/* parse string using a simple state machine */
|
|
for (p = string; *p; p++) {
|
|
if (state == STATE_DONE) {
|
|
return 0;
|
|
} else if ((*p >= '0') && (*p <= '9')) {
|
|
in = in * 10 + (int)(*p - '0');
|
|
if (frac)
|
|
denom *= 10;
|
|
} else if (*p == '.') {
|
|
if (frac)
|
|
return 0;
|
|
frac++;
|
|
} else {
|
|
switch (state) {
|
|
case STATE_INITIAL:
|
|
if (*p == 'w') {
|
|
dur += in * WEEKINDAYS / denom;
|
|
state = STATE_WWDD_DD;
|
|
} else if (*p == 'd') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
case STATE_WWDD_DD:
|
|
if (*p == 'd') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
in = frac = 0;
|
|
denom = 1;
|
|
}
|
|
}
|
|
|
|
if (state == STATE_DONE && in > 0)
|
|
return 0;
|
|
|
|
dur += in;
|
|
*days = dur;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Check if time is valid.
|
|
*/
|
|
int check_time(unsigned hours, unsigned minutes)
|
|
{
|
|
return (hours < DAYINHOURS && minutes < HOURINMIN);
|
|
}
|
|
|
|
/*
|
|
* Converts a time string into hours and minutes. Short forms like "23:"
|
|
* (23:00) or ":45" (0:45) are allowed.
|
|
*
|
|
* Returns 1 on success and 0 on failure.
|
|
*/
|
|
int parse_time(const char *string, unsigned *hour, unsigned *minute)
|
|
{
|
|
const char *p;
|
|
unsigned in[2] = { 0, 0 }, n = 0;
|
|
|
|
if (!string)
|
|
return 0;
|
|
|
|
/* parse string into in[], read up to two integers */
|
|
for (p = string; *p; p++) {
|
|
if (*p == ':') {
|
|
if ((++n) > 1)
|
|
return 0;
|
|
} else if ((*p >= '0') && (*p <= '9')) {
|
|
if ((n == 0) && (p == (string + 2)) && *(p + 1))
|
|
n++;
|
|
in[n] = in[n] * 10 + (int)(*p - '0');
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (n != 1 || !check_time(in[0], in[1]))
|
|
return 0;
|
|
|
|
*hour = in[0];
|
|
*minute = in[1];
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Converts a duration string into minutes.
|
|
*
|
|
* Allowed formats (noted as regular expressions):
|
|
*
|
|
* - \d*:\d*
|
|
* - (\d*m|\d*h(|\d*m)|\d*d(|\d*m|\d*h(|\d*m)))
|
|
* - \d+
|
|
*
|
|
* "\d" is used as a placeholder for "(0|1|2|3|4|5|6|7|8|9)".
|
|
*
|
|
* Returns 1 on success and 0 on failure.
|
|
*/
|
|
int parse_duration(const char *string, unsigned *duration)
|
|
{
|
|
enum {
|
|
STATE_INITIAL,
|
|
STATE_HHMM_MM,
|
|
STATE_DDHHMM_HH,
|
|
STATE_DDHHMM_MM,
|
|
STATE_DONE
|
|
} state = STATE_INITIAL;
|
|
|
|
const char *p;
|
|
unsigned in = 0, frac = 0, denom = 1;
|
|
unsigned dur = 0;
|
|
|
|
if (!string || *string == '\0')
|
|
return 0;
|
|
|
|
/* parse string using a simple state machine */
|
|
for (p = string; *p; p++) {
|
|
if (state == STATE_DONE) {
|
|
return 0;
|
|
} else if ((*p >= '0') && (*p <= '9')) {
|
|
in = in * 10 + (int)(*p - '0');
|
|
if (frac)
|
|
denom *= 10;
|
|
} else if (*p == '.') {
|
|
if (frac)
|
|
return 0;
|
|
frac++;
|
|
} else {
|
|
switch (state) {
|
|
case STATE_INITIAL:
|
|
if (*p == ':') {
|
|
dur += in * HOURINMIN / denom;
|
|
state = STATE_HHMM_MM;
|
|
} else if (*p == 'd') {
|
|
dur += in * DAYINMIN / denom;
|
|
state = STATE_DDHHMM_HH;
|
|
} else if (*p == 'h') {
|
|
dur += in * HOURINMIN / denom;
|
|
state = STATE_DDHHMM_MM;
|
|
} else if (*p == 'm') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
case STATE_DDHHMM_HH:
|
|
if (*p == 'h') {
|
|
dur += in * HOURINMIN / denom;
|
|
state = STATE_DDHHMM_MM;
|
|
} else if (*p == 'm') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
case STATE_DDHHMM_MM:
|
|
if (*p == 'm') {
|
|
dur += in / denom;
|
|
state = STATE_DONE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
break;
|
|
case STATE_HHMM_MM:
|
|
return 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
in = frac = 0;
|
|
denom = 1;
|
|
}
|
|
}
|
|
|
|
if ((state == STATE_HHMM_MM && in >= HOURINMIN) ||
|
|
((state == STATE_DDHHMM_HH || state == STATE_DDHHMM_MM)
|
|
&& in > 0))
|
|
return 0;
|
|
|
|
dur += in;
|
|
*duration = dur;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Converts a string containing a date or a time into a time stamp.
|
|
*
|
|
* Takes a date/time string and a time stamp. If the string only contains a
|
|
* date, the date of the time stamp is updated while the time remains
|
|
* untouched. If the string only contains a time, the time of the time stamp is
|
|
* updated and the date remains the same. If the string contains both a date
|
|
* and a time, the time stamp is updated to match the given string.
|
|
*
|
|
* Returns a positive value on success and 0 on failure. The least-significant
|
|
* bit is set if the date was updated. Bit 1 is set if the time was updated.
|
|
*/
|
|
int parse_datetime(const char *string, long *ts)
|
|
{
|
|
char *t = mem_strdup(string);
|
|
char *p = strtok(t, " ");
|
|
|
|
unsigned int hour, minute;
|
|
int year, month, day;
|
|
struct date new_date;
|
|
int ret = 0;
|
|
|
|
while (p) {
|
|
if (parse_date(p, conf.input_datefmt, &year, &month, &day,
|
|
ui_calendar_get_slctd_day())) {
|
|
new_date.dd = day;
|
|
new_date.mm = month;
|
|
new_date.yyyy = year;
|
|
*ts = date2sec(new_date, 0, 0) + get_item_time(*ts);
|
|
ret |= PARSE_DATETIME_HAS_DATE;
|
|
} else if (parse_time(p, &hour, &minute) == 1) {
|
|
*ts = update_time_in_date(*ts, hour, minute);
|
|
ret |= PARSE_DATETIME_HAS_TIME;
|
|
} else {
|
|
return 0;
|
|
}
|
|
p = strtok(NULL, " ");
|
|
}
|
|
|
|
mem_free(t);
|
|
return ret;
|
|
}
|
|
|
|
void file_close(FILE * f, const char *pos)
|
|
{
|
|
EXIT_IF((fclose(f)) != 0, _("Error when closing file at %s"), pos);
|
|
}
|
|
|
|
/*
|
|
* Sleep the given number of seconds, but make it more 'precise' than sleep(3)
|
|
* (hence the 'p') in a way that even if a signal is caught during the sleep
|
|
* process, this function will return to sleep afterwards.
|
|
*/
|
|
void psleep(unsigned secs)
|
|
{
|
|
unsigned unslept;
|
|
|
|
for (unslept = sleep(secs); unslept; unslept = sleep(unslept)) ;
|
|
}
|
|
|
|
/*
|
|
* Fork and execute an external process.
|
|
*
|
|
* If pfdin and/or pfdout point to a valid address, a pipe is created and the
|
|
* appropriate file descriptors are written to pfdin/pfdout.
|
|
*/
|
|
int fork_exec(int *pfdin, int *pfdout, const char *path,
|
|
const char *const *arg)
|
|
{
|
|
int pin[2], pout[2];
|
|
int pid;
|
|
|
|
if (pfdin && (pipe(pin) == -1))
|
|
return 0;
|
|
if (pfdout && (pipe(pout) == -1))
|
|
return 0;
|
|
|
|
if ((pid = fork()) == 0) {
|
|
if (pfdout) {
|
|
if (dup2(pout[0], STDIN_FILENO) < 0)
|
|
_exit(127);
|
|
close(pout[0]);
|
|
close(pout[1]);
|
|
}
|
|
|
|
if (pfdin) {
|
|
if (dup2(pin[1], STDOUT_FILENO) < 0)
|
|
_exit(127);
|
|
close(pin[0]);
|
|
close(pin[1]);
|
|
}
|
|
|
|
execvp(path, (char *const *)arg);
|
|
_exit(127);
|
|
} else {
|
|
if (pfdin)
|
|
close(pin[1]);
|
|
if (pfdout)
|
|
close(pout[0]);
|
|
|
|
if (pid > 0) {
|
|
if (pfdin) {
|
|
fcntl(pin[0], F_SETFD, FD_CLOEXEC);
|
|
*pfdin = pin[0];
|
|
}
|
|
if (pfdout) {
|
|
fcntl(pout[1], F_SETFD, FD_CLOEXEC);
|
|
*pfdout = pout[1];
|
|
}
|
|
} else {
|
|
if (pfdin)
|
|
close(pin[0]);
|
|
if (pfdout)
|
|
close(pout[1]);
|
|
return 0;
|
|
}
|
|
}
|
|
return pid;
|
|
}
|
|
|
|
/* Execute an external program in a shell. */
|
|
int
|
|
shell_exec(int *pfdin, int *pfdout, const char *path,
|
|
const char *const *arg)
|
|
{
|
|
int argc, i;
|
|
const char **narg;
|
|
char *arg0 = NULL;
|
|
int ret;
|
|
|
|
for (argc = 0; arg[argc]; argc++) ;
|
|
|
|
if (argc < 1)
|
|
return -1;
|
|
|
|
narg = mem_calloc(argc + 4, sizeof(const char *));
|
|
|
|
narg[0] = "sh";
|
|
narg[1] = "-c";
|
|
|
|
if (argc > 1) {
|
|
asprintf(&arg0, "%s \"$@\"", path);
|
|
narg[2] = arg0;
|
|
|
|
for (i = 0; i < argc; i++)
|
|
narg[i + 3] = arg[i];
|
|
narg[argc + 3] = NULL;
|
|
} else {
|
|
narg[2] = path;
|
|
narg[3] = NULL;
|
|
}
|
|
|
|
ret = fork_exec(pfdin, pfdout, *narg, narg);
|
|
|
|
if (arg0)
|
|
mem_free(arg0);
|
|
mem_free(narg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Wait for a child process to terminate. */
|
|
int child_wait(int *pfdin, int *pfdout, int pid)
|
|
{
|
|
int stat;
|
|
|
|
if (pfdin)
|
|
close(*pfdin);
|
|
if (pfdout)
|
|
close(*pfdout);
|
|
|
|
waitpid(pid, &stat, 0);
|
|
return stat;
|
|
}
|
|
|
|
/* Display "Press any key to continue..." and wait for a key press. */
|
|
void press_any_key(void)
|
|
{
|
|
struct termios t_attr_old, t_attr;
|
|
|
|
tcgetattr(STDIN_FILENO, &t_attr_old);
|
|
memcpy(&t_attr, &t_attr_old, sizeof(struct termios));
|
|
t_attr.c_lflag &= ~(ICANON | ECHO | ECHONL);
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_attr);
|
|
|
|
fflush(stdout);
|
|
fputs(_("Press any key to continue..."), stdout);
|
|
fflush(stdout);
|
|
fgetc(stdin);
|
|
fflush(stdin);
|
|
fputs("\r\n", stdout);
|
|
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_attr_old);
|
|
}
|
|
|
|
/*
|
|
* Display note contents if one is asociated with the currently displayed item
|
|
* (to be used together with the '-a' or '-t' flag in non-interactive mode).
|
|
* Each line begins with nbtab tabs.
|
|
* Print "No note file found", if the notefile does not exists.
|
|
*
|
|
* (patch submitted by Erik Saule).
|
|
*/
|
|
static void print_notefile(FILE * out, const char *filename, int nbtab)
|
|
{
|
|
char *path_to_notefile;
|
|
FILE *notefile;
|
|
char linestarter[BUFSIZ];
|
|
char buffer[BUFSIZ];
|
|
int i;
|
|
int printlinestarter = 1;
|
|
|
|
if (nbtab < BUFSIZ) {
|
|
for (i = 0; i < nbtab; i++)
|
|
linestarter[i] = '\t';
|
|
linestarter[nbtab] = '\0';
|
|
} else {
|
|
linestarter[0] = '\0';
|
|
}
|
|
|
|
asprintf(&path_to_notefile, "%s/%s", path_notes, filename);
|
|
notefile = fopen(path_to_notefile, "r");
|
|
mem_free(path_to_notefile);
|
|
if (notefile) {
|
|
while (fgets(buffer, BUFSIZ, notefile) != 0) {
|
|
if (printlinestarter) {
|
|
fputs(linestarter, out);
|
|
printlinestarter = 0;
|
|
}
|
|
fputs(buffer, out);
|
|
if (buffer[strlen(buffer) - 1] == '\n')
|
|
printlinestarter = 1;
|
|
}
|
|
fputs("\n", out);
|
|
file_close(notefile, __FILE_POS__);
|
|
} else {
|
|
fputs(linestarter, out);
|
|
fputs(_("No note file found\n"), out);
|
|
}
|
|
}
|
|
|
|
/* Print an escape sequence and return its length. */
|
|
static int print_escape(const char *s)
|
|
{
|
|
switch (*(s + 1)) {
|
|
case 'a':
|
|
putchar('\a');
|
|
return 1;
|
|
case 'b':
|
|
putchar('\b');
|
|
return 1;
|
|
case 'f':
|
|
putchar('\f');
|
|
return 1;
|
|
case 'n':
|
|
putchar('\n');
|
|
return 1;
|
|
case 'r':
|
|
putchar('\r');
|
|
return 1;
|
|
case 't':
|
|
putchar('\t');
|
|
return 1;
|
|
case 'v':
|
|
putchar('\v');
|
|
return 1;
|
|
case '0':
|
|
putchar('\0');
|
|
return 1;
|
|
case '\'':
|
|
putchar('\'');
|
|
return 1;
|
|
case '"':
|
|
putchar('"');
|
|
return 1;
|
|
case '\?':
|
|
putchar('?');
|
|
return 1;
|
|
case '\\':
|
|
putchar('\\');
|
|
return 1;
|
|
case '\0':
|
|
return 0;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* Parse a format specifier. */
|
|
static enum format_specifier parse_fs(const char **s, char *extformat)
|
|
{
|
|
char buf[FS_EXT_MAXLEN];
|
|
int i;
|
|
|
|
extformat[0] = '\0';
|
|
|
|
switch (**s) {
|
|
case 's':
|
|
strcpy(extformat, "epoch");
|
|
return FS_STARTDATE;
|
|
case 'S':
|
|
return FS_STARTDATE;
|
|
case 'd':
|
|
return FS_DURATION;
|
|
case 'e':
|
|
strcpy(extformat, "epoch");
|
|
return FS_ENDDATE;
|
|
case 'E':
|
|
return FS_ENDDATE;
|
|
case 'm':
|
|
return FS_MESSAGE;
|
|
case 'n':
|
|
return FS_NOTE;
|
|
case 'N':
|
|
return FS_NOTEFILE;
|
|
case 'p':
|
|
return FS_PRIORITY;
|
|
case 'r':
|
|
return FS_REMAINING;
|
|
case '(':
|
|
/* Long format specifier. */
|
|
for ((*s)++, i = 0; **s != ':' && **s != ')'; (*s)++, i++) {
|
|
if (**s == '\0')
|
|
return FS_EOF;
|
|
|
|
if (i < FS_EXT_MAXLEN)
|
|
buf[i] = **s;
|
|
}
|
|
|
|
buf[(i < FS_EXT_MAXLEN) ? i : FS_EXT_MAXLEN - 1] = '\0';
|
|
|
|
if (**s == ':') {
|
|
for ((*s)++, i = 0; **s != ')'; (*s)++, i++) {
|
|
if (**s == '\0')
|
|
return FS_EOF;
|
|
|
|
if (i < FS_EXT_MAXLEN)
|
|
extformat[i] = **s;
|
|
}
|
|
|
|
extformat[(i <
|
|
FS_EXT_MAXLEN) ? i : FS_EXT_MAXLEN -
|
|
1] = '\0';
|
|
}
|
|
|
|
if (!strcmp(buf, "start"))
|
|
return FS_STARTDATE;
|
|
else if (!strcmp(buf, "duration"))
|
|
return FS_DURATION;
|
|
else if (!strcmp(buf, "end"))
|
|
return FS_ENDDATE;
|
|
else if (!strcmp(buf, "remaining"))
|
|
return FS_REMAINING;
|
|
else if (!strcmp(buf, "message"))
|
|
return FS_MESSAGE;
|
|
else if (!strcmp(buf, "noteid"))
|
|
return FS_NOTE;
|
|
else if (!strcmp(buf, "note"))
|
|
return FS_NOTEFILE;
|
|
else if (!strcmp(buf, "priority"))
|
|
return FS_PRIORITY;
|
|
else if (!strcmp(buf, "raw"))
|
|
return FS_RAW;
|
|
else if (!strcmp(buf, "hash"))
|
|
return FS_HASH;
|
|
else
|
|
return FS_UNKNOWN;
|
|
case '%':
|
|
return FS_PSIGN;
|
|
case '\0':
|
|
return FS_EOF;
|
|
default:
|
|
return FS_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
/* Print a formatted date to stdout. */
|
|
static void print_date(long date, long day, const char *extformat)
|
|
{
|
|
char buf[BUFSIZ];
|
|
|
|
if (!strcmp(extformat, "epoch")) {
|
|
printf("%ld", date);
|
|
} else {
|
|
time_t day_end = date_sec_change(day, 0, 1);
|
|
time_t t = date;
|
|
struct tm lt;
|
|
|
|
localtime_r((time_t *) & t, <);
|
|
|
|
if (extformat[0] == '\0' || !strcmp(extformat, "default")) {
|
|
if (date >= day && date <= day_end)
|
|
strftime(buf, BUFSIZ, "%H:%M", <);
|
|
else
|
|
strftime(buf, BUFSIZ, "..:..", <);
|
|
} else {
|
|
strftime(buf, BUFSIZ, extformat, <);
|
|
}
|
|
|
|
printf("%s", buf);
|
|
}
|
|
}
|
|
|
|
/* Print a time difference to stdout. */
|
|
static void print_datediff(long difference, const char *extformat)
|
|
{
|
|
const char *p;
|
|
const char *numfmt;
|
|
bool usetotal;
|
|
long value;
|
|
|
|
if (!strcmp(extformat, "epoch")) {
|
|
printf("%ld", difference);
|
|
} else {
|
|
if (extformat[0] == '\0' || !strcmp(extformat, "default")) {
|
|
/* Set a default format if none specified. */
|
|
p = "%EH:%M";
|
|
} else {
|
|
p = extformat;
|
|
}
|
|
while (*p) {
|
|
if (*p == '%') {
|
|
p++;
|
|
/* Default is to zero-pad, and assume
|
|
* the user wants the time unit modulo
|
|
* the next biggest time unit. */
|
|
numfmt = "%02d";
|
|
usetotal = FALSE;
|
|
if (*p == '-') {
|
|
numfmt = "%d";
|
|
p++;
|
|
}
|
|
if (*p == 'E') {
|
|
usetotal = TRUE;
|
|
p++;
|
|
}
|
|
switch (*p) {
|
|
case '\0':
|
|
return;
|
|
case 'd':
|
|
value = difference / DAYINSEC;
|
|
printf(numfmt, value);
|
|
break;
|
|
case 'H':
|
|
value = difference / HOURINSEC;
|
|
if (!usetotal)
|
|
value %= DAYINHOURS;
|
|
printf(numfmt, value);
|
|
break;
|
|
case 'M':
|
|
value = difference / MININSEC;
|
|
if (!usetotal)
|
|
value %= HOURINMIN;
|
|
printf(numfmt, value);
|
|
break;
|
|
case 'S':
|
|
value = difference;
|
|
if (!usetotal)
|
|
value %= MININSEC;
|
|
printf(numfmt, value);
|
|
break;
|
|
case '%':
|
|
putchar('%');
|
|
break;
|
|
default:
|
|
putchar('?');
|
|
break;
|
|
}
|
|
} else {
|
|
putchar(*p);
|
|
}
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print a formatted appointment to stdout. */
|
|
static void print_apoint_helper(const char *format, long day,
|
|
struct apoint *apt, struct recur_apoint *rapt)
|
|
{
|
|
const char *p;
|
|
char extformat[FS_EXT_MAXLEN];
|
|
|
|
for (p = format; *p; p++) {
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (parse_fs(&p, extformat)) {
|
|
case FS_STARTDATE:
|
|
print_date(apt->start, day, extformat);
|
|
break;
|
|
case FS_DURATION:
|
|
/* Backwards compatibility: Use epoch by
|
|
* default. */
|
|
if (*extformat == '\0')
|
|
strcpy(extformat, "epoch");
|
|
print_datediff(apt->dur, extformat);
|
|
break;
|
|
case FS_ENDDATE:
|
|
print_date(apt->start + apt->dur, day,
|
|
extformat);
|
|
break;
|
|
case FS_REMAINING:
|
|
print_datediff(difftime(apt->start, now()),
|
|
extformat);
|
|
break;
|
|
case FS_MESSAGE:
|
|
printf("%s", apt->mesg);
|
|
break;
|
|
case FS_NOTE:
|
|
printf("%s", apt->note);
|
|
break;
|
|
case FS_NOTEFILE:
|
|
print_notefile(stdout, apt->note, 1);
|
|
break;
|
|
case FS_RAW:
|
|
if (rapt)
|
|
recur_apoint_write(rapt, stdout);
|
|
else
|
|
apoint_write(apt, stdout);
|
|
break;
|
|
case FS_HASH:
|
|
if (rapt)
|
|
printf("%s", recur_apoint_hash(rapt));
|
|
else
|
|
printf("%s", apoint_hash(apt));
|
|
break;
|
|
case FS_PSIGN:
|
|
putchar('%');
|
|
break;
|
|
case FS_EOF:
|
|
return;
|
|
break;
|
|
default:
|
|
putchar('?');
|
|
break;
|
|
}
|
|
} else if (*p == '\\') {
|
|
p += print_escape(p);
|
|
} else {
|
|
putchar(*p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print a formatted event to stdout. */
|
|
static void print_event_helper(const char *format, long day, struct event *ev,
|
|
struct recur_event *rev)
|
|
{
|
|
const char *p;
|
|
char extformat[FS_EXT_MAXLEN];
|
|
|
|
for (p = format; *p; p++) {
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (parse_fs(&p, extformat)) {
|
|
case FS_MESSAGE:
|
|
printf("%s", ev->mesg);
|
|
break;
|
|
case FS_NOTE:
|
|
printf("%s", ev->note);
|
|
break;
|
|
case FS_NOTEFILE:
|
|
print_notefile(stdout, ev->note, 1);
|
|
break;
|
|
case FS_PSIGN:
|
|
putchar('%');
|
|
break;
|
|
case FS_RAW:
|
|
if (rev)
|
|
recur_event_write(rev, stdout);
|
|
else
|
|
event_write(ev, stdout);
|
|
break;
|
|
case FS_HASH:
|
|
if (rev)
|
|
printf("%s", recur_event_hash(rev));
|
|
else
|
|
printf("%s", event_hash(ev));
|
|
break;
|
|
case FS_EOF:
|
|
return;
|
|
break;
|
|
default:
|
|
putchar('?');
|
|
break;
|
|
}
|
|
} else if (*p == '\\') {
|
|
p += print_escape(p);
|
|
} else {
|
|
putchar(*p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Print a formatted appointment to stdout. */
|
|
void print_apoint(const char *format, long day, struct apoint *apt)
|
|
{
|
|
print_apoint_helper(format, day, apt, NULL);
|
|
}
|
|
|
|
/* Print a formatted event to stdout. */
|
|
void print_event(const char *format, long day, struct event *ev)
|
|
{
|
|
print_event_helper(format, day, ev, NULL);
|
|
}
|
|
|
|
/* Print a formatted recurrent appointment to stdout. */
|
|
void
|
|
print_recur_apoint(const char *format, long day, time_t occurrence,
|
|
struct recur_apoint *rapt)
|
|
{
|
|
struct apoint apt;
|
|
|
|
apt.start = occurrence;
|
|
apt.dur = rapt->dur;
|
|
apt.mesg = rapt->mesg;
|
|
apt.note = rapt->note;
|
|
|
|
print_apoint_helper(format, day, &apt, rapt);
|
|
}
|
|
|
|
/* Print a formatted recurrent event to stdout. */
|
|
void print_recur_event(const char *format, long day,
|
|
struct recur_event *rev)
|
|
{
|
|
struct event ev;
|
|
|
|
ev.mesg = rev->mesg;
|
|
ev.note = rev->note;
|
|
|
|
print_event_helper(format, day, &ev, rev);
|
|
}
|
|
|
|
/* Print a formatted todo item to stdout. */
|
|
void print_todo(const char *format, struct todo *todo)
|
|
{
|
|
const char *p;
|
|
char extformat[FS_EXT_MAXLEN];
|
|
|
|
for (p = format; *p; p++) {
|
|
if (*p == '%') {
|
|
p++;
|
|
switch (parse_fs(&p, extformat)) {
|
|
case FS_PRIORITY:
|
|
printf("%d", abs(todo->id));
|
|
break;
|
|
case FS_MESSAGE:
|
|
printf("%s", todo->mesg);
|
|
break;
|
|
case FS_NOTE:
|
|
printf("%s", todo->note);
|
|
break;
|
|
case FS_NOTEFILE:
|
|
print_notefile(stdout, todo->note, 1);
|
|
break;
|
|
case FS_RAW:
|
|
todo_write(todo, stdout);
|
|
break;
|
|
case FS_HASH:
|
|
printf("%s", todo_hash(todo));
|
|
break;
|
|
case FS_PSIGN:
|
|
putchar('%');
|
|
break;
|
|
case FS_EOF:
|
|
return;
|
|
break;
|
|
default:
|
|
putchar('?');
|
|
break;
|
|
}
|
|
} else if (*p == '\\') {
|
|
p += print_escape(p);
|
|
} else {
|
|
putchar(*p);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
asprintf(char **str, const char *format, ...)
|
|
{
|
|
struct string s;
|
|
va_list ap;
|
|
int n;
|
|
|
|
va_start(ap, format);
|
|
string_init(&s);
|
|
n = string_vcatf(&s, format, ap);
|
|
*str = string_buf(&s);
|
|
va_end(ap);
|
|
|
|
return n;
|
|
}
|
|
|
|
int starts_with(const char *s, const char *p)
|
|
{
|
|
for (; *p && *p == *s; s++, p++);
|
|
return (*p == '\0');
|
|
}
|
|
|
|
int starts_with_ci(const char *s, const char *p)
|
|
{
|
|
for (; *p && tolower(*p) == tolower(*s); s++, p++);
|
|
return (*p == '\0');
|
|
}
|
|
|
|
int hash_matches(const char *pattern, const char *hash)
|
|
{
|
|
int invert = 0;
|
|
|
|
if (pattern[0] == '!') {
|
|
invert = 1;
|
|
pattern++;
|
|
}
|
|
|
|
return (starts_with(hash, pattern) != invert);
|
|
}
|
|
|
|
int show_dialogs(void)
|
|
{
|
|
return (!quiet) && conf.system_dialogs;
|
|
}
|