HTTPS
https://git.nageyqma.icu/nageyqma/st.git
SSH
git@git.nageyqma.icu:nageyqma/st.git
HTTPS 链接可直接 git clone;推送代码请使用 SSH。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868
/* See LICENSE for license details. */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* memfd_create, pipe2 */
#endif
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <poll.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/timerfd.h>
#include <linux/input-event-codes.h>
#include <fontconfig/fontconfig.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"
#include "fractional-scale-client-protocol.h"
#include "viewporter-client-protocol.h"
#include "cursor-shape-client-protocol.h"
#include "system-bell-client-protocol.h"
#include "primary-selection-client-protocol.h"
#include "text-input-client-protocol.h"
#include "st.h"
#include "win.h"
#include "hb.h"
char *argv0;
#include "arg.h"
/* Types used by 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;
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 *) __attribute__((unused));
#include "config.h"
#define IS_SET(flag) ((win.mode & (flag)) != 0)
/* How long a printable key waits for an input method to claim it (ms). */
#define IMEWAIT 20
#define TRUERED(x) (((x) & 0xff0000) >> 16)
#define TRUEGREEN(x) (((x) & 0xff00) >> 8)
#define TRUEBLUE(x) ((x) & 0xff)
/* All geometry in this backend is in device (buffer) pixels, except for the
* logical window size we get from xdg-shell. */
typedef struct {
int tw, th; /* terminal area in buffer pixels */
int w, h; /* buffer size */
int lw, lh; /* logical window size */
int hborderpx, vborderpx;
int ch; /* cell height */
int cw; /* cell width */
int mode; /* window state/mode flags */
int cursor; /* cursor style */
} TermWindow;
typedef struct {
char *primary;
struct timespec tclick1;
struct timespec tclick2;
} Selection;
typedef struct {
FT_Face face;
FcPattern *pattern;
int height, width;
int ascent, descent;
int badslant, badweight;
double pxsize; /* device pixels */
double logicalpx; /* logical pixels */
} Font;
typedef struct {
uint32_t *col;
size_t collen;
Font font, bfont, ifont, ibfont;
} DC;
typedef struct {
FT_Face face;
int flags;
Rune unicodep;
} Fontcache;
typedef struct {
int type; /* 0 motion, 1 press, 2 release */
int x, y; /* buffer pixels */
uint button;
uint state;
uint32_t serial;
uint32_t time;
} MouseEvent;
typedef struct {
struct wl_data_offer *data;
struct zwp_primary_selection_offer_v1 *primary;
char **mimes;
int nmimes;
} Offer;
typedef struct {
struct wl_data_source *data;
struct zwp_primary_selection_source_v1 *primary;
char *text;
} Source;
typedef struct Transfer {
struct Transfer *next;
int fd;
int writing;
char *buf;
size_t len, off, cap;
} Transfer;
/* Two buffers, alternating. KWin keeps the current buffer alive as the
* window content and only releases it once another one is committed, so a
* single buffer would stall after the first frame. */
static struct {
struct wl_buffer *buffer[2];
uint32_t *data[2];
uint32_t *target; /* buffer currently being drawn into */
int stride; /* bytes */
size_t size;
int busy[2];
int cur; /* index of the buffer we draw into next */
int drawn;
} fb;
/* Globals */
static DC dc;
static TermWindow win;
static Selection xsel;
static GlyphSpec *specbuf = NULL;
static FT_Library ftlib;
static Fontcache *frc = NULL;
static int frclen = 0;
static int frccap = 0;
static char *usedfont = NULL;
static double usedfontsize = 0;
static double defaultfontsize = 0;
static int fontsloaded = 0;
static struct wl_display *display = NULL;
static struct wl_registry *registry = NULL;
static struct wl_compositor *compositor = NULL;
static struct wl_shm *shm = NULL;
static struct xdg_wm_base *wm_base = NULL;
static struct wl_seat *seat = NULL;
static struct wl_data_device_manager *data_manager = NULL;
static struct zwp_primary_selection_device_manager_v1 *primary_manager = NULL;
static struct zwp_text_input_manager_v3 *text_input_manager = NULL;
static struct wp_cursor_shape_manager_v1 *cursor_shape_manager = NULL;
static struct wp_fractional_scale_manager_v1 *fractional_manager = NULL;
static struct wp_viewporter *viewporter = NULL;
static struct xdg_system_bell_v1 *systembell = NULL;
static struct wl_surface *surface = NULL;
static struct xdg_surface *xdg_surface = NULL;
static struct xdg_toplevel *xdg_toplevel = NULL;
static struct wp_fractional_scale_v1 *fractional = NULL;
static struct wp_viewport *viewport = NULL;
static struct wl_keyboard *keyboard = NULL;
static struct wl_pointer *pointer = NULL;
static struct wp_cursor_shape_device_v1 *cursor_shape = NULL;
static struct wl_data_device *data_device = NULL;
static struct zwp_primary_selection_device_v1 *primary_device = NULL;
static struct zwp_text_input_v3 *text_input = NULL;
static Offer *clipboard_offer = NULL;
static Offer *primary_offer = NULL;
static Offer *pending_offer = NULL;
static Offer *pending_primary_offer = NULL;
static Source *clipboard_source = NULL;
static Source *primary_source = NULL;
static Transfer *transfers = NULL;
static struct xkb_context *xkbcontext = NULL;
static struct xkb_keymap *xkbkeymap = NULL;
static struct xkb_state *xkbstate = NULL;
static struct xkb_compose_table *composetable = NULL;
static struct xkb_compose_state *composestate = NULL;
static int shift_idx = -1, lock_idx = -1, ctrl_idx = -1;
static int mod1_idx = -1, mod2_idx = -1, mod3_idx = -1;
static int mod4_idx = -1, mod5_idx = -1;
static int repeatfd = -1;
static xkb_keycode_t repeatkey = 0;
static int repeatrate = 25, repeatdelay = 600;
static int ime_enabled = 0;
static int ime_active = 0;
/* Printable key text is held back briefly so an input method can claim it. */
static char *pendingtext = NULL;
static size_t pendinglen = 0, pendingcap = 0;
static struct timespec pendinguntil;
static int ime_x = 0, ime_y = 0;
static uint32_t last_serial = 0;
static uint32_t pointer_serial = 0;
static int pointer_hidden = 0;
static int pointer_x = 0, pointer_y = 0;
static double scale = 1.0;
static int iscale = 1;
static int use_viewport = 0;
static int have_frac_scale = 0;
static uint32_t frac_scale = 120;
static int surface_scale = 0;
static int started = 0;
static int configured = 0;
static int draw_pending = 0;
static int pending_event = 0;
static int conf_w = 0, conf_h = 0;
static int shm_argb = 0;
static int clipx1, clipy1, clipx2, clipy2;
static uint buttonstate = 0;
static int axis_dy = 0, axis_dx = 0;
static double axis_y = 0, axis_x = 0;
static int oldbutton = 3; /* button event on startup: 3 = release */
static char *opt_alpha = NULL;
static char *opt_class = NULL;
static char **opt_cmd = NULL;
static char *opt_font = NULL;
static char *opt_io = NULL;
static char *opt_line = NULL;
static char *opt_title = NULL;
static int opt_fixed = 0;
/* Rendering */
static uint32_t premul(uint32_t);
static uint32_t invert(uint32_t);
static int parsecolor(const char *, uint32_t *);
static int loadcolor(int, const char *, uint32_t *);
static int sixd_to_16bit(int);
static void setclip(int, int, int, int);
static void resetclip(void);
static void drawglyphbitmap(FT_Face, uint32_t, int, int, uint32_t);
static FT_Face loadfallbackface(Font *, Rune, FT_UInt *);
static int makeglyphspecs(GlyphSpec *, const Glyph *, int, int, int);
static void drawglyphspecs(const GlyphSpec *, Glyph, int, int, int);
static void drawglyph(Glyph, int, int);
/* Fonts */
static int loadfont(Font *, FcPattern *, double);
static void loadfonts(char *, double);
static void unloadfont(Font *);
static void unloadfonts(void);
/* Window */
static int borderpxdev(void);
static double surfacescale(void);
static int topx(double);
static int tological(int);
static void updatesize(void);
static void destroybuffer(void);
static int createbuffer(void);
static void wlresize(void);
static void cresize(int, int);
static void setscale(double);
static void flushdisplay(void);
static uint32_t cursorshapeid(void);
static void setcursorshape(uint32_t);
static void hidepointer(uint32_t);
static void showpointer(uint32_t);
/* Input */
static uint modmask(void);
static void updatemods(void);
static void keypress(xkb_keycode_t);
static int keytext(xkb_keycode_t, char *, int);
static char *kmap(KeySym, uint);
static int match(uint, uint);
static void startrepeat(xkb_keycode_t);
static void stoprepeat(void);
static void repeattick(void);
static uint buttonmask(uint);
static int evcol(MouseEvent *);
static int evrow(MouseEvent *);
static void bpress(MouseEvent *);
static void brelease(MouseEvent *);
static void bmotion(MouseEvent *);
static void mousesel(MouseEvent *, int);
static void mousereport(MouseEvent *);
static int mouseaction(MouseEvent *, uint);
/* Clipboard */
static void offerdestroy(Offer *);
static void offeraddmime(Offer *, const char *);
static const char *offerpicked(Offer *);
static void offerreceive(Offer *, int);
static void pasteselection(Offer *);
static void sourcedestroy(Source *);
static void newsource(int, const char *);
static void transferdone(Transfer *);
static void transferadd(int, int, const char *, size_t);
static void transferready(Transfer *);
static void transferspoll(struct pollfd *);
/* IME */
static void pendingflush(void);
static void pendingdiscard(void);
static void textqueue(const char *, size_t);
static int hascontrol(const char *, int);
static void imeenable(void);
static void imedisable(void);
static void imespotsend(void);
/* Setup and main loop */
static void wlinit(int, int);
static void run(void);
static void usage(void);
static const struct wl_buffer_listener buffer_listener;
static const struct wl_data_offer_listener data_offer_listener;
static const struct zwp_primary_selection_offer_v1_listener
primary_offer_listener;
static const struct wl_data_source_listener data_source_listener;
static const struct zwp_primary_selection_source_v1_listener
primary_source_listener;
static const struct wl_keyboard_listener keyboard_listener;
static const struct wl_pointer_listener pointer_listener;
static const struct wl_shm_listener shm_listener;
static const struct wl_registry_listener registry_listener;
/* Listener callbacks */
static void registryglobal(void *, struct wl_registry *, uint32_t,
const char *, uint32_t);
static void registryglobalremove(void *, struct wl_registry *, uint32_t);
static void shmformat(void *, struct wl_shm *, uint32_t);
static void bufferrelease(void *, struct wl_buffer *);
static void wmbaseping(void *, struct xdg_wm_base *, uint32_t);
static void xdgsurfaceconfigure(void *, struct xdg_surface *, uint32_t);
static void toplevelconfigure(void *, struct xdg_toplevel *, int32_t,
int32_t, struct wl_array *);
static void toplevelclose(void *, struct xdg_toplevel *);
static void toplevelbounds(void *, struct xdg_toplevel *, int32_t, int32_t);
static void toplevelcaps(void *, struct xdg_toplevel *, struct wl_array *);
static void surfaceenter(void *, struct wl_surface *, struct wl_output *);
static void surfaceleave(void *, struct wl_surface *, struct wl_output *);
static void surfaceprefscale(void *, struct wl_surface *, int32_t);
static void surfacepreftransform(void *, struct wl_surface *, uint32_t);
static void fractionalprefscale(void *, struct wp_fractional_scale_v1 *,
uint32_t);
static void seatcapabilities(void *, struct wl_seat *, uint32_t);
static void seatname(void *, struct wl_seat *, const char *);
static void keyboardkeymap(void *, struct wl_keyboard *, uint32_t, int32_t,
uint32_t);
static void keyboardenter(void *, struct wl_keyboard *, uint32_t,
struct wl_surface *, struct wl_array *);
static void keyboardleave(void *, struct wl_keyboard *, uint32_t,
struct wl_surface *);
static void keyboardkey(void *, struct wl_keyboard *, uint32_t, uint32_t,
uint32_t, uint32_t);
static void keyboardmodifiers(void *, struct wl_keyboard *, uint32_t,
uint32_t, uint32_t, uint32_t, uint32_t);
static void keyboardrepeat(void *, struct wl_keyboard *, int32_t, int32_t);
static void pointerenter(void *, struct wl_pointer *, uint32_t,
struct wl_surface *, wl_fixed_t, wl_fixed_t);
static void pointerleave(void *, struct wl_pointer *, uint32_t,
struct wl_surface *);
static void pointermotion(void *, struct wl_pointer *, uint32_t, wl_fixed_t,
wl_fixed_t);
static void pointerbutton(void *, struct wl_pointer *, uint32_t, uint32_t,
uint32_t, uint32_t);
static void pointeraxis(void *, struct wl_pointer *, uint32_t, uint32_t,
wl_fixed_t);
static void pointerframe(void *, struct wl_pointer *);
static void pointeraxissource(void *, struct wl_pointer *, uint32_t);
static void pointeraxisstop(void *, struct wl_pointer *, uint32_t, uint32_t);
static void pointeraxisdisc(void *, struct wl_pointer *, uint32_t, int32_t);
static void dataoffer(void *, struct wl_data_device *,
struct wl_data_offer *);
static void dataenter(void *, struct wl_data_device *, uint32_t,
struct wl_surface *, wl_fixed_t, wl_fixed_t,
struct wl_data_offer *);
static void dataleave(void *, struct wl_data_device *);
static void datamotion(void *, struct wl_data_device *, uint32_t,
wl_fixed_t, wl_fixed_t);
static void datadrop(void *, struct wl_data_device *);
static void dataselection(void *, struct wl_data_device *,
struct wl_data_offer *);
static void primaryoffer(void *, struct zwp_primary_selection_device_v1 *,
struct zwp_primary_selection_offer_v1 *);
static void primaryselection(void *, struct zwp_primary_selection_device_v1 *,
struct zwp_primary_selection_offer_v1 *);
static void offeroffer(void *, struct wl_data_offer *, const char *);
static void primaryofferoffer(void *,
struct zwp_primary_selection_offer_v1 *,
const char *);
static void datasourcesend(void *, struct wl_data_source *, const char *,
int32_t);
static void datasourcecancelled(void *, struct wl_data_source *);
static void primarysourcesend(void *,
struct zwp_primary_selection_source_v1 *,
const char *, int32_t);
static void primarysourcecancelled(void *,
struct zwp_primary_selection_source_v1 *);
static void textinputenter(void *, struct zwp_text_input_v3 *,
struct wl_surface *);
static void textinputleave(void *, struct zwp_text_input_v3 *,
struct wl_surface *);
static void textinputpreedit(void *, struct zwp_text_input_v3 *, const char *,
int32_t, int32_t);
static void textinputcommit(void *, struct zwp_text_input_v3 *, const char *);
static void textinputdelete(void *, struct zwp_text_input_v3 *, uint32_t,
uint32_t);
static void textinputdone(void *, struct zwp_text_input_v3 *, uint32_t);
static void textinputaction(void *, struct zwp_text_input_v3 *, uint32_t,
uint32_t);
static void textinputlanguage(void *, struct zwp_text_input_v3 *, const char *);
static void textinputpreedithint(void *, struct zwp_text_input_v3 *, uint32_t,
uint32_t, uint32_t);
/* ---- colors ---- */
static int
sixd_to_16bit(int x)
{
return x == 0 ? 0 : 0x3737 + 0x2828 * x;
}
static uint32_t
premul(uint32_t c)
{
uint32_t a = c >> 24;
uint32_t r = (c >> 16) & 0xff;
uint32_t g = (c >> 8) & 0xff;
uint32_t b = c & 0xff;
if (a == 0xff)
return c;
if (a == 0)
return 0;
return (a << 24) | (((r * a + 127) / 255) << 16) |
(((g * a + 127) / 255) << 8) | ((b * a + 127) / 255);
}
static uint32_t
invert(uint32_t c)
{
return (c & 0xff000000) | (~c & 0x00ffffff);
}
static int
hexval(int c)
{
if (c >= '0' && c <= '9')
return c - '0';
c |= 0x20;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
static int
parsehex(const char **pp, int *digits)
{
int v = 0, n = 0, c;
while (n < 4 && (c = hexval((unsigned char)**pp)) >= 0) {
v = v * 16 + c;
(*pp)++;
n++;
}
*digits = n;
return v;
}
static int
parsenamed(const char *name, uint32_t *out)
{
static const struct {
const char *name;
uint32_t rgb;
} colors[] = {
{ "black", 0x000000 }, { "white", 0xffffff },
{ "red", 0xff0000 }, { "green", 0x00ff00 },
{ "blue", 0x0000ff }, { "yellow", 0xffff00 },
{ "magenta", 0xff00ff }, { "cyan", 0x00ffff },
{ "gray", 0xbebebe }, { "grey", 0xbebebe },
{ "darkgray", 0xa9a9a9 }, { "darkgrey", 0xa9a9a9 },
{ "lightgray", 0xd3d3d3 }, { "lightgrey", 0xd3d3d3 },
{ "orange", 0xffa500 }, { "purple", 0xa020f0 },
{ "pink", 0xffc0cb }, { "brown", 0xa52a2a },
{ "navy", 0x000080 }, { "teal", 0x008080 },
{ "olive", 0x808000 }, { "maroon", 0x800000 },
{ "silver", 0xc0c0c0 }, { "lime", 0x00ff00 },
{ "aqua", 0x00ffff }, { "fuchsia", 0xff00ff },
};
size_t i;
for (i = 0; i < LEN(colors); i++)
if (!strcasecmp(name, colors[i].name)) {
*out = 0xff000000 | colors[i].rgb;
return 0;
}
return 1;
}
/* Accepts #rgb, #rrggbb, #rrggbbaa, rgb:r/g/b (1-4 hex digits each) and a
* small set of names, so config.h can use whatever people are used to. */
static int
parsecolor(const char *name, uint32_t *out)
{
const char *p;
unsigned int r = 0, g = 0, b = 0, a = 0xff;
int n, digits, i;
if (name[0] == '#') {
p = name + 1;
n = strlen(p);
if (n != 3 && n != 6 && n != 8)
return 1;
if (n == 3) {
int hv;
if ((hv = hexval(p[0])) < 0 ||
(r = hv * 0x11, (hv = hexval(p[1])) < 0) ||
(g = hv * 0x11, (hv = hexval(p[2])) < 0))
return 1;
b = hv * 0x11;
*out = 0xff000000 | r << 16 | g << 8 | b;
return 0;
}
for (i = 0; i < n; i += 2) {
int hi = hexval(p[i]), lo = hexval(p[i + 1]);
if (hi < 0 || lo < 0)
return 1;
if (i == 0)
r = hi * 16 + lo;
else if (i == 2)
g = hi * 16 + lo;
else if (i == 4)
b = hi * 16 + lo;
else
a = hi * 16 + lo;
}
*out = a << 24 | r << 16 | g << 8 | b;
return 0;
}
if (!strncmp(name, "rgb:", 4)) {
unsigned int v[3];
p = name + 4;
for (i = 0; i < 3; i++) {
if (i > 0 && *p++ != '/')
return 1;
v[i] = parsehex(&p, &digits);
if (digits < 1)
return 1;
if (digits == 1)
v[i] = v[i] * 0x11;
else if (digits == 3)
v[i] = (v[i] * 255 + 0x7ff) / 0xfff;
else if (digits == 4)
v[i] = v[i] >> 8;
}
*out = 0xff000000 | v[0] << 16 | v[1] << 8 | v[2];
return 0;
}
return parsenamed(name, out);
}
static int
loadcolor(int i, const char *name, uint32_t *out)
{
unsigned int r, g, b;
if (!name) {
if (BETWEEN(i, 16, 255)) {
if (i < 6 * 6 * 6 + 16) {
r = sixd_to_16bit(((i - 16) / 36) % 6) >> 8;
g = sixd_to_16bit(((i - 16) / 6) % 6) >> 8;
b = sixd_to_16bit(((i - 16) / 1) % 6) >> 8;
} else {
r = g = b = 0x08 + 0x0a * (i - (6 * 6 * 6 + 16));
}
*out = 0xff000000 | r << 16 | g << 8 | b;
return 0;
}
if (i < 0 || i >= (int)LEN(colorname) || !colorname[i])
return 1;
name = colorname[i];
}
return parsecolor(name, out);
}
void
loadcols(void)
{
int i;
uint32_t c, a;
if (!dc.col) {
dc.collen = MAX(LEN(colorname), 256);
dc.col = xmalloc(dc.collen * sizeof(uint32_t));
}
for (i = 0; i < (int)dc.collen; i++) {
if (loadcolor(i, NULL, &c)) {
if (i < (int)LEN(colorname) && colorname[i])
die("could not load color '%s'\n", colorname[i]);
die("could not load color %d\n", i);
}
dc.col[i] = c;
}
/* set alpha value of bg color */
if (opt_alpha)
alpha = strtof(opt_alpha, NULL);
a = (uint32_t)(0xff * alpha + 0.5);
dc.col[defaultbg] = (dc.col[defaultbg] & 0x00ffffff) | a << 24;
for (i = 0; i < (int)dc.collen; i++)
dc.col[i] = premul(dc.col[i]);
}
int
setcolorname(int x, const char *name)
{
uint32_t c;
if (x < 0 || x >= (int)dc.collen)
return 1;
if (loadcolor(x, name, &c))
return 1;
if (x == (int)defaultbg)
c = (c & 0x00ffffff) | (uint32_t)(0xff * alpha + 0.5) << 24;
dc.col[x] = premul(c);
return 0;
}
/* ---- fonts ---- */
static int
loadfont(Font *f, FcPattern *pattern, double px)
{
FcPattern *configured, *match;
FcResult result;
FT_Face face;
FcChar8 *file;
FcBool outline;
int index, wantattr, haveattr;
double size;
unsigned int i, n = 0;
long adv = 0;
configured = FcPatternDuplicate(pattern);
if (!configured)
return 1;
if (px > 0) {
FcPatternDel(configured, FC_PIXEL_SIZE);
FcPatternDel(configured, FC_SIZE);
FcPatternAddDouble(configured, FC_PIXEL_SIZE, px);
}
FcConfigSubstitute(NULL, configured, FcMatchPattern);
FcDefaultSubstitute(configured);
match = FcFontMatch(NULL, configured, &result);
if (!match) {
FcPatternDestroy(configured);
return 1;
}
if (px > 0) {
f->pxsize = px;
f->logicalpx = px / scale;
} else {
if (FcPatternGetDouble(match, FC_PIXEL_SIZE, 0, &size) !=
FcResultMatch)
size = 12;
f->logicalpx = size;
f->pxsize = size * scale;
}
/* Only scalable outlines; bitmap emoji still come through fontconfig
* as scalable color fonts, but a stale bitmap family would be blurry. */
if (FcPatternGetBool(match, FC_OUTLINE, 0, &outline) == FcResultMatch &&
!outline) {
FcPatternDestroy(match);
FcPatternDestroy(configured);
return 1;
}
if (FcPatternGetString(match, FC_FILE, 0, &file) != FcResultMatch ||
FcPatternGetInteger(match, FC_INDEX, 0, &index) != FcResultMatch) {
FcPatternDestroy(match);
FcPatternDestroy(configured);
return 1;
}
if (FT_New_Face(ftlib, (const char *)file, index, &face)) {
FcPatternDestroy(match);
FcPatternDestroy(configured);
return 1;
}
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
FT_Set_Char_Size(face, 0, (FT_F26Dot6)(f->pxsize * 64 + 0.5), 72, 72);
if (FcPatternGetInteger(pattern, FC_SLANT, 0, &wantattr) ==
FcResultMatch) {
if (FcPatternGetInteger(match, FC_SLANT, 0, &haveattr) !=
FcResultMatch || haveattr < wantattr) {
f->badslant = 1;
fputs("font slant does not match\n", stderr);
}
}
if (FcPatternGetInteger(pattern, FC_WEIGHT, 0, &wantattr) ==
FcResultMatch) {
if (FcPatternGetInteger(match, FC_WEIGHT, 0, &haveattr) !=
FcResultMatch || haveattr != wantattr) {
f->badweight = 1;
fputs("font weight does not match\n", stderr);
}
}
f->ascent = (face->size->metrics.ascender + 63) >> 6;
f->descent = (-face->size->metrics.descender + 63) >> 6;
f->height = f->ascent + f->descent;
for (i = 0; ascii_printable[i]; i++) {
if (FT_Load_Char(face, (unsigned char)ascii_printable[i],
FT_LOAD_DEFAULT))
continue;
adv += face->glyph->advance.x;
n++;
}
f->width = n ? (int)ceil((double)adv / (64.0 * n)) : 1;
f->width = MAX(1, f->width);
f->face = face;
f->pattern = configured;
FcPatternDestroy(match);
return 0;
}
static void
loadfonts(char *fontstr, double fontsize)
{
FcPattern *pattern;
double px, fontval;
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, fontsize);
usedfontsize = fontsize;
px = fontsize * scale;
} else {
px = 0;
if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==
FcResultMatch) {
usedfontsize = fontval;
} else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==
FcResultMatch) {
usedfontsize = -1;
} else {
FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);
usedfontsize = 12;
}
defaultfontsize = usedfontsize;
}
if (loadfont(&dc.font, pattern, px))
die("can't open font %s\n", fontstr);
if (usedfontsize < 0) {
usedfontsize = dc.font.logicalpx;
if (fontsize == 0)
defaultfontsize = usedfontsize;
}
win.cw = MAX(1, (int)ceil(dc.font.width * cwscale));
win.ch = MAX(1, (int)ceil(dc.font.height * chscale));
FcPatternDel(pattern, FC_SLANT);
FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
if (loadfont(&dc.ifont, pattern, dc.font.pxsize))
die("can't open font %s\n", fontstr);
FcPatternDel(pattern, FC_WEIGHT);
FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
if (loadfont(&dc.ibfont, pattern, dc.font.pxsize))
die("can't open font %s\n", fontstr);
FcPatternDel(pattern, FC_SLANT);
FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);
if (loadfont(&dc.bfont, pattern, dc.font.pxsize))
die("can't open font %s\n", fontstr);
fontsloaded = 1;
FcPatternDestroy(pattern);
}
static void
unloadfont(Font *f)
{
if (f->face)
FT_Done_Face(f->face);
if (f->pattern)
FcPatternDestroy(f->pattern);
memset(f, 0, sizeof(*f));
}
static void
unloadfonts(void)
{
int i;
/* HarfBuzz keeps pointers to the FT_Face objects. */
hbunloadfonts();
for (i = 0; i < frclen; i++)
FT_Done_Face(frc[i].face);
free(frc);
frc = NULL;
frclen = frccap = 0;
unloadfont(&dc.font);
unloadfont(&dc.bfont);
unloadfont(&dc.ifont);
unloadfont(&dc.ibfont);
fontsloaded = 0;
}
/* ---- rendering ---- */
static void
setclip(int x, int y, int w, int h)
{
clipx1 = MAX(0, x);
clipy1 = MAX(0, y);
clipx2 = MIN(win.w, x + w);
clipy2 = MIN(win.h, y + h);
}
static void
resetclip(void)
{
clipx1 = 0;
clipy1 = 0;
clipx2 = win.w;
clipy2 = win.h;
}
void
clearrect(int x, int y, int w, int h, uint32_t col)
{
uint32_t *p;
int x1, y1, x2, y2, i, j;
if (!fb.target)
return;
x1 = MAX(x, clipx1);
y1 = MAX(y, clipy1);
x2 = MIN(x + w, clipx2);
y2 = MIN(y + h, clipy2);
if (x1 >= x2 || y1 >= y2)
return;
fb.drawn = 1;
for (j = y1; j < y2; j++) {
p = fb.target + (size_t)j * win.w;
for (i = x1; i < x2; i++)
p[i] = col;
}
}
void
blendrect(int x, int y, int w, int h, uint32_t col)
{
uint32_t *p, d, sr, sg, sb, sa, ia;
int x1, y1, x2, y2, i, j;
sa = col >> 24;
if (!fb.target || sa == 0)
return;
x1 = MAX(x, clipx1);
y1 = MAX(y, clipy1);
x2 = MIN(x + w, clipx2);
y2 = MIN(y + h, clipy2);
if (x1 >= x2 || y1 >= y2)
return;
sr = (col >> 16) & 0xff;
sg = (col >> 8) & 0xff;
sb = col & 0xff;
ia = 255 - sa;
fb.drawn = 1;
for (j = y1; j < y2; j++) {
p = fb.target + (size_t)j * win.w;
for (i = x1; i < x2; i++) {
d = p[i];
p[i] = ((sa + (((d >> 24) * ia + 127) / 255)) << 24) |
((sr + ((((d >> 16) & 0xff) * ia + 127) / 255)) << 16) |
((sg + ((((d >> 8) & 0xff) * ia + 127) / 255)) << 8) |
(sb + (((d & 0xff) * ia + 127) / 255));
}
}
}
/* Rasterize one FreeType glyph at pen position (x, y). */
static void
drawglyphbitmap(FT_Face face, uint32_t glyph, int x, int y, uint32_t fg)
{
FT_GlyphSlot slot;
unsigned char *src, *row;
uint32_t *p, d, cov, sa, ia, fr, fg_, fb_, sr, sg, sb;
int i, j, w, h, stride, px, py, mode;
if (!fb.target || !glyph)
return;
if (FT_Load_Glyph(face, glyph, FT_LOAD_RENDER | FT_LOAD_COLOR))
return;
slot = face->glyph;
if (slot->format != FT_GLYPH_FORMAT_BITMAP) {
if (FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL))
return;
}
if (!slot->bitmap.width || !slot->bitmap.rows ||
!slot->bitmap.buffer)
return;
w = slot->bitmap.width;
h = slot->bitmap.rows;
stride = slot->bitmap.pitch;
mode = slot->bitmap.pixel_mode;
src = slot->bitmap.buffer;
px = x + slot->bitmap_left;
py = y - slot->bitmap_top;
fr = (fg >> 16) & 0xff;
fg_ = (fg >> 8) & 0xff;
fb_ = fg & 0xff;
ia = 0;
for (j = 0; j < h; j++) {
int yy = py + j;
if (yy < clipy1 || yy >= clipy2)
continue;
row = stride >= 0 ? src + j * stride : src + (h - 1 - j) * -stride;
p = fb.target + (size_t)yy * win.w;
for (i = 0; i < w; i++) {
int xx = px + i;
if (xx < clipx1 || xx >= clipx2)
continue;
if (mode == FT_PIXEL_MODE_BGRA)
cov = row[i * 4 + 3];
else if (mode == FT_PIXEL_MODE_MONO)
cov = (row[i >> 3] & (0x80 >> (i & 7))) ? 255 : 0;
else
cov = row[i];
if (!cov)
continue;
sa = (fg >> 24) * cov / 255;
if (!sa)
continue;
ia = 255 - sa;
d = p[xx];
if (mode == FT_PIXEL_MODE_BGRA) {
/* FreeType color bitmaps are premultiplied BGRA */
sr = row[i * 4 + 2] * (fg >> 24) / 255;
sg = row[i * 4 + 1] * (fg >> 24) / 255;
sb = row[i * 4 + 0] * (fg >> 24) / 255;
} else {
sr = fr * cov / 255;
sg = fg_ * cov / 255;
sb = fb_ * cov / 255;
}
p[xx] = ((sa + (((d >> 24) * ia + 127) / 255)) << 24) |
((sr + ((((d >> 16) & 0xff) * ia + 127) / 255)) << 16) |
((sg + ((((d >> 8) & 0xff) * ia + 127) / 255)) << 8) |
(sb + (((d & 0xff) * ia + 127) / 255));
fb.drawn = 1;
}
}
}
/* Font Ring Cache */
enum {
FRC_NORMAL,
FRC_ITALIC,
FRC_BOLD,
FRC_ITALICBOLD
};
/* Face for a rune the configured font does not carry, or NULL when nothing on
* the system does. *glyphidx is its index for that face (0 when not found).
*
* fontconfig's cached charsets over-report: the best match it offers may have
* no cmap entry at all for the rune, which is how CJK came out blank. Only
* FreeType knows what a loaded face really contains, so walk the sorted
* candidates and ask it. Capped because the set is the whole font cache and a
* rune nobody covers must not turn a redraw into a file scan. */
static FT_Face
loadfallbackface(Font *f, Rune rune, FT_UInt *glyphidx)
{
FcPattern *pattern;
FcCharSet *charset;
FcFontSet *set;
FcResult result;
FcChar8 *file;
FT_Face face = NULL;
int i, index;
charset = FcCharSetCreate();
pattern = FcPatternDuplicate(f->pattern);
if (!charset || !pattern)
die("out of memory looking up a fallback font\n");
FcCharSetAddChar(charset, rune);
FcPatternAddCharSet(pattern, FC_CHARSET, charset);
FcPatternAddBool(pattern, FC_SCALABLE, FcTrue);
FcConfigSubstitute(NULL, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
*glyphidx = 0;
set = FcFontSort(NULL, pattern, FcTrue, NULL, &result);
for (i = 0; set && i < set->nfont && i < 32; i++) {
if (FcPatternGetString(set->fonts[i], FC_FILE, 0, &file) !=
FcResultMatch ||
FcPatternGetInteger(set->fonts[i], FC_INDEX, 0, &index) !=
FcResultMatch)
continue;
if (FT_New_Face(ftlib, (const char *)file, index, &face))
continue;
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
FT_Set_Char_Size(face, 0, (FT_F26Dot6)(f->pxsize * 64 + 0.5),
72, 72);
*glyphidx = FT_Get_Char_Index(face, rune);
if (*glyphidx)
break;
FT_Done_Face(face);
}
if (set)
FcFontSetDestroy(set);
FcPatternDestroy(pattern);
FcCharSetDestroy(charset);
if (!*glyphidx)
return NULL;
return face;
}
/* Turn a run of glyphs into font specs. Exactly one spec is appended per
* non-dummy glyph so that hbtransform() can stay index-aligned. */
static int
makeglyphspecs(GlyphSpec *specs, const Glyph *glyphs, int len, int x, int y)
{
double winx = win.hborderpx + x * win.cw,
winy = win.vborderpx + y * win.ch, xp, yp;
ushort mode, prevmode = USHRT_MAX;
Font *font = &dc.font;
int frcflags = FRC_NORMAL;
double runewidth = win.cw;
Rune rune;
FT_UInt glyphidx;
FT_Face face;
int f, numspecs = 0, i;
for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
rune = glyphs[i].u;
mode = glyphs[i].mode;
/* Skip dummy wide-character spacing. */
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.0 : 1.0);
if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
font = &dc.ibfont;
frcflags = FRC_ITALICBOLD;
} else if (mode & ATTR_ITALIC) {
font = &dc.ifont;
frcflags = FRC_ITALIC;
} else if (mode & ATTR_BOLD) {
font = &dc.bfont;
frcflags = FRC_BOLD;
}
yp = winy + font->ascent;
}
if (mode & ATTR_BOXDRAW) {
/* minor shoehorning: boxdraw uses only this ushort */
glyphidx = boxdrawindex(&glyphs[i]);
} else {
glyphidx = FT_Get_Char_Index(font->face, rune);
}
if (glyphidx) {
specs[numspecs].font = font->face;
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. A NULL
* face is a rune nothing on the system covers, see below. */
for (f = 0; f < frclen; f++) {
glyphidx = frc[f].face ?
FT_Get_Char_Index(frc[f].face, rune) : 0;
/* 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. Ask fontconfig for a face carrying the rune
* and cache the answer either way, so a rune is looked up once and
* an uncovered one does not rescan the fonts on every redraw. */
if (f >= frclen) {
face = loadfallbackface(font, rune, &glyphidx);
if (frclen >= frccap) {
frccap += 16;
frc = xrealloc(frc, frccap * sizeof(Fontcache));
}
frc[frclen].face = face;
frc[frclen].flags = frcflags;
frc[frclen].unicodep = rune;
f = frclen++;
}
specs[numspecs].font = frc[f].face ? frc[f].face : font->face;
specs[numspecs].glyph = glyphidx;
specs[numspecs].x = (short)xp;
specs[numspecs].y = (short)yp;
xp += runewidth;
numspecs++;
}
/* Harfbuzz transformation for ligatures. */
hbtransform(specs, glyphs, len, x, y);
return numspecs;
}
static void
drawglyph(Glyph g, int x, int y)
{
int numspecs;
GlyphSpec spec;
numspecs = makeglyphspecs(&spec, &g, 1, x, y);
drawglyphspecs(&spec, g, numspecs, x, y);
}
static void
drawglyphspecs(const GlyphSpec *specs, Glyph base, int len, int x, int y)
{
int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
int winx = win.hborderpx + x * win.cw;
int winy = win.vborderpx + y * win.ch;
int width = charlen * win.cw;
uint32_t fg, bg, temp;
int i;
/* 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;
}
fg = IS_TRUECOL(base.fg) ? 0xff000000 | (base.fg & 0xffffff)
: dc.col[base.fg];
bg = IS_TRUECOL(base.bg) ? 0xff000000 | (base.bg & 0xffffff)
: dc.col[base.bg];
/* Change basic system colors [0-7] to bright system colors [8-15] */
if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
fg = dc.col[base.fg + 8];
if (IS_SET(MODE_REVERSE)) {
if (!IS_TRUECOL(base.fg) && base.fg == defaultfg)
fg = dc.col[defaultbg];
else
fg = invert(fg);
if (!IS_TRUECOL(base.bg) && base.bg == defaultbg)
bg = dc.col[defaultfg];
else
bg = invert(bg);
}
if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT)
fg = (fg & 0xff000000) | ((fg >> 1) & 0x7f7f7f);
if (base.mode & ATTR_REVERSE) {
temp = fg;
fg = bg;
bg = temp;
}
if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)
fg = bg;
if (base.mode & ATTR_INVISIBLE)
fg = bg;
/* Intelligent cleaning up of the borders, horizontally only: the top and
* the bottom padding always belong to the default background, see
* startdraw(), so that a coloured block on the first or the last row does
* not bleed out of its own row. */
if (x == 0)
clearrect(0, winy, win.hborderpx, win.ch, bg);
if (winx + width >= win.hborderpx + win.tw)
clearrect(winx + width, winy, win.w - (winx + width),
win.ch, bg);
/* Clean up the region we want to draw to. */
clearrect(winx, winy, width, win.ch, bg);
/* Set the clip region because our primitives are dumb. */
setclip(winx, winy, width, win.ch);
if (base.mode & ATTR_BOXDRAW) {
drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
} else {
for (i = 0; i < len; i++)
drawglyphbitmap((FT_Face)specs[i].font, specs[i].glyph,
specs[i].x, specs[i].y, fg);
}
/* Render underline and strikethrough. */
if (base.mode & ATTR_UNDERLINE)
blendrect(winx, winy + dc.font.ascent + 1, width, 1, fg);
if (base.mode & ATTR_STRUCK)
blendrect(winx, winy + 2 * dc.font.ascent / 3, width, 1, fg);
resetclip();
}
void
drawline(Line line, int x1, int y1, int x2)
{
int i, x, ox, numspecs;
Glyph base, new;
GlyphSpec *specs = specbuf;
if (!specs || !fb.target)
return;
numspecs = makeglyphspecs(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))
new.mode ^= ATTR_REVERSE;
if (i > 0 && ATTRCMP(base, new)) {
drawglyphspecs(specs, base, i, ox, y1);
specs += i;
numspecs -= i;
i = 0;
}
if (i == 0) {
ox = x;
base = new;
}
i++;
}
if (i > 0)
drawglyphspecs(specs, base, i, ox, y1);
}
void
drawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line,
int len)
{
uint32_t drawcol;
int ct;
/* remove the old cursor */
if (selected(ox, oy))
og.mode ^= ATTR_REVERSE;
/* Redraw the line where cursor was previously.
* It will restore the ligatures broken by the cursor. */
drawline(line, 0, oy, len);
if (IS_SET(MODE_HIDE))
return;
/*
* Select the right color for the right mode.
*/
g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|
ATTR_BOXDRAW;
if (IS_SET(MODE_REVERSE)) {
g.mode |= ATTR_REVERSE;
g.bg = defaultfg;
if (selected(cx, cy)) {
drawcol = dc.col[defaultcs];
g.fg = defaultrcs;
} else {
drawcol = dc.col[defaultrcs];
g.fg = defaultcs;
}
} else {
if (selected(cx, cy)) {
g.fg = defaultfg;
g.bg = defaultrcs;
} else if (!(og.mode & ATTR_REVERSE)) {
uint32_t col = g.bg;
g.bg = g.fg;
g.fg = col;
}
if (IS_TRUECOL(g.bg))
drawcol = 0xff000000 | (g.bg & 0xffffff);
else
drawcol = dc.col[g.bg];
}
ct = MAX(1, (int)(cursorthickness * scale + 0.5));
/* draw the new one */
if (IS_SET(MODE_FOCUSED)) {
switch (win.cursor) {
case 7: /* st extension */
g.u = 0x2603; /* snowman (U+2603) */
/* FALLTHROUGH */
case 0: /* Blinking Block */
case 1: /* Blinking Block (Default) */
case 2: /* Steady Block */
drawglyph(g, cx, cy);
break;
case 3: /* Blinking Underline */
case 4: /* Steady Underline */
blendrect(win.hborderpx + cx * win.cw,
win.vborderpx + (cy + 1) * win.ch - ct,
win.cw, ct, drawcol);
break;
case 5: /* Blinking bar */
case 6: /* Steady bar */
blendrect(win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch, ct, win.ch,
drawcol);
break;
}
} else {
blendrect(win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch, win.cw - 1, 1, drawcol);
blendrect(win.hborderpx + cx * win.cw,
win.vborderpx + cy * win.ch, 1, win.ch - 1, drawcol);
blendrect(win.hborderpx + (cx + 1) * win.cw - 1,
win.vborderpx + cy * win.ch, 1, win.ch - 1, drawcol);
blendrect(win.hborderpx + cx * win.cw,
win.vborderpx + (cy + 1) * win.ch - 1, win.cw, 1,
drawcol);
}
}
/* ---- shm buffer / drawing ---- */
int
startdraw(void)
{
uint32_t padcol;
if (!fb.data[0] || !IS_SET(MODE_VISIBLE))
return 0;
if (fb.busy[fb.cur]) {
draw_pending = 1;
return 0;
}
/* We commit whole buffers, so every frame repaints the whole screen.
* This also keeps the two rotating buffers identical. */
fb.target = fb.data[fb.cur];
fb.drawn = 0;
draw_pending = 0;
resetclip();
/* The padding the character grid does not fill keeps the default
* background, even under reverse video. Rows must not paint into it,
* a coloured first or last row would otherwise look too tall. */
padcol = dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg];
clearrect(0, 0, win.w, win.vborderpx, padcol);
clearrect(0, win.vborderpx + win.th, win.w,
win.h - (win.vborderpx + win.th), padcol);
tfulldirt();
return 1;
}
void
finishdraw(void)
{
if (!fb.target || !fb.drawn) {
fb.drawn = 0;
return;
}
wl_surface_attach(surface, fb.buffer[fb.cur], 0, 0);
if (wl_surface_get_version(surface) >=
WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION)
wl_surface_damage_buffer(surface, 0, 0, win.w, win.h);
else
wl_surface_damage(surface, 0, 0, win.lw, win.lh);
wl_surface_commit(surface);
fb.busy[fb.cur] = 1;
fb.cur ^= 1;
fb.drawn = 0;
}
static void
bufferrelease(void *data, struct wl_buffer *buffer)
{
int i = (int)(intptr_t)data;
fb.busy[i] = 0;
if (draw_pending) {
draw_pending = 0;
pending_event = 1;
}
}
static int
createshm(size_t size)
{
int fd;
char name[32];
fd = memfd_create("st", MFD_CLOEXEC);
if (fd < 0) {
snprintf(name, sizeof(name), "/st-%ld", (long)getpid());
fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
shm_unlink(name);
}
if (fd < 0)
return -1;
if (ftruncate(fd, size) < 0) {
close(fd);
return -1;
}
return fd;
}
static void
destroybuffer(void)
{
int i;
for (i = 0; i < 2; i++) {
if (fb.buffer[i])
wl_buffer_destroy(fb.buffer[i]);
if (fb.data[i])
munmap(fb.data[i], fb.size);
fb.buffer[i] = NULL;
fb.data[i] = NULL;
fb.busy[i] = 0;
}
fb.target = NULL;
fb.size = 0;
fb.stride = 0;
fb.cur = 0;
fb.drawn = 0;
draw_pending = 0;
}
static int
createbuffer(void)
{
struct wl_shm_pool *pool;
int fd, stride, i;
if (win.w < 1 || win.h < 1)
return 1;
stride = win.w * 4;
fb.size = (size_t)stride * win.h;
for (i = 0; i < 2; i++) {
fd = createshm(fb.size);
if (fd < 0)
goto fail;
fb.data[i] = mmap(NULL, fb.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (fb.data[i] == MAP_FAILED) {
fb.data[i] = NULL;
close(fd);
goto fail;
}
pool = wl_shm_create_pool(shm, fd, fb.size);
fb.buffer[i] = wl_shm_pool_create_buffer(pool, 0, win.w, win.h,
stride,
WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
close(fd);
if (!fb.buffer[i])
goto fail;
fb.busy[i] = 0;
}
fb.stride = stride;
fb.cur = 0;
fb.target = fb.data[0];
fb.drawn = 0;
for (i = 0; i < 2; i++)
wl_buffer_add_listener(fb.buffer[i], &buffer_listener,
(void *)(intptr_t)i);
return 0;
fail:
destroybuffer();
return 1;
}
static void
wlresize(void)
{
if (createbuffer())
die("failed to create wl_shm buffer\n");
}
/* ---- window geometry and scaling ---- */
static int
borderpxdev(void)
{
return borderpx ? MAX(1, (int)ceil(borderpx * scale)) : 0;
}
static double
surfacescale(void)
{
return use_viewport ? scale : iscale;
}
static int
topx(double v)
{
return (int)(v * surfacescale() + 0.5);
}
static int
tological(int v)
{
return (int)(v / surfacescale() + 0.5);
}
static void
updatesize(void)
{
if (use_viewport) {
win.w = (int)ceil(win.lw * scale);
win.h = (int)ceil(win.lh * scale);
if (surface)
wl_surface_set_buffer_scale(surface, 1);
if (viewport && win.lw > 0 && win.lh > 0)
wp_viewport_set_destination(viewport, win.lw, win.lh);
} else {
iscale = MAX(1, (int)ceil(scale));
win.w = win.lw * iscale;
win.h = win.lh * iscale;
if (surface)
wl_surface_set_buffer_scale(surface, iscale);
}
win.w = MAX(1, win.w);
win.h = MAX(1, win.h);
}
static void
setscale(double s)
{
int oldviewport = use_viewport;
int changed;
if (s <= 0)
return;
changed = fabs(s - scale) > 1e-6;
scale = s;
use_viewport = have_frac_scale && viewporter != NULL;
changed = changed || use_viewport != oldviewport;
updatesize();
if (!changed || !fontsloaded)
return;
unloadfonts();
loadfonts(usedfont, usedfontsize);
if (started)
cresize(win.lw, win.lh);
}
static void
cresize(int lw, int lh)
{
int col, row, bw;
if (lw > 0)
win.lw = lw;
if (lh > 0)
win.lh = lh;
updatesize();
bw = borderpxdev();
col = (win.w - 2 * bw) / win.cw;
row = (win.h - 2 * bw) / win.ch;
col = MAX(1, col);
row = MAX(1, row);
win.hborderpx = (win.w - col * win.cw) / 2;
win.vborderpx = (win.h - row * win.ch) / 2;
win.tw = col * win.cw;
win.th = row * win.ch;
/* Make sure anything drawn from tresize() (keyboard_select) cannot
* touch a buffer the compositor is still reading. */
specbuf = xrealloc(specbuf, col * sizeof(GlyphSpec));
destroybuffer();
tresize(col, row);
wlresize();
win.mode |= MODE_VISIBLE;
if (started)
ttyresize(win.tw, win.th);
if (opt_fixed) {
xdg_toplevel_set_min_size(xdg_toplevel, win.lw, win.lh);
xdg_toplevel_set_max_size(xdg_toplevel, win.lw, win.lh);
}
}
/* ---- xdg-shell / surface callbacks ---- */
static void
wmbaseping(void *data, struct xdg_wm_base *base, uint32_t serial)
{
xdg_wm_base_pong(base, serial);
}
static void
xdgsurfaceconfigure(void *data, struct xdg_surface *s, uint32_t serial)
{
xdg_surface_ack_configure(s, serial);
if (conf_w > 0 && conf_h > 0 &&
(conf_w != win.lw || conf_h != win.lh)) {
win.lw = conf_w;
win.lh = conf_h;
if (started)
cresize(win.lw, win.lh);
}
configured = 1;
}
static void
toplevelconfigure(void *data, struct xdg_toplevel *t, int32_t w, int32_t h,
struct wl_array *states)
{
conf_w = w;
conf_h = h;
}
static void
toplevelclose(void *data, struct xdg_toplevel *t)
{
ttyhangup();
exit(0);
}
static void
toplevelbounds(void *data, struct xdg_toplevel *t, int32_t w, int32_t h)
{
/* no size hints on Wayland; the grid is centered instead */
}
static void
toplevelcaps(void *data, struct xdg_toplevel *t, struct wl_array *caps)
{
}
static void
surfaceenter(void *data, struct wl_surface *s, struct wl_output *o)
{
}
static void
surfaceleave(void *data, struct wl_surface *s, struct wl_output *o)
{
}
static void
surfaceprefscale(void *data, struct wl_surface *s, int32_t factor)
{
/* The integer fallback is only used without fractional-scale. */
if (fractional_manager)
return;
surface_scale = factor;
setscale(factor);
}
static void
surfacepreftransform(void *data, struct wl_surface *s, uint32_t transform)
{
}
static void
fractionalprefscale(void *data, struct wp_fractional_scale_v1 *fs,
uint32_t s)
{
if (s == 0)
return;
frac_scale = s;
have_frac_scale = 1;
setscale(s / 120.0);
}
static const struct wl_buffer_listener buffer_listener = {
.release = bufferrelease,
};
static const struct xdg_wm_base_listener wm_base_listener = {
.ping = wmbaseping,
};
static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdgsurfaceconfigure,
};
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
.configure = toplevelconfigure,
.close = toplevelclose,
.configure_bounds = toplevelbounds,
.wm_capabilities = toplevelcaps,
};
static const struct wl_surface_listener surface_listener = {
.enter = surfaceenter,
.leave = surfaceleave,
.preferred_buffer_scale = surfaceprefscale,
.preferred_buffer_transform = surfacepreftransform,
};
static const struct wp_fractional_scale_v1_listener fractional_listener = {
.preferred_scale = fractionalprefscale,
};
/* ---- clipboard and primary selection ---- */
static const char *mimetypes[] = {
"text/plain;charset=utf-8",
"UTF8_STRING",
"text/plain",
"STRING",
"TEXT",
};
static void
offeraddmime(Offer *o, const char *mime)
{
o->mimes = xrealloc(o->mimes, (o->nmimes + 1) * sizeof(char *));
o->mimes[o->nmimes++] = xstrdup((char *)mime);
}
static void
offerdestroy(Offer *o)
{
int i;
if (!o)
return;
if (o->data)
wl_data_offer_destroy(o->data);
if (o->primary)
zwp_primary_selection_offer_v1_destroy(o->primary);
for (i = 0; i < o->nmimes; i++)
free(o->mimes[i]);
free(o->mimes);
free(o);
}
static const char *
offerpicked(Offer *o)
{
size_t i;
int j;
if (!o)
return NULL;
for (i = 0; i < LEN(mimetypes); i++)
for (j = 0; j < o->nmimes; j++)
if (!strcmp(o->mimes[j], mimetypes[i]))
return mimetypes[i];
return NULL;
}
static void
offerreceive(Offer *o, int fd)
{
const char *mime = offerpicked(o);
if (!mime) {
fprintf(stderr, "no usable selection mime type\n");
close(fd);
return;
}
if (o->data)
wl_data_offer_receive(o->data, mime, fd);
else
zwp_primary_selection_offer_v1_receive(o->primary, mime, fd);
close(fd);
}
static void
transferdone(Transfer *t)
{
Transfer **pp;
for (pp = &transfers; *pp; pp = &(*pp)->next)
if (*pp == t) {
*pp = t->next;
break;
}
close(t->fd);
free(t->buf);
free(t);
}
static void
transferready(Transfer *t)
{
char buf[8192];
char *p;
ssize_t n;
if (t->writing) {
while (t->off < t->len) {
n = write(t->fd, t->buf + t->off, t->len - t->off);
if (n < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN)
return;
transferdone(t);
return;
}
t->off += n;
}
transferdone(t);
return;
}
for (;;) {
n = read(t->fd, buf, sizeof(buf));
if (n < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN)
return;
transferdone(t);
return;
}
if (n == 0) {
if (t->len) {
t->buf = xrealloc(t->buf, t->len + 1);
t->buf[t->len] = '\0';
for (p = t->buf; *p; p++)
if (*p == '\n')
*p = '\r';
if (IS_SET(MODE_BRCKTPASTE))
ttywrite("\033[200~", 6, 0);
ttywrite(t->buf, t->len, 1);
if (IS_SET(MODE_BRCKTPASTE))
ttywrite("\033[201~", 6, 0);
}
transferdone(t);
return;
}
if (t->len + n > t->cap) {
t->cap = MAX(8192, t->len + n);
t->buf = xrealloc(t->buf, t->cap);
}
memcpy(t->buf + t->len, buf, n);
t->len += n;
}
}
static void
transferadd(int fd, int writing, const char *buf, size_t len)
{
Transfer *t;
t = xmalloc(sizeof(*t));
memset(t, 0, sizeof(*t));
t->fd = fd;
t->writing = writing;
if (writing) {
t->buf = xstrdup((char *)buf);
t->len = len;
}
t->next = transfers;
transfers = t;
if (writing)
transferready(t);
}
static void
transferspoll(struct pollfd *pfd)
{
Transfer *t, *next;
int i = 0;
for (t = transfers; t; t = next, i++) {
next = t->next;
if (pfd[i].revents)
transferready(t);
}
}
static void
pasteselection(Offer *o)
{
int fds[2];
if (!o)
return;
if (pipe2(fds, O_CLOEXEC) < 0) {
fprintf(stderr, "pipe2: %s\n", strerror(errno));
return;
}
fcntl(fds[0], F_SETFL, O_NONBLOCK);
offerreceive(o, fds[1]);
transferadd(fds[0], 0, NULL, 0);
}
static void
sourcedestroy(Source *s)
{
if (!s)
return;
if (s->data) {
if (clipboard_source == s)
clipboard_source = NULL;
wl_data_source_destroy(s->data);
}
if (s->primary) {
if (primary_source == s)
primary_source = NULL;
zwp_primary_selection_source_v1_destroy(s->primary);
}
free(s->text);
free(s);
}
static void
newsource(int primary, const char *text)
{
Source *s;
size_t i;
s = xmalloc(sizeof(*s));
memset(s, 0, sizeof(*s));
s->text = xstrdup((char *)text);
if (primary && primary_manager && primary_device) {
s->primary =
zwp_primary_selection_device_manager_v1_create_source(
primary_manager);
zwp_primary_selection_source_v1_add_listener(
s->primary, &primary_source_listener, s);
for (i = 0; i < LEN(mimetypes); i++)
zwp_primary_selection_source_v1_offer(s->primary,
mimetypes[i]);
primary_source = s;
zwp_primary_selection_device_v1_set_selection(
primary_device, s->primary, last_serial);
} else if (!primary && data_manager && data_device) {
s->data = wl_data_device_manager_create_data_source(data_manager);
wl_data_source_add_listener(s->data, &data_source_listener, s);
for (i = 0; i < LEN(mimetypes); i++)
wl_data_source_offer(s->data, mimetypes[i]);
clipboard_source = s;
wl_data_device_set_selection(data_device, s->data, last_serial);
} else {
free(s->text);
free(s);
}
}
void
setsel(char *str)
{
if (!str)
return;
free(xsel.primary);
xsel.primary = str;
newsource(1, xsel.primary);
newsource(0, xsel.primary);
}
void
clipcopy(const Arg *dummy)
{
(void)dummy;
if (xsel.primary)
newsource(0, xsel.primary);
}
static void
clippaste(const Arg *dummy)
{
(void)dummy;
pasteselection(clipboard_offer);
}
static void
selpaste(const Arg *dummy)
{
(void)dummy;
if (primary_offer)
pasteselection(primary_offer);
else
pasteselection(clipboard_offer);
}
/* ---- selection listeners ---- */
static void
offeroffer(void *data, struct wl_data_offer *offer, const char *mime)
{
offeraddmime(data, mime);
}
static void
primaryofferoffer(void *data, struct zwp_primary_selection_offer_v1 *offer,
const char *mime)
{
offeraddmime(data, mime);
}
static void
dataoffer(void *data, struct wl_data_device *dev, struct wl_data_offer *id)
{
Offer *o;
offerdestroy(pending_offer);
o = xmalloc(sizeof(*o));
memset(o, 0, sizeof(*o));
o->data = id;
wl_data_offer_add_listener(id, &data_offer_listener, o);
pending_offer = o;
}
static void
dataenter(void *data, struct wl_data_device *dev, uint32_t serial,
struct wl_surface *s, wl_fixed_t x, wl_fixed_t y,
struct wl_data_offer *id)
{
/* Drag and drop is not implemented; drop the offer. */
if (pending_offer && pending_offer->data == id) {
offerdestroy(pending_offer);
pending_offer = NULL;
}
}
static void
dataleave(void *data, struct wl_data_device *dev)
{
}
static void
datamotion(void *data, struct wl_data_device *dev, uint32_t time,
wl_fixed_t x, wl_fixed_t y)
{
}
static void
datadrop(void *data, struct wl_data_device *dev)
{
}
static void
dataselection(void *data, struct wl_data_device *dev,
struct wl_data_offer *id)
{
if (!id) {
offerdestroy(clipboard_offer);
clipboard_offer = NULL;
return;
}
if (pending_offer && pending_offer->data == id) {
offerdestroy(clipboard_offer);
clipboard_offer = pending_offer;
pending_offer = NULL;
}
}
static void
primaryoffer(void *data, struct zwp_primary_selection_device_v1 *dev,
struct zwp_primary_selection_offer_v1 *id)
{
Offer *o;
offerdestroy(pending_primary_offer);
o = xmalloc(sizeof(*o));
memset(o, 0, sizeof(*o));
o->primary = id;
zwp_primary_selection_offer_v1_add_listener(id,
&primary_offer_listener, o);
pending_primary_offer = o;
}
static void
primaryselection(void *data, struct zwp_primary_selection_device_v1 *dev,
struct zwp_primary_selection_offer_v1 *id)
{
if (!id) {
offerdestroy(primary_offer);
primary_offer = NULL;
return;
}
if (pending_primary_offer && pending_primary_offer->primary == id) {
offerdestroy(primary_offer);
primary_offer = pending_primary_offer;
pending_primary_offer = NULL;
}
}
static void
datasourcesend(void *data, struct wl_data_source *src, const char *mime,
int32_t fd)
{
Source *s = data;
fcntl(fd, F_SETFL, O_NONBLOCK);
transferadd(fd, 1, s->text, strlen(s->text));
}
static void
datasourcecancelled(void *data, struct wl_data_source *src)
{
sourcedestroy(data);
}
static void
primarysourcesend(void *data,
struct zwp_primary_selection_source_v1 *src,
const char *mime, int32_t fd)
{
Source *s = data;
fcntl(fd, F_SETFL, O_NONBLOCK);
transferadd(fd, 1, s->text, strlen(s->text));
}
static void
primarysourcecancelled(void *data,
struct zwp_primary_selection_source_v1 *src)
{
sourcedestroy(data);
}
static const struct wl_data_offer_listener data_offer_listener = {
.offer = offeroffer,
};
static const struct zwp_primary_selection_offer_v1_listener
primary_offer_listener = {
.offer = primaryofferoffer,
};
static const struct wl_data_source_listener data_source_listener = {
.target = NULL,
.send = datasourcesend,
.cancelled = datasourcecancelled,
};
static const struct zwp_primary_selection_source_v1_listener
primary_source_listener = {
.send = primarysourcesend,
.cancelled = primarysourcecancelled,
};
static const struct wl_data_device_listener data_device_listener = {
.data_offer = dataoffer,
.enter = dataenter,
.leave = dataleave,
.motion = datamotion,
.drop = datadrop,
.selection = dataselection,
};
static const struct zwp_primary_selection_device_v1_listener
primary_device_listener = {
.data_offer = primaryoffer,
.selection = primaryselection,
};
/* ---- input method ---- */
static void
imespotsend(void)
{
int x, y;
if (!text_input || !ime_enabled)
return;
x = win.hborderpx + ime_x * win.cw;
y = win.vborderpx + ime_y * win.ch;
zwp_text_input_v3_set_cursor_rectangle(text_input, tological(x),
tological(y), tological(win.cw),
tological(win.ch));
zwp_text_input_v3_commit(text_input);
}
static void
imeenable(void)
{
if (!text_input || ime_enabled)
return;
zwp_text_input_v3_enable(text_input);
zwp_text_input_v3_set_content_type(text_input,
ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE,
ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
ime_enabled = 1;
imespotsend();
}
static void
imedisable(void)
{
if (!text_input || !ime_enabled)
return;
zwp_text_input_v3_disable(text_input);
zwp_text_input_v3_commit(text_input);
ime_enabled = 0;
ime_active = 0;
}
void
imespot(int x, int y)
{
ime_x = x;
ime_y = y;
imespotsend();
}
static void
textinputenter(void *data, struct zwp_text_input_v3 *ti,
struct wl_surface *s)
{
}
static void
textinputleave(void *data, struct zwp_text_input_v3 *ti,
struct wl_surface *s)
{
ime_active = 0;
}
static void
textinputpreedit(void *data, struct zwp_text_input_v3 *ti, const char *text,
int32_t begin, int32_t end)
{
/* The preedit is not rendered here; it only gates key handling. */
pendingdiscard();
ime_active = text != NULL && *text != '\0';
}
static void
textinputcommit(void *data, struct zwp_text_input_v3 *ti, const char *text)
{
pendingdiscard();
if (!text || !*text)
return;
ttywrite(text, strlen(text), 1);
ime_active = 0;
}
static void
textinputdelete(void *data, struct zwp_text_input_v3 *ti, uint32_t before,
uint32_t after)
{
}
static void
textinputdone(void *data, struct zwp_text_input_v3 *ti, uint32_t serial)
{
}
static void
textinputaction(void *data, struct zwp_text_input_v3 *ti, uint32_t action,
uint32_t serial)
{
}
static void
textinputlanguage(void *data, struct zwp_text_input_v3 *ti, const char *lang)
{
}
static void
textinputpreedithint(void *data, struct zwp_text_input_v3 *ti,
uint32_t start, uint32_t end, uint32_t hint)
{
}
static const struct zwp_text_input_v3_listener text_input_listener = {
.enter = textinputenter,
.leave = textinputleave,
.preedit_string = textinputpreedit,
.commit_string = textinputcommit,
.delete_surrounding_text = textinputdelete,
.done = textinputdone,
.action = textinputaction,
.language = textinputlanguage,
.preedit_hint = textinputpreedithint,
};
/* ---- keyboard ---- */
static uint
modmask(void)
{
uint mask = 0;
if (!xkbstate)
return 0;
if (shift_idx >= 0 && xkb_state_mod_index_is_active(
xkbstate, shift_idx, XKB_STATE_MODS_EFFECTIVE))
mask |= ShiftMask;
if (lock_idx >= 0 && xkb_state_mod_index_is_active(
xkbstate, lock_idx, XKB_STATE_MODS_EFFECTIVE))
mask |= LockMask;
if (ctrl_idx >= 0 && xkb_state_mod_index_is_active(
xkbstate, ctrl_idx, XKB_STATE_MODS_EFFECTIVE))
mask |= ControlMask;
if (mod1_idx >= 0 && xkb_state_mod_index_is_active(
xkbstate, mod1_idx, XKB_STATE_MODS_EFFECTIVE))
mask |= Mod1Mask;
if (mod2_idx >= 0 && xkb_state_mod_index_is_active(
xkbstate, mod2_idx, XKB_STATE_MODS_EFFECTIVE))
mask |= Mod2Mask;
if (mod3_idx >= 0 && xkb_state_mod_index_is_active(
xkbstate, mod3_idx, XKB_STATE_MODS_EFFECTIVE))
mask |= Mod3Mask;
if (mod4_idx >= 0 && xkb_state_mod_index_is_active(
xkbstate, mod4_idx, XKB_STATE_MODS_EFFECTIVE))
mask |= Mod4Mask;
if (mod5_idx >= 0 && xkb_state_mod_index_is_active(
xkbstate, mod5_idx, XKB_STATE_MODS_EFFECTIVE))
mask |= Mod5Mask;
return mask;
}
static void
updatemods(void)
{
uint mask;
if (!xkbstate)
return;
mask = modmask();
MODBIT(win.mode, (mask & Mod2Mask) != 0, MODE_NUMLOCK);
}
static int
match(uint mask, uint state)
{
return mask == MOD_ANY || mask == (state & ~ignoremod);
}
static 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;
}
/* xkbcommon returns the level text; apply the classic Ctrl mapping. */
static int
ctrlfilter(char *buf, int len)
{
uchar c;
if (len != 1 || !(modmask() & ControlMask))
return len;
c = buf[0];
if (c >= '@' && c <= '_')
buf[0] = c & 0x1f;
else if (c >= 'a' && c <= 'z')
buf[0] = c & 0x1f;
else if (c == '?')
buf[0] = 0x7f;
else if (c == ' ')
buf[0] = 0;
return len;
}
static int
keytext(xkb_keycode_t keycode, char *buf, int len)
{
enum xkb_compose_status status;
KeySym sym;
int n;
sym = xkb_state_key_get_one_sym(xkbstate, keycode);
if (composestate) {
xkb_compose_state_feed(composestate, sym);
status = xkb_compose_state_get_status(composestate);
if (status == XKB_COMPOSE_COMPOSING)
return 0;
if (status == XKB_COMPOSE_COMPOSED) {
n = xkb_compose_state_get_utf8(composestate, buf, len);
xkb_compose_state_reset(composestate);
return n > 0 ? ctrlfilter(buf, n) : 0;
}
if (status == XKB_COMPOSE_CANCELLED) {
xkb_compose_state_reset(composestate);
return 0;
}
}
n = xkb_state_key_get_utf8(xkbstate, keycode, buf, len);
if (n <= 0)
return 0;
return ctrlfilter(buf, n);
}
/* Input methods see the same key events we do and react a few milliseconds
* later. Hold printable text back for IMEWAIT ms: if the IME produces a
* preedit or commit in the meantime, the key belonged to it and is dropped. */
static void
pendingflush(void)
{
if (!pendinglen)
return;
ttywrite(pendingtext, pendinglen, 1);
pendinglen = 0;
}
static void
pendingdiscard(void)
{
pendinglen = 0;
}
static void
textqueue(const char *buf, size_t len)
{
struct timespec now;
if (pendinglen + len > pendingcap) {
pendingcap = MAX(64, pendinglen + len);
pendingtext = xrealloc(pendingtext, pendingcap);
}
if (!pendinglen) {
clock_gettime(CLOCK_MONOTONIC, &now);
pendinguntil = now;
pendinguntil.tv_nsec += IMEWAIT * 1000000L;
if (pendinguntil.tv_nsec >= 1000000000L) {
pendinguntil.tv_sec++;
pendinguntil.tv_nsec -= 1000000000L;
}
}
memcpy(pendingtext + pendinglen, buf, len);
pendinglen += len;
}
static int
hascontrol(const char *buf, int len)
{
int i;
for (i = 0; i < len; i++)
if ((uchar)buf[i] < 0x20 || buf[i] == 0x7f)
return 1;
return 0;
}
static void
keypress(xkb_keycode_t keycode)
{
KeySym sym;
uint state;
char buf[64];
int len;
Shortcut *bp;
char *customkey;
if (pointer && !pointer_hidden)
hidepointer(pointer_serial ? pointer_serial : last_serial);
if (IS_SET(MODE_KBDLOCK))
return;
if (ime_active)
return;
sym = xkb_state_key_get_one_sym(xkbstate, keycode);
state = modmask();
if (IS_SET(MODE_KBDSELECT)) {
pendingflush();
len = keytext(keycode, buf, sizeof(buf));
if (match(MOD_NONE, state) || (state & ShiftMask))
win.mode ^= trt_kbdselect(sym, buf, len);
return;
}
/* 1. shortcuts */
for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
if (sym == bp->keysym && match(bp->mod, state)) {
pendingflush();
bp->func(&(bp->arg));
return;
}
}
/* 2. custom keys from config.h */
if ((customkey = kmap(sym, state))) {
pendingflush();
ttywrite(customkey, strlen(customkey), 1);
return;
}
/* 3. composed string from input method */
len = keytext(keycode, buf, sizeof(buf));
if (len == 0)
return;
if (len == 1 && state & Mod1Mask) {
pendingflush();
if (IS_SET(MODE_8BIT)) {
if (*buf < 0177) {
Rune c = *buf | 0x80;
len = utf8encode(c, buf);
}
} else {
buf[1] = buf[0];
buf[0] = '\033';
len = 2;
}
}
if (!text_input || hascontrol(buf, len)) {
pendingflush();
ttywrite(buf, len, 1);
} else {
textqueue(buf, len);
}
}
static void
startrepeat(xkb_keycode_t keycode)
{
struct itimerspec ts;
if (!xkbkeymap || !repeatrate ||
!xkb_keymap_key_repeats(xkbkeymap, keycode)) {
stoprepeat();
return;
}
if (repeatfd < 0) {
repeatfd = timerfd_create(CLOCK_MONOTONIC,
TFD_NONBLOCK | TFD_CLOEXEC);
if (repeatfd < 0)
return;
}
repeatkey = keycode;
memset(&ts, 0, sizeof(ts));
ts.it_value.tv_sec = repeatdelay / 1000;
ts.it_value.tv_nsec = (repeatdelay % 1000) * 1000000L;
ts.it_interval.tv_nsec = 1000000000L / repeatrate;
timerfd_settime(repeatfd, 0, &ts, NULL);
}
static void
stoprepeat(void)
{
struct itimerspec ts;
repeatkey = 0;
if (repeatfd < 0)
return;
memset(&ts, 0, sizeof(ts));
timerfd_settime(repeatfd, 0, &ts, NULL);
}
static void
repeattick(void)
{
uint64_t exp;
if (repeatfd < 0 || !repeatkey)
return;
if (read(repeatfd, &exp, sizeof(exp)) != sizeof(exp))
return;
keypress(repeatkey);
}
static void
numlock(const Arg *dummy)
{
(void)dummy;
win.mode ^= MODE_NUMLOCK;
}
/* ---- cursor shape ---- */
static uint32_t
cursorshapeid(void)
{
const char *s = mouseshape;
if (!strcmp(s, "text"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT;
if (!strcmp(s, "pointer") || !strcmp(s, "hand1") ||
!strcmp(s, "hand2"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
if (!strcmp(s, "crosshair"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR;
if (!strcmp(s, "wait"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT;
if (!strcmp(s, "progress"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS;
if (!strcmp(s, "move") || !strcmp(s, "fleur"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE;
if (!strcmp(s, "ew-resize") || !strcmp(s, "sb_h_double_arrow"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE;
if (!strcmp(s, "ns-resize") || !strcmp(s, "sb_v_double_arrow"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE;
if (!strcmp(s, "nw-resize"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE;
if (!strcmp(s, "ne-resize"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE;
if (!strcmp(s, "sw-resize"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE;
if (!strcmp(s, "se-resize"))
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE;
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
}
static void
setcursorshape(uint32_t serial)
{
if (!cursor_shape)
return;
wp_cursor_shape_device_v1_set_shape(cursor_shape, serial,
cursorshapeid());
}
static void
hidepointer(uint32_t serial)
{
if (!pointer || !cursor_shape || pointer_hidden)
return;
wl_pointer_set_cursor(pointer, serial, NULL, 0, 0);
pointer_hidden = 1;
}
static void
showpointer(uint32_t serial)
{
if (!pointer)
return;
if (cursor_shape)
setcursorshape(serial);
pointer_hidden = 0;
}
/* ---- seat and keyboard ---- */
static void
seatcapabilities(void *data, struct wl_seat *s, uint32_t caps)
{
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
pointer = wl_seat_get_pointer(s);
wl_pointer_add_listener(pointer, &pointer_listener, NULL);
if (cursor_shape_manager)
cursor_shape = wp_cursor_shape_manager_v1_get_pointer(
cursor_shape_manager, pointer);
} else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && pointer) {
if (cursor_shape) {
wp_cursor_shape_device_v1_destroy(cursor_shape);
cursor_shape = NULL;
}
wl_pointer_destroy(pointer);
pointer = NULL;
}
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard) {
keyboard = wl_seat_get_keyboard(s);
wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL);
} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard) {
wl_keyboard_destroy(keyboard);
keyboard = NULL;
}
}
static void
seatname(void *data, struct wl_seat *s, const char *name)
{
}
static void
keyboardkeymap(void *data, struct wl_keyboard *kb, uint32_t format,
int32_t fd, uint32_t size)
{
struct xkb_keymap *keymap;
char *map;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
close(fd);
return;
}
map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
close(fd);
return;
}
keymap = xkb_keymap_new_from_string(xkbcontext, map,
XKB_KEYMAP_FORMAT_TEXT_V1,
XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(map, size);
close(fd);
if (!keymap)
return;
if (xkbstate)
xkb_state_unref(xkbstate);
if (xkbkeymap)
xkb_keymap_unref(xkbkeymap);
xkbkeymap = keymap;
xkbstate = xkb_state_new(xkbkeymap);
if (!xkbstate)
die("failed to create xkb state\n");
shift_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_SHIFT);
lock_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_CAPS);
ctrl_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_CTRL);
mod1_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_ALT);
mod2_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_NUM);
mod3_idx = xkb_keymap_mod_get_index(xkbkeymap, "Mod3");
mod4_idx = xkb_keymap_mod_get_index(xkbkeymap, XKB_MOD_NAME_LOGO);
mod5_idx = xkb_keymap_mod_get_index(xkbkeymap, "Mod5");
}
static void
keyboardenter(void *data, struct wl_keyboard *kb, uint32_t serial,
struct wl_surface *s, struct wl_array *keys)
{
last_serial = serial;
win.mode |= MODE_FOCUSED;
if (IS_SET(MODE_FOCUS))
ttywrite("\033[I", 3, 0);
imeenable();
}
static void
keyboardleave(void *data, struct wl_keyboard *kb, uint32_t serial,
struct wl_surface *s)
{
last_serial = serial;
pendingflush();
win.mode &= ~MODE_FOCUSED;
if (IS_SET(MODE_FOCUS))
ttywrite("\033[O", 3, 0);
stoprepeat();
imedisable();
}
static void
keyboardkey(void *data, struct wl_keyboard *kb, uint32_t serial,
uint32_t time, uint32_t key, uint32_t state)
{
xkb_keycode_t keycode = key + 8;
last_serial = serial;
if (!xkbstate)
return;
if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
xkb_state_update_key(xkbstate, keycode, XKB_KEY_DOWN);
updatemods();
keypress(keycode);
startrepeat(keycode);
} else {
xkb_state_update_key(xkbstate, keycode, XKB_KEY_UP);
updatemods();
if (keycode == repeatkey)
stoprepeat();
}
}
static void
keyboardmodifiers(void *data, struct wl_keyboard *kb, uint32_t serial,
uint32_t depressed, uint32_t latched, uint32_t locked,
uint32_t group)
{
last_serial = serial;
if (!xkbstate)
return;
xkb_state_update_mask(xkbstate, depressed, latched, locked, 0, 0, group);
updatemods();
}
static void
keyboardrepeat(void *data, struct wl_keyboard *kb, int32_t rate,
int32_t delay)
{
if (rate <= 0 || delay <= 0) {
repeatrate = 0;
stoprepeat();
return;
}
repeatrate = rate;
repeatdelay = delay;
if (repeatkey)
startrepeat(repeatkey);
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seatcapabilities,
.name = seatname,
};
static const struct wl_keyboard_listener keyboard_listener = {
.keymap = keyboardkeymap,
.enter = keyboardenter,
.leave = keyboardleave,
.key = keyboardkey,
.modifiers = keyboardmodifiers,
.repeat_info = keyboardrepeat,
};
/* ---- pointer ---- */
static void
pointerenter(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *s, wl_fixed_t sx, wl_fixed_t sy)
{
pointer_serial = serial;
last_serial = serial;
pointer_x = topx(wl_fixed_to_double(sx));
pointer_y = topx(wl_fixed_to_double(sy));
showpointer(serial);
}
static void
pointerleave(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *s)
{
pointer_serial = 0;
}
static void
pointermotion(void *data, struct wl_pointer *p, uint32_t time,
wl_fixed_t sx, wl_fixed_t sy)
{
MouseEvent ev;
memset(&ev, 0, sizeof(ev));
ev.type = 0;
ev.x = pointer_x = topx(wl_fixed_to_double(sx));
ev.y = pointer_y = topx(wl_fixed_to_double(sy));
ev.state = buttonstate | modmask();
ev.time = time;
bmotion(&ev);
}
static void
pointerbutton(void *data, struct wl_pointer *p, uint32_t serial,
uint32_t time, uint32_t button, uint32_t state)
{
MouseEvent ev;
uint b;
switch (button) {
case BTN_LEFT:
b = Button1;
break;
case BTN_MIDDLE:
b = Button2;
break;
case BTN_RIGHT:
b = Button3;
break;
case BTN_SIDE:
b = 8;
break;
case BTN_EXTRA:
b = 9;
break;
default:
return;
}
pointer_serial = serial;
last_serial = serial;
memset(&ev, 0, sizeof(ev));
ev.x = pointer_x;
ev.y = pointer_y;
ev.button = b;
ev.serial = serial;
ev.time = time;
ev.state = buttonstate | modmask();
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
ev.type = 1;
bpress(&ev);
buttonstate |= buttonmask(b);
} else {
ev.type = 2;
brelease(&ev);
buttonstate &= ~buttonmask(b);
}
}
static void
pointeraxis(void *data, struct wl_pointer *p, uint32_t time, uint32_t axis,
wl_fixed_t value)
{
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
axis_y += wl_fixed_to_double(value);
else
axis_x += wl_fixed_to_double(value);
}
static void
pointeraxisdisc(void *data, struct wl_pointer *p, uint32_t axis,
int32_t value)
{
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
axis_dy += value;
else
axis_dx += value;
}
static void
pointeraxissource(void *data, struct wl_pointer *p, uint32_t source)
{
}
static void
pointeraxisstop(void *data, struct wl_pointer *p, uint32_t time,
uint32_t axis)
{
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
axis_dy = 0;
axis_y = 0;
} else {
axis_dx = 0;
axis_x = 0;
}
}
static void
wheel(uint button)
{
MouseEvent ev;
memset(&ev, 0, sizeof(ev));
ev.x = pointer_x;
ev.y = pointer_y;
ev.button = button;
ev.state = buttonstate | modmask();
ev.type = 1;
bpress(&ev);
ev.type = 2;
brelease(&ev);
}
static void
pointerframe(void *data, struct wl_pointer *p)
{
int n, i;
if (axis_dy) {
n = MIN(axis_dy < 0 ? -axis_dy : axis_dy, 8);
for (i = 0; i < n; i++)
wheel(axis_dy < 0 ? Button4 : Button5);
} else if (axis_y <= -10.0 || axis_y >= 10.0) {
wheel(axis_y < 0 ? Button4 : Button5);
}
if (axis_dx) {
n = MIN(axis_dx < 0 ? -axis_dx : axis_dx, 8);
for (i = 0; i < n; i++)
wheel(axis_dx < 0 ? Button6 : Button7);
} else if (axis_x <= -10.0 || axis_x >= 10.0) {
wheel(axis_x < 0 ? Button6 : Button7);
}
axis_dy = 0;
axis_dx = 0;
axis_y = 0;
axis_x = 0;
}
static const struct wl_pointer_listener pointer_listener = {
.enter = pointerenter,
.leave = pointerleave,
.motion = pointermotion,
.button = pointerbutton,
.axis = pointeraxis,
.frame = pointerframe,
.axis_source = pointeraxissource,
.axis_stop = pointeraxisstop,
.axis_discrete = pointeraxisdisc,
};
void
setpointermotion(int set)
{
/* The compositor always sends motion events. */
(void)set;
}
/* ---- mouse selection and reporting ---- */
static uint
buttonmask(uint button)
{
return button == Button1 ? Button1Mask
: button == Button2 ? Button2Mask
: button == Button3 ? Button3Mask
: button == Button4 ? Button4Mask
: button == Button5 ? Button5Mask
: 0;
}
static int
evcol(MouseEvent *e)
{
int x = e->x - win.hborderpx;
LIMIT(x, 0, win.tw - 1);
return x / win.cw;
}
static int
evrow(MouseEvent *e)
{
int y = e->y - win.vborderpx;
LIMIT(y, 0, win.th - 1);
return y / win.ch;
}
static void
mousesel(MouseEvent *e, int done)
{
int type, seltype = SEL_REGULAR;
uint state = e->state & ~(Button1Mask | forcemousemod);
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());
}
static void
mousereport(MouseEvent *e)
{
int len, x = evcol(e), y = evrow(e),
button = e->button, state = e->state;
char buf[40];
static int ox, oy;
/* from urxvt */
if (e->type == 0) {
if (x == ox && y == oy)
return;
if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))
return;
/* MOUSE_MOTION: no reporting if no button is pressed */
if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)
return;
button = oldbutton + 32;
ox = x;
oy = y;
} else {
if (!IS_SET(MODE_MOUSESGR) && e->type == 2) {
button = 3;
} else {
button -= Button1;
if (button >= 3)
button += 64 - 3;
}
if (e->type == 1) {
oldbutton = button;
ox = x;
oy = y;
} else if (e->type == 2) {
oldbutton = 3;
/* MODE_MOUSEX10: no button release reporting */
if (IS_SET(MODE_MOUSEX10))
return;
if (button == 64 || button == 65)
return;
}
}
if (!IS_SET(MODE_MOUSEX10)) {
button += ((state & ShiftMask) ? 4 : 0)
+ ((state & Mod4Mask) ? 8 : 0)
+ ((state & ControlMask) ? 16 : 0);
}
if (IS_SET(MODE_MOUSESGR)) {
len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c",
button, x + 1, y + 1,
e->type == 2 ? 'm' : 'M');
} else if (x < 223 && y < 223) {
len = snprintf(buf, sizeof(buf), "\033[M%c%c%c",
32 + button, 32 + x + 1, 32 + y + 1);
} else {
return;
}
ttywrite(buf, len, 0);
}
static int
mouseaction(MouseEvent *e, uint release)
{
MouseShortcut *ms;
/* ignore Button<N>mask for Button<N> - it's set on release */
uint state = e->state & ~buttonmask(e->button);
for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
if (ms->release == release &&
ms->button == e->button &&
(match(ms->mod, state) || /* exact or forced */
match(ms->mod, state & ~forcemousemod))) {
ms->func(&(ms->arg));
return 1;
}
}
return 0;
}
static void
bpress(MouseEvent *e)
{
struct timespec now;
int snap;
if (IS_SET(MODE_MOUSE) && !(e->state & forcemousemod)) {
mousereport(e);
return;
}
if (mouseaction(e, 0))
return;
if (e->button == 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;
selstart(evcol(e), evrow(e), snap);
}
}
static void
brelease(MouseEvent *e)
{
if (IS_SET(MODE_MOUSE) && !(e->state & forcemousemod)) {
mousereport(e);
return;
}
if (mouseaction(e, 1))
return;
if (e->button == Button1)
mousesel(e, 1);
}
static void
bmotion(MouseEvent *e)
{
if (pointer_hidden)
showpointer(pointer_serial ? pointer_serial : last_serial);
if (IS_SET(MODE_MOUSE) && !(e->state & forcemousemod)) {
mousereport(e);
return;
}
mousesel(e, 0);
}
/* ---- st.c facing helpers ---- */
static void
zoom(const Arg *arg)
{
Arg larg;
larg.f = usedfontsize + arg->f;
zoomabs(&larg);
}
static void
zoomabs(const Arg *arg)
{
unloadfonts();
loadfonts(usedfont, arg->f);
if (started)
cresize(win.lw, win.lh);
redraw();
}
static void
zoomreset(const Arg *arg)
{
Arg larg;
(void)arg;
if (defaultfontsize > 0) {
larg.f = defaultfontsize;
zoomabs(&larg);
}
}
static void
ttysend(const Arg *arg)
{
ttywrite(arg->s, strlen(arg->s), 1);
}
void
bell(void)
{
if (bellvolume && systembell)
xdg_system_bell_v1_ring(systembell, surface);
}
void
setmode(int set, unsigned int flags)
{
int mode = win.mode;
MODBIT(win.mode, set, flags);
if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE))
redraw();
}
int
setcursor(int cursor)
{
if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
return 1;
win.cursor = cursor;
return 0;
}
void
settitle(char *p)
{
DEFAULT(p, opt_title);
if (!p)
p = "st";
xdg_toplevel_set_title(xdg_toplevel, p);
}
void
toggle_winmode(int flag)
{
win.mode ^= flag;
}
void
keyboard_select(const Arg *dummy)
{
(void)dummy;
win.mode ^= trt_kbdselect(-1, NULL, 0);
}
/* ---- registry / setup ---- */
static void
registryglobal(void *data, struct wl_registry *reg, uint32_t name,
const char *interface, uint32_t version)
{
if (!strcmp(interface, wl_compositor_interface.name)) {
compositor = wl_registry_bind(reg, name, &wl_compositor_interface,
MIN(version, 6));
} else if (!strcmp(interface, wl_shm_interface.name)) {
shm = wl_registry_bind(reg, name, &wl_shm_interface, 1);
wl_shm_add_listener(shm, &shm_listener, NULL);
} else if (!strcmp(interface, xdg_wm_base_interface.name)) {
wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface,
MIN(version, 6));
xdg_wm_base_add_listener(wm_base, &wm_base_listener, NULL);
} else if (!strcmp(interface, wl_seat_interface.name)) {
seat = wl_registry_bind(reg, name, &wl_seat_interface,
MIN(version, 7));
} else if (!strcmp(interface, wl_data_device_manager_interface.name)) {
data_manager = wl_registry_bind(reg, name,
&wl_data_device_manager_interface, 1);
} else if (!strcmp(interface,
zwp_primary_selection_device_manager_v1_interface.name)) {
primary_manager = wl_registry_bind(reg, name,
&zwp_primary_selection_device_manager_v1_interface, 1);
} else if (!strcmp(interface, zwp_text_input_manager_v3_interface.name)) {
text_input_manager = wl_registry_bind(reg, name,
&zwp_text_input_manager_v3_interface, 1);
} else if (!strcmp(interface, wp_cursor_shape_manager_v1_interface.name)) {
cursor_shape_manager = wl_registry_bind(reg, name,
&wp_cursor_shape_manager_v1_interface, 1);
} else if (!strcmp(interface,
wp_fractional_scale_manager_v1_interface.name)) {
fractional_manager = wl_registry_bind(reg, name,
&wp_fractional_scale_manager_v1_interface, 1);
} else if (!strcmp(interface, wp_viewporter_interface.name)) {
viewporter = wl_registry_bind(reg, name, &wp_viewporter_interface, 1);
} else if (!strcmp(interface, xdg_system_bell_v1_interface.name)) {
systembell = wl_registry_bind(reg, name,
&xdg_system_bell_v1_interface, 1);
}
}
static void
registryglobalremove(void *data, struct wl_registry *reg, uint32_t name)
{
}
static void
shmformat(void *data, struct wl_shm *s, uint32_t format)
{
if (format == WL_SHM_FORMAT_ARGB8888)
shm_argb = 1;
}
static const struct wl_registry_listener registry_listener = {
.global = registryglobal,
.global_remove = registryglobalremove,
};
static const struct wl_shm_listener shm_listener = {
.format = shmformat,
};
static void
flushdisplay(void)
{
struct pollfd pfd;
pfd.fd = wl_display_get_fd(display);
pfd.events = POLLOUT;
while (wl_display_flush(display) < 0) {
if (errno != EAGAIN)
die("wl_display_flush: %s\n", strerror(errno));
if (poll(&pfd, 1, -1) < 0 && errno != EINTR)
die("poll: %s\n", strerror(errno));
}
}
static void
wlinit(int ncols, int nrows)
{
int bw;
signal(SIGPIPE, SIG_IGN);
display = wl_display_connect(NULL);
if (!display)
die("can't connect to Wayland display\n");
registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_roundtrip(display);
if (!compositor)
die("compositor does not support wl_compositor\n");
if (!wm_base)
die("compositor does not support xdg_wm_base\n");
if (!shm)
die("compositor does not support wl_shm\n");
xkbcontext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!xkbcontext)
die("failed to create xkb context\n");
composetable = xkb_compose_table_new_from_locale(xkbcontext,
setlocale(LC_CTYPE, NULL),
XKB_COMPOSE_COMPILE_NO_FLAGS);
if (composetable)
composestate = xkb_compose_state_new(composetable,
XKB_COMPOSE_STATE_NO_FLAGS);
if (seat) {
wl_seat_add_listener(seat, &seat_listener, NULL);
if (data_manager) {
data_device = wl_data_device_manager_get_data_device(
data_manager, seat);
wl_data_device_add_listener(data_device,
&data_device_listener, NULL);
}
if (primary_manager) {
primary_device =
zwp_primary_selection_device_manager_v1_get_device(
primary_manager, seat);
zwp_primary_selection_device_v1_add_listener(
primary_device, &primary_device_listener, NULL);
}
if (text_input_manager) {
text_input = zwp_text_input_manager_v3_get_text_input(
text_input_manager, seat);
zwp_text_input_v3_add_listener(text_input,
&text_input_listener, NULL);
}
}
/* seat capabilities and wl_shm formats */
wl_display_roundtrip(display);
if (!shm_argb)
die("compositor does not support WL_SHM_FORMAT_ARGB8888\n");
if (!FcInit())
die("could not init fontconfig.\n");
if (FT_Init_FreeType(&ftlib))
die("could not init freetype.\n");
surface = wl_compositor_create_surface(compositor);
wl_surface_add_listener(surface, &surface_listener, NULL);
xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface);
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);
xdg_toplevel_set_app_id(xdg_toplevel, opt_class ? opt_class : termname);
settitle(NULL);
if (fractional_manager) {
fractional = wp_fractional_scale_manager_v1_get_fractional_scale(
fractional_manager, surface);
wp_fractional_scale_v1_add_listener(fractional,
&fractional_listener, NULL);
}
if (viewporter && fractional_manager)
viewport = wp_viewporter_get_viewport(viewporter, surface);
/* An empty commit is required for the initial configure. */
wl_surface_commit(surface);
while (!configured && wl_display_dispatch(display) != -1)
;
if (!configured)
die("compositor did not send xdg_surface.configure\n");
/* Pick up fractional-scale / preferred_buffer_scale. */
wl_display_roundtrip(display);
if (have_frac_scale)
scale = frac_scale / 120.0;
else if (surface_scale > 0)
scale = surface_scale;
else
scale = 1.0;
use_viewport = have_frac_scale && viewporter != NULL;
loadcols();
usedfont = opt_font ? opt_font : font;
loadfonts(usedfont, 0);
if (win.lw <= 0 || win.lh <= 0) {
bw = borderpxdev();
win.lw = (int)ceil((2.0 * bw + ncols * win.cw) / scale);
win.lh = (int)ceil((2.0 * bw + nrows * win.ch) / scale);
}
updatesize();
wl_display_flush(display);
}
/* ---- main loop ---- */
static void
run(void)
{
int ttyfd, drawing, npfd, xev, i;
struct timespec now, lastblink, trigger;
struct pollfd *pfds = NULL;
Transfer *t;
double timeout;
ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
started = 1;
cresize(win.lw, win.lh);
draw();
flushdisplay();
clock_gettime(CLOCK_MONOTONIC, &lastblink);
for (timeout = -1, drawing = 0;;) {
npfd = 3;
for (t = transfers; t; t = t->next)
npfd++;
pfds = xrealloc(pfds, npfd * sizeof(*pfds));
memset(pfds, 0, npfd * sizeof(*pfds));
pfds[0].fd = wl_display_get_fd(display);
pfds[0].events = POLLIN;
pfds[1].fd = ttyfd;
pfds[1].events = POLLIN;
pfds[2].fd = repeatfd;
pfds[2].events = POLLIN;
i = 3;
for (t = transfers; t; t = t->next, i++) {
pfds[i].fd = t->fd;
pfds[i].events = t->writing ? POLLOUT : POLLIN;
}
if (wl_display_dispatch_pending(display) > 0)
timeout = 0; /* existing events might not set the fd */
flushdisplay();
if (pendinglen) {
struct timespec pnow;
double left;
clock_gettime(CLOCK_MONOTONIC, &pnow);
left = TIMEDIFF(pendinguntil, pnow);
if (left <= 0)
pendingflush();
else if (timeout < 0 || left < timeout)
timeout = left;
}
if (poll(pfds, npfd, timeout < 0 ? -1 : (int)timeout) < 0) {
if (errno == EINTR)
continue;
die("poll failed: %s\n", strerror(errno));
}
clock_gettime(CLOCK_MONOTONIC, &now);
xev = 0;
if (pfds[1].revents)
ttyread();
if (pfds[2].revents)
repeattick();
if (npfd > 3)
transferspoll(pfds + 3);
if (pfds[0].revents) {
if (wl_display_dispatch(display) < 0)
die("wayland connection lost\n");
xev = 1;
}
if (pending_event) {
pending_event = 0;
xev = 1;
}
/*
* 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,
* and eventually draw even without idle after maxlatency ms.
*/
if (pfds[1].revents || xev) {
if (!drawing) {
trigger = now;
drawing = 1;
}
timeout = (maxlatency - TIMEDIFF(now, trigger)) /
maxlatency * minlatency;
if (timeout > 0)
continue; /* we have time, try to find idle */
}
/* idle detected or maxlatency exhausted -> draw */
timeout = -1;
if (blinktimeout && tattrset(ATTR_BLINK)) {
timeout = blinktimeout - TIMEDIFF(now, lastblink);
if (timeout <= 0) {
if (-timeout > blinktimeout) /* start visible */
win.mode |= MODE_BLINK;
win.mode ^= MODE_BLINK;
tsetdirtattr(ATTR_BLINK);
lastblink = now;
timeout = blinktimeout;
}
}
draw();
flushdisplay();
drawing = 0;
}
}
static void
usage(void)
{
die("usage: %s [-aiv] [-A alpha] [-c class] [-f font] [-g geometry]"
" [-o file]\n"
" [-T title] [-t title]"
" [[-e] command [args ...]]\n"
" %s [-aiv] [-A alpha] [-c class] [-f font] [-g geometry]"
" [-o file]\n"
" [-T title] [-t title] -l line"
" [stty_args ...]\n", argv0, argv0);
}
static void
parsegeometry(char *s)
{
char *p;
long w, h;
w = strtol(s, &p, 10);
h = 0;
if (*p == 'x' || *p == 'X')
h = strtol(p + 1, &p, 10);
if (w > 0)
cols = w;
if (h > 0)
rows = h;
if (*p == '+' || *p == '-')
fprintf(stderr, "%s: ignoring window position in -g\n",
argv0);
}
int
main(int argc, char *argv[])
{
win.mode = MODE_NUMLOCK;
setcursor(cursorshape);
ARGBEGIN {
case 'a':
allowaltscreen = 0;
break;
case 'A':
opt_alpha = EARGF(usage());
break;
case 'c':
opt_class = EARGF(usage());
break;
case 'e':
if (argc > 0)
--argc, ++argv;
goto run;
case 'f':
opt_font = EARGF(usage());
break;
case 'g':
parsegeometry(EARGF(usage()));
break;
case 'i':
opt_fixed = 1;
break;
case 'l':
opt_line = EARGF(usage());
break;
case 'o':
opt_io = EARGF(usage());
break;
case 't':
case 'T':
opt_title = 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, "");
cols = MAX(cols, 1);
rows = MAX(rows, 1);
tnew(cols, rows);
wlinit(cols, rows);
selinit();
run();
return 0;
}