1/*
2 * Misc function implementation
3 *
4 * These are things that either don't fit neatly into another category,
5 * or fit into a category too small to be worth making individual files
6 * for.
7 */
8
9#include "ctwm.h"
10
11#include <stdlib.h>
12
13#include "animate.h"
14#include "ctwm_shutdown.h"
15#include "functions.h"
16#include "functions_defs.h"
17#include "functions_internal.h"
18#include "icons.h"
19#include "otp.h"
20#include "screen.h"
21#ifdef SOUNDS
22#include "sound.h"
23#endif
24#include "util.h"
25#include "win_iconify.h"
26#ifdef WINBOX
27#include "windowbox.h"
28#endif
29#include "workspace_utils.h"
30
31#include "ext/repl_str.h"
32
33
34
35/*
36 * Animation-related
37 */
38DFHANDLER(startanimation)
39{
40	StartAnimation();
41}
42
43DFHANDLER(stopanimation)
44{
45	StopAnimation();
46}
47
48DFHANDLER(speedupanimation)
49{
50	ModifyAnimationSpeed(1);
51}
52
53DFHANDLER(slowdownanimation)
54{
55	ModifyAnimationSpeed(-1);
56}
57
58
59
60/*
61 * Menu-related
62 */
63DFHANDLER(menu)
64{
65	/*
66	 * n.b.: The f.menu handler is all kinds of magic; it's actually
67	 * completely unrelated to pulling up the menu.
68	 *
69	 * When a button/key binding invokes f.menu to open up a menu, that's
70	 * actually handled in the KeyPress or ButtonPress handlers by
71	 * calling do{_key,}_menu().  When we descend into a submenu, that's
72	 * handled in KeyPress handler for keyboard navigation when we hit
73	 * the Right arrow, or inside the
74	 * event loop recapture in UpdateMenu() for mouse navigation when we
75	 * move it to the right side of the menu entry.
76	 *
77	 * This handler is only used by "invoking" a menu item; releasing the
78	 * mouse button on the left side without moving right to open out the
79	 * submenu, or hitting the Enter key.  All it does is immediately
80	 * invoke the default entry, if there is one.
81	 */
82	if(action && ! strncmp(action, "WGOTO : ", 8)) {
83		GotoWorkSpaceByName(/* XXXXX */ Scr->currentvs,
84		                                ((char *)action) + 8);
85	}
86	else {
87		MenuItem *item;
88
89		item = ActiveItem;
90		while(item && item->sub) {
91			if(!item->sub->defaultitem) {
92				break;
93			}
94			if(item->sub->defaultitem->func != F_MENU) {
95				break;
96			}
97			item = item->sub->defaultitem;
98		}
99		if(item && item->sub && item->sub->defaultitem) {
100			ExecuteFunction(item->sub->defaultitem->func,
101			                item->sub->defaultitem->action,
102			                w, tmp_win, eventp, context, pulldown);
103		}
104	}
105}
106
107
108DFHANDLER(pin)
109{
110	if(! ActiveMenu) {
111		return;
112	}
113	if(ActiveMenu->pinned) {
114		XUnmapWindow(dpy, ActiveMenu->w);
115		ActiveMenu->mapped = MRM_UNMAPPED;
116	}
117	else {
118		XWindowAttributes attr;
119		MenuRoot *menu;
120
121		if(ActiveMenu->pmenu == NULL) {
122			menu  = malloc(sizeof(MenuRoot));
123			*menu = *ActiveMenu;
124			menu->pinned = true;
125			menu->mapped = MRM_NEVER;
126			menu->width -= 10;
127			if(menu->pull) {
128				menu->width -= 16 + 10;
129			}
130			MakeMenu(menu);
131			ActiveMenu->pmenu = menu;
132		}
133		else {
134			menu = ActiveMenu->pmenu;
135		}
136		if(menu->mapped == MRM_MAPPED) {
137			return;
138		}
139		XGetWindowAttributes(dpy, ActiveMenu->w, &attr);
140		menu->x = attr.x;
141		menu->y = attr.y;
142		XMoveWindow(dpy, menu->w, menu->x, menu->y);
143		XMapRaised(dpy, menu->w);
144		menu->mapped = MRM_MAPPED;
145	}
146	PopDownMenu();
147}
148
149
150
151/*
152 * Alternate keymaps/contexts
153 */
154DFHANDLER(altkeymap)
155{
156	int alt, stat_;
157
158	if(! action) {
159		return;
160	}
161	stat_ = sscanf(action, "%d", &alt);
162	if(stat_ != 1) {
163		return;
164	}
165	if((alt < 1) || (alt > 5)) {
166		return;
167	}
168	AlternateKeymap = Alt1Mask << (alt - 1);
169	XGrabPointer(dpy, Scr->Root, True, ButtonPressMask | ButtonReleaseMask,
170	             GrabModeAsync, GrabModeAsync,
171	             Scr->Root, Scr->AlterCursor, CurrentTime);
172	func_reset_cursor = false;  // Leave special cursor alone
173	XGrabKeyboard(dpy, Scr->Root, True, GrabModeAsync, GrabModeAsync, CurrentTime);
174	return;
175}
176
177DFHANDLER(altcontext)
178{
179	AlternateContext = true;
180	XGrabPointer(dpy, Scr->Root, False, ButtonPressMask | ButtonReleaseMask,
181	             GrabModeAsync, GrabModeAsync,
182	             Scr->Root, Scr->AlterCursor, CurrentTime);
183	func_reset_cursor = false;  // Leave special cursor alone
184	XGrabKeyboard(dpy, Scr->Root, False, GrabModeAsync, GrabModeAsync, CurrentTime);
185	return;
186}
187
188
189
190/*
191 * A few trivial ctwm-control-ish meta-functions
192 */
193DFHANDLER(quit)
194{
195	DoShutdown();
196}
197
198DFHANDLER(restart)
199{
200	DoRestart(eventp->xbutton.time);
201}
202
203DFHANDLER(beep)
204{
205	XBell(dpy, 0);
206}
207
208DFHANDLER(trace)
209{
210	DebugTrace(action);
211}
212
213
214
215#ifdef WINBOX
216/*
217 * Special windowbox-related
218 */
219DFHANDLER(fittocontent)
220{
221	if(!tmp_win->iswinbox) {
222		XBell(dpy, 0);
223		return;
224	}
225	fittocontent(tmp_win);
226}
227#endif
228
229
230
231/*
232 * A few things that are sorta windows/icons related, but don't really
233 * fit with the window-targetted things in functions_win.
234 */
235DFHANDLER(showbackground)
236{
237	ShowBackground(Scr->currentvs, -1);
238}
239
240DFHANDLER(raiseicons)
241{
242	for(TwmWindow *t = Scr->FirstWindow; t != NULL; t = t->next) {
243		if(t->icon && t->icon->w) {
244			OtpRaise(t, IconWin);
245		}
246	}
247}
248
249DFHANDLER(rescuewindows)
250{
251	RescueWindows();
252}
253
254
255
256/*
257 * Despite the name, this is more like 'gotoworkspace' than the other
258 * 'warpto*' funcs, as it's just about switching your view, not anything
259 * going to a window.
260 */
261static void
262WarpToScreen(int n, int inc)
263{
264	Window dumwin;
265	int x, y, dumint;
266	unsigned int dummask;
267	ScreenInfo *newscr = NULL;
268
269	while(!newscr) {
270		/* wrap around */
271		if(n < 0) {
272			n = NumScreens - 1;
273		}
274		else if(n >= NumScreens) {
275			n = 0;
276		}
277
278		newscr = ScreenList[n];
279		if(!newscr) {                   /* make sure screen is managed */
280			if(inc) {                   /* walk around the list */
281				n += inc;
282				continue;
283			}
284			fprintf(stderr, "%s:  unable to warp to unmanaged screen %d\n",
285			        ProgramName, n);
286			XBell(dpy, 0);
287			return;
288		}
289	}
290
291	if(Scr->screen == n) {
292		return;        /* already on that screen */
293	}
294
295	PreviousScreen = Scr->screen;
296	XQueryPointer(dpy, Scr->Root, &dumwin, &dumwin, &x, &y,
297	              &dumint, &dumint, &dummask);
298
299	XWarpPointer(dpy, None, newscr->Root, 0, 0, 0, 0, x, y);
300	Scr = newscr;
301	return;
302}
303
304DFHANDLER(warptoscreen)
305{
306	if(strcmp(action, WARPSCREEN_NEXT) == 0) {
307		WarpToScreen(Scr->screen + 1, 1);
308	}
309	else if(strcmp(action, WARPSCREEN_PREV) == 0) {
310		WarpToScreen(Scr->screen - 1, -1);
311	}
312	else if(strcmp(action, WARPSCREEN_BACK) == 0) {
313		WarpToScreen(PreviousScreen, 0);
314	}
315	else {
316		WarpToScreen(atoi(action), 0);
317	}
318}
319
320
321
322/*
323 * Sound-related
324 */
325#ifdef SOUNDS
326DFHANDLER(togglesound)
327{
328	toggle_sound();
329}
330
331DFHANDLER(rereadsounds)
332{
333	reread_sounds();
334}
335#endif
336
337
338
339/*
340 * And executing an external program
341 */
342static void Execute(const char *_s);
343
344DFHANDLER(exec)
345{
346	PopDownMenu();
347	if(!Scr->NoGrabServer) {
348		XUngrabServer(dpy);
349		XSync(dpy, 0);
350	}
351	XUngrabPointer(dpy, CurrentTime);
352	XSync(dpy, 0);
353	Execute(action);
354}
355
356
357static void
358Execute(const char *_s)
359{
360	char *s;
361	char *_ds;
362	char *orig_display;
363	int restorevar = 0;
364	char *subs;
365
366	/* Seatbelt */
367	if(!_s) {
368		return;
369	}
370
371	/* Work on a local copy since we're mutating it */
372	s = strdup(_s);
373	if(!s) {
374		return;
375	}
376
377	/* Stash up current $DISPLAY value for resetting */
378	orig_display = getenv("DISPLAY");
379
380
381	/*
382	 * Build a display string using the current screen number, so that
383	 * X programs which get fired up from a menu come up on the screen
384	 * that they were invoked from, unless specifically overridden on
385	 * their command line.
386	 *
387	 * Which is to say, given that we're on display "foo.bar:1.2", we
388	 * want to translate that into "foo.bar:1.{Scr->screen}".
389	 *
390	 * We strdup() because DisplayString() is a macro returning into the
391	 * dpy structure, and we're going to mutate the value we get from it.
392	 */
393	_ds = DisplayString(dpy);
394	if(_ds) {
395		char *ds;
396		char *colon;
397
398		ds = strdup(_ds);
399		if(!ds) {
400			goto end_execute;
401		}
402
403		/* If it's not host:dpy, we don't have anything to do here */
404		colon = strrchr(ds, ':');
405		if(colon) {
406			char *dot, *new_display;
407
408			/* Find the . in display.screen and chop it off */
409			dot = strchr(colon, '.');
410			if(dot) {
411				*dot = '\0';
412			}
413
414			/* Build a new string with our correct screen info */
415			asprintf(&new_display, "%s.%d", ds, Scr->screen);
416			if(!new_display) {
417				free(ds);
418				goto end_execute;
419			}
420
421			/* And set */
422			setenv("DISPLAY", new_display, 1);
423			free(new_display);
424			restorevar = 1;
425		}
426		free(ds);
427	}
428
429
430	/*
431	 * We replace a couple placeholders in the string.  $currentworkspace
432	 * is documented in the manual; $redirect is not.
433	 */
434	subs = strstr(s, "$currentworkspace");
435	if(subs) {
436		char *tmp;
437		char *wsname;
438
439		wsname = GetCurrentWorkSpaceName(Scr->currentvs);
440		if(!wsname) {
441			wsname = "";
442		}
443
444		tmp = replace_substr(s, "$currentworkspace", wsname);
445		if(!tmp) {
446			goto end_execute;
447		}
448		free(s);
449		s = tmp;
450	}
451
452#ifdef CAPTIVE
453	subs = strstr(s, "$redirect");
454	if(subs) {
455		char *tmp;
456		char *redir;
457
458		if(CLarg.is_captive) {
459			asprintf(&redir, "-xrm 'ctwm.redirect:%s'", Scr->captivename);
460			if(!redir) {
461				goto end_execute;
462			}
463		}
464		else {
465			redir = malloc(1);
466			*redir = '\0';
467		}
468
469		tmp = replace_substr(s, "$redirect", redir);
470		free(s);
471		s = tmp;
472
473		free(redir);
474	}
475#endif
476
477
478	/*
479	 * Call it.  Return value doesn't really matter, since whatever
480	 * happened we're done.  Maybe someday if we develop a "show user
481	 * message" generalized func, we can tell the user if executing
482	 * failed somehow.
483	 */
484	system(s);
485
486
487	/*
488	 * Restore $DISPLAY if we changed it.  It's probably only necessary
489	 * in edge cases (it might be used by ctwm restarting itself, for
490	 * instance) and it's not quite clear whether the DisplayString()
491	 * result would even be wrong for that, but what the heck, setenv()
492	 * is cheap.
493	 */
494	if(restorevar) {
495		if(orig_display) {
496			setenv("DISPLAY", orig_display, 1);
497		}
498		else {
499			unsetenv("DISPLAY");
500		}
501	}
502
503
504	/* Clean up */
505end_execute:
506	free(s);
507	return;
508}
509