diff options
Diffstat (limited to 'ui.c')
-rw-r--r-- | ui.c | 772 |
1 files changed, 257 insertions, 515 deletions
diff --git a/ui.c b/ui.c index 9cf21d3..079ee19 100644 --- a/ui.c +++ b/ui.c @@ -1,615 +1,357 @@ -/* Copyright (C) 2018, 2019 C. McEnroe <june@causal.agency> +/* Copyright (C) 2020 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by + * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this Program, or any covered work, by linking or + * combining it with OpenSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of OpenSSL used as well as that of the + * covered work. */ #define _XOPEN_SOURCE_EXTENDED +#include <assert.h> #include <curses.h> #include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> #include <stdarg.h> #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/file.h> #include <sysexits.h> -#include <wchar.h> -#include <wctype.h> +#include <term.h> +#include <time.h> +#include <unistd.h> -#ifndef A_ITALIC -#define A_ITALIC A_UNDERLINE +#ifdef __FreeBSD__ +#include <capsicum_helpers.h> #endif #include "chat.h" -#undef uiFmt - -#define CTRL(ch) ((ch) & 037) -enum { Esc = L'\33', Del = L'\177' }; - -static const int LogLines = 512; - -static int lastLine(void) { - return LINES - 1; -} -static int lastCol(void) { - return COLS - 1; -} -static int logHeight(void) { - return LINES - 2; -} - -struct Window { - struct Tag tag; - WINDOW *log; - bool hot; - bool mark; - int scroll; - uint unread; - struct Window *prev; - struct Window *next; -}; -static struct { - struct Window *active; - struct Window *other; - struct Window *head; - struct Window *tail; - struct Window *tag[TagsLen]; -} windows; - -static void windowAppend(struct Window *win) { - if (windows.tail) windows.tail->next = win; - win->prev = windows.tail; - win->next = NULL; - windows.tail = win; - if (!windows.head) windows.head = win; - windows.tag[win->tag.id] = win; -} - -static void windowInsert(struct Window *win, struct Window *next) { - win->prev = next->prev; - win->next = next; - if (win->prev) win->prev->next = win; - win->next->prev = win; - if (!win->prev) windows.head = win; - windows.tag[win->tag.id] = win; -} +#define BOTTOM (LINES - 1) +#define RIGHT (COLS - 1) +#define MAIN_LINES (LINES - StatusLines - InputLines) -static void windowRemove(struct Window *win) { - windows.tag[win->tag.id] = NULL; - if (win->prev) win->prev->next = win->next; - if (win->next) win->next->prev = win->prev; - if (windows.head == win) windows.head = win->next; - if (windows.tail == win) windows.tail = win->prev; -} - -static struct Window *windowFor(struct Tag tag) { - struct Window *win = windows.tag[tag.id]; - if (win) { - win->tag = tag; - return win; - } +WINDOW *uiStatus; +WINDOW *uiMain; +WINDOW *uiInput; - win = calloc(1, sizeof(*win)); - if (!win) err(EX_OSERR, "calloc"); +static short colorPairs; - win->tag = tag; - win->mark = true; - win->scroll = LogLines; - - win->log = newpad(LogLines, COLS); - wsetscrreg(win->log, 0, LogLines - 1); - scrollok(win->log, true); - wmove(win->log, LogLines - 1, 0); - - windowAppend(win); - return win; -} - -static void windowResize(struct Window *win) { - wresize(win->log, LogLines, COLS); - wmove(win->log, LogLines - 1, lastCol()); -} - -static void windowMark(struct Window *win) { - win->mark = true; -} -static void windowUnmark(struct Window *win) { - win->mark = false; - win->unread = 0; - win->hot = false; -} - -static void windowShow(struct Window *win) { - if (windows.active) windowMark(windows.active); - if (win) { - touchwin(win->log); - windowUnmark(win); +static void colorInit(void) { + start_color(); + use_default_colors(); + if (!COLORS) return; + for (short pair = 0; pair < 16; ++pair) { + init_pair(1 + pair, pair % COLORS, -1); } - windows.other = windows.active; - windows.active = win; + colorPairs = 17; } -static void windowClose(struct Window *win) { - if (windows.active == win) windowShow(win->next ? win->next : win->prev); - if (windows.other == win) windows.other = NULL; - windowRemove(win); - delwin(win->log); - free(win); +static attr_t colorAttr(short fg) { + if (!COLORS) return (fg > 0 ? A_BOLD : A_NORMAL); + if (fg != COLOR_BLACK && fg % COLORS == COLOR_BLACK) return A_BOLD; + if (COLORS > 8) return A_NORMAL; + return (fg / COLORS & 1 ? A_BOLD : A_NORMAL); } -static void windowScroll(struct Window *win, int lines) { - if (lines < 0) { - if (win->scroll == logHeight()) return; - if (win->scroll == LogLines) windowMark(win); - win->scroll = MAX(win->scroll + lines, logHeight()); - } else { - if (win->scroll == LogLines) return; - win->scroll = MIN(win->scroll + lines, LogLines); - if (win->scroll == LogLines) windowUnmark(win); +static short colorPair(short fg, short bg) { + if (!COLORS) return 0; + fg %= COLORS; + bg %= COLORS; + if (bg == -1 && fg < 16) return 1 + fg; + for (short pair = 17; pair < colorPairs; ++pair) { + short f, b; + pair_content(pair, &f, &b); + if (f == fg && b == bg) return pair; } + init_pair(colorPairs, fg, bg); + return colorPairs++; } -static void colorInit(void) { - start_color(); - use_default_colors(); - if (COLORS < 16) { - for (short pair = 0; pair < 0100; ++pair) { - if (pair < 010) { - init_pair(1 + pair, pair, -1); - } else { - init_pair(1 + pair, pair & 007, (pair & 070) >> 3); - } - } - } else { - for (short pair = 0; pair < 0x100; ++pair) { - if (pair < 0x10) { - init_pair(1 + pair, pair, -1); - } else { - init_pair(1 + pair, pair & 0x0F, (pair & 0xF0) >> 4); - } - } - } -} +// XXX: Assuming terminals will be fine with these even if they're unsupported, +// since they're "private" modes. +static const char *FocusMode[2] = { "\33[?1004l", "\33[?1004h" }; +static const char *PasteMode[2] = { "\33[?2004l", "\33[?2004h" }; -static attr_t colorAttr(short color) { - if (color < 0) return A_NORMAL; - if (COLORS < 16 && (color & 0x08)) return A_BOLD; - return A_NORMAL; -} -static short colorPair(short color) { - if (color < 0) return 0; - if (COLORS < 16) return 1 + ((color & 0x70) >> 1 | (color & 0x07)); - return 1 + color; +static void errExit(void) { + putp(FocusMode[false]); + putp(PasteMode[false]); + reset_shell_mode(); } -static struct { - bool hide; - WINDOW *status; - WINDOW *input; -} ui; - void uiInit(void) { initscr(); cbreak(); noecho(); - termInit(); - termNoFlow(); - def_prog_mode(); colorInit(); - ui.status = newwin(1, COLS, 0, 0); - ui.input = newpad(1, 512); - keypad(ui.input, true); - nodelay(ui.input, true); - uiShow(); -} + atexit(errExit); -static void uiResize(void) { - wresize(ui.status, 1, COLS); - for (struct Window *win = windows.head; win; win = win->next) { - windowResize(win); +#ifndef A_ITALIC +#define A_ITALIC A_BLINK + // Force ncurses to use individual enter_attr_mode strings: + set_attributes = NULL; + enter_blink_mode = enter_italics_mode; +#endif + + if (!to_status_line && !strncmp(termname(), "xterm", 5)) { + to_status_line = "\33]2;"; + from_status_line = "\7"; } -} -void uiShow(void) { - ui.hide = false; - termMode(TermFocus, true); - uiDraw(); -} -void uiHide(void) { - ui.hide = true; - termMode(TermFocus, false); - endwin(); -} + uiStatus = newwin(StatusLines, COLS, 0, 0); + if (!uiStatus) err(EX_OSERR, "newwin"); -void uiExit(int status) { - uiHide(); - printf( - "This program is AGPLv3 Free Software!\n" - "Code is available from <" SOURCE_URL ">.\n" - ); - exit(status); + uiMain = newwin(MAIN_LINES, COLS, StatusLines, 0); + if (!uiMain) err(EX_OSERR, "newwin"); + + uiInput = newpad(InputLines, InputCols); + if (!uiInput) err(EX_OSERR, "newpad"); + + windowInit(); + uiShow(); } -static int _; +static bool hidden = true; + +char uiTitle[TitleCap]; +static char prevTitle[TitleCap]; + void uiDraw(void) { - if (ui.hide) return; - wnoutrefresh(ui.status); - if (windows.active) { - pnoutrefresh( - windows.active->log, - windows.active->scroll - logHeight(), 0, - 1, 0, - lastLine() - 1, lastCol() - ); - } - int x; - getyx(ui.input, _, x); + if (hidden) return; + wnoutrefresh(uiStatus); + wnoutrefresh(uiMain); + int y, x; + getyx(uiInput, y, x); pnoutrefresh( - ui.input, - 0, MAX(0, x - lastCol() + 3), - lastLine(), 0, - lastLine(), lastCol() + uiInput, + 0, (x + 1 > RIGHT ? x + 1 - RIGHT : 0), + LINES - InputLines, 0, + BOTTOM, RIGHT ); + (void)y; doupdate(); -} -static const short Colors[] = { - [IRCWhite] = 8 + COLOR_WHITE, - [IRCBlack] = 0 + COLOR_BLACK, - [IRCBlue] = 0 + COLOR_BLUE, - [IRCGreen] = 0 + COLOR_GREEN, - [IRCRed] = 8 + COLOR_RED, - [IRCBrown] = 0 + COLOR_RED, - [IRCMagenta] = 0 + COLOR_MAGENTA, - [IRCOrange] = 0 + COLOR_YELLOW, - [IRCYellow] = 8 + COLOR_YELLOW, - [IRCLightGreen] = 8 + COLOR_GREEN, - [IRCCyan] = 0 + COLOR_CYAN, - [IRCLightCyan] = 8 + COLOR_CYAN, - [IRCLightBlue] = 8 + COLOR_BLUE, - [IRCPink] = 8 + COLOR_MAGENTA, - [IRCGray] = 8 + COLOR_BLACK, - [IRCLightGray] = 0 + COLOR_WHITE, + if (!to_status_line) return; + if (!strcmp(uiTitle, prevTitle)) return; + strcpy(prevTitle, uiTitle); + putp(tparm(to_status_line, 0)); + putp(uiTitle); + putp(from_status_line); + fflush(stdout); +} + +static const short Colors[ColorCap] = { + [Default] = -1, + [White] = 8 + COLOR_WHITE, + [Black] = 0 + COLOR_BLACK, + [Blue] = 0 + COLOR_BLUE, + [Green] = 0 + COLOR_GREEN, + [Red] = 8 + COLOR_RED, + [Brown] = 0 + COLOR_RED, + [Magenta] = 0 + COLOR_MAGENTA, + [Orange] = 0 + COLOR_YELLOW, + [Yellow] = 8 + COLOR_YELLOW, + [LightGreen] = 8 + COLOR_GREEN, + [Cyan] = 0 + COLOR_CYAN, + [LightCyan] = 8 + COLOR_CYAN, + [LightBlue] = 8 + COLOR_BLUE, + [Pink] = 8 + COLOR_MAGENTA, + [Gray] = 8 + COLOR_BLACK, + [LightGray] = 0 + COLOR_WHITE, + 52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89, + 88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125, + 124, 166, 184, 106, 34, 49, 37, 33, 19, 129, 127, 161, + 196, 208, 226, 154, 46, 86, 51, 75, 21, 171, 201, 198, + 203, 215, 227, 191, 83, 122, 87, 111, 63, 177, 207, 205, + 217, 223, 229, 193, 157, 158, 159, 153, 147, 183, 219, 212, + 16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231, }; -static void addFormat(WINDOW *win, const struct Format *format) { +uint uiAttr(struct Style style) { attr_t attr = A_NORMAL; - if (format->bold) attr |= A_BOLD; - if (format->italic) attr |= A_ITALIC; - if (format->underline) attr |= A_UNDERLINE; - if (format->reverse) attr |= A_REVERSE; - - short color = -1; - if (format->fg != IRCDefault) color = Colors[format->fg]; - if (format->bg != IRCDefault) color |= Colors[format->bg] << 4; - - wattr_set(win, attr | colorAttr(color), colorPair(color), NULL); - waddnwstr(win, format->str, format->len); -} - -static int printWidth(const wchar_t *str, size_t len) { - int width = 0; - for (size_t i = 0; i < len; ++i) { - if (iswprint(str[i])) width += wcwidth(str[i]); - } - return width; + if (style.attr & Bold) attr |= A_BOLD; + if (style.attr & Reverse) attr |= A_REVERSE; + if (style.attr & Italic) attr |= A_ITALIC; + if (style.attr & Underline) attr |= A_UNDERLINE; + return attr | colorAttr(Colors[style.fg]); } -static int addWrap(WINDOW *win, const wchar_t *str) { - int lines = 0; - struct Format format = { .str = str }; - formatReset(&format); - - while (formatParse(&format, NULL)) { - size_t word = 1 + wcscspn(&format.str[1], L" "); - if (word < format.len) format.len = word; - - int x, xMax; - getyx(win, _, x); - getmaxyx(win, _, xMax); - if (xMax - x - 1 < printWidth(format.str, word)) { - if (format.str[0] == L' ') { - format.str++; - format.len--; - } - waddch(win, '\n'); - lines++; - } - addFormat(win, &format); - } - return lines; -} - -static void title(const struct Window *win) { - int unread; - char *str; - int len = asprintf(&str, "%s%n (%u)", win->tag.name, &unread, win->unread); - if (len < 0) err(EX_OSERR, "asprintf"); - if (!win->unread) str[unread] = '\0'; - termTitle(str); - free(str); -} +bool uiSpoilerReveal; -static void uiStatus(void) { - wmove(ui.status, 0, 0); - int num = 0; - for (const struct Window *win = windows.head; win; win = win->next, ++num) { - if (!win->unread && windows.active != win) continue; - if (windows.active == win) title(win); - int unread; - wchar_t *str; - int len = aswprintf( - &str, L"%c\3%d %d %s %n(\3%02d%u\3%d) ", - (windows.active == win ? IRCReverse : IRCReset), colorFor(win->tag), - num, win->tag.name, - &unread, (win->hot ? IRCWhite : colorFor(win->tag)), win->unread, - colorFor(win->tag) - ); - if (len < 0) err(EX_OSERR, "aswprintf"); - if (!win->unread) str[unread] = L'\0'; - addWrap(ui.status, str); - free(str); +short uiPair(struct Style style) { + if (uiSpoilerReveal && style.fg == style.bg) { + return colorPair(Colors[Default], Colors[style.bg]); } - wclrtoeol(ui.status); + return colorPair(Colors[style.fg], Colors[style.bg]); } -static void uiShowWindow(struct Window *win) { - windowShow(win); - uiStatus(); - uiPrompt(false); +void uiShow(void) { + if (!hidden) return; + prevTitle[0] = '\0'; + putp(FocusMode[true]); + putp(PasteMode[true]); + fflush(stdout); + hidden = false; + windowUnmark(); } -void uiShowTag(struct Tag tag) { - uiShowWindow(windowFor(tag)); +void uiHide(void) { + if (hidden) return; + windowMark(); + hidden = true; + putp(FocusMode[false]); + putp(PasteMode[false]); + endwin(); } -static void uiShowAuto(void) { - struct Window *unread = NULL; - struct Window *hot; - for (hot = windows.head; hot; hot = hot->next) { - if (hot->hot) break; - if (!unread && hot->unread) unread = hot; - } - if (!hot && !unread) return; - uiShowWindow(hot ? hot : unread); -} +struct Util uiNotifyUtil; +static void notify(uint id, const char *str) { + if (self.restricted) return; + if (!uiNotifyUtil.argc) return; -void uiShowNum(int num, bool relative) { - struct Window *win = (relative ? windows.active : windows.head); - if (num < 0) { - for (; win; win = win->prev) if (!num++) break; - } else { - for (; win; win = win->next) if (!num--) break; - } - if (win) uiShowWindow(win); -} + char buf[1024]; + styleStrip(buf, sizeof(buf), str); -void uiMoveTag(struct Tag tag, int num, bool relative) { - struct Window *win = windowFor(tag); - windowRemove(win); - struct Window *ins = (relative ? win : windows.head); - if (num < 0) { - for (; ins; ins = ins->prev) if (!num++) break; - } else { - if (relative) ins = ins->next; - for (; ins; ins = ins->next) if (!num--) break; - } - ins ? windowInsert(win, ins) : windowAppend(win); - uiStatus(); -} + struct Util util = uiNotifyUtil; + utilPush(&util, idNames[id]); + utilPush(&util, buf); -void uiCloseTag(struct Tag tag) { - windowClose(windowFor(tag)); - uiStatus(); - uiPrompt(false); -} + pid_t pid = fork(); + if (pid < 0) err(EX_OSERR, "fork"); + if (pid) return; -static void notify(struct Tag tag, const wchar_t *str) { - beep(); - if (!self.notify) return; - - size_t len = 0; - char buf[256]; - struct Format format = { .str = str }; - formatReset(&format); - while (formatParse(&format, NULL)) { - int n = snprintf( - &buf[len], sizeof(buf) - len, - "%.*ls", (int)format.len, format.str - ); - if (n < 0) err(EX_OSERR, "snprintf"); - len += n; - if (len >= sizeof(buf)) break; - } - eventPipe((const char *[]) { "notify-send", tag.name, buf, NULL }); + setsid(); + close(STDIN_FILENO); + dup2(utilPipe[1], STDOUT_FILENO); + dup2(utilPipe[1], STDERR_FILENO); + execvp(util.argv[0], (char *const *)util.argv); + warn("%s", util.argv[0]); + _exit(EX_CONFIG); } -void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *str) { - struct Window *win = windowFor(tag); - int lines = 1; - waddch(win->log, '\n'); - if (win->mark && heat > UICold) { - if (!win->unread++) { - lines++; - waddch(win->log, '\n'); - } - if (heat > UIWarm) { - win->hot = true; - notify(tag, str); - } - uiStatus(); +void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) { + bool note = windowWrite(id, heat, src, str); + if (note) { + beep(); + notify(id, str); } - lines += addWrap(win->log, str); - if (win->scroll != LogLines) win->scroll -= lines; } -void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...) { - wchar_t *str; +void uiFormat( + uint id, enum Heat heat, const time_t *time, const char *format, ... +) { + char buf[1024]; va_list ap; va_start(ap, format); - vaswprintf(&str, format, ap); + int len = vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); - if (!str) err(EX_OSERR, "vaswprintf"); - uiLog(tag, heat, str); - free(str); + assert((size_t)len < sizeof(buf)); + uiWrite(id, heat, time, buf); } -static void keyCode(wchar_t code) { - if (code == KEY_RESIZE) uiResize(); - struct Window *win = windows.active; - if (!win) return; - switch (code) { - break; case KEY_UP: windowScroll(win, -1); - break; case KEY_DOWN: windowScroll(win, +1); - break; case KEY_PPAGE: windowScroll(win, -(logHeight() - 1)); - break; case KEY_NPAGE: windowScroll(win, +(logHeight() - 1)); - break; case KEY_LEFT: edit(win->tag, EditLeft, 0); - break; case KEY_RIGHT: edit(win->tag, EditRight, 0); - break; case KEY_HOME: edit(win->tag, EditHome, 0); - break; case KEY_END: edit(win->tag, EditEnd, 0); - break; case KEY_DC: edit(win->tag, EditDelete, 0); - break; case KEY_BACKSPACE: edit(win->tag, EditBackspace, 0); - break; case KEY_ENTER: edit(win->tag, EditEnter, 0); - break; default: return; - } - uiStatus(); +void uiResize(void) { + wclear(uiMain); + wresize(uiMain, MAIN_LINES, COLS); + windowResize(); } -static void keyMeta(wchar_t ch) { - struct Window *win = windows.active; - if (ch >= L'0' && ch <= L'9') uiShowNum(ch - L'0', false); - if (ch == L'a') uiShowAuto(); - if (ch == L'/' && windows.other) uiShowWindow(windows.other); - if (!win) return; - switch (ch) { - break; case L'b': edit(win->tag, EditBackWord, 0); - break; case L'f': edit(win->tag, EditForeWord, 0); - break; case L'\b': edit(win->tag, EditKillBackWord, 0); - break; case L'd': edit(win->tag, EditKillForeWord, 0); - break; case L'l': uiHide(); logList(win->tag); - break; case L'm': uiLog(win->tag, UICold, L""); - } -} +static FILE *saveFile; + +static const uint64_t Signatures[] = { + 0x6C72696774616301, // no heat, unread, unreadWarm + 0x6C72696774616302, // no self.pos + 0x6C72696774616303, // no buffer line heat + 0x6C72696774616304, // no mute + 0x6C72696774616305, // no URLs + 0x6C72696774616306, // no thresh + 0x6C72696774616307, // no window time + 0x6C72696774616308, // no input + 0x6C72696774616309, +}; -static void keyChar(wchar_t ch) { - struct Window *win = windows.active; - if (ch == CTRL(L'L')) clearok(curscr, true); - if (!win) return; - switch (ch) { - break; case CTRL(L'N'): uiShowNum(+1, true); - break; case CTRL(L'P'): uiShowNum(-1, true); - - break; case CTRL(L'A'): edit(win->tag, EditHome, 0); - break; case CTRL(L'B'): edit(win->tag, EditLeft, 0); - break; case CTRL(L'D'): edit(win->tag, EditDelete, 0); - break; case CTRL(L'E'): edit(win->tag, EditEnd, 0); - break; case CTRL(L'F'): edit(win->tag, EditRight, 0); - break; case CTRL(L'K'): edit(win->tag, EditKillEnd, 0); - break; case CTRL(L'U'): edit(win->tag, EditKill, 0); - break; case CTRL(L'W'): edit(win->tag, EditKillBackWord, 0); - - break; case CTRL(L'C'): edit(win->tag, EditInsert, IRCColor); - break; case CTRL(L'O'): edit(win->tag, EditInsert, IRCBold); - break; case CTRL(L'R'): edit(win->tag, EditInsert, IRCColor); - break; case CTRL(L'S'): edit(win->tag, EditInsert, IRCReset); - break; case CTRL(L'T'): edit(win->tag, EditInsert, IRCItalic); - break; case CTRL(L'V'): edit(win->tag, EditInsert, IRCReverse); - break; case CTRL(L'_'): edit(win->tag, EditInsert, IRCUnderline); - - break; case L'\b': edit(win->tag, EditBackspace, 0); - break; case L'\t': edit(win->tag, EditComplete, 0); - break; case L'\n': edit(win->tag, EditEnter, 0); - - break; default: if (iswprint(ch)) edit(win->tag, EditInsert, ch); +static size_t signatureVersion(uint64_t signature) { + for (size_t i = 0; i < ARRAY_LEN(Signatures); ++i) { + if (signature == Signatures[i]) return i; } + errx(EX_DATAERR, "unknown save file signature %" PRIX64, signature); } -void uiRead(void) { - if (ui.hide) uiShow(); - static bool meta; - int ret; - wint_t ch; - enum TermEvent event; - while (ERR != (ret = wget_wch(ui.input, &ch))) { - if (ret == KEY_CODE_YES) { - keyCode(ch); - } else if (ch < 0200 && (event = termEvent((char)ch))) { - struct Window *win = windows.active; - switch (event) { - break; case TermFocusIn: if (win) windowUnmark(win); - break; case TermFocusOut: if (win) windowMark(win); - break; default: {} - } - uiStatus(); - } else if (ch == Esc) { - meta = true; - continue; - } else if (meta) { - keyMeta(ch == Del ? '\b' : ch); - } else { - keyChar(ch == Del ? '\b' : ch); - } - meta = false; - } - uiPrompt(false); +static int writeUint64(FILE *file, uint64_t u) { + return (fwrite(&u, sizeof(u), 1, file) ? 0 : -1); } -static bool isAction(struct Tag tag, const wchar_t *input) { - if (tag.id == TagStatus.id || tag.id == TagRaw.id) return false; - return !wcsncasecmp(input, L"/me ", 4); +int uiSave(void) { + return 0 + || ftruncate(fileno(saveFile), 0) + || writeUint64(saveFile, Signatures[8]) + || writeUint64(saveFile, self.pos) + || windowSave(saveFile) + || inputSave(saveFile) + || urlSave(saveFile) + || fclose(saveFile); } -static bool isCommand(struct Tag tag, const wchar_t *input) { - if (tag.id == TagStatus.id || tag.id == TagRaw.id) return true; - if (input[0] != L'/') return false; - const wchar_t *space = wcschr(&input[1], L' '); - const wchar_t *extra = wcschr(&input[1], L'/'); - return !extra || (space && extra > space); +static uint64_t readUint64(FILE *file) { + uint64_t u; + fread(&u, sizeof(u), 1, file); + if (ferror(file)) err(EX_IOERR, "fread"); + if (feof(file)) errx(EX_DATAERR, "unexpected end of save file"); + return u; } -void uiPrompt(bool nickChanged) { - static wchar_t *promptMesg; - static wchar_t *promptAction; - if (nickChanged || !promptMesg || !promptAction) { - free(promptMesg); - free(promptAction); - enum IRCColor color = colorGen(self.user); - int len = aswprintf(&promptMesg, L"\3%d<%s>\3 ", color, self.nick); - if (len < 0) err(EX_OSERR, "aswprintf"); - len = aswprintf(&promptAction, L"\3%d* %s\3 ", color, self.nick); - if (len < 0) err(EX_OSERR, "aswprintf"); - } +void uiLoad(const char *name) { + int error; + saveFile = dataOpen(name, "a+e"); + if (!saveFile) exit(EX_CANTCREAT); + rewind(saveFile); - const wchar_t *input = editHead(); +#ifdef __FreeBSD__ + cap_rights_t rights; + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_FLOCK, CAP_FTRUNCATE); + error = caph_rights_limit(fileno(saveFile), &rights); + if (error) err(EX_OSERR, "cap_rights_limit"); +#endif + + error = flock(fileno(saveFile), LOCK_EX | LOCK_NB); + if (error && errno == EWOULDBLOCK) { + errx(EX_CANTCREAT, "%s: save file in use", name); + } - wmove(ui.input, 0, 0); - if (windows.active) { - if (isAction(windows.active->tag, input) && editTail() >= &input[4]) { - input = &input[4]; - addWrap(ui.input, promptAction); - } else if (!isCommand(windows.active->tag, input)) { - addWrap(ui.input, promptMesg); - } + time_t signature; + fread(&signature, sizeof(signature), 1, saveFile); + if (ferror(saveFile)) err(EX_IOERR, "fread"); + if (feof(saveFile)) { + return; } + size_t version = signatureVersion(signature); - int x = 0; - struct Format format = { .str = input }; - formatReset(&format); - while (formatParse(&format, editTail())) { - if (format.split) getyx(ui.input, _, x); - addFormat(ui.input, &format); + if (version > 1) { + self.pos = readUint64(saveFile); } - wclrtoeol(ui.input); - wmove(ui.input, 0, x); + windowLoad(saveFile, version); + inputLoad(saveFile, version); + urlLoad(saveFile, version); } |