calcurse-edge/src/utils.c
Baptiste Jonglez 4c4d4d3eb3 Use mvwaddstr() instead of mvwprintw()
When we only want to display a string at a specific place of the
screen, there's no need to use the more complex mvwprintw(), use
mvwaddstr() instead.

This should be slightly more efficient, and, above all, it prevents
weird things to happen if our string contains a '%', being interpreted
as an unwanted format string.

Signed-off-by: Baptiste Jonglez <baptiste--git@jonglez.org>
Signed-off-by: Lukas Fleischer <calcurse@cryptocrack.de>
2012-05-31 20:27:43 +02:00

1342 lines
30 KiB
C

/*
* Calcurse - text-based organizer
*
* Copyright (c) 2004-2012 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 <string.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"
#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_MESSAGE,
FS_NOTE,
FS_NOTEFILE,
FS_PRIORITY,
FS_PSIGN,
FS_EOF,
FS_UNKNOWN
};
/* General routine to exit calcurse properly. */
void exit_calcurse(int status)
{
int was_interactive;
if (ui_mode == UI_CURSES) {
notify_stop_main_thread();
clear();
wins_refresh();
endwin();
ui_mode = UI_CMDLINE;
was_interactive = 1;
} else
was_interactive = 0;
calendar_stop_date_thread();
io_stop_psave_thread();
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)
{
day_free_list();
event_llist_free();
event_free_bkp();
apoint_llist_free();
apoint_free_bkp();
recur_apoint_llist_free();
recur_event_llist_free();
recur_apoint_free_bkp();
recur_event_free_bkp();
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);
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);
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);
}
/*
* 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)
{
int i, ch;
char tmp[BUFSIZ];
/* "[4/2/f/t/w/.../Z] " */
char avail_choice[2 * nb_choice + 3];
avail_choice[0] = '[';
avail_choice[1] = '\0';
for (i = 1; i <= nb_choice; i++) {
snprintf(tmp, BUFSIZ, (i == nb_choice) ? "%c] " : "%c/", choice[i]);
strcat(avail_choice, tmp);
}
status_mesg(message, avail_choice);
for (;;) {
ch = wgetch(win[STA].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[BUFSIZ];
/* "(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++) {
snprintf(tmp, BUFSIZ, ((i + 1) == nb_choice) ? "(%d) %s?" : "(%d) %s, ",
(i + 1), choice[i]);
strcat(choicestr, 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)
{
return (localtime((time_t *) & date))->tm_hour;
}
int get_item_min(long date)
{
return (localtime((time_t *) & date))->tm_min;
}
long date2sec(struct date day, unsigned hour, unsigned min)
{
time_t t = now();
struct tm start = *(localtime(&t));
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;
t = mktime(&start);
EXIT_IF(t == -1, _("failure in mktime"));
return t;
}
/* 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 {
lt = localtime((time_t *) & sec);
strftime(datestr, BUFSIZ, datefmt, lt);
}
return datestr;
}
/* Generic function to format date. */
void date_sec2date_fmt(long sec, const char *fmt, char *datef)
{
/* TODO: Find a better way to deal with localization and strftime(). */
char *locale_old = mem_strdup (setlocale (LC_ALL, NULL));
setlocale (LC_ALL, "C");
struct tm *lt = localtime((time_t *)&sec);
strftime(datef, BUFSIZ, fmt, lt);
setlocale (LC_ALL, locale_old);
mem_free (locale_old);
}
/*
* 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;
lt = localtime(&t);
lt->tm_mon += delta_month;
lt->tm_mday += delta_day;
lt->tm_isdst = -1;
t = mktime(lt);
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;
lt = localtime(&t);
lt->tm_hour = hr;
lt->tm_min = mn;
new_date = mktime(lt);
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.
*/
long get_sec_date(struct date date)
{
struct tm *ptrtime;
time_t timer;
long long_date;
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);
ptrtime = localtime(&timer);
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);
}
long_date = date2sec(date, 0, 0);
return long_date;
}
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);
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 *saved_a_start, const char *saved_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 (strcmp(pop_title, _("Appointment")) == 0) {
mvwprintw(popup_win, margin_top, margin_left, "- %s -> %s",
saved_a_start, saved_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. */
long get_today(void)
{
struct tm *lt;
time_t current_time;
long current_day;
struct date day;
current_time = time(NULL);
lt = localtime(&current_time);
day.mm = lt->tm_mon + 1;
day.dd = lt->tm_mday;
day.yyyy = lt->tm_year + 1900;
current_day = date2sec(day, 0, 0);
return current_day;
}
/* Returns the current time in seconds. */
long now(void)
{
return (long)time(NULL);
}
char *nowstr(void)
{
static char buf[BUFSIZ];
time_t t = now();
strftime(buf, sizeof buf, "%a %b %d %T %Y", localtime(&t));
return buf;
}
long mystrtol(const char *str)
{
char *ep;
long lval;
errno = 0;
lval = strtol(str, &ep, 10);
if (str[0] == '\0' || *ep != '\0')
EXIT(_("could not convert string"));
if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN))
EXIT(_("out of range"));
return lval;
}
/* 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, int trailing_len)
{
char fullname[BUFSIZ];
int prefix_len, fd;
FILE *file;
if (prefix == NULL)
return NULL;
prefix_len = strlen(prefix);
if (prefix_len + trailing_len >= BUFSIZ)
return NULL;
memcpy(fullname, prefix, prefix_len);
memset(fullname + prefix_len, 'X', trailing_len);
fullname[prefix_len + trailing_len] = '\0';
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);
return NULL;
}
fclose(file);
return mem_strdup(fullname + prefix_len);
}
/*
* 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;
/* 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 (y < 1902 || y > 2037 || m < 1 || m > 12 || d < 1 ||
d > days[m - 1] + (m == 2 && ISLEAP(y)) ? 1 : 0)
return 0;
if (year)
*year = y;
if (month)
*month = m;
if (day)
*day = d;
return 1;
}
/*
* 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'))
in[n] = in[n] * 10 + (int)(*p - '0');
else
return 0;
}
if (n != 1 || in[0] >= DAYINHOURS || in[1] >= HOURINMIN)
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;
unsigned dur = 0;
if (!string || *string == '\0')
return 0;
/* parse string using a simple state machine */
for (p = string; *p; p++) {
if ((*p >= '0') && (*p <= '9')) {
if (state == STATE_DONE)
return 0;
else
in = in * 10 + (int)(*p - '0');
} else {
switch (state) {
case STATE_INITIAL:
if (*p == ':') {
dur += in * HOURINMIN;
state = STATE_HHMM_MM;
} else if (*p == 'd') {
dur += in * DAYINMIN;
state = STATE_DDHHMM_HH;
} else if (*p == 'h') {
dur += in * HOURINMIN;
state = STATE_DDHHMM_MM;
} else if (*p == 'm') {
dur += in;
state = STATE_DONE;
} else
return 0;
break;
case STATE_DDHHMM_HH:
if (*p == 'h') {
dur += in * HOURINMIN;
state = STATE_DDHHMM_MM;
} else if (*p == 'm') {
dur += in;
state = STATE_DONE;
} else
return 0;
break;
case STATE_DDHHMM_MM:
if (*p == 'm') {
dur += in;
state = STATE_DONE;
} else
return 0;
break;
case STATE_HHMM_MM:
case STATE_DONE:
return 0;
break;
}
in = 0;
}
}
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;
}
void str_toupper(char *s)
{
if (!s)
return;
for (; *s; s++)
*s = toupper(*s);
}
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) {
arg0 = mem_malloc(strlen(path) + 6);
sprintf(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[BUFSIZ];
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';
snprintf(path_to_notefile, BUFSIZ, "%s/%s", path_notes, filename);
notefile = fopen(path_to_notefile, "r");
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 '(':
/* 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, "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
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 t = date;
struct tm *lt = localtime((time_t *) & t);
if (extformat[0] == '\0' || !strcmp(extformat, "default")) {
if (date >= day && date <= day + DAYINSEC)
strftime(buf, BUFSIZ, "%H:%M", lt);
else
strftime(buf, BUFSIZ, "..:..", lt);
} else {
strftime(buf, BUFSIZ, extformat, lt);
}
printf("%s", buf);
}
}
/* Print a formatted appointment to stdout. */
void print_apoint(const char *format, long day, struct apoint *apt)
{
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:
printf("%ld", apt->dur);
break;
case FS_ENDDATE:
print_date(apt->start + apt->dur, day, 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_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. */
void print_event(const char *format, long day, struct event *ev)
{
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_EOF:
return;
break;
default:
putchar('?');
break;
}
} else if (*p == '\\')
p += print_escape(p);
else
putchar(*p);
}
}
/* Print a formatted recurrent appointment to stdout. */
void
print_recur_apoint(const char *format, long day, unsigned 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(format, day, &apt);
}
/* 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(format, day, &ev);
}
/* 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_PSIGN:
putchar('%');
break;
case FS_EOF:
return;
break;
default:
putchar('?');
break;
}
} else if (*p == '\\')
p += print_escape(p);
else
putchar(*p);
}
}