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