Compare commits

...

18 Commits
0.9 ... master

Author SHA1 Message Date
Hiltjo Posthuma
6d80aeb884
support colons in SGR character attributes
Patch by Mikhail Kot <to@myrrc.dev>
With some modifications to behave more like xterm (see note below).

Example:

	printf '\033[48;2;255:0:0mtest\n'

https://invisible-island.net/xterm/ctlseqs/ctlseqs.html

Some notes:

"CSI Pm m  Character Attributes (SGR).
[...]
o   xterm allows either colons (standard) or semicolons
(legacy) to separate the subparameters (but after the
first colon, colons must be used).
2024-11-22 20:12:12 +01:00
7a4ee3b56b
Adding visual selector 2024-10-24 23:45:34 +02:00
3454623c49
Adding ligature patch
https://st.suckless.org/patches/ligatures/
-> st-ligatures-alpha-scrollback-0.9
2023-12-31 14:12:55 +01:00
aleks
94cd9d4927
Create a desktop-entry for st
Enables to find st in a graphical menu and to display it with a nice
icon.

If some applications still are not displaying an icon for st try the patch
[netwmicon](../netwmicon/). Programs like tint2 and alttab rely on a hardcoded
icon which has to be stored by st in the \_NET\_WM\_ICON window-property.
2023-10-18 15:10:21 +02:00
Benji Encalada Mora
628bd144f8
fix: replace xfps and actionfps variables 2023-06-18 21:30:41 +02:00
Santtu Lakkala
77ce1529ac
Loop through urls on screen and copy to clipboard
Replace url detection heuristics with a DFA, enabling urls that span
multiple lines. Also fix the selection not to use snapping so that urls
are selected exactly.
2023-04-13 00:15:36 +02:00
Debucquoy
5bfe4b3592
dracula theme + bg and fg modification 2023-04-03 17:53:38 +02:00
asparagii
5ad60325b9
st-scrollback-mouse
Signed-off-by: Debucquoy <debucqquoy.anthony@gmail.com>
2023-04-03 16:43:26 +02:00
Debucquoy
62b2a7eb60
ScrollBack patch
This apply shift + pageUp/pageDown to scroll up or down
2023-04-03 16:38:41 +02:00
Kipras Melnikovas
785b3a4666
refactor dynamic-cursor-color patch
Signed-off-by: Kipras Melnikovas <kipras@kipras.org>
Signed-off-by: Debucquoy <debucqquoy.anthony@gmail.com>
2023-04-03 15:42:16 +02:00
Bakkeby
74e2cea30d
Adding anysize patch
Signed-off-by: Debucquoy <debucqquoy.anthony@gmail.com>
2023-04-03 14:55:40 +02:00
Debucquoy
010407757e
Adding config.h to gitignore 2023-04-03 14:50:29 +02:00
Debucquoy
840e3a234c
Adding alpha
Also delete config.h because it should only be modified when all patches
are applied.
2023-04-03 14:42:12 +02:00
Debucquoy
8e3a174ce2
first config 2023-04-03 14:27:44 +02:00
Hiltjo Posthuma
211964d56e ignore C1 control characters in UTF-8 mode
Ignore processing and printing C1 control characters in UTF-8 mode.
These are in the range: 0x80 - 0x9f.

By default in st the mode is set to UTF-8.

This matches more the behaviour of xterm with the options -u8 or +u8 also.
Also see the xterm resource "allowC1Printable".

Let me know if this breaks something, in most cases I don't think so.

As usual a very good reference is:
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
2023-02-07 20:00:59 +01:00
Adam Price
f17abd25b3 Add support for DSR response "OK" escape sequence
"VT100 defines an escape sequence [1] called Device Status Report (DSR). When
the DSR sequence received is `csi 5n`, an "OK" response `csi 0n` is returned.
This patch adds that "OK" response.

I encountered this missing sequence when I noticed that fzf [2] would clobber
my prompt whenever completing a find.

To test that ST doesn't currently respond to `csi 5n`, use fzf's shell
extension in ST's repo to complete the path for a file.

    my-fancy-prompt $ vim **<tab>
    <select a file>
    st.c

Select a file with <enter>, and notice that fzf clobbers some or all of your
prompt.

After applying this patch, do the same test as above and notice that fzf has no
longer clobbered your prompt by placing the file name in the correct position
in your command.

    my-fancy-prompt $ vim **<tab>
    <select a file>
    my-fancy prompt $ vim st.c

Thank you for considering my first patch submission.

[1] https://www.xfree86.org/current/ctlseqs.html#VT100%20Mode
[2] https://github.com/junegunn/fzf
"

Patch slightly adapted with input from the mailinglist,
2023-02-07 19:57:34 +01:00
Hiltjo Posthuma
7e8050cc62 Fixed OSC color reset without parameter->resets all colors
Adapted from (garbled) patch by wim <wim@thinkerwim.org>

Additional notes: it should reset all the colors using xloadcols().
To reproduce: set a different (theme) color using some escape code, then reset
it:

	printf '\x1b]104\x07'
2023-02-05 13:29:35 +01:00
Hiltjo Posthuma
e5e959835b fix buffer overflow when handling long composed input
To reproduce the issue:

"
If you already have the multi-key enabled on your system, then add this line
to your ~/.XCompose file:

[...]
<question> <T> <E> <S> <T> <question> :
"1234567890123456789012345678901234567890123456789012345678901234567890"
"

Reported by and an initial patch by Andy Gozas <andy@gozas.me>, thanks!

Adapted the patch, for now st (like dmenu) handles a fixed amount of composed
characters, or otherwise ignores it. This is done for simplicity sake.
2022-10-25 17:11:11 +02:00
11 changed files with 1060 additions and 253 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
config.h
*.o
st

View File

@ -4,7 +4,7 @@
include config.mk
SRC = st.c x.c
SRC = st.c x.c hb.c
OBJ = $(SRC:.c=.o)
all: options st
@ -22,7 +22,8 @@ config.h:
$(CC) $(STCFLAGS) -c $<
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
@ -49,9 +50,12 @@ install: st
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1
tic -sx st.info
@echo Please see the README file regarding the terminfo entry of st.
mkdir -p $(DESTDIR)$(APPPREFIX)
cp -f st.desktop $(DESTDIR)$(APPPREFIX)
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/st
rm -f $(DESTDIR)$(APPPREFIX)/st.desktop
rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
.PHONY: all options clean dist install uninstall

View File

