diary

Text-based journaling program
git clone https://git.in0rdr.ch/diary.git
Log | Files | Refs | README | LICENSE

commit ef7b3ea342515a951edd2ef08a1c62eeea693d0f
parent eea1cd058d29d5b857f9142a57b93844bd11549e
Author: Andreas Gruhler <agruhl@gmx.ch>
Date:   Sat, 11 Dec 2021 19:54:31 +0100

Merge pull request #84 from in0rdr/feat/pty

Preview Journal Entry with Pseudo Terminal (pty)
Diffstat:
MREADME.md | 1+
Mconfig/diary.cfg | 2++
Mman1/diary.1 | 7+++++++
Msrc/diary.c | 106++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/diary.h | 2++
Msrc/utils.c | 1+
Msrc/utils.h | 2++
7 files changed, 110 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md @@ -122,6 +122,7 @@ The file `${XDG_CONFIG_HOME:-~/.config}/diary/diary.cfg` should adhere to a basi | `--editor` or `-e` | `editor` | vim | (empty) | Editor to open journal files with. If unset, defaults to environment variable `$EDITOR`. If no editor is provided, the diary is opened read-only. | | `--fmt` or `-f` | `fmt` | %d_%b_%y | %Y-%m-%d | Date format and file name for the files inside the `dir`. For the format specifiers, see [`man strftime`](https://man7.org/linux/man-pages/man3/strftime.3.html). Be careful: If you change this, you might no longer find your existing diary entries, because the diary assumes to find the journal files under another file name. Hence, a change in FMT shows an empty diary, at first. Rename all files in the DIARY_DIR to migrate to a new FMT. | | `--fmt-cmd` or `-F` | `fmt_cmd` | `fmt -w75 -s` or `mdcat -c` | (empty) | Journal entries are formatted with this command. If not set (default), the characters are printed to the preview screen character by character (without word wrap). For example, to enable word wrap after 75 characters, use "fmt -s" as FMT_CMD. | +| `--no-pty` or `-p` | `no_pty` | `true` | false (0) | No pseudo terminal (pty) for entry preview. If set to true (1), journal entries are displayed with Ncurses. If set to false (0), the entry preview is printed to a pseudo terminal (pty) and not processed with Ncurses. | | `--range` or `-r` | `range` | 10 | 1 | Number of years to show before/after todays date | | `--weekday` or `-w` | `weekday` | 0 | 1 | First weekday, `7` = Sunday, `1` = Monday, ..., `6` = Saturday. Use `7` (or `0`) to display week beginning at Sunday ("S-M-T-W-T-F-S"), or `1` for "M-T-W-T-F-S-S". If `glibc` is installed, the first day of the week is derived from the current locale setting (`$LANG`, see `man locale`). Without `glibc`, the first weekday defaults to 1 (Monday), unless specified otherwise with this option. | | n/a | `google_calendar` | diary | (empty) | Displayname of Google Calendar for [CalDAV sync](#CalDAV-sync) | diff --git a/config/diary.cfg b/config/diary.cfg @@ -8,6 +8,8 @@ weekday = 1 fmt = %Y-%m-%d # Text format command for entry preview fmt_cmd = +# Use pseudo terminal (pty) for entry preview +#no_pty = false # Editor to open journal files with editor = # Google calendar name for CalDAV sync diff --git a/man1/diary.1 b/man1/diary.1 @@ -39,6 +39,11 @@ the characters are printed to the preview screen character by character (without word wrap). For example, to enable word wrap after 75 characters, use "fmt -s" as FMT_CMD. To format markdown, use "mdcat -c" or similar. .TP +\fB\-p\fR, \fB\-\-no-pty\fR +No pseudo terminal (pty) for entry preview. If set, journal entries are +displayed with Ncurses. If not set (default), the entry preview is printed +to a pseudo terminal (pty) and not processed with Ncurses. +.TP \fB\-r\fR, \fB\-\-range\fR=\fI\,RANGE\/\fR RANGE is the number of years to show before/after todays date. Defaults to 1 year. .TP @@ -135,6 +140,8 @@ weekday = 1 fmt = %Y-%m-%d # Text format command for entry preview fmt_cmd = +# Use pseudo terminal (pty) for entry preview +#no_pty = false # Editor to open journal files with editor = # Google calendar name for CalDAV sync diff --git a/src/diary.c b/src/diary.c @@ -84,8 +84,29 @@ void display_entry(const char* dir, size_t dir_size, const struct tm* date, WIND FILE* fp; int c; + // pty file descriptors + int fdm, fds; + char* pts = ""; + + size_t entrys, indents; + + // formatted entry + char* entry = calloc(1, sizeof(char)); + void* entryr; + + // start pty output at row number 2 (1 is header) + int row = 2; + // ANSI esc char to point to row/col (indent): + // https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html + // three digit max rows for winy + // start indent col num winx in two digit range 99 + char indent[strlen("\033[999;99H") + 1]; + wclear(win); + int winx, winy; + getmaxyx(win, winy, winx); + if (date_has_entry(dir, dir_size, date)) { // get entry path fpath(dir, dir_size, date, &ppath, sizeof path); @@ -94,6 +115,18 @@ void display_entry(const char* dir, size_t dir_size, const struct tm* date, WIND return; } + // open master pty + if ((fdm = posix_openpt(O_RDWR)) < 0) + perror("error on posix_openpt"); + + // unlock slave pty corresponding to master, otherwise, + // EIO 5 Input/output error when opening slave fds below + if (unlockpt(fdm) < 0) + perror("error on unlockpt"); + + if ((pts = ptsname(fdm)) == NULL) + perror("error on ptsname"); + if (strcmp(CONFIG.fmt_cmd, "") == 0) { // no formatting command defined, read and print lines fp = fopen(ppath, "r"); @@ -103,15 +136,15 @@ void display_entry(const char* dir, size_t dir_size, const struct tm* date, WIND } else { int pipeds[2]; if(pipe(pipeds) == -1) - perror("Failed to crate pipeds"); + perror("Failed to create pipeds"); pid_t pid1, pid2; - if((pid1 = fork()) == 0) { + if ((pid1 = fork()) == 0) { dup2(pipeds[1], STDOUT_FILENO); // re-use stdout in 1 close(pipeds[0]); // 1 silently closed by dup2 already int max_arg = 10; - char *argv[max_arg]; + char* argv[max_arg]; int i = 0; char* tok = strtok(CONFIG.fmt_cmd, " "); while (tok != NULL && i < max_arg-1) { @@ -127,16 +160,55 @@ void display_entry(const char* dir, size_t dir_size, const struct tm* date, WIND exit(0); } - if((pid2 = fork()) == 0) { + if ((pid2 = fork()) == 0) { dup2(pipeds[0], STDIN_FILENO); // re-use stdin in 0 close(pipeds[1]); // 0 silently closed by dup2 already - char line[256]; - while (fgets(line, sizeof line, stdin)) { - // print formatted entry from 0 to screen - wprintw(win, line); + char line[winx]; + + if (CONFIG.no_pty) { + while (fgets(line, sizeof line, stdin)) { + // print formatted entry from 0 to screen + wprintw(win, line); + } + wrefresh(win); + exit(0); + } + + // Read formatted entry line by line. Use fgets, + // fseek on stdin/pipe illegal (ESPIPE 29 Illegal seek). + // Three digit max rows for preview 999. + while (fgets(line, sizeof line, stdin) && row <= winy) { + // advance row number in indent + sprintf(indent, "\033[%d;%dH", row++, ASIDE_WIDTH + CAL_WIDTH + 1); + + entrys = strlen(entry); + indents = strlen(indent); + + // execvp does not return, store all lines in entry buffer + entryr = realloc(entry, entrys + indents + strlen(line) + 1); + if (entryr == NULL) { + perror("failed to realloc entry buffer"); + break; + } else { + // entry ptr already freed by realloc if moved + entry = (char*) entryr; + // append indent to entry buffer + strcat(entry + entrys, indent); + // append line to entry buffer + strcat(entry + entrys + indents, line); + } } - wrefresh(win); + + // open slave pty + if ((fds = open(pts, O_RDWR)) < 0) + perror("error opening fds"); + + // print line to pty with ANSI esc chars for indentation + char* argv[] = {"echo", "-en", entry, (char*) NULL}; + if (execvp(argv[0], argv) > 0) + perror("execvp of --fmt-cmd failed"); + exit(0); } @@ -150,6 +222,7 @@ void display_entry(const char* dir, size_t dir_size, const struct tm* date, WIND wrefresh(win); } + free(entry); } /* Writes edit command for 'date' entry to 'rcmd'. '*rcmd' is NULL on error. */ @@ -270,6 +343,10 @@ bool read_config(const char* file_path) { value_multiword[strlen(value_multiword) - 1] = '\0'; // strip newline CONFIG.fmt_cmd = (char *) malloc(strlen(value_multiword) + 1 * sizeof(char)); strcpy(CONFIG.fmt_cmd, value_multiword); + } else if (strcmp("no_pty", key_buf) == 0) { + if (strcmp("1", value_buf) == 0 || strcmp("true", value_buf) == 0) { + CONFIG.no_pty = true; + } } else if (strcmp("editor", key_buf) == 0) { CONFIG.editor = (char *) malloc(strlen(value_buf) + 1 * sizeof(char)); strcpy(CONFIG.editor, value_buf); @@ -306,6 +383,7 @@ void usage() { printf(" -e, --editor EDITOR : Editor to open journal files with\n"); printf(" -f, --fmt FMT : Date and file format, change with care\n"); printf(" -F, --fmt-cmd FMT_CMD : Format entry preview with command FMT_CMD\n"); + printf(" -p, --no-pty : No pseudo terminal (pty) for entry preview\n"); printf(" -r, --range RANGE : RANGE is the number of years to show before/after today's date\n"); printf(" -w, --weekday DAY : First day of the week, 0 = Sun, 1 = Mon, ..., 6 = Sat\n"); printf("\n"); @@ -401,6 +479,7 @@ int main(int argc, char** argv) { { "editor", required_argument, 0, 'e' }, { "fmt", required_argument, 0, 'f' }, { "fmt-cmd", required_argument, 0, 'F' }, + { "no-pty", no_argument, 0, 'p' }, { "range", required_argument, 0, 'r' }, { "weekday", required_argument, 0, 'w' }, { 0, 0, 0, 0 } @@ -408,7 +487,7 @@ int main(int argc, char** argv) { // read option characters while (1) { - option_char = getopt_long(argc, argv, "vhd:r:w:f:F:e:", long_options, &option_index); + option_char = getopt_long(argc, argv, "vhd:r:w:f:F:p:e:", long_options, &option_index); if (option_char == -1) { break; @@ -451,6 +530,10 @@ int main(int argc, char** argv) { CONFIG.fmt_cmd = (char *) calloc(strlen(optarg) + 1, sizeof(char)); strcpy(CONFIG.fmt_cmd, optarg); break; + case 'p': + // set no-pty flag + CONFIG.no_pty = true; + break; case 'e': // set default editor from option character CONFIG.editor = (char *) calloc(strlen(optarg) + 1, sizeof(char)); @@ -729,7 +812,8 @@ int main(int argc, char** argv) { wresize(header, 1, prev_width); // read the diary - display_entry(CONFIG.dir, diary_dir_size, &curs_date, prev, prev_width); + if (ch != 'q') + display_entry(CONFIG.dir, diary_dir_size, &curs_date, prev, prev_width); } } while (ch != 'q'); diff --git a/src/diary.h b/src/diary.h @@ -4,6 +4,7 @@ #ifdef __MACH__ #include <CoreFoundation/CoreFoundation.h> #endif +#define _XOPEN_SOURCE 700 #include <stdio.h> #include <stdint.h> @@ -18,6 +19,7 @@ #include <locale.h> #include <langinfo.h> #include <sys/wait.h> +#include <fcntl.h> #include "utils.h" #include "import.h" diff --git a/src/utils.c b/src/utils.c @@ -403,6 +403,7 @@ config CONFIG = { .weekday = 1, .fmt = "%Y-%m-%d", .fmt_cmd = "", + .no_pty = false, .editor = "", .google_tokenfile = GOOGLE_OAUTH_TOKEN_FILE, .google_clientid = GOOGLE_OAUTH_CLIENT_ID, diff --git a/src/utils.h b/src/utils.h @@ -47,6 +47,8 @@ typedef struct // Text formatting command/utility fmt, // default width 75 chars (-w75), do not refill lines (-s) char* fmt_cmd; + // Don't use pty, use ncurses for preview + bool no_pty; // Editor to open journal files with char* editor; // File for Google OAuth access token