tinyWM
学习窗口管理器的简单入门示例, 以下翻译自:
/* TinyWM is written by Nick Welch <nick@incise.org> in 2005 & 2011.
*
* This software is in the public domain
* and is provided AS IS, with NO WARRANTY. */
/* much of tinywm's purpose is to serve as a very basic example of how to do X
* stuff and/or understand window managers, so i wanted to put comments in the
* code explaining things, but i really hate wading through code that is
* over-commented -- and for that matter, tinywm is supposed to be as concise
* as possible, so having lots of comments just wasn't really fitting for it.
* i want tinywm.c to be something you can just look at and go "wow, that's
* it? cool!" so what i did was just copy it over to annotated.c and comment
* the hell out of it. ahh, but now i have to make every code change twice!
* oh well. i could always use some sort of script to process the comments out
* of this and write it to tinywm.c ... nah.
*/
/* 大多数X的东西需要包含在Xlib.h,但有些需要而外的头文件,比如
* Xmd.h, keysym.h , 等等.
*/
#include <X11/Xlib.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main(void)
{
Display * dpy;
XWindowAttributes attr;
/* 用来保存窗口[移动/调整大小]时的初始状态. */
XButtonEvent start;
XEvent ev;
/* 如果不能连接到 X server ,退出程序.*/
if(!(dpy = XOpenDisplay(0x0))) return 1;
/* 我们使用 DefaultRootWindow 来获取根窗口(root window)是个幼稚的方式,
* 因为它将只会运行在默认屏幕(screen)上,尽管许多人只有一个屏幕,但不是所有人.
* if you run multi-head without
* xinerama then you quite possibly have multiple screens. (i'm not sure
* about vendor-specific implementations, like nvidia's)
*
* 通常上,大部分窗口管理器只处理一个屏幕,所以实际上这也不是那么幼稚:).
*
* 如果你想要获得一个特定屏幕的根窗口,你应该用RootWindow()
* 但用户也可以控制那一个屏幕为自己的默认屏幕(default screen).
* 如果他们设置 $DISPLAY 为 ":0.foo",
* 那么他们的默认屏幕就是他们设置的"foo".
*/
/* you could also include keysym.h and use the XK_F1 constant instead of
* the call to XStringToKeysym, but this method is more "dynamic." imagine
* you have config files which specify key bindings. instead of parsing
* the key names and having a huge table or whatever to map strings to XK_*
* constants, you can just take the user-specified string and hand it off
* to XStringToKeysym. XStringToKeysym will give you back the appropriate
* keysym or tell you if it's an invalid key name.
*
* a keysym is basically a platform-independent numeric representation of a
* key, like "F1", "a", "b", "L", "5", "Shift", etc. a keycode is a
* numeric representation of a key on the keyboard sent by the keyboard
* driver (or something along those lines -- i'm no hardware/driver expert)
* to X. so we never want to hard-code keycodes, because they can and will
* differ between systems.
*/
XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask,
DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync);
/* XGrabKey and XGrabButton are basically ways of saying "when this
* combination of modifiers and key/button is pressed, send me the events."
* so we can safely assume that we'll receive Alt+F1 events, Alt+Button1
* events, and Alt+Button3 events, but no others. You can either do
* individual grabs like these for key/mouse combinations, or you can use
* XSelectInput with KeyPressMask/ButtonPressMask/etc to catch all events
* of those types and filter them as you receive them.
*/
XGrabButton(dpy, 1, Mod1Mask, DefaultRootWindow(dpy), True,
ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
XGrabButton(dpy, 3, Mod1Mask, DefaultRootWindow(dpy), True,
ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
start.subwindow = None;
for(;;)
{
/* this is the most basic way of looping through X events; you can be
* more flexible by using XPending(), or ConnectionNumber() along with
* select() (or poll() or whatever floats your boat).
*/
XNextEvent(dpy, &ev);
/* this is our keybinding for raising windows. as i saw someone
* mention on the ratpoison wiki, it is pretty stupid; however, i
* wanted to fit some sort of keyboard binding in here somewhere, and
* this was the best fit for it.
*
* i was a little confused about .window vs. .subwindow for a while,
* but a little RTFMing took care of that. our passive grabs above
* grabbed on the root window, so since we're only interested in events
* for its child windows, we look at .subwindow. when subwindow ==
* None, that means that the window the event happened in was the same
* window that was grabbed on -- in this case, the root window.
*/
if(ev.type == KeyPress && ev.xkey.subwindow != None)
XRaiseWindow(dpy, ev.xkey.subwindow);
else if(ev.type == ButtonPress && ev.xbutton.subwindow != None)
{
/* we "remember" the position of the pointer at the beginning of
* our move/resize, and the size/position of the window. that way,
* when the pointer moves, we can compare it to our initial data
* and move/resize accordingly.
*/
XGetWindowAttributes(dpy, ev.xbutton.subwindow, &attr);
start = ev.xbutton;
}
/* we only get motion events when a button is being pressed,
* but we still have to check that the drag started on a window */
else if(ev.type == MotionNotify && start.subwindow != None)
{
/* here we could "compress" motion notify events by doing:
*
* while(XCheckTypedEvent(dpy, MotionNotify, &ev));
*
* if there are 10 of them waiting, it makes no sense to look at
* any of them but the most recent. in some cases -- if the window
* is really big or things are just acting slowly in general --
* failing to do this can result in a lot of "drag lag," especially
* if your wm does a lot of drawing and whatnot that causes it to
* lag.
*
* for window managers with things like desktop switching, it can
* also be useful to compress EnterNotify events, so that you don't
* get "focus flicker" as windows shuffle around underneath the
* pointer.
*/
/* now we use the stuff we saved at the beginning of the
* move/resize and compare it to the pointer's current position to
* determine what the window's new size or position should be.
*
* if the initial button press was button 1, then we're moving.
* otherwise it was 3 and we're resizing.
*
* we also make sure not to go negative with the window's
* dimensions, resulting in "wrapping" which will make our window
* something ridiculous like 65000 pixels wide (often accompanied
* by lots of swapping and slowdown).
*
* even worse is if we get "lucky" and hit a width or height of
* exactly zero, triggering an X error. so we specify a minimum
* width/height of 1 pixel.
*/
int xdiff = ev.xbutton.x_root - start.x_root;
int ydiff = ev.xbutton.y_root - start.y_root;
XMoveResizeWindow(dpy, start.subwindow,
attr.x + (start.button==1 ? xdiff : 0),
attr.y + (start.button==1 ? ydiff : 0),
MAX(1, attr.width + (start.button==3 ? xdiff : 0)),
MAX(1, attr.height + (start.button==3 ? ydiff : 0)));
}
else if(ev.type == ButtonRelease)
{
start.subwindow = None;
}
}
}