windowmaker-wl/src/startup.c

644 lines
18 KiB
C

/*
* Window Maker window manager
*
* Copyright (c) 1997-2003 Alfredo K. Kojima
* Copyright (c) 1998-2003 Dan Pascu
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "wconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#ifdef __FreeBSD__
#include <sys/signal.h>
#endif
#include "WindowMaker.h"
#include "GNUstep.h"
#include "texture.h"
#include "screen.h"
#include "window.h"
#include "actions.h"
#include "client.h"
#include "main.h"
#include "startup.h"
#include "dock.h"
#include "workspace.h"
#include "keybind.h"
#include "framewin.h"
#include "session.h"
#include "defaults.h"
#include "backend/x11/x11_backend.h"
#include "properties.h"
#include "dialog.h"
#include "event.h"
#include "switchmenu.h"
#include "xutil.h"
/* for SunOS */
#ifndef SA_RESTART
# define SA_RESTART 0
#endif
/* Just in case, for weirdo systems */
#ifndef SA_NODEFER
# define SA_NODEFER 0
#endif
/***** Local *****/
static WScreen **wScreen = NULL;
unsigned int _NumLockMask = 0;
unsigned int _ScrollLockMask = 0;
/* catchXError and handleXIO moved to x11_backend.c (Phase 25) */
/*
*----------------------------------------------------------------------
* handleExitSig--
* User generated exit signal handler.
*----------------------------------------------------------------------
*/
static RETSIGTYPE handleExitSig(int sig)
{
sigset_t sigs;
sigfillset(&sigs);
sigprocmask(SIG_BLOCK, &sigs, NULL);
if (sig == SIGUSR1) {
wwarning("got signal %i - restarting", sig);
SIG_WCHANGE_STATE(WSTATE_NEED_RESTART);
} else if (sig == SIGUSR2) {
wwarning("got signal %i - rereading defaults", sig);
SIG_WCHANGE_STATE(WSTATE_NEED_REREAD);
} else if (sig == SIGTERM || sig == SIGINT || sig == SIGHUP) {
wwarning("got signal %i - exiting...", sig);
SIG_WCHANGE_STATE(WSTATE_NEED_EXIT);
}
sigprocmask(SIG_UNBLOCK, &sigs, NULL);
DispatchEvent(NULL); /* Dispatch events immediately. */
}
/* Dummy signal handler */
static void dummyHandler(int sig)
{
/* Parameter is not used, but tell the compiler that it is ok */
(void) sig;
}
/*
*----------------------------------------------------------------------
* handleSig--
* general signal handler. Exits the program gently.
*----------------------------------------------------------------------
*/
static RETSIGTYPE handleSig(int sig)
{
wfatal("got signal %i", sig);
/* Setting the signal behaviour back to default and then reraising the
* signal is a cleaner way to make program exit and core dump than calling
* abort(), since it correctly returns from the signal handler and sets
* the flags accordingly. -Dan
*/
if (sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS || sig == SIGILL || sig == SIGABRT) {
signal(sig, SIG_DFL);
kill(getpid(), sig);
return;
}
wAbort(0);
}
static RETSIGTYPE buryChild(int foo)
{
pid_t pid;
int status;
int save_errno = errno;
sigset_t sigs;
/* Parameter not used, but tell the compiler that it is ok */
(void) foo;
sigfillset(&sigs);
/* Block signals so that NotifyDeadProcess() doesn't get fux0red */
sigprocmask(SIG_BLOCK, &sigs, NULL);
/* R.I.P. */
/* If 2 or more kids exit in a small time window, before this handler gets
* the chance to get invoked, the SIGCHLD signals will be merged and only
* one SIGCHLD signal will be sent to us. We use a while loop to get all
* exited child status because we can't count on the number of SIGCHLD
* signals to know exactly how many kids have exited. -Dan
*/
while ((pid = waitpid(-1, &status, WNOHANG)) > 0 || (pid < 0 && errno == EINTR)) {
NotifyDeadProcess(pid, WEXITSTATUS(status));
}
sigprocmask(SIG_UNBLOCK, &sigs, NULL);
errno = save_errno;
}
static void getOffendingModifiers(void)
{
wm_backend->setup_modifier_lock_masks();
}
#ifdef NUMLOCK_HACK
void
wHackedGrabKey(int keycode, unsigned int modifiers,
WNativeWindow grab_window, Bool owner_events, int pointer_mode, int keyboard_mode)
{
(void)owner_events; (void)pointer_mode; (void)keyboard_mode;
wm_backend->key_grab_hacked((WNativeWindow)grab_window, keycode, modifiers);
}
#endif
void
wHackedGrabButton(unsigned int button, unsigned int modifiers,
WNativeWindow grab_window, Bool owner_events,
unsigned int event_mask, int pointer_mode, int keyboard_mode, WNativeWindow confine_to, WMNativeCursor cursor)
{
(void)pointer_mode; (void)keyboard_mode; (void)confine_to; (void)cursor;
wm_backend->button_grab_hacked(button, modifiers, (WNativeWindow)grab_window,
owner_events, event_mask);
}
WScreen *wScreenWithNumber(int i)
{
assert(i < w_global.screen_count);
return wScreen[i];
}
WScreen *wScreenForRootWindow(WNativeWindow window)
{
int i;
if (w_global.screen_count == 1)
return wScreen[0];
/* Since the number of heads will probably be small (normally 2),
* it should be faster to use this than a hash table, because
* of the overhead. */
for (i = 0; i < w_global.screen_count; i++)
if (wScreen[i]->root_win == window)
return wScreen[i];
return wScreenForWindow(window);
}
WScreen *wScreenForWindow(WNativeWindow window)
{
if (w_global.screen_count == 1)
return wScreen[0];
return wm_backend->screen_for_window(window);
}
static char *atomNames[] = {
"WM_STATE",
"WM_CHANGE_STATE",
"WM_PROTOCOLS",
"WM_TAKE_FOCUS",
"WM_DELETE_WINDOW",
"WM_SAVE_YOURSELF",
"WM_CLIENT_LEADER",
"WM_COLORMAP_WINDOWS",
"WM_COLORMAP_NOTIFY",
"_WINDOWMAKER_MENU",
"_WINDOWMAKER_STATE",
"_WINDOWMAKER_WM_PROTOCOLS",
"_WINDOWMAKER_WM_FUNCTION",
"_WINDOWMAKER_NOTICEBOARD",
"_WINDOWMAKER_COMMAND",
"_WINDOWMAKER_ICON_SIZE",
"_WINDOWMAKER_ICON_TILE",
GNUSTEP_WM_ATTR_NAME,
GNUSTEP_WM_MINIATURIZE_WINDOW,
GNUSTEP_TITLEBAR_STATE,
"_GTK_APPLICATION_OBJECT_PATH",
"WM_IGNORE_FOCUS_EVENTS"
};
/*
*----------------------------------------------------------
* StartUp--
* starts the window manager and setup global data.
* Called from main() at startup.
*
* Side effects:
* global data declared in main.c is initialized
*----------------------------------------------------------
*/
void StartUp(Bool defaultScreenOnly)
{
struct sigaction sig_action;
int i, j, max;
char **formats;
WMNativeAtom atom[wlengthof(atomNames)];
/*
* Ignore CapsLock in modifiers
*/
w_global.shortcut.modifiers_mask = 0xff & ~WM_MOD_LOCK;
getOffendingModifiers();
/*
* Ignore NumLock and ScrollLock too
*/
w_global.shortcut.modifiers_mask &= ~(_NumLockMask | _ScrollLockMask);
memset(&wKeyBindings, 0, sizeof(wKeyBindings));
wm_backend->context_init();
{
int ai;
for (ai = 0; ai < (int)wlengthof(atomNames); ai++)
atom[ai] = (WMNativeAtom)wm_backend->atom_intern(atomNames[ai]);
}
w_global.atom.wm.state = atom[0];
w_global.atom.wm.change_state = atom[1];
w_global.atom.wm.protocols = atom[2];
w_global.atom.wm.take_focus = atom[3];
w_global.atom.wm.delete_window = atom[4];
w_global.atom.wm.save_yourself = atom[5];
w_global.atom.wm.client_leader = atom[6];
w_global.atom.wm.colormap_windows = atom[7];
w_global.atom.wm.colormap_notify = atom[8];
w_global.atom.wmaker.menu = atom[9];
w_global.atom.wmaker.state = atom[10];
w_global.atom.wmaker.wm_protocols = atom[11];
w_global.atom.wmaker.wm_function = atom[12];
w_global.atom.wmaker.noticeboard = atom[13];
w_global.atom.wmaker.command = atom[14];
w_global.atom.wmaker.icon_size = atom[15];
w_global.atom.wmaker.icon_tile = atom[16];
w_global.atom.gnustep.wm_attr = atom[17];
w_global.atom.gnustep.wm_miniaturize_window = atom[18];
w_global.atom.gnustep.titlebar_state = atom[19];
w_global.atom.desktop.gtk_object_path = atom[20];
w_global.atom.wm.ignore_focus_events = atom[21];
wm_backend->xdnd_init();
/* cursors */
wm_backend->cursors_init();
/* emergency exit... */
sig_action.sa_handler = handleSig;
sigemptyset(&sig_action.sa_mask);
sig_action.sa_flags = SA_RESTART;
sigaction(SIGQUIT, &sig_action, NULL);
/* instead of catching these, we let the default handler abort the
* program. The new monitor process will take appropriate action
* when it detects the crash.
sigaction(SIGSEGV, &sig_action, NULL);
sigaction(SIGBUS, &sig_action, NULL);
sigaction(SIGFPE, &sig_action, NULL);
sigaction(SIGABRT, &sig_action, NULL);
*/
sig_action.sa_handler = handleExitSig;
/* Here we set SA_RESTART for safety, because SIGUSR1 may not be handled
* immediately. -Dan */
sig_action.sa_flags = SA_RESTART;
sigaction(SIGTERM, &sig_action, NULL);
sigaction(SIGINT, &sig_action, NULL);
sigaction(SIGHUP, &sig_action, NULL);
sigaction(SIGUSR1, &sig_action, NULL);
sigaction(SIGUSR2, &sig_action, NULL);
/* ignore dead pipe */
/* Because POSIX mandates that only signal with handlers are reset
* across an exec*(), we do not want to propagate ignoring SIGPIPEs
* to children. Hence the dummy handler.
* Philippe Troin <phil@fifi.org>
*/
sig_action.sa_handler = &dummyHandler;
sig_action.sa_flags = SA_RESTART;
sigaction(SIGPIPE, &sig_action, NULL);
/* handle dead children */
sig_action.sa_handler = buryChild;
sig_action.sa_flags = SA_NOCLDSTOP | SA_RESTART;
sigaction(SIGCHLD, &sig_action, NULL);
/* Now we unblock all signals, that may have been blocked by the parent
* who exec()-ed us. This can happen for example if Window Maker crashes
* and restarts itself or another window manager from the signal handler.
* In this case, the new process inherits the blocked signal mask and
* will no longer react to that signal, until unblocked.
* This is because the signal handler of the process who crashed (parent)
* didn't return, and the signal remained blocked. -Dan
*/
sigfillset(&sig_action.sa_mask);
sigprocmask(SIG_UNBLOCK, &sig_action.sa_mask, NULL);
/* handle X shutdowns and errors via backend */
wm_backend->register_error_handlers();
/* set hook for our event dispatcher in WINGs event dispatcher.
* In Wayland mode, events are drained from the frame timer. */
WMHookEventHandler(DispatchEvent);
/* initialize defaults stuff */
w_global.domain.wmaker = wDefaultsInitDomain("WindowMaker", True);
if (!w_global.domain.wmaker->dictionary)
wwarning(_("could not read domain \"%s\" from defaults database"), "WindowMaker");
/* read defaults that don't change until a restart and are
* screen independent */
wReadStaticDefaults(w_global.domain.wmaker ? w_global.domain.wmaker->dictionary : NULL);
/* check sanity of some values */
if (wPreferences.icon_size < 16) {
wwarning(_("icon size is configured to %i, but it's too small. Using 16 instead"),
wPreferences.icon_size);
wPreferences.icon_size = 16;
}
/* init other domains */
w_global.domain.root_menu = wDefaultsInitDomain("WMRootMenu", False);
if (!w_global.domain.root_menu->dictionary)
wwarning(_("could not read domain \"%s\" from defaults database"), "WMRootMenu");
wDefaultsMergeGlobalMenus(w_global.domain.root_menu);
w_global.domain.window_attr = wDefaultsInitDomain("WMWindowAttributes", True);
if (!w_global.domain.window_attr->dictionary)
wwarning(_("could not read domain \"%s\" from defaults database"), "WMWindowAttributes");
wm_backend->extensions_init();
if (defaultScreenOnly)
max = 1;
else
max = wm_backend->display_screen_count();
wScreen = wmalloc(sizeof(WScreen *) * max);
w_global.screen_count = 0;
/* wm_backend is selected in real_main() before display_open(). */
/* Check if TIFF images are supported */
formats = RSupportedFileFormats();
if (formats) {
for (i = 0; formats[i] != NULL; i++) {
if (strcmp(formats[i], "TIFF") == 0) {
wPreferences.supports_tiff = 1;
break;
}
}
}
/* manage the screens */
for (j = 0; j < max; j++) {
if (defaultScreenOnly || max == 1) {
wScreen[w_global.screen_count] = wScreenInit(wm_backend->display_default_screen());
if (!wScreen[w_global.screen_count]) {
wfatal(_("it seems that there is already a window manager running"));
Exit(1);
}
} else {
wScreen[w_global.screen_count] = wScreenInit(j);
if (!wScreen[w_global.screen_count]) {
wwarning(_("could not manage screen %i"), j);
continue;
}
}
w_global.screen_count++;
}
InitializeSwitchMenu();
/* initialize/restore state for the screens */
for (j = 0; j < w_global.screen_count; j++) {
int lastDesktop;
lastDesktop = wm_backend->netwm_get_current_desktop(wScreen[j]);
wwarning("startup: wScreenRestoreState begin (screen %d)", j);
wScreenRestoreState(wScreen[j]);
wwarning("startup: wScreenRestoreState done");
/* manage all windows that were already here before us */
if (!wPreferences.flags.nodock && wScreen[j]->dock)
wScreen[j]->last_dock = wScreen[j]->dock;
wwarning("startup: manageAllWindows begin");
wm_backend->manage_all_windows(wScreen[j], wPreferences.flags.restarting == 2);
wwarning("startup: manageAllWindows done");
/* restore saved menus */
wwarning("startup: wMenuRestoreState begin");
wMenuRestoreState(wScreen[j]);
wwarning("startup: wMenuRestoreState done");
/* If we're not restarting, restore session */
if (wPreferences.flags.restarting == 0 && !wPreferences.flags.norestore)
wSessionRestoreState(wScreen[j]);
if (!wPreferences.flags.noautolaunch) {
/* auto-launch apps */
if (!wPreferences.flags.nodock && wScreen[j]->dock) {
wScreen[j]->last_dock = wScreen[j]->dock;
wDockDoAutoLaunch(wScreen[j]->dock, 0);
}
/* auto-launch apps in clip */
if (!wPreferences.flags.noclip) {
int i;
for (i = 0; i < wScreen[j]->workspace_count; i++) {
if (wScreen[j]->workspaces[i]->clip) {
wScreen[j]->last_dock = wScreen[j]->workspaces[i]->clip;
wDockDoAutoLaunch(wScreen[j]->workspaces[i]->clip, i);
}
}
}
/* auto-launch apps in drawers */
if (!wPreferences.flags.nodrawer) {
WDrawerChain *dc;
for (dc = wScreen[j]->drawers; dc; dc = dc->next) {
wScreen[j]->last_dock = dc->adrawer;
wDockDoAutoLaunch(dc->adrawer, 0);
}
}
}
/* go to workspace where we were before restart */
if (lastDesktop >= 0)
wWorkspaceForceChange(wScreen[j], lastDesktop);
else
wSessionRestoreLastWorkspace(wScreen[j]);
}
if (w_global.screen_count == 0) {
wfatal(_("could not manage any screen"));
Exit(1);
}
#ifndef HAVE_INOTIFY
if (!wPreferences.flags.noupdates)
WMAddTimerHandler(3000, wDefaultsCheckDomains, NULL);
#endif
}
static Bool windowInList(WNativeWindow window, WNativeWindow * list, int count)
{
for (; count >= 0; count--) {
if (window == list[count])
return True;
}
return False;
}
/*
*-----------------------------------------------------------------------
* manageAllWindows--
* Manages all windows in the screen.
*
* Notes:
* Called when the wm is being started.
* No events can be processed while the windows are being
* reparented/managed.
*-----------------------------------------------------------------------
*/
void wManageAllWindows(WScreen * scr, int crashRecovery)
{
WNativeWindow *children;
unsigned int nchildren;
unsigned int i, j;
WWindow *wwin;
wm_backend->server_grab();
wm_backend->query_tree((WNativeWindow)scr->root_win, &children, &nchildren);
scr->flags.startup = 1;
/* first remove all icon windows */
for (i = 0; i < nchildren; i++) {
WWMHints *wmhints;
if (children[i] == WM_NATIVE_NONE)
continue;
wmhints = wm_backend->prop_get_wm_hints((WNativeWindow)children[i]);
if (wmhints && (wmhints->flags & WMH_ICON_WINDOW_HINT)) {
for (j = 0; j < nchildren; j++) {
if (children[j] == (WNativeWindow)wmhints->icon_window) {
wfree(wmhints);
wmhints = NULL;
children[j] = WM_NATIVE_NONE;
break;
}
}
}
if (wmhints) {
wfree(wmhints);
}
}
for (i = 0; i < nchildren; i++) {
if (children[i] == WM_NATIVE_NONE)
continue;
wwin = wManageWindow(scr, children[i]);
if (wwin) {
/* apply states got from WSavedState */
/* shaded + minimized is not restored correctly */
if (wwin->flags.shaded) {
wwin->flags.shaded = 0;
wShadeWindow(wwin);
}
if (wwin->flags.miniaturized
&& (wwin->transient_for == WM_NATIVE_NONE
|| wwin->transient_for == scr->root_win
|| !windowInList(wwin->transient_for, children, nchildren))) {
wwin->flags.skip_next_animation = 1;
wwin->flags.miniaturized = 0;
wIconifyWindow(wwin);
} else {
wClientSetState(wwin, WM_STATE_NORMAL, WM_NATIVE_NONE);
}
if (crashRecovery) {
int border;
border = (!HAS_BORDER(wwin) ? 0 : scr->frame_border_width);
wWindowMove(wwin, wwin->frame_x - border,
wwin->frame_y - border -
(wwin->frame->titlebar ? wwin->frame->titlebar->height : 0));
}
}
}
wm_backend->server_ungrab();
/* hide apps */
wwin = scr->focused_window;
while (wwin) {
if (wwin->flags.hidden) {
WApplication *wapp = wApplicationOf(wwin->main_window);
if (wapp) {
wwin->flags.hidden = 0;
wHideApplication(wapp);
} else {
wwin->flags.hidden = 0;
}
}
wwin = wwin->prev;
}
wfree(children);
scr->flags.startup = 0;
scr->flags.startup2 = 1;
while (wm_backend->event_pending()) {
WMEvent ev;
WMNextEvent(scr->wmscreen, &ev);
WMHandleEvent(&ev);
}
scr->last_workspace = 0;
wWorkspaceForceChange(scr, 0);
if (!wPreferences.flags.noclip)
wDockShowIcons(scr->workspaces[scr->current_workspace]->clip);
scr->flags.startup2 = 0;
}