Newer
Older
x-wm / x-wm.c
/* $Id$ */
/*
 * Copyright (c) 2007 Andreas Jaggi <andreas.jaggi@waterwave.ch>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "x-wm.h"
#include "client.h"

Display* display;
int screen;
Window root;
Window focus;
Atom wm_delete_window;
Atom wm_protocols;
int clientcounter = 0;
int quit = 0;
char* startscript = NULL;

void grab_keys();
void grab_buttons();
void loop();
void handle_motion(XMotionEvent* ev);
void handle_crossing(XCrossingEvent* ev);
void handle_focus(XFocusChangeEvent* ev);
void handle_create(XCreateWindowEvent* ev);
void handle_destroy(XDestroyWindowEvent* ev);
void handle_buttonPress(XButtonEvent* ev);
void handle_keyPress(XKeyEvent* ev);
void close_window(Window w);

int main ( int argc, char* argv[] ) {

	Window dw1, dw2;
	Window* existing_windows;

	int i, win_count;
	int r = -1;
	int status;

	XSetWindowAttributes attr;

	while ( (r = getopt(argc, argv, "hlvs:")) != -1 ) {
		switch ( r ) {
			case 'h':
				usage();
				exit(0);
			case 'l':
				license();
				exit(0);
			case 'v':
				version();
				exit(0);
			case 's':
				startscript = optarg;
				break;
			default:
				usage();
				exit(1);
		}
	}

	fprintf(stdout, "starting x-wm...\n");

	if ( startscript != NULL ) {
		fprintf(stdout, "x-wm: running startscript: %s...\n", startscript);
		switch ( fork() ) {
			case 0:
				execlp(startscript,startscript,0);
			case -1:
				fprintf(stderr, "x-wm: could not fork()!\n");
			default:
				wait(&status);

				if ( WIFEXITED(status) ) {
					fprintf(stderr, "x-wm: startscript (%s) ended with exit(%d)\n", startscript, WEXITSTATUS(status));
				}
				if ( WIFSIGNALED(status) ) {
					fprintf(stderr, "x-wm: startscript (%s) received signal %d\n", startscript, WTERMSIG(status));
				}
		}
	}

	if ( !(display = XOpenDisplay(NULL)) ) {
		fprintf(stderr, "x-wm: cannot open display\n");
		exit(1);
	}

	screen = DefaultScreen(display);
	root = RootWindow(display, screen);

	attr.event_mask = 0
		//| SubstructureRedirectMask
		//| SubstructureNotifyMask
		| StructureNotifyMask
		//| PropertyChangeMask
		| EnterWindowMask
		//| LeaveWindowMask
		| ButtonPressMask
		//| ButtonReleaseMask
		| KeyPressMask
		//| KeyReleaseMask
		//| FocusChangeMask
		//| PointerMotionMask
		//| PointerMotionHintMask
		//| Button1MotionMask
		//| Button2MotionMask
		//| Button3MotionMask
		//| Button4MotionMask
		//| Button5MotionMask
		//| ButtonMotionMask
		;

	XChangeWindowAttributes(display, root, CWEventMask, &attr);

	wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False);
	wm_protocols = XInternAtom(display, "WM_PROTOCOLS", False);

	grab_keys();

	grab_buttons();

	init_clients();

	XQueryTree(display, root, &dw1, &dw2, &existing_windows, &win_count);
	fprintf(stderr, "x-wm: win_count = %d\n", win_count);
	for ( i = 0; i < win_count; i++ ) {
		if ( existing_windows[i] != root ) {
			clientcounter++;
			create_client(existing_windows[i]);
		}
	}
	XFree(existing_windows);

	fprintf(stderr, "x-wm: clientcount = %d\n", clientcounter);

	loop();

	exit(0);
}

void grab_buttons ( ) {
	/* listen to mouse clicks into the root window */
	XGrabButton(display, Button1, 0, root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);

	/* listen to mouse clicks into the root window */
	XGrabButton(display, Button1, (Mod1Mask|ControlMask), root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);

	/* listen to scroll wheel + ALT */
	XGrabButton(display, Button4, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
	XGrabButton(display, Button5, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);

	/* listen to scroll wheel + ALT + Ctrl (+ Shift) */
	XGrabButton(display, Button4, (Mod1Mask|ControlMask), root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
	XGrabButton(display, Button5, (Mod1Mask|ControlMask), root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
	XGrabButton(display, Button4, (Mod1Mask|ControlMask|ShiftMask), root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
	XGrabButton(display, Button5, (Mod1Mask|ControlMask|ShiftMask), root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
}

void grab_keys ( ) {
	/* listen to Alt+Tab in the root window */
	XGrabKey(display, XKeysymToKeycode(display, XStringToKeysym("Tab")), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
	/* listen to Alt+F4 in the root window */
	XGrabKey(display, XKeysymToKeycode(display, XStringToKeysym("F4")), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
	/* listen to Alt+F2 in the root window */
	XGrabKey(display, XKeysymToKeycode(display, XStringToKeysym("F2")), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
	/* listen to Alt+UP in the root window */
	XGrabKey(display, XKeysymToKeycode(display, XStringToKeysym("Up")), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
	/* listen to Alt+DOWN in the root window */
	XGrabKey(display, XKeysymToKeycode(display, XStringToKeysym("Down")), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
	/* listen to Alt+LEFT in the root window */
	XGrabKey(display, XKeysymToKeycode(display, XStringToKeysym("Left")), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
	/* listen to Alt+RIGHT in the root window */
	XGrabKey(display, XKeysymToKeycode(display, XStringToKeysym("Right")), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
}

void loop ( ) {
	XEvent ev;
	while ( !quit ) {
		XNextEvent(display, &ev);

		switch ( ev.type ) {
			case MotionNotify:
				handle_motion(&(ev.xmotion));
				break;
			case ButtonPress:
				handle_buttonPress(&(ev.xbutton));
				break;
			case KeyPress:
				handle_keyPress(&(ev.xkey));
				break;
			case EnterNotify:
				handle_crossing(&(ev.xcrossing));
				break;
			case CreateNotify:
				handle_create(&(ev.xcreatewindow));
				break;
			case DestroyNotify:
				handle_destroy(&(ev.xdestroywindow));
				break;
			default:
				fprintf(stderr, "x-wm: received XEvent of unknown type %d\n", ev.type);
		}
	}
}

void handle_destroy( XDestroyWindowEvent* ev ) {
	fprintf(stdout, "x-wm: received XDestroyWindowEvent event\n");
	clientcounter--;
	fprintf(stderr, "x-wm: clientcounter: %d\n", clientcounter);
	destroy_client(ev->window);

	if ( ev->window == root ) {
		fprintf(stderr, "x-wm: root window destroyed, quitting...\n");
		quit = 1;
	}

	if ( clientcounter <= 0 ) {
		fprintf(stderr, "x-wm: clientcounter <= 0 (%d), quitting...\n", clientcounter);
		quit = 1;
	}
}

void handle_create ( XCreateWindowEvent* ev ) {

	fprintf(stdout, "x-wm: received XCreateWindowEvent event\n");

	if ( find_client(ev->window) == NULL ) {
		create_client(ev->window);
	}

	XAddToSaveSet(display, ev->window);
	XRaiseWindow(display, ev->window);
	XSetInputFocus(display, ev->window, RevertToPointerRoot, CurrentTime);

	clientcounter++;
	fprintf(stderr, "x-wm: clientcounter: %d\n", clientcounter);
}

void handle_focus ( XFocusChangeEvent* ev ) {
	/* TODO */
	fprintf(stdout, "x-wm: received XFocusChangeEvent event\n");
}

void handle_crossing ( XCrossingEvent* ev ) {
	fprintf(stdout, "x-wm: received XCrossingEvent event: state %d\n", ev->state);
	if ( ev->type == EnterNotify ) {
		XUngrabButton(display, Button1, 0, root);

		fprintf(stdout, "x-wm: received EnterNotify event: state %d\n", ev->state);
		XSetInputFocus(display, ev->window, RevertToPointerRoot, ev->time);
	}

	if ( ev->type == LeaveNotify ) {
		fprintf(stdout, "x-wm: received LeaveNotify event: state %d\n", ev->state);
		XGrabButton(display, Button1, 0, root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
	}
}

void handle_motion ( XMotionEvent* ev ) {
	/* TODO */
	fprintf(stdout, "x-wm: received MotionNotify event: state %d\n", ev->state);
}

void handle_buttonPress ( XButtonEvent* ev ) {
	Window foo;
	int bar,width,height;
	XWindowAttributes winattr;
	Client* c;

	fprintf(stdout, "x-wm: received ButtonPress event: button %d, state %d\n", ev->button, ev->state);

	while ( XCheckTypedEvent(display, ButtonPress, (XEvent*) ev) ) { }

	if ( XQueryPointer(display, root, &foo, &focus, &bar, &bar, &bar, &bar, &bar) == BadWindow ) {
		fprintf(stderr, "x-wm: received BadWindow from XQueryPointer\n");
	}

	/* cycle through windows with scroll wheel + Alt */
	if ( ev->button == Button4 && ev->state == Mod1Mask ) {
		if ( XCirculateSubwindowsUp(display, root) == BadWindow ) {
			fprintf(stderr, "x-wm: error while cycling upwards through windows (BadWindow)\n");
		} else {
			fprintf(stdout, "x-wm: cycle through windows\n");
		}
	}
	if ( ev->button == Button5 && ev->state == Mod1Mask ) {
		if ( XCirculateSubwindowsDown(display, root) == BadWindow ) {
			fprintf(stderr, "x-wm: error while cycling downwards through windows (BadWindow)\n");
		} else {
			fprintf(stdout, "x-wm: cycle through windows\n");
		}
	}

	if ( ev->button == Button1 ) {
		switch ( ev->state ) {
			case (Mod1Mask|ControlMask):
				/* click into some window -> raise it */
				fprintf(stdout, "x-wm: raise window\n");
				XRaiseWindow(display, focus);
				XSetInputFocus(display, focus, RevertToPointerRoot, CurrentTime);
				//XSendEvent(display, focus, False, NoEventMask, (XEvent*)ev);
				break;
			case 0:
				if ( focus == root || focus == None ) {
					/* click into root window -> launch xterm */
					fprintf(stdout, "x-wm: launch xterm\n");
					if ( fork() == 0 ) {
						execlp("xterm","xterm",0);
						fprintf(stderr, "x-wm: failed to launch xterm\n");
					}
				}
				break;
		}
	}

	if ( focus == None ) {
		return;
	}

	/* resize focused window veticaly width scroll wheel + ALT + Ctrl */
	if ( (ev->button == Button4 || ev->button == Button5) && ev->state == (Mod1Mask|ControlMask) ) {
		XGetWindowAttributes(display, focus, &winattr);

		width = winattr.width;
		height = winattr.height;

		if ( ev->button == Button4 && height >= 10 ) {
			XResizeWindow(display, focus, width, height-10);

			if ( (c = find_client(focus)) != NULL ) {
				c->maximized = 0;
			}
		}

		if ( (c = find_client(focus)) == NULL || c->maximized == 0 ) {
			if ( ev->button == Button5 && height+winattr.y <= DisplayHeight(display, screen)-10 ) {
				XResizeWindow(display, focus, width, height+10);
			}
		}
	}

	/* resize focused window horizontaly width scroll wheel + ALT + Ctrl + Shift */
	if ( (ev->button == Button4 || ev->button == Button5) && ev->state == (Mod1Mask|ControlMask|ShiftMask) ) {
		XGetWindowAttributes(display, focus, &winattr);

		width = winattr.width;
		height = winattr.height;

		if ( ev->button == Button4 && width >= 10) {
			XResizeWindow(display, focus, width-10, height);

			if ( (c = find_client(focus)) != NULL ) {
				c->maximized = 0;
			}
		}

		if ( (c = find_client(focus)) == NULL || c->maximized == 0 ) {
			if ( ev->button == Button5 && width+winattr.x <= DisplayWidth(display, screen)-10) {
				XResizeWindow(display, focus, width+10, height);
			}
		}
	}
}

void handle_keyPress ( XKeyEvent* ev ) {
	Window foo;
	int bar,x,y,width,height;
	Client* c;
	XWindowAttributes winattr;

	fprintf(stdout, "x-wm: received KeyPress event: keycode %d, state %d\n", ev->keycode, ev->state);

	if ( XQueryPointer(display, root, &foo, &focus, &bar, &bar, &bar, &bar, &bar) == BadWindow ) {
		fprintf(stderr, "x-wm: received BadWindow from XQueryPointer\n");
	}

	if ( ev->keycode == XKeysymToKeycode(display, XStringToKeysym("Tab")) && ev->state == Mod1Mask ) {
		/* cycle trough subwindows of root with Alt-Tab */
		if ( XCirculateSubwindowsUp(display, root) == BadWindow ) {
			fprintf(stderr, "x-wm: error while cycling through windows (BadWindow)\n");
		} else {
			fprintf(stdout, "x-wm: cycle through windows\n");
		}
	}

	if ( focus == None ) {
		return;
	}

	if ( ev->keycode == XKeysymToKeycode(display, XStringToKeysym("F4")) && ev->state == Mod1Mask ) {
		/* close window with Alt-F4 */
		fprintf(stdout, "x-wm: received close request for a window (Alt-F4)\n");
		close_window(focus);
	}

	if ( ev->keycode == XKeysymToKeycode(display, XStringToKeysym("F2")) && ev->state == Mod1Mask ) {
		/* maximize/restore window with Alt-F2 */

		width = DisplayWidth(display, screen);
		height = DisplayHeight(display, screen);
		x = 0;
		y = 0;

		c = find_client(focus);

		if ( c == NULL ) {
			fprintf(stdout, "x-wm: found window without client struct\n");
			c = create_client(focus);
		}

		if ( c->maximized ) {
			fprintf(stdout, "x-wm: unmaximize window\n");
			c->maximized = 0;
			x = c->x;
			y = c->y;
			width = c->width;
			height = c->height;
		} else {
			XGetWindowAttributes(display, focus, &winattr);
			fprintf(stdout, "x-wm: maximize window\n");

			c->maximized = 1;
			c->width = winattr.width;
			c->height = winattr.height;
			c->x = winattr.x;
			c->y = winattr.y;
		}

		XMoveResizeWindow(display, focus, x, y, width, height);
	}

	if ( ev->keycode == XKeysymToKeycode(display, XStringToKeysym("Up")) && ev->state == Mod1Mask ) {
		/* move window 'up' with Alt+UP */
		if ( (c = find_client(focus)) == NULL || c->maximized == 0 ) {
			XGetWindowAttributes(display, focus, &winattr);

			if ( winattr.y >= 10 ) {
				XMoveWindow(display, focus, winattr.x, winattr.y-10);
			}
		}
	}

	if ( ev->keycode == XKeysymToKeycode(display, XStringToKeysym("Down")) && ev->state == Mod1Mask ) {
		/* move window 'down' with Alt+Down */
		if ( (c = find_client(focus)) == NULL || c->maximized == 0 ) {
			XGetWindowAttributes(display, focus, &winattr);

			if ( winattr.y <= DisplayHeight(display, screen) - winattr.height - 10 ) {
				XMoveWindow(display, focus, winattr.x, winattr.y+10);
			}
		}
	}

	if ( ev->keycode == XKeysymToKeycode(display, XStringToKeysym("Left")) && ev->state == Mod1Mask ) {
		/* move window 'left' with Alt+Left */
		if ( (c = find_client(focus)) == NULL || c->maximized == 0 ) {
			XGetWindowAttributes(display, focus, &winattr);

			if ( winattr.x >= 10 ) {
				XMoveWindow(display, focus, winattr.x-10, winattr.y);
			}
		}
	}

	if ( ev->keycode == XKeysymToKeycode(display, XStringToKeysym("Right")) && ev->state == Mod1Mask ) {
		/* move window 'right' with Alt+Right */
		if ( (c = find_client(focus)) == NULL || c->maximized == 0 ) {
			XGetWindowAttributes(display, focus, &winattr);

			if ( winattr.x <= DisplayWidth(display, screen) - winattr.width - 10 ) {
				XMoveWindow(display, focus, winattr.x+10, winattr.y);
			}
		}
	}
}

void close_window ( Window w ) {
	XEvent ev;
	int protocols_count, i;
	Atom* protocols;

	if ( XGetWMProtocols(display, w, &protocols, &protocols_count) ) {
		for ( i = 0; i < protocols_count; i++ ) {
			if ( protocols[i] == wm_delete_window ) {
				ev.type = ClientMessage;
				ev.xclient.window = w;
				ev.xclient.message_type = wm_protocols;
				ev.xclient.format = 32;
				ev.xclient.data.l[0] = wm_delete_window;
				ev.xclient.data.l[1] = CurrentTime;

				XSendEvent(display, w, False, NoEventMask, &ev);
				XFree(protocols);
				return;
			}
		}

		if ( protocols ) {
			XFree(protocols);
		}
	}

	XKillClient(display, w);
}