Compare commits

15 Commits
master ... sam

Author SHA1 Message Date
aecf3959eb patch: alpha 2026-01-05 21:51:28 -05:00
2900f20078 patch: ligatures 2026-01-05 20:42:46 -05:00
7ae65ec8ed patch: ligatures 2026-01-05 20:42:21 -05:00
fed2a38f94 patch: appsync 2026-01-05 20:34:38 -05:00
ff42b9bd86 patch: open-selected 2026-01-01 15:07:12 -05:00
cef81e7128 config.h shouldn't be part of the tree 2026-01-01 14:04:28 -05:00
2b850e65ec patch: scrollback-mouse + scrollback-mouse-altscreen 2026-01-01 14:03:46 -05:00
95f4807ccb no clang format 2026-01-01 13:47:51 -05:00
27c06ee787 remove diff files 2026-01-01 13:47:02 -05:00
1369487bb8 Revert "patch: ligatures-scrollback-ringbuffer"
This reverts commit ab0590b48a.
2026-01-01 13:46:17 -05:00
77accdfa1f .gitignore 2026-01-01 13:41:26 -05:00
ab0590b48a patch: ligatures-scrollback-ringbuffer 2026-01-01 13:39:47 -05:00
d9fa845fc4 patch: bold-is-not-bright 2026-01-01 13:13:04 -05:00
577f633ef4 patch: bold-is-not-bright 2026-01-01 13:13:04 -05:00
ab5d49f991 patch: scrollback-ringbuffer 2026-01-01 13:04:01 -05:00
12 changed files with 695 additions and 268 deletions

3
.clang-format Normal file
View File

@@ -0,0 +1,3 @@
---
DisableFormat: true
---

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
st
*.o
*.orig
*.regj
config.h
*.diff

View File

@@ -4,7 +4,7 @@
include config.mk include config.mk
SRC = st.c x.c SRC = st.c x.c hb.c
OBJ = $(SRC:.c=.o) OBJ = $(SRC:.c=.o)
all: st all: st
@@ -16,7 +16,8 @@ config.h:
$(CC) $(STCFLAGS) -c $< $(CC) $(STCFLAGS) -c $<
st.o: config.h st.h win.h st.o: config.h st.h win.h
x.o: arg.h config.h st.h win.h x.o: arg.h config.h st.h win.h hb.h
hb.o: st.h
$(OBJ): config.h config.mk $(OBJ): config.h config.mk

View File

