1 /*
2 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
3 * Copyright (C) 2004-2021 Kim Woelders
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies of the Software, its documentation and marketing & publicity
14 * materials, and acknowledgment shall be given in the documentation, materials
15 * and software packages that this Software was used.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24 /*
25 * Author: Merlin Hughes
26 * - merlin@merlin.org
27 *
28 * This code is free software.
29 *
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
33 */
34 #include "config.h"
35
36 #include <X11/Xlib.h>
37 #include <X11/keysym.h>
38
39 #include "E.h"
40 #include "desktops.h"
41 #include "emodule.h"
42 #include "ewins.h"
43 #include "focus.h"
44 #include "grabs.h"
45 #include "iclass.h"
46 #include "icons.h"
47 #include "screen.h"
48 #include "shapewin.h"
49 #include "tclass.h"
50 #include "tooltips.h"
51 #include "xwin.h"
52
53 static void
WarpShapeDraw(EWin * ewin)54 WarpShapeDraw(EWin * ewin)
55 {
56 static ShapeWin *shape_win = NULL;
57 int bl, br, bt, bb;
58
59 if (!ewin)
60 {
61 ShapewinDestroy(shape_win);
62 shape_win = NULL;
63 return;
64 }
65
66 if (!shape_win)
67 shape_win = ShapewinCreate(MR_BOX);
68 if (!shape_win)
69 return;
70
71 EwinBorderGetSize(ewin, &bl, &br, &bt, &bb);
72 ShapewinShapeSet(shape_win, MR_BOX, EoGetX(ewin), EoGetY(ewin),
73 ewin->client.w, ewin->client.h, bl, br, bt, bb);
74 EoMap(shape_win, 0);
75 }
76
77 typedef struct {
78 EWin *ewin;
79 Win win;
80 char *txt;
81 } WarplistItem;
82
83 typedef struct {
84 EObj o;
85 TextClass *tc;
86 ImageClass *ic;
87 int mw, mh, tw, th;
88 } WarpFocusWin;
89
90 static void WarpFocusHandleEvent(Win win, XEvent * ev, void *prm);
91
92 static WarpFocusWin *warpFocusWindow = NULL;
93
94 static int warpFocusIndex = 0;
95 static unsigned int warpFocusKey = 0;
96 static unsigned int warpFocusState = 0;
97 static int warplist_num = 0;
98 static WarplistItem *warplist;
99
100 #define ICON_PAD 2
101
102 static WarpFocusWin *
WarpFocusWinCreate(void)103 WarpFocusWinCreate(void)
104 {
105 WarpFocusWin *fw;
106
107 fw = ECALLOC(WarpFocusWin, 1);
108 if (!fw)
109 return fw;
110
111 EoInit(fw, EOBJ_TYPE_MISC, NoXID, 0, 0, 1, 1, 1, "Warp");
112 EoSetFloating(fw, 1);
113 EoSetLayer(fw, 20);
114 EoSetFade(fw, 1);
115 EoSetShadow(fw, 1);
116
117 EventCallbackRegister(EoGetWin(fw), WarpFocusHandleEvent, NULL);
118 ESelectInput(EoGetWin(fw), ButtonReleaseMask);
119
120 fw->tc = TextclassFind("WARPFOCUS", 0);
121 if (!fw->tc)
122 fw->tc = TextclassFind("COORDS", 1);
123
124 fw->ic = ImageclassFind("WARPFOCUS", 0);
125 if (!fw->ic)
126 fw->ic = ImageclassFind("COORDS", 1);
127
128 return fw;
129 }
130
131 #if 0
132 static void
133 WarpFocusWinDestroy(WarpFocusWin * fw)
134 {
135 EventCallbackUnregister(EoGetWin(fw), WarpFocusHandleEvent, NULL);
136 EoFini(fw);
137 Efree(fw);
138 }
139 #endif
140
141 static void
WarpFocusWinShow(WarpFocusWin * fw)142 WarpFocusWinShow(WarpFocusWin * fw)
143 {
144 WarplistItem *wi;
145 EImageBorder *pad;
146 EWin *ewin;
147 int i, x, y, w, h, ww, hh;
148 char s[1024], ss[32];
149 const char *fmt;
150
151 w = 0;
152 h = 0;
153 pad = ImageclassGetPadding(fw->ic);
154
155 for (i = 0; i < warplist_num; i++)
156 {
157 wi = warplist + i;
158 wi->win = ECreateWindow(EoGetWin(fw), 0, 0, 1, 1, 0);
159 EMapWindow(wi->win);
160
161 ewin = wi->ewin;
162 if (ewin->state.iconified)
163 fmt = "%s[%s]";
164 else if (ewin->state.shaded)
165 fmt = "%s=%s=";
166 else
167 fmt = "%s%s";
168 ss[0] = '\0';
169 if (Conf.warplist.showalldesks)
170 {
171 if (EoIsSticky(ewin) || ewin->state.iconified)
172 strcpy(ss, "[-] ");
173 else
174 Esnprintf(ss, sizeof(ss), "[%d] ", EoGetDeskNum(ewin));
175 }
176 Esnprintf(s, sizeof(s), fmt, ss, EwinGetTitle(ewin));
177 wi->txt = Estrdup(s);
178 TextSize(fw->tc, 0, 0, 0, wi->txt, &ww, &hh, 17);
179 if (ww > w)
180 w = ww;
181 if (hh > h)
182 h = hh;
183 }
184
185 fw->tw = w; /* Text size */
186 fw->th = h;
187 w += pad->left + pad->right;
188 h += pad->top + pad->bottom;
189 if (Conf.warplist.icon_mode != 0)
190 {
191 if (Conf.warplist.icon_mode != EWIN_ICON_MODE_APP_IMG &&
192 Conf.warplist.icon_mode != EWIN_ICON_MODE_IMG_APP)
193 Conf.warplist.icon_mode = EWIN_ICON_MODE_APP_IMG;
194 w += h;
195 }
196 fw->mw = w; /* Focus list item size */
197 fw->mh = h;
198
199 /* Reset shape */
200 EShapeSetMask(EoGetWin(fw), 0, 0, NoXID);
201
202 ScreenGetAvailableAreaByPointer(&x, &y, &ww, &hh, Conf.place.ignore_struts);
203 x += (ww - w) / 2;
204 y += (hh - h * warplist_num) / 2;
205 EoMoveResize(fw, x, y, w, h * warplist_num);
206
207 for (i = 0; i < warplist_num; i++)
208 EMoveResizeWindow(warplist[i].win, 0, (h * i), fw->mw, fw->mh);
209
210 EoMap(fw, 0);
211
212 /*
213 * Grab the keyboard. The grab is automatically released when
214 * WarpFocusHide unmaps warpFocusWindow.
215 */
216 GrabKeyboardSet(EoGetWin(fw));
217 GrabPointerSet(EoGetWin(fw), NoXID, 0);
218
219 TooltipsEnable(0);
220 }
221
222 static void
WarpFocusWinHide(WarpFocusWin * fw)223 WarpFocusWinHide(WarpFocusWin * fw)
224 {
225 int i;
226
227 EoUnmap(fw);
228 for (i = 0; i < warplist_num; i++)
229 {
230 EDestroyWindow(warplist[i].win);
231 Efree(warplist[i].txt);
232 }
233 #if 0 /* We might as well keep it around */
234 WarpFocusWinDestroy(fw);
235 warpFocusWindow = NULL;
236 #endif
237
238 TooltipsEnable(1);
239 }
240
241 static void
WarpFocusWinPaint(WarpFocusWin * fw)242 WarpFocusWinPaint(WarpFocusWin * fw)
243 {
244 int i, state, iw;
245 WarplistItem *wi;
246 EImageBorder *pad;
247
248 pad = ImageclassGetPadding(fw->ic);
249
250 for (i = 0; i < warplist_num; i++)
251 {
252 wi = warplist + i;
253
254 if (!EwinFindByPtr(wi->ewin))
255 wi->ewin = NULL;
256 if (!wi->ewin)
257 continue;
258
259 state = (i == warpFocusIndex) ? STATE_CLICKED : STATE_NORMAL;
260
261 ImageclassApply(fw->ic, wi->win, 0, 0, state);
262
263 iw = 0;
264 if (Conf.warplist.icon_mode != 0)
265 {
266 int icon_size = fw->mh - 2 * ICON_PAD;
267 EImage *im;
268
269 im = EwinIconImageGet(wi->ewin, icon_size,
270 Conf.warplist.icon_mode);
271 if (im)
272 {
273 EImageRenderOnDrawable(im, wi->win, NoXID,
274 EIMAGE_BLEND | EIMAGE_ANTI_ALIAS,
275 pad->left + ICON_PAD, ICON_PAD,
276 icon_size, icon_size);
277 EImageFree(im);
278 }
279 iw = fw->mh;
280 }
281
282 TextDraw(fw->tc, wi->win, NoXID, 0, 0, state, wi->txt,
283 pad->left + iw, pad->top, fw->tw, fw->th, 0, 0);
284 }
285
286 /* FIXME - Check shape */
287 EoShapeUpdate(fw, 1);
288 }
289
290 static void
WarpFocusShow(void)291 WarpFocusShow(void)
292 {
293 WarpFocusWin *fw = warpFocusWindow;
294
295 if (!warplist)
296 return;
297
298 if (!fw)
299 {
300 warpFocusWindow = fw = WarpFocusWinCreate();
301 if (!fw)
302 return;
303 }
304
305 if (!EoIsShown(fw))
306 WarpFocusWinShow(fw);
307
308 WarpFocusWinPaint(fw);
309 }
310
311 static void
WarpFocusHide(void)312 WarpFocusHide(void)
313 {
314 WarpFocusWin *fw = warpFocusWindow;
315
316 if (fw && EoIsShown(fw))
317 WarpFocusWinHide(fw);
318
319 EFREE_NULL(warplist);
320 warplist_num = 0;
321 }
322
323 static void
_WarpAddEwins(int samehead)324 _WarpAddEwins(int samehead)
325 {
326 EWin *const *lst;
327 EWin *ewin;
328 int i, num, head_ptr, head_ewin;
329 WarplistItem *wl;
330
331 head_ptr = ScreenGetCurrent();
332 if (head_ptr < 0 && !samehead)
333 return; /* Want ewins on different head but only one head */
334 head_ewin = head_ptr; /* Speed-up if one head */
335
336 lst = EwinListFocusGet(&num);
337 for (i = 0; i < num; i++)
338 {
339 ewin = lst[i];
340 /* Either visible or iconified or show all */
341 if (!(EwinIsOnScreen(ewin) || ewin->state.iconified ||
342 Conf.warplist.showalldesks))
343 continue;
344 /* Exclude windows that explicitely say so */
345 if (ewin->props.skip_focuslist || ewin->props.skip_ext_task)
346 continue;
347 /* Keep shaded windows if conf says so */
348 if (ewin->state.shaded && !Conf.warplist.showshaded)
349 continue;
350 /* Keep sticky windows if conf says so */
351 if (EoIsSticky(ewin) && !Conf.warplist.showsticky)
352 continue;
353 /* Keep iconified windows if conf says so */
354 if (ewin->state.iconified && !Conf.warplist.showiconified)
355 continue;
356 /* Current (pointer) head, or the rest */
357 if (head_ptr >= 0)
358 head_ewin = ScreenGetHead(EoGetX(ewin) + EoGetW(ewin) / 2,
359 EoGetY(ewin) + EoGetH(ewin) / 2);
360 if ((samehead && head_ewin != head_ptr) ||
361 (!samehead && head_ewin == head_ptr))
362 continue;
363
364 /* All good - add it */
365 warplist_num++;
366 warplist = EREALLOC(WarplistItem, warplist, warplist_num);
367 wl = warplist + warplist_num - 1;
368 wl->ewin = ewin;
369 }
370 }
371
372 void
WarpFocus(int delta)373 WarpFocus(int delta)
374 {
375 WarpFocusWin *fw = warpFocusWindow;
376 EWin *ewin;
377
378 /* Remember invoking keycode (ugly hack) */
379 if (!fw || !EoIsShown(fw))
380 {
381 warpFocusKey = Mode.events.last_keycode;
382 warpFocusState = Mode.events.last_keystate;
383 }
384
385 if (!warplist)
386 {
387 warplist_num = 0; /* Not necessary but silences clang */
388 _WarpAddEwins(1);
389 _WarpAddEwins(0);
390
391 /* Hmmm. Hack... */
392 if (warplist_num >= 2 && warplist[1].ewin == GetFocusEwin())
393 {
394 warplist[1].ewin = warplist[0].ewin;
395 warplist[0].ewin = GetFocusEwin();
396 }
397
398 warpFocusIndex = 0;
399 }
400
401 if (!warplist || warplist_num <= 0)
402 return;
403
404 warpFocusIndex = (warpFocusIndex + warplist_num + delta) % warplist_num;
405 ewin = warplist[warpFocusIndex].ewin;
406 if (!EwinFindByPtr(ewin))
407 ewin = NULL;
408 if (!ewin)
409 return;
410
411 WarpFocusShow();
412
413 if (!EwinIsOnScreen(ewin))
414 return;
415
416 if (Conf.warplist.show_shape)
417 WarpShapeDraw(ewin);
418
419 if (Conf.focus.raise_on_next)
420 EwinRaise(ewin);
421 if (Conf.focus.warp_on_next)
422 EwinWarpTo(ewin, 0);
423 if (Conf.warplist.warpfocused)
424 FocusToEWin(ewin, FOCUS_SET);
425 }
426
427 static void
WarpFocusClick(int ix)428 WarpFocusClick(int ix)
429 {
430 EWin *ewin;
431
432 if (!warplist)
433 return;
434 if (ix < 0 || ix >= warplist_num)
435 return;
436 if (ix == warpFocusIndex)
437 return;
438
439 warpFocusIndex = ix;
440 WarpFocusShow();
441
442 ewin = warplist[ix].ewin;
443 if (!EwinFindByPtr(ewin))
444 return;
445
446 if (Conf.focus.raise_on_next)
447 EwinRaise(ewin);
448
449 FocusToEWin(ewin, FOCUS_SET);
450 }
451
452 static void
WarpFocusFinish(void)453 WarpFocusFinish(void)
454 {
455 EWin *ewin;
456
457 ewin = warplist[warpFocusIndex].ewin;
458
459 WarpShapeDraw(NULL);
460 WarpFocusHide();
461
462 if (!EwinFindByPtr(ewin))
463 return;
464
465 EwinOpActivate(ewin, OPSRC_USER, Conf.warplist.raise_on_select);
466 if (Conf.warplist.warp_on_select)
467 EwinWarpTo(ewin, 0);
468 }
469
470 static void
WarpFocusHandleEvent(Win win __UNUSED__,XEvent * ev,void * prm __UNUSED__)471 WarpFocusHandleEvent(Win win __UNUSED__, XEvent * ev, void *prm __UNUSED__)
472 {
473 WarpFocusWin *fw = warpFocusWindow;
474 EX_KeySym keysym;
475 unsigned int mask;
476
477 if (!EoIsShown(fw))
478 return;
479
480 switch (ev->type)
481 {
482 case KeyPress:
483 if (ev->xkey.keycode == warpFocusKey)
484 {
485 if (((ev->xkey.state ^ warpFocusState) &
486 Mode.masks.mod_key_mask) == 0)
487 keysym = 0x80000000;
488 else
489 keysym = 0x80000001;
490 }
491 else
492 keysym = XLookupKeysym(&ev->xkey, 0);
493 switch (keysym)
494 {
495 default:
496 break;
497 case 0x80000000:
498 case XK_Down:
499 WarpFocus(1);
500 break;
501 case XK_Up:
502 case 0x80000001:
503 WarpFocus(-1);
504 break;
505 }
506 break;
507
508 case KeyRelease:
509 mask = 0;
510 EQueryPointer(NULL, NULL, NULL, NULL, &mask);
511 if ((mask & Mode.masks.mod_key_mask) == 0)
512 {
513 WarpFocusFinish();
514 break;
515 }
516 if (ev->xkey.keycode == warpFocusKey)
517 keysym = 0x80000000;
518 else
519 keysym = XLookupKeysym(&ev->xkey, 0);
520 switch (keysym)
521 {
522 case XK_Escape:
523 WarpFocusHide();
524 break;
525 default:
526 case 0x80000000:
527 case XK_Down:
528 case XK_Up:
529 break;
530 }
531 break;
532
533 case ButtonRelease:
534 WarpFocusClick((ev->xbutton.y * warplist_num) / EoGetH(fw));
535 break;
536 }
537 }
538
539 /*
540 * Warplist module
541 */
542
543 static const CfgItem WarplistCfgItems[] = {
544 CFG_ITEM_BOOL(Conf.warplist, enable, 1),
545 CFG_ITEM_BOOL(Conf.warplist, showsticky, 1),
546 CFG_ITEM_BOOL(Conf.warplist, showshaded, 1),
547 CFG_ITEM_BOOL(Conf.warplist, showiconified, 1),
548 CFG_ITEM_BOOL(Conf.warplist, showalldesks, 0),
549 CFG_ITEM_BOOL(Conf.warplist, warpfocused, 1),
550 CFG_ITEM_BOOL(Conf.warplist, raise_on_select, 1),
551 CFG_ITEM_BOOL(Conf.warplist, warp_on_select, 0),
552 CFG_ITEM_BOOL(Conf.warplist, show_shape, 0),
553 CFG_ITEM_INT(Conf.warplist, icon_mode, EWIN_ICON_MODE_APP_IMG),
554 };
555
556 extern const EModule ModWarplist;
557
558 const EModule ModWarplist = {
559 "warplist", "warp",
560 NULL,
561 {0, NULL},
562 MOD_ITEMS(WarplistCfgItems)
563 };
564