Add toggleable display of timestamps
parent
604de4ff5a
commit
c118c594e3
13
catgirl.1
13
catgirl.1
|
@ -1,4 +1,4 @@
|
||||||
.Dd January 25, 2021
|
.Dd January 26, 2021
|
||||||
.Dt CATGIRL 1
|
.Dt CATGIRL 1
|
||||||
.Os
|
.Os
|
||||||
.
|
.
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
.Op Fl N Ar notify
|
.Op Fl N Ar notify
|
||||||
.Op Fl O Ar open
|
.Op Fl O Ar open
|
||||||
.Op Fl S Ar bind
|
.Op Fl S Ar bind
|
||||||
|
.Op Fl T Ar timestamp
|
||||||
.Op Fl a Ar plain
|
.Op Fl a Ar plain
|
||||||
.Op Fl c Ar cert
|
.Op Fl c Ar cert
|
||||||
.Op Fl h Ar host
|
.Op Fl h Ar host
|
||||||
|
@ -181,6 +182,14 @@ Bind to source address
|
||||||
.Ar host
|
.Ar host
|
||||||
when connecting to the server.
|
when connecting to the server.
|
||||||
.
|
.
|
||||||
|
.It Fl T Ar format , Cm timestamp Op = Ar format
|
||||||
|
Show timestamps by default,
|
||||||
|
in the specified
|
||||||
|
.Xr strftime 3
|
||||||
|
.Ar format .
|
||||||
|
The default format is
|
||||||
|
.Qq \&%T .
|
||||||
|
.
|
||||||
.It Fl a Ar user : Ns Ar pass , Cm sasl-plain = Ar user : Ns Ar pass
|
.It Fl a Ar user : Ns Ar pass , Cm sasl-plain = Ar user : Ns Ar pass
|
||||||
Authenticate as
|
Authenticate as
|
||||||
.Ar user
|
.Ar user
|
||||||
|
@ -640,6 +649,8 @@ Insert a blank line in the window.
|
||||||
Scroll to next highlight.
|
Scroll to next highlight.
|
||||||
.It Ic M-p
|
.It Ic M-p
|
||||||
Scroll to previous highlight.
|
Scroll to previous highlight.
|
||||||
|
.It Ic M-t
|
||||||
|
Toggle timestamps.
|
||||||
.It Ic M-u
|
.It Ic M-u
|
||||||
Scroll to first unread line.
|
Scroll to first unread line.
|
||||||
.It Ic M-v
|
.It Ic M-v
|
||||||
|
|
13
chat.c
13
chat.c
|
@ -40,6 +40,7 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <sysexits.h>
|
#include <sysexits.h>
|
||||||
|
#include <time.h>
|
||||||
#include <tls.h>
|
#include <tls.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
@ -124,6 +125,16 @@ static void parseHash(char *str) {
|
||||||
if (*str) hashBound = strtoul(&str[1], NULL, 0);
|
if (*str) hashBound = strtoul(&str[1], NULL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parseTimestamp(const char *format) {
|
||||||
|
uiTime.enable = true;
|
||||||
|
if (!format) return;
|
||||||
|
char buf[TimeCap];
|
||||||
|
uiTime.format = format;
|
||||||
|
struct tm *time = localtime(&(time_t) { -22100400 });
|
||||||
|
uiTime.width = strftime(buf, sizeof(buf), format, time);
|
||||||
|
if (!uiTime.width) errx(EX_USAGE, "invalid timestamp format: %s", format);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __OpenBSD__
|
#ifdef __OpenBSD__
|
||||||
|
|
||||||
static void unveilConfig(const char *name) {
|
static void unveilConfig(const char *name) {
|
||||||
|
@ -197,6 +208,7 @@ int main(int argc, char *argv[]) {
|
||||||
{ .val = 'O', .name = "open", required_argument },
|
{ .val = 'O', .name = "open", required_argument },
|
||||||
{ .val = 'R', .name = "restrict", no_argument },
|
{ .val = 'R', .name = "restrict", no_argument },
|
||||||
{ .val = 'S', .name = "bind", required_argument },
|
{ .val = 'S', .name = "bind", required_argument },
|
||||||
|
{ .val = 'T', .name = "timestamp", optional_argument },
|
||||||
{ .val = 'a', .name = "sasl-plain", required_argument },
|
{ .val = 'a', .name = "sasl-plain", required_argument },
|
||||||
{ .val = 'c', .name = "cert", required_argument },
|
{ .val = 'c', .name = "cert", required_argument },
|
||||||
{ .val = 'e', .name = "sasl-external", no_argument },
|
{ .val = 'e', .name = "sasl-external", no_argument },
|
||||||
|
@ -234,6 +246,7 @@ int main(int argc, char *argv[]) {
|
||||||
break; case 'O': utilPush(&urlOpenUtil, optarg);
|
break; case 'O': utilPush(&urlOpenUtil, optarg);
|
||||||
break; case 'R': self.restricted = true;
|
break; case 'R': self.restricted = true;
|
||||||
break; case 'S': bind = optarg;
|
break; case 'S': bind = optarg;
|
||||||
|
break; case 'T': parseTimestamp(optarg);
|
||||||
break; case 'a': sasl = true; self.plain = optarg;
|
break; case 'a': sasl = true; self.plain = optarg;
|
||||||
break; case 'c': cert = optarg;
|
break; case 'c': cert = optarg;
|
||||||
break; case 'e': sasl = true;
|
break; case 'e': sasl = true;
|
||||||
|
|
6
chat.h
6
chat.h
|
@ -279,6 +279,12 @@ const char *commandIsAction(uint id, const char *input);
|
||||||
void commandCompleteAdd(void);
|
void commandCompleteAdd(void);
|
||||||
|
|
||||||
enum Heat { Ice, Cold, Warm, Hot };
|
enum Heat { Ice, Cold, Warm, Hot };
|
||||||
|
enum { TimeCap = 64 };
|
||||||
|
extern struct Time {
|
||||||
|
bool enable;
|
||||||
|
const char *format;
|
||||||
|
size_t width;
|
||||||
|
} uiTime;
|
||||||
extern struct Util uiNotifyUtil;
|
extern struct Util uiNotifyUtil;
|
||||||
void uiInitEarly(void);
|
void uiInitEarly(void);
|
||||||
void uiInitLate(void);
|
void uiInitLate(void);
|
||||||
|
|
95
ui.c
95
ui.c
|
@ -69,6 +69,11 @@ enum {
|
||||||
#define RIGHT (COLS - 1)
|
#define RIGHT (COLS - 1)
|
||||||
#define MAIN_LINES (LINES - StatusLines - InputLines)
|
#define MAIN_LINES (LINES - StatusLines - InputLines)
|
||||||
|
|
||||||
|
struct Time uiTime = {
|
||||||
|
.format = "%T",
|
||||||
|
.width = 8,
|
||||||
|
};
|
||||||
|
|
||||||
static WINDOW *status;
|
static WINDOW *status;
|
||||||
static WINDOW *main;
|
static WINDOW *main;
|
||||||
static WINDOW *input;
|
static WINDOW *input;
|
||||||
|
@ -78,6 +83,7 @@ struct Window {
|
||||||
int scroll;
|
int scroll;
|
||||||
bool mark;
|
bool mark;
|
||||||
bool mute;
|
bool mute;
|
||||||
|
bool time;
|
||||||
enum Heat thresh;
|
enum Heat thresh;
|
||||||
enum Heat heat;
|
enum Heat heat;
|
||||||
uint unreadSoft;
|
uint unreadSoft;
|
||||||
|
@ -129,15 +135,13 @@ static uint windowFor(uint id) {
|
||||||
for (uint num = 0; num < windows.len; ++num) {
|
for (uint num = 0; num < windows.len; ++num) {
|
||||||
if (windows.ptrs[num]->id == id) return num;
|
if (windows.ptrs[num]->id == id) return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Window *window = calloc(1, sizeof(*window));
|
struct Window *window = calloc(1, sizeof(*window));
|
||||||
if (!window) err(EX_OSERR, "malloc");
|
if (!window) err(EX_OSERR, "malloc");
|
||||||
|
|
||||||
window->id = id;
|
window->id = id;
|
||||||
window->mark = true;
|
window->mark = true;
|
||||||
|
window->time = uiTime.enable;
|
||||||
window->thresh = Cold;
|
window->thresh = Cold;
|
||||||
window->buffer = bufferAlloc();
|
window->buffer = bufferAlloc();
|
||||||
|
|
||||||
return windowPush(window);
|
return windowPush(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +203,7 @@ static short colorPair(short fg, short bg) {
|
||||||
X(KeyMetaN, "\33n", NULL) \
|
X(KeyMetaN, "\33n", NULL) \
|
||||||
X(KeyMetaP, "\33p", NULL) \
|
X(KeyMetaP, "\33p", NULL) \
|
||||||
X(KeyMetaQ, "\33q", NULL) \
|
X(KeyMetaQ, "\33q", NULL) \
|
||||||
|
X(KeyMetaT, "\33t", NULL) \
|
||||||
X(KeyMetaU, "\33u", NULL) \
|
X(KeyMetaU, "\33u", NULL) \
|
||||||
X(KeyMetaV, "\33v", NULL) \
|
X(KeyMetaV, "\33v", NULL) \
|
||||||
X(KeyMetaEnter, "\33\r", "\33\n") \
|
X(KeyMetaEnter, "\33\r", "\33\n") \
|
||||||
|
@ -466,12 +471,36 @@ static size_t windowBottom(const struct Window *window) {
|
||||||
return bottom;
|
return bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mainAdd(int y, const char *str) {
|
static int windowCols(const struct Window *window) {
|
||||||
|
if (!window->time) return COLS;
|
||||||
|
return COLS - (uiTime.width + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mainAdd(int y, bool time, const struct Line *line) {
|
||||||
int ny, nx;
|
int ny, nx;
|
||||||
wmove(main, y, 0);
|
wmove(main, y, 0);
|
||||||
styleAdd(main, str);
|
if (!line || !line->str[0]) {
|
||||||
|
wclrtoeol(main);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (time && line->time) {
|
||||||
|
char buf[TimeCap];
|
||||||
|
strftime(buf, sizeof(buf), uiTime.format, localtime(&line->time));
|
||||||
|
wattr_set(
|
||||||
|
main,
|
||||||
|
colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1),
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
waddstr(main, buf);
|
||||||
|
waddch(main, ' ');
|
||||||
|
} else if (time) {
|
||||||
|
whline(main, ' ', uiTime.width + 1);
|
||||||
|
wmove(main, y, uiTime.width + 1);
|
||||||
|
}
|
||||||
|
styleAdd(main, line->str);
|
||||||
getyx(main, ny, nx);
|
getyx(main, ny, nx);
|
||||||
if (ny == y) wclrtoeol(main);
|
if (ny != y) return;
|
||||||
|
wclrtoeol(main);
|
||||||
(void)nx;
|
(void)nx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,16 +510,14 @@ static void mainUpdate(void) {
|
||||||
int y = 0;
|
int y = 0;
|
||||||
int marker = MAIN_LINES - SplitLines - MarkerLines;
|
int marker = MAIN_LINES - SplitLines - MarkerLines;
|
||||||
for (size_t i = windowTop(window); i < BufferCap; ++i) {
|
for (size_t i = windowTop(window); i < BufferCap; ++i) {
|
||||||
const struct Line *line = bufferHard(window->buffer, i);
|
mainAdd(y++, window->time, bufferHard(window->buffer, i));
|
||||||
mainAdd(y++, (line ? line->str : ""));
|
|
||||||
if (window->scroll && y == marker) break;
|
if (window->scroll && y == marker) break;
|
||||||
}
|
}
|
||||||
if (!window->scroll) return;
|
if (!window->scroll) return;
|
||||||
|
|
||||||
y = MAIN_LINES - SplitLines;
|
y = MAIN_LINES - SplitLines;
|
||||||
for (size_t i = BufferCap - SplitLines; i < BufferCap; ++i) {
|
for (size_t i = BufferCap - SplitLines; i < BufferCap; ++i) {
|
||||||
const struct Line *line = bufferHard(window->buffer, i);
|
mainAdd(y++, window->time, bufferHard(window->buffer, i));
|
||||||
mainAdd(y++, (line ? line->str : ""));
|
|
||||||
}
|
}
|
||||||
wattr_set(main, A_NORMAL, 0, NULL);
|
wattr_set(main, A_NORMAL, 0, NULL);
|
||||||
mvwhline(main, marker, 0, ACS_BULLET, COLS);
|
mvwhline(main, marker, 0, ACS_BULLET, COLS);
|
||||||
|
@ -540,7 +567,10 @@ void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) {
|
||||||
}
|
}
|
||||||
if (window->mark && heat > Cold) {
|
if (window->mark && heat > Cold) {
|
||||||
if (!window->unreadWarm++) {
|
if (!window->unreadWarm++) {
|
||||||
int lines = bufferPush(window->buffer, COLS, false, Warm, ts, "");
|
int lines = bufferPush(
|
||||||
|
window->buffer, windowCols(window),
|
||||||
|
window->thresh, Warm, ts, ""
|
||||||
|
);
|
||||||
if (window->scroll) windowScroll(window, lines);
|
if (window->scroll) windowScroll(window, lines);
|
||||||
if (window->unreadSoft > 1) {
|
if (window->unreadSoft > 1) {
|
||||||
window->unreadSoft++;
|
window->unreadSoft++;
|
||||||
|
@ -550,7 +580,10 @@ void uiWrite(uint id, enum Heat heat, const time_t *src, const char *str) {
|
||||||
if (heat > window->heat) window->heat = heat;
|
if (heat > window->heat) window->heat = heat;
|
||||||
statusUpdate();
|
statusUpdate();
|
||||||
}
|
}
|
||||||
int lines = bufferPush(window->buffer, COLS, window->thresh, heat, ts, str);
|
int lines = bufferPush(
|
||||||
|
window->buffer, windowCols(window),
|
||||||
|
window->thresh, heat, ts, str
|
||||||
|
);
|
||||||
window->unreadHard += lines;
|
window->unreadHard += lines;
|
||||||
if (window->scroll) windowScroll(window, lines);
|
if (window->scroll) windowScroll(window, lines);
|
||||||
if (window == windows.ptrs[windows.show]) mainUpdate();
|
if (window == windows.ptrs[windows.show]) mainUpdate();
|
||||||
|
@ -583,7 +616,8 @@ static void windowReflow(struct Window *window) {
|
||||||
const struct Line *line = bufferHard(window->buffer, windowTop(window));
|
const struct Line *line = bufferHard(window->buffer, windowTop(window));
|
||||||
if (line) num = line->num;
|
if (line) num = line->num;
|
||||||
window->unreadHard = bufferReflow(
|
window->unreadHard = bufferReflow(
|
||||||
window->buffer, COLS, window->thresh, window->unreadSoft
|
window->buffer, windowCols(window),
|
||||||
|
window->thresh, window->unreadSoft
|
||||||
);
|
);
|
||||||
if (!window->scroll || !num) return;
|
if (!window->scroll || !num) return;
|
||||||
for (size_t i = 0; i < BufferCap; ++i) {
|
for (size_t i = 0; i < BufferCap; ++i) {
|
||||||
|
@ -595,12 +629,12 @@ static void windowReflow(struct Window *window) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void resize(void) {
|
static void resize(void) {
|
||||||
statusUpdate();
|
|
||||||
wclear(main);
|
wclear(main);
|
||||||
wresize(main, MAIN_LINES, COLS);
|
wresize(main, MAIN_LINES, COLS);
|
||||||
for (uint num = 0; num < windows.len; ++num) {
|
for (uint num = 0; num < windows.len; ++num) {
|
||||||
windowReflow(windows.ptrs[num]);
|
windowReflow(windows.ptrs[num]);
|
||||||
}
|
}
|
||||||
|
statusUpdate();
|
||||||
mainUpdate();
|
mainUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,13 +654,10 @@ static void windowList(const struct Window *window) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct tm *tm = localtime(&line->time);
|
char buf[TimeCap];
|
||||||
if (!tm) err(EX_OSERR, "localtime");
|
strftime(buf, sizeof(buf), uiTime.format, localtime(&line->time));
|
||||||
|
|
||||||
char buf[sizeof("00:00:00")];
|
|
||||||
strftime(buf, sizeof(buf), "%T", tm);
|
|
||||||
vid_attr(colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1), NULL);
|
vid_attr(colorAttr(Colors[Gray]), colorPair(Colors[Gray], -1), NULL);
|
||||||
printf("[%s] ", buf);
|
printf("%s ", buf);
|
||||||
|
|
||||||
bool align = false;
|
bool align = false;
|
||||||
struct Style style = StyleDefault;
|
struct Style style = StyleDefault;
|
||||||
|
@ -679,7 +710,7 @@ static void inputAdd(struct Style *style, const char *str) {
|
||||||
static void inputUpdate(void) {
|
static void inputUpdate(void) {
|
||||||
size_t pos;
|
size_t pos;
|
||||||
char *buf = editBuffer(&pos);
|
char *buf = editBuffer(&pos);
|
||||||
uint id = windows.ptrs[windows.show]->id;
|
struct Window *window = windows.ptrs[windows.show];
|
||||||
|
|
||||||
const char *prefix = "";
|
const char *prefix = "";
|
||||||
const char *prompt = self.nick;
|
const char *prompt = self.nick;
|
||||||
|
@ -688,9 +719,9 @@ static void inputUpdate(void) {
|
||||||
struct Style stylePrompt = { .fg = self.color, .bg = Default };
|
struct Style stylePrompt = { .fg = self.color, .bg = Default };
|
||||||
struct Style styleInput = StyleDefault;
|
struct Style styleInput = StyleDefault;
|
||||||
|
|
||||||
const char *privmsg = commandIsPrivmsg(id, buf);
|
const char *privmsg = commandIsPrivmsg(window->id, buf);
|
||||||
const char *notice = commandIsNotice(id, buf);
|
const char *notice = commandIsNotice(window->id, buf);
|
||||||
const char *action = commandIsAction(id, buf);
|
const char *action = commandIsAction(window->id, buf);
|
||||||
if (privmsg) {
|
if (privmsg) {
|
||||||
prefix = "<"; suffix = "> ";
|
prefix = "<"; suffix = "> ";
|
||||||
skip = privmsg;
|
skip = privmsg;
|
||||||
|
@ -703,7 +734,7 @@ static void inputUpdate(void) {
|
||||||
stylePrompt.attr |= Italic;
|
stylePrompt.attr |= Italic;
|
||||||
styleInput.attr |= Italic;
|
styleInput.attr |= Italic;
|
||||||
skip = action;
|
skip = action;
|
||||||
} else if (id == Debug && buf[0] != '/') {
|
} else if (window->id == Debug && buf[0] != '/') {
|
||||||
prompt = "<< ";
|
prompt = "<< ";
|
||||||
stylePrompt.fg = Gray;
|
stylePrompt.fg = Gray;
|
||||||
} else {
|
} else {
|
||||||
|
@ -716,6 +747,10 @@ static void inputUpdate(void) {
|
||||||
|
|
||||||
int y, x;
|
int y, x;
|
||||||
wmove(input, 0, 0);
|
wmove(input, 0, 0);
|
||||||
|
if (window->time && window->id != Network) {
|
||||||
|
whline(input, ' ', uiTime.width + 1);
|
||||||
|
wmove(input, 0, uiTime.width + 1);
|
||||||
|
}
|
||||||
wattr_set(input, styleAttr(stylePrompt), stylePair(stylePrompt), NULL);
|
wattr_set(input, styleAttr(stylePrompt), stylePair(stylePrompt), NULL);
|
||||||
waddstr(input, prefix);
|
waddstr(input, prefix);
|
||||||
waddstr(input, prompt);
|
waddstr(input, prompt);
|
||||||
|
@ -807,6 +842,14 @@ static void scrollSearch(struct Window *window, const char *str, int dir) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void toggleTime(struct Window *window) {
|
||||||
|
window->time ^= true;
|
||||||
|
windowReflow(window);
|
||||||
|
statusUpdate();
|
||||||
|
mainUpdate();
|
||||||
|
inputUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
static void incThresh(struct Window *window, int n) {
|
static void incThresh(struct Window *window, int n) {
|
||||||
if (n > 0 && window->thresh == Hot) return;
|
if (n > 0 && window->thresh == Hot) return;
|
||||||
if (n < 0 && window->thresh == Ice) {
|
if (n < 0 && window->thresh == Ice) {
|
||||||
|
@ -815,6 +858,7 @@ static void incThresh(struct Window *window, int n) {
|
||||||
window->thresh += n;
|
window->thresh += n;
|
||||||
}
|
}
|
||||||
windowReflow(window);
|
windowReflow(window);
|
||||||
|
statusUpdate();
|
||||||
mainUpdate();
|
mainUpdate();
|
||||||
statusUpdate();
|
statusUpdate();
|
||||||
}
|
}
|
||||||
|
@ -874,6 +918,7 @@ static void keyCode(int code) {
|
||||||
break; case KeyMetaN: scrollHot(window, +1);
|
break; case KeyMetaN: scrollHot(window, +1);
|
||||||
break; case KeyMetaP: scrollHot(window, -1);
|
break; case KeyMetaP: scrollHot(window, -1);
|
||||||
break; case KeyMetaQ: edit(id, EditCollapse, 0);
|
break; case KeyMetaQ: edit(id, EditCollapse, 0);
|
||||||
|
break; case KeyMetaT: toggleTime(window);
|
||||||
break; case KeyMetaU: scrollTo(window, window->unreadHard);
|
break; case KeyMetaU: scrollTo(window, window->unreadHard);
|
||||||
break; case KeyMetaV: scrollPage(window, +1);
|
break; case KeyMetaV: scrollPage(window, +1);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue