Add UI "heat" for status/messages/pings

Bring back the beeps! Allow pings from notices. Also factor out
dequoting of part/quit messages.
master
Curtis McEnroe 2018-08-17 14:00:08 -04:00
parent a38738c938
commit 38fc42f03d
No known key found for this signature in database
GPG Key ID: CEA2F97ADCFCD77C
6 changed files with 151 additions and 61 deletions

10
chat.c
View File

@ -60,7 +60,7 @@ static union {
void spawn(char *const argv[]) { void spawn(char *const argv[]) {
if (fds.pipe.events) { if (fds.pipe.events) {
uiLog(TAG_STATUS, L"spawn: existing pipe"); uiLog(TAG_STATUS, UI_WARM, L"spawn: existing pipe");
return; return;
} }
@ -93,7 +93,7 @@ static void pipeRead(void) {
if (len) { if (len) {
buf[len] = '\0'; buf[len] = '\0';
len = strcspn(buf, "\n"); len = strcspn(buf, "\n");
uiFmt(TAG_STATUS, "spawn: %.*s", (int)len, buf); uiFmt(TAG_STATUS, UI_WARM, "spawn: %.*s", (int)len, buf);
} else { } else {
close(fds.pipe.fd); close(fds.pipe.fd);
fds.pipe.events = 0; fds.pipe.events = 0;
@ -124,9 +124,9 @@ static void sigchld(int sig) {
pid_t pid = wait(&status); pid_t pid = wait(&status);
if (pid < 0) err(EX_OSERR, "wait"); if (pid < 0) err(EX_OSERR, "wait");
if (WIFEXITED(status) && WEXITSTATUS(status)) { if (WIFEXITED(status) && WEXITSTATUS(status)) {
uiFmt(TAG_STATUS, "spawn: exit %d", WEXITSTATUS(status)); uiFmt(TAG_STATUS, UI_WARM, "spawn: exit %d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) { } else if (WIFSIGNALED(status)) {
uiFmt(TAG_STATUS, "spawn: signal %d", WTERMSIG(status)); uiFmt(TAG_STATUS, UI_WARM, "spawn: signal %d", WTERMSIG(status));
} }
} }
@ -182,7 +182,7 @@ int main(int argc, char *argv[]) {
inputTab(); inputTab();
uiInit(); uiInit();
uiLog(TAG_STATUS, L"Traveling..."); uiLog(TAG_STATUS, UI_WARM, L"Traveling...");
uiDraw(); uiDraw();
fds.irc.fd = ircConnect(host, port, pass, webirc); fds.irc.fd = ircConnect(host, port, pass, webirc);

17
chat.h
View File

@ -91,12 +91,19 @@ void uiHide(void);
void uiExit(void); void uiExit(void);
void uiDraw(void); void uiDraw(void);
void uiRead(void); void uiRead(void);
void uiViewTag(struct Tag tag); void uiViewTag(struct Tag tag);
void uiViewNum(int num); void uiViewNum(int num);
void uiCloseTag(struct Tag tag); void uiCloseTag(struct Tag tag);
enum UIHeat {
UI_COLD,
UI_WARM,
UI_HOT,
};
void uiTopic(struct Tag tag, const char *topic); void uiTopic(struct Tag tag, const char *topic);
void uiLog(struct Tag tag, const wchar_t *line); void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *line);
void uiFmt(struct Tag tag, const wchar_t *format, ...); void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...);
enum TermMode { enum TermMode {
TERM_FOCUS, TERM_FOCUS,
@ -155,10 +162,10 @@ int vaswprintf(wchar_t **ret, const wchar_t *format, va_list ap);
// HACK: clang won't check wchar_t *format strings. // HACK: clang won't check wchar_t *format strings.
#ifdef NDEBUG #ifdef NDEBUG
#define uiFmt(tag, format, ...) uiFmt(tag, L##format, __VA_ARGS__) #define uiFmt(tag, heat, format, ...) uiFmt(tag, heat, L##format, __VA_ARGS__)
#else #else
#define uiFmt(tag, format, ...) do { \ #define uiFmt(tag, heat, format, ...) do { \
snprintf(NULL, 0, format, __VA_ARGS__); \ snprintf(NULL, 0, format, __VA_ARGS__); \
uiFmt(tag, L##format, __VA_ARGS__); \ uiFmt(tag, heat, L##format, __VA_ARGS__); \
} while(0) } while(0)
#endif #endif

127
handle.c
View File

@ -93,8 +93,7 @@ static bool isSelf(const char *nick, const char *user) {
return false; return false;
} }
static bool isPing(const char *nick, const char *user, const char *mesg) { static bool isPing(const char *mesg) {
if (isSelf(nick, user)) return false;
size_t len = strlen(self.nick); size_t len = strlen(self.nick);
const char *match = mesg; const char *match = mesg;
while (NULL != (match = strcasestr(match, self.nick))) { while (NULL != (match = strcasestr(match, self.nick))) {
@ -108,6 +107,13 @@ static bool isPing(const char *nick, const char *user, const char *mesg) {
return false; return false;
} }
static char *dequote(char *mesg) {
if (mesg[0] == '"') mesg = &mesg[1];
size_t len = strlen(mesg);
if (mesg[len - 1] == '"') mesg[len - 1] = '\0';
return mesg;
}
typedef void (*Handler)(char *prefix, char *params); typedef void (*Handler)(char *prefix, char *params);
static void handlePing(char *prefix, char *params) { static void handlePing(char *prefix, char *params) {
@ -118,39 +124,46 @@ static void handlePing(char *prefix, char *params) {
static void handleReplyErroneousNickname(char *prefix, char *params) { static void handleReplyErroneousNickname(char *prefix, char *params) {
char *mesg; char *mesg;
shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg); shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, NULL, &mesg);
uiLog(TAG_STATUS, L"You can't use that name here"); // FIXME: Better formatting.
uiFmt(TAG_STATUS, "Sheriff says, \"%s\"", mesg); uiLog(TAG_STATUS, UI_HOT, L"You can't use that name here");
uiLog(TAG_STATUS, L"Type /nick <name> to choose a new one"); uiFmt(TAG_STATUS, UI_HOT, "Sheriff says, \"%s\"", mesg);
uiLog(TAG_STATUS, UI_HOT, L"Type /nick <name> to choose a new one");
} }
static void handleReplyWelcome(char *prefix, char *params) { static void handleReplyWelcome(char *prefix, char *params) {
char *nick; char *nick;
shift(prefix, NULL, NULL, NULL, params, 1, 0, &nick); shift(prefix, NULL, NULL, NULL, params, 1, 0, &nick);
if (strcmp(nick, self.nick)) selfNick(nick); if (strcmp(nick, self.nick)) selfNick(nick);
tabTouch(TAG_STATUS, self.nick);
if (self.join) ircFmt("JOIN %s\r\n", self.join); if (self.join) ircFmt("JOIN %s\r\n", self.join);
uiLog(TAG_STATUS, L"You have arrived"); tabTouch(TAG_STATUS, self.nick);
uiLog(TAG_STATUS, UI_WARM, L"You have arrived");
} }
static void handleReplyMOTD(char *prefix, char *params) { static void handleReplyMOTD(char *prefix, char *params) {
char *mesg; char *mesg;
shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &mesg); shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &mesg);
if (mesg[0] == '-' && mesg[1] == ' ') mesg = &mesg[2]; if (mesg[0] == '-' && mesg[1] == ' ') mesg = &mesg[2];
urlScan(TAG_STATUS, mesg); urlScan(TAG_STATUS, mesg);
uiFmt(TAG_STATUS, "%s", mesg); uiFmt(TAG_STATUS, UI_COLD, "%s", mesg);
} }
static void handleJoin(char *prefix, char *params) { static void handleJoin(char *prefix, char *params) {
char *nick, *user, *chan; char *nick, *user, *chan;
shift(prefix, &nick, &user, NULL, params, 1, 0, &chan); shift(prefix, &nick, &user, NULL, params, 1, 0, &chan);
struct Tag tag = tagFor(chan); struct Tag tag = tagFor(chan);
if (isSelf(nick, user)) { if (isSelf(nick, user)) {
tabTouch(TAG_NONE, chan); tabTouch(TAG_NONE, chan);
uiViewTag(tag); uiViewTag(tag);
} }
tabTouch(tag, nick); tabTouch(tag, nick);
uiFmt( uiFmt(
tag, "\3%d%s\3 arrives in \3%d%s\3", tag, UI_COLD,
"\3%d%s\3 arrives in \3%d%s\3",
color(user), nick, color(chan), chan color(user), nick, color(chan), chan
); );
} }
@ -159,16 +172,24 @@ static void handlePart(char *prefix, char *params) {
char *nick, *user, *chan, *mesg; char *nick, *user, *chan, *mesg;
shift(prefix, &nick, &user, NULL, params, 1, 1, &chan, &mesg); shift(prefix, &nick, &user, NULL, params, 1, 1, &chan, &mesg);
struct Tag tag = tagFor(chan); struct Tag tag = tagFor(chan);
(void)(isSelf(nick, user) ? tabClear(tag) : tabRemove(tag, nick));
if (isSelf(nick, user)) {
tabClear(tag);
} else {
tabRemove(tag, nick);
}
if (mesg) { if (mesg) {
urlScan(tag, mesg); urlScan(tag, mesg);
uiFmt( uiFmt(
tag, "\3%d%s\3 leaves \3%d%s\3, \"%s\"", tag, UI_COLD,
"\3%d%s\3 leaves \3%d%s\3, \"%s\"",
color(user), nick, color(chan), chan, mesg color(user), nick, color(chan), chan, mesg
); );
} else { } else {
uiFmt( uiFmt(
tag, "\3%d%s\3 leaves \3%d%s\3", tag, UI_COLD,
"\3%d%s\3 leaves \3%d%s\3",
color(user), nick, color(chan), chan color(user), nick, color(chan), chan
); );
} }
@ -178,16 +199,26 @@ static void handleKick(char *prefix, char *params) {
char *nick, *user, *chan, *kick, *mesg; char *nick, *user, *chan, *kick, *mesg;
shift(prefix, &nick, &user, NULL, params, 2, 1, &chan, &kick, &mesg); shift(prefix, &nick, &user, NULL, params, 2, 1, &chan, &kick, &mesg);
struct Tag tag = tagFor(chan); struct Tag tag = tagFor(chan);
(void)(isSelf(nick, user) ? tabClear(tag) : tabRemove(tag, nick)); bool kicked = !strcmp(kick, self.nick);
if (kicked) {
tabClear(tag);
} else {
tabRemove(tag, kick);
}
if (mesg) { if (mesg) {
urlScan(tag, mesg); urlScan(tag, mesg);
uiFmt( uiFmt(
tag, "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"", tag, (kicked ? UI_HOT : UI_COLD),
color(user), nick, color(kick), kick, color(chan), chan, mesg "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3, \"%s\"",
color(user), nick, color(kick), kick, color(chan), chan,
dequote(mesg)
); );
} else { } else {
uiFmt( uiFmt(
tag, "\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3", tag, (kicked ? UI_HOT : UI_COLD),
"\3%d%s\3 kicks \3%d%s\3 out of \3%d%s\3",
color(user), nick, color(kick), kick, color(chan), chan color(user), nick, color(kick), kick, color(chan), chan
); );
} }
@ -196,18 +227,20 @@ static void handleKick(char *prefix, char *params) {
static void handleQuit(char *prefix, char *params) { static void handleQuit(char *prefix, char *params) {
char *nick, *user, *mesg; char *nick, *user, *mesg;
shift(prefix, &nick, &user, NULL, params, 0, 1, &mesg); shift(prefix, &nick, &user, NULL, params, 0, 1, &mesg);
char *quot = (mesg && mesg[0] == '"') ? "" : "\"";
struct Tag tag; struct Tag tag;
while (TAG_NONE.id != (tag = tabTag(nick)).id) { while (TAG_NONE.id != (tag = tabTag(nick)).id) {
tabRemove(tag, nick); tabRemove(tag, nick);
if (mesg) { if (mesg) {
urlScan(tag, mesg); urlScan(tag, mesg);
uiFmt( uiFmt(
tag, "\3%d%s\3 leaves, %s%s%s", tag, UI_COLD,
color(user), nick, quot, mesg, quot "\3%d%s\3 leaves, \"%s\"",
color(user), nick, dequote(mesg)
); );
} else { } else {
uiFmt(tag, "\3%d%s\3 leaves", color(user), nick); uiFmt(tag, UI_COLD, "\3%d%s\3 leaves", color(user), nick);
} }
} }
} }
@ -216,10 +249,12 @@ static void handleReplyTopic(char *prefix, char *params) {
char *chan, *topic; char *chan, *topic;
shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &chan, &topic); shift(prefix, NULL, NULL, NULL, params, 3, 0, NULL, &chan, &topic);
struct Tag tag = tagFor(chan); struct Tag tag = tagFor(chan);
urlScan(tag, topic); urlScan(tag, topic);
uiTopic(tag, topic); uiTopic(tag, topic);
uiFmt( uiFmt(
tag, "The sign in \3%d%s\3 reads, \"%s\"", tag, UI_COLD,
"The sign in \3%d%s\3 reads, \"%s\"",
color(chan), chan, topic color(chan), chan, topic
); );
} }
@ -228,11 +263,14 @@ static void handleTopic(char *prefix, char *params) {
char *nick, *user, *chan, *topic; char *nick, *user, *chan, *topic;
shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &topic); shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &topic);
struct Tag tag = tagFor(chan); struct Tag tag = tagFor(chan);
if (!isSelf(nick, user)) tabTouch(tag, nick); if (!isSelf(nick, user)) tabTouch(tag, nick);
urlScan(tag, topic); urlScan(tag, topic);
uiTopic(tag, topic); uiTopic(tag, topic);
uiFmt( uiFmt(
tag, "\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"", tag, UI_COLD,
"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
color(user), nick, color(chan), chan, topic color(user), nick, color(chan), chan, topic
); );
} }
@ -256,7 +294,10 @@ static void handleReplyWho(char *prefix, char *params) {
params, 6, 0, NULL, &chan, &user, NULL, NULL, &nick params, 6, 0, NULL, &chan, &user, NULL, NULL, &nick
); );
struct Tag tag = tagFor(chan); struct Tag tag = tagFor(chan);
// FIXME: Don't clobber order if they already exist.
tabTouch(tag, nick); tabTouch(tag, nick);
size_t cap = sizeof(who.buf) - who.len; size_t cap = sizeof(who.buf) - who.len;
int len = snprintf( int len = snprintf(
&who.buf[who.len], cap, &who.buf[who.len], cap,
@ -270,8 +311,10 @@ static void handleReplyEndOfWho(char *prefix, char *params) {
char *chan; char *chan;
shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan); shift(prefix, NULL, NULL, NULL, params, 2, 0, NULL, &chan);
struct Tag tag = tagFor(chan); struct Tag tag = tagFor(chan);
uiFmt( uiFmt(
tag, "In \3%d%s\3 are %s", tag, UI_COLD,
"In \3%d%s\3 are %s",
color(chan), chan, who.buf color(chan), chan, who.buf
); );
who.len = 0; who.len = 0;
@ -280,12 +323,16 @@ static void handleReplyEndOfWho(char *prefix, char *params) {
static void handleNick(char *prefix, char *params) { static void handleNick(char *prefix, char *params) {
char *prev, *user, *next; char *prev, *user, *next;
shift(prefix, &prev, &user, NULL, params, 1, 0, &next); shift(prefix, &prev, &user, NULL, params, 1, 0, &next);
if (isSelf(prev, user)) selfNick(next); if (isSelf(prev, user)) selfNick(next);
struct Tag tag; struct Tag tag;
while (TAG_NONE.id != (tag = tabTag(prev)).id) { while (TAG_NONE.id != (tag = tabTag(prev)).id) {
tabReplace(tag, prev, next); tabReplace(tag, prev, next);
uiFmt( uiFmt(
tag, "\3%d%s\3 is now known as \3%d%s\3", tag, UI_COLD,
"\3%d%s\3 is now known as \3%d%s\3",
color(user), prev, color(user), next color(user), prev, color(user), next
); );
} }
@ -296,11 +343,15 @@ static void handleCTCP(struct Tag tag, char *nick, char *user, char *mesg) {
char *ctcp = strsep(&mesg, " "); char *ctcp = strsep(&mesg, " ");
char *params = strsep(&mesg, "\1"); char *params = strsep(&mesg, "\1");
if (strcmp(ctcp, "ACTION")) return; if (strcmp(ctcp, "ACTION")) return;
if (!isSelf(nick, user)) tabTouch(tag, nick);
bool self = isSelf(nick, user);
if (!self) tabTouch(tag, nick);
urlScan(tag, params); urlScan(tag, params);
bool ping = isPing(nick, user, params); bool ping = !self && isPing(params);
uiFmt( uiFmt(
tag, "%c\3%d* %s\17 %s", tag, (ping ? UI_HOT : UI_WARM),
"%c\3%d* %s\17 %s",
ping["\17\26"], color(user), nick, params ping["\17\26"], color(user), nick, params
); );
} }
@ -313,12 +364,15 @@ static void handlePrivmsg(char *prefix, char *params) {
handleCTCP(tag, nick, user, mesg); handleCTCP(tag, nick, user, mesg);
return; return;
} }
if (!isSelf(nick, user)) tabTouch(tag, nick);
urlScan(tag, mesg);
bool self = isSelf(nick, user); bool self = isSelf(nick, user);
bool ping = isPing(nick, user, mesg); if (!self) tabTouch(tag, nick);
urlScan(tag, mesg);
bool ping = !self && isPing(mesg);
uiFmt( uiFmt(
tag, "%c\3%d%c%s%c\17 %s", tag, (ping ? UI_HOT : UI_WARM),
"%c\3%d%c%s%c\17 %s",
ping["\17\26"], color(user), self["<("], nick, self[">)"], mesg ping["\17\26"], color(user), self["<("], nick, self[">)"], mesg
); );
} }
@ -328,11 +382,16 @@ static void handleNotice(char *prefix, char *params) {
shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg); shift(prefix, &nick, &user, NULL, params, 2, 0, &chan, &mesg);
struct Tag tag = TAG_STATUS; struct Tag tag = TAG_STATUS;
if (user) tag = (strcmp(chan, self.nick) ? tagFor(chan) : tagFor(nick)); if (user) tag = (strcmp(chan, self.nick) ? tagFor(chan) : tagFor(nick));
if (!isSelf(nick, user)) tabTouch(tag, nick);
bool self = isSelf(nick, user);
if (!self) tabTouch(tag, nick);
urlScan(tag, mesg); urlScan(tag, mesg);
bool ping = !self && isPing(mesg);
uiFmt( uiFmt(
tag, "\3%d-%s-\3 %s", tag, (ping ? UI_HOT : UI_WARM),
color(user), nick, mesg "%c\3%d-%s-\17 %s",
ping["\17\26"], color(user), nick, mesg
); );
} }

View File

@ -40,7 +40,7 @@ static void privmsg(struct Tag tag, bool action, const char *mesg) {
static char *param(const char *command, char **params, const char *name) { static char *param(const char *command, char **params, const char *name) {
char *param = strsep(params, " "); char *param = strsep(params, " ");
if (param) return param; if (param) return param;
uiFmt(TAG_STATUS, "%s requires a %s", command, name); uiFmt(TAG_STATUS, UI_WARM, "%s requires a %s", command, name);
return NULL; return NULL;
} }
@ -123,7 +123,7 @@ static void inputView(struct Tag tag, char *params) {
if (tag.id != TAG_NONE.id) { if (tag.id != TAG_NONE.id) {
uiViewTag(tag); uiViewTag(tag);
} else { } else {
uiFmt(TAG_STATUS, "No view for %s", view); uiFmt(TAG_STATUS, UI_WARM, "No view for %s", view);
} }
} }
} }
@ -178,7 +178,7 @@ void input(struct Tag tag, char *input) {
COMMANDS[i].handler(tag, input); COMMANDS[i].handler(tag, input);
return; return;
} }
uiFmt(TAG_STATUS, "%s isn't a recognized command", command); uiFmt(TAG_STATUS, UI_WARM, "%s isn't a recognized command", command);
} }
void inputTab(void) { void inputTab(void) {

14
irc.c
View File

@ -106,7 +106,12 @@ void ircFmt(const char *format, ...) {
int len = vasprintf(&buf, format, ap); int len = vasprintf(&buf, format, ap);
va_end(ap); va_end(ap);
if (!buf) err(EX_OSERR, "vasprintf"); if (!buf) err(EX_OSERR, "vasprintf");
if (self.verbose) uiFmt(TAG_VERBOSE, "\3%d<<<\3 %.*s", IRC_WHITE, len - 2, buf); if (self.verbose) {
uiFmt(
TAG_VERBOSE, UI_COLD,
"\3%d<<<\3 %.*s", IRC_WHITE, len - 2, buf
);
}
ircWrite(buf, len); ircWrite(buf, len);
free(buf); free(buf);
} }
@ -126,7 +131,12 @@ void ircRead(void) {
char *crlf, *line = buf; char *crlf, *line = buf;
while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) { while ((crlf = strnstr(line, "\r\n", &buf[len] - line))) {
crlf[0] = '\0'; crlf[0] = '\0';
if (self.verbose) uiFmt(TAG_VERBOSE, "\3%d>>>\3 %s", IRC_GRAY, line); if (self.verbose) {
uiFmt(
TAG_VERBOSE, UI_COLD,
"\3%d>>>\3 %s", IRC_GRAY, line
);
}
handle(line); handle(line);
line = &crlf[2]; line = &crlf[2];
} }

38
ui.c
View File

@ -75,6 +75,8 @@ struct View {
WINDOW *topic; WINDOW *topic;
WINDOW *log; WINDOW *log;
int scroll; int scroll;
int unread;
bool hot;
bool mark; bool mark;
struct View *prev; struct View *prev;
struct View *next; struct View *next;
@ -145,6 +147,15 @@ static void viewClose(struct View *view) {
free(view); free(view);
} }
static void viewMark(struct View *view) {
view->mark = true;
}
static void viewUnmark(struct View *view) {
view->unread = 0;
view->hot = false;
view->mark = false;
}
static struct { static struct {
bool hide; bool hide;
struct View *view; struct View *view;
@ -233,8 +244,8 @@ static void uiView(struct View *view) {
termTitle(view->tag.name); termTitle(view->tag.name);
if (view->topic) touchwin(view->topic); if (view->topic) touchwin(view->topic);
touchwin(view->log); touchwin(view->log);
ui.view->mark = true; viewMark(ui.view);
view->mark = false; viewUnmark(view);
ui.view = view; ui.view = view;
} }
@ -383,37 +394,40 @@ void uiTopic(struct Tag tag, const char *topic) {
free(wcs); free(wcs);
} }
void uiLog(struct Tag tag, const wchar_t *line) { void uiLog(struct Tag tag, enum UIHeat heat, const wchar_t *line) {
struct View *view = viewTag(tag); struct View *view = viewTag(tag);
waddch(view->log, '\n'); waddch(view->log, '\n');
if (view->mark) { if (view->mark && heat > UI_COLD) {
waddch(view->log, '\n'); if (!view->unread++) waddch(view->log, '\n');
view->mark = false; if (heat > UI_WARM) {
view->hot = true;
beep(); // TODO: Notification.
}
} }
addIRC(view->log, line); addIRC(view->log, line);
} }
void uiFmt(struct Tag tag, const wchar_t *format, ...) { void uiFmt(struct Tag tag, enum UIHeat heat, const wchar_t *format, ...) {
wchar_t *wcs; wchar_t *wcs;
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
vaswprintf(&wcs, format, ap); vaswprintf(&wcs, format, ap);
va_end(ap); va_end(ap);
if (!wcs) err(EX_OSERR, "vaswprintf"); if (!wcs) err(EX_OSERR, "vaswprintf");
uiLog(tag, wcs); uiLog(tag, heat, wcs);
free(wcs); free(wcs);
} }
static void logScrollUp(int lines) { static void logScrollUp(int lines) {
int height = logHeight(ui.view); int height = logHeight(ui.view);
if (ui.view->scroll == height) return; if (ui.view->scroll == height) return;
if (ui.view->scroll == LOG_LINES) ui.view->mark = true; if (ui.view->scroll == LOG_LINES) viewMark(ui.view);
ui.view->scroll = MAX(ui.view->scroll - lines, height); ui.view->scroll = MAX(ui.view->scroll - lines, height);
} }
static void logScrollDown(int lines) { static void logScrollDown(int lines) {
if (ui.view->scroll == LOG_LINES) return; if (ui.view->scroll == LOG_LINES) return;
ui.view->scroll = MIN(ui.view->scroll + lines, LOG_LINES); ui.view->scroll = MIN(ui.view->scroll + lines, LOG_LINES);
if (ui.view->scroll == LOG_LINES) ui.view->mark = false; if (ui.view->scroll == LOG_LINES) viewUnmark(ui.view);
} }
static void logPageUp(void) { static void logPageUp(void) {
logScrollUp(logHeight(ui.view) / 2); logScrollUp(logHeight(ui.view) / 2);
@ -426,8 +440,8 @@ static bool keyChar(wchar_t ch) {
if (iswascii(ch)) { if (iswascii(ch)) {
enum TermEvent event = termEvent((char)ch); enum TermEvent event = termEvent((char)ch);
switch (event) { switch (event) {
break; case TERM_FOCUS_IN: ui.view->mark = false; break; case TERM_FOCUS_IN: viewUnmark(ui.view);
break; case TERM_FOCUS_OUT: ui.view->mark = true; break; case TERM_FOCUS_OUT: viewMark(ui.view);
break; default: {} break; default: {}
} }
if (event) return false; if (event) return false;