st/x.c

2660 lines
61 KiB
C
Raw Permalink Normal View History

2017-01-20 09:06:39 +01:00
/* See LICENSE for license details. */
#include <errno.h>
2018-03-09 15:36:38 +01:00
#include <math.h>
#include <limits.h>
2017-01-20 09:06:39 +01:00
#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;
2017-01-20 09:06:39 +01:00
#include "arg.h"
#include "st.h"
#include "win.h"
2017-01-20 09:06:39 +01:00
/* types used in config.h */
typedef struct {
uint mod;
KeySym keysym;
void (*func)(const Arg *);
const Arg arg;
} Shortcut;
typedef struct {
uint mod;
uint button;
void (*func)(const Arg *);
const Arg arg;
uint release;
} MouseShortcut;
typedef struct {
KeySym k;
uint mask;
char *s;
/* three-valued logic variables: 0 indifferent, 1 on, -1 off */
signed char appkey; /* application keypad */
signed char appcursor; /* application cursor */
} Key;
2024-09-02 15:04:04 +02:00
/* 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
};
/* 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 zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
static void ttysend(const Arg *);
/* config.h for applying patches and the configuration. */
#include "config.h"
2017-01-20 09:06:39 +01:00
/* XEMBED messages */
#define XEMBED_FOCUS_IN 4
#define XEMBED_FOCUS_OUT 5
/* macros */
#define IS_SET(flag) ((win.mode & (flag)) != 0)
2017-01-20 09:06:39 +01:00
#define TRUERED(x) (((x) & 0xff0000) >> 8)
#define TRUEGREEN(x) (((x) & 0xff00))
#define TRUEBLUE(x) (((x) & 0xff) << 8)
typedef XftDraw *Draw;
typedef XftColor Color;
typedef XftGlyphFontSpec GlyphFontSpec;
2017-01-20 09:06:39 +01:00
/* Purely graphic info */
typedef struct {
int tw, th; /* tty width and height */
int w, h; /* window width and height */
2024-09-02 14:58:37 +02:00
int hborderpx, vborderpx;
int ch; /* char height */
int cw; /* char width */
int mode; /* window state/mode flags */
int cursor; /* cursor style */
} TermWindow;
2017-01-20 09:06:39 +01:00
typedef struct {
Display *dpy;
Colormap cmap;
Window win;
Drawable buf;
GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
struct {
XIM xim;
XIC xic;
XPoint spot;
XVaNestedList spotlist;
} ime;
2017-01-20 09:06:39 +01:00
Draw draw;
Visual *vis;
XSetWindowAttributes attrs;
int scr;
int isfixed; /* is fixed geometry? */
2024-09-02 14:57:18 +02:00
int depth; /* bit depth */
2017-01-20 09:06:39 +01:00
int l, t; /* left and top offset */
int gm; /* geometry mask */
} XWindow;
typedef struct {
Atom xtarget;
char *primary, *clipboard;
struct timespec tclick1;
struct timespec tclick2;
2017-01-20 09:06:39 +01:00
} XSelection;
/* Font structure */
#define Font Font_
2017-01-20 09:06:39 +01:00
typedef struct {
int height;
int width;
int ascent;
int descent;
int badslant;
int badweight;
short lbearing;
short rbearing;
XftFont *match;
FcFontSet *set;
FcPattern *pattern;
} Font;
/* Drawing Context */
typedef struct {
Color *col;
size_t collen;
Font font, bfont, ifont, ibfont;
GC gc;
} DC;
static inline ushort sixd_to_16bit(int);
static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
static void xdrawglyph(Glyph, int, int);
static void xclear(int, int, int, int);
static int xgeommasktogravity(int);
static int ximopen(Display *);
static void ximinstantiate(Display *, XPointer, XPointer);
static void ximdestroy(XIM, XPointer, XPointer);
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 *);
2017-01-20 09:06:39 +01:00
static int xloadfont(Font *, FcPattern *);
static void xloadfonts(const char *, double);
2024-09-02 15:02:52 +02:00
static int xloadsparefont(FcPattern *, int);
static void xloadsparefonts(void);
2017-01-20 09:06:39 +01:00
static void xunloadfont(Font *);
static void xunloadfonts(void);
static void xsetenv(void);
static void xseturgency(int);
static int evcol(XEvent *);
static int evrow(XEvent *);
2017-01-20 09:06:39 +01:00
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 int mouseaction(XEvent *, uint);
2017-01-20 09:06:39 +01:00
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);
static void mousesel(XEvent *, int);
2017-01-20 09:06:39 +01:00
static void mousereport(XEvent *);
static char *kmap(KeySym, uint);
static int match(uint, uint);
2017-01-20 09:06:39 +01:00
static void run(void);
static void usage(void);
2017-01-20 09:06:39 +01:00
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,
};
/* Globals */
static DC dc;
static XWindow xw;
static XSelection xsel;
static TermWindow win;
2017-01-20 09:06:39 +01:00
/* 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. */
static Fontcache *frc = NULL;
2017-01-20 09:06:39 +01:00
static int frclen = 0;
static int frccap = 0;
static char *usedfont = NULL;
static double usedfontsize = 0;
static double defaultfontsize = 0;
2017-01-20 09:06:39 +01:00
2024-09-02 14:57:18 +02:00
static char *opt_alpha = NULL;
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;
static uint buttons; /* bit field of pressed buttons */
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;
clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);
XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard,
xw.win, CurrentTime);
}
void
selpaste(const Arg *dummy)
{
XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,
xw.win, CurrentTime);
}
void
numlock(const Arg *dummy)
{
win.mode ^= MODE_NUMLOCK;
}
void
zoom(const Arg *arg)
{
Arg larg;
larg.f = usedfontsize + arg->f;
zoomabs(&larg);
}
void
zoomabs(const Arg *arg)
{
xunloadfonts();
xloadfonts(usedfont, arg->f);
2024-09-02 15:02:52 +02:00
xloadsparefonts();
cresize(0, 0);
redraw();
xhints();
}
void
zoomreset(const Arg *arg)
{
Arg larg;
if (defaultfontsize > 0) {
larg.f = defaultfontsize;
zoomabs(&larg);
}
}
void
ttysend(const Arg *arg)
{
ttywrite(arg->s, strlen(arg->s), 1);
}
int
evcol(XEvent *e)
{
2024-09-02 14:58:37 +02:00
int x = e->xbutton.x - win.hborderpx;
LIMIT(x, 0, win.tw - 1);
return x / win.cw;
}
int
evrow(XEvent *e)
{
2024-09-02 14:58:37 +02:00
int y = e->xbutton.y - win.vborderpx;
LIMIT(y, 0, win.th - 1);
return y / win.ch;
}
2017-01-20 09:06:39 +01:00
void
mousesel(XEvent *e, int done)
2017-01-20 09:06:39 +01:00
{
int type, seltype = SEL_REGULAR;
uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);
2017-01-20 09:06:39 +01:00
for (type = 1; type < LEN(selmasks); ++type) {
2017-01-20 09:06:39 +01:00
if (match(selmasks[type], state)) {
seltype = type;
2017-01-20 09:06:39 +01:00
break;
}
}
selextend(evcol(e), evrow(e), seltype, done);
if (done)
setsel(getsel(), e->xbutton.time);
2017-01-20 09:06:39 +01:00
}
void
mousereport(XEvent *e)
{
int len, btn, code;
int x = evcol(e), y = evrow(e);
int state = e->xbutton.state;
2017-01-20 09:06:39 +01:00
char buf[40];
static int ox, oy;
if (e->type == MotionNotify) {
2017-01-20 09:06:39 +01:00
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)
2017-01-20 09:06:39 +01:00
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;
2017-01-20 09:06:39 +01:00
} else {
btn = e->xbutton.button;
/* Only buttons 1 through 11 can be encoded */
if (btn < 1 || btn > 11)
return;
if (e->type == ButtonRelease) {
2017-01-20 09:06:39 +01:00
/* 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)
2017-01-20 09:06:39 +01:00
return;
}
code = 0;
2017-01-20 09:06:39 +01:00
}
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;
2017-01-20 09:06:39 +01:00
if (!IS_SET(MODE_MOUSEX10)) {
code += ((state & ShiftMask ) ? 4 : 0)
+ ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */
+ ((state & ControlMask) ? 16 : 0);
2017-01-20 09:06:39 +01:00
}
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');
2017-01-20 09:06:39 +01:00
} else if (x < 223 && y < 223) {
len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
32+code, 32+x+1, 32+y+1);
2017-01-20 09:06:39 +01:00
} else {
return;
}
ttywrite(buf, len, 0);
2017-01-20 09:06:39 +01:00
}
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;
/* 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 &&
(match(ms->mod, state) || /* exact or forced */
match(ms->mod, state & ~forcemousemod))) {
ms->func(&(ms->arg));
return 1;
}
}
return 0;
}
2017-01-20 09:06:39 +01:00
void
bpress(XEvent *e)
{
int btn = e->xbutton.button;
2017-01-20 09:06:39 +01:00
struct timespec now;
int snap;
2017-01-20 09:06:39 +01:00
if (1 <= btn && btn <= 11)
buttons |= 1 << (btn-1);
if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
2017-01-20 09:06:39 +01:00
mousereport(e);
return;
}
if (mouseaction(e, 0))
return;
2017-01-20 09:06:39 +01:00
if (btn == Button1) {
2017-01-20 09:06:39 +01:00
/*
* 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;
2017-01-20 09:06:39 +01:00
} else {
snap = 0;
2017-01-20 09:06:39 +01:00
}
xsel.tclick2 = xsel.tclick1;
xsel.tclick1 = now;
selstart(evcol(e), evrow(e), snap);
2017-01-20 09:06:39 +01:00
}
}
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);
}
}
void
selnotify(XEvent *e)
{
ulong nitems, ofs, rem;
int format;
uchar *data, *last, *repl;
Atom type, incratom, property = None;
2017-01-20 09:06:39 +01:00
incratom = XInternAtom(xw.dpy, "INCR", 0);
ofs = 0;
if (e->type == SelectionNotify)
2017-01-20 09:06:39 +01:00
property = e->xselection.property;
else if (e->type == PropertyNotify)
2017-01-20 09:06:39 +01:00
property = e->xproperty.atom;
2017-01-20 09:06:39 +01:00
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;
}
if (e->type == PropertyNotify && nitems == 0 && rem == 0) {
/*
* 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.
*/
MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);
XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,
&xw.attrs);
/*
* Deleting the property is the transfer start signal.
*/
XDeleteProperty(xw.dpy, xw.win, (int)property);
continue;
}
/*
* 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);
2017-01-20 09:06:39 +01:00
if (IS_SET(MODE_BRCKTPASTE) && rem == 0)
ttywrite("\033[201~", 6, 0);
2017-01-20 09:06:39 +01:00
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);
2017-01-20 09:06:39 +01:00
}
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;
2017-01-20 09:06:39 +01:00
} else if (xsre->selection == clipboard) {
seltext = xsel.clipboard;
2017-01-20 09:06:39 +01:00
} 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)
2017-01-20 09:06:39 +01:00
{
if (!str)
return;
free(xsel.primary);
xsel.primary = str;
2017-01-20 09:06:39 +01:00
XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);
if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)
selclear();
2017-01-20 09:06:39 +01:00
}
void
xsetsel(char *str)
{
setsel(str, CurrentTime);
}
2017-01-20 09:06:39 +01:00
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)) {
2017-01-20 09:06:39 +01:00
mousereport(e);
return;
}
if (mouseaction(e, 1))
return;
if (btn == Button1)
mousesel(e, 1);
2017-01-20 09:06:39 +01:00
}
void
bmotion(XEvent *e)
{
if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
2017-01-20 09:06:39 +01:00
mousereport(e);
return;
}
mousesel(e, 0);
2017-01-20 09:06:39 +01:00
}
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;
col = MAX(1, col);
row = MAX(1, row);
2024-09-02 14:58:37 +02:00
win.hborderpx = (win.w - col * win.cw) / 2;
win.vborderpx = (win.h - row * win.ch) / 2;
tresize(col, row);
xresize(col, row);
ttyresize(win.tw, win.th);
}
2017-01-20 09:06:39 +01:00
void
xresize(int col, int row)
{
win.tw = col * win.cw;