@ -5,7 +5,7 @@
*
* font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html
*/
static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
static char *font = "Fira Code:pixelsize=14:antialias=true:autohint=true";
static int borderpx = 2;
/*
@ -93,29 +93,34 @@ char *termname = "st-256color";
*/
unsigned int tabspaces = 8;
/* bg opacity */
float alpha = 0.8;
/* Terminal colors (16 first used in escape sequence) */
static const char *colorname[] = {
/* 8 normal colors */
"black",
"red3",
"green3",
"yellow3",
"blue2",
"magenta3",
"cyan3",
"gray90",
/* 8 bright colors */
"gray50",
"red",
"green",
"yellow",
"#5c5cff",
"magenta",
"cyan",
"white",
[255] = 0,
/* 8 normal colors */
[0] = "#000000", /* black */
[1] = "#ff5555", /* red */
[2] = "#50fa7b", /* green */
[3] = "#f1fa8c", /* yellow */
[4] = "#bd93f9", /* blue */
[5] = "#ff79c6", /* magenta */
[6] = "#8be9fd", /* cyan */
[7] = "#bbbbbb", /* white */
/* 8 bright colors */
[8] = "#44475a", /* black */
[9] = "#ff5555", /* red */
[10] = "#50fa7b", /* green */
[11] = "#f1fa8c", /* yellow */
[12] = "#bd93f9", /* blue */
[13] = "#ff79c6", /* magenta */
[14] = "#8be9fd", /* cyan */
[15] = "#ffffff", /* white */
/* special colors */
[256] = "#222222", /* background */
[257] = "#ffffff", /* foreground */
/* more colors can be added after 255 to use with DefaultXX */
"#cccccc",
@ -127,13 +132,21 @@ static const char *colorname[] = {
/*
* Default colors (colorname index)
* foreground, background, cursor, reverse cursor
* foreground, background, cursor
*/
unsigned int defaultfg = 258;
unsigned int defaultbg = 259;
unsigned int defaultcs = 256;
unsigned int defaultfg = 257;
unsigned int defaultbg = 256;
unsigned int defaultcs = 257;
static unsigned int defaultrcs = 257;
/*
* Colors used, when the specific fg == defaultfg. So in reverse mode this
* will reverse too. Another logic would only make the simple feature too
* complex.
*/
unsigned int defaultitalic = 7;
unsigned int defaultunderline = 7;
/*
* Default shape of cursor
* 2: Block ("")
@ -170,12 +183,50 @@ static unsigned int defaultattr = 11;
*/
static uint forcemousemod = ShiftMask;
/*
* Xresources preferences to load at startup
*/
ResourcePref resources[] = {
{ "font", STRING, &font },
{ "color0", STRING, &colorname[0] },
{ "color1", STRING, &colorname[1] },
{ "color2", STRING, &colorname[2] },
{ "color3", STRING, &colorname[3] },
{ "color4", STRING, &colorname[4] },
{ "color5", STRING, &colorname[5] },
{ "color6", STRING, &colorname[6] },
{ "color7", STRING, &colorname[7] },
{ "color8", STRING, &colorname[8] },
{ "color9", STRING, &colorname[9] },
{ "color10", STRING, &colorname[10] },
{ "color11", STRING, &colorname[11] },
{ "color12", STRING, &colorname[12] },
{ "color13", STRING, &colorname[13] },
{ "color14", STRING, &colorname[14] },
{ "color15", STRING, &colorname[15] },
{ "background", STRING, &colorname[256] },
{ "foreground", STRING, &colorname[257] },
{ "cursorColor", STRING, &colorname[258] },
{ "termname", STRING, &termname },
{ "shell", STRING, &shell },
{ "minlatency", INTEGER, &minlatency },
{ "maxlatency", INTEGER, &maxlatency },
{ "blinktimeout", INTEGER, &blinktimeout },
{ "bellvolume", INTEGER, &bellvolume },
{ "tabspaces", INTEGER, &tabspaces },
{ "borderpx", INTEGER, &borderpx },
{ "cwscale", FLOAT, &cwscale },
{ "chscale", FLOAT, &chscale },
};
/*
* Internal mouse shortcuts.
* Beware that overloading Button1 will disable the selection.
*/
static MouseShortcut mshortcuts[] = {
/* mask button function argument release */
{ ShiftMask, Button4, kscrollup, {.i = 1} },
{ ShiftMask, Button5, kscrolldown, {.i = 1} },
{ XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 },
{ ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} },
{ XK_ANY_MOD, Button4, ttysend, {.s = "\031"} },
@ -201,6 +252,10 @@ static Shortcut shortcuts[] = {
{ TERMMOD, XK_Y, selpaste, {.i = 0} },
{ ShiftMask, XK_Insert, selpaste, {.i = 0} },
{ TERMMOD, XK_Num_Lock, numlock, {.i = 0} },
{ MODKEY, XK_l, copyurl, {.i = 0} },
{ ShiftMask, XK_Page_Up, kscrollup, {.i = -1} },
{ ShiftMask, XK_Page_Down, kscrolldown, {.i = -1} },
{ TERMMOD, XK_Escape, keyboard_select,{.i = 0} },
};
/*

View File

@ -5,6 +5,7 @@ VERSION = 0.9
# paths
PREFIX = /usr/local
APPPREFIX = $(PREFIX)/share/applications
MANPREFIX = $(PREFIX)/share/man
X11INC = /usr/X11R6/include
@ -15,10 +16,12 @@ PKG_CONFIG = pkg-config
# includes and libs
INCS = -I$(X11INC) \
`$(PKG_CONFIG) --cflags fontconfig` \
`$(PKG_CONFIG) --cflags freetype2`
LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft \
`$(PKG_CONFIG) --cflags freetype2` \
`$(PKG_CONFIG) --cflags harfbuzz`
LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXrender \
`$(PKG_CONFIG) --libs fontconfig` \
`$(PKG_CONFIG) --libs freetype2`
`$(PKG_CONFIG) --libs freetype2` \
`$(PKG_CONFIG) --libs harfbuzz`
# flags
STCPPFLAGS = -DVERSION=\"$(VERSION)\" -D_XOPEN_SOURCE=600

124
hb.c Normal file
View File

@ -0,0 +1,124 @@
#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[] = { };
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);
/* 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));
}

14
hb.h Normal file
View File

@ -0,0 +1,14 @@
#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 *);

468
st.c
View File

@ -16,6 +16,8 @@
#include <termios.h>
#include <unistd.h>
#include <wchar.h>
#include <X11/keysym.h>
#include <X11/X.h>
#include "st.h"
#include "win.h"
@ -35,6 +37,7 @@
#define ESC_ARG_SIZ 16
#define STR_BUF_SIZ ESC_BUF_SIZ
#define STR_ARG_SIZ ESC_ARG_SIZ
#define HISTSIZE 2000
/* macros */
#define IS_SET(flag) ((term.mode & (flag)) != 0)
@ -42,6 +45,9 @@
#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f))
#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c))
#define ISDELIM(u) (u && wcschr(worddelimiters, u))
#define TLINE(y) ((y) < term.scr ? term.hist[((y) + term.histi - \
term.scr + HISTSIZE + 1) % HISTSIZE] : \
term.line[(y) - term.scr])
enum term_mode {
MODE_WRAP = 1 << 0,
@ -115,6 +121,9 @@ typedef struct {
int col; /* nb col */
Line *line; /* screen */
Line *alt; /* alternate screen */
Line hist[HISTSIZE]; /* history buffer */
int histi; /* history index */
int scr; /* scroll back */
int *dirty; /* dirtyness of lines */
TCursor c; /* cursor */
int ocx; /* old cursor col */
@ -152,6 +161,11 @@ typedef struct {
int narg; /* nb of args */
} STREscape;
typedef struct {
int state;
size_t length;
} URLdfa;
static void execsh(char *, char **);
static void stty(char **);
static void sigchld(int);
@ -185,8 +199,8 @@ static void tnewline(int);
static void tputtab(int);
static void tputc(Rune);
static void treset(void);
static void tscrollup(int, int);
static void tscrolldown(int, int);
static void tscrollup(int, int, int);
static void tscrolldown(int, int, int);
static void tsetattr(const int *, int);
static void tsetchar(Rune, const Glyph *, int, int);
static void tsetdirt(int, int);
@ -201,6 +215,7 @@ static void tdefutf8(char);
static int32_t tdefcolor(const int *, int *, int);
static void tdeftran(char);
static void tstrsequence(uchar);
static int daddch(URLdfa *, char);
static void drawregion(int, int, int, int);
@ -409,10 +424,10 @@ tlinelen(int y)
{
int i = term.col;
if (term.line[y][i - 1].mode & ATTR_WRAP)
if (TLINE(y)[i - 1].mode & ATTR_WRAP)
return i;
while (i > 0 && term.line[y][i - 1].u == ' ')
while (i > 0 && TLINE(y)[i - 1].u == ' ')
--i;
return i;
@ -521,7 +536,7 @@ selsnap(int *x, int *y, int direction)
* Snap around if the word wraps around at the end or
* beginning of a line.
*/
prevgp = &term.line[*y][*x];
prevgp = &TLINE(*y)[*x];
prevdelim = ISDELIM(prevgp->u);
for (;;) {
newx = *x + direction;
@ -536,14 +551,14 @@ selsnap(int *x, int *y, int direction)
yt = *y, xt = *x;
else
yt = newy, xt = newx;
if (!(term.line[yt][xt].mode & ATTR_WRAP))
if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
break;
}
if (newx >= tlinelen(newy))
break;
gp = &term.line[newy][newx];
gp = &TLINE(newy)[newx];
delim = ISDELIM(gp->u);
if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
|| (delim && gp->u != prevgp->u)))
@ -564,14 +579,14 @@ selsnap(int *x, int *y, int direction)
*x = (direction < 0) ? 0 : term.col - 1;
if (direction < 0) {
for (; *y > 0; *y += direction) {
if (!(term.line[*y-1][term.col-1].mode
if (!(TLINE(*y-1)[term.col-1].mode
& ATTR_WRAP)) {
break;
}
}
} else if (direction > 0) {
for (; *y < term.row-1; *y += direction) {
if (!(term.line[*y][term.col-1].mode
if (!(TLINE(*y)[term.col-1].mode
& ATTR_WRAP)) {
break;
}
@ -602,13 +617,13 @@ getsel(void)
}
if (sel.type == SEL_RECTANGULAR) {
gp = &term.line[y][sel.nb.x];
gp = &TLINE(y)[sel.nb.x];
lastx = sel.ne.x;
} 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;
}
last = &term.line[y][MIN(lastx, linelen-1)];
last = &TLINE(y)[MIN(lastx, linelen-1)];
while (last >= gp && last->u == ' ')
--last;
@ -844,6 +859,9 @@ void
ttywrite(const char *s, size_t n, int may_echo)
{
const char *next;
Arg arg = (Arg) { .i = term.scr };
kscrolldown(&arg);
if (may_echo && IS_SET(MODE_ECHO))
twrite(s, n, 1);
@ -1055,12 +1073,52 @@ tswapscreen(void)
}
void
tscrolldown(int orig, int n)
kscrolldown(const Arg* a)
{
int n = a->i;
if (n < 0)
n = term.row + n;
if (n > term.scr)
n = term.scr;
if (term.scr > 0) {
term.scr -= n;
selscroll(0, -n);
tfulldirt();
}
}
void
kscrollup(const Arg* a)
{
int n = a->i;
if (n < 0)
n = term.row + n;
if (term.scr <= HISTSIZE-n) {
term.scr += n;
selscroll(0, n);
tfulldirt();
}
}
void
tscrolldown(int orig, int n, int copyhist)
{
int i;
Line temp;
LIMIT(n, 0, term.bot-orig+1);
if (copyhist) {
term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
temp = term.hist[term.histi];
term.hist[term.histi] = term.line[term.bot];
term.line[term.bot] = temp;
}
tsetdirt(orig, term.bot-n);
tclearregion(0, term.bot-n+1, term.col-1, term.bot);
@ -1071,17 +1129,28 @@ tscrolldown(int orig, int n)
term.line[i-n] = temp;
}
selscroll(orig, n);
if (term.scr == 0)
selscroll(orig, n);
}
void
tscrollup(int orig, int n)
tscrollup(int orig, int n, int copyhist)
{
int i;
Line temp;
LIMIT(n, 0, term.bot-orig+1);
if (copyhist) {
term.histi = (term.histi + 1) % HISTSIZE;
temp = term.hist[term.histi];
term.hist[term.histi] = term.line[orig];
term.line[orig] = temp;
}
if (term.scr > 0 && term.scr < HISTSIZE)
term.scr = MIN(term.scr + n, HISTSIZE-1);
tclearregion(0, orig, term.col-1, orig+n-1);
tsetdirt(orig+n, term.bot);
@ -1091,7 +1160,8 @@ tscrollup(int orig, int n)
term.line[i+n] = temp;
}
selscroll(orig, -n);
if (term.scr == 0)
selscroll(orig, -n);
}
void
@ -1120,7 +1190,7 @@ tnewline(int first_col)
int y = term.c.y;
if (y == term.bot) {
tscrollup(term.top, 1);
tscrollup(term.top, 1, 1);
} else {
y++;
}
@ -1132,6 +1202,7 @@ csiparse(void)
{
char *p = csiescseq.buf, *np;
long int v;
int sep = ';'; /* colon or semi-colon, but not both */
csiescseq.narg = 0;
if (*p == '?') {
@ -1149,7 +1220,9 @@ csiparse(void)
v = -1;
csiescseq.arg[csiescseq.narg++] = v;
p = np;
if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
if (sep == ';' && *p == ':')
sep = ':'; /* allow override to colon once */
if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
break;
p++;
}
@ -1285,14 +1358,14 @@ void
tinsertblankline(int n)
{
if (BETWEEN(term.c.y, term.top, term.bot))
tscrolldown(term.c.y, n);
tscrolldown(term.c.y, n, 0);
}
void
tdeleteline(int n)
{
if (BETWEEN(term.c.y, term.top, term.bot))
tscrollup(term.c.y, n);
tscrollup(term.c.y, n, 0);
}
int32_t
@ -1729,11 +1802,11 @@ csihandle(void)
break;
case 'S': /* SU -- Scroll <n> line up */
DEFAULT(csiescseq.arg[0], 1);
tscrollup(term.top, csiescseq.arg[0]);
tscrollup(term.top, csiescseq.arg[0], 0);
break;
case 'T': /* SD -- Scroll <n> line down */
DEFAULT(csiescseq.arg[0], 1);
tscrolldown(term.top, csiescseq.arg[0]);
tscrolldown(term.top, csiescseq.arg[0], 0);
break;
case 'L': /* IL -- Insert <n> blank lines */
DEFAULT(csiescseq.arg[0], 1);
@ -1769,11 +1842,18 @@ csihandle(void)
case 'm': /* SGR -- Terminal attribute (color) */
tsetattr(csiescseq.arg, csiescseq.narg);
break;
case 'n': /* DSR Device Status Report (cursor position) */
if (csiescseq.arg[0] == 6) {
case 'n': /* DSR -- Device Status Report */
switch (csiescseq.arg[0]) {
case 5: /* Status Report "OK" `0n` */
ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
break;
case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
term.c.y+1, term.c.x+1);
term.c.y+1, term.c.x+1);
ttywrite(buf, len, 0);
break;
default:
goto unknown;
}
break;
case 'r': /* DECSTBM -- Set Scrolling Region */
@ -1932,8 +2012,10 @@ strhandle(void)
if (p && !strcmp(p, "?")) {
osc_color_response(j, 0, 1);
} else if (xsetcolorname(j, p)) {
if (par == 104 && narg <= 1)
if (par == 104 && narg <= 1) {
xloadcols();
return; /* color reset without parameter */
}
fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
j, p ? p : "(null)");
} else {
@ -2296,7 +2378,7 @@ eschandle(uchar ascii)
return 0;
case 'D': /* IND -- Linefeed */
if (term.c.y == term.bot) {
tscrollup(term.top, 1);
tscrollup(term.top, 1, 1);
} else {
tmoveto(term.c.x, term.c.y+1);
}
@ -2309,7 +2391,7 @@ eschandle(uchar ascii)
break;
case 'M': /* RI -- Reverse index */
if (term.c.y == term.top) {
tscrolldown(term.top, 1);
tscrolldown(term.top, 1, 1);
} else {
tmoveto(term.c.x, term.c.y-1);
}
@ -2413,6 +2495,9 @@ check_control_code:
* they must not cause conflicts with sequences.
*/
if (control) {
/* in UTF-8 mode ignore handling C1 control characters */
if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
return;
tcontrolcode(u);
/*
* control codes are not shown ever
@ -2523,12 +2608,15 @@ twrite(const char *buf, int buflen, int show_ctrl)
void
tresize(int col, int row)
{
int i;
int i, j;
int minrow = MIN(row, term.row);
int mincol = MIN(col, term.col);
int *bp;
TCursor c;
if ( row < term.row || col < term.col )
toggle_winmode(trt_kbdselect(XK_Escape, NULL, 0));
if (col < 1 || row < 1) {
fprintf(stderr,
"tresize: error resizing to %dx%d\n", col, row);
@ -2560,6 +2648,14 @@ tresize(int col, int row)
term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
for (i = 0; i < HISTSIZE; i++) {
term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
for (j = mincol; j < col; j++) {
term.hist[i][j] = term.c.attr;
term.hist[i][j].u = ' ';
}
}
/* resize each row to new width, zero-pad if needed */
for (i = 0; i < minrow; i++) {
term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
@ -2618,7 +2714,7 @@ drawregion(int x1, int y1, int x2, int y2)
continue;
term.dirty[y] = 0;
xdrawline(term.line[y], x1, y, x2);
xdrawline(TLINE(y), x1, y, x2);
}
}
@ -2639,8 +2735,11 @@ draw(void)
cx--;
drawregion(0, 0, term.col, term.row);
xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
if (term.scr == 0)
xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
term.ocx, term.ocy, term.line[term.ocy][term.ocx],
term.line[term.ocy], term.col);
term.ocx = cx;
term.ocy = term.c.y;
xfinishdraw();
@ -2654,3 +2753,308 @@ redraw(void)
tfulldirt();
draw();
}
int
daddch(URLdfa *dfa, char c)
{
/* () and [] can appear in urls, but excluding them here will reduce false
* positives when figuring out where a given url ends.
*/
static const char URLCHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-._~:/?#@!$&'*+,;=%";
static const char RPFX[] = "//:sptth";
if (!strchr(URLCHARS, c)) {
dfa->length = 0;
dfa->state = 0;
return 0;
}
dfa->length++;
if (dfa->state == 2 && c == '/') {
dfa->state = 0;
} else if (dfa->state == 3 && c == 'p') {
dfa->state++;
} else if (c != RPFX[dfa->state]) {
dfa->state = 0;
return 0;
}
if (dfa->state++ == 7) {
dfa->state = 0;
return 1;
}
return 0;
}
/*
** Select and copy the previous url on screen (do nothing if there's no url).
*/
void
copyurl(const Arg *arg) {
int row = 0, /* row of current URL */
col = 0, /* column of current URL start */
colend = 0, /* column of last occurrence */
passes = 0; /* how many rows have been scanned */
const char *c = NULL,
*match = NULL;
URLdfa dfa = { 0 };
row = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.y : term.bot;
LIMIT(row, term.top, term.bot);
colend = (sel.ob.x >= 0 && sel.nb.y > 0) ? sel.nb.x : term.col;
LIMIT(colend, 0, term.col);
/*
** Scan from (term.row - 1,term.col - 1) to (0,0) and find
** next occurrance of a URL
*/
for (passes = 0; passes < term.row; passes++) {
/* Read in each column of every row until
** we hit previous occurrence of URL
*/
for (col = colend; col--;)
if (daddch(&dfa, term.line[row][col].u < 128 ? term.line[row][col].u : ' '))
break;
if (col >= 0)
break;
if (--row < 0)
row = term.row - 1;
colend = term.col;
}
if (passes < term.row) {
selstart(col, row, 0);
selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 0);
selextend((col + dfa.length - 1) % term.col, row + (col + dfa.length - 1) / term.col, SEL_REGULAR, 1);
xsetsel(getsel());
xclipcopy();
}
}
void set_notifmode(int type, KeySym ksym) {
static char *lib[] = { " MOVE ", " SEL "};
static Glyph *g, *deb, *fin;
static int col, bot;
if ( ksym == -1 ) {
free(g);
col = term.col, bot = term.bot;
g = xmalloc(col * sizeof(Glyph));
memcpy(g, term.line[bot], col * sizeof(Glyph));
}
else if ( ksym == -2 )
memcpy(term.line[bot], g, col * sizeof(Glyph));
if ( type < 2 ) {
char *z = lib[type];
for (deb = &term.line[bot][col - 6], fin = &term.line[bot][col]; deb < fin; z++, deb++)
deb->mode = ATTR_REVERSE,
deb->u = *z,
deb->fg = defaultfg, deb->bg = defaultbg;
}
else if ( type < 5 )
memcpy(term.line[bot], g, col * sizeof(Glyph));
else {
for (deb = &term.line[bot][0], fin = &term.line[bot][col]; deb < fin; deb++)
deb->mode = ATTR_REVERSE,
deb->u = ' ',
deb->fg = defaultfg, deb->bg = defaultbg;
term.line[bot][0].u = ksym;
}
term.dirty[bot] = 1;
drawregion(0, bot, col, bot + 1);
}
void select_or_drawcursor(int selectsearch_mode, int type) {
int done = 0;
if ( selectsearch_mode & 1 ) {
selextend(term.c.x, term.c.y, type, done);
xsetsel(getsel());
}
else
xdrawcursor(term.c.x, term.c.y, term.line[term.c.y][term.c.x],
term.ocx, term.ocy, term.line[term.ocy][term.ocx],
term.line[term.ocy], term.col);
}
void search(int selectsearch_mode, Rune *target, int ptarget, int incr, int type, TCursor *cu) {
Rune *r;
int i, bound = (term.col * cu->y + cu->x) * (incr > 0) + incr;
for (i = term.col * term.c.y + term.c.x + incr; i != bound; i += incr) {
for (r = target; r - target < ptarget; r++) {
if ( *r == term.line[(i + r - target) / term.col][(i + r - target) % term.col].u ) {
if ( r - target == ptarget - 1 ) break;
} else {
r = NULL;
break;
}
}
if ( r != NULL ) break;
}
if ( i != bound ) {
term.c.y = i / term.col, term.c.x = i % term.col;
select_or_drawcursor(selectsearch_mode, type);
}
}
int trt_kbdselect(KeySym ksym, char *buf, int len) {
static TCursor cu;
static Rune target[64];
static int type = 1, ptarget, in_use;
static int sens, quant;
static char selectsearch_mode;
int i, bound, *xy;
if ( selectsearch_mode & 2 ) {
if ( ksym == XK_Return ) {
selectsearch_mode ^= 2;
set_notifmode(selectsearch_mode, -2);
if ( ksym == XK_Escape ) ptarget = 0;
return 0;
}
else if ( ksym == XK_BackSpace ) {
if ( !ptarget ) return 0;
term.line[term.bot][ptarget--].u = ' ';
}
else if ( len < 1 ) {
return 0;
}
else if ( ptarget == term.col || ksym == XK_Escape ) {
return 0;
}
else {
utf8decode(buf, &target[ptarget++], len);
term.line[term.bot][ptarget].u = target[ptarget - 1];
}
if ( ksym != XK_BackSpace )
search(selectsearch_mode, &target[0], ptarget, sens, type, &cu);
term.dirty[term.bot] = 1;
drawregion(0, term.bot, term.col, term.bot + 1);
return 0;
}
switch ( ksym ) {
case -1 :
in_use = 1;
cu.x = term.c.x, cu.y = term.c.y;
set_notifmode(0, ksym);
return MODE_KBDSELECT;
case XK_s :
if ( selectsearch_mode & 1 )
selclear();
else
selstart(term.c.x, term.c.y, 0);
set_notifmode(selectsearch_mode ^= 1, ksym);
break;
case XK_t :
selextend(term.c.x, term.c.y, type ^= 3, i = 0); /* 2 fois */
selextend(term.c.x, term.c.y, type, i = 0);
break;
case XK_slash :
case XK_KP_Divide :
case XK_question :
ksym &= XK_question; /* Divide to slash */
sens = (ksym == XK_slash) ? -1 : 1;
ptarget = 0;
set_notifmode(15, ksym);
selectsearch_mode ^= 2;
break;
case XK_Escape :
if ( !in_use ) break;
selclear();
case XK_Return :
set_notifmode(4, ksym);
term.c.x = cu.x, term.c.y = cu.y;
select_or_drawcursor(selectsearch_mode = 0, type);
in_use = quant = 0;
return MODE_KBDSELECT;
case XK_n :
case XK_N :
if ( ptarget )
search(selectsearch_mode, &target[0], ptarget, (ksym == XK_n) ? -1 : 1, type, &cu);
break;
case XK_BackSpace :
term.c.x = 0;
select_or_drawcursor(selectsearch_mode, type);
break;
case XK_dollar :
term.c.x = term.col - 1;
select_or_drawcursor(selectsearch_mode, type);
break;
case XK_Home :
term.c.x = 0, term.c.y = 0;
select_or_drawcursor(selectsearch_mode, type);
break;
case XK_End :
term.c.x = cu.x, term.c.y = cu.y;
select_or_drawcursor(selectsearch_mode, type);
break;
case XK_Page_Up :
case XK_Page_Down :
term.c.y = (ksym == XK_Prior ) ? 0 : cu.y;
select_or_drawcursor(selectsearch_mode, type);
break;
case XK_exclam :
term.c.x = term.col >> 1;
select_or_drawcursor(selectsearch_mode, type);
break;
case XK_asterisk :
case XK_KP_Multiply :
term.c.x = term.col >> 1;
case XK_underscore :
term.c.y = cu.y >> 1;
select_or_drawcursor(selectsearch_mode, type);
break;
default :
if ( ksym >= XK_0 && ksym <= XK_9 ) { /* 0-9 keyboard */
quant = (quant * 10) + (ksym ^ XK_0);
return 0;
}
else if ( ksym >= XK_KP_0 && ksym <= XK_KP_9 ) { /* 0-9 numpad */
quant = (quant * 10) + (ksym ^ XK_KP_0);
return 0;
}
else if ( ksym == XK_k || ksym == XK_h )
i = ksym & 1;
else if ( ksym == XK_l || ksym == XK_j )
i = ((ksym & 6) | 4) >> 1;
else if ( (XK_Home & ksym) != XK_Home || (i = (ksym ^ XK_Home) - 1) > 3 )
break;
xy = (i & 1) ? &term.c.y : &term.c.x;
sens = (i & 2) ? 1 : -1;
bound = (i >> 1 ^ 1) ? 0 : (i ^ 3) ? term.col - 1 : term.bot;
if ( quant == 0 )
quant++;
if ( *xy == bound && ((sens < 0 && bound == 0) || (sens > 0 && bound > 0)) )
break;
*xy += quant * sens;
if ( *xy < 0 || ( bound > 0 && *xy > bound) )
*xy = bound;
select_or_drawcursor(selectsearch_mode, type);
}
quant = 0;
return 0;
}

