1 /* -*-c-*- */
2 /* This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or
5  * (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, see: <http://www.gnu.org/licenses/>
14  */
15 
16 /* ---------------------------- included header files ---------------------- */
17 
18 #include "config.h"
19 
20 #include <stdio.h>
21 #include <stdarg.h>
22 #include <X11/Xatom.h>
23 
24 #include "libs/ftime.h"
25 #ifdef FVWM_DEBUG_TIME
26 #include <sys/times.h>
27 #endif
28 #include "libs/Parse.h"
29 #include "libs/Target.h"
30 #include "fvwm.h"
31 #include "externs.h"
32 #include "execcontext.h"
33 #include "cursor.h"
34 #include "misc.h"
35 #include "screen.h"
36 #include "module_interface.h"
37 #include "events.h"
38 #include "eventmask.h"
39 
40 /* ---------------------------- local definitions -------------------------- */
41 
42 /* ---------------------------- local macros ------------------------------- */
43 
44 #define GRAB_EVMASK (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | \
45 	PointerMotionMask | EnterWindowMask | LeaveWindowMask)
46 
47 /* ---------------------------- imports ------------------------------------ */
48 
49 /* ---------------------------- included code files ------------------------ */
50 
51 /* ---------------------------- local types -------------------------------- */
52 
53 /* ---------------------------- forward declarations ----------------------- */
54 
55 /* ---------------------------- local variables ---------------------------- */
56 
57 static int grab_count[GRAB_MAXVAL] = { 1, 1, 0, 0, 0, 0, 0 };
58 
59 /* ---------------------------- exported variables (globals) --------------- */
60 
61 /* ---------------------------- local functions ---------------------------- */
62 
63 /*
64  * Change the appearance of the grabbed cursor.
65  */
change_grab_cursor(int cursor)66 static void change_grab_cursor(int cursor)
67 {
68 	if (cursor != None)
69 	{
70 		XChangeActivePointerGrab(
71 			dpy, GRAB_EVMASK, Scr.FvwmCursors[cursor], CurrentTime);
72 	}
73 
74 	return;
75 }
76 
77 /* ---------------------------- interface functions ------------------------ */
78 
GetTwoArguments(char * action,int * val1,int * val2,int * val1_unit,int * val2_unit)79 int GetTwoArguments(
80 	char *action, int *val1, int *val2, int *val1_unit, int *val2_unit)
81 {
82 	*val1_unit = Scr.MyDisplayWidth;
83 	*val2_unit = Scr.MyDisplayHeight;
84 
85 	return GetTwoPercentArguments(action, val1, val2, val1_unit, val2_unit);
86 }
87 
88 /*
89  * Grab the pointer.
90  * grab_context: GRAB_NORMAL, GRAB_BUSY, GRAB_MENU, GRAB_BUSYMENU,
91  * GRAB_PASSIVE.
92  * GRAB_STARTUP and GRAB_NONE are used at startup but are not made
93  * to be grab_context.
94  * GRAB_PASSIVE does not actually grab, but only delays the following ungrab
95  * until the GRAB_PASSIVE is released too.
96  */
97 #define DEBUG_GRAB 0
98 #if DEBUG_GRAB
print_grab_stats(char * text)99 void print_grab_stats(char *text)
100 {
101 	int i;
102 
103 	fprintf(stderr,"grab_stats (%s):", text);
104 	for (i = 0; i < GRAB_MAXVAL; i++)
105 	{
106 		fprintf(stderr," %d", grab_count[i]);
107 	}
108 	fprintf(stderr," \n");
109 
110 	return;
111 }
112 #endif
113 
GrabEm(int cursor,int grab_context)114 Bool GrabEm(int cursor, int grab_context)
115 {
116 	int i = 0;
117 	int val = 0;
118 	int rep;
119 	Window grab_win;
120 	extern Window PressedW;
121 
122 	if (grab_context <= GRAB_STARTUP || grab_context >= GRAB_MAXVAL)
123 	{
124 		fvwm_msg(
125 			ERR, "GrabEm", "Bug: Called with illegal context %d",
126 			grab_context);
127 		return False;
128 	}
129 
130 	if (grab_context == GRAB_PASSIVE)
131 	{
132 		grab_count[grab_context]++;
133 		grab_count[GRAB_ALL]++;
134 		return True;
135 	}
136 
137 	if (grab_count[GRAB_ALL] > grab_count[GRAB_PASSIVE])
138 	{
139 		/* already grabbed, just change the grab cursor */
140 		if (grab_context == GRAB_FREEZE_CURSOR)
141 		{
142 			if (XGrabPointer(
143 				    dpy, (PressedW != None) ?
144 				    PressedW : Scr.NoFocusWin, True,
145 				    GRAB_EVMASK, GrabModeAsync, GrabModeAsync,
146 				    None, Scr.FvwmCursors[CRS_DEFAULT],
147 				    CurrentTime) != GrabSuccess)
148 			{
149 				return False;
150 			}
151 			return True;
152 		}
153 		grab_count[grab_context]++;
154 		grab_count[GRAB_ALL]++;
155 		if (grab_context != GRAB_BUSY || grab_count[GRAB_STARTUP] == 0)
156 		{
157 			change_grab_cursor(cursor);
158 		}
159 		return True;
160 	}
161 
162 	/* move the keyboard focus prior to grabbing the pointer to
163 	 * eliminate the enterNotify and exitNotify events that go
164 	 * to the windows. But GRAB_BUSY. */
165 	switch (grab_context)
166 	{
167 	case GRAB_BUSY:
168 		if ( Scr.Hilite != NULL )
169 		{
170 			grab_win = FW_W(Scr.Hilite);
171 		}
172 		else
173 		{
174 			grab_win = Scr.Root;
175 		}
176 		/* retry to grab the busy cursor only once */
177 		rep = 2;
178 		break;
179 	case GRAB_PASSIVE:
180 		/* cannot happen */
181 		return False;
182 	case GRAB_FREEZE_CURSOR:
183 		grab_win = (PressedW != None) ? PressedW : Scr.NoFocusWin;
184 		rep = 2;
185 		break;
186 	default:
187 		grab_win = Scr.Root;
188 		rep = NUMBER_OF_GRAB_ATTEMPTS;
189 		break;
190 	}
191 
192 	XFlush(dpy);
193 	while (i < rep &&
194 	       (val = XGrabPointer(
195 		      dpy, grab_win, True, GRAB_EVMASK, GrabModeAsync,
196 		      GrabModeAsync, None,
197 		      (grab_context == GRAB_FREEZE_CURSOR) ?
198 		      None : Scr.FvwmCursors[cursor], CurrentTime) !=
199 		GrabSuccess))
200 	{
201 		switch (val)
202 		{
203 		case GrabInvalidTime:
204 		case GrabNotViewable:
205 			/* give up */
206 			i += rep;
207 			break;
208 		case GrabSuccess:
209 			break;
210 		case AlreadyGrabbed:
211 		case GrabFrozen:
212 		default:
213 			/* If you go too fast, other windows may not get a
214 			 * chance to release any grab that they have. */
215 			i++;
216 			if (grab_context == GRAB_FREEZE_CURSOR)
217 			{
218 				break;
219 			}
220 			if (i < rep)
221 			{
222 				usleep(1000 * TIME_BETWEEN_GRAB_ATTEMPTS);
223 			}
224 			break;
225 		}
226 	}
227 	XFlush(dpy);
228 
229 	/* If we fall out of the loop without grabbing the pointer, its
230 	 * time to give up */
231 	if (val != GrabSuccess)
232 	{
233 		return False;
234 	}
235 	grab_count[grab_context]++;
236 	grab_count[GRAB_ALL]++;
237 #if DEBUG_GRAB
238 	print_grab_stats("grabbed");
239 #endif
240 	return True;
241 }
242 
243 
244 /*
245  *
246  * UnGrab the pointer
247  *
248  */
UngrabEm(int ungrab_context)249 Bool UngrabEm(int ungrab_context)
250 {
251 	if (ungrab_context <= GRAB_ALL || ungrab_context >= GRAB_MAXVAL)
252 	{
253 		fvwm_msg(
254 			ERR, "UngrabEm", "Bug: Called with illegal context %d",
255 			ungrab_context);
256 		return False;
257 	}
258 
259 	if (grab_count[ungrab_context] == 0 || grab_count[GRAB_ALL] == 0)
260 	{
261 		/* context is not grabbed */
262 		return False;
263 	}
264 
265 	XFlush(dpy);
266 	grab_count[ungrab_context]--;
267 	grab_count[GRAB_ALL]--;
268 	if (grab_count[GRAB_ALL] > 0)
269 	{
270 		int new_cursor = None;
271 
272 		/* there are still grabs left - switch grab cursor */
273 		switch (ungrab_context)
274 		{
275 		case GRAB_NORMAL:
276 		case GRAB_BUSY:
277 		case GRAB_MENU:
278 			if (grab_count[GRAB_BUSYMENU] > 0)
279 			{
280 				new_cursor = CRS_WAIT;
281 			}
282 			else if (grab_count[GRAB_BUSY] > 0)
283 			{
284 				new_cursor = CRS_WAIT;
285 			}
286 			else if (grab_count[GRAB_MENU] > 0)
287 			{
288 				new_cursor = CRS_MENU;
289 			}
290 			else
291 			{
292 				new_cursor = None;
293 			}
294 			break;
295 		case GRAB_BUSYMENU:
296 			/* switch back from busymenu cursor to normal menu
297 			 * cursor */
298 			new_cursor = CRS_MENU;
299 			break;
300 		default:
301 			new_cursor = None;
302 			break;
303 		}
304 		if (grab_count[GRAB_ALL] > grab_count[GRAB_PASSIVE])
305 		{
306 #if DEBUG_GRAB
307 			print_grab_stats("-restore");
308 #endif
309 			change_grab_cursor(new_cursor);
310 		}
311 	}
312 	else
313 	{
314 #if DEBUG_GRAB
315 		print_grab_stats("-ungrab");
316 #endif
317 		XUngrabPointer(dpy, CurrentTime);
318 	}
319 	XFlush(dpy);
320 
321 	return True;
322 }
323 
324 #ifndef fvwm_msg /* Some ports (i.e. VMS) define their own version */
325 /*
326 ** fvwm_msg: used to send output from fvwm to files and or stderr/stdout
327 **
328 ** type -> DBG == Debug, ERR == Error, INFO == Information, WARN == Warning,
329 ** OLD == Command or option deprecated
330 ** id -> name of function, or other identifier
331 */
332 static char *fvwm_msg_strings[] =
333 {
334 	"<<DEBUG>> ", "", "", "<<WARNING>> ", "<<DEPRECATED>> ", "<<ERROR>> "
335 };
336 
fvwm_msg(fvwm_msg_t type,char * id,char * msg,...)337 void fvwm_msg(fvwm_msg_t type, char *id, char *msg, ...)
338 {
339 	va_list args;
340 	char *mfmt;
341 	char fvwm_id[20];
342 	char time_str[40] = "\0";
343 #ifdef FVWM_DEBUG_TIME
344 	clock_t time_val, time_taken;
345 	static clock_t start_time = 0;
346 	static clock_t prev_time = 0;
347 	struct tms not_used_tms;
348 	time_t mytime;
349 	struct tm *t_ptr;
350 #endif
351 
352 #ifdef FVWM_DEBUG_TIME
353 	time(&mytime);
354 	t_ptr = localtime(&mytime);
355 	if (start_time == 0)
356 	{
357 		/* get clock ticks */
358 		prev_time = start_time = (unsigned int)times(&not_used_tms);
359 	}
360 	time_val = (unsigned int)times(&not_used_tms); /* get clock ticks */
361 	time_taken = time_val - prev_time;
362 	prev_time = time_val;
363 	sprintf(time_str, "%.2d:%.2d:%.2d%7ld ",
364 		t_ptr->tm_hour, t_ptr->tm_min, t_ptr->tm_sec, time_taken);
365 #endif
366 
367 	strcpy(fvwm_id, "fvwm");
368 	if (Scr.NumberOfScreens > 1)
369 	{
370 		sprintf(&fvwm_id[strlen(fvwm_id)], ".%d", (int)Scr.screen);
371 	}
372 
373 	if (type == ERR)
374 	{
375 		/* I hate to use a fixed length but this will do for now */
376 		char tmp[2 * MAX_TOKEN_LENGTH];
377 		sprintf(tmp, "[%s][%s]: %s",
378 			fvwm_id, id, fvwm_msg_strings[(int)type]);
379 		va_start(args, msg);
380 		vsprintf(tmp + strlen(tmp), msg, args);
381 		va_end(args);
382 		tmp[strlen(tmp) + 1] = '\0';
383 		tmp[strlen(tmp)] = '\n';
384 		if (strlen(tmp) >= MAX_MODULE_INPUT_TEXT_LEN)
385 		{
386 			sprintf(tmp + MAX_MODULE_INPUT_TEXT_LEN - 5, "...\n");
387 		}
388 		fprintf(stderr, "%s", tmp);
389 		BroadcastName(M_ERROR, 0, 0, 0, tmp);
390 	}
391 	else
392 	{
393 		fprintf(stderr, "%s[%s][%s]: %s",
394 				time_str, fvwm_id, id, fvwm_msg_strings[(int)type]);
395 
396 		va_start(args, msg);
397 		{
398 			int n;
399 
400 			n = asprintf(&mfmt, "%s\n", msg);
401 			(void)n;
402 		}
403 		vfprintf(stderr, mfmt, args);
404 		va_end(args);
405 		free(mfmt);
406 	}
407 
408 } /* fvwm_msg */
409 #endif
410 
fvwm_msg_report_app(void)411 void fvwm_msg_report_app(void)
412 {
413 	fprintf(
414 		stderr,
415 		"    If you are having a problem with the application, send a"
416 		" bug report\n"
417 		"    with this message included to the application owner.\n"
418 		"    There is no need to notify fvwm-workers@fvwm.org.\n");
419 
420 	return;
421 }
422 
fvwm_msg_report_app_and_workers(void)423 void fvwm_msg_report_app_and_workers(void)
424 {
425 	fprintf(
426 		stderr,
427 		"    If you are having a problem with the application, send"
428 		" a bug report with\n"
429 		"    this message included to the application owner and"
430 		" notify\n"
431 		"    fvwm-workers@fvwm.org.\n");
432 
433 	return;
434 }
435 
436 /* Store the last item that was added with '+' */
set_last_added_item(last_added_item_t type,void * item)437 void set_last_added_item(last_added_item_t type, void *item)
438 {
439 	Scr.last_added_item.type = type;
440 	Scr.last_added_item.item = item;
441 
442 	return;
443 }
444 
445 /* some fancy font handling stuff */
NewFontAndColor(FlocaleFont * flf,Pixel color,Pixel backcolor)446 void NewFontAndColor(FlocaleFont *flf, Pixel color, Pixel backcolor)
447 {
448 	Globalgcm = GCForeground | GCBackground;
449 	if (flf->font)
450 	{
451 		Globalgcm |= GCFont;
452 		Globalgcv.font = flf->font->fid;
453 	}
454 	Globalgcv.foreground = color;
455 	Globalgcv.background = backcolor;
456 	XChangeGC(dpy,Scr.TitleGC,Globalgcm,&Globalgcv);
457 
458 	return;
459 }
460 
461 
462 /*
463  *
464  * For menus, move, and resize operations, we can effect keyboard
465  * shortcuts by warping the pointer.
466  *
467  */
Keyboard_shortcuts(XEvent * ev,FvwmWindow * fw,int * x_defect,int * y_defect,int ReturnEvent)468 void Keyboard_shortcuts(
469 	XEvent *ev, FvwmWindow *fw, int *x_defect, int *y_defect,
470 	int ReturnEvent)
471 {
472 	int x_move_size = 0;
473 	int y_move_size = 0;
474 
475 	if (fw)
476 	{
477 		x_move_size = fw->hints.width_inc;
478 		y_move_size = fw->hints.height_inc;
479 	}
480 	fvwmlib_keyboard_shortcuts(
481 		dpy, Scr.screen, ev, x_move_size, y_move_size, x_defect,
482 		y_defect, ReturnEvent);
483 
484 	return;
485 }
486 
487 
488 /*
489  *
490  * Check if the given FvwmWindow structure still points to a valid window.
491  *
492  */
493 
check_if_fvwm_window_exists(FvwmWindow * fw)494 Bool check_if_fvwm_window_exists(FvwmWindow *fw)
495 {
496 	FvwmWindow *t;
497 
498 	for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
499 	{
500 		if (t == fw)
501 			return True;
502 	}
503 	return False;
504 }
505 
506 /* rounds x down to the next multiple of m */
truncate_to_multiple(int x,int m)507 int truncate_to_multiple (int x, int m)
508 {
509 	return (x < 0) ? (m * (((x + 1) / m) - 1)) : (m * (x / m));
510 }
511 
IsRectangleOnThisPage(const rectangle * rec,int desk)512 Bool IsRectangleOnThisPage(const rectangle *rec, int desk)
513 {
514 	return (desk == Scr.CurrentDesk &&
515 		rec->x + (signed int)rec->width > 0 &&
516 		(rec->x < 0 || rec->x < Scr.MyDisplayWidth) &&
517 		rec->y + (signed int)rec->height > 0 &&
518 		(rec->y < 0 || rec->y < Scr.MyDisplayHeight)) ?
519 		True : False;
520 }
521 
522 /* returns the FvwmWindow that contains the pointer or NULL if none */
get_pointer_fvwm_window(void)523 FvwmWindow *get_pointer_fvwm_window(void)
524 {
525 	int x,y;
526 	Window win;
527 	Window ancestor;
528 	FvwmWindow *t;
529 
530 	if (FQueryPointer(
531 		    dpy, Scr.Root, &JunkRoot, &win, &JunkX, &JunkY,
532 		    &x, &y, &JunkMask) == False)
533 	{
534 		/* pointer is on a different screen */
535 		return NULL;
536 	}
537 	for (t = NULL ; win != Scr.Root && win != None; win = ancestor)
538 	{
539 		Window root = None;
540 		Window *children;
541 		unsigned int nchildren;
542 
543 		if (XFindContext(dpy, win, FvwmContext, (caddr_t *) &t) !=
544 		    XCNOENT)
545 		{
546 			/* found a matching window context */
547 			return t;
548 		}
549 		/* get next higher ancestor window */
550 		children = NULL;
551 		if (!XQueryTree(
552 			    dpy, win, &root, &ancestor, &children, &nchildren))
553 		{
554 			return NULL;
555 		}
556 		if (children)
557 		{
558 			XFree(children);
559 		}
560 	}
561 
562 	return t;
563 }
564 
565 
566 /* Returns the current X server time */
get_server_time(void)567 Time get_server_time(void)
568 {
569 	XEvent xev;
570 	XSetWindowAttributes attr;
571 
572 	/* add PropChange to NoFocusWin events */
573 	attr.event_mask = PropertyChangeMask;
574 	XChangeWindowAttributes (dpy, Scr.NoFocusWin, CWEventMask, &attr);
575 	/* provoke an event */
576 	XChangeProperty(
577 		dpy, Scr.NoFocusWin, XA_WM_CLASS, XA_STRING, 8, PropModeAppend,
578 		NULL, 0);
579 	FWindowEvent(dpy, Scr.NoFocusWin, PropertyChangeMask, &xev);
580 	attr.event_mask = XEVMASK_NOFOCUSW;
581 	XChangeWindowAttributes(dpy, Scr.NoFocusWin, CWEventMask, &attr);
582 
583 	return xev.xproperty.time;
584 }
585 
print_g(char * text,rectangle * g)586 void print_g(char *text, rectangle *g)
587 {
588 	fprintf(stderr,"%s: ", (text != NULL) ? text : "");
589 	if (g == NULL)
590 	{
591 		fprintf(stderr, "(null)\n");
592 
593 		return;
594 	}
595 	fprintf(stderr,"%4d %4d %4dx%4d (%4d - %4d, %4d - %4d)\n",
596 		g->x, g->y, g->width, g->height,
597 		g->x, g->x + g->width, g->y, g->y + g->height);
598 
599 	return;
600 }
601