mirror of
https://github.com/Phantop/phantop.github.io
synced 2024-12-23 21:26:40 +00:00
This commit is contained in:
parent
58ec71909c
commit
0b4e0dc674
317
reads/tinywm.md
Normal file
317
reads/tinywm.md
Normal file
|
@ -0,0 +1,317 @@
|
|||
|
||||
[Source](http://incise.org/tinywm.html "Permalink to tinywm")
|
||||
|
||||
# tinywm
|
||||
|
||||
TinyWM is a tiny window manager that I created as an exercise in minimalism. It is also maybe helpful in learning some of the very basics of creating a window manager. It is only around 50 lines of C. There is also a Python version using python-xlib.
|
||||
|
||||
It lets you do four basic things:
|
||||
|
||||
1. Move windows interactively with Alt+Button1 drag (left mouse button)
|
||||
2. Resize windows interactively with Alt+Button3 drag (right mouse button)
|
||||
3. Raise windows with Alt+F1 (not high on usability I know, but I needed a keybinding in there somewhere)
|
||||
4. Focus windows with the mouse pointer (X does this on its own)
|
||||
|
||||
## Download
|
||||
|
||||
## Known to be packaged in
|
||||
|
||||
* Debian
|
||||
* Ubuntu
|
||||
* FreeBSD
|
||||
* CRUX
|
||||
|
||||
## TinyWM around the web
|
||||
|
||||
## See Also
|
||||
|
||||
## The source
|
||||
|
||||
Here is tinywm.c from the most recent release, 1.3:
|
||||
|
||||
|
||||
/* TinyWM is written by Nick Welch , 2005.
|
||||
*
|
||||
* This software is in the public domain
|
||||
* and is provided AS IS, with NO WARRANTY. */
|
||||
|
||||
#include
|
||||
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
int main()
|
||||
{
|
||||
Display * dpy;
|
||||
Window root;
|
||||
XWindowAttributes attr;
|
||||
XButtonEvent start;
|
||||
XEvent ev;
|
||||
|
||||
if(!(dpy = XOpenDisplay(0x0))) return 1;
|
||||
|
||||
root = DefaultRootWindow(dpy);
|
||||
|
||||
XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask, root,
|
||||
True, GrabModeAsync, GrabModeAsync);
|
||||
XGrabButton(dpy, 1, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync,
|
||||
GrabModeAsync, None, None);
|
||||
XGrabButton(dpy, 3, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync,
|
||||
GrabModeAsync, None, None);
|
||||
|
||||
for(;;)
|
||||
{
|
||||
XNextEvent(dpy, &ev);
|
||||
if(ev.type == KeyPress && ev.xkey.subwindow != None)
|
||||
XRaiseWindow(dpy, ev.xkey.subwindow);
|
||||
else if(ev.type == ButtonPress && ev.xbutton.subwindow != None)
|
||||
{
|
||||
XGrabPointer(dpy, ev.xbutton.subwindow, True,
|
||||
PointerMotionMask|ButtonReleaseMask, GrabModeAsync,
|
||||
GrabModeAsync, None, None, CurrentTime);
|
||||
XGetWindowAttributes(dpy, ev.xbutton.subwindow, &attr);
|
||||
start = ev.xbutton;
|
||||
}
|
||||
else if(ev.type == MotionNotify)
|
||||
{
|
||||
int xdiff, ydiff;
|
||||
while(XCheckTypedEvent(dpy, MotionNotify, &ev));
|
||||
xdiff = ev.xbutton.x_root - start.x_root;
|
||||
ydiff = ev.xbutton.y_root - start.y_root;
|
||||
XMoveResizeWindow(dpy, ev.xmotion.window,
|
||||
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)
|
||||
XUngrabPointer(dpy, CurrentTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Here is annotated.c, which is just tinywm.c with a lot of comments explaining what is going on. This should give you a reasonable idea of how everything works.
|
||||
|
||||
|
||||
/* TinyWM is written by Nick Welch , 2005.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* most X stuff will be included with Xlib.h, but a few things require other
|
||||
* headers, like Xmd.h, keysym.h, etc.
|
||||
*/
|
||||
#include
|
||||
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
|
||||
int main()
|
||||
{
|
||||
Display * dpy;
|
||||
Window root;
|
||||
XWindowAttributes attr;
|
||||
|
||||
/* we use this to save the pointer's state at the beginning of the
|
||||
* move/resize.
|
||||
*/
|
||||
XButtonEvent start;
|
||||
|
||||
XEvent ev;
|
||||
|
||||
|
||||
/* return failure status if we can't connect */
|
||||
if(!(dpy = XOpenDisplay(0x0))) return 1;
|
||||
|
||||
/* you'll usually be referencing the root window a lot. this is a somewhat
|
||||
* naive approach that will only work on the default screen. most people
|
||||
* only have one screen, but not everyone. 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)
|
||||
*
|
||||
* many, probably most window managers only handle one screen, so in
|
||||
* reality this isn't really *that* naive.
|
||||
*
|
||||
* if you wanted to get the root window of a specific screen you'd use
|
||||
* RootWindow(), but the user can also control which screen is our default:
|
||||
* if they set $DISPLAY to ":0.foo", then our default screen number is
|
||||
* whatever they specify "foo" as.
|
||||
*/
|
||||
root = DefaultRootWindow(dpy);
|
||||
|
||||
/* 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, root,
|
||||
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, root, True, ButtonPressMask, GrabModeAsync,
|
||||
GrabModeAsync, None, None);
|
||||
XGrabButton(dpy, 3, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync,
|
||||
GrabModeAsync, None, 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)
|
||||
{
|
||||
/* now we take command of the pointer, looking for motion and
|
||||
* button release events.
|
||||
*/
|
||||
XGrabPointer(dpy, ev.xbutton.subwindow, True,
|
||||
PointerMotionMask|ButtonReleaseMask, GrabModeAsync,
|
||||
GrabModeAsync, None, None, CurrentTime);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
/* the only way we'd receive a motion notify event is if we already did
|
||||
* a pointer grab and we're in move/resize mode, so we assume that. */
|
||||
else if(ev.type == MotionNotify)
|
||||
{
|
||||
int xdiff, ydiff;
|
||||
|
||||
/* here we "compress" motion notify events. 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."
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
while(XCheckTypedEvent(dpy, MotionNotify, &ev));
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
xdiff = ev.xbutton.x_root - start.x_root;
|
||||
ydiff = ev.xbutton.y_root - start.y_root;
|
||||
XMoveResizeWindow(dpy, ev.xmotion.window,
|
||||
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)));
|
||||
}
|
||||
/* like motion notifies, the only way we'll receive a button release is
|
||||
* during a move/resize, due to our pointer grab. this ends the
|
||||
* move/resize.
|
||||
*/
|
||||
else if(ev.type == ButtonRelease)
|
||||
XUngrabPointer(dpy, CurrentTime);
|
||||
}
|
||||
}
|
||||
|
||||
And here's tinywm.py. XCheckTypedEvent has no equivalent in python-xlib, so it is commented out. It doesn't affect functionality, except that responsiveness is worse when you are moving/resizing (especially resizing a large window).
|
||||
|
||||
|
||||
# TinyWM is written by Nick Welch , 2005.
|
||||
#
|
||||
# This software is in the public domain
|
||||
# and is provided AS IS, with NO WARRANTY.
|
||||
|
||||
from Xlib.display import Display
|
||||
from Xlib import X, XK
|
||||
|
||||
dpy = Display()
|
||||
root = dpy.screen().root
|
||||
|
||||
root.grab_key(XK.string_to_keysym("F1"), X.Mod1Mask, 1,
|
||||
X.GrabModeAsync, X.GrabModeAsync)
|
||||
root.grab_button(1, X.Mod1Mask, 1, X.ButtonPressMask,
|
||||
X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE)
|
||||
root.grab_button(3, X.Mod1Mask, 1, X.ButtonPressMask,
|
||||
X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE)
|
||||
|
||||
while 1:
|
||||
ev = root.display.next_event()
|
||||
|
||||
if ev.type == X.KeyPress and ev.child != X.NONE:
|
||||
ev.window.circulate(X.RaiseLowest)
|
||||
elif ev.type == X.ButtonPress and ev.child != X.NONE:
|
||||
ev.child.grab_pointer(1, X.PointerMotionMask|X.ButtonReleaseMask,
|
||||
X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE, X.CurrentTime)
|
||||
attr = ev.child.get_geometry()
|
||||
start = ev
|
||||
elif ev.type == X.MotionNotify:
|
||||
#while(XCheckTypedEvent(dpy, MotionNotify, &ev));
|
||||
xdiff = ev.root_x - start.root_x
|
||||
ydiff = ev.root_y - start.root_y
|
||||
ev.window.configure(
|
||||
x = attr.x + (start.detail == 1 and xdiff or 0),
|
||||
y = attr.y + (start.detail == 1 and ydiff or 0),
|
||||
width = max(1, attr.width + (start.detail == 3 and xdiff or 0)),
|
||||
height = max(1, attr.height + (start.detail == 3 and ydiff or 0)))
|
||||
elif ev.type == X.ButtonRelease:
|
||||
dpy.ungrab_pointer(X.CurrentTime)
|
||||
|
||||
|
Loading…
Reference in a new issue