12
st.desktop Normal file
View File

@ -0,0 +1,12 @@
[Desktop Entry]
Type=Application
Exec=st
TryExec=st
Icon=utilities-terminal
Terminal=false
Categories=System;TerminalEmulator;
Name=st
GenericName=Terminal
Comment=st is a simple terminal implementation for X
StartupWMClass=st-256color

70
st.h
View File

@ -11,45 +11,46 @@
#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d))
#define DEFAULT(a, b) (a) = (a) ? (a) : (b)
#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 || \
(a).bg != (b).bg)
#define ATTRCMP(a, b) (((a).mode & (~ATTR_WRAP)) != ((b).mode & (~ATTR_WRAP)) || \
(a).fg != (b).fg || \
(a).bg != (b).bg)
#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)
#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b))
#define IS_TRUECOL(x) (1 << 24 & (x))
enum glyph_attribute {
ATTR_NULL = 0,
ATTR_BOLD = 1 << 0,
ATTR_FAINT = 1 << 1,
ATTR_ITALIC = 1 << 2,
ATTR_UNDERLINE = 1 << 3,
ATTR_BLINK = 1 << 4,
ATTR_REVERSE = 1 << 5,
ATTR_INVISIBLE = 1 << 6,
ATTR_STRUCK = 1 << 7,
ATTR_WRAP = 1 << 8,
ATTR_WIDE = 1 << 9,
ATTR_WDUMMY = 1 << 10,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
ATTR_NULL = 0,
ATTR_BOLD = 1 << 0,
ATTR_FAINT = 1 << 1,
ATTR_ITALIC = 1 << 2,
ATTR_UNDERLINE = 1 << 3,
ATTR_BLINK = 1 << 4,
ATTR_REVERSE = 1 << 5,
ATTR_INVISIBLE = 1 << 6,
ATTR_STRUCK = 1 << 7,
ATTR_WRAP = 1 << 8,
ATTR_WIDE = 1 << 9,
ATTR_WDUMMY = 1 << 10,
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
};
enum selection_mode {
SEL_IDLE = 0,
SEL_EMPTY = 1,
SEL_READY = 2
SEL_IDLE = 0,
SEL_EMPTY = 1,
SEL_READY = 2
};
enum selection_type {
SEL_REGULAR = 1,
SEL_RECTANGULAR = 2
SEL_REGULAR = 1,
SEL_RECTANGULAR = 2
};
enum selection_snap {
SNAP_WORD = 1,
SNAP_LINE = 2
SNAP_WORD = 1,
SNAP_LINE = 2
};
typedef unsigned char uchar;
@ -61,30 +62,33 @@ typedef uint_least32_t Rune;
#define Glyph Glyph_
typedef struct {
Rune u; /* character code */
ushort mode; /* attribute flags */
uint32_t fg; /* foreground */
uint32_t bg; /* background */
Rune u; /* character code */
ushort mode; /* attribute flags */
uint32_t fg; /* foreground */
uint32_t bg; /* background */
} Glyph;
typedef Glyph *Line;
typedef union {
int i;
uint ui;
float f;
const void *v;
const char *s;
int i;
uint ui;
float f;
const void *v;
const char *s;
} Arg;
void die(const char *, ...);
void redraw(void);
void draw(void);
void kscrolldown(const Arg *);
void kscrollup(const Arg *);
void printscreen(const Arg *);
void printsel(const Arg *);
void sendbreak(const Arg *);
void toggleprinter(const Arg *);
void copyurl(const Arg *);
int tattrset(int);
void tnew(int, int);
@ -110,6 +114,7 @@ size_t utf8encode(Rune, char *);
void *xmalloc(size_t);
void *xrealloc(void *, size_t);
char *xstrdup(const char *);
int trt_kbdselect(KeySym, char *, int);
/* config.h globals */
extern char *utmp;
@ -124,3 +129,4 @@ extern unsigned int tabspaces;
extern unsigned int defaultfg;
extern unsigned int defaultbg;
extern unsigned int defaultcs;
extern float alpha;

