1 /* $Id$
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., Inc., 51 Franklin Street, Fifth Floor, Boston,
16 MA 02110-1301, USA.
17
18
19 oroborus - (c) 2001 Ken Lynch
20 xfwm4 - (c) 2002-2011 Olivier Fourdan
21
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include <X11/X.h>
29 #include <X11/Xlib.h>
30 #include <glib.h>
31 #include <gdk/gdk.h>
32 #include <gdk/gdkx.h>
33 #include <gtk/gtk.h>
34 #include <libxfce4util/libxfce4util.h>
35 #include <libxfce4ui/libxfce4ui.h>
36
37 #include <sys/stat.h>
38 #include <sys/time.h>
39 #include <sys/types.h>
40 #include <sys/wait.h>
41 #include <time.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <signal.h>
48 #include <string.h>
49
50 #include "display.h"
51 #include "screen.h"
52 #include "events.h"
53 #include "event_filter.h"
54 #include "frame.h"
55 #include "settings.h"
56 #include "client.h"
57 #include "menu.h"
58 #include "focus.h"
59 #include "keyboard.h"
60 #include "workspaces.h"
61 #include "mywindow.h"
62 #include "session.h"
63 #include "startup_notification.h"
64 #include "compositor.h"
65 #include "spinning_cursor.h"
66
67 #define BASE_EVENT_MASK \
68 SubstructureNotifyMask|\
69 StructureNotifyMask|\
70 SubstructureRedirectMask|\
71 ButtonPressMask|\
72 ButtonReleaseMask|\
73 KeyPressMask|\
74 KeyReleaseMask|\
75 FocusChangeMask|\
76 PropertyChangeMask|\
77 ColormapChangeMask
78
79 #ifdef HAVE_COMPOSITOR
80 #define MAIN_EVENT_MASK BASE_EVENT_MASK|ExposureMask
81 #else /* HAVE_COMPOSITOR */
82 #define MAIN_EVENT_MASK BASE_EVENT_MASK
83 #endif /* HAVE_COMPOSITOR */
84
85 #ifdef HAVE_COMPOSITOR
86 static gboolean compositor = TRUE;
87 static vblankMode vblank_mode = VBLANK_AUTO;
88 #define XFWM4_ERROR (xfwm4_error_quark ())
89
90 static GQuark
xfwm4_error_quark(void)91 xfwm4_error_quark (void)
92 {
93 return g_quark_from_static_string ("xfwm4-error-quark");
94 }
95 #endif /* HAVE_COMPOSITOR */
96
97 #ifdef DEBUG
98 static gboolean
setupLog(gboolean debug)99 setupLog (gboolean debug)
100 {
101 const gchar *str;
102 gchar *logfile;
103 int fd;
104
105 if (debug)
106 {
107 str = g_getenv ("XFWM4_LOG_FILE");
108 if (str)
109 {
110 logfile = g_strdup (str);
111 }
112 else
113 {
114 logfile = g_strdup_printf ("xfwm4-debug-%d.log", (int) getpid ());
115 }
116 }
117 else
118 {
119 logfile = "/dev/null";
120 }
121
122 fd = dup(fileno(stderr));
123 if (fd == -1)
124 {
125 g_warning ("Fail to open %s: %s", logfile, g_strerror (errno));
126 g_free (logfile);
127 return FALSE;
128 }
129
130 if (!freopen (logfile, "w", stderr))
131 {
132 g_warning ("Fail to redirect stderr: %s", g_strerror (errno));
133 g_free (logfile);
134 close (fd);
135 return FALSE;
136 }
137
138 if (debug)
139 {
140 g_print ("Logging to %s\n", logfile);
141 g_free (logfile);
142 }
143
144 return TRUE;
145 }
146 #endif /* DEBUG */
147
148 static void
handleSignal(int sig)149 handleSignal (int sig)
150 {
151 DisplayInfo *display_info;
152
153 display_info = myDisplayGetDefault ();
154 if (display_info)
155 {
156 switch (sig)
157 {
158 case SIGINT:
159 /* Walk thru */
160 case SIGTERM:
161 gtk_main_quit ();
162 display_info->quit = TRUE;
163 break;
164 case SIGHUP:
165 /* Walk thru */
166 case SIGUSR1:
167 display_info->reload = TRUE;
168 break;
169 default:
170 break;
171 }
172 }
173 }
174
175 static void
setupHandler(gboolean install)176 setupHandler (gboolean install)
177 {
178 struct sigaction act;
179
180 if (install)
181 act.sa_handler = handleSignal;
182 else
183 act.sa_handler = SIG_DFL;
184
185 sigemptyset (&act.sa_mask);
186 act.sa_flags = 0;
187 sigaction (SIGINT, &act, NULL);
188 sigaction (SIGTERM, &act, NULL);
189 sigaction (SIGHUP, &act, NULL);
190 sigaction (SIGUSR1, &act, NULL);
191 }
192
193 static void
cleanUp(void)194 cleanUp (void)
195 {
196 GSList *screens;
197 DisplayInfo *display_info;
198
199 TRACE ("entering");
200
201 setupHandler (FALSE);
202
203 display_info = myDisplayGetDefault ();
204 g_return_if_fail (display_info);
205
206 eventFilterClose (display_info->xfilter);
207 for (screens = display_info->screens; screens; screens = g_slist_next (screens))
208 {
209 ScreenInfo *screen_info_n = (ScreenInfo *) screens->data;
210 myScreenClose (screen_info_n);
211 g_free (screen_info_n);
212 }
213 sn_close_display ();
214 sessionFreeWindowStates ();
215
216 myDisplayClose (display_info);
217 g_free (display_info);
218
219 xfconf_shutdown();
220 }
221
222 static void
ensure_basedir_spec(void)223 ensure_basedir_spec (void)
224 {
225 char *new, *old, path[PATH_MAX];
226 GError *error;
227 GDir *gdir;
228 const char *name;
229
230 /* test if new directory is there */
231
232 new = xfce_resource_save_location (XFCE_RESOURCE_CONFIG,
233 "xfce4" G_DIR_SEPARATOR_S "xfwm4",
234 FALSE);
235
236 if (g_file_test (new, G_FILE_TEST_IS_DIR))
237 {
238 g_free (new);
239 return;
240 }
241
242 error = NULL;
243 if (!xfce_mkdirhier(new, 0700, &error))
244 {
245 g_warning("Unable to create config dir %s: %s", new, error->message);
246 g_error_free (error);
247 g_free (new);
248 return;
249 }
250
251 g_free (new);
252
253 /* copy xfwm4rc */
254
255 old = xfce_get_userfile ("xfwm4rc", NULL);
256
257 if (g_file_test (old, G_FILE_TEST_EXISTS))
258 {
259 FILE *r, *w;
260
261 g_strlcpy (path, "xfce4/xfwm4/xfwm4rc", PATH_MAX);
262 new = xfce_resource_save_location (XFCE_RESOURCE_CONFIG, path, FALSE);
263
264 r = fopen (old, "r");
265 w = fopen (new, "w");
266
267 g_free (new);
268
269 if (w && r)
270 {
271 int c;
272
273 while ((c = getc (r)) != EOF)
274 {
275 putc (c, w);
276 }
277 }
278
279 if (r)
280 {
281 fclose (r);
282 }
283
284 if (w)
285 {
286 fclose (w);
287 }
288 }
289
290 g_free (old);
291
292 /* copy saved session data */
293
294 new = xfce_resource_save_location (XFCE_RESOURCE_CACHE, "sessions", FALSE);
295 if (!xfce_mkdirhier(new, 0700, &error))
296 {
297 g_warning("Unable to create session dir %s: %s", new, error->message);
298 g_error_free (error);
299 g_free (new);
300 return;
301 }
302
303 old = xfce_get_userfile ("sessions", NULL);
304 gdir = g_dir_open (old, 0, NULL);
305
306 if (gdir)
307 {
308 while ((name = g_dir_read_name (gdir)) != NULL)
309 {
310 FILE *r, *w;
311
312 g_snprintf (path, PATH_MAX, "%s/%s", old, name);
313 r = fopen (path, "r");
314
315 g_snprintf (path, PATH_MAX, "%s/%s", new, name);
316 w = fopen (path, "w");
317
318 if (w && r)
319 {
320 int c;
321
322 while ((c = getc (r)) != EOF)
323 putc (c, w);
324 }
325
326 if (r)
327 fclose (r);
328 if (w)
329 fclose (w);
330 }
331
332 g_dir_close (gdir);
333 }
334
335 g_free (old);
336 g_free (new);
337 }
338
339 static void
print_version(void)340 print_version (void)
341 {
342 g_print ("\tThis is %s version %s (revision %s) for Xfce %s\n",
343 PACKAGE, VERSION, REVISION, xfce_version_string());
344 g_print ("\tReleased under the terms of the GNU General Public License.\n");
345 g_print ("\tCompiled against GTK+-%d.%d.%d, ",
346 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
347 g_print ("using GTK+-%d.%d.%d.\n",
348 gtk_major_version, gtk_minor_version, gtk_micro_version);
349 g_print ("\n");
350 g_print ("\tBuild configuration and supported features:\n");
351
352 g_print ("\t- Startup notification support: ");
353 #ifdef HAVE_LIBSTARTUP_NOTIFICATION
354 g_print ("Yes\n");
355 #else
356 g_print ("No\n");
357 #endif
358
359 g_print ("\t- XSync support: ");
360 #ifdef HAVE_XSYNC
361 g_print ("Yes\n");
362 #else
363 g_print ("No\n");
364 #endif
365
366 g_print ("\t- Render support: ");
367 #ifdef HAVE_RENDER
368 g_print ("Yes\n");
369 #else
370 g_print ("No\n");
371 #endif
372
373 g_print ("\t- Xrandr support: ");
374 #ifdef HAVE_RANDR
375 g_print ("Yes\n");
376 #else
377 g_print ("No\n");
378 #endif
379
380 g_print ("\t- Xpresent support: ");
381 #ifdef HAVE_PRESENT_EXTENSION
382 g_print ("Yes\n");
383 #else
384 g_print ("No\n");
385 #endif
386
387 g_print ("\t- X Input 2 support: ");
388 #ifdef HAVE_XI2
389 g_print ("Yes\n");
390 #else
391 g_print ("No\n");
392 #endif
393
394 g_print ("\t- Embedded compositor: ");
395 #ifdef HAVE_COMPOSITOR
396 g_print ("Yes\n");
397 #else
398 g_print ("No\n");
399 #endif
400
401 #ifdef HAVE_COMPOSITOR
402 g_print ("\t- Epoxy support: ");
403 #ifdef HAVE_EPOXY
404 g_print ("Yes\n");
405 #else
406 g_print ("No\n");
407 #endif /* HAVE_EPOXY */
408 #endif /* HAVE_COMPOSITOR */
409
410 g_print ("\t- KDE systray proxy (deprecated): ");
411 #ifdef ENABLE_KDE_SYSTRAY_PROXY
412 g_print ("Yes\n");
413 #else
414 g_print ("No\n");
415 #endif
416 }
417
418 #ifdef HAVE_COMPOSITOR
419 static gboolean
compositor_callback(const gchar * name,const gchar * value,gpointer user_data,GError ** error)420 compositor_callback (const gchar *name,
421 const gchar *value,
422 gpointer user_data,
423 GError **error)
424 {
425 gboolean succeed = TRUE;
426
427 g_return_val_if_fail (value != NULL, FALSE);
428
429 if (strcmp (value, "off") == 0)
430 {
431 compositor = FALSE;
432 }
433 else if (strcmp (value, "on") == 0)
434 {
435 compositor = TRUE;
436 }
437 else
438 {
439 g_set_error (error, XFWM4_ERROR, 0, "Unrecognized compositor option \"%s\"", value);
440 succeed = FALSE;
441 }
442
443 return succeed;
444 }
445
446 static gboolean
vblank_callback(const gchar * name,const gchar * value,gpointer user_data,GError ** error)447 vblank_callback (const gchar *name,
448 const gchar *value,
449 gpointer user_data,
450 GError **error)
451 {
452 gboolean succeed = TRUE;
453
454 g_return_val_if_fail (value != NULL, FALSE);
455
456 #ifdef HAVE_PRESENT_EXTENSION
457 if (strcmp (value, "xpresent") == 0)
458 {
459 vblank_mode = VBLANK_XPRESENT;
460 }
461 else
462 #endif /* HAVE_PRESENT_EXTENSION */
463 #ifdef HAVE_EPOXY
464 if (strcmp (value, "glx") == 0)
465 {
466 vblank_mode = VBLANK_GLX;
467 }
468 else
469 #endif /* HAVE_EPOXY */
470 if (strcmp (value, "off") == 0)
471 {
472 vblank_mode = VBLANK_OFF;
473 }
474 else
475 {
476 g_set_error (error, XFWM4_ERROR, 0, "Unrecognized compositor option \"%s\"", value);
477 succeed = FALSE;
478 }
479
480 return succeed;
481 }
482
483 static void
init_compositor_screen(ScreenInfo * screen_info)484 init_compositor_screen (ScreenInfo *screen_info)
485 {
486 DisplayInfo *display_info;
487
488 display_info = screen_info->display_info;
489 if (vblank_mode != VBLANK_AUTO)
490 {
491 compositorSetVblankMode (screen_info, vblank_mode);
492 }
493
494 if (display_info->enable_compositor)
495 {
496 gboolean xfwm4_compositor;
497
498 xfwm4_compositor = TRUE;
499 if (screen_info->params->use_compositing)
500 {
501 /* Enable compositor if "use compositing" is enabled */
502 xfwm4_compositor = compositorManageScreen (screen_info);
503 }
504 /*
505 The user may want to use the manual compositing, but the installed
506 system may not support it, so we need to double check, to see if
507 initialization of the compositor was successful.
508 */
509 if (xfwm4_compositor)
510 {
511 /*
512 Acquire selection on XFWM4_COMPOSITING_MANAGER to advertise our own
513 compositing manager (used by WM tweaks to determine whether or not
514 show the "compositor" tab.
515 */
516 setAtomIdManagerOwner (display_info, XFWM4_COMPOSITING_MANAGER,
517 screen_info->xroot, screen_info->xfwm4_win);
518 }
519 }
520 }
521 #endif /* HAVE_COMPOSITOR */
522
523 static int
initialize(gboolean replace_wm)524 initialize (gboolean replace_wm)
525 {
526 DisplayInfo *display_info;
527 gint i, nscreens, default_screen;
528
529 DBG ("xfwm4 starting, using GTK+-%d.%d.%d", gtk_major_version,
530 gtk_minor_version, gtk_micro_version);
531
532 ensure_basedir_spec ();
533
534 initMenuEventWin ();
535 clientClearFocus (NULL);
536 display_info = myDisplayInit (gdk_display_get_default ());
537
538 #ifdef HAVE_COMPOSITOR
539 display_info->enable_compositor = compositor;
540 #else
541 display_info->enable_compositor = FALSE;
542 #endif /* HAVE_COMPOSITOR */
543
544 initModifiers (display_info->dpy);
545
546 setupHandler (TRUE);
547
548 nscreens = ScreenCount (display_info->dpy);
549 default_screen = DefaultScreen (display_info->dpy);
550 for(i = 0; i < nscreens; i++)
551 {
552 ScreenInfo *screen_info;
553 GdkScreen *gscr;
554 Window temp_xwindow;
555 GdkWindow *screen_window;
556
557 if (i == default_screen)
558 {
559 gscr = gdk_display_get_default_screen (display_info->gdisplay);
560 }
561 else
562 {
563 /* create temp 1x1 child window on this screen */
564 temp_xwindow = XCreateSimpleWindow (display_info->dpy,
565 RootWindow (display_info->dpy, i),
566 0, 0, 1, 1, 0, 0, 0);
567 /* allocate new GdkWindow with GdkScreen for this window */
568 screen_window =
569 gdk_x11_window_foreign_new_for_display (display_info->gdisplay,
570 temp_xwindow);
571 XDestroyWindow (display_info->dpy, temp_xwindow);
572
573 if (screen_window == NULL)
574 {
575 g_warning ("Cannot create GdkScreen for screen %i", i);
576 continue;
577 }
578
579 gscr = gdk_window_get_screen (screen_window);
580
581 /* foreign windows have 2 references */
582 g_object_unref (screen_window);
583 g_object_unref (screen_window);
584 }
585 screen_info = myScreenInit (display_info, gscr, MAIN_EVENT_MASK, replace_wm);
586
587 if (!screen_info)
588 {
589 continue;
590 }
591
592 if (!initSettings (screen_info))
593 {
594 return -2;
595 }
596 #ifdef HAVE_COMPOSITOR
597 if (display_info->enable_compositor)
598 {
599 init_compositor_screen (screen_info);
600 }
601 #endif /* HAVE_COMPOSITOR */
602 sn_init_display (screen_info);
603 myDisplayAddScreen (display_info, screen_info);
604 screen_info->current_ws = getNetCurrentDesktop (display_info, screen_info->xroot);
605 setUTF8StringHint (display_info, screen_info->xfwm4_win, NET_WM_NAME, "Xfwm4");
606 setNetSupportedHint (display_info, screen_info->xroot, screen_info->xfwm4_win);
607 setNetDesktopInfo (display_info, screen_info->xroot, screen_info->current_ws,
608 screen_info->width,
609 screen_info->height);
610 workspaceUpdateArea (screen_info);
611 XSetInputFocus (display_info->dpy, screen_info->xfwm4_win, RevertToPointerRoot, CurrentTime);
612
613 clientFrameAll (screen_info);
614
615 initPerScreenCallbacks (screen_info);
616
617 XDefineCursor (display_info->dpy, screen_info->xroot, myDisplayGetCursorRoot(display_info));
618 }
619
620 /* No screen to manage, give up */
621 if (!display_info->nb_screens)
622 {
623 return -1;
624 }
625 display_info->xfilter = eventFilterInit (display_info->devices, (gpointer) display_info);
626 eventFilterPush (display_info->xfilter, xfwm4_event_filter, (gpointer) display_info);
627 initPerDisplayCallbacks (display_info);
628
629 return sessionStart (display_info);
630 }
631
632 static void
init_pango_cache(void)633 init_pango_cache (void)
634 {
635 GtkWidget *tmp_win;
636 PangoLayout *layout;
637
638 /*
639 * The first time the first Gtk application on a display uses pango,
640 * pango grabs the XServer while it creates the font cache window.
641 * Therefore, force the cache window to be created now instead of
642 * trying to do it while we have another grab and deadlocking the server.
643 */
644 tmp_win = gtk_window_new (GTK_WINDOW_POPUP);
645 layout = gtk_widget_create_pango_layout (tmp_win, "-");
646 pango_layout_get_pixel_extents (layout, NULL, NULL);
647 g_object_unref (G_OBJECT (layout));
648 gtk_widget_destroy (GTK_WIDGET (tmp_win));
649 }
650
651 int
main(int argc,char ** argv)652 main (int argc, char **argv)
653 {
654 gboolean version = FALSE;
655 gboolean replace_wm = FALSE;
656 int status;
657 GOptionContext *context;
658 GError *error = NULL;
659 #ifdef DEBUG
660 gboolean debug = FALSE;
661 #endif /* DEBUG */
662 GOptionEntry option_entries[] =
663 {
664 #ifdef HAVE_COMPOSITOR
665 { "compositor", 'c', 0, G_OPTION_ARG_CALLBACK,
666 compositor_callback, N_("Set the compositor mode"), "on|off" },
667 { "vblank", 'b', 0, G_OPTION_ARG_CALLBACK,
668 vblank_callback, N_("Set the vblank mode"), "off"
669 #ifdef HAVE_PRESENT_EXTENSION
670 "|xpresent"
671 #endif /* HAVE_PRESENT_EXTENSION */
672 #ifdef HAVE_EPOXY
673 "|glx"
674 #endif /* HAVE_EPOXY */
675 },
676 #endif /* HAVE_COMPOSITOR */
677 { "replace", 'r', 0, G_OPTION_ARG_NONE,
678 &replace_wm, N_("Replace the existing window manager"), NULL },
679 { "version", 'V', 0, G_OPTION_ARG_NONE,
680 &version, N_("Print version information and exit"), NULL },
681 #ifdef DEBUG
682 { "debug", 'd', 0, G_OPTION_ARG_NONE,
683 &debug, N_("Enable debug logging"), NULL },
684 #endif /* DEBUG */
685 { NULL }
686 };
687
688 #ifdef HAVE_EPOXY
689 /* NVIDIA proprietary/closed source driver queues up to 2 frames by
690 * default before blocking in glXSwapBuffers(), whereas our compositor
691 * expects `glXSwapBuffers()` to block until the next vblank.
692 *
693 * To avoid that, our compositor was issuing a `glXWaitGL()` immediately
694 * after the call to `glXSwapBuffers()` but that translates as a busy
695 * wait, hence dramatically increasing CPU usage of xfwm4 with the
696 * NVIDIA proprietary/closed source driver.
697 *
698 * Instruct the NVIDIA proprietary/closed source driver to allow only
699 * 1 frame using the environment variable “__GL_MaxFramesAllowed” so
700 * that it matches our expectations.
701 *
702 * This must be set before libGL is loaded, hence before gtk_init().
703 *
704 * Taken from similar patch posted by NVIDIA developer for kwin:
705 * https://phabricator.kde.org/D19867
706 */
707 g_setenv("__GL_MaxFramesAllowed", "1", TRUE);
708 #endif /* HAVE_EPOXY */
709
710 xfce_textdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR, "UTF-8");
711
712 /* xfwm4 is an X11 window manager, no point in trying to connect to
713 * any other display server (like when running nested within a
714 * Wayland compositor).
715 */
716 gdk_set_allowed_backends ("x11");
717
718 #ifndef HAVE_XI2
719 /* Disable XI2 in GDK */
720 gdk_disable_multidevice ();
721 #endif
722
723 context = g_option_context_new (_("[ARGUMENTS...]"));
724 g_option_context_add_main_entries (context, option_entries, GETTEXT_PACKAGE);
725 g_option_context_add_group (context, gtk_get_option_group (FALSE));
726 g_option_context_add_group (context, xfce_sm_client_get_option_group (argc, argv));
727 if (!g_option_context_parse (context, &argc, &argv, &error))
728 {
729 g_print ("%s: %s.\n", PACKAGE_NAME, error->message);
730 g_print (_("Type \"%s --help\" for usage."), G_LOG_DOMAIN);
731 g_print ("\n");
732 g_error_free (error);
733
734 return EXIT_FAILURE;
735 }
736 g_option_context_free (context);
737
738 #ifdef DEBUG
739 setupLog (debug);
740 #endif /* DEBUG */
741 DBG ("xfwm4 starting");
742
743 gtk_init (&argc, &argv);
744
745 if (G_UNLIKELY (version))
746 {
747 print_version ();
748 return EXIT_SUCCESS;
749 }
750 init_pango_cache ();
751
752 status = initialize (replace_wm);
753 /*
754 status < 0 => Error, cancel execution
755 status == 0 => Run w/out session manager
756 status == 1 => Connected to session manager
757 */
758 switch (status)
759 {
760 case -1:
761 g_warning ("Could not find a screen to manage, exiting");
762 exit (1);
763 break;
764 case -2:
765 g_warning ("Missing data from default files");
766 exit (1);
767 break;
768 case 0:
769 case 1:
770 /* enter GTK main loop */
771 gtk_main ();
772 break;
773 default:
774 g_warning ("Unknown error occurred");
775 exit (1);
776 break;
777 }
778 cleanUp ();
779 DBG ("xfwm4 terminated");
780 return 0;
781 }
782