st/x.c

3911 lines
96 KiB
C
Raw Normal View History

/* See LICENSE for license details. */
#include <errno.h>
#include <math.h>
#include <limits.h>
#include <locale.h>
#include <signal.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include <libgen.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>
#include <X11/XKBlib.h>
char *argv0;
#include "arg.h"
#include "st.h"
#include "win.h"
#if LIGATURES_PATCH
#include "hb.h"
#endif // LIGATURES_PATCH
#if THEMED_CURSOR_PATCH
#include <X11/Xcursor/Xcursor.h>
#endif // THEMED_CURSOR_PATCH
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
#if SIXEL_PATCH
#include <Imlib2.h>
#include "sixel.h"
#endif // SIXEL_PATCH
#if UNDERCURL_PATCH
/* Undercurl slope types */
enum undercurl_slope_type {
UNDERCURL_SLOPE_ASCENDING = 0,
UNDERCURL_SLOPE_TOP_CAP = 1,
UNDERCURL_SLOPE_DESCENDING = 2,
UNDERCURL_SLOPE_BOTTOM_CAP = 3
};
#endif // UNDERCURL_PATCH
/* X modifiers */
#define XK_ANY_MOD UINT_MAX
#define XK_NO_MOD 0
#define XK_SWITCH_MOD (1<<13|1<<14)
/* function definitions used in config.h */
static void clipcopy(const Arg *);
static void clippaste(const Arg *);
static void numlock(const Arg *);
static void selpaste(const Arg *);
static void ttysend(const Arg *);
static void zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
#include "patch/st_include.h"
#include "patch/x_include.h"
2019-09-16 08:40:16 +00:00
/* config.h for applying patches and the configuration. */
#include "config.h"
2021-08-18 08:07:42 +00:00
#if CSI_22_23_PATCH
/* size of title stack */
#define TITLESTACKSIZE 8
#endif // CSI_22_23_PATCH
/* XEMBED messages */
#define XEMBED_FOCUS_IN 4
#define XEMBED_FOCUS_OUT 5
/* macros */
#define IS_SET(flag) ((win.mode & (flag)) != 0)
#define TRUERED(x) (((x) & 0xff0000) >> 8)
#define TRUEGREEN(x) (((x) & 0xff00))
#define TRUEBLUE(x) (((x) & 0xff) << 8)
static inline ushort sixd_to_16bit(int);
static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
#if LIGATURES_PATCH && WIDE_GLYPHS_PATCH
2024-03-07 14:34:21 +00:00
static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int, int);
#elif LIGATURES_PATCH || WIDE_GLYPHS_PATCH
static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
2020-11-14 15:24:07 +00:00
#else
static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
#endif // WIDE_GLYPHS_PATCH | LIGATURES_PATCH
2022-12-19 09:09:06 +00:00
#if LIGATURES_PATCH
2024-03-07 14:34:21 +00:00
static inline void xresetfontsettings(uint32_t mode, Font **font, int *frcflags);
2022-12-19 09:09:06 +00:00
#endif // LIGATURES_PATCH
2024-03-13 09:33:51 +00:00
void xdrawglyph(Glyph, int, int);
static void xclear(int, int, int, int);
static int xgeommasktogravity(int);
2020-03-24 14:41:43 +00:00
static int ximopen(Display *);
static void ximinstantiate(Display *, XPointer, XPointer);
static void ximdestroy(XIM, XPointer, XPointer);
2020-03-24 14:41:43 +00:00
static int xicdestroy(XIC, XPointer, XPointer);
static void xinit(int, int);
static void cresize(int, int);
static void xresize(int, int);
static void xhints(void);
static int xloadcolor(int, const char *, Color *);
static int xloadfont(Font *, FcPattern *);
static void xloadfonts(const char *, double);
static void xunloadfont(Font *);
static void xunloadfonts(void);
static void xsetenv(void);
static void xseturgency(int);
static int evcol(XEvent *);
static int evrow(XEvent *);
static void expose(XEvent *);
static void visibility(XEvent *);
static void unmap(XEvent *);
static void kpress(XEvent *);
static void cmessage(XEvent *);
static void resize(XEvent *);
static void focus(XEvent *);
static uint buttonmask(uint);
static void brelease(XEvent *);
static void bpress(XEvent *);
static void bmotion(XEvent *);
static void propnotify(XEvent *);
static void selnotify(XEvent *);
static void selclear_(XEvent *);
static void selrequest(XEvent *);
static void setsel(char *, Time);
#if XRESOURCES_PATCH && XRESOURCES_RELOAD_PATCH || BACKGROUND_IMAGE_PATCH && BACKGROUND_IMAGE_RELOAD_PATCH
static void sigusr1_reload(int sig);
#endif // XRESOURCES_RELOAD_PATCH | BACKGROUND_IMAGE_RELOAD_PATCH
static int mouseaction(XEvent *, uint);
static void mousesel(XEvent *, int);
static void mousereport(XEvent *);
static char *kmap(KeySym, uint);
static int match(uint, uint);
static void run(void);
static void usage(void);
static void (*handler[LASTEvent])(XEvent *) = {
[KeyPress] = kpress,
[ClientMessage] = cmessage,
[ConfigureNotify] = resize,
[VisibilityNotify] = visibility,
[UnmapNotify] = unmap,
[Expose] = expose,
[FocusIn] = focus,
[FocusOut] = focus,
[MotionNotify] = bmotion,
[ButtonPress] = bpress,
[ButtonRelease] = brelease,
/*
* Uncomment if you want the selection to disappear when you select something
* different in another window.
*/
/* [SelectionClear] = selclear_, */
[SelectionNotify] = selnotify,
/*
* PropertyNotify is only turned on when there is some INCR transfer happening
* for the selection retrieval.
*/
[PropertyNotify] = propnotify,
[SelectionRequest] = selrequest,
2020-01-07 07:05:00 +00:00
#if ST_EMBEDDER_PATCH
[CreateNotify] = createnotify,
[DestroyNotify] = destroynotify,
#endif // ST_EMBEDDER_PATCH
};
/* Globals */
2021-03-10 17:09:47 +00:00
Term term;
DC dc;
XWindow xw;
XSelection xsel;
TermWindow win;
2021-08-18 08:07:42 +00:00
#if CSI_22_23_PATCH
static int tstki; /* title stack index */
static char *titlestack[TITLESTACKSIZE]; /* title stack */
#endif // CSI_22_23_PATCH
/* Font Ring Cache */
enum {
FRC_NORMAL,
FRC_ITALIC,
FRC_BOLD,
FRC_ITALICBOLD
};
typedef struct {
XftFont *font;
int flags;
Rune unicodep;
} Fontcache;
/* Fontcache is an array now. A new font will be appended to the array. */
2020-03-21 15:41:43 +00:00
static Fontcache *frc = NULL;
static int frclen = 0;
2020-03-21 15:41:43 +00:00
static int frccap = 0;
static char *usedfont = NULL;
static double usedfontsize = 0;
static double defaultfontsize = 0;
#if ALPHA_PATCH
static char *opt_alpha = NULL;
#endif // ALPHA_PATCH
static char *opt_class = NULL;
static char **opt_cmd = NULL;
static char *opt_embed = NULL;
static char *opt_font = NULL;
static char *opt_io = NULL;
static char *opt_line = NULL;
static char *opt_name = NULL;
static char *opt_title = NULL;
2020-03-29 14:46:38 +00:00
#if WORKINGDIR_PATCH
static char *opt_dir = NULL;
#endif // WORKINGDIR_PATCH
2021-05-09 12:40:30 +00:00
#if ALPHA_PATCH && ALPHA_FOCUS_HIGHLIGHT_PATCH
static int focused = 0;
#endif // ALPHA_FOCUS_HIGHLIGHT_PATCH
static uint buttons; /* bit field of pressed buttons */
2021-05-08 08:13:20 +00:00
#if BLINKING_CURSOR_PATCH
static int cursorblinks = 0;
#endif // BLINKING_CURSOR_PATCH
2020-08-08 16:09:00 +00:00
#if VISUALBELL_1_PATCH
static int bellon = 0; /* visual bell status */
#endif // VISUALBELL_1_PATCH
#if RELATIVEBORDER_PATCH
int borderpx;
#endif // RELATIVEBORDER_PATCH
2021-05-16 09:40:15 +00:00
#if SWAPMOUSE_PATCH
static Cursor cursor;
static XColor xmousefg, xmousebg;
#endif // SWAPMOUSE_PATCH
#include "patch/x_include.c"
void
clipcopy(const Arg *dummy)
{
Atom clipboard;
free(xsel.clipboard);
xsel.clipboard = NULL;
if (xsel.primary != NULL) {
xsel.clipboard = xstrdup(xsel.primary);
clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);
}
}
void
clippaste(const Arg *dummy)
{
Atom clipboard;
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
if (IS_SET(MODE_KBDSELECT) && !kbds_issearchmode())
return;
#endif // KEYBOARDSELECT_PATCH
clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard,
xw.win, CurrentTime);
}
void
numlock(const Arg *dummy)
{
win.mode ^= MODE_NUMLOCK;
}
void
selpaste(const Arg *dummy)
{
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
if (IS_SET(MODE_KBDSELECT) && !kbds_issearchmode())
return;
#endif // KEYBOARDSELECT_PATCH
XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,
xw.win, CurrentTime);
}
void
ttysend(const Arg *arg)
{
ttywrite(arg->s, strlen(arg->s), 1);
}
void
zoom(const Arg *arg)
{
Arg larg;
larg.f = usedfontsize + arg->f;
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
#if SIXEL_PATCH
if (larg.f >= 1.0)
zoomabs(&larg);
#else
zoomabs(&larg);
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
#endif // SIXEL_PATCH
}
void
zoomabs(const Arg *arg)
{
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
#if SIXEL_PATCH
ImageList *im;
#endif // SIXEL_PATCH
xunloadfonts();
xloadfonts(usedfont, arg->f);
2020-03-21 15:41:43 +00:00
#if FONT2_PATCH
xloadsparefonts();
#endif // FONT2_PATCH
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
#if SIXEL_PATCH
/* deleting old pixmaps forces the new scaled pixmaps to be created */
for (im = term.images; im; im = im->next) {
if (im->pixmap)
XFreePixmap(xw.dpy, (Drawable)im->pixmap);
im->pixmap = NULL;
}
#endif // SIXEL_PATCH
cresize(0, 0);
redraw();
xhints();
}
void
zoomreset(const Arg *arg)
{
Arg larg;
if (defaultfontsize > 0) {
larg.f = defaultfontsize;
zoomabs(&larg);
}
}
int
evcol(XEvent *e)
{
#if ANYSIZE_PATCH
int x = e->xbutton.x - win.hborderpx;
#else
int x = e->xbutton.x - borderpx;
#endif // ANYSIZE_PATCH
LIMIT(x, 0, win.tw - 1);
return x / win.cw;
}
int
evrow(XEvent *e)
{
#if ANYSIZE_PATCH
int y = e->xbutton.y - win.vborderpx;
#else
int y = e->xbutton.y - borderpx;
#endif // ANYSIZE_PATCH
LIMIT(y, 0, win.th - 1);
return y / win.ch;
}
uint
buttonmask(uint button)
{
return button == Button1 ? Button1Mask
: button == Button2 ? Button2Mask
: button == Button3 ? Button3Mask
: button == Button4 ? Button4Mask
: button == Button5 ? Button5Mask
: 0;
}
int
mouseaction(XEvent *e, uint release)
{
MouseShortcut *ms;
int screen = tisaltscr() ? S_ALT : S_PRI;
/* ignore Button<N>mask for Button<N> - it's set on release */
uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
if (ms->release == release &&
ms->button == e->xbutton.button &&
(!ms->screen || (ms->screen == screen)) &&
(match(ms->mod, state) || /* exact or forced */
match(ms->mod, state & ~forcemousemod))) {
ms->func(&(ms->arg));
return 1;
}
}
return 0;
}
void
mousesel(XEvent *e, int done)
{
int type, seltype = SEL_REGULAR;
uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
if (kbds_isselectmode())
return;
#endif // KEYBOARDSELECT_PATCH
for (type = 1; type < LEN(selmasks); ++type) {
if (match(selmasks[type], state)) {
seltype = type;
break;
}
}
selextend(evcol(e), evrow(e), seltype, done);
if (done)
setsel(getsel(), e->xbutton.time);
}
void
mousereport(XEvent *e)
{
int len, btn, code;
int x = evcol(e), y = evrow(e);
int state = e->xbutton.state;
char buf[40];
static int ox, oy;
if (e->type == MotionNotify) {
if (x == ox && y == oy)
return;
if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
return;
/* MODE_MOUSEMOTION: no reporting if no button is pressed */
if (IS_SET(MODE_MOUSEMOTION) && buttons == 0)
return;
/* Set btn to lowest-numbered pressed button, or 12 if no
* buttons are pressed. */
for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++)
;
code = 32;
} else {
btn = e->xbutton.button;
/* Only buttons 1 through 11 can be encoded */
if (btn < 1 || btn > 11)
return;
if (e->type == ButtonRelease) {
/* MODE_MOUSEX10: no button release reporting */
if (IS_SET(MODE_MOUSEX10))
return;
/* Don't send release events for the scroll wheel */
if (btn == 4 || btn == 5)
return;
}
code = 0;
}
ox = x;
oy = y;
/* Encode btn into code. If no button is pressed for a motion event in
* MODE_MOUSEMANY, then encode it as a release. */
if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12)
code += 3;
else if (btn >= 8)
code += 128 + btn - 8;
else if (btn >= 4)
code += 64 + btn - 4;
else
code += btn - 1;
if (!IS_SET(MODE_MOUSEX10)) {
code += ((state & ShiftMask ) ? 4 : 0)
+ ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */
+ ((state & ControlMask) ? 16 : 0);
}
if (IS_SET(MODE_MOUSESGR)) {
len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
code, x+1, y+1,
e->type == ButtonRelease ? 'm' : 'M');
} else if (x < 223 && y < 223) {
len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
32+code, 32+x+1, 32+y+1);
} else {
return;
}
ttywrite(buf, len, 0);
}
void
bpress(XEvent *e)
{
int btn = e->xbutton.button;
struct timespec now;
int snap;
if (1 <= btn && btn <= 11)
buttons |= 1 << (btn-1);
if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
mousereport(e);
return;
}
if (mouseaction(e, 0))
return;
if (btn == Button1) {
/*
* If the user clicks below predefined timeouts specific
* snapping behaviour is exposed.
*/
clock_gettime(CLOCK_MONOTONIC, &now);
if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) {
snap = SNAP_LINE;
} else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) {
snap = SNAP_WORD;
} else {
snap = 0;
}
xsel.tclick2 = xsel.tclick1;
xsel.tclick1 = now;
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
if (kbds_isselectmode())
return;
#endif // KEYBOARDSELECT_PATCH
selstart(evcol(e), evrow(e), snap);
#if OPENURLONCLICK_PATCH
clearurl();
url_click = 1;
#endif // OPENURLONCLICK_PATCH
}
}
void
propnotify(XEvent *e)
{
XPropertyEvent *xpev;
Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
xpev = &e->xproperty;
if (xpev->state == PropertyNewValue &&
(xpev->atom == XA_PRIMARY ||
xpev->atom == clipboard)) {
selnotify(e);
}
2022-03-10 10:37:49 +00:00
#if BACKGROUND_IMAGE_PATCH
if (pseudotransparency &&
!strncmp(XGetAtomName(xw.dpy, e->xproperty.atom), "_NET_WM_STATE", 13)) {
updatexy();
redraw();
}
#endif // BACKGROUND_IMAGE_PATCH
}
void
selnotify(XEvent *e)
{
ulong nitems, ofs, rem;
int format;
uchar *data, *last, *repl;
Atom type, incratom, property = None;
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
int append = 0;
#endif // KEYBOARDSELECT_PATCH
incratom = XInternAtom(xw.dpy, "INCR", 0);
ofs = 0;
if (e->type == SelectionNotify)
property = e->xselection.property;
else if (e->type == PropertyNotify)
property = e->xproperty.atom;
if (property == None)
return;
do {
if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,
BUFSIZ/4, False, AnyPropertyType,
&type, &format, &nitems, &rem,
&data)) {
fprintf(stderr, "Clipboard allocation failed\n");
return;
}
2022-03-10 10:37:49 +00:00
#if BACKGROUND_IMAGE_PATCH
if (e->type == PropertyNotify && nitems == 0 && rem == 0 && !pseudotransparency)
#else
if (e->type == PropertyNotify && nitems == 0 && rem == 0)
#endif // BACKGROUND_IMAGE_PATCH
{
/*
* If there is some PropertyNotify with no data, then
* this is the signal of the selection owner that all
* data has been transferred. We won't need to receive
* PropertyNotify events anymore.
*/
MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);
XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
&xw.attrs);
}
if (type == incratom) {
/*
* Activate the PropertyNotify events so we receive
* when the selection owner does send us the next
* chunk of data.
*/
2022-03-10 10:37:49 +00:00
#if BACKGROUND_IMAGE_PATCH
if (!pseudotransparency) {
#endif // BACKGROUND_IMAGE_PATCH
MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
&xw.attrs);
2022-03-10 10:37:49 +00:00
#if BACKGROUND_IMAGE_PATCH
}
#endif // BACKGROUND_IMAGE_PATCH
/*
* Deleting the property is the transfer start signal.
*/
XDeleteProperty(xw.dpy, xw.win, (int)property);
continue;
}
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
if (IS_SET(MODE_KBDSELECT) && kbds_issearchmode()) {
kbds_pasteintosearch(data, nitems * format / 8, append++);
} else {
/*
* As seen in getsel:
* Line endings are inconsistent in the terminal and GUI world
* copy and pasting. When receiving some selection data,
* replace all '\n' with '\r'.
* FIXME: Fix the computer world.
*/
repl = data;
last = data + nitems * format / 8;
while ((repl = memchr(repl, '\n', last - repl))) {
*repl++ = '\r';
}
if (IS_SET(MODE_BRCKTPASTE) && ofs == 0)
ttywrite("\033[200~", 6, 0);
ttywrite((char *)data, nitems * format / 8, 1);
if (IS_SET(MODE_BRCKTPASTE) && rem == 0)
ttywrite("\033[201~", 6, 0);
}
#else
/*
* As seen in getsel:
* Line endings are inconsistent in the terminal and GUI world
* copy and pasting. When receiving some selection data,
* replace all '\n' with '\r'.
* FIXME: Fix the computer world.
*/
repl = data;
last = data + nitems * format / 8;
while ((repl = memchr(repl, '\n', last - repl))) {
*repl++ = '\r';
}
if (IS_SET(MODE_BRCKTPASTE) && ofs == 0)
ttywrite("\033[200~", 6, 0);
ttywrite((char *)data, nitems * format / 8, 1);
if (IS_SET(MODE_BRCKTPASTE) && rem == 0)
ttywrite("\033[201~", 6, 0);
2024-03-13 09:33:51 +00:00
#endif // KEYBOARDSELECT_PATCH
XFree(data);
/* number of 32-bit chunks returned */
ofs += nitems * format / 32;
} while (rem > 0);
/*
* Deleting the property again tells the selection owner to send the
* next data chunk in the property.
*/
XDeleteProperty(xw.dpy, xw.win, (int)property);
}
void
xclipcopy(void)
{
clipcopy(NULL);
}
void
selclear_(XEvent *e)
{
selclear();
}
void
selrequest(XEvent *e)
{
XSelectionRequestEvent *xsre;
XSelectionEvent xev;
Atom xa_targets, string, clipboard;
char *seltext;
xsre = (XSelectionRequestEvent *) e;
xev.type = SelectionNotify;
xev.requestor = xsre->requestor;
xev.selection = xsre->selection;
xev.target = xsre->target;
xev.time = xsre->time;
if (xsre->property == None)
xsre->property = xsre->target;
/* reject */
xev.property = None;
xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);
if (xsre->target == xa_targets) {
/* respond with the supported type */
string = xsel.xtarget;
XChangeProperty(xsre->display, xsre->requestor, xsre->property,
XA_ATOM, 32, PropModeReplace,
(uchar *) &string, 1);
xev.property = xsre->property;
} else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) {
/*
* xith XA_STRING non ascii characters may be incorrect in the
* requestor. It is not our problem, use utf8.
*/
clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
if (xsre->selection == XA_PRIMARY) {
seltext = xsel.primary;
} else if (xsre->selection == clipboard) {
seltext = xsel.clipboard;
} else {
fprintf(stderr,
"Unhandled clipboard selection 0x%lx\n",
xsre->selection);
return;
}
if (seltext != NULL) {
XChangeProperty(xsre->display, xsre->requestor,
xsre->property, xsre->target,
8, PropModeReplace,
(uchar *)seltext, strlen(seltext));
xev.property = xsre->property;
}
}
/* all done, send a notification to the listener */
if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))
fprintf(stderr, "Error sending SelectionNotify event\n");
}
void
setsel(char *str, Time t)
{
if (!str)
return;
free(xsel.primary);
xsel.primary = str;
XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
selclear();
2019-09-16 07:51:41 +00:00
#if CLIPBOARD_PATCH
clipcopy(NULL);
#endif // CLIPBOARD_PATCH
}
#if XRESOURCES_PATCH && XRESOURCES_RELOAD_PATCH || BACKGROUND_IMAGE_PATCH && BACKGROUND_IMAGE_RELOAD_PATCH
void
sigusr1_reload(int sig)
{
#if XRESOURCES_PATCH && XRESOURCES_RELOAD_PATCH
reload_config(sig);
#endif // XRESOURCES_RELOAD_PATCH
#if BACKGROUND_IMAGE_PATCH && BACKGROUND_IMAGE_RELOAD_PATCH
reload_image();
#endif // BACKGROUND_IMAGE_RELOAD_PATCH
signal(SIGUSR1, sigusr1_reload);
}
#endif // XRESOURCES_RELOAD_PATCH | BACKGROUND_IMAGE_RELOAD_PATCH
void
xsetsel(char *str)
{
setsel(str, CurrentTime);
}
void
brelease(XEvent *e)
{
int btn = e->xbutton.button;
if (1 <= btn && btn <= 11)
buttons &= ~(1 << (btn-1));
if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
mousereport(e);
return;
}
if (mouseaction(e, 1))
return;
if (btn == Button1) {
mousesel(e, 1);
2021-07-07 08:08:43 +00:00
#if OPENURLONCLICK_PATCH
if (url_click && e->xkey.state & url_opener_modkey)
openUrlOnClick(evcol(e), evrow(e), url_opener);
2021-07-07 08:08:43 +00:00
#endif // OPENURLONCLICK_PATCH
}
#if RIGHTCLICKTOPLUMB_PATCH
else if (btn == Button3)
plumb(xsel.primary);
#endif // RIGHTCLICKTOPLUMB_PATCH
}
void
bmotion(XEvent *e)
{
#if HIDECURSOR_PATCH
if (!xw.pointerisvisible) {
2021-05-16 09:40:15 +00:00
#if SWAPMOUSE_PATCH
if (win.mode & MODE_MOUSE)
XUndefineCursor(xw.dpy, xw.win);
else
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
#else
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
2021-05-16 09:40:15 +00:00
#endif // SWAPMOUSE_PATCH
xw.pointerisvisible = 1;
if (!IS_SET(MODE_MOUSEMANY))
xsetpointermotion(0);
}
#endif // HIDECURSOR_PATCH
#if OPENURLONCLICK_PATCH
if (!IS_SET(MODE_MOUSE)) {
if (!(e->xbutton.state & Button1Mask) && detecturl(evcol(e), evrow(e), 1))
XDefineCursor(xw.dpy, xw.win, xw.upointer);
else
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
}
url_click = 0;
#endif // OPENURLONCLICK_PATCH
if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
mousereport(e);
return;
}
mousesel(e, 0);
}
void
cresize(int width, int height)
{
int col, row;
if (width != 0)
win.w = width;
if (height != 0)
win.h = height;
col = (win.w - 2 * borderpx) / win.cw;
row = (win.h - 2 * borderpx) / win.ch;
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
col = MAX(2, col);
row = MAX(1, row);
#if ANYSIZE_PATCH
win.hborderpx = (win.w - col * win.cw) / 2;
win.vborderpx = (win.h - row * win.ch) / 2;
#endif // ANYSIZE_PATCH
tresize(col, row);
xresize(col, row);
ttyresize(win.tw, win.th);
}
void
xresize(int col, int row)
{
win.tw = col * win.cw;
win.th = row * win.ch;
#if !SINGLE_DRAWABLE_BUFFER_PATCH
XFreePixmap(xw.dpy, xw.buf);
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
2024-03-13 09:33:51 +00:00
#if ALPHA_PATCH
xw.depth
#else
DefaultDepth(xw.dpy, xw.scr)
#endif // ALPHA_PATCH
);
XftDrawChange(xw.draw, xw.buf);
#endif // SINGLE_DRAWABLE_BUFFER_PATCH
xclear(0, 0, win.w, win.h);
/* resize to new width */
2024-03-07 14:34:21 +00:00
#if LIGATURES_PATCH
xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4);
xw.specseq = xrealloc(xw.specseq, col * sizeof(GlyphFontSeq));
#else
xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));
2024-03-07 14:34:21 +00:00
#endif // LIGATURES_PATCH
}
ushort
sixd_to_16bit(int x)
{
return x == 0 ? 0 : 0x3737 + 0x2828 * x;
}
int
xloadcolor(int i, const char *name, Color *ncolor)
{
XRenderColor color = { .alpha = 0xffff };
if (!name) {
if (BETWEEN(i, 16, 255)) { /* 256 color */
if (i < 6*6*6+16) { /* same colors as xterm */
color.red = sixd_to_16bit( ((i-16)/36)%6 );
color.green = sixd_to_16bit( ((i-16)/6) %6 );
color.blue = sixd_to_16bit( ((i-16)/1) %6 );
} else { /* greyscale */
color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));
color.green = color.blue = color.red;
}
return XftColorAllocValue(xw.dpy, xw.vis,
xw.cmap, &color, ncolor);
} else
name = colorname[i];
}
return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);
}
2021-05-09 12:40:30 +00:00
#if ALPHA_PATCH && ALPHA_FOCUS_HIGHLIGHT_PATCH
void
xloadalpha(void)
{
float const usedAlpha = focused ? alpha : alphaUnfocused;
if (opt_alpha) alpha = strtof(opt_alpha, NULL);
dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * usedAlpha);
dc.col[defaultbg].pixel &= 0x00FFFFFF;
dc.col[defaultbg].pixel |= (unsigned char)(0xff * usedAlpha) << 24;
}
#endif // ALPHA_FOCUS_HIGHLIGHT_PATCH
#if ALPHA_PATCH && ALPHA_FOCUS_HIGHLIGHT_PATCH
void
xloadcols(void)
{
static int loaded;
Color *cp;
if (!loaded) {
dc.collen = 1 + (defaultbg = MAX(LEN(colorname), 256));
dc.col = xmalloc((dc.collen) * sizeof(Color));
}
for (int i = 0; i+1 < dc.collen; ++i)
if (!xloadcolor(i, NULL, &dc.col[i])) {
if (colorname[i])
die("could not allocate color '%s'\n", colorname[i]);
else
die("could not allocate color %d\n", i);
}
if (dc.collen) // cannot die, as the color is already loaded.
xloadcolor(focused ? bg : bgUnfocused, NULL, &dc.col[defaultbg]);
xloadalpha();
loaded = 1;
}
#else
void
xloadcols(void)
{
int i;
static int loaded;
Color *cp;
if (loaded) {
for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)
XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);
} else {
dc.collen = MAX(LEN(colorname), 256);
dc.col = xmalloc(dc.collen * sizeof(Color));
}
for (i = 0; i < dc.collen; i++)
if (!xloadcolor(i, NULL, &dc.col[i])) {
if (colorname[i])
die("could not allocate color '%s'\n", colorname[i]);
else
die("could not allocate color %d\n", i);
}
#if ALPHA_PATCH
/* 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;
#endif // ALPHA_PATCH
loaded = 1;
}
2021-05-09 12:40:30 +00:00
#endif // ALPHA_FOCUS_HIGHLIGHT_PATCH
int
xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b)
{
if (!BETWEEN(x, 0, dc.collen - 1))
return 1;
*r = dc.col[x].color.red >> 8;
*g = dc.col[x].color.green >> 8;
*b = dc.col[x].color.blue >> 8;
return 0;
}
int
xsetcolorname(int x, const char *name)
{
Color ncolor;
if (!BETWEEN(x, 0, dc.collen - 1))
return 1;
if (!xloadcolor(x, name, &ncolor))
return 1;
XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);
dc.col[x] = ncolor;
#if ALPHA_PATCH
/* set alpha value of bg color */
if (x == defaultbg) {
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;
}
#endif // ALPHA_PATCH
return 0;
}
/*
* Absolute coordinates.
*/
void
xclear(int x1, int y1, int x2, int y2)
{
2022-03-10 10:37:49 +00:00
#if BACKGROUND_IMAGE_PATCH
if (pseudotransparency)
XSetTSOrigin(xw.dpy, xw.bggc, -win.x, -win.y);
XFillRectangle(xw.dpy, xw.buf, xw.bggc, x1, y1, x2-x1, y2-y1);
#elif INVERT_PATCH
2020-03-29 13:38:16 +00:00
Color c;
c = dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg];
if (invertcolors) {
c = invertedcolor(&c);
}
XftDrawRect(xw.draw, &c, x1, y1, x2-x1, y2-y1);
#else
XftDrawRect(xw.draw,
&dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],
x1, y1, x2-x1, y2-y1);
2020-03-29 13:38:16 +00:00
#endif // INVERT_PATCH
}
void
xclearwin(void)
{
xclear(0, 0, win.w, win.h);
}
void
xhints(void)
{
2019-09-16 12:21:09 +00:00
#if XRESOURCES_PATCH
XClassHint class = {opt_name ? opt_name : "st",
opt_class ? opt_class : "St"};
#else
XClassHint class = {opt_name ? opt_name : termname,
opt_class ? opt_class : termname};
2019-09-16 12:21:09 +00:00
#endif // XRESOURCES_PATCH
XWMHints wm = {.flags = InputHint, .input = 1};
XSizeHints *sizeh;
sizeh = XAllocSizeHints();
sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;
sizeh->height = win.h;
sizeh->width = win.w;
#if ANYSIZE_PATCH || ANYSIZE_SIMPLE_PATCH
sizeh->height_inc = 1;
sizeh->width_inc = 1;
#else
sizeh->height_inc = win.ch;
sizeh->width_inc = win.cw;
#endif // ANYSIZE_PATCH
sizeh->base_height = 2 * borderpx;
sizeh->base_width = 2 * borderpx;
sizeh->min_height = win.ch + 2 * borderpx;
sizeh->min_width = win.cw + 2 * borderpx;
if (xw.isfixed) {
sizeh->flags |= PMaxSize;
sizeh->min_width = sizeh->max_width = win.w;
sizeh->min_height = sizeh->max_height = win.h;
}
if (xw.gm & (XValue|YValue)) {
sizeh->flags |= USPosition | PWinGravity;
sizeh->x = xw.l;
sizeh->y = xw.t;
sizeh->win_gravity = xgeommasktogravity(xw.gm);
}
XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm,
&class);
XFree(sizeh);
}
int
xgeommasktogravity(int mask)
{
switch (mask & (XNegative|YNegative)) {
case 0:
return NorthWestGravity;
case XNegative:
return NorthEastGravity;
case YNegative:
return SouthWestGravity;
}
return SouthEastGravity;
}
2020-03-24 14:41:43 +00:00
int
ximopen(Display *dpy)
{
2020-03-24 14:41:43 +00:00
XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };
XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };
2020-03-24 14:41:43 +00:00
xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);
if (xw.ime.xim == NULL)
return 0;
if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))
fprintf(stderr, "XSetIMValues: "
"Could not set XNDestroyCallback.\n");
xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,
NULL);
2020-03-24 14:41:43 +00:00
if (xw.ime.xic == NULL) {
xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,
XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, xw.win,
XNDestroyCallback, &icdestroy,
NULL);
}
if (xw.ime.xic == NULL)
fprintf(stderr, "XCreateIC: Could not create input context.\n");
return 1;
}
void
ximinstantiate(Display *dpy, XPointer client, XPointer call)
{
2020-03-24 14:41:43 +00:00
if (ximopen(dpy))
XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
ximinstantiate, NULL);
}
void
ximdestroy(XIM xim, XPointer client, XPointer call)
{
xw.ime.xim = NULL;
XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
2020-03-24 14:41:43 +00:00
ximinstantiate, NULL);
XFree(xw.ime.spotlist);
}
int
xicdestroy(XIC xim, XPointer client, XPointer call)
{
xw.ime.xic = NULL;
return 1;
}
int
xloadfont(Font *f, FcPattern *pattern)
{
FcPattern *configured;
FcPattern *match;
FcResult result;
XGlyphInfo extents;
int wantattr, haveattr;
/*
* Manually configure instead of calling XftMatchFont
* so that we can use the configured pattern for
* "missing glyph" lookups.
*/
configured = FcPatternDuplicate(pattern);
if (!configured)
return 1;
FcConfigSubstitute(NULL, configured, FcMatchPattern);
XftDefaultSubstitute(xw.dpy, xw.scr, configured);
2022-08-28 19:05:55 +00:00
#if USE_XFTFONTMATCH_PATCH
match = XftFontMatch(xw.dpy, xw.scr, pattern, &result);
#else
match = FcFontMatch(NULL, configured, &result);
2022-08-28 19:05:55 +00:00
#endif // USE_XFTFONTMATCH_PATCH
if (!match) {
FcPatternDestroy(configured);
return 1;
}
if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {
FcPatternDestroy(configured);
FcPatternDestroy(match);
return 1;
}
if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) ==
XftResultMatch)) {
/*
* Check if xft was unable to find a font with the appropriate
* slant but gave us one anyway. Try to mitigate.
*/
if ((XftPatternGetInteger(f->match->pattern, "slant", 0,
&haveattr) != XftResultMatch) || haveattr < wantattr) {
f->badslant = 1;
fputs("font slant does not match\n", stderr);
}
}
if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) ==
XftResultMatch)) {
if ((XftPatternGetInteger(f->match->pattern, "weight", 0,
&haveattr) != XftResultMatch) || haveattr != wantattr) {
f->badweight = 1;
fputs("font weight does not match\n", stderr);
}
}
XftTextExtentsUtf8(xw.dpy, f->match,
(const FcChar8 *) ascii_printable,
strlen(ascii_printable), &extents);
f->set = NULL;
f->pattern = configured;
f->ascent = f->match->ascent;
f->descent = f->match->descent;
f->lbearing = 0;
f->rbearing = f->match->max_advance_width;
f->height = f->ascent + f->descent;
#if WIDE_GLYPH_SPACING_PATCH
f->width = DIVCEIL(extents.xOff > 18 ? extents.xOff / 3 : extents.xOff, strlen(ascii_printable));
#else
f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
2021-05-15 07:16:51 +00:00
#endif // WIDE_GLYPH_SPACING_PATCH
return 0;
}
void
xloadfonts(const char *fontstr, double fontsize)
{
FcPattern *pattern;
double fontval;
if (fontstr[0] == '-')
pattern = XftXlfdParse(fontstr, False, False);
else
pattern = FcNameParse((const FcChar8 *)fontstr);
if (!pattern)
die("can't open font %s\n", fontstr);
if (fontsize > 1) {
FcPatternDel(pattern, FC_PIXEL_SIZE);
FcPatternDel(pattern, FC_SIZE);
FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);
usedfontsize = fontsize;
} else {
if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
FcResultMatch) {
usedfontsize = fontval;
} else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
FcResultMatch) {
usedfontsize = -1;
} else {
/*
* Default font size is 12, if none given. This is to
* have a known usedfontsize value.
*/
FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
usedfontsize = 12;
}
defaultfontsize = usedfontsize;
}
if (xloadfont(&dc.font, pattern))
die("can't open font %s\n", fontstr);
if (usedfontsize < 0) {
FcPatternGetDouble(dc.font.match->pattern,
FC_PIXEL_SIZE, 0, &fontval);
usedfontsize = fontval;
if (fontsize == 0)
defaultfontsize = fontval;
}
/* Setting character width and height. */
win.cw = ceilf(dc.font.width * cwscale);
win.ch = ceilf(dc.font.height * chscale);
2019-09-16 13:31:58 +00:00
#if VERTCENTER_PATCH
win.cyo = ceilf(dc.font.height * (chscale - 1) / 2);
#endif // VERTCENTER_PATCH
#if RELATIVEBORDER_PATCH
borderpx = (int) ceilf(((float)borderperc / 100) * win.cw);
#endif // RELATIVEBORDER_PATCH
FcPatternDel(pattern, FC_SLANT);
2019-09-16 08:40:16 +00:00
#if !DISABLE_ITALIC_FONTS_PATCH
FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
2019-09-16 08:40:16 +00:00
#endif // DISABLE_ITALIC_FONTS_PATCH
if (xloadfont(&dc.ifont, pattern))
die("can't open font %s\n", fontstr);
FcPatternDel(pattern, FC_WEIGHT);
2019-09-16 08:40:16 +00:00
#if !DISABLE_BOLD_FONTS_PATCH
FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
2019-09-16 08:40:16 +00:00
#endif // DISABLE_BOLD_FONTS_PATCH
if (xloadfont(&dc.ibfont, pattern))
die("can't open font %s\n", fontstr);
FcPatternDel(pattern, FC_SLANT);
2019-09-16 08:40:16 +00:00
#if !DISABLE_ROMAN_FONTS_PATCH
FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
2019-09-16 08:40:16 +00:00
#endif // DISABLE_ROMAN_FONTS_PATCH
if (xloadfont(&dc.bfont, pattern))
die("can't open font %s\n", fontstr);
FcPatternDestroy(pattern);
}
void
xunloadfont(Font *f)
{
XftFontClose(xw.dpy, f->match);
FcPatternDestroy(f->pattern);
if (f->set)
FcFontSetDestroy(f->set);
}
void
xunloadfonts(void)
{
#if LIGATURES_PATCH
/* Clear Harfbuzz font cache. */
hbunloadfonts();
#endif // LIGATURES_PATCH
/* Free the loaded fonts in the font cache. */
while (frclen > 0)
XftFontClose(xw.dpy, frc[--frclen].font);
xunloadfont(&dc.font);
xunloadfont(&dc.bfont);
xunloadfont(&dc.ifont);
xunloadfont(&dc.ibfont);
}
void
xinit(int cols, int rows)
{
XGCValues gcvalues;
#if HIDECURSOR_PATCH
Pixmap blankpm;
2021-05-16 09:40:15 +00:00
#elif !SWAPMOUSE_PATCH
Cursor cursor;
#endif // HIDECURSOR_PATCH
Window parent;
pid_t thispid = getpid();
2021-05-16 09:40:15 +00:00
#if !SWAPMOUSE_PATCH
XColor xmousefg, xmousebg;
2021-05-16 09:40:15 +00:00
#endif // SWAPMOUSE_PATCH
#if ALPHA_PATCH
XWindowAttributes attr;
XVisualInfo vis;
#endif // ALPHA_PATCH
#if !XRESOURCES_PATCH
if (!(xw.dpy = XOpenDisplay(NULL)))
die("can't open display\n");
#endif // XRESOURCES_PATCH
xw.scr = XDefaultScreen(xw.dpy);
#if ALPHA_PATCH
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;
#else
xw.vis = XDefaultVisual(xw.dpy, xw.scr);
#endif // ALPHA_PATCH
/* font */
if (!FcInit())
die("could not init fontconfig.\n");
usedfont = (opt_font == NULL)? font : opt_font;
xloadfonts(usedfont, 0);
2020-03-21 15:41:43 +00:00
#if FONT2_PATCH
/* spare fonts */
xloadsparefonts();
#endif // FONT2_PATCH
/* colors */
#if ALPHA_PATCH
xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None);
#else
xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
#endif // ALPHA_PATCH
xloadcols();
/* adjust fixed window geometry */
#if ANYSIZE_PATCH
win.w = 2 * win.hborderpx + cols * win.cw;
win.h = 2 * win.vborderpx + rows * win.ch;
#else
win.w = 2 * borderpx + cols * win.cw;
win.h = 2 * borderpx + rows * win.ch;
#endif // ANYSIZE_PATCH
if (xw.gm & XNegative)
xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;
if (xw.gm & YNegative)
xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;
/* Events */
xw.attrs.background_pixel = dc.col[defaultbg].pixel;
xw.attrs.border_pixel = dc.col[defaultbg].pixel;
xw.attrs.bit_gravity = NorthWestGravity;
xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask
| ExposureMask | VisibilityChangeMask | StructureNotifyMask
2020-01-07 07:05:00 +00:00
| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask
#if ST_EMBEDDER_PATCH
| SubstructureNotifyMask | SubstructureRedirectMask
#endif // ST_EMBEDDER_PATCH
;
xw.attrs.colormap = xw.cmap;
#if OPENURLONCLICK_PATCH
xw.attrs.event_mask |= PointerMotionMask;
#endif // OPENURLONCLICK_PATCH
#if !ALPHA_PATCH
if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
parent = XRootWindow(xw.dpy, xw.scr);
#endif // ALPHA_PATCH
xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,
#if ALPHA_PATCH
win.w, win.h, 0, xw.depth, InputOutput,
#else
win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,
#endif // ALPHA_PATCH
xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity
| CWEventMask | CWColormap, &xw.attrs);
memset(&gcvalues, 0, sizeof(gcvalues));
gcvalues.graphics_exposures = False;
#if ALPHA_PATCH
#if SINGLE_DRAWABLE_BUFFER_PATCH
xw.buf = xw.win;
#else
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth);
#endif // SINGLE_DRAWABLE_BUFFER_PATCH
dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues);
#else
dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,
&gcvalues);
#if SINGLE_DRAWABLE_BUFFER_PATCH
xw.buf = xw.win;
#else
xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,
DefaultDepth(xw.dpy, xw.scr));
#endif // SINGLE_DRAWABLE_BUFFER_PATCH
#endif // ALPHA_PATCH
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 */
2024-03-07 14:34:21 +00:00
#if LIGATURES_PATCH
xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4);
xw.specseq = xmalloc(cols * sizeof(GlyphFontSeq));
#else
xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));
2024-03-07 14:34:21 +00:00
#endif // LIGATURES_PATCH
/* Xft rendering context */
xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);
/* input methods */
2020-03-24 14:41:43 +00:00
if (!ximopen(xw.dpy)) {
XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,
ximinstantiate, NULL);
}
/* white cursor, black outline */
#if HIDECURSOR_PATCH
xw.pointerisvisible = 1;
#if THEMED_CURSOR_PATCH
xw.vpointer = XcursorLibraryLoadCursor(xw.dpy, mouseshape);
#else
xw.vpointer = XCreateFontCursor(xw.dpy, mouseshape);
#endif // THEMED_CURSOR_PATCH
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
#elif THEMED_CURSOR_PATCH
cursor = XcursorLibraryLoadCursor(xw.dpy, mouseshape);
XDefineCursor(xw.dpy, xw.win, cursor);
#else
cursor = XCreateFontCursor(xw.dpy, mouseshape);
XDefineCursor(xw.dpy, xw.win, cursor);
#endif // HIDECURSOR_PATCH
#if !THEMED_CURSOR_PATCH
if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) {
xmousefg.red = 0xffff;
xmousefg.green = 0xffff;
xmousefg.blue = 0xffff;
}
if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) {
xmousebg.red = 0x0000;
xmousebg.green = 0x0000;
xmousebg.blue = 0x0000;
}
#endif // THEMED_CURSOR_PATCH
#if HIDECURSOR_PATCH
#if !THEMED_CURSOR_PATCH
XRecolorCursor(xw.dpy, xw.vpointer, &xmousefg, &xmousebg);
#endif // THEMED_CURSOR_PATCH
blankpm = XCreateBitmapFromData(xw.dpy, xw.win, &(char){0}, 1, 1);
xw.bpointer = XCreatePixmapCursor(xw.dpy, blankpm, blankpm,
&xmousefg, &xmousebg, 0, 0);
#elif !THEMED_CURSOR_PATCH
XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
#endif // HIDECURSOR_PATCH
#if OPENURLONCLICK_PATCH
xw.upointer = XCreateFontCursor(xw.dpy, XC_hand2);
#if !HIDECURSOR_PATCH
xw.vpointer = cursor;
xw.pointerisvisible = 1;
#endif // HIDECURSOR_PATCH
#endif // OPENURLONCLICK_PATCH
xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
#if NETWMICON_PATCH || NETWMICON_FF_PATCH || NETWMICON_LEGACY_PATCH
setnetwmicon();
#endif // NETWMICON_PATCH
2021-05-08 12:49:59 +00:00
2022-08-24 09:04:49 +00:00
#if NO_WINDOW_DECORATIONS_PATCH
Atom motifwmhints = XInternAtom(xw.dpy, "_MOTIF_WM_HINTS", False);
unsigned int data[] = { 0x2, 0x0, 0x0, 0x0, 0x0 };
XChangeProperty(xw.dpy, xw.win, motifwmhints, motifwmhints, 16,
PropModeReplace, (unsigned char *)data, 5);
#endif // NO_WINDOW_DECORATIONS_PATCH
xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,
PropModeReplace, (uchar *)&thispid, 1);
2022-10-24 09:27:51 +00:00
#if FULLSCREEN_PATCH
xw.netwmstate = XInternAtom(xw.dpy, "_NET_WM_STATE", False);
xw.netwmfullscreen = XInternAtom(xw.dpy, "_NET_WM_STATE_FULLSCREEN", False);
#endif // FULLSCREEN_PATCH
win.mode = MODE_NUMLOCK;
resettitle();
xhints();
XMapWindow(xw.dpy, xw.win);
XSync(xw.dpy, False);
clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);
clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2);
xsel.primary = NULL;
xsel.clipboard = NULL;
xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None)
xsel.xtarget = XA_STRING;
#if BOXDRAW_PATCH
boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis);
#endif // BOXDRAW_PATCH
}
2022-12-19 09:09:06 +00:00
#if LIGATURES_PATCH
void
2024-03-07 14:34:21 +00:00
xresetfontsettings(uint32_t mode, Font **font, int *frcflags)
2022-12-19 09:09:06 +00:00
{
*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;
}
}
#endif // LIGATURES_PATCH
int
xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
{
#if ANYSIZE_PATCH
float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp;
#else
float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;
#endif // ANYSIZE_PATCH
ushort mode, prevmode = USHRT_MAX;
Font *font = &dc.font;
int frcflags = FRC_NORMAL;
2024-03-07 14:34:21 +00:00
float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
Rune rune;
FT_UInt glyphidx;
FcResult fcres;
FcPattern *fcpattern, *fontpattern;
FcFontSet *fcsets[] = { NULL };
FcCharSet *fccharset;
int i, f, numspecs = 0;
#if LIGATURES_PATCH
2024-03-07 14:34:21 +00:00
float cluster_xp, cluster_yp;
HbTransformData shaped;
2022-12-19 09:09:06 +00:00
/* Initial values. */
2024-03-07 14:34:21 +00:00
xresetfontsettings(glyphs[0].mode, &font, &frcflags);
#if VERTCENTER_PATCH
2024-03-07 14:34:21 +00:00
xp = winx, yp = winy + font->ascent + win.cyo;
#else
xp = winx, yp = winy + font->ascent;
#endif // VERTCENTER_PATCH
2024-03-07 14:34:21 +00:00
cluster_xp = xp; cluster_yp = yp;
/* Shape the segment. */
hbtransform(&shaped, font->match, glyphs, 0, len);
2022-12-19 09:09:06 +00:00
#endif // LIGATURES_PATCH
2024-03-07 14:34:21 +00:00
#if LIGATURES_PATCH
for (int code_idx = 0; code_idx < shaped.count; code_idx++)
#elif VERTCENTER_PATCH
2019-09-16 13:31:58 +00:00
for (i = 0, xp = winx, yp = winy + font->ascent + win.cyo; i < len; ++i)
#else
for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i)
2024-03-07 14:34:21 +00:00
#endif // LIGATURES_PATCH | VERTCENTER_PATCH
2019-09-16 13:31:58 +00:00
{
/* Fetch rune and mode for current glyph. */
#if LIGATURES_PATCH
2024-03-07 14:34:21 +00:00
int idx = shaped.glyphs[code_idx].cluster;
2021-05-09 15:48:28 +00:00
#else
rune = glyphs[i].u;
mode = glyphs[i].mode;
#endif // LIGATURES_PATCH
/* Skip dummy wide-character spacing. */
#if LIGATURES_PATCH
2024-03-07 14:34:21 +00:00
if (glyphs[idx].mode & ATTR_WDUMMY)
2022-12-19 09:09:06 +00:00
continue;
2024-03-07 14:34:21 +00:00
/* 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;
}
#if BOXDRAW_PATCH
if (glyphs[idx].mode & ATTR_BOXDRAW) {
/* minor shoehorning: boxdraw uses only this ushort */
specs[numspecs].font = font->match;
specs[numspecs].glyph = boxdrawindex(&glyphs[idx]);
specs[numspecs].x = xp;
specs[numspecs].y = yp;
numspecs++;
} else if (shaped.glyphs[code_idx].codepoint != 0) {
#else
if (shaped.glyphs[code_idx].codepoint != 0) {
#endif // BOXDRAW_PATCH
/* 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[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;
}
2022-12-19 09:09:06 +00:00
}
2024-03-07 14:34:21 +00:00
/* 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));
}
2022-12-19 09:09:06 +00:00
2024-03-07 14:34:21 +00:00
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;
2022-12-19 09:09:06 +00:00
2024-03-07 14:34:21 +00:00
glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
2022-12-19 09:09:06 +00:00
2024-03-07 14:34:21 +00:00
f = frclen;
frclen++;
2022-12-19 09:09:06 +00:00
2024-03-07 14:34:21 +00:00
FcPatternDestroy(fcpattern);
FcCharSetDestroy(fccharset);
2022-12-19 09:09:06 +00:00
}
2024-03-07 14:34:21 +00:00
specs[numspecs].font = frc[f].font;
specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp;
numspecs++;
2022-12-19 09:09:06 +00:00
}
#else // !LIGATURES_PATCH
if (mode == ATTR_WDUMMY)
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;
}
2019-09-16 13:31:58 +00:00
#if VERTCENTER_PATCH
yp = winy + font->ascent + win.cyo;
#else
yp = winy + font->ascent;
2019-09-16 13:31:58 +00:00
#endif // VERTCENTER_PATCH
}
#if BOXDRAW_PATCH
if (mode & ATTR_BOXDRAW) {
/* minor shoehorning: boxdraw uses only this ushort */
glyphidx = boxdrawindex(&glyphs[i]);
} else {
/* Lookup character index with default font. */
glyphidx = XftCharIndex(xw.dpy, font->match, rune);
}
#else
/* Lookup character index with default font. */
glyphidx = XftCharIndex(xw.dpy, font->match, rune);
#endif // BOXDRAW_PATCH
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)
2022-12-19 09:09:06 +00:00
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);
2022-12-19 09:09:06 +00:00
FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
2022-08-28 19:05:55 +00:00
#if !USE_XFTFONTMATCH_PATCH
FcConfigSubstitute(0, fcpattern, FcMatchPattern);
FcDefaultSubstitute(fcpattern);
2022-08-28 19:05:55 +00:00
#endif // USE_XFTFONTMATCH_PATCH
2022-08-28 19:05:55 +00:00
fontpattern = FcFontSetMatch(0, fcsets, 1, fcpattern, &fcres);
2020-03-21 15:41:43 +00:00
/* Allocate memory for the new cache entry. */
if (frclen >= frccap) {
frccap += 16;
frc = xrealloc(frc, frccap * sizeof(Fontcache));
2021-05-09 15:48:28 +00:00
}
2022-12-19 09:09:06 +00:00
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;
xp += runewidth;
numspecs++;
2022-12-19 09:09:06 +00:00
#endif // LIGATURES_PATCH
}
return numspecs;
}
#if UNDERCURL_PATCH
static int isSlopeRising (int x, int iPoint, int waveWidth)
{
// . . . .
// / \ / \ / \ / \
// / \ / \ / \ / \
// . . . . .
// Find absolute `x` of point
x += iPoint * (waveWidth/2);
// Find index of absolute wave
int absSlope = x / ((float)waveWidth/2);
return (absSlope % 2);
}
static int getSlope (int x, int iPoint, int waveWidth)
{
// Sizes: Caps are half width of slopes
// 1_2 1_2 1_2 1_2
// / \ / \ / \ / \
// / \ / \ / \ / \
// 0 3_0 3_0 3_0 3_
// <2-> <1> <---6---->
// Find type of first point
int firstType;
x -= (x / waveWidth) * waveWidth;
if (x < (waveWidth * (2.f/6.f)))
firstType = UNDERCURL_SLOPE_ASCENDING;
else if (x < (waveWidth * (3.f/6.f)))
firstType = UNDERCURL_SLOPE_TOP_CAP;
else if (x < (waveWidth * (5.f/6.f)))
firstType = UNDERCURL_SLOPE_DESCENDING;
else
firstType = UNDERCURL_SLOPE_BOTTOM_CAP;
// Find type of given point
int pointType = (iPoint % 4);
pointType += firstType;
pointType %= 4;
return pointType;
}
#endif // UNDERCURL_PATCH
void
xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y
2024-03-07 14:34:21 +00:00
#if WIDE_GLYPHS_PATCH
,int dmode
#endif // WIDE_GLYPHS_PATCH
#if LIGATURES_PATCH
, int charlen
#endif // LIGATURES_PATCH
) {
#if LIGATURES_PATCH
2024-03-07 14:34:21 +00:00
int width = charlen * win.cw;
#else
int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
2024-03-07 14:34:21 +00:00
int width = charlen * win.cw;
#endif // WIDE_GLYPHS_PATCH
#if ANYSIZE_PATCH
int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch;
#else
int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch;
#endif // ANYSIZE_PATCH
Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
XRenderColor colfg, colbg;
XRectangle r;
/* Fallback on color display for attributes not supported by the font */
if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {
if (dc.ibfont.badslant || dc.ibfont.badweight)
base.fg = defaultattr;
} else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||
(base.mode & ATTR_BOLD && dc.bfont.badweight)) {
base.fg = defaultattr;
}
if (IS_TRUECOL(base.fg)) {
colfg.alpha = 0xffff;
colfg.red = TRUERED(base.fg);
colfg.green = TRUEGREEN(base.fg);
colfg.blue = TRUEBLUE(base.fg);
XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);
fg = &truefg;
} else {
fg = &dc.col[base.fg];
}
if (IS_TRUECOL(base.bg)) {
colbg.alpha = 0xffff;
colbg.green = TRUEGREEN(base.bg);
colbg.red = TRUERED(base.bg);
colbg.blue = TRUEBLUE(base.bg);
XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);
bg = &truebg;
} else {
bg = &dc.col[base.bg];
}
2019-09-16 07:40:40 +00:00
#if !BOLD_IS_NOT_BRIGHT_PATCH
/* 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];
2019-09-16 07:40:40 +00:00
#endif // BOLD_IS_NOT_BRIGHT_PATCH
if (IS_SET(MODE_REVERSE)) {
if (fg == &dc.col[defaultfg]) {
fg = &dc.col[defaultbg];
} else {
colfg.red = ~fg->color.red;
colfg.green = ~fg->color.green;
colfg.blue = ~fg->color.blue;
colfg.alpha = fg->color.alpha;
XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg,
&revfg);
fg = &revfg;
}
if (bg == &dc.col[defaultbg]) {
bg = &dc.col[defaultfg];
} else {
colbg.red = ~bg->color.red;
colbg.green = ~bg->color.green;
colbg.blue = ~bg->color.blue;
colbg.alpha = bg->color.alpha;
XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg,
&revbg);
bg = &revbg;
}
}
if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
colfg.red = fg->color.red / 2;
colfg.green = fg->color.green / 2;
colfg.blue = fg->color.blue / 2;
colfg.alpha = fg->color.alpha;
XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);
fg = &revfg;
}
if (base.mode & ATTR_REVERSE) {
#if SPOILER_PATCH
if (bg == fg) {
bg = &dc.col[defaultfg];
fg = &dc.col[defaultbg];
} else {
temp = fg;
fg = bg;
bg = temp;
}
#else
temp = fg;
fg = bg;
bg = temp;
#endif // SPOILER_PATCH
}
#if SELECTION_COLORS_PATCH
if (base.mode & ATTR_SELECTED) {
bg = &dc.col[selectionbg];
if (!ignoreselfg)
fg = &dc.col[selectionfg];
}
#endif // SELECTION_COLORS_PATCH
if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
fg = bg;
if (base.mode & ATTR_INVISIBLE)
fg = bg;
2020-03-29 13:38:16 +00:00
#if INVERT_PATCH
if (invertcolors) {
revfg = invertedcolor(fg);
revbg = invertedcolor(bg);
fg = &revfg;
bg = &revbg;
}
#endif // INVERT_PATCH
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
if (base.mode & ATTR_HIGHLIGHT) {
fg = &dc.col[(base.mode & ATTR_REVERSE) ? highlightbg : highlightfg];
bg = &dc.col[(base.mode & ATTR_REVERSE) ? highlightfg : highlightbg];
}
#endif // KEYBOARDSELECT_PATCH
2021-02-15 13:37:37 +00:00
#if ALPHA_PATCH && ALPHA_GRADIENT_PATCH
// gradient
bg->color.alpha = grad_alpha * 0xffff * (win.h - y*win.ch) / win.h + stat_alpha * 0xffff;
// uncomment to invert the gradient
// bg->color.alpha = grad_alpha * 0xffff * (y*win.ch) / win.h + stat_alpha * 0xffff;
#endif // ALPHA_PATCH | ALPHA_GRADIENT_PATCH
2020-11-14 15:24:07 +00:00
#if WIDE_GLYPHS_PATCH
if (dmode & DRAW_BG) {
#endif // WIDE_GLYPHS_PATCH
/* Intelligent cleaning up of the borders. */
#if ANYSIZE_PATCH
if (x == 0) {
2022-07-16 20:35:27 +00:00
xclear(0, (y == 0)? 0 : winy, win.hborderpx,
winy + win.ch +
((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
}
if (winx + width >= win.hborderpx + win.tw) {
xclear(winx + width, (y == 0)? 0 : winy, win.w,
((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
}
if (y == 0)
2022-07-16 20:35:27 +00:00
xclear(winx, 0, winx + width, win.vborderpx);
if (winy + win.ch >= win.vborderpx + win.th)
xclear(winx, winy + win.ch, winx + width, win.h);
#else
if (x == 0) {
xclear(0, (y == 0)? 0 : winy, borderpx,
winy + win.ch +
((winy + win.ch >= borderpx + win.th)? win.h : 0));
}
if (winx + width >= borderpx + win.tw) {
xclear(winx + width, (y == 0)? 0 : winy, win.w,
((winy + win.ch >= borderpx + 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, winy + win.ch, winx + width, win.h);
#endif // ANYSIZE_PATCH
2020-11-14 15:24:07 +00:00
/* Clean up the region we want to draw to. */
2022-03-10 10:37:49 +00:00
#if BACKGROUND_IMAGE_PATCH
if (bg == &dc.col[defaultbg])
xclear(winx, winy, winx + width, winy + win.ch);
else
#endif // BACKGROUND_IMAGE_PATCH
#if !WIDE_GLYPHS_PATCH
XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
2020-11-14 15:24:07 +00:00
#endif // WIDE_GLYPHS_PATCH
/* Set the clip region because Xft is sometimes dirty. */
r.x = 0;
r.y = 0;
r.height = win.ch;
r.width = width;
XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
#if WIDE_GLYPHS_PATCH
2024-03-07 14:34:21 +00:00
/* Fill the background */
XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
}
2021-05-15 07:16:51 +00:00
#endif // WIDE_GLYPHS_PATCH
2020-11-14 15:24:07 +00:00
#if WIDE_GLYPHS_PATCH
if (dmode & DRAW_FG) {
#endif // WIDE_GLYPHS_PATCH
#if BOXDRAW_PATCH
if (base.mode & ATTR_BOXDRAW) {
drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
} else {
/* Render the glyphs. */
XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
}
#else
/* Render the glyphs. */
XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
#endif // BOXDRAW_PATCH
/* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE) {
2021-05-08 08:53:46 +00:00
#if UNDERCURL_PATCH
// Underline Color
const int widthThreshold = 28; // +1 width every widthThreshold px of font
int wlw = (win.ch / widthThreshold) + 1; // Wave Line Width
2021-05-08 08:53:46 +00:00
int linecolor;
if ((base.ucolor[0] >= 0) &&
!(base.mode & ATTR_BLINK && win.mode & MODE_BLINK) &&
!(base.mode & ATTR_INVISIBLE)
) {
// Special color for underline
// Index
if (base.ucolor[1] < 0) {
linecolor = dc.col[base.ucolor[0]].pixel;
}
// RGB
else {
XColor lcolor;
lcolor.red = base.ucolor[0] * 257;
lcolor.green = base.ucolor[1] * 257;
lcolor.blue = base.ucolor[2] * 257;
lcolor.flags = DoRed | DoGreen | DoBlue;
XAllocColor(xw.dpy, xw.cmap, &lcolor);
linecolor = lcolor.pixel;
}
} else {
// Foreground color for underline
linecolor = fg->pixel;
}
XGCValues ugcv = {
.foreground = linecolor,
.line_width = wlw,
.line_style = LineSolid,
.cap_style = CapNotLast
};
GC ugc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw),
GCForeground | GCLineWidth | GCLineStyle | GCCapStyle,
&ugcv);
// Underline Style
if (base.ustyle != 3) {
XFillRectangle(xw.dpy, XftDrawDrawable(xw.draw), ugc, winx,
winy + dc.font.ascent * chscale + 1, width, wlw);
2021-05-08 08:53:46 +00:00
} else if (base.ustyle == 3) {
int ww = win.cw;//width;
int wh = dc.font.descent - wlw/2 - 1;//r.height/7;
int wx = winx;
int wy = winy + win.ch - dc.font.descent;
#if VERTCENTER_PATCH
2021-05-09 15:48:28 +00:00
wy -= win.cyo;
2021-05-08 08:53:46 +00:00
#endif // VERTCENTER_PATCH
#if UNDERCURL_STYLE == UNDERCURL_CURLY
2021-05-08 08:53:46 +00:00
// Draw waves
int narcs = charlen * 2 + 1;
XArc *arcs = xmalloc(sizeof(XArc) * narcs);
int i = 0;
for (i = 0; i < charlen-1; i++) {
arcs[i*2] = (XArc) {
.x = wx + win.cw * i + ww / 4,
.y = wy,
.width = win.cw / 2,
.height = wh,
.angle1 = 0,
.angle2 = 180 * 64
};
arcs[i*2+1] = (XArc) {
.x = wx + win.cw * i + ww * 0.75,
.y = wy,
.width = win.cw/2,
.height = wh,
.angle1 = 180 * 64,
.angle2 = 180 * 64
};
}
// Last wave
arcs[i*2] = (XArc) {wx + ww * i + ww / 4, wy, ww / 2, wh,
0, 180 * 64 };
// Last wave tail
arcs[i*2+1] = (XArc) {wx + ww * i + ww * 0.75, wy, ceil(ww / 2.),
wh, 180 * 64, 90 * 64};
// First wave tail
i++;
arcs[i*2] = (XArc) {wx - ww/4 - 1, wy, ceil(ww / 2.), wh, 270 * 64,
90 * 64 };
XDrawArcs(xw.dpy, XftDrawDrawable(xw.draw), ugc, arcs, narcs);
free(arcs);
#elif UNDERCURL_STYLE == UNDERCURL_SPIKY
// Make the underline corridor larger
/*
wy -= wh;
*/
wh *= 2;
// Set the angle of the slope to 45°
ww = wh;
// Position of wave is independent of word, it's absolute
wx = (wx / (ww/2)) * (ww/2);
int marginStart = winx - wx;
// Calculate number of points with floating precision
float n = width; // Width of word in pixels
n = (n / ww) * 2; // Number of slopes (/ or \)
n += 2; // Add two last points
int npoints = n; // Convert to int
// Total length of underline
float waveLength = 0;
if (npoints >= 3) {
// We add an aditional slot in case we use a bonus point
XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
// First point (Starts with the word bounds)
points[0] = (XPoint) {
.x = wx + marginStart,
.y = (isSlopeRising(wx, 0, ww))
? (wy - marginStart + ww/2.f)
: (wy + marginStart)
};
// Second point (Goes back to the absolute point coordinates)
points[1] = (XPoint) {
.x = (ww/2.f) - marginStart,
.y = (isSlopeRising(wx, 1, ww))
? (ww/2.f - marginStart)
: (-ww/2.f + marginStart)
};
waveLength += (ww/2.f) - marginStart;
// The rest of the points
for (int i = 2; i < npoints-1; i++) {
points[i] = (XPoint) {
.x = ww/2,
.y = (isSlopeRising(wx, i, ww))
? wh/2
: -wh/2
};
waveLength += ww/2;
}
// Last point
points[npoints-1] = (XPoint) {
.x = ww/2,
.y = (isSlopeRising(wx, npoints-1, ww))
? wh/2
: -wh/2
};
waveLength += ww/2;
// End
if (waveLength < width) { // Add a bonus point?
int marginEnd = width - waveLength;
points[npoints] = (XPoint) {
.x = marginEnd,
.y = (isSlopeRising(wx, npoints, ww))
? (marginEnd)
: (-marginEnd)
};
npoints++;
} else if (waveLength > width) { // Is last point too far?
int marginEnd = waveLength - width;
points[npoints-1].x -= marginEnd;
if (isSlopeRising(wx, npoints-1, ww))
points[npoints-1].y -= (marginEnd);
else
points[npoints-1].y += (marginEnd);
}
// Draw the lines
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
CoordModePrevious);
// Draw a second underline with an offset of 1 pixel
if ( ((win.ch / (widthThreshold/2)) % 2)) {
points[0].x++;
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
npoints, CoordModePrevious);
}
// Free resources
free(points);
}
#else // UNDERCURL_CAPPED
// Cap is half of wave width
float capRatio = 0.5f;
// Make the underline corridor larger
wh *= 2;
// Set the angle of the slope to 45°
ww = wh;
ww *= 1 + capRatio; // Add a bit of width for the cap
// Position of wave is independent of word, it's absolute
wx = (wx / ww) * ww;
float marginStart;
switch(getSlope(winx, 0, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
marginStart = winx - wx;
break;
case UNDERCURL_SLOPE_TOP_CAP:
marginStart = winx - (wx + (ww * (2.f/6.f)));
break;
case UNDERCURL_SLOPE_DESCENDING:
marginStart = winx - (wx + (ww * (3.f/6.f)));
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
marginStart = winx - (wx + (ww * (5.f/6.f)));
break;
}
// Calculate number of points with floating precision
float n = width; // Width of word in pixels
// ._.
n = (n / ww) * 4; // Number of points (./ \.)
n += 2; // Add two last points
int npoints = n; // Convert to int
// Position of the pen to draw the lines
float penX = 0;
float penY = 0;
if (npoints >= 3) {
XPoint *points = xmalloc(sizeof(XPoint) * (npoints + 1));
// First point (Starts with the word bounds)
penX = winx;
switch (getSlope(winx, 0, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
penY = wy + wh/2.f - marginStart;
break;
case UNDERCURL_SLOPE_TOP_CAP:
penY = wy;
break;
case UNDERCURL_SLOPE_DESCENDING:
penY = wy + marginStart;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
penY = wy + wh/2.f;
break;
}
points[0].x = penX;
points[0].y = penY;
// Second point (Goes back to the absolute point coordinates)
switch (getSlope(winx, 1, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
penX += ww * (1.f/6.f) - marginStart;
penY += 0;
break;
case UNDERCURL_SLOPE_TOP_CAP:
penX += ww * (2.f/6.f) - marginStart;
penY += -wh/2.f + marginStart;
break;
case UNDERCURL_SLOPE_DESCENDING:
penX += ww * (1.f/6.f) - marginStart;
penY += 0;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
penX += ww * (2.f/6.f) - marginStart;
penY += -marginStart + wh/2.f;
break;
}
points[1].x = penX;
points[1].y = penY;
// The rest of the points
for (int i = 2; i < npoints; i++) {
switch (getSlope(winx, i, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
case UNDERCURL_SLOPE_DESCENDING:
penX += ww * (1.f/6.f);
penY += 0;
break;
case UNDERCURL_SLOPE_TOP_CAP:
penX += ww * (2.f/6.f);
penY += -wh / 2.f;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
penX += ww * (2.f/6.f);
penY += wh / 2.f;
break;
}
points[i].x = penX;
points[i].y = penY;
}
// End
float waveLength = penX - winx;
if (waveLength < width) { // Add a bonus point?
int marginEnd = width - waveLength;
penX += marginEnd;
switch(getSlope(winx, npoints, ww)) {
case UNDERCURL_SLOPE_ASCENDING:
case UNDERCURL_SLOPE_DESCENDING:
//penY += 0;
break;
case UNDERCURL_SLOPE_TOP_CAP:
penY += -marginEnd;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
penY += marginEnd;
break;
}
points[npoints].x = penX;
points[npoints].y = penY;
npoints++;
} else if (waveLength > width) { // Is last point too far?
int marginEnd = waveLength - width;
points[npoints-1].x -= marginEnd;
switch(getSlope(winx, npoints-1, ww)) {
case UNDERCURL_SLOPE_TOP_CAP:
points[npoints-1].y += marginEnd;
break;
case UNDERCURL_SLOPE_BOTTOM_CAP:
points[npoints-1].y -= marginEnd;
break;
default:
break;
}
}
// Draw the lines
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points, npoints,
CoordModeOrigin);
// Draw a second underline with an offset of 1 pixel
if ( ((win.ch / (widthThreshold/2)) % 2)) {
for (int i = 0; i < npoints; i++)
points[i].x++;
XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), ugc, points,
npoints, CoordModeOrigin);
}
// Free resources
free(points);
}
#endif
2021-05-08 08:53:46 +00:00
}
XFreeGC(xw.dpy, ugc);
#elif VERTCENTER_PATCH
XftDrawRect(xw.draw, fg, winx, winy + win.cyo + dc.font.ascent * chscale + 1,
2019-09-16 13:31:58 +00:00
width, 1);
#else
XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
width, 1);
2021-05-15 07:16:51 +00:00
#endif // UNDERCURL_PATCH | VERTCENTER_PATCH
}
if (base.mode & ATTR_STRUCK) {
2019-09-16 13:31:58 +00:00
#if VERTCENTER_PATCH
XftDrawRect(xw.draw, fg, winx, winy + win.cyo + 2 * dc.font.ascent * chscale / 3,
2019-09-16 13:31:58 +00:00
width, 1);
#else
XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
width, 1);
2019-09-16 13:31:58 +00:00
#endif // VERTCENTER_PATCH
}
2020-11-14 15:24:07 +00:00
#if WIDE_GLYPHS_PATCH
}
#endif // WIDE_GLYPHS_PATCH
#if OPENURLONCLICK_PATCH
2024-03-07 14:34:21 +00:00
/* underline url (openurlonclick patch) */
if (url_draw && y >= url_y1 && y <= url_y2) {
int x1 = (y == url_y1) ? url_x1 : 0;
int x2 = (y == url_y2) ? MIN(url_x2, term.col-1) : url_maxcol;
if (x + charlen > x1 && x <= x2) {
int xu = MAX(x, x1);
int wu = (x2 - xu + 1) * win.cw;
#if ANYSIZE_PATCH
xu = win.hborderpx + xu * win.cw;
#else
xu = borderpx + xu * win.cw;
#endif // ANYSIZE_PATCH
#if VERTCENTER_PATCH
XftDrawRect(xw.draw, fg, xu, winy + win.cyo + dc.font.ascent * chscale + 2, wu, 1);
#else
XftDrawRect(xw.draw, fg, xu, winy + dc.font.ascent * chscale + 2, wu, 1);
#endif // VERTCENTER_PATCH
url_draw = (y != url_y2 || x + charlen <= x2);
}
}
#endif // OPENURLONCLICK_PATCH
/* Reset clip to none. */
XftDrawSetClip(xw.draw, 0);
}
void
xdrawglyph(Glyph g, int x, int y)
{
int numspecs;
XftGlyphFontSpec *specs = xw.specbuf;
numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
xdrawglyphfontspecs(specs, g, numspecs, x, y
#if WIDE_GLYPHS_PATCH
,DRAW_BG | DRAW_FG
#endif // WIDE_GLYPHS_PATCH
#if LIGATURES_PATCH
,(g.mode & ATTR_WIDE) ? 2 : 1
#endif // LIGATURES_PATCH
);
}
void
#if LIGATURES_PATCH
xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len)
#else
xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)
#endif // LIGATURES_PATCH
{
Color drawcol;
#if DYNAMIC_CURSOR_COLOR_PATCH
XRenderColor colbg;
#endif // DYNAMIC_CURSOR_COLOR_PATCH
2024-03-07 22:40:50 +00:00
#if !DYNAMIC_CURSOR_COLOR_PATCH
/* remove the old cursor */
if (selected(ox, oy))
#if SELECTION_COLORS_PATCH
og.mode |= ATTR_SELECTED;
#else
og.mode ^= ATTR_REVERSE;
#endif // SELECTION_COLORS_PATCH
2024-03-07 22:40:50 +00:00
#endif // DYNAMIC_CURSOR_COLOR_PATCH
2024-03-07 14:34:21 +00:00
#if LIGATURES_PATCH
/* Redraw the line where cursor was previously.
* It will restore the ligatures broken by the cursor. */
xdrawline(line, 0, oy, len);
#else
xdrawglyph(og, ox, oy);
#endif // LIGATURES_PATCH
2021-06-09 07:54:50 +00:00
#if HIDE_TERMINAL_CURSOR_PATCH
if (IS_SET(MODE_HIDE) || !IS_SET(MODE_FOCUSED))
return;
#else
if (IS_SET(MODE_HIDE))
return;
2021-06-09 07:54:50 +00:00
#endif // HIDE_TERMINAL_CURSOR_PATCH
/*
* Select the right color for the right mode.
*/
2024-03-07 22:40:50 +00:00
g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE
#if BOXDRAW_PATCH
2024-03-07 22:40:50 +00:00
|ATTR_BOXDRAW
#endif // BOXDRAW_PATCH
2024-03-07 22:40:50 +00:00
#if DYNAMIC_CURSOR_COLOR_PATCH
|ATTR_REVERSE
#endif // DYNAMIC_CURSOR_COLOR_PATCH
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
|ATTR_HIGHLIGHT
#endif // KEYBOARDSELECT_PATCH
2024-03-07 22:40:50 +00:00
;
if (IS_SET(MODE_REVERSE)) {
g.mode |= ATTR_REVERSE;
g.bg = defaultfg;
#if SELECTION_COLORS_PATCH
g.fg = defaultcs;
drawcol = dc.col[defaultrcs];
#else
if (selected(cx, cy)) {
drawcol = dc.col[defaultcs];
g.fg = defaultrcs;
} else {
drawcol = dc.col[defaultrcs];
g.fg = defaultcs;
}
#endif // SELECTION_COLORS_PATCH
} else {
#if SELECTION_COLORS_PATCH
g.fg = defaultbg;
g.bg = defaultcs;
drawcol = dc.col[defaultcs];
#else
if (selected(cx, cy)) {
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
g.mode &= ~(ATTR_REVERSE | ATTR_HIGHLIGHT);
#elif DYNAMIC_CURSOR_COLOR_PATCH
2024-03-07 22:40:50 +00:00
g.mode &= ~ATTR_REVERSE;
#endif // DYNAMIC_CURSOR_COLOR_PATCH
g.fg = defaultfg;
g.bg = defaultrcs;
2024-03-07 22:40:50 +00:00
} else {
#if DYNAMIC_CURSOR_COLOR_PATCH
unsigned int tmpcol = g.bg;
g.bg = g.fg;
g.fg = tmpcol;
2024-03-07 22:40:50 +00:00
#else
g.fg = defaultbg;
g.bg = defaultcs;
#endif // DYNAMIC_CURSOR_COLOR_PATCH
}
2024-03-07 22:40:50 +00:00
#if DYNAMIC_CURSOR_COLOR_PATCH
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];
2024-03-07 22:40:50 +00:00
#else
drawcol = dc.col[g.bg];
#endif // DYNAMIC_CURSOR_COLOR_PATCH
#endif // SELECTION_COLORS_PATCH
}
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
if (g.mode & ATTR_HIGHLIGHT)
g.mode ^= ATTR_REVERSE;
#endif // KEYBOARDSELECT_PATCH
/* draw the new one */
if (IS_SET(MODE_FOCUSED)) {
switch (win.cursor) {
2021-05-08 08:13:20 +00:00
#if !BLINKING_CURSOR_PATCH
case 7: /* st extension */
g.u = 0x2603; /* snowman (U+2603) */
/* FALLTHROUGH */
2021-05-08 08:13:20 +00:00
#endif // BLINKING_CURSOR_PATCH
case 0: /* Blinking block */
case 1: /* Blinking block (default) */
#if BLINKING_CURSOR_PATCH
if (IS_SET(MODE_BLINK))
break;
/* FALLTHROUGH */
#endif // BLINKING_CURSOR_PATCH
case 2: /* Steady block */
xdrawglyph(g, cx, cy);
break;
2021-05-08 08:13:20 +00:00
case 3: /* Blinking underline */
#if BLINKING_CURSOR_PATCH
if (IS_SET(MODE_BLINK))
break;
/* FALLTHROUGH */
#endif // BLINKING_CURSOR_PATCH
case 4: /* Steady underline */
#if ANYSIZE_PATCH
XftDrawRect(xw.draw, &drawcol,
win.hborderpx + cx * win.cw,
win.vborderpx + (cy + 1) * win.ch - \
cursorthickness,
win.cw, cursorthickness);
#else
XftDrawRect(xw.draw, &drawcol,
borderpx + cx * win.cw,
borderpx + (cy + 1) * win.ch - \
cursorthickness,
win.cw, cursorthickness);
#endif // ANYSIZE_PATCH
break;
case 5: /* Blinking bar */
2021-05-08 08:13:20 +00:00
#if BLINKING_CURSOR_PATCH
if (IS_SET(MODE_BLINK))
break;
/* FALLTHROUGH */
#endif // BLINKING_CURSOR_PATCH
case 6: /* Steady bar */
XftDrawRect(xw.draw, &drawcol,
#if ANYSIZE_PATCH
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
#else
borderpx + cx * win.cw,
borderpx + cy * win.ch,
#endif // ANYSIZE_PATCH
cursorthickness, win.ch);
break;
2021-05-08 08:13:20 +00:00
#if BLINKING_CURSOR_PATCH
case 7: /* Blinking st cursor */
if (IS_SET(MODE_BLINK))
break;
/* FALLTHROUGH */
case 8: /* Steady st cursor */
g.u = stcursor;
xdrawglyph(g, cx, cy);
break;
#endif // BLINKING_CURSOR_PATCH
}
} else {
XftDrawRect(xw.draw, &drawcol,
#if ANYSIZE_PATCH
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
#else
borderpx + cx * win.cw,
borderpx + cy * win.ch,
#endif // ANYSIZE_PATCH
win.cw - 1, 1);
XftDrawRect(xw.draw, &drawcol,
#if ANYSIZE_PATCH
win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch,
#else
borderpx + cx * win.cw,
borderpx + cy * win.ch,
#endif // ANYSIZE_PATCH
1, win.ch - 1);
XftDrawRect(xw.draw, &drawcol,
#if ANYSIZE_PATCH
win.hborderpx + (cx + 1) * win.cw - 1,
win.vborderpx + cy * win.ch,
#else
borderpx + (cx + 1) * win.cw - 1,
borderpx + cy * win.ch,
#endif // ANYSIZE_PATCH
1, win.ch - 1);
XftDrawRect(xw.draw, &drawcol,
#if ANYSIZE_PATCH
win.hborderpx + cx * win.cw,
win.vborderpx + (cy + 1) * win.ch - 1,
#else
borderpx + cx * win.cw,
borderpx + (cy + 1) * win.ch - 1,
#endif // ANYSIZE_PATCH
win.cw, 1);
}
}
void
xsetenv(void)
{
char buf[sizeof(long) * 8 + 1];
snprintf(buf, sizeof(buf), "%lu", xw.win);
setenv("WINDOWID", buf, 1);
}
void
xseticontitle(char *p)
{
XTextProperty prop;
DEFAULT(p, opt_title);
if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
&prop) != Success)
return;
XSetWMIconName(xw.dpy, xw.win, &prop);
XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname);
XFree(prop.value);
}
2021-08-18 08:07:42 +00:00
#if CSI_22_23_PATCH
void
xsettitle(char *p, int pop)
{
XTextProperty prop;
free(titlestack[tstki]);
if (pop) {
titlestack[tstki] = NULL;
tstki = (tstki - 1 + TITLESTACKSIZE) % TITLESTACKSIZE;
p = titlestack[tstki] ? titlestack[tstki] : opt_title;
} else if (p) {
titlestack[tstki] = xstrdup(p);
} else {
titlestack[tstki] = NULL;
p = opt_title;
}
if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
&prop) != Success)
return;
2021-08-18 08:07:42 +00:00
XSetWMName(xw.dpy, xw.win, &prop);
XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
XFree(prop.value);
}
void
xpushtitle(void)
{
int tstkin = (tstki + 1) % TITLESTACKSIZE;
free(titlestack[tstkin]);
titlestack[tstkin] = titlestack[tstki] ? xstrdup(titlestack[tstki]) : NULL;
tstki = tstkin;
}
void
xfreetitlestack(void)
{
for (int i = 0; i < LEN(titlestack); i++) {
free(titlestack[i]);
titlestack[i] = NULL;
}
}
#else
void
xsettitle(char *p)
{
XTextProperty prop;
DEFAULT(p, opt_title);
if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
&prop) != Success)
return;
XSetWMName(xw.dpy, xw.win, &prop);
XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
XFree(prop.value);
}
2021-08-18 08:07:42 +00:00
#endif // CSI_22_23_PATCH
int
xstartdraw(void)
{
2020-06-14 18:04:27 +00:00
#if W3M_PATCH
if (IS_SET(MODE_VISIBLE))
XCopyArea(xw.dpy, xw.win, xw.buf, dc.gc, 0, 0, win.w, win.h, 0, 0);
#endif // W3M_PATCH
return IS_SET(MODE_VISIBLE);
}
#if LIGATURES_PATCH && WIDE_GLYPHS_PATCH
2024-03-07 14:34:21 +00:00
void
xdrawline(Line line, int x1, int y1, int x2)
{
int i, j, x, ox, numspecs;
Glyph new;
GlyphFontSeq *seq = xw.specseq;
XftGlyphFontSpec *specs = xw.specbuf;
/* Draw line in 2 passes: background and foreground. This way wide glyphs
won't get truncated (#223) */
/* background */
i = j = ox = 0;
for (x = x1; x < x2; x++) {
new = line[x];
if (new.mode == ATTR_WDUMMY)
continue;
if (selected(x, y1))
#if SELECTION_COLORS_PATCH
new.mode |= ATTR_SELECTED;
#else
new.mode ^= ATTR_REVERSE;
#endif // SELECTION_COLORS_PATCH
if ((i > 0) && ATTRCMP(seq[j].base, new)) {
numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
xdrawglyphfontspecs(specs, seq[j].base, numspecs, ox, y1, DRAW_BG, x - ox);
seq[j].charlen = x - ox;
seq[j++].numspecs = numspecs;
specs += numspecs;
i = 0;
}
if (i == 0) {
ox = x;
seq[j].ox= ox;
seq[j].base = new;
}
i++;
}
if (i > 0) {
numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
xdrawglyphfontspecs(specs, seq[j].base, numspecs, ox, y1, DRAW_BG, x2 - ox);
seq[j].charlen = x2 - ox;
seq[j++].numspecs = numspecs;
}
/* foreground */
specs = xw.specbuf;
for (i = 0; i < j; i++) {
xdrawglyphfontspecs(specs, seq[i].base, seq[i].numspecs, seq[i].ox, y1, DRAW_FG, seq[i].charlen);
specs += seq[i].numspecs;
}
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
kbds_drawstatusbar(y1);
#endif // KEYBOARDSELECT_PATCH
2024-03-07 14:34:21 +00:00
}
#elif LIGATURES_PATCH
void
xdrawline(Line line, int x1, int y1, int x2)
{
int i, x, ox, numspecs;
Glyph base, new;
2024-03-07 14:34:21 +00:00
XftGlyphFontSpec *specs = xw.specbuf;
2024-03-07 14:34:21 +00:00
i = ox = 0;
for (x = x1; x < x2; x++) {
new = line[x];
if (new.mode == ATTR_WDUMMY)
continue;
if (selected(x, y1))
#if SELECTION_COLORS_PATCH
new.mode |= ATTR_SELECTED;
#else
new.mode ^= ATTR_REVERSE;
#endif // SELECTION_COLORS_PATCH
if ((i > 0) && ATTRCMP(base, new)) {
numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
2024-03-07 14:34:21 +00:00
i = 0;
}
if (i == 0) {
ox = x;
base = new;
}
i++;
}
if (i > 0) {
numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
2024-03-07 14:34:21 +00:00
}
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
kbds_drawstatusbar(y1);
#endif // KEYBOARDSELECT_PATCH
2024-03-07 14:34:21 +00:00
}
#elif WIDE_GLYPHS_PATCH
void
xdrawline(Line line, int x1, int y1, int x2)
{
int i, x, ox, numspecs, numspecs_cached;
Glyph base, new;
2020-11-14 15:24:07 +00:00
XftGlyphFontSpec *specs;
numspecs_cached = xmakeglyphfontspecs(xw.specbuf, &line[x1], x2 - x1, x1, y1);
/* Draw line in 2 passes: background and foreground. This way wide glyphs
won't get truncated (#223) */
for (int dmode = DRAW_BG; dmode <= DRAW_FG; dmode <<= 1) {
specs = xw.specbuf;
numspecs = numspecs_cached;
i = ox = 0;
for (x = x1; x < x2 && i < numspecs; x++) {
new = line[x];
if (new.mode == ATTR_WDUMMY)
continue;
if (selected(x, y1))
#if SELECTION_COLORS_PATCH
new.mode |= ATTR_SELECTED;
#else
2020-11-14 15:24:07 +00:00
new.mode ^= ATTR_REVERSE;
#endif // SELECTION_COLORS_PATCH
2020-11-14 15:24:07 +00:00
if (i > 0 && ATTRCMP(base, new)) {
xdrawglyphfontspecs(specs, base, i, ox, y1, dmode);
2020-11-14 15:24:07 +00:00
specs += i;
numspecs -= i;
i = 0;
}
if (i == 0) {
ox = x;
base = new;
}
i++;
}
if (i > 0)
xdrawglyphfontspecs(specs, base, i, ox, y1, dmode);
2020-11-14 15:24:07 +00:00
}
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
kbds_drawstatusbar(y1);
#endif // KEYBOARDSELECT_PATCH
2024-03-07 14:34:21 +00:00
}
#else // !WIDE_GLYPHS_PATCH and !LIGATURES_PATCH
void
xdrawline(Line line, int x1, int y1, int x2)
{
int i, x, ox, numspecs;
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++) {
new = line[x];
if (new.mode == ATTR_WDUMMY)
continue;
if (selected(x, y1))
#if SELECTION_COLORS_PATCH
new.mode |= ATTR_SELECTED;
#else
new.mode ^= ATTR_REVERSE;
#endif // SELECTION_COLORS_PATCH
if (i > 0 && ATTRCMP(base, new)) {
xdrawglyphfontspecs(specs, base, i, ox, y1);
specs += i;
numspecs -= i;
i = 0;
}
if (i == 0) {
ox = x;
base = new;
}
i++;
}
if (i > 0)
xdrawglyphfontspecs(specs, base, i, ox, y1);
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
kbds_drawstatusbar(y1);
#endif // KEYBOARDSELECT_PATCH
}
2024-03-07 14:34:21 +00:00
#endif // WIDE_GLYPHS_PATCH | LIGATURES_PATCH
void
xfinishdraw(void)
{
2021-03-10 17:09:47 +00:00
#if SIXEL_PATCH
Fix sixel issues and add a clearing sequence (#99) This patch fixes the following sixel issues: - The current sixel implementation cleared all cells from the left side of the image when the image was drawn. The fix only clears the cells where the image will be drawn. - The deletion routine didn't work correctly. In certain situations, it left the image or images undrawn. For example, if the first image was marked for deletion, it didn't draw the second one. - The drawing routine caused a high cpu usage, because XCopyArea() triggered the X server to send the NoExpose event, which caused sixels to be redrawn and the X server to send another NoExpose event and so on. This loop caused constant redraw of sixels and high cpu usage. The fix prevents the X server from sending GraphicsExpose and NoExpose events. The patch also adds a control sequence for removing sixels: Because the sixels are implemented as overlay images, they cannot be removed by clearing the underlaying cells. Therefore, we need a control sequence to remove them. I opted to choose ESC[6J as the control sequence because it is not used and the number refers to sixels. So when the lf file manager supports sixels [1], you can use the following minimal scripts to preview images in lf: previewer: #!/bin/sh case "$(readlink -f "$1")" in *.bmp|*.gif|*.jpg|*.jpeg|*.png|*.webp|*.six|*.svg|*.xpm) chafa -s "$(($2-3))x$3" -f sixels "$1" exit 1 ;; *) bat "$1" ;; esac cleaner: #!/bin/sh printf "\033[6J" >/dev/tty [1] https://github.com/gokcehan/lf/pull/1211
2023-06-12 14:02:19 +00:00
ImageList *im, *next;
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
Imlib_Image origin, scaled;
2021-03-10 17:09:47 +00:00
XGCValues gcvalues;
GC gc;
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
int width, height;
int x, x2, del;
Line line;
2021-03-10 17:09:47 +00:00
#endif // SIXEL_PATCH
#if SIXEL_PATCH
Fix sixel issues and add a clearing sequence (#99) This patch fixes the following sixel issues: - The current sixel implementation cleared all cells from the left side of the image when the image was drawn. The fix only clears the cells where the image will be drawn. - The deletion routine didn't work correctly. In certain situations, it left the image or images undrawn. For example, if the first image was marked for deletion, it didn't draw the second one. - The drawing routine caused a high cpu usage, because XCopyArea() triggered the X server to send the NoExpose event, which caused sixels to be redrawn and the X server to send another NoExpose event and so on. This loop caused constant redraw of sixels and high cpu usage. The fix prevents the X server from sending GraphicsExpose and NoExpose events. The patch also adds a control sequence for removing sixels: Because the sixels are implemented as overlay images, they cannot be removed by clearing the underlaying cells. Therefore, we need a control sequence to remove them. I opted to choose ESC[6J as the control sequence because it is not used and the number refers to sixels. So when the lf file manager supports sixels [1], you can use the following minimal scripts to preview images in lf: previewer: #!/bin/sh case "$(readlink -f "$1")" in *.bmp|*.gif|*.jpg|*.jpeg|*.png|*.webp|*.six|*.svg|*.xpm) chafa -s "$(($2-3))x$3" -f sixels "$1" exit 1 ;; *) bat "$1" ;; esac cleaner: #!/bin/sh printf "\033[6J" >/dev/tty [1] https://github.com/gokcehan/lf/pull/1211
2023-06-12 14:02:19 +00:00
for (im = term.images; im; im = next) {
next = im->next;
2021-03-10 17:09:47 +00:00
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
/* do not draw or process the image, if it is not visible */
if (im->x >= term.col || im->y >= term.row || im->y < 0)
Fix sixel issues and add a clearing sequence (#99) This patch fixes the following sixel issues: - The current sixel implementation cleared all cells from the left side of the image when the image was drawn. The fix only clears the cells where the image will be drawn. - The deletion routine didn't work correctly. In certain situations, it left the image or images undrawn. For example, if the first image was marked for deletion, it didn't draw the second one. - The drawing routine caused a high cpu usage, because XCopyArea() triggered the X server to send the NoExpose event, which caused sixels to be redrawn and the X server to send another NoExpose event and so on. This loop caused constant redraw of sixels and high cpu usage. The fix prevents the X server from sending GraphicsExpose and NoExpose events. The patch also adds a control sequence for removing sixels: Because the sixels are implemented as overlay images, they cannot be removed by clearing the underlaying cells. Therefore, we need a control sequence to remove them. I opted to choose ESC[6J as the control sequence because it is not used and the number refers to sixels. So when the lf file manager supports sixels [1], you can use the following minimal scripts to preview images in lf: previewer: #!/bin/sh case "$(readlink -f "$1")" in *.bmp|*.gif|*.jpg|*.jpeg|*.png|*.webp|*.six|*.svg|*.xpm) chafa -s "$(($2-3))x$3" -f sixels "$1" exit 1 ;; *) bat "$1" ;; esac cleaner: #!/bin/sh printf "\033[6J" >/dev/tty [1] https://github.com/gokcehan/lf/pull/1211
2023-06-12 14:02:19 +00:00
continue;
2021-03-10 17:09:47 +00:00
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
/* scale the image */
width = im->width * win.cw / im->cw;
height = im->height * win.ch / im->ch;
2021-03-10 17:09:47 +00:00
if (!im->pixmap) {
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
im->pixmap = (void *)XCreatePixmap(xw.dpy, xw.win, width, height,
2021-03-10 17:09:47 +00:00
#if ALPHA_PATCH
xw.depth
#else
DefaultDepth(xw.dpy, xw.scr)
#endif // ALPHA_PATCH
);
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
if (win.cw == im->cw && win.ch == im->ch) {
XImage ximage = {
.format = ZPixmap,
.data = (char *)im->pixels,
.width = im->width,
.height = im->height,
.xoffset = 0,
.byte_order = sixelbyteorder,
.bitmap_bit_order = MSBFirst,
.bits_per_pixel = 32,
.bytes_per_line = im->width * 4,
.bitmap_unit = 32,
.bitmap_pad = 32,
#if ALPHA_PATCH
.depth = xw.depth
#else
.depth = 24
#endif // ALPHA_PATCH
};
XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height);
} else {
origin = imlib_create_image_using_data(im->width, im->height, (DATA32 *)im->pixels);
if (!origin)
continue;
imlib_context_set_image(origin);
imlib_image_set_has_alpha(1);
scaled = imlib_create_cropped_scaled_image(0, 0, im->width, im->height, width, height);
imlib_free_image_and_decache();
if (!scaled)
continue;
imlib_context_set_image(scaled);
imlib_image_set_has_alpha(1);
XImage ximage = {
.format = ZPixmap,
.data = (char *)imlib_image_get_data_for_reading_only(),
.width = width,
.height = height,
.xoffset = 0,
.byte_order = sixelbyteorder,
.bitmap_bit_order = MSBFirst,
.bits_per_pixel = 32,
.bytes_per_line = width * 4,
.bitmap_unit = 32,
.bitmap_pad = 32,
#if ALPHA_PATCH
.depth = xw.depth
#else
.depth = 24
#endif // ALPHA_PATCH
};
XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height);
imlib_free_image_and_decache();
}
}
/* clip the image so it does not go over to borders */
x2 = MIN(im->x + im->cols, term.col);
width = MIN(width, (x2 - im->x) * win.cw);
/* delete the image if the text cells behind it have been changed */
2024-03-13 09:33:51 +00:00
#if SCROLLBACK_PATCH || REFLOW_PATCH
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
line = TLINE(im->y);
#else
line = term.line[im->y];
2024-03-13 09:33:51 +00:00
#endif // SCROLLBACK_PATCH | REFLOW_PATCH
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
for (del = 0, x = im->x; x < x2; x++) {
if ((del = !(line[x].mode & ATTR_SIXEL)))
break;
}
if (del) {
delete_image(im);
continue;
2021-03-10 17:09:47 +00:00
}
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
/* draw the image */
2021-03-10 17:09:47 +00:00
memset(&gcvalues, 0, sizeof(gcvalues));
Fix sixel issues and add a clearing sequence (#99) This patch fixes the following sixel issues: - The current sixel implementation cleared all cells from the left side of the image when the image was drawn. The fix only clears the cells where the image will be drawn. - The deletion routine didn't work correctly. In certain situations, it left the image or images undrawn. For example, if the first image was marked for deletion, it didn't draw the second one. - The drawing routine caused a high cpu usage, because XCopyArea() triggered the X server to send the NoExpose event, which caused sixels to be redrawn and the X server to send another NoExpose event and so on. This loop caused constant redraw of sixels and high cpu usage. The fix prevents the X server from sending GraphicsExpose and NoExpose events. The patch also adds a control sequence for removing sixels: Because the sixels are implemented as overlay images, they cannot be removed by clearing the underlaying cells. Therefore, we need a control sequence to remove them. I opted to choose ESC[6J as the control sequence because it is not used and the number refers to sixels. So when the lf file manager supports sixels [1], you can use the following minimal scripts to preview images in lf: previewer: #!/bin/sh case "$(readlink -f "$1")" in *.bmp|*.gif|*.jpg|*.jpeg|*.png|*.webp|*.six|*.svg|*.xpm) chafa -s "$(($2-3))x$3" -f sixels "$1" exit 1 ;; *) bat "$1" ;; esac cleaner: #!/bin/sh printf "\033[6J" >/dev/tty [1] https://github.com/gokcehan/lf/pull/1211
2023-06-12 14:02:19 +00:00
gcvalues.graphics_exposures = False;
gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues);
#if ANYSIZE_PATCH
XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0,
width, height, win.hborderpx + im->x * win.cw, win.vborderpx + im->y * win.ch);
#else
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0,
2024-03-07 14:34:21 +00:00
width, height, borderpx + im->x * win.cw, borderpx + im->y * win.ch);
#endif // ANYSIZE_PATCH
2021-03-10 17:09:47 +00:00
XFreeGC(xw.dpy, gc);
}
#endif // SIXEL_PATCH
#if !SINGLE_DRAWABLE_BUFFER_PATCH
XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, win.h, 0, 0);
#endif // SINGLE_DRAWABLE_BUFFER_PATCH
Reworking sixel implementation based on veltza's implementation (#117) * sixel: remove black bars from sixel images When the images don't fully cover the text cells, black bars are added to them. This fix removes those bars, but if you need the old behavior, you can restore it by setting 'sixelremovebars' to zero in config.h * sixel: fix a potential memory leak * sixel: improve behavior with text reflow * sixel: prevent animated gifs from choking the terminal Animated gifs constantly spawn new images that eventually choke the terminal because the old animation frames are kept in the image buffer. This fix removes overlapping images from the image buffer and prevents them from piling up. * sixel: add zooming and clipping * sixel: copying bulk of changes * sixel: move sixel_parser_parse() and add missing sequences and blocks (#113) - Move sixel_parser_parse() from tputc() to twrite() - Add missing 8452, DECSDM, XTSMGRAPHICS and XTWINOPS sequences - Add more conditional blocks for the scrollback and sync patches - Remove unused reflow_y from ImageList. It is only used for the scrollback-reflow patch in st-sx. * sixel: update vtiden to VT200 family * sixel: fix scrolling issues inside tmux (#114) tmux is using the scrolling region and sequence to clear the screen below the shell prompt. This peculiar behavior caused the tscrollup() function to be called, which always scrolled the images regardless of whether they were inside the region or not. So the images moved out of place whenever the bottom of the screen was cleared. This fix checks that the images are inside the region before scrolling them. * sixel: prevent images from being deleted when resizing (#115) This fixes resizing issues outside of tmux not inside. * Rewriting tresize logic based on veltza's proposed implementation in PR #115 * tresize: correction for tscrollup call when scrollback patch is used --------- Co-authored-by: veltza <106755522+veltza@users.noreply.github.com>
2024-03-07 08:22:44 +00:00
XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel);
}
void
xximspot(int x, int y)
{
if (xw.ime.xic == NULL)
return;
xw.ime.spot.x = borderpx + x * win.cw;
xw.ime.spot.y = borderpx + (y + 1) * win.ch;
XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL);
}
void
expose(XEvent *ev)
{
redraw();
}
void
visibility(XEvent *ev)
{
XVisibilityEvent *e = &ev->xvisibility;
MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE);
}
void
unmap(XEvent *ev)
{
2020-01-07 07:05:00 +00:00
#if ST_EMBEDDER_PATCH
if (embed == ev->xunmap.window) {
embed = 0;
XRaiseWindow(xw.dpy, xw.win);
XSetInputFocus(xw.dpy, xw.win, RevertToParent, CurrentTime);
}
#endif // ST_EMBEDDER_PATCH
win.mode &= ~MODE_VISIBLE;
}
void
xsetpointermotion(int set)
{
#if HIDECURSOR_PATCH
if (!set && !xw.pointerisvisible)
return;
#endif // HIDECURSOR_PATCH
#if OPENURLONCLICK_PATCH
set = 1; /* keep MotionNotify event enabled */
#endif // OPENURLONCLICK_PATCH
MODBIT(xw.attrs.event_mask, set, PointerMotionMask);
XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
}
void
xsetmode(int set, unsigned int flags)
{
int mode = win.mode;
MODBIT(win.mode, set, flags);
2021-05-16 09:40:15 +00:00
#if SWAPMOUSE_PATCH
if ((flags & MODE_MOUSE)
#if HIDECURSOR_PATCH
&& xw.pointerisvisible
#endif // HIDECURSOR_PATCH
) {
if (win.mode & MODE_MOUSE)
XUndefineCursor(xw.dpy, xw.win);
else
#if HIDECURSOR_PATCH
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
#else
2021-05-16 09:40:15 +00:00
XDefineCursor(xw.dpy, xw.win, cursor);
#endif // HIDECURSOR_PATCH
2021-05-16 09:40:15 +00:00
}
#elif OPENURLONCLICK_PATCH
if (win.mode & MODE_MOUSE && xw.pointerisvisible)
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
2021-05-16 09:40:15 +00:00
#endif // SWAPMOUSE_PATCH
if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE))
redraw();
}
int
xsetcursor(int cursor)
{
2021-05-08 08:13:20 +00:00
#if BLINKING_CURSOR_PATCH
if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */
#else
if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
2021-05-08 08:13:20 +00:00
#endif // BLINKING_CURSOR_PATCH
return 1;
2021-05-11 14:35:30 +00:00
#if DEFAULT_CURSOR_PATCH
#if BLINKING_CURSOR_PATCH
win.cursor = (cursor ? cursor : cursorstyle);
#else
win.cursor = (cursor ? cursor : cursorshape);
#endif // BLINKING_CURSOR_PATCH
#else
win.cursor = cursor;
2021-05-11 14:35:30 +00:00
#endif // DEFAULT_CURSOR_PATCH
2021-05-08 08:13:20 +00:00
#if BLINKING_CURSOR_PATCH
cursorblinks = win.cursor == 0 || win.cursor == 1 ||
win.cursor == 3 || win.cursor == 5 ||
win.cursor == 7;
#endif // BLINKING_CURSOR_PATCH
return 0;
}
void
xseturgency(int add)
{
XWMHints *h = XGetWMHints(xw.dpy, xw.win);
MODBIT(h->flags, add, XUrgencyHint);
XSetWMHints(xw.dpy, xw.win, h);
XFree(h);
}
void
xbell(void)
{
if (!(IS_SET(MODE_FOCUSED)))
xseturgency(1);
if (bellvolume)
XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL);
2020-08-08 16:09:00 +00:00
#if VISUALBELL_1_PATCH
if (!bellon) /* turn visual bell on */
bellon = 1;
#endif // VISUALBELL_1_PATCH
}
void
focus(XEvent *ev)
{
XFocusChangeEvent *e = &ev->xfocus;
2020-01-07 07:05:00 +00:00
#if ST_EMBEDDER_PATCH
if (embed && ev->type == FocusIn) {
XRaiseWindow(xw.dpy, embed);
XSetInputFocus(xw.dpy, embed, RevertToParent, CurrentTime);
sendxembed(XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0);
sendxembed(XEMBED_WINDOW_ACTIVATE, 0, 0, 0);
}
#endif // ST_EMBEDDER_PATCH
if (e->mode == NotifyGrab)
return;
if (ev->type == FocusIn) {
if (xw.ime.xic)
XSetICFocus(xw.ime.xic);
win.mode |= MODE_FOCUSED;
xseturgency(0);
if (IS_SET(MODE_FOCUS))
ttywrite("\033[I", 3, 0);
2021-05-09 12:40:30 +00:00
#if ALPHA_PATCH && ALPHA_FOCUS_HIGHLIGHT_PATCH
if (!focused) {
focused = 1;
xloadcols();
tfulldirt();
2021-05-09 12:40:30 +00:00
}
#endif // ALPHA_FOCUS_HIGHLIGHT_PATCH
} else {
if (xw.ime.xic)
XUnsetICFocus(xw.ime.xic);
win.mode &= ~MODE_FOCUSED;
if (IS_SET(MODE_FOCUS))
ttywrite("\033[O", 3, 0);
2021-05-09 12:40:30 +00:00
#if ALPHA_PATCH && ALPHA_FOCUS_HIGHLIGHT_PATCH
if (focused) {
focused = 0;
xloadcols();
tfulldirt();
2021-05-09 12:40:30 +00:00
}
#endif // ALPHA_FOCUS_HIGHLIGHT_PATCH
}
}
int
match(uint mask, uint state)
{
return mask == XK_ANY_MOD || mask == (state & ~ignoremod);
}
char*
kmap(KeySym k, uint state)
{
Key *kp;
int i;
/* Check for mapped keys out of X11 function keys. */
for (i = 0; i < LEN(mappedkeys); i++) {
if (mappedkeys[i] == k)
break;
}
if (i == LEN(mappedkeys)) {
if ((k & 0xFFFF) < 0xFD00)
return NULL;
}
for (kp = key; kp < key + LEN(key); kp++) {
if (kp->k != k)
continue;
if (!match(kp->mask, state))
continue;
if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)
continue;
if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2)
continue;
if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)
continue;
return kp->s;
}
return NULL;
}
void
kpress(XEvent *ev)
{
XKeyEvent *e = &ev->xkey;
KeySym ksym = NoSymbol;
char buf[64], *customkey;
int len, screen;
Rune c;
Status status;
Shortcut *bp;
#if HIDECURSOR_PATCH
if (xw.pointerisvisible && hidecursor) {
#if OPENURLONCLICK_PATCH
#if ANYSIZE_PATCH
int x = e->x - win.hborderpx;
int y = e->y - win.vborderpx;
#else
int x = e->x - borderpx;
int y = e->y - borderpx;
#endif // ANYSIZE_PATCH
LIMIT(x, 0, win.tw - 1);
LIMIT(y, 0, win.th - 1);
if (!detecturl(x / win.cw, y / win.ch, 0)) {
XDefineCursor(xw.dpy, xw.win, xw.bpointer);
xsetpointermotion(1);
xw.pointerisvisible = 0;
}
#else
XDefineCursor(xw.dpy, xw.win, xw.bpointer);
xsetpointermotion(1);
xw.pointerisvisible = 0;
#endif // OPENURLONCLICK_PATCH
}
#endif // HIDECURSOR_PATCH
if (IS_SET(MODE_KBDLOCK))
return;
if (xw.ime.xic) {
2020-03-24 14:41:43 +00:00
len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);
if (status == XBufferOverflow)
return;
} else {
2020-03-24 14:41:43 +00:00
len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
}
2024-03-13 09:33:51 +00:00
#if KEYBOARDSELECT_PATCH && REFLOW_PATCH
if (IS_SET(MODE_KBDSELECT) ) {
if (kbds_issearchmode()) {
for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
if (ksym == bp->keysym && match(bp->mod, e->state) &&
(!bp->screen || bp->screen == screen) &&
(bp->func == clippaste || bp->func == selpaste)) {
bp->func(&(bp->arg));
return;
}
}
}
if (match(XK_NO_MOD, e->state) ||
(XK_Shift_L | XK_Shift_R) & e->state )
win.mode ^= kbds_keyboardhandler(ksym, buf, len, 0);
return;
}
#elif KEYBOARDSELECT_PATCH
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;
}
#endif // KEYBOARDSELECT_PATCH
screen = tisaltscr() ? S_ALT : S_PRI;
/* 1. shortcuts */
for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
if (ksym == bp->keysym && match(bp->mod, e->state) &&
(!bp->screen || bp->screen == screen)) {
bp->func(&(bp->arg));
return;
}
}
/* 2. custom keys from config.h */
if ((customkey = kmap(ksym, e->state))) {
ttywrite(customkey, strlen(customkey), 1);
return;
}
/* 3. composed string from input method */
if (len == 0)
return;
if (len == 1 && e->state & Mod1Mask) {
if (IS_SET(MODE_8BIT)) {
if (*buf < 0177) {
c = *buf | 0x80;
len = utf8encode(c, buf);
}
} else {
buf[1] = buf[0];
buf[0] = '\033';
len = 2;
}
}
ttywrite(buf, len, 1);
}
void
cmessage(XEvent *e)
{
/*
* See xembed specs
* http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html
*/
if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {
if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {
win.mode |= MODE_FOCUSED;
xseturgency(0);
} else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {
win.mode &= ~MODE_FOCUSED;
}
} else if (e->xclient.data.l[0] == xw.wmdeletewin) {
ttyhangup();
exit(0);
}
}
void
resize(XEvent *e)
{
2020-01-07 07:05:00 +00:00
#if ST_EMBEDDER_PATCH
XWindowChanges wc;
#endif // ST_EMBEDDER_PATCH
2022-03-10 10:37:49 +00:00
#if BACKGROUND_IMAGE_PATCH
if (pseudotransparency) {
if (e->xconfigure.width == win.w &&
e->xconfigure.height == win.h &&
e->xconfigure.x == win.x && e->xconfigure.y == win.y)
return;
updatexy();
} else
#endif // BACKGROUND_IMAGE_PATCH
if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)
return;
2020-01-07 07:05:00 +00:00
#if ST_EMBEDDER_PATCH
if (embed) {
wc.width = e->xconfigure.width;
wc.height = e->xconfigure.height;
XConfigureWindow(xw.dpy, embed, CWWidth | CWHeight, &wc);
}
#endif // ST_EMBEDDER_PATCH
cresize(e->xconfigure.width, e->xconfigure.height);
}
void
run(void)
{
XEvent ev;
int w = win.w, h = win.h;
fd_set rfd;
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
struct timespec seltv, *tv, now, lastblink, trigger;
double timeout;
/* Waiting for window mapping */
do {
XNextEvent(xw.dpy, &ev);
/*
* This XFilterEvent call is required because of XOpenIM. It
* does filter out the key event and some client message for
* the input method too.
*/
if (XFilterEvent(&ev, None))
continue;
if (ev.type == ConfigureNotify) {
w = ev.xconfigure.width;
h = ev.xconfigure.height;
}
} while (ev.type != MapNotify);
ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
cresize(w, h);
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
FD_ZERO(&rfd);
FD_SET(ttyfd, &rfd);
FD_SET(xfd, &rfd);
2021-05-09 07:25:22 +00:00
#if SYNC_PATCH
if (XPending(xw.dpy) || ttyread_pending())
#else
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
if (XPending(xw.dpy))
2021-05-09 07:25:22 +00:00
#endif // SYNC_PATCH
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
timeout = 0; /* existing events might not set xfd */
seltv.tv_sec = timeout / 1E3;
seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
tv = timeout >= 0 ? &seltv : NULL;
if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
if (errno == EINTR)
continue;
die("select failed: %s\n", strerror(errno));
}
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
clock_gettime(CLOCK_MONOTONIC, &now);
2021-05-09 07:25:22 +00:00
#if SYNC_PATCH
int ttyin = FD_ISSET(ttyfd, &rfd) || ttyread_pending();
if (ttyin)
ttyread();
#else
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
if (FD_ISSET(ttyfd, &rfd))
ttyread();
2021-05-09 07:25:22 +00:00
#endif // SYNC_PATCH
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
xev = 0;
while (XPending(xw.dpy)) {
xev = 1;
XNextEvent(xw.dpy, &ev);
if (XFilterEvent(&ev, None))
continue;
if (handler[ev.type])
(handler[ev.type])(&ev);
}
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
/*
* To reduce flicker and tearing, when new content or event
* triggers drawing, we first wait a bit to ensure we got
* everything, and if nothing new arrives - we draw.
* We start with trying to wait minlatency ms. If more content
* arrives sooner, we retry with shorter and shorter periods,
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
* and eventually draw even without idle after maxlatency ms.
* Typically this results in low latency while interacting,
* maximum latency intervals during `cat huge.txt`, and perfect
* sync with periodic updates from animations/key-repeats/etc.
*/
2021-05-09 07:25:22 +00:00
#if SYNC_PATCH
if (ttyin || xev)
#else
if (FD_ISSET(ttyfd, &rfd) || xev)
#endif // SYNC_PATCH
{
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
if (!drawing) {
trigger = now;
2021-05-08 08:13:20 +00:00
#if BLINKING_CURSOR_PATCH
if (IS_SET(MODE_BLINK)) {
win.mode ^= MODE_BLINK;
}
lastblink = now;
#endif // BLINKING_CURSOR_PATCH
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
drawing = 1;
}
timeout = (maxlatency - TIMEDIFF(now, trigger)) \
/ maxlatency * minlatency;
if (timeout > 0)
continue; /* we have time, try to find idle */
}
2021-05-09 07:25:22 +00:00
#if SYNC_PATCH
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;
}
#endif // SYNC_PATCH
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
/* idle detected or maxlatency exhausted -> draw */
timeout = -1;
2021-05-08 08:13:20 +00:00
#if BLINKING_CURSOR_PATCH
if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK)))
#else
if (blinktimeout && tattrset(ATTR_BLINK))
#endif // BLINKING_CURSOR_PATCH
{
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
timeout = blinktimeout - TIMEDIFF(now, lastblink);
if (timeout <= 0) {
if (-timeout > blinktimeout) /* start visible */
win.mode |= MODE_BLINK;
win.mode ^= MODE_BLINK;
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
tsetdirtattr(ATTR_BLINK);
lastblink = now;
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
timeout = blinktimeout;
}
}
2020-08-08 16:09:00 +00:00
#if VISUALBELL_1_PATCH
if (bellon) {
bellon++;
bellon %= 3;
MODBIT(win.mode, !IS_SET(MODE_REVERSE), MODE_REVERSE);
redraw();
}
else
draw();
#else
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
draw();
2020-08-08 16:09:00 +00:00
#endif // VISUALBELL_1_PATCH
auto-sync: draw on idle to avoid flicker/tearing st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
2020-05-20 12:15:57 +00:00
XFlush(xw.dpy);
drawing = 0;
}
}
void
usage(void)
{
2020-03-29 14:46:38 +00:00
die("usage: %s [-aiv] [-c class]"
#if WORKINGDIR_PATCH
" [-d path]"
#endif // WORKINGDIR_PATCH
" [-f font] [-g geometry]"
2024-03-07 14:34:21 +00:00
" [-n name] [-o file]\n"
" [-T title] [-t title] [-w windowid]"
" [[-e] command [args ...]]\n"
" %s [-aiv] [-c class]"
2020-03-29 14:46:38 +00:00
#if WORKINGDIR_PATCH
" [-d path]"
#endif // WORKINGDIR_PATCH
" [-f font] [-g geometry]"
2024-03-07 14:34:21 +00:00
" [-n name] [-o file]\n"
" [-T title] [-t title] [-w windowid] -l line"
" [stty_args ...]\n", argv0, argv0);
}
int
main(int argc, char *argv[])
{
xw.l = xw.t = 0;
xw.isfixed = False;
2021-05-08 08:13:20 +00:00
#if BLINKING_CURSOR_PATCH
xsetcursor(cursorstyle);
#else
xsetcursor(cursorshape);
2021-05-08 08:13:20 +00:00
#endif // BLINKING_CURSOR_PATCH
ARGBEGIN {
case 'a':
allowaltscreen = 0;
break;
#if ALPHA_PATCH
case 'A':
opt_alpha = EARGF(usage());
break;
#endif // ALPHA_PATCH
case 'c':
opt_class = EARGF(usage());
break;
2020-03-29 14:46:38 +00:00
#if WORKINGDIR_PATCH
case 'd':
opt_dir = EARGF(usage());
break;
#endif // WORKINGDIR_PATCH
case 'e':
if (argc > 0)
--argc, ++argv;
goto run;
case 'f':
opt_font = EARGF(usage());
break;
case 'g':
xw.gm = XParseGeometry(EARGF(usage()),
&xw.l, &xw.t, &cols, &rows);
break;
case 'i':
xw.isfixed = 1;
break;
case 'o':
opt_io = EARGF(usage());
break;
case 'l':
opt_line = EARGF(usage());
break;
case 'n':
opt_name = EARGF(usage());
break;
case 't':
case 'T':
opt_title = EARGF(usage());
break;
case 'w':
opt_embed = EARGF(usage());
break;
case 'v':
die("%s " VERSION "\n", argv0);
break;
default:
usage();
} ARGEND;
run:
if (argc > 0) /* eat all remaining arguments */
opt_cmd = argv;
if (!opt_title)
opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0];
setlocale(LC_CTYPE, "");
XSetLocaleModifiers("");
#if XRESOURCES_PATCH && XRESOURCES_RELOAD_PATCH || BACKGROUND_IMAGE_PATCH && BACKGROUND_IMAGE_RELOAD_PATCH
signal(SIGUSR1, sigusr1_reload);
#endif // XRESOURCES_RELOAD_PATCH | BACKGROUND_IMAGE_RELOAD_PATCH
#if XRESOURCES_PATCH
2019-09-16 12:21:09 +00:00
if (!(xw.dpy = XOpenDisplay(NULL)))
die("Can't open display\n");
config_init(xw.dpy);
#endif // XRESOURCES_PATCH
2024-03-07 14:34:21 +00:00
#if LIGATURES_PATCH
hbcreatebuffer();
#endif // LIGATURES_PATCH
cols = MAX(cols, 1);
rows = MAX(rows, 1);
2021-05-09 12:40:30 +00:00
#if ALPHA_PATCH && ALPHA_FOCUS_HIGHLIGHT_PATCH
defaultbg = MAX(LEN(colorname), 256);
#endif // ALPHA_FOCUS_HIGHLIGHT_PATCH
tnew(cols, rows);
xinit(cols, rows);
2022-03-10 10:37:49 +00:00
#if BACKGROUND_IMAGE_PATCH
bginit();
#endif // BACKGROUND_IMAGE_PATCH
xsetenv();
selinit();
2020-03-29 14:46:38 +00:00
#if WORKINGDIR_PATCH
if (opt_dir && chdir(opt_dir))
die("Can't change to working directory %s\n", opt_dir);
#endif // WORKINGDIR_PATCH
run();
2024-03-07 14:34:21 +00:00
#if LIGATURES_PATCH
hbdestroybuffer();
#endif // LIGATURES_PATCH
return 0;
}