5
win.h
View File

@ -21,11 +21,12 @@ enum win_mode {
MODE_NUMLOCK = 1 << 17,
MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\
|MODE_MOUSEMANY,
MODE_KBDSELECT = 1 << 18,
};
void xbell(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 xfinishdraw(void);
void xloadcols(void);
@ -38,4 +39,6 @@ void xsetmode(int, unsigned int);
void xsetpointermotion(int);
void xsetsel(char *);
int xstartdraw(void);
void toggle_winmode(int);
void keyboard_select(const Arg *);
void xximspot(int, int);

493
x.c
View File

@ -14,11 +14,13 @@
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>
#include <X11/XKBlib.h>
#include <X11/Xresource.h>
char *argv0;
#include "arg.h"
#include "st.h"
#include "win.h"
#include "hb.h"
/* types used in config.h */
typedef struct {
@ -45,6 +47,19 @@ typedef struct {
signed char appcursor; /* application cursor */
} Key;
/* Xresources preferences */
enum resource_type {
STRING = 0,
INTEGER = 1,
FLOAT = 2
};
typedef struct {
char *name;
enum resource_type type;
void *dst;
} ResourcePref;
/* X modifiers */
#define XK_ANY_MOD UINT_MAX
#define XK_NO_MOD 0
@ -81,6 +96,7 @@ typedef XftGlyphFontSpec GlyphFontSpec;
typedef struct {
int tw, th; /* tty width and height */
int w, h; /* window width and height */
int hborderpx, vborderpx;
int ch; /* char height */
int cw; /* char width */
int mode; /* window state/mode flags */
@ -105,6 +121,7 @@ typedef struct {
XSetWindowAttributes attrs;
int scr;
int isfixed; /* is fixed geometry? */
int depth; /* bit depth */
int l, t; /* left and top offset */
int gm; /* geometry mask */
} XWindow;
@ -141,6 +158,7 @@ typedef struct {
} DC;
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 void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
static void xdrawglyph(Glyph, int, int);
@ -243,6 +261,7 @@ static char *usedfont = NULL;
static double usedfontsize = 0;
static double defaultfontsize = 0;
static char *opt_alpha = NULL;
static char *opt_class = NULL;
static char **opt_cmd = NULL;
static char *opt_embed = NULL;
@ -331,7 +350,7 @@ ttysend(const Arg *arg)
int
evcol(XEvent *e)
{
int x = e->xbutton.x - borderpx;
int x = e->xbutton.x - win.hborderpx;
LIMIT(x, 0, win.tw - 1);
return x / win.cw;
}
@ -339,7 +358,7 @@ evcol(XEvent *e)
int
evrow(XEvent *e)
{
int y = e->xbutton.y - borderpx;
int y = e->xbutton.y - win.vborderpx;
LIMIT(y, 0, win.th - 1);
return y / win.ch;
}
@ -739,6 +758,9 @@ cresize(int width, int height)
col = MAX(1, col);
row = MAX(1, row);
win.hborderpx = (win.w - col * win.cw) / 2;
win.vborderpx = (win.h - row * win.ch) / 2;
tresize(col, row);
xresize(col, row);
ttyresize(win.tw, win.th);
@ -752,12 +774,12 @@ xresize(int col, int row)
XFreePixmap(xw.dpy, xw.buf);
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
DefaultDepth(xw.dpy, xw.scr));
xw.depth);
XftDrawChange(xw.draw, xw.buf);
xclear(0, 0, win.w, win.h);
/* resize to new width */
xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
}
ushort
@ -812,6 +834,13 @@ xloadcols(void)
else
die("could not allocate color %d\n", i);
}
/* set alpha value of bg color */
if (opt_alpha)
alpha = strtof(opt_alpha, NULL);
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;
}
@ -859,8 +888,8 @@ xclear(int x1, int y1, int x2, int y2)
void
xhints(void)
{
XClassHint class = {opt_name ? opt_name : termname,
opt_class ? opt_class : termname};
XClassHint class = {opt_name ? opt_name : "st",
opt_class ? opt_class : "St"};
XWMHints wm = {.flags = InputHint, .input = 1};
XSizeHints *sizeh;
@ -869,8 +898,8 @@ xhints(void)
sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
sizeh->height = win.h;
sizeh->width = win.w;
sizeh->height_inc = win.ch;
sizeh->width_inc = win.cw;
sizeh->height_inc = 1;
sizeh->width_inc = 1;
sizeh->base_height = 2 * borderpx;
sizeh->base_width = 2 * borderpx;
sizeh->min_height = win.ch + 2 * borderpx;
@ -1062,6 +1091,9 @@ xunloadfont(Font *f)
void
xunloadfonts(void)
{
/* Clear Harfbuzz font cache. */
hbunloadfonts();
/* Free the loaded fonts in the font cache. */
while (frclen > 0)
XftFontClose(xw.dpy, frc[--frclen].font);
@ -1134,11 +1166,21 @@ xinit(int cols, int rows)
Window parent;
pid_t thispid = getpid();
XColor xmousefg, xmousebg;
XWindowAttributes attr;
XVisualInfo vis;
if (!(xw.dpy = XOpenDisplay(NULL)))
die("can't open display\n");
xw.scr = XDefaultScreen(xw.dpy);
xw.vis = XDefaultVisual(xw.dpy, xw.scr);
if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) {
parent = XRootWindow(xw.dpy, xw.scr);
xw.depth = 32;
} else {
XGetWindowAttributes(xw.dpy, parent, &attr);
xw.depth = attr.depth;
}
XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis);
xw.vis = vis.visual;
/* font */
if (!FcInit())
@ -1148,12 +1190,12 @@ xinit(int cols, int rows)
xloadfonts(usedfont, 0);
/* colors */
xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None);
xloadcols();
/* adjust fixed window geometry */
win.w = 2 * borderpx + cols * win.cw;
win.h = 2 * borderpx + rows * win.ch;
win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw;
win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch;
if (xw.gm & XNegative)
xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
if (xw.gm & YNegative)
@ -1168,24 +1210,20 @@ xinit(int cols, int rows)
| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
xw.attrs.colormap = xw.cmap;
if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
parent = XRootWindow(xw.dpy, xw.scr);
xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
win.w, win.h, 0, xw.depth, InputOutput,
xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
| CWEventMask | CWColormap, &xw.attrs);
memset(&gcvalues, 0, sizeof(gcvalues));
gcvalues.graphics_exposures = False;
dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
&gcvalues);
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
DefaultDepth(xw.dpy, xw.scr));
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth);
dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues);
XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);
XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);
/* font spec buffer */
xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
/* Xft rendering context */
xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
@ -1239,10 +1277,26 @@ xinit(int cols, int rows)
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
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 = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp;
ushort mode, prevmode = USHRT_MAX;
Font *font = &dc.font;
int frcflags = FRC_NORMAL;
@ -1253,119 +1307,148 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x
FcPattern *fcpattern, *fontpattern;
FcFontSet *fcsets[] = { NULL };
FcCharSet *fccharset;
int i, f, numspecs = 0;
int i, f, length = 0, start = 0, numspecs = 0;
float cluster_xp = xp, cluster_yp = yp;
HbTransformData shaped = { 0 };
/* Initial values. */
mode = prevmode = glyphs[0].mode;
xresetfontsettings(mode, &font, &frcflags);
for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
/* Fetch rune and mode for current glyph. */
rune = glyphs[i].u;
mode = glyphs[i].mode;
/* Skip dummy wide-character spacing. */
if (mode == ATTR_WDUMMY)
if (mode & ATTR_WDUMMY && i < (len - 1))
continue;
/* Determine font for glyph if different from previous glyph. */
if (prevmode != mode) {
prevmode = mode;
font = &dc.font;
frcflags = FRC_NORMAL;
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. */
glyphidx = XftCharIndex(xw.dpy, font->match, rune);
if (glyphidx) {
specs[numspecs].font = font->match;
specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp;
xp += runewidth;
numspecs++;
continue;
}
/* Fallback on font cache, search the font cache for match. */
for (f = 0; f < frclen; f++) {
glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
/* Everything correct. */
if (glyphidx && frc[f].flags == frcflags)
break;
/* We got a default font for a not found glyph. */
if (!glyphidx && frc[f].flags == frcflags
&& frc[f].unicodep == rune) {
break;
}
}
/* Nothing was found. Use fontconfig to find matching font. */
if (f >= frclen) {
if (!font->set)
font->set = FcFontSort(0, font->pattern,
1, 0, &fcres);
fcsets[0] = font->set;
/*
* Nothing was found in the cache. Now use
* some dozen of Fontconfig calls to get the
* font for one single character.
*
* Xft and fontconfig are design failures.
*/
fcpattern = FcPatternDuplicate(font->pattern);
fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, rune);
FcPatternAddCharSet(fcpattern, FC_CHARSET,
fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
FcConfigSubstitute(0, fcpattern,
FcMatchPattern);
FcDefaultSubstitute(fcpattern);
fontpattern = FcFontSetMatch(0, fcsets, 1,
fcpattern, &fcres);
/* Allocate memory for the new cache entry. */
if (frclen >= frccap) {
frccap += 16;
frc = xrealloc(frc, frccap * sizeof(Fontcache));
if (
prevmode != mode
|| ATTRCMP(glyphs[start], glyphs[i])
|| selected(x + i, y) != selected(x + start, y)
|| i == (len - 1)
) {
/* Handle 1-character wide segments and end of line */
length = i - start;
if (i == start) {
length = 1;
} else if (i == (len - 1)) {
length = (i - start + 1);
}
frc[frclen].font = XftFontOpenPattern(xw.dpy,
fontpattern);
if (!frc[frclen].font)
die("XftFontOpenPattern failed seeking fallback font: %s\n",
strerror(errno));
frc[frclen].flags = frcflags;
frc[frclen].unicodep = rune;
/* Shape the segment. */
hbtransform(&shaped, font->match, glyphs, start, length);
runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
cluster_xp = xp; cluster_yp = yp;
for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
int idx = shaped.glyphs[code_idx].cluster;
glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
if (glyphs[start + idx].mode & ATTR_WDUMMY)
continue;
f = frclen;
frclen++;
/* Advance the drawing cursor if we've moved to a new cluster */
if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
xp += runewidth;
cluster_xp = xp;
cluster_yp = yp;
runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
}
FcPatternDestroy(fcpattern);
FcCharSetDestroy(fccharset);
if (shaped.glyphs[code_idx].codepoint != 0) {
/* If symbol is found, put it into the specs. */
specs[numspecs].font = font->match;
specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
cluster_xp += shaped.positions[code_idx].x_advance / 64.;
cluster_yp += shaped.positions[code_idx].y_advance / 64.;
numspecs++;
} else {
/* If it's not found, try to fetch it through the font cache. */
rune = glyphs[start + idx].u;
for (f = 0; f < frclen; f++) {
glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
/* Everything correct. */
if (glyphidx && frc[f].flags == frcflags)
break;
/* We got a default font for a not found glyph. */
if (!glyphidx && frc[f].flags == frcflags
&& frc[f].unicodep == rune) {
break;
}
}
/* Nothing was found. Use fontconfig to find matching font. */
if (f >= frclen) {
if (!font->set)
font->set = FcFontSort(0, font->pattern,
1, 0, &fcres);
fcsets[0] = font->set;
/*
* Nothing was found in the cache. Now use
* some dozen of Fontconfig calls to get the
* font for one single character.
*
* Xft and fontconfig are design failures.
*/
fcpattern = FcPatternDuplicate(font->pattern);
fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, rune);
FcPatternAddCharSet(fcpattern, FC_CHARSET,
fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
FcConfigSubstitute(0, fcpattern,
FcMatchPattern);
FcDefaultSubstitute(fcpattern);
fontpattern = FcFontSetMatch(0, fcsets, 1,
fcpattern, &fcres);
/* Allocate memory for the new cache entry. */
if (frclen >= frccap) {
frccap += 16;
frc = xrealloc(frc, frccap * sizeof(Fontcache));
}
frc[frclen].font = XftFontOpenPattern(xw.dpy,
fontpattern);
if (!frc[frclen].font)
die("XftFontOpenPattern failed seeking fallback font: %s\n",
strerror(errno));
frc[frclen].flags = frcflags;
frc[frclen].unicodep = rune;
glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
f = frclen;
frclen++;
FcPatternDestroy(fcpattern);
FcCharSetDestroy(fccharset);
}
specs[numspecs].font = frc[f].font;
specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp;
numspecs++;
}
}
/* Cleanup and get ready for next segment. */
hbcleanup(&shaped);
start = i;
/* Determine font for glyph if different from previous glyph. */
if (prevmode != mode) {
prevmode = mode;
xresetfontsettings(mode, &font, &frcflags);
yp = winy + font->ascent;
}
}
specs[numspecs].font = frc[f].font;
specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp;
xp += runewidth;
numspecs++;
}
return numspecs;
@ -1375,7 +1458,7 @@ void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)
{
int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,
int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch,
width = charlen * win.cw;
Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
XRenderColor colfg, colbg;
@ -1465,17 +1548,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
/* Intelligent cleaning up of the borders. */
if (x == 0) {
xclear(0, (y == 0)? 0 : winy, borderpx,
xclear(0, (y == 0)? 0 : winy, win.hborderpx,
winy + win.ch +
((winy + win.ch >= borderpx + win.th)? win.h : 0));
((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
}
if (winx + width >= borderpx + win.tw) {
if (winx + width >= win.hborderpx + win.tw) {
xclear(winx + width, (y == 0)? 0 : winy, win.w,
((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
}
if (y == 0)
xclear(winx, 0, winx + width, borderpx);
if (winy + win.ch >= borderpx + win.th)
xclear(winx, 0, winx + width, win.vborderpx);
if (winy + win.ch >= win.vborderpx + win.th)
xclear(winx, winy + win.ch, winx + width, win.h);
/* Clean up the region we want to draw to. */
@ -1517,14 +1600,18 @@ xdrawglyph(Glyph g, int x, int y)
}
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;
XRenderColor colbg;
/* remove the old cursor */
if (selected(ox, oy))
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))
return;
@ -1549,10 +1636,24 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
g.fg = defaultfg;
g.bg = defaultrcs;
} else {
/** this is the main part of the dynamic cursor color patch */
g.bg = g.fg;
g.fg = defaultbg;
g.bg = defaultcs;
}
drawcol = dc.col[g.bg];
/**
* and this is the second part of the dynamic cursor color patch.
* it handles the `drawcol` variable
*/
if (IS_TRUECOL(g.bg)) {
colbg.alpha = 0xffff;
colbg.red = TRUERED(g.bg);
colbg.green = TRUEGREEN(g.bg);
colbg.blue = TRUEBLUE(g.bg);
XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &drawcol);
} else {
drawcol = dc.col[g.bg];
}
}
/* draw the new one */
@ -1569,35 +1670,35 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
case 3: /* Blinking Underline */
case 4: /* Steady Underline */
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + (cy + 1) * win.ch - \
win.hborderpx + cx * win.cw,
win.vborderpx + (cy + 1) * win.ch - \
cursorthickness,
win.cw, cursorthickness);
break;
case 5: /* Blinking bar */
case 6: /* Steady bar */
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + cy * win.ch,
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
cursorthickness, win.ch);
break;
}
} else {
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + cy * win.ch,
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
win.cw - 1, 1);
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + cy * win.ch,
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
1, win.ch - 1);
XftDrawRect(xw.draw, &drawcol,
borderpx + (cx + 1) * win.cw - 1,
borderpx + cy * win.ch,
win.hborderpx + (cx + 1) * win.cw - 1,
win.vborderpx + cy * win.ch,
1, win.ch - 1);
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + (cy + 1) * win.ch - 1,
win.hborderpx + cx * win.cw,
win.vborderpx + (cy + 1) * win.ch - 1,
win.cw, 1);
}
}
@ -1652,18 +1753,16 @@ xdrawline(Line line, int x1, int y1, int x2)
Glyph base, new;
XftGlyphFontSpec *specs = xw.specbuf;
numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);
i = ox = 0;
for (x = x1; x < x2 && i < numspecs; x++) {
for (x = x1; x < x2; x++) {
new = line[x];
if (new.mode == ATTR_WDUMMY)
continue;
if (selected(x, y1))
new.mode ^= ATTR_REVERSE;
if (i > 0 && ATTRCMP(base, new)) {
xdrawglyphfontspecs(specs, base, i, ox, y1);
specs += i;
numspecs -= i;
if ((i > 0) && ATTRCMP(base, new)) {
numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
xdrawglyphfontspecs(specs, base, numspecs, ox, y1);
i = 0;
}
if (i == 0) {
@ -1672,8 +1771,10 @@ xdrawline(Line line, int x1, int y1, int x2)
}
i++;
}
if (i > 0)
xdrawglyphfontspecs(specs, base, i, ox, y1);
if (i > 0) {
numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
xdrawglyphfontspecs(specs, base, numspecs, ox, y1);
}
}
void
@ -1833,7 +1934,7 @@ void
kpress(XEvent *ev)
{
XKeyEvent *e = &ev->xkey;
KeySym ksym;
KeySym ksym = NoSymbol;
char buf[64], *customkey;
int len;
Rune c;
@ -1843,10 +1944,19 @@ kpress(XEvent *ev)
if (IS_SET(MODE_KBDLOCK))
return;
if (xw.ime.xic)
if (xw.ime.xic) {
len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
else
if (status == XBufferOverflow)
return;
} else {
len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
}
if ( IS_SET(MODE_KBDSELECT) ) {
if ( match(XK_NO_MOD, e->state) ||
(XK_Shift_L | XK_Shift_R) & e->state )
win.mode ^= trt_kbdselect(ksym, buf, len);
return;
}
/* 1. shortcuts */
for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
if (ksym == bp->keysym && match(bp->mod, e->state)) {
@ -2011,6 +2121,59 @@ run(void)
}
}
int
resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
{
char **sdst = dst;
int *idst = dst;
float *fdst = dst;
char fullname[256];
char fullclass[256];
char *type;
XrmValue ret;
snprintf(fullname, sizeof(fullname), "%s.%s",
opt_name ? opt_name : "st", name);
snprintf(fullclass, sizeof(fullclass), "%s.%s",
opt_class ? opt_class : "St", name);
fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
XrmGetResource(db, fullname, fullclass, &type, &ret);
if (ret.addr == NULL || strncmp("String", type, 64))
return 1;
switch (rtype) {
case STRING:
*sdst = ret.addr;
break;
case INTEGER:
*idst = strtoul(ret.addr, NULL, 10);
break;
case FLOAT:
*fdst = strtof(ret.addr, NULL);
break;
}
return 0;
}
void
config_init(void)
{
char *resm;
XrmDatabase db;
ResourcePref *p;
XrmInitialize();
resm = XResourceManagerString(xw.dpy);
if (!resm)
return;
db = XrmGetStringDatabase(resm);
for (p = resources; p < resources + LEN(resources); p++)
resource_load(db, p->name, p->type, p->dst);
}
void
usage(void)
{
@ -2024,6 +2187,14 @@ usage(void)
" [stty_args ...]\n", argv0, argv0);
}
void toggle_winmode(int flag) {
win.mode ^= flag;
}
void keyboard_select(const Arg *dummy) {
win.mode ^= trt_kbdselect(-1, NULL, 0);
}
int
main(int argc, char *argv[])
{
@ -2035,6 +2206,9 @@ main(int argc, char *argv[])
case 'a':
allowaltscreen = 0;
break;
case 'A':
opt_alpha = EARGF(usage());
break;
case 'c':
opt_class = EARGF(usage());
break;
@ -2084,6 +2258,11 @@ run:
setlocale(LC_CTYPE, "");
XSetLocaleModifiers("");
if(!(xw.dpy = XOpenDisplay(NULL)))
die("Can't open display\n");
config_init();
cols = MAX(cols, 1);
rows = MAX(rows, 1);
tnew(cols, rows);