-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfiletwm.c
1842 lines (1643 loc) · 57.9 KB
/
filetwm.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* See LICENSE file for copyright and license details.
*
* This is a minimalistic fork to dwm, aiming to be smaller, simpler
* and friendlier.
*
* Filet-Lignux's dynamic window manager is designed like any other X client.
* It is driven through handling X events. In contrast to other X clients,
* a window manager selects for SubstructureRedirectMask on the root window,
* to receive events about window (dis-)appearance. Only one X connection at a
* time is allowed to select for this event mask.
*
* Each child of the root window is called a client, except windows which have
* set the override_redirect flag. Clients are organized in a linked client
* list. Each client contains a bit array to indicate the tags (workspaces)
* of a client.
*
* Keyboard shortcuts are organized as arrays.
*
* Mouse motion tracking governs window focus, along with
* a click-to-raise behavior. Mouse motion is stateful and supports different
* drag-modes for moving and resizing windows.
*
* Consult the included man page for further documentation.
*
* To understand everything else, start reading main().
*/
#include <dlfcn.h>
#include <dirent.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <X11/cursorfont.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/Xrandr.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <X11/XF86keysym.h>
#include <X11/Xft/Xft.h>
#include <X11/Xlib.h>
#include <X11/Xproto.h>
#include <X11/Xutil.h>
/* basic macros */
#define DIE(M) {fputs(M, stderr); exit(1);}
#define LOADCONF(P,C) ((*(void **)(&C)) = dlsym(dlopen(P, RTLD_LAZY), "config"))
/* leave the loaded lib in memory until process cleanup */
#define KEYMASK(mask) (mask & (ShiftMask|ControlMask|Mod1Mask|Mod4Mask))
#define KCODE(keysym) ((KeyCode)(XKeysymToKeycode(dpy, keysym)))
#define KCHAR(E, C, L) (Xutf8LookupString(XCreateIC(XOpenIM(dpy, 0, 0, 0),\
XNInputStyle, XIMPreeditNothing|XIMStatusNothing, NULL), E, C, L, NULL, NULL))
#define ISVISIBLE(C) ((C->tags & tagset))
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MOUSEINF(W,X,Y,M) (XQueryPointer(dpy,root,&dwin,&W,&X,&Y,&di,&di,&M))
#define PROPADD(P, W, A, T, S, V, E) {XChangeProperty(dpy, W, xatom[A], T,\
S, PropMode##P, (unsigned char *) V, E);}
#define PROPSET(W, A, T, S, V, E) PROPADD(Replace, W, A, T, S, V, E)
#define TEXTPAD (xfont->ascent + xfont->descent) /* side padding of text */
#define TEXTW(X) (drawntextwidth(X) + TEXTPAD)
/* edge dragging and region macros*/
#define INZONE(C, X, Y) (X >= C->x - C->bw && Y >= C->y - C->bw\
&& X <= C->x + WIDTH(C) + C->bw && Y <= C->y + HEIGHT(C) + C->bw)
#define MOVEZONE(C, X, Y) (INZONE(C, X, Y)\
&& (abs(C->x - X) <= C->bw || abs(C->y - Y) <= C->bw))
#define RESIZEZONE(C, X, Y) (INZONE(C, X, Y)\
&& (abs(C->x + WIDTH(C) - X) <= C->bw || abs(C->y + HEIGHT(C) - Y) <= C->bw))
/* monitor macros */
#define BARH (TEXTPAD + 2)
#define INMON(X, Y, M)\
(X >= M.mx && X < M.mx + M.mw && Y >= M.my && Y < M.my + M.mh)
#define MONNULL(M) (M.mx == 0 && M.my == 0 && M.mw == 0 && M.mh == 0)
#define SETMON(M, R) {M.mx = R.x; M.my = R.y; M.mw = R.width; M.mh = R.height;}
#define ONMON(C, M) INMON(C->x + WIDTH(C)/2, C->y + HEIGHT(C)/2, M)
/* window macros */
#define HEIGHT(X) ((X)->h + 2 * (X)->bw)
#define WIDTH(X) ((X)->w + 2 * (X)->bw)
/* virtual desktop macros */
#define TAGMASK ((1 << tagslen) - 1)
#define TAGSHIFT(TAGS, I) (I < 0 ? (TAGS >> -I) | (TAGS << (tagslen + I))\
: (TAGS << I) | (TAGS >> (tagslen - I)))
/* launcher macros */
#define NUMCMDS 8000
#define LENCMD 64
#define CMDCMP(I) (strncmp(cmdfilter, cmds[I], strlen(cmdfilter)))
#define CMDFIND(S,D) {for (int i = S; i>=0 && i<NUMCMDS && cmds[i][0]; i = i D)\
if (!CMDCMP(i)) {cmdi = i; break;}}
/* enums */
enum { fg, bg, mark, bdr, selbdr, colslen }; /* colors */
enum { NetSupported, NetWMName, NetWMState, NetWMCheck, /* EWMH atoms */
NetWMFullscreen, NetActiveWindow, NetWMWindowType,
NetWMWinDialog, NetClientList, NetCliStack, NetLast,
/* default atoms */
WMProtocols, WMDelete, WMState, WMTakeFocus, XAtomLast };
/* bar click regions */
enum { ClkStatus, ClkTagBar, ClkSelTag, ClkLast };
/* mouse motion modes */
enum { DragMove, DragSize, WinEdge, ZoomStack, CtrlNone };
/* window stack actions */
enum { CliPin, CliRaise, CliZoom, CliRemove, BarShow, BarHide, CliNone };
/* argument template for keyboard shortcut and bar click actions */
typedef union {
int i;
unsigned int ui;
const void *v;
} Arg;
/* bar click action */
typedef struct {
unsigned int click, button;
void (*func)(const Arg *arg);
const Arg arg;
} Button;
/* clients wrap managed windows */
typedef struct Client Client;
struct Client {
float mina, maxa;
/* actual, and intended (f*) positon and size */
int x, y, w, h, fx, fy, fw, fh;
int basew, baseh, maxw, maxh, minw, minh, bw, fbw, tile, chain, full;
unsigned int tags;
Client *next;
Window win;
};
/* keyboard shortcut action */
typedef struct {
unsigned int mod;
KeySym key;
void (*func)(const Arg *);
const Arg arg;
} Key;
/* A monitor could be a connected display.
* You can also have multiple monitors across displays if you
* want custom windowing regions. */
typedef struct { int mx, my, mw, mh; } Monitor; /* windowing region size */
/* function declarations callable from config plugins */
void grabresize(const Arg *arg);
void killclient(const Arg *arg);
void launcher(const Arg *arg);
void pin(const Arg *arg);
void quit(const Arg *arg);
void spawn(const Arg *arg);
void tag(const Arg *arg);
void stackgrab(const Arg *arg);
void stackshift(const Arg *arg);
void togglefloating(const Arg *arg);
void togglefullscreen(const Arg *arg);
void toggletag(const Arg *arg);
void view(const Arg *arg);
void viewshift(const Arg *arg);
void viewtagshift(const Arg *arg);
void zoom(const Arg *arg);
/* event handler function declarations */
static void buttonpress(XEvent *e);
static void clientmessage(XEvent *e);
static void configurerequest(XEvent *e);
static void destroynotify(XEvent *e);
static void expose(XEvent *e);
static void exthandler(XEvent *ev);
static void grabkeys(XEvent *e);
static void keypress(XEvent *e);
static void maprequest(XEvent *e);
static void propertynotify(XEvent *e);
static void unmapnotify(XEvent *e);
/* general function declarations */
static void focus(Client *c);
/* variables */
static char cmds[NUMCMDS][LENCMD], cmdfilter[LENCMD] = {'\0'}, stxt[256] = {
'f','i','l','e','t','-','w','m','\0',[255]='\0'};
static int sw, sh; /* X display screen geometry width, height */
static int (*xerrorxlib)(Display *, XErrorEvent *);
static unsigned int tagset = 1; /* mask for which workspaces are displayed */
/* The event handlers are organized in an array which is accessed
* whenever a new event has been fetched. The array indicies associate
* with event numbers. This allows event dispatching in O(1) time.
*/
static void (*handler[LASTEvent]) (XEvent *) = { /* XEvent callbacks */
[ButtonPress] = buttonpress,
[ClientMessage] = clientmessage,
[ConfigureRequest] = configurerequest,
[DestroyNotify] = destroynotify,
[Expose] = expose,
[GenericEvent] = exthandler,
[KeyPress] = keypress,
[MappingNotify] = grabkeys,
[MapRequest] = maprequest,
[PropertyNotify] = propertynotify,
[UnmapNotify] = unmapnotify,
};
static Atom xatom[XAtomLast]; /* holds X types */
static int end, domotion; /* event loop helpers */
static Display *dpy; /* X session display reference */
static Drawable drawable; /* canvas for drawing (bar) */
static XftDraw *drawablexft; /* font rendering for canvas */
static GC gc; /* graphics context */
/* references to managed windows */
static Client *clients, *pinned = NULL, *sel;
static Window barwin, root, wmcheckwin, lastcw = {0};
static int barfocus, barcmds, cmdi; /* bar status (force show / in launcher) */
static int ctrlmode = CtrlNone; /* mouse mode (resize/repos/arrange/etc) */
static Cursor curpoint, cursize; /* mouse cursor icons */
static XftColor cols[colslen]; /* colors (fg, bg, mark, bdr, selbdr) */
static XftFont *xfont; /* X font reference */
/* dummy variables */
static int di;
static unsigned long dl;
static unsigned int dui;
static Window dwin;
/***********************
* Configuration Section
* Allows config plugins to change config variables.
* The defaultconfig method has plugin compatible code but check the README
* for details including the flavors of the macros that will work in the plugin.
************************/
/* config assignment for single value variables */
#define S(T, N, V) N = V /* compatibily for plugin-flavor macro */
/* config assignment for arrays of variable length */
#define V(T, N, L, ...) do {static T _##N[] = __VA_ARGS__; N = _##N; L} while(0)
/* config assignment for pointer arrays with determinable length */
#define P(T, N, ...) V(T,N,,__VA_ARGS__;)
/* config assignment for arrays (populating a xxxlen variable) */
#define A(T, N, ...) V(T,N,N##len = sizeof _##N/sizeof *_##N;,__VA_ARGS__;)
/* configurable values (see defaultconfig) */
Monitor *mons;
char *font, **colors, **tags, **startup, **terminal, **upvol, **downvol,
**mutevol, **suspend, **poweroff, **dimup, **dimdown, **help;
int borderpx, snap, tagslen, monslen, keyslen, buttonslen;
int *barpos;
KeySym stackrelease, barshow;
Key *keys;
Button *buttons;
void defaultconfig(void) {
/* appearance */
S(int, borderpx, 1); /* border pixel width of windows */
S(int, snap, 8); /* edge snap pixel distance */
S(char*, font, "monospace:size=7");
/* colors (must be five colors: fg, bg, highlight, border, sel-border) */
P(char*, colors, { "#dddddd", "#111111", "#335577", "#555555", "#dd4422" });
/* virtual workspaces (must be 32 or less) */
A(char*, tags, { "1", "2", "3", "4", "5", "6", "7", "8", "9" });
/* monitor layout
Set mons to the number of monitors you want supported.
Initialise with {0} for autodetection of monitors,
otherwise set the position and size ({x,y,w,h}).
!!!Warning!!! maximum of 32 monitors supported.
e.g:
A(Monitor, mons, {
{2420, 0, 1020, 1080},
{1920, 0, 500, 1080},
{3440, 0, 400, 1080}
});
or to autodetect up to 3 monitors:
A(Monitor, mons, {{0}, {0}, {0}});
*/
A(Monitor, mons, {{0}});
/* position and width of the bar (x, y, w) */
V(int, barpos,, {0, 0, 640});
/* commands */
#define CMD(C) "sh", "-c", C, NULL
#define TRY(C,A) "(command -v "#C" && ("#C" "#A"||true))||"
#define TERM(A) CMD(TRY(alacritty,A)TRY(st,A)TRY(urxvt,A)TRY(xterm,A)\
"xsetroot -name \"need: alacritty/st/urxvt/xterm\"")
P(char*, terminal, {TERM()});
P(char*, help, {TERM(-e sh -c "man -l ~/.config/filetwmconf.1 || \
man filetwm || man -l $(dirname $FILETWM)/filetwm.1")});
#define VOLCMD(A) ("amixer -q set Master "#A"; xsetroot -name \"Volume: "\
"$(amixer sget Master | grep -m1 '%]' | "\
"sed -e 's/[^\\[]*\\[\\([0-9]*%\\).*\\[\\([onf]*\\).*/\\1 \\2/')\"")
P(char*, upvol, {CMD(VOLCMD("5%+"))});
P(char*, downvol, {CMD(VOLCMD("5%-"))});
P(char*, mutevol, {CMD(VOLCMD("toggle"))});
#define LOCKCMD(C) CMD(TRY(slock,C)TRY(i3lock, && C)\
"xsetroot -name \"need: slock/i3lock\"")
P(char*, suspend, {LOCKCMD(systemctl suspend -i)});
P(char*, poweroff, {LOCKCMD(systemctl poweroff -i)});
#define DIMCMD(A) ("xbacklight "#A" 5; xsetroot -name \"Brightness: "\
"$(xbacklight | cut -d. -f1)%\"")
P(char*, dimup, {CMD(DIMCMD("-inc"))});
P(char*, dimdown, {CMD(DIMCMD("-dec"))});
/* the startup command is run when filetwm opens */
P(char*, startup, {CMD(TRY(filetstatus,)"$(dirname $FILETWM)/filetstatus")});
/* keyboard shortcut definitions */
#define AltMask Mod1Mask
#define WinMask Mod4Mask
#define TK(KEY) { WinMask, XK_##KEY, view, {.ui = 1 << (KEY - 1)} }, \
{ WinMask|ShiftMask, XK_##KEY, tag, {.ui = 1 << (KEY - 1)} }, \
{ WinMask|AltMask, XK_##KEY, toggletag, {.ui = 1 << (KEY - 1)} },
/* Alt+Tab style window switch and lift behaviour key release */
S(KeySym, stackrelease, XK_Super_L);
/* Key to raise the bar for visibility when held down */
S(KeySym, barshow, XK_Super_L);
A(Key, keys, {
/* modifier / key, function / argument */
{ WinMask, XK_Escape, launcher, {.i = 1} },
{ WinMask|ControlMask, XK_space, spawn, {.v = &terminal } },
{ WinMask, XK_space, grabresize, {.i = DragSize } },
{ WinMask|AltMask, XK_space, grabresize, {.i = DragMove } },
{ WinMask, XK_BackSpace, togglefloating, {0} },
{ WinMask, XK_Return, togglefullscreen, {0} },
{ WinMask|AltMask, XK_Return, pin, {0} },
{ WinMask|ShiftMask, XK_Return, zoom, {0} },
{ WinMask, XK_Tab, stackgrab, {.i = +1 } },
{ WinMask|ShiftMask, XK_Tab, stackgrab, {.i = -1 } },
{ WinMask, XK_Up, stackshift, {.i = -1 } },
{ WinMask, XK_Down, stackshift, {.i = +1 } },
{ WinMask|ShiftMask, XK_Up, stackshift, {.i = -2 } },
{ WinMask|ShiftMask, XK_Down, stackshift, {.i = +2 } },
{ WinMask, XK_Left, viewshift, {.i = -1 } },
{ WinMask, XK_Right, viewshift, {.i = +1 } },
{ WinMask|ShiftMask, XK_Left, viewtagshift, {.i = -1 } },
{ WinMask|ShiftMask, XK_Right, viewtagshift, {.i = +1 } },
{ WinMask, XK_0, tag, {.ui = ~0 } },
{ WinMask|ControlMask|ShiftMask, XK_F4, quit, {0} },
{ WinMask, XK_F4, killclient, {0} },
{ WinMask|ShiftMask, XK_F4, spawn, {.v = &suspend } },
{ 0, XF86XK_AudioLowerVolume, spawn, {.v = &downvol } },
{ 0, XF86XK_AudioMute, spawn, {.v = &mutevol } },
{ 0, XF86XK_AudioRaiseVolume, spawn, {.v = &upvol } },
{ 0, XF86XK_Sleep, spawn, {.v = &suspend } },
{ 0, XF86XK_PowerOff, spawn, {.v = &poweroff } },
{ 0, XF86XK_MonBrightnessUp, spawn, {.v = &dimup } },
{ 0, XF86XK_MonBrightnessDown, spawn, {.v = &dimdown } },
TK(1) TK(2) TK(3) TK(4) TK(5) TK(6) TK(7) TK(8) TK(9)
});
/* bar actions */
A(Button, buttons, {
/* click, button, function / argument */
{ ClkSelTag, Button1, launcher, {.i = 1} },
{ ClkStatus, Button1, spawn, {.v = &help } },
{ ClkTagBar, Button1, view, {0} },
{ ClkTagBar, Button3, tag, {0} },
});
}
/* End Configuration Section
****************************/
/***********************
* Utility functions
************************/
/**
* Add a client into the the list after another client
* or at the top.
* @c: Client* - a pointer to the client to add to the list.
* @after: Client* - a pointer to the client to place the
* window after. NULL adds to the top of the list.
*/
void attach(Client *c, Client *after) {
c->next = after? after->next : clients;
*(after? &after->next : &clients) = c;
}
/**
* Send a configure event to a client, informing it of
* its recently updated windowing details.
*/
void configure(Client *c) {
XConfigureEvent ce;
ce.type = ConfigureNotify;
ce.display = dpy;
ce.event = c->win;
ce.window = c->win;
ce.x = c->x;
ce.y = c->y;
ce.width = c->w;
ce.height = c->h;
ce.border_width = c->bw;
ce.above = None;
ce.override_redirect = False;
XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
}
/**
* Remove a specific client from the list.
*/
void detach(Client *c) {
Client **tc;
for (tc = &clients; *tc && *tc != c; tc = &(*tc)->next);
*tc = c->next;
}
/**
* Query the X server for a window property.
*/
Atom getatomprop(Client *c, Atom prop) {
unsigned char *p = NULL;
Atom da, atom = None;
if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM,
&da, &di, &dl, &dl, &p) == Success && p) {
atom = *(Atom *)p;
XFree(p);
}
return atom;
}
/**
* Resize the window of the given client, if the values change,
* respecting edge snapping, client specified sizing constraints,
* and sizing implications of floating and fullscreen states.
* The resulting size is stored on the client's size attributes, and
* when in floating mode, the size before respecting
* adjustments and constraints is stored on the client's f* size
* attributes, for future reference.
* Also, the border width of the window is updated depending
* on the fullscreen state.
* @active: int(bool) - whether the size change should be forced
* to apply to its floating state.
*/
void resize(Client *c, int x, int y, int w, int h, int active) {
int m1, m2;
XWindowChanges wc;
/* set minimum possible size */
w = MAX(1, w);
h = MAX(1, h);
/* remain in visible area */
x = MAX(1 - w - 2*c->bw, MIN(sw - 1, x));
y = MAX(1 - h - 2*c->bw, MIN(sh - 1, y));
/* apply change to fixed persistant values, if relevant */
if ((!c->tile || active) && !c->full) {
c->fx = x;
c->fy = y;
c->fw = w;
c->fh = h;
}
/* snap edges for floating windows */
if (!c->tile && !c->full) {
/* find monitor so as to snap position to edges */
for (m1 = 0; m1 < monslen-1 && !INMON(x+snap, y+snap, mons[m1]); m1++);
for (m2 = 0; m2 < monslen-1 && !INMON(x+w-snap, y+h-snap, mons[m2]); m2++);
/* snap position */
x = (abs(mons[m1].mx - x) < snap) ? mons[m1].mx : x;
y = (abs(mons[m1].my - y) < snap) ? mons[m1].my : y;
/* snap size */
if (abs((mons[m2].mx + mons[m2].mw) - (x + w + 2*c->bw)) < snap)
w = mons[m2].mx + mons[m2].mw - x - 2*c->bw;
if (abs((mons[m2].my + mons[m2].mh) - (y + h + 2*c->bw)) < snap)
h = mons[m2].my + mons[m2].mh - y - 2*c->bw;
}
/* adjust for aspect limits */
/* see last two sentences in ICCCM 4.1.2.3 */
w -= c->basew;
h -= c->baseh;
if (c->mina > 0 && c->maxa > 0 && !c->full) {
if (c->maxa < (float)w / h)
w = h * c->maxa + 0.5;
else if (c->mina < (float)h / w)
h = w * c->mina + 0.5;
}
/* restore base dimensions and apply max and min dimensions */
w = MAX(w + c->basew, c->minw);
h = MAX(h + c->baseh, c->minh);
w = (c->maxw && !c->full) ? MIN(w, c->maxw) : w;
h = (c->maxh && !c->full) ? MIN(h, c->maxh) : h;
/* apply the resize if anything ended up changing */
if (x != c->x || y != c->y || w != c->w || h != c->h) {
c->x = wc.x = x;
c->y = wc.y = y;
c->w = wc.width = w;
c->h = wc.height = h;
/* fullscreen changes update the border width */
wc.border_width = c->bw;
XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc);
configure(c);
}
}
/**
* Reorders client window stack, front to back (respecting layers).
* Stack layer order is pinned, selected, floating, tiled, then fullscreen.
* Mode values changes the placement in the order
* stack of the given client:
* - CliPin: pinned window to the very top above all layers (or unpin).
* - CliRaise: temporarily show above all layers, but below pinned.
* If no client is passed with this, this will reselect
* the previous raised window.
* - CliZoom: bring the window to the top of stack.
* - CliRemove: Remove window from stack entirely.
* - BarShow: Raise bar (c ignored).
* - BarHide: Drop bar to its normal stack order (c ignored).
*/
void restack(Client *c, int mode) {
static Client *allraised[32] = {0}; /* raised window over all workspaces */
Client **raised = &allraised[0];
int barup, i = 0, j;
Window topstack[4] = {0};
XWindowChanges wc;
switch (mode) {
case CliPin:
/* toggle pinned state */
pinned = pinned != c ? c : NULL;
break;
case CliRemove:
detach(c);
pinned = pinned != c ? pinned : NULL;
for (int j = 0; j < tagslen; j++)
allraised[j] = allraised[j] != c ? allraised[j] : NULL;
sel = sel != c ? sel : NULL;
break;
case BarHide:
case BarShow:
if (barfocus == (mode == BarShow)) return;
barfocus = mode == BarShow;
focus(sel);
break;
case CliZoom:
if (c) {
detach(c);
attach(c, NULL);
}
}
/* find the raised window pointer for the first active workspace,
and raise the current client if needed */
for (j = 0; j < tagslen && !(tagset&1<<j); j++);
raised = &allraised[j];
if (mode == CliZoom || mode == CliRaise)
focus(c ? (*raised = c) : *raised);
/* start window stacking */
/* bar window is above all when bar is focused,
or under selected pinned or selected raised window.
pinned window is always above raised. */
XDeleteProperty(dpy, root, xatom[NetCliStack]);
barup = barfocus || (pinned != sel && *raised != sel);
if (barup) topstack[i++] = barwin;
if (pinned) {
topstack[i++] = pinned->win;
PROPADD(Prepend, root, NetCliStack, XA_WINDOW, 32, &pinned->win, 1);
}
if (*raised) {
topstack[i++] = (*raised)->win;
PROPADD(Prepend, root, NetCliStack, XA_WINDOW, 32, &(*raised)->win, 1);
}
if (!barup) topstack[i++] = barwin;
XRaiseWindow(dpy, (wc.sibling = topstack[0]));
wc.stack_mode = Below;
for (i = 1; i < 4 && topstack[i]; i++) {
XConfigureWindow(dpy, topstack[i], CWSibling|CWStackMode, &wc);
wc.sibling = topstack[i];
}
/* show windows in the standard layers */
/* order layers - floating then tiled then fullscreen (if not raised) */
/* 0=floating 1=tiled 2=fullscreen */
for (int l = 0; l < 3; l++)
for (c = clients; c; c = c->next)
if (c != pinned && c != *raised && (c->full?2:c->tile?1:0) == l) {
XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc);
PROPADD(Prepend, root, NetCliStack, XA_WINDOW, 32, &c->win, 1);
wc.sibling = c->win;
}
}
/**
* Send a message to a cliant via the XServer.
*/
int sendevent(const Client *c, Atom proto) {
int n;
Atom *protocols;
int exists = 0;
XEvent ev;
if (XGetWMProtocols(dpy, c->win, &protocols, &n)) {
while (!exists && n--)
exists = protocols[n] == proto;
XFree(protocols);
}
if (exists) {
ev.type = ClientMessage;
ev.xclient.window = c->win;
ev.xclient.message_type = xatom[WMProtocols];
ev.xclient.format = 32;
ev.xclient.data.l[0] = proto;
ev.xclient.data.l[1] = CurrentTime;
XSendEvent(dpy, c->win, False, NoEventMask, &ev);
}
return exists;
}
/**
* Signal handler that ensures zombie subprocesses
* are cleaned up immediately.
*/
void sigchld(int unused) {
/* self-register this method as the SIGCHLD handler (if haven't already) */
if (signal(SIGCHLD, sigchld) == SIG_ERR)
DIE("can't install SIGCHLD handler.\n");
/* immediately release resources associated with any zombie child */
while (0 < waitpid(-1, NULL, WNOHANG));
}
/**
* Retrieve size hint information for a client.
* Stores the sizing information for the client
* for future layout operations.
*/
void updatesizehints(Client *c) {
long msize;
XSizeHints size;
c->basew = c->baseh = c->maxw = c->maxh = c->minw = c->minh = 0;
c->maxa = c->mina = 0.0;
if (!XGetWMNormalHints(dpy, c->win, &size, &msize)) return;
if (size.flags & PBaseSize) {
c->basew = c->minw = size.base_width;
c->baseh = c->minh = size.base_height;
}
if (size.flags & PMaxSize) {
c->maxw = size.max_width;
c->maxh = size.max_height;
}
if (size.flags & PMinSize) {
c->minw = size.min_width;
c->minh = size.min_height;
}
if (size.flags & PAspect) {
c->mina = (float)size.min_aspect.y / size.min_aspect.x;
c->maxa = (float)size.max_aspect.x / size.max_aspect.y;
}
}
/**
* Returns a pointer to the client which manages the given X window,
* or NULL if the given X window is not a managed client.
*/
Client* wintoclient(Window w) {
Client *c;
for (c = clients; c && c->win != w; c = c->next);
return c;
}
/**
* Xlib error handler.
* There's no way to check accesses to destroyed
* windows, thus those cases are ignored (especially
* on UnmapNotify's). Other types of errors call Xlibs
* default error handler, which may call exit.
*/
int xerror(Display *dpy, XErrorEvent *ee) {
if (ee->error_code == BadWindow
|| (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
|| (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
|| (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
|| (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
|| (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
|| (ee->request_code == X_GrabButton && ee->error_code == BadAccess)
|| (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
|| (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
return 0;
else if (ee->request_code == X_ChangeWindowAttributes
&& ee->error_code == BadAccess)
DIE("filetwm: another window manager may already be running.\n");
fprintf(stderr, "filetwm: fatal error: request code=%d, error code=%d\n",
ee->request_code, ee->error_code);
return xerrorxlib(dpy, ee); /* may call exit */
}
/***********************
* General functions
************************/
/**
* Rearranges all windows to display the ones
* visible in the current workspace (tag) selection
* and tile all windows in the tiling layer.
* The selected window is brought to the top since
* it may have changed with a workspace (tag)
* selection change.
* Minimum tile size before squeezing windows is 50th of width.
* @active: Client* - if not NULL, this client will be allowed to
* free float so as to be dropped back into the tiling
* layer with another location or size.
* @drop: int(bool) - if true, move the active window into
* its new position in the stack and clear the remaining column.
*/
void arrange(Client *active, int drop) {
Client *c;
/* maximum of 32 monitors supported */
int m, rm;
int w[32]={0}, h[32]={0}, nw[32]={0}, nh[32], x[32]={0}, y[32]={0}, s[32];
/* ensure a visible window has focus */
focus(NULL);
/* hide and show clients for the current workspace */
for (c = clients; c; c = c->next)
XMoveWindow(dpy, c->win, ISVISIBLE(c) ? c->x : WIDTH(c) * -2, c->y);
/* evaluate the number of columns per monitor */
for (c = clients; c; c = c->next)
if (c->tile && !c->full && ISVISIBLE(c)) {
for (m = monslen-1; m > 0 && !ONMON(c, mons[m]); m--); /* find monitor */
/* increment the column count ensuring the first column leader */
nw[m] += !(c->chain = c->chain && nw[m]);
}
/* orient columns horizontally for vertical monitors */
#define ORIENT(C, R) (mons[m].mw > mons[m].mh ? C : R)
#define X ORIENT(x, y)
#define Y ORIENT(y, x)
#define W ORIENT(w, h)
#define H ORIENT(h, w)
#define MW ORIENT(mons[m].mw, mons[m].mh)
#define MH ORIENT(mons[m].mh, mons[m].mw)
#define INCOL(C) (ORIENT(C->x,C->y) > X[m] && ORIENT(C->x,C->y) < X[m]+W[m])
#define INROW(C) (ORIENT(C->y,C->x) > Y[m] && ORIENT(C->y,C->x) < Y[m]+H[m])
#define SMH (active && INCOL(active) ? MH-s[m] : MH)
/* tile all the relevant clients */
for (c = clients; c; c = c->next) {
if (!c->tile || c->full || !ISVISIBLE(c)) continue;
/* find the monitor placement */
for (m = monslen-1; m > 0 && !ONMON(c, mons[m]); m--);
/* arrange columns from the left */
if (!c->chain) {
X[m] += W[m];
W[m] = MW-X[m]-ORIENT(c->fw, c->fh) <= MW/50*nw[m] ? (MW-X[m])/nw[m] : \
nw[m]>1? ORIENT(c->fw, c->fh) : MW-X[m]; /* fitted tile width */
nw[m]--;
/* reset at column leader and find the number of rows */
Y[m] = H[m] = 0;
/* the roaming active window reserves a 25th of the screen in the
destination position.*/
s[m] = MH / 25;
nh[m] = 1;
for (Client *r = c->next; r; r = r->next)
if (r->tile && !r->full && ISVISIBLE(r)) {
for (rm = monslen-1; rm > 0 && !ONMON(r, mons[rm]); rm--);
if (rm == m && !r->chain) break;
if (rm == m) nh[m]++; /* increment the row count */
}
}
/* stack rows from the top */
Y[m] += H[m];
/* height of the tile, shrunk to make space for a roaming active window */
H[m] = SMH-Y[m]-ORIENT(c->fh, c->fw) <= SMH/50*nh[m] ? (SMH-Y[m])/nh[m] : \
nh[m]>1? ORIENT(c->fh, c->fw) : SMH-Y[m]; /* fitted tile height */
nh[m]--;
/* place the client into the tile position */
if (c != active)
resize(c, mons[m].mx+x[m], mons[m].my+y[m],
w[m]-2*c->bw, h[m]-2*c->bw, 0);
/* drop the active window into the new position and arrange again,
or just leave some room for the active window being dragged */
if (active && INCOL(active) && INROW(active)) {
if (drop && active != c) {
if (active->next) active->next->chain &= active->chain;
active->chain = 1;
detach(active);
attach(active, c);
arrange(NULL, 0);
return;
} else {
Y[m] += s[m];
s[m] = 0;
}
}
}
/* handle a dropping client landing off monitor,
or into place where it already was,
otherwise it should have been caught above */
if (active && drop) {
active->chain = 0;
arrange(NULL, 0);
}
}
/**
* Finds the width of the given text, when drawn.
* @text: the text to measure the width of.
*/
int drawntextwidth(const char *text) {
XGlyphInfo ext;
XftTextExtentsUtf8(dpy, xfont, (XftChar8*)text, strlen(text), &ext);
return ext.xOff;
}
/**
* Render the given text onto the bar's drawable with
* a given position and background color.
* @x: the horizontal position to start writing.
* @text: the text to write.
* @bg: the background color to use.
* Returns the horizontal position at the end of the writing.
*/
int drawbartext(int x, const char *text, const XftColor *bg) {
int ty = (BARH - (xfont->ascent + xfont->descent)) / 2 + xfont->ascent;
XSetForeground(dpy, gc, bg->pixel);
XFillRectangle(dpy, drawable, gc, x, 0, TEXTW(text), BARH);
XftDrawStringUtf8(drawablexft, &cols[fg], xfont, x + (TEXTPAD / 2), ty,
(XftChar8 *)text, strlen(text));
return x + TEXTW(text);
}
/**
* Re-render the bar, updating the status text and workspaces (tags).
* If the bar is in launcher mode, draw the launcher status instead.
*/
void drawbar() {
int i, x = 0, f = 0;
/* blank the drawable */
XSetForeground(dpy, gc, cols[bg].pixel);
XFillRectangle(dpy, drawable, gc, 0, 0, barpos[2], BARH);
if (barcmds) {
/* draw command filter (being typed) */
x = drawbartext(x, cmdfilter, &cols[bg]);
/* draw command matches */
for (i = cmdi; i < NUMCMDS && cmds[i][0] && x < barpos[2]; i++)
if (!CMDCMP(i))
x = drawbartext(x, cmds[i], &cols[f++==0?mark:bg]);
/* highlight typed text if no match */
if (CMDCMP(cmdi))
drawbartext(0, cmdfilter, &cols[mark]);
} else {
/* draw tags */
for (i = 0; i < tagslen; i++)
x = drawbartext(x, tags[i], &cols[tagset&1<<i ?mark:bg]);
/* draw status */
drawbartext(x, stxt, &cols[bg]);
}
/* display composited bar */
XCopyArea(dpy, drawable, barwin, gc, 0, 0, barpos[2], BARH, 0, 0);
}
/**
* Focus on the given client window, if provided,
* and if it is visible (on the current workspace),
* otherwise fallback to selecting the previous
* selection, if visible, or the highest visible
* client window in the stack.
*/
void focus(Client *c) {
/* if c is not set or not visible, update c to be
the currently selected window, if it is visible,
otherwise search for the next visible window in
the stack. */
if ((!c || !ISVISIBLE(c)) && (!(c = sel) || !ISVISIBLE(sel)))
/* search in layer order - floating then tiled then fullscreen */
for (int l = 0; l < 3 && (!c || !ISVISIBLE(c)); l++)
for (c = clients; c && ((c->full?2:c->tile?1:0) != l || !ISVISIBLE(c));
c = c->next);
/* unfocus the previously selected window */
if (sel && sel != c)
XSetWindowBorder(dpy, sel->win, cols[sel == pinned ? mark : bdr].pixel);
/* focus on the new window */
sel = c;
if (sel) {
/* catch the Click-to-Raise that could be coming */
XGrabButton(dpy, AnyButton, AnyModifier, sel->win, False,
ButtonPressMask, GrabModeSync, GrabModeSync, None, None);
/* set the window color */
XSetWindowBorder(dpy, sel->win, cols[selbdr].pixel);
}
if (sel && !barfocus) {
XSetInputFocus(dpy, sel->win, RevertToPointerRoot, CurrentTime);
PROPSET(root, NetActiveWindow, XA_WINDOW, 32, &sel->win, 1);
sendevent(sel, xatom[WMTakeFocus]);
} else {
XSetInputFocus(dpy, barwin, RevertToPointerRoot, CurrentTime);
XDeleteProperty(dpy, barwin, xatom[NetActiveWindow]);
}
/* reset the mouse motion last window cache */
lastcw = 0;
/* Refresh the stack in case the bar should now be shown */
restack(NULL, CliNone);
}
/**
* Register all the keyboard shortcuts with the x server
* so, as long as nothing else grabs the whole keyboard,
* we will get keypress events when they are triggered.
*/
void grabkeys(XEvent *e) {
/* NumLock assumed to be Mod2Mask */
unsigned int mods[] = { 0, LockMask, Mod2Mask, Mod2Mask|LockMask };
/* Register capture of all configured keyboard shortcuts. */
XUngrabKey(dpy, AnyKey, AnyModifier, root);
for (int i = 0; i < keyslen; i++)
for (int j = 0; j < sizeof mods/sizeof *mods && KCODE(keys[i].key); j++)
XGrabKey(dpy, KCODE(keys[i].key), keys[i].mod | mods[j], root,
True, GrabModeAsync, GrabModeAsync);
}
/**
* This ends the process started by grabresize.
* This is optimised to return quickly if not currently
* within a grabresize state.
* See grabresize.
*/
void grabresizeabort() {
/* release the drag */
XUngrabPointer(dpy, CurrentTime);
if (sel && sel->tile && (ctrlmode == DragMove || ctrlmode == DragSize))
arrange(ctrlmode == DragMove ? sel : NULL, 1);
ctrlmode = CtrlNone;
}
/**
* This is called for any mouse movement event and handles
* resizing during grabresize states (see grabresize),
* raise and lowering the bar for the trigger-key
* state (see barshow), watching for window-edge behaviour,
* and managing focus-follows-mouse behaviour.
*/
void motion() {
int rx, ry, x, y;
static int lx = 0, ly = 0;
unsigned int mask;
char keystate[32];
Window cw;
static Client *c = NULL;
/* capture pointer and motion details */
if (!MOUSEINF(cw, rx, ry, mask)) return;
x = rx - lx; lx = rx;
y = ry - ly; ly = ry;
#define WV(V) (sel->tile ? sel->V : sel->f##V)
/* handle any drag modes */
if (sel && ctrlmode == DragMove)
resize(sel, WV(x)+x, WV(y)+y, WV(w), WV(h), 1);
if (sel && ctrlmode == DragSize)
resize(sel, WV(x), WV(y), WV(w)+x, WV(h)+y, 1);
/* update the monitor layout to match any tiling changes */
if (sel && sel->tile && (ctrlmode == DragMove || ctrlmode == DragSize))
arrange(sel, 0);
if (ctrlmode == WinEdge && /* watch for mouse over window edge */
(!sel || (!MOVEZONE(sel, rx, ry) && !RESIZEZONE(sel, rx, ry))))
grabresizeabort();
if (ctrlmode != CtrlNone) return;