@@ -56,6 +56,12 @@ int allowwindowops = 0;
static double minlatency = 2; static double minlatency = 2;
static double maxlatency = 33; static double maxlatency = 33;
/*
* Synchronized-Update timeout in ms
* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec
*/
static uint su_timeout = 200;
/* /*
* blinking timeout (set to 0 to disable blinking) for the terminal blinking * blinking timeout (set to 0 to disable blinking) for the terminal blinking
* attribute. * attribute.
@@ -93,6 +99,9 @@ char *termname = "st-256color";
*/ */
unsigned int tabspaces = 8; unsigned int tabspaces = 8;
/* bg opacity */
float alpha = 0.8;
/* Terminal colors (16 first used in escape sequence) */ /* Terminal colors (16 first used in escape sequence) */
static const char *colorname[] = { static const char *colorname[] = {
/* 8 normal colors */ /* 8 normal colors */
@@ -176,6 +185,9 @@ static uint forcemousemod = ShiftMask;
*/ */
static MouseShortcut mshortcuts[] = { static MouseShortcut mshortcuts[] = {
/* mask button function argument release */ /* mask button function argument release */
{ ControlMask, Button2, selopen, {.i = 0}, 1 },
{ XK_ANY_MOD, Button4, kscrollup, {.i = 1}, 0, /* !alt */ -1 },
{ XK_ANY_MOD, Button5, kscrolldown, {.i = 1}, 0, /* !alt */ -1 },
{ XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
{ ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} },
{ XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} },
@@ -201,6 +213,8 @@ static Shortcut shortcuts[] = {
{ TERMMOD, XK_Y, selpaste, {.i = 0} }, { TERMMOD, XK_Y, selpaste, {.i = 0} },
{ ShiftMask, XK_Insert, selpaste, {.i = 0} }, { ShiftMask, XK_Insert, selpaste, {.i = 0} },
{ TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, { TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
{ ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
{ ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
}; };
/* /*

View File

@@ -15,10 +15,12 @@ PKG_CONFIG = pkg-config
# includes and libs # includes and libs
INCS = -I$(X11INC) \ INCS = -I$(X11INC) \
`$(PKG_CONFIG) --cflags fontconfig` \ `$(PKG_CONFIG) --cflags fontconfig` \
`$(PKG_CONFIG) --cflags freetype2` `$(PKG_CONFIG) --cflags freetype2` \
`$(PKG_CONFIG) --cflags harfbuzz`
LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \ LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
`$(PKG_CONFIG) --libs fontconfig` \ `$(PKG_CONFIG) --libs fontconfig` \
`$(PKG_CONFIG) --libs freetype2` `$(PKG_CONFIG) --libs freetype2` \
`$(PKG_CONFIG) --libs harfbuzz`
# flags # flags
STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600
@@ -29,7 +31,8 @@ STLDFLAGS = $(LIBS) $(LDFLAGS)
#CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE #CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600 -D_BSD_SOURCE
#LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \ #LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
# `$(PKG_CONFIG) --libs fontconfig` \ # `$(PKG_CONFIG) --libs fontconfig` \
# `$(PKG_CONFIG) --libs freetype2` # `$(PKG_CONFIG) --libs freetype2` \
# `$(PKG_CONFIG) --libs harfbuzz`
#MANPREFIX = ${PREFIX}/man #MANPREFIX = ${PREFIX}/man
# compiler and linker # compiler and linker

125
hb.c Normal file
View File

@@ -0,0 +1,125 @@
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <X11/Xft/Xft.h>
#include <X11/cursorfont.h>
#include <hb.h>
#include <hb-ft.h>
#include "st.h"
#include "hb.h"
#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END }
#define BUFFER_STEP 256
hb_font_t *hbfindfont(XftFont *match);
typedef struct {
XftFont *match;
hb_font_t *font;
} HbFontMatch;
typedef struct {
size_t capacity;
HbFontMatch *fonts;
} HbFontCache;
static HbFontCache hbfontcache = { 0, NULL };
typedef struct {
size_t capacity;
Rune *runes;
} RuneBuffer;
static RuneBuffer hbrunebuffer = { 0, NULL };
/*
* Poplulate the array with a list of font features, wrapped in FEATURE macro,
* e. g.
* FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g')
*/
hb_feature_t features[] = { FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') };
void
hbunloadfonts()
{
for (int i = 0; i < hbfontcache.capacity; i++) {
hb_font_destroy(hbfontcache.fonts[i].font);
XftUnlockFace(hbfontcache.fonts[i].match);
}
if (hbfontcache.fonts != NULL) {
free(hbfontcache.fonts);
hbfontcache.fonts = NULL;
}
hbfontcache.capacity = 0;
}
hb_font_t *
hbfindfont(XftFont *match)
{
for (int i = 0; i < hbfontcache.capacity; i++) {
if (hbfontcache.fonts[i].match == match)
return hbfontcache.fonts[i].font;
}
/* Font not found in cache, caching it now. */
hbfontcache.fonts = realloc(hbfontcache.fonts, sizeof(HbFontMatch) * (hbfontcache.capacity + 1));
FT_Face face = XftLockFace(match);
hb_font_t *font = hb_ft_font_create(face, NULL);
if (font == NULL)
die("Failed to load Harfbuzz font.");
hbfontcache.fonts[hbfontcache.capacity].match = match;
hbfontcache.fonts[hbfontcache.capacity].font = font;
hbfontcache.capacity += 1;
return font;
}
void hbtransform(HbTransformData *data, XftFont *xfont, const Glyph *glyphs, int start, int length) {
ushort mode = USHRT_MAX;
unsigned int glyph_count;
int rune_idx, glyph_idx, end = start + length;
hb_font_t *font = hbfindfont(xfont);
if (font == NULL)
return;
hb_buffer_t *buffer = hb_buffer_create();
hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
/* Resize the buffer if required length is larger. */
if (hbrunebuffer.capacity < length) {
hbrunebuffer.capacity = (length / BUFFER_STEP + 1) * BUFFER_STEP;
hbrunebuffer.runes = realloc(hbrunebuffer.runes, hbrunebuffer.capacity * sizeof(Rune));
}
/* Fill buffer with codepoints. */
for (rune_idx = 0, glyph_idx = start; glyph_idx < end; glyph_idx++, rune_idx++) {
hbrunebuffer.runes[rune_idx] = glyphs[glyph_idx].u;
mode = glyphs[glyph_idx].mode;
if (mode & ATTR_WDUMMY)
hbrunebuffer.runes[rune_idx] = 0x0020;
}
hb_buffer_add_codepoints(buffer, hbrunebuffer.runes, length, 0, length);
/* Shape the segment. */
hb_shape(font, buffer, features, sizeof(features)/sizeof(hb_feature_t));
/* Get new glyph info. */
hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &glyph_count);
hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &glyph_count);
/* Fill the output. */
data->buffer = buffer;
data->glyphs = info;
data->positions = pos;
data->count = glyph_count;
}
void hbcleanup(HbTransformData *data) {
hb_buffer_destroy(data->buffer);
memset(data, 0, sizeof(HbTransformData));
}

15
hb.h Normal file
View File

@@ -0,0 +1,15 @@
#include <X11/Xft/Xft.h>
#include <hb.h>
#include <hb-ft.h>
typedef struct {
hb_buffer_t *buffer;
hb_glyph_info_t *glyphs;
hb_glyph_position_t *positions;
unsigned int count;
} HbTransformData;
void hbunloadfonts();
void hbtransform(HbTransformData *, XftFont *, const Glyph *, int, int);
void hbcleanup(HbTransformData *);

425
st.c
View File

@@ -43,6 +43,10 @@
#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
#define ISDELIM(u) (u && wcschr(worddelimiters, u)) #define ISDELIM(u) (u && wcschr(worddelimiters, u))
#define TSCREEN term.screen[IS_SET(MODE_ALTSCREEN)]
#define TLINEOFFSET(y) (((y) + TSCREEN.cur - TSCREEN.off + TSCREEN.size) % TSCREEN.size)
#define TLINE(y) (TSCREEN.buffer[TLINEOFFSET(y)])
enum term_mode { enum term_mode {
MODE_WRAP = 1 << 0, MODE_WRAP = 1 << 0,
MODE_INSERT = 1 << 1, MODE_INSERT = 1 << 1,
@@ -109,12 +113,21 @@ typedef struct {
int alt; int alt;
} Selection; } Selection;
/* Screen lines */
typedef struct {
Line* buffer; /* ring buffer */
int size; /* size of buffer */
int cur; /* start of active screen */
int off; /* scrollback line offset */
TCursor sc; /* saved cursor */
} LineBuffer;
/* Internal representation of the screen */ /* Internal representation of the screen */
typedef struct { typedef struct {
int row; /* nb row */ int row; /* nb row */
int col; /* nb col */ int col; /* nb col */
Line *line; /* screen */ LineBuffer screen[2]; /* screen and alternate screen */
Line *alt; /* alternate screen */ int linelen; /* allocated line length */
int *dirty; /* dirtyness of lines */ int *dirty; /* dirtyness of lines */
TCursor c; /* cursor */ TCursor c; /* cursor */
int ocx; /* old cursor col */ int ocx; /* old cursor col */
@@ -203,6 +216,8 @@ static void tdeftran(char);
static void tstrsequence(uchar); static void tstrsequence(uchar);
static void drawregion(int, int, int, int); static void drawregion(int, int, int, int);
static void clearline(Line, Glyph, int, int);
static Line ensureline(Line);
static void selnormalize(void); static void selnormalize(void);
static void selscroll(int, int); static void selscroll(int, int);
@@ -232,6 +247,33 @@ static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; static const Rune utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
#include <time.h>
static int su = 0;
struct timespec sutv;
static void
tsync_begin()
{
clock_gettime(CLOCK_MONOTONIC, &sutv);
su = 1;
}
static void
tsync_end()
{
su = 0;
}
int
tinsync(uint timeout)
{
struct timespec now;
if (su && !clock_gettime(CLOCK_MONOTONIC, &now)
&& TIMEDIFF(now, sutv) >= timeout)
su = 0;
return su;
}
ssize_t ssize_t
xwrite(int fd, const char *s, size_t len) xwrite(int fd, const char *s, size_t len)
{ {
@@ -408,11 +450,12 @@ int
tlinelen(int y) tlinelen(int y)
{ {
int i = term.col; int i = term.col;
Line line = TLINE(y);
if (term.line[y][i - 1].mode & ATTR_WRAP) if (line[i - 1].mode & ATTR_WRAP)
return i; return i;
while (i > 0 && term.line[y][i - 1].u == ' ') while (i > 0 && line[i - 1].u == ' ')
--i; --i;
return i; return i;
@@ -521,7 +564,7 @@ selsnap(int *x, int *y, int direction)
* Snap around if the word wraps around at the end or * Snap around if the word wraps around at the end or
* beginning of a line. * beginning of a line.
*/ */
prevgp = &term.line[*y][*x]; prevgp = &TLINE(*y)[*x];
prevdelim = ISDELIM(prevgp->u); prevdelim = ISDELIM(prevgp->u);
for (;;) { for (;;) {
newx = *x + direction; newx = *x + direction;
@@ -536,14 +579,14 @@ selsnap(int *x, int *y, int direction)
yt = *y, xt = *x; yt = *y, xt = *x;
else else
yt = newy, xt = newx; yt = newy, xt = newx;
if (!(term.line[yt][xt].mode & ATTR_WRAP)) if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
break; break;
} }
if (newx >= tlinelen(newy)) if (newx >= tlinelen(newy))
break; break;
gp = &term.line[newy][newx]; gp = &TLINE(newy)[newx];
delim = ISDELIM(gp->u); delim = ISDELIM(gp->u);
if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
|| (delim && gp->u != prevgp->u))) || (delim && gp->u != prevgp->u)))
@@ -564,14 +607,14 @@ selsnap(int *x, int *y, int direction)
*x = (direction < 0) ? 0 : term.col - 1; *x = (direction < 0) ? 0 : term.col - 1;
if (direction < 0) { if (direction < 0) {
for (; *y > 0; *y += direction) { for (; *y > 0; *y += direction) {
if (!(term.line[*y-1][term.col-1].mode if (!(TLINE(*y-1)[term.col-1].mode
& ATTR_WRAP)) { & ATTR_WRAP)) {
break; break;
} }
} }
} else if (direction > 0) { } else if (direction > 0) {
for (; *y < term.row-1; *y += direction) { for (; *y < term.row-1; *y += direction) {
if (!(term.line[*y][term.col-1].mode if (!(TLINE(*y)[term.col-1].mode
& ATTR_WRAP)) { & ATTR_WRAP)) {
break; break;
} }
@@ -602,13 +645,13 @@ getsel(void)
} }
if (sel.type == SEL_RECTANGULAR) { if (sel.type == SEL_RECTANGULAR) {
gp = &term.line[y][sel.nb.x]; gp = &TLINE(y)[sel.nb.x];
lastx = sel.ne.x; lastx = sel.ne.x;
} else { } else {
gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
} }
last = &term.line[y][MIN(lastx, linelen-1)]; last = &TLINE(y)[MIN(lastx, linelen-1)];
while (last >= gp && last->u == ' ') while (last >= gp && last->u == ' ')
--last; --last;
@@ -814,6 +857,9 @@ ttynew(const char *line, char *cmd, const char *out, char **args)
return cmdfd; return cmdfd;
} }
static int twrite_aborted = 0;
int ttyread_pending() { return twrite_aborted; }
size_t size_t
ttyread(void) ttyread(void)
{ {
@@ -822,7 +868,7 @@ ttyread(void)
int ret, written; int ret, written;
/* append read bytes to unprocessed bytes */ /* append read bytes to unprocessed bytes */
ret = read(cmdfd, buf+buflen, LEN(buf)-buflen); ret = twrite_aborted ? 1 : read(cmdfd, buf+buflen, LEN(buf)-buflen);
switch (ret) { switch (ret) {
case 0: case 0:
@@ -830,7 +876,7 @@ ttyread(void)
case -1: case -1:
die("couldn't read from shell: %s\n", strerror(errno)); die("couldn't read from shell: %s\n", strerror(errno));
default: default:
buflen += ret; buflen += twrite_aborted ? 0 : ret;
written = twrite(buf, buflen, 0); written = twrite(buf, buflen, 0);
buflen -= written; buflen -= written;
/* keep any incomplete UTF-8 byte sequence for the next call */ /* keep any incomplete UTF-8 byte sequence for the next call */
@@ -949,12 +995,15 @@ int
tattrset(int attr) tattrset(int attr)
{ {
int i, j; int i, j;
int y = TLINEOFFSET(0);
for (i = 0; i < term.row-1; i++) { for (i = 0; i < term.row-1; i++) {
Line line = TSCREEN.buffer[y];
for (j = 0; j < term.col-1; j++) { for (j = 0; j < term.col-1; j++) {
if (term.line[i][j].mode & attr) if (line[j].mode & attr)
return 1; return 1;
} }
y = (y+1) % TSCREEN.size;
} }
return 0; return 0;
@@ -976,47 +1025,43 @@ void
tsetdirtattr(int attr) tsetdirtattr(int attr)
{ {
int i, j; int i, j;
int y = TLINEOFFSET(0);
for (i = 0; i < term.row-1; i++) { for (i = 0; i < term.row-1; i++) {
Line line = TSCREEN.buffer[y];
for (j = 0; j < term.col-1; j++) { for (j = 0; j < term.col-1; j++) {
if (term.line[i][j].mode & attr) { if (line[j].mode & attr) {
tsetdirt(i, i); tsetdirt(i, i);
break; break;
} }
} }
y = (y+1) % TSCREEN.size;
} }
} }
void void
tfulldirt(void) tfulldirt(void)
{ {
tsync_end();
tsetdirt(0, term.row-1); tsetdirt(0, term.row-1);
} }
void void
tcursor(int mode) tcursor(int mode)
{ {
static TCursor c[2];
int alt = IS_SET(MODE_ALTSCREEN);
if (mode == CURSOR_SAVE) { if (mode == CURSOR_SAVE) {
c[alt] = term.c; TSCREEN.sc = term.c;
} else if (mode == CURSOR_LOAD) { } else if (mode == CURSOR_LOAD) {
term.c = c[alt]; term.c = TSCREEN.sc;
tmoveto(c[alt].x, c[alt].y); tmoveto(term.c.x, term.c.y);
} }
} }
void void
treset(void) treset(void)
{ {
uint i; int i, j;
Glyph g = (Glyph){ .fg = defaultfg, .bg = defaultbg};
term.c = (TCursor){{
.mode = ATTR_NULL,
.fg = defaultfg,
.bg = defaultbg
}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
memset(term.tabs, 0, term.col * sizeof(*term.tabs)); memset(term.tabs, 0, term.col * sizeof(*term.tabs));
for (i = tabspaces; i < term.col; i += tabspaces) for (i = tabspaces; i < term.col; i += tabspaces)
@@ -1028,32 +1073,85 @@ treset(void)
term.charset = 0; term.charset = 0;
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
tmoveto(0, 0); term.screen[i].sc = (TCursor){{
tcursor(CURSOR_SAVE); .fg = defaultfg,
tclearregion(0, 0, term.col-1, term.row-1); .bg = defaultbg
tswapscreen(); }};
term.screen[i].cur = 0;
term.screen[i].off = 0;
for (j = 0; j < term.row; ++j) {
if (term.col != term.linelen)
term.screen[i].buffer[j] = xrealloc(term.screen[i].buffer[j], term.col * sizeof(Glyph));
clearline(term.screen[i].buffer[j], g, 0, term.col);
} }
for (j = term.row; j < term.screen[i].size; ++j) {
free(term.screen[i].buffer[j]);
term.screen[i].buffer[j] = NULL;
}
}
tcursor(CURSOR_LOAD);
term.linelen = term.col;
tfulldirt();
} }
void void
tnew(int col, int row) tnew(int col, int row)
{ {
term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; int i;
term = (Term){};
term.screen[0].buffer = xmalloc(HISTSIZE * sizeof(Line));
term.screen[0].size = HISTSIZE;
term.screen[1].buffer = NULL;
for (i = 0; i < HISTSIZE; ++i) term.screen[0].buffer[i] = NULL;
tresize(col, row); tresize(col, row);
treset(); treset();
} }
int tisaltscr(void)
{
return IS_SET(MODE_ALTSCREEN);
}
void void
tswapscreen(void) tswapscreen(void)
{ {
Line *tmp = term.line;
term.line = term.alt;
term.alt = tmp;
term.mode ^= MODE_ALTSCREEN; term.mode ^= MODE_ALTSCREEN;
tfulldirt(); tfulldirt();
} }
void
kscrollup(const Arg *a)
{
int n = a->i;
if (IS_SET(MODE_ALTSCREEN))
return;
if (n < 0) n = (-n) * term.row;
if (n > TSCREEN.size - term.row - TSCREEN.off) n = TSCREEN.size - term.row - TSCREEN.off;
while (!TLINE(-n)) --n;
TSCREEN.off += n;
selscroll(0, n);
tfulldirt();
}
void
kscrolldown(const Arg *a)
{
int n = a->i;
if (IS_SET(MODE_ALTSCREEN))
return;
if (n < 0) n = (-n) * term.row;
if (n > TSCREEN.off) n = TSCREEN.off;
TSCREEN.off -= n;
selscroll(0, -n);
tfulldirt();
}
void void
tscrolldown(int orig, int n) tscrolldown(int orig, int n)
{ {
@@ -1062,15 +1160,29 @@ tscrolldown(int orig, int n)
LIMIT(n, 0, term.bot-orig+1); LIMIT(n, 0, term.bot-orig+1);
tsetdirt(orig, term.bot-n); /* Ensure that lines are allocated */
tclearregion(0, term.bot-n+1, term.col-1, term.bot); for (i = -n; i < 0; i++) {
TLINE(i) = ensureline(TLINE(i));
for (i = term.bot; i >= orig+n; i--) {
temp = term.line[i];
term.line[i] = term.line[i-n];
term.line[i-n] = temp;
} }
/* Shift non-scrolling areas in ring buffer */
for (i = term.bot+1; i < term.row; i++) {
temp = TLINE(i);
TLINE(i) = TLINE(i-n);
TLINE(i-n) = temp;
}
for (i = 0; i < orig; i++) {
temp = TLINE(i);
TLINE(i) = TLINE(i-n);
TLINE(i-n) = temp;
}
/* Scroll buffer */
TSCREEN.cur = (TSCREEN.cur + TSCREEN.size - n) % TSCREEN.size;
/* Clear lines that have entered the view */
tclearregion(0, orig, term.linelen-1, orig+n-1);
/* Redraw portion of the screen that has scrolled */
tsetdirt(orig+n-1, term.bot);
selscroll(orig, n); selscroll(orig, n);
} }
@@ -1082,15 +1194,29 @@ tscrollup(int orig, int n)
LIMIT(n, 0, term.bot-orig+1); LIMIT(n, 0, term.bot-orig+1);
tclearregion(0, orig, term.col-1, orig+n-1); /* Ensure that lines are allocated */
tsetdirt(orig+n, term.bot); for (i = term.row; i < term.row + n; i++) {
TLINE(i) = ensureline(TLINE(i));
for (i = orig; i <= term.bot-n; i++) {
temp = term.line[i];
term.line[i] = term.line[i+n];
term.line[i+n] = temp;
} }
/* Shift non-scrolling areas in ring buffer */
for (i = orig-1; i >= 0; i--) {
temp = TLINE(i);
TLINE(i) = TLINE(i+n);
TLINE(i+n) = temp;
}
for (i = term.row-1; i >term.bot; i--) {
temp = TLINE(i);
TLINE(i) = TLINE(i+n);
TLINE(i+n) = temp;
}
/* Scroll buffer */
TSCREEN.cur = (TSCREEN.cur + n) % TSCREEN.size;
/* Clear lines that have entered the view */
tclearregion(0, term.bot-n+1, term.linelen-1, term.bot);
/* Redraw portion of the screen that has scrolled */
tsetdirt(orig, term.bot-n+1);
selscroll(orig, -n); selscroll(orig, -n);
} }
@@ -1197,6 +1323,7 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
"", "", "", "", "", "", "", "", /* p - w */ "", "", "", "", "", "", "", "", /* p - w */
"", "", "", "π", "", "£", "·", /* x - ~ */ "", "", "", "π", "", "£", "·", /* x - ~ */
}; };
Line line = TLINE(y);
/* /*
* The table is proudly stolen from rxvt. * The table is proudly stolen from rxvt.
@@ -1205,25 +1332,25 @@ tsetchar(Rune u, const Glyph *attr, int x, int y)
BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ); utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
if (term.line[y][x].mode & ATTR_WIDE) { if (line[x].mode & ATTR_WIDE) {
if (x+1 < term.col) { if (x+1 < term.col) {
term.line[y][x+1].u = ' '; line[x+1].u = ' ';
term.line[y][x+1].mode &= ~ATTR_WDUMMY; line[x+1].mode &= ~ATTR_WDUMMY;
} }
} else if (term.line[y][x].mode & ATTR_WDUMMY) { } else if (line[x].mode & ATTR_WDUMMY) {
term.line[y][x-1].u = ' '; line[x-1].u = ' ';
term.line[y][x-1].mode &= ~ATTR_WIDE; line[x-1].mode &= ~ATTR_WIDE;
} }
term.dirty[y] = 1; term.dirty[y] = 1;
term.line[y][x] = *attr; line[x] = *attr;
term.line[y][x].u = u; line[x].u = u;
} }
void void
tclearregion(int x1, int y1, int x2, int y2) tclearregion(int x1, int y1, int x2, int y2)
{ {
int x, y, temp; int x, y, L, S, temp;
Glyph *gp; Glyph *gp;
if (x1 > x2) if (x1 > x2)
@@ -1231,15 +1358,16 @@ tclearregion(int x1, int y1, int x2, int y2)
if (y1 > y2) if (y1 > y2)
temp = y1, y1 = y2, y2 = temp; temp = y1, y1 = y2, y2 = temp;
LIMIT(x1, 0, term.col-1); LIMIT(x1, 0, term.linelen-1);
LIMIT(x2, 0, term.col-1); LIMIT(x2, 0, term.linelen-1);
LIMIT(y1, 0, term.row-1); LIMIT(y1, 0, term.row-1);
LIMIT(y2, 0, term.row-1); LIMIT(y2, 0, term.row-1);
L = TLINEOFFSET(y1);
for (y = y1; y <= y2; y++) { for (y = y1; y <= y2; y++) {
term.dirty[y] = 1; term.dirty[y] = 1;
for (x = x1; x <= x2; x++) { for (x = x1; x <= x2; x++) {
gp = &term.line[y][x]; gp = &TSCREEN.buffer[L][x];
if (selected(x, y)) if (selected(x, y))
selclear(); selclear();
gp->fg = term.c.attr.fg; gp->fg = term.c.attr.fg;
@@ -1247,6 +1375,7 @@ tclearregion(int x1, int y1, int x2, int y2)
gp->mode = 0; gp->mode = 0;
gp->u = ' '; gp->u = ' ';
} }
L = (L + 1) % TSCREEN.size;
} }
} }
@@ -1261,7 +1390,7 @@ tdeletechar(int n)
dst = term.c.x; dst = term.c.x;
src = term.c.x + n; src = term.c.x + n;
size = term.col - src; size = term.col - src;
line = term.line[term.c.y]; line = TLINE(term.c.y);
memmove(&line[dst], &line[src], size * sizeof(Glyph)); memmove(&line[dst], &line[src], size * sizeof(Glyph));
tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
@@ -1278,7 +1407,7 @@ tinsertblank(int n)
dst = term.c.x + n; dst = term.c.x + n;
src = term.c.x; src = term.c.x;
size = term.col - dst; size = term.col - dst;
line = term.line[term.c.y]; line = TLINE(term.c.y);
memmove(&line[dst], &line[src], size * sizeof(Glyph)); memmove(&line[dst], &line[src], size * sizeof(Glyph));
tclearregion(src, term.c.y, dst - 1, term.c.y); tclearregion(src, term.c.y, dst - 1, term.c.y);
@@ -1789,6 +1918,16 @@ csihandle(void)
term.c.y+1, term.c.x+1); term.c.y+1, term.c.x+1);
ttywrite(buf, len, 0); ttywrite(buf, len, 0);
break; break;
case 7:
if (strstr(strescseq.args[1], "file://") != strescseq.args[1]) {
fprintf(stderr, "erresc: dir %s must have prefix 'file://'\n",
strescseq.args[1]);
return;
}
if (chdir(strescseq.args[1] + 7) != 0) /* +7 to remove prefix */
fprintf(stderr, "erresc: invalid directory %s\n",
strescseq.args[1]);
return;
default: default:
goto unknown; goto unknown;
} }
@@ -1986,6 +2125,12 @@ strhandle(void)
xsettitle(strescseq.args[0]); xsettitle(strescseq.args[0]);
return; return;
case 'P': /* DCS -- Device Control String */ case 'P': /* DCS -- Device Control String */
/* https://gitlab.com/gnachman/iterm2/-/wikis/synchronized-updates-spec */
if (strstr(strescseq.buf, "=1s") == strescseq.buf)
tsync_begin(); /* BSU */
else if (strstr(strescseq.buf, "=2s") == strescseq.buf)
tsync_end(); /* ESU */
return;
case '_': /* APC -- Application Program Command */ case '_': /* APC -- Application Program Command */
case '^': /* PM -- Privacy Message */ case '^': /* PM -- Privacy Message */
return; return;
@@ -2105,7 +2250,7 @@ tdumpline(int n)
char buf[UTF_SIZ]; char buf[UTF_SIZ];
const Glyph *bp, *end; const Glyph *bp, *end;
bp = &term.line[n][0]; bp = &TLINE(n)[0];
end = &bp[MIN(tlinelen(n), term.col) - 1]; end = &bp[MIN(tlinelen(n), term.col) - 1];
if (bp != end || bp->u != ' ') { if (bp != end || bp->u != ' ') {
for ( ; bp <= end; ++bp) for ( ; bp <= end; ++bp)
@@ -2493,11 +2638,11 @@ check_control_code:
if (selected(term.c.x, term.c.y)) if (selected(term.c.x, term.c.y))
selclear(); selclear();
gp = &term.line[term.c.y][term.c.x]; gp = &TLINE(term.c.y)[term.c.x];
if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) { if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
gp->mode |= ATTR_WRAP; gp->mode |= ATTR_WRAP;
tnewline(1); tnewline(1);
gp = &term.line[term.c.y][term.c.x]; gp = &TLINE(term.c.y)[term.c.x];
} }
if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) { if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
@@ -2510,7 +2655,7 @@ check_control_code:
tnewline(1); tnewline(1);
else else
tmoveto(term.col - width, term.c.y); tmoveto(term.col - width, term.c.y);
gp = &term.line[term.c.y][term.c.x]; gp = &TLINE(term.c.y)[term.c.x];
} }
tsetchar(u, &term.c.attr, term.c.x, term.c.y); tsetchar(u, &term.c.attr, term.c.x, term.c.y);
@@ -2541,6 +2686,14 @@ twrite(const char *buf, int buflen, int show_ctrl)
Rune u; Rune u;
int n; int n;
if (TSCREEN.off) {
TSCREEN.off = 0;
tfulldirt();
}
int su0 = su;
twrite_aborted = 0;
for (n = 0; n < buflen; n += charsize) { for (n = 0; n < buflen; n += charsize) {
if (IS_SET(MODE_UTF8)) { if (IS_SET(MODE_UTF8)) {
/* process a complete utf8 char */ /* process a complete utf8 char */
@@ -2551,6 +2704,10 @@ twrite(const char *buf, int buflen, int show_ctrl)
u = buf[n] & 0xFF; u = buf[n] & 0xFF;
charsize = 1; charsize = 1;
} }
if (su0 && !su) {
twrite_aborted = 1;
break; // ESU - allow rendering before a new BSU
}
if (show_ctrl && ISCONTROL(u)) { if (show_ctrl && ISCONTROL(u)) {
if (u & 0x80) { if (u & 0x80) {
u &= 0x7f; u &= 0x7f;
@@ -2567,56 +2724,85 @@ twrite(const char *buf, int buflen, int show_ctrl)
} }
void void
tresize(int col, int row) clearline(Line line, Glyph g, int x, int xend)
{ {
int i; int i;
g.mode = 0;
g.u = ' ';
for (i = x; i < xend; ++i) {
line[i] = g;
}
}
Line
ensureline(Line line)
{
if (!line) {
line = xmalloc(term.linelen * sizeof(Glyph));
}
return line;
}
void
tresize(int col, int row)
{
int i, j;
int minrow = MIN(row, term.row); int minrow = MIN(row, term.row);
int mincol = MIN(col, term.col); int mincol = MIN(col, term.col);
int linelen = MAX(col, term.linelen);
int *bp; int *bp;
TCursor c;
if (col < 1 || row < 1) { if (col < 1 || row < 1 || row > HISTSIZE) {
fprintf(stderr, fprintf(stderr,
"tresize: error resizing to %dx%d\n", col, row); "tresize: error resizing to %dx%d\n", col, row);
return; return;
} }
/* /* Shift buffer to keep the cursor where we expect it */
* slide screen to keep cursor where we expect it - if (row <= term.c.y) {
* tscrollup would work here, but we can optimize to term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size;
* memmove because we're freeing the earlier lines
*/
for (i = 0; i <= term.c.y - row; i++) {
free(term.line[i]);
free(term.alt[i]);
} }
/* ensure that both src and dst are not NULL */
if (i > 0) { /* Resize and clear line buffers as needed */
memmove(term.line, term.line + i, row * sizeof(Line)); if (linelen > term.linelen) {
memmove(term.alt, term.alt + i, row * sizeof(Line)); for (i = 0; i < term.screen[0].size; ++i) {
if (term.screen[0].buffer[i]) {
term.screen[0].buffer[i] = xrealloc(term.screen[0].buffer[i], linelen * sizeof(Glyph));
clearline(term.screen[0].buffer[i], term.c.attr, term.linelen, linelen);
} }
for (i += row; i < term.row; i++) { }
free(term.line[i]); for (i = 0; i < minrow; ++i) {
free(term.alt[i]); term.screen[1].buffer[i] = xrealloc(term.screen[1].buffer[i], linelen * sizeof(Glyph));
clearline(term.screen[1].buffer[i], term.c.attr, term.linelen, linelen);
}
}
/* Allocate all visible lines for regular line buffer */
for (j = term.screen[0].cur, i = 0; i < row; ++i, j = (j + 1) % term.screen[0].size)
{
if (!term.screen[0].buffer[j]) {
term.screen[0].buffer[j] = xmalloc(linelen * sizeof(Glyph));
}
if (i >= term.row) {
clearline(term.screen[0].buffer[j], term.c.attr, 0, linelen);
}
}
/* Resize alt screen */
term.screen[1].cur = 0;
term.screen[1].size = row;
for (i = row; i < term.row; ++i) {
free(term.screen[1].buffer[i]);
}
term.screen[1].buffer = xrealloc(term.screen[1].buffer, row * sizeof(Line));
for (i = term.row; i < row; ++i) {
term.screen[1].buffer[i] = xmalloc(linelen * sizeof(Glyph));
clearline(term.screen[1].buffer[i], term.c.attr, 0, linelen);
} }
/* resize to new height */ /* resize to new height */
term.line = xrealloc(term.line, row * sizeof(Line));
term.alt = xrealloc(term.alt, row * sizeof(Line));
term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
/* resize each row to new width, zero-pad if needed */ /* fix tabstops */
for (i = 0; i < minrow; i++) {
term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
term.alt[i] = xrealloc(term.alt[i], col * sizeof(Glyph));
}
/* allocate any new rows */
for (/* i = minrow */; i < row; i++) {
term.line[i] = xmalloc(col * sizeof(Glyph));
term.alt[i] = xmalloc(col * sizeof(Glyph));
}
if (col > term.col) { if (col > term.col) {
bp = term.tabs + term.col; bp = term.tabs + term.col;
@@ -2626,26 +2812,16 @@ tresize(int col, int row)
for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
*bp = 1; *bp = 1;
} }
/* update terminal size */ /* update terminal size */
term.col = col; term.col = col;
term.row = row; term.row = row;
term.linelen = linelen;
/* reset scrolling region */ /* reset scrolling region */
tsetscroll(0, row-1); tsetscroll(0, row-1);
/* make use of the LIMIT in tmoveto */ /* make use of the LIMIT in tmoveto */
tmoveto(term.c.x, term.c.y); tmoveto(term.c.x, term.c.y);
/* Clearing both screens (it makes dirty all lines) */ tfulldirt();
c = term.c;
for (i = 0; i < 2; i++) {
if (mincol < col && 0 < minrow) {
tclearregion(mincol, 0, col - 1, minrow - 1);
}
if (0 < col && minrow < row) {
tclearregion(0, minrow, col - 1, row - 1);
}
tswapscreen();
tcursor(CURSOR_LOAD);
}
term.c = c;
} }
void void
@@ -2657,14 +2833,15 @@ resettitle(void)
void void
drawregion(int x1, int y1, int x2, int y2) drawregion(int x1, int y1, int x2, int y2)
{ {
int y; int y, L;
L = TLINEOFFSET(y1);
for (y = y1; y < y2; y++) { for (y = y1; y < y2; y++) {
if (!term.dirty[y]) if (term.dirty[y]) {
continue;
term.dirty[y] = 0; term.dirty[y] = 0;
xdrawline(term.line[y], x1, y, x2); xdrawline(TSCREEN.buffer[L], x1, y, x2);
}
L = (L + 1) % TSCREEN.size;
} }
} }
@@ -2679,14 +2856,16 @@ draw(void)
/* adjust cursor position */ /* adjust cursor position */
LIMIT(term.ocx, 0, term.col-1); LIMIT(term.ocx, 0, term.col-1);
LIMIT(term.ocy, 0, term.row-1); LIMIT(term.ocy, 0, term.row-1);
if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY) if (TLINE(term.ocy)[term.ocx].mode & ATTR_WDUMMY)
term.ocx--; term.ocx--;
if (term.line[term.c.y][cx].mode & ATTR_WDUMMY) if (TLINE(term.c.y)[cx].mode & ATTR_WDUMMY)
cx--; cx--;
drawregion(0, 0, term.col, term.row); drawregion(0, 0, term.col, term.row);
xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], if (TSCREEN.off == 0)
term.ocx, term.ocy, term.line[term.ocy][term.ocx]); xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
term.ocx, term.ocy, TLINE(term.ocy)[term.ocx],
TLINE(term.ocy), term.col);
term.ocx = cx; term.ocx = cx;
term.ocy = term.c.y; term.ocy = term.c.y;
xfinishdraw(); xfinishdraw();

5
st.h
View File

@@ -11,7 +11,8 @@
#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) #define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
#define DEFAULT(a, b) (a) = (a) ? (a) : (b) #define DEFAULT(a, b) (a) = (a) ? (a) : (b)
#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || \ #define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
(a).fg != (b).fg || \
(a).bg != (b).bg) (a).bg != (b).bg)
#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \ #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + \
(t1.tv_nsec-t2.tv_nsec)/1E6) (t1.tv_nsec-t2.tv_nsec)/1E6)
@@ -19,6 +20,7 @@
#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) #define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
#define IS_TRUECOL(x) (1 << 24 & (x)) #define IS_TRUECOL(x) (1 << 24 & (x))
#define HISTSIZE 2000
enum glyph_attribute { enum glyph_attribute {
ATTR_NULL = 0, ATTR_NULL = 0,
@@ -87,6 +89,7 @@ void sendbreak(const Arg *);
void toggleprinter(const Arg *); void toggleprinter(const Arg *);
int tattrset(int); int tattrset(int);
int tisaltscr(void);
void tnew(int, int); void tnew(int, int);
void tresize(int, int); void tresize(int, int);
void tsetdirtattr(int); void tsetdirtattr(int);

View File

@@ -195,6 +195,7 @@ st-mono| simpleterm monocolor,
Ms=\E]52;%p1%s;%p2%s\007, Ms=\E]52;%p1%s;%p2%s\007,
Se=\E[2 q, Se=\E[2 q,
Ss=\E[%p1%d q, Ss=\E[%p1%d q,
Sync=\EP=%p1%ds\E\\,
st| simpleterm, st| simpleterm,
use=st-mono, use=st-mono,

2
win.h
View File

@@ -25,7 +25,7 @@ enum win_mode {
void xbell(void); void xbell(void);
void xclipcopy(void); void xclipcopy(void);
void xdrawcursor(int, int, Glyph, int, int, Glyph); void xdrawcursor(int, int, Glyph, int, int, Glyph, Line, int);
void xdrawline(Line, int, int, int); void xdrawline(Line, int, int, int);
void xfinishdraw(void); void xfinishdraw(void);
void xloadcols(void); void xloadcols(void);

221
x.c
View File

@@ -5,6 +5,7 @@
#include <locale.h> #include <locale.h>
#include <signal.h> #include <signal.h>
#include <sys/select.h> #include <sys/select.h>
#include <sys/wait.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <libgen.h> #include <libgen.h>
@@ -19,6 +20,7 @@ char *argv0;
#include "arg.h" #include "arg.h"
#include "st.h" #include "st.h"
#include "win.h" #include "win.h"
#include "hb.h"
/* types used in config.h */ /* types used in config.h */
typedef struct { typedef struct {
@@ -34,6 +36,7 @@ typedef struct {
void (*func)(const Arg *); void (*func)(const Arg *);
const Arg arg; const Arg arg;
uint release; uint release;
int altscrn; /* 0: don't care, -1: not alt screen, 1: alt screen */
} MouseShortcut; } MouseShortcut;
typedef struct { typedef struct {
@@ -55,10 +58,13 @@ static void clipcopy(const Arg *);
static void clippaste(const Arg *); static void clippaste(const Arg *);
static void numlock(const Arg *); static void numlock(const Arg *);
static void selpaste(const Arg *); static void selpaste(const Arg *);
static void selopen(const Arg *);
static void zoom(const Arg *); static void zoom(const Arg *);
static void zoomabs(const Arg *); static void zoomabs(const Arg *);
static void zoomreset(const Arg *); static void zoomreset(const Arg *);
static void ttysend(const Arg *); static void ttysend(const Arg *);
void kscrollup(const Arg *);
void kscrolldown(const Arg *);
/* config.h for applying patches and the configuration. */ /* config.h for applying patches and the configuration. */
#include "config.h" #include "config.h"
@@ -105,6 +111,7 @@ typedef struct {
XSetWindowAttributes attrs; XSetWindowAttributes attrs;
int scr; int scr;
int isfixed; /* is fixed geometry? */ int isfixed; /* is fixed geometry? */
int depth; /* bit depth */
int l, t; /* left and top offset */ int l, t; /* left and top offset */
int gm; /* geometry mask */ int gm; /* geometry mask */
} XWindow; } XWindow;
@@ -141,8 +148,9 @@ typedef struct {
} DC; } DC;
static inline ushort sixd_to_16bit(int); static inline ushort sixd_to_16bit(int);
static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
static void xdrawglyph(Glyph, int, int); static void xdrawglyph(Glyph, int, int);
static void xclear(int, int, int, int); static void xclear(int, int, int, int);
static int xgeommasktogravity(int); static int xgeommasktogravity(int);
@@ -286,6 +294,20 @@ selpaste(const Arg *dummy)
xw.win, CurrentTime); xw.win, CurrentTime);
} }
void
selopen(const Arg *dummy)
{
pid_t chpid;
if ((chpid = fork()) == 0) {
if (fork() == 0)
execlp("xdg-open", "xdg-open", getsel(), NULL);
exit(1);
}
if (chpid > 0)
waitpid(chpid, NULL, 0);
}
void void
numlock(const Arg *dummy) numlock(const Arg *dummy)
{ {
@@ -455,6 +477,7 @@ mouseaction(XEvent *e, uint release)
for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
if (ms->release == release && if (ms->release == release &&
ms->button == e->xbutton.button && ms->button == e->xbutton.button &&
(!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) &&
(match(ms->mod, state) || /* exact or forced */ (match(ms->mod, state) || /* exact or forced */
match(ms->mod, state & ~forcemousemod))) { match(ms->mod, state & ~forcemousemod))) {
ms->func(&(ms->arg)); ms->func(&(ms->arg));
@@ -752,12 +775,12 @@ xresize(int col, int row)
XFreePixmap(xw.dpy, xw.buf); XFreePixmap(xw.dpy, xw.buf);
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
DefaultDepth(xw.dpy, xw.scr)); xw.depth);
XftDrawChange(xw.draw, xw.buf); XftDrawChange(xw.draw, xw.buf);
xclear(0, 0, win.w, win.h); xclear(0, 0, win.w, win.h);
/* resize to new width */ /* resize to new width */
xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
} }
ushort ushort
@@ -812,6 +835,10 @@ xloadcols(void)
else else
die("could not allocate color %d\n", i); die("could not allocate color %d\n", i);
} }
dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha);
dc.col[defaultbg].pixel &= 0x00FFFFFF;
dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24;
loaded = 1; loaded = 1;
} }
@@ -842,6 +869,12 @@ xsetcolorname(int x, const char *name)
XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
dc.col[x] = ncolor; dc.col[x] = ncolor;
if (x == defaultbg) {
dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha);
dc.col[defaultbg].pixel &= 0x00FFFFFF;
dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24;
}
return 0; return 0;
} }
@@ -1062,6 +1095,9 @@ xunloadfont(Font *f)
void void
xunloadfonts(void) xunloadfonts(void)
{ {
/* Clear Harfbuzz font cache. */
hbunloadfonts();
/* Free the loaded fonts in the font cache. */ /* Free the loaded fonts in the font cache. */
while (frclen > 0) while (frclen > 0)
XftFontClose(xw.dpy, frc[--frclen].font); XftFontClose(xw.dpy, frc[--frclen].font);
@@ -1134,11 +1170,25 @@ xinit(int cols, int rows)
Window parent, root; Window parent, root;
pid_t thispid = getpid(); pid_t thispid = getpid();
XColor xmousefg, xmousebg; XColor xmousefg, xmousebg;
XWindowAttributes attr;
XVisualInfo vis;
if (!(xw.dpy = XOpenDisplay(NULL))) if (!(xw.dpy = XOpenDisplay(NULL)))
die("can't open display\n"); die("can't open display\n");
xw.scr = XDefaultScreen(xw.dpy); xw.scr = XDefaultScreen(xw.dpy);
xw.vis = XDefaultVisual(xw.dpy, xw.scr);
root = XRootWindow(xw.dpy, xw.scr);
if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
parent = root;
if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) {
xw.vis = vis.visual;
xw.depth = vis.depth;
} else {
XGetWindowAttributes(xw.dpy, parent, &attr);
xw.vis = attr.visual;
xw.depth = attr.depth;
}
/* font */ /* font */
if (!FcInit()) if (!FcInit())
@@ -1148,7 +1198,7 @@ xinit(int cols, int rows)
xloadfonts(usedfont, 0); xloadfonts(usedfont, 0);
/* colors */ /* colors */
xw.cmap = XDefaultColormap(xw.dpy, xw.scr); xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None);
xloadcols(); xloadcols();
/* adjust fixed window geometry */ /* adjust fixed window geometry */
@@ -1168,11 +1218,8 @@ xinit(int cols, int rows)
| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
xw.attrs.colormap = xw.cmap; xw.attrs.colormap = xw.cmap;
root = XRootWindow(xw.dpy, xw.scr); xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) win.w, win.h, 0, xw.depth, InputOutput,
parent = root;
xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t,
win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
| CWEventMask | CWColormap, &xw.attrs); | CWEventMask | CWColormap, &xw.attrs);
if (parent != root) if (parent != root)
@@ -1183,12 +1230,12 @@ xinit(int cols, int rows)
dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures,
&gcvalues); &gcvalues);
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
DefaultDepth(xw.dpy, xw.scr)); xw.depth);
XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
/* font spec buffer */ /* font spec buffer */
xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
/* Xft rendering context */ /* Xft rendering context */
xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
@@ -1242,63 +1289,73 @@ xinit(int cols, int rows)
xsel.xtarget = XA_STRING; xsel.xtarget = XA_STRING;
} }
void
xresetfontsettings(ushort mode, Font **font, int *frcflags)
{
*font = &dc.font;
if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
*font = &dc.ibfont;
*frcflags = FRC_ITALICBOLD;
} else if (mode & ATTR_ITALIC) {
*font = &dc.ifont;
*frcflags = FRC_ITALIC;
} else if (mode & ATTR_BOLD) {
*font = &dc.bfont;
*frcflags = FRC_BOLD;
}
}
int int
xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
{ {
float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
ushort mode, prevmode = USHRT_MAX; ushort mode = glyphs[0].mode & ~ATTR_WRAP;
Font *font = &dc.font; Font *font = &dc.font;
int frcflags = FRC_NORMAL; int frcflags = FRC_NORMAL;
float runewidth = win.cw; float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
Rune rune; Rune rune;
FT_UInt glyphidx; FT_UInt glyphidx;
FcResult fcres; FcResult fcres;
FcPattern *fcpattern, *fontpattern; FcPattern *fcpattern, *fontpattern;
FcFontSet *fcsets[] = { NULL }; FcFontSet *fcsets[] = { NULL };
FcCharSet *fccharset; FcCharSet *fccharset;
int i, f, numspecs = 0; int f, code_idx, numspecs = 0;
float cluster_xp = xp, cluster_yp = yp;
HbTransformData shaped = { 0 };
for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { /* Initial values. */
/* Fetch rune and mode for current glyph. */ xresetfontsettings(mode, &font, &frcflags);
rune = glyphs[i].u;
mode = glyphs[i].mode;
/* Skip dummy wide-character spacing. */ /* Shape the segment. */
if (mode == ATTR_WDUMMY) hbtransform(&shaped, font->match, glyphs, 0, len);
xp = winx; yp = winy + font->ascent;
cluster_xp = xp; cluster_yp = yp;
for (code_idx = 0; code_idx < shaped.count; code_idx++) {
int idx = shaped.glyphs[code_idx].cluster;
if (glyphs[idx].mode & ATTR_WDUMMY)
continue; continue;
/* Determine font for glyph if different from previous glyph. */ /* Advance the drawing cursor if we've moved to a new cluster */
if (prevmode != mode) { if (code_idx > 0 && (idx != shaped.glyphs[code_idx - 1].cluster)) {
prevmode = mode; xp += runewidth * (idx - shaped.glyphs[code_idx - 1].cluster);
font = &dc.font; cluster_xp = xp;
frcflags = FRC_NORMAL; cluster_yp = yp;
runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
font = &dc.ibfont;
frcflags = FRC_ITALICBOLD;
} else if (mode & ATTR_ITALIC) {
font = &dc.ifont;
frcflags = FRC_ITALIC;
} else if (mode & ATTR_BOLD) {
font = &dc.bfont;
frcflags = FRC_BOLD;
}
yp = winy + font->ascent;
} }
/* Lookup character index with default font. */ if (shaped.glyphs[code_idx].codepoint != 0) {
glyphidx = XftCharIndex(xw.dpy, font->match, rune); /* If symbol is found, put it into the specs. */
if (glyphidx) {
specs[numspecs].font = font->match; specs[numspecs].font = font->match;
specs[numspecs].glyph = glyphidx; specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
specs[numspecs].x = (short)xp; specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
specs[numspecs].y = (short)yp; specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
xp += runewidth; cluster_xp += shaped.positions[code_idx].x_advance / 64.;
cluster_yp += shaped.positions[code_idx].y_advance / 64.;
numspecs++; numspecs++;
continue; } else {
} /* If it's not found, try to fetch it through the font cache. */
rune = glyphs[idx].u;
/* Fallback on font cache, search the font cache for match. */
for (f = 0; f < frclen; f++) { for (f = 0; f < frclen; f++) {
glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
/* Everything correct. */ /* Everything correct. */
@@ -1367,17 +1424,18 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
specs[numspecs].glyph = glyphidx; specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp; specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp; specs[numspecs].y = (short)yp;
xp += runewidth;
numspecs++; numspecs++;
} }
}
/* Cleanup and get ready for next segment. */
hbcleanup(&shaped);
return numspecs; return numspecs;
} }
void void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
{ {
int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
width = charlen * win.cw; width = charlen * win.cw;
Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
@@ -1415,10 +1473,6 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
bg = &dc.col[base.bg]; bg = &dc.col[base.bg];
} }
/* Change basic system colors [0-7] to bright system colors [8-15] */
if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
fg = &dc.col[base.fg + 8];
if (IS_SET(MODE_REVERSE)) { if (IS_SET(MODE_REVERSE)) {
if (fg == &dc.col[defaultfg]) { if (fg == &dc.col[defaultfg]) {
fg = &dc.col[defaultbg]; fg = &dc.col[defaultbg];
@@ -1513,21 +1567,24 @@ void
xdrawglyph(Glyph g, int x, int y) xdrawglyph(Glyph g, int x, int y)
{ {
int numspecs; int numspecs;
XftGlyphFontSpec spec; XftGlyphFontSpec *specs = xw.specbuf;
numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
xdrawglyphfontspecs(&spec, g, numspecs, x, y); xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
} }
void void
xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
{ {
Color drawcol; Color drawcol;
/* remove the old cursor */ /* remove the old cursor */
if (selected(ox, oy)) if (selected(ox, oy))
og.mode ^= ATTR_REVERSE; og.mode ^= ATTR_REVERSE;
xdrawglyph(og, ox, oy);
/* Redraw the line where cursor was previously.
* It will restore the ligatures broken by the cursor. */
xdrawline(line, 0, oy, len);
if (IS_SET(MODE_HIDE)) if (IS_SET(MODE_HIDE))
return; return;
@@ -1661,18 +1718,16 @@ xdrawline(Line line, int x1, int y1, int x2)
Glyph base, new; Glyph base, new;
XftGlyphFontSpec *specs = xw.specbuf; XftGlyphFontSpec *specs = xw.specbuf;
numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
i = ox = 0; i = ox = 0;
for (x = x1; x < x2 && i < numspecs; x++) { for (x = x1; x < x2; x++) {
new = line[x]; new = line[x];
if (new.mode == ATTR_WDUMMY) if (new.mode == ATTR_WDUMMY)
continue; continue;
if (selected(x, y1)) if (selected(x, y1))
new.mode ^= ATTR_REVERSE; new.mode ^= ATTR_REVERSE;
if (i > 0 && ATTRCMP(base, new)) { if ((i > 0) && ATTRCMP(base, new)) {
xdrawglyphfontspecs(specs, base, i, ox, y1); numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
specs += i; xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
numspecs -= i;
i = 0; i = 0;
} }
if (i == 0) { if (i == 0) {
@@ -1681,8 +1736,10 @@ xdrawline(Line line, int x1, int y1, int x2)
} }
i++; i++;
} }
if (i > 0) if (i > 0) {
xdrawglyphfontspecs(specs, base, i, ox, y1); numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
}
} }
void void
@@ -1920,6 +1977,9 @@ resize(XEvent *e)
cresize(e->xconfigure.width, e->xconfigure.height); cresize(e->xconfigure.width, e->xconfigure.height);
} }
int tinsync(uint);
int ttyread_pending();
void void
run(void) run(void)
{ {
@@ -1954,7 +2014,7 @@ run(void)
FD_SET(ttyfd, &rfd); FD_SET(ttyfd, &rfd);
FD_SET(xfd, &rfd); FD_SET(xfd, &rfd);
if (XPending(xw.dpy)) if (XPending(xw.dpy) || ttyread_pending())
timeout = 0; /* existing events might not set xfd */ timeout = 0; /* existing events might not set xfd */
seltv.tv_sec = timeout / 1E3; seltv.tv_sec = timeout / 1E3;
@@ -1968,7 +2028,8 @@ run(void)
} }
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
if (FD_ISSET(ttyfd, &rfd)) int ttyin = FD_ISSET(ttyfd, &rfd) || ttyread_pending();
if (ttyin)
ttyread(); ttyread();
xev = 0; xev = 0;
@@ -1992,7 +2053,7 @@ run(void)
* maximum latency intervals during `cat huge.txt`, and perfect * maximum latency intervals during `cat huge.txt`, and perfect
* sync with periodic updates from animations/key-repeats/etc. * sync with periodic updates from animations/key-repeats/etc.
*/ */
if (FD_ISSET(ttyfd, &rfd) || xev) { if (ttyin || xev) {
if (!drawing) { if (!drawing) {
trigger = now; trigger = now;
drawing = 1; drawing = 1;
@@ -2003,6 +2064,18 @@ run(void)
continue; /* we have time, try to find idle */ continue; /* we have time, try to find idle */
} }
if (tinsync(su_timeout)) {
/*
* on synchronized-update draw-suspension: don't reset
* drawing so that we draw ASAP once we can (just after
* ESU). it won't be too soon because we already can
* draw now but we skip. we set timeout > 0 to draw on
* SU-timeout even without new content.
*/
timeout = minlatency;
continue;
}
/* idle detected or maxlatency exhausted -> draw */ /* idle detected or maxlatency exhausted -> draw */
timeout = -1; timeout = -1;
if (blinktimeout && tattrset(ATTR_BLINK)) { if (blinktimeout && tattrset(ATTR_BLINK)) {
@@ -2047,6 +2120,10 @@ main(int argc, char *argv[])
case 'a': case 'a':
allowaltscreen = 0; allowaltscreen = 0;
break; break;
case 'A':
alpha = strtof(EARGF(usage()), NULL);
LIMIT(alpha, 0.0, 1.0);
break;
case 'c': case 'c':
opt_class = EARGF(usage()); opt_class = EARGF(usage());
break; break;