/*
* Nickleby Copyright (c) 2007, James Bailie.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * The name of James Bailie may not be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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 <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
Display *dpy;
Screen screen;
Window root, position;
GC gc1, gc2;
XFontStruct *font;
int tw, cw, sw, ew, workspace = 1, bw;
Atom delete, protocols, state, focus;
void update_titlebars( XEvent *, int );
char *font_names[ 5 ] =
{
"-*-helvetica-medium-r-normal-*-*-80-*-*-*-iso8859-1",
"-*-lucida-medium-r-normal-*-*-80-*-*-*-iso8859-1",
"-*-clean-medium-r-normal-*-*-80-*-*-*-iso8859-1",
"-*-fixed-medium-r-normal-*-*-80-*-*-*-iso8859-1",
NULL
};
int font_height, position_onscreen = 0;
unsigned long white, black, gray;
XButtonEvent start;
XWindowAttributes move_resize_attr, root_attr;
XSizeHints hints;
struct client
{
Window win, client, title, footer, button,
t_close, t_shade, t_expand,
f_close, f_shade, f_expand;
XSizeHints hints;
XWindowAttributes attr;
int workspace, hidden, max, focus, delete, focussed, ox, oy, ow, oh,
reparenting, shaded, recursive;
struct client *next;
} *clients = NULL;
struct client *client = NULL;
void *memory( unsigned int size )
{
void *ptr;
if ( size == 0 )
return NULL;
if (( ptr = malloc( size )) == NULL )
{
perror( "malloc" );
exit( 1 );
}
return ptr;
}
struct client *find_client( Window win )
{
struct client *ptr;
for( ptr = clients; ptr != NULL; ptr = ptr->next )
if ( ptr->win == win )
break;
return ptr;
}
struct client *find_client_window( Window win )
{
struct client *ptr;
for( ptr = clients; ptr != NULL; ptr = ptr->next )
if ( ptr->client == win )
break;
return ptr;
}
void send_message_to_client( Window win, Atom msg )
{
XEvent ev;
bzero( &ev, sizeof( ev ));
ev.type = ClientMessage;
ev.xclient.type = ClientMessage;
ev.xclient.window = win;
ev.xclient.message_type = protocols;
ev.xclient.data.l[ 0 ] = msg;
ev.xclient.data.l[ 1 ] = CurrentTime;
ev.xclient.format = 32;
XSendEvent( dpy, win, False, 0, &ev );
XFlush( dpy );
}
void draw_name( struct client *ptr )
{
char *name;
int in_focus, need_free = 1, len, width, height;
if ( ptr == NULL )
return;
XFetchName( dpy, ptr->client, &name );
in_focus = ptr->focussed;
if ( name == NULL )
{
need_free = 0;
name = "(untitled)";
}
len = strlen( name );
if ( need_free )
XFree( name );
XClearWindow( dpy, ptr->title );
XDrawString( dpy, ptr->t_shade, ( in_focus ? gc1 : gc2 ),
0, font->ascent, "_", 1 );
XDrawString( dpy, ptr->t_expand, ( in_focus ? gc1 : gc2 ),
0, font->ascent, "[]", 2 );
XDrawString( dpy, ptr->t_close, ( in_focus ? gc1 : gc2 ),
0, font->ascent, "||", 2 );
XDrawString( dpy, ptr->title, ( in_focus ? gc1 : gc2 ),
8, font->ascent + 5, name, len );
height = font_height + 10;
width = ( ptr->attr.width + ptr->attr.border_width * 2 );
if ( ptr->hidden )
XDrawRectangle( dpy, ptr->title, gc2, 1, 1, width - 3, height - 3 );
else if ( client == NULL )
XDrawRectangle( dpy, ptr->title, ( in_focus ? gc1 : gc2 ),
1, 1, width - 3, height - 2 );
else
XDrawRectangle( dpy, ptr->title, ( in_focus ? gc1 : gc2 ),
1, 1, width - 3, height - ( in_focus ? 3 : 2 ));
XClearWindow( dpy, ptr->footer );
XDrawString( dpy, ptr->f_shade, ( in_focus ? gc1 : gc2 ),
0, font->ascent - 1, "_", 1 );
XDrawString( dpy, ptr->f_expand, ( in_focus ? gc1 : gc2 ),
0, font->ascent - 1, "[]", 2 );
XDrawString( dpy, ptr->f_close, ( in_focus ? gc1 : gc2 ),
0, font->ascent - 1, "||", 2 );
XDrawString( dpy, ptr->footer, ( in_focus ? gc1 : gc2 ),
8, font->ascent + 4, name, len );
if ( ptr->hidden )
XDrawRectangle( dpy, ptr->footer, gc2, 1, 1, width - 3, height - 3 );
else if ( client == NULL )
XDrawRectangle( dpy, ptr->footer, ( in_focus ? gc1 : gc2 ),
1, 0, width - 3, height - 2 );
else
XDrawRectangle( dpy, ptr->footer, ( in_focus ? gc1 : gc2 ),
1, 1, width - 3, height - ( in_focus ? 3 : 2 ));
}
void focus_raise( struct client *ptr, int raise )
{
struct client *ptr2;
if ( ptr->hidden )
return;
if ( raise )
{
XRaiseWindow( dpy, ptr->win );
XRaiseWindow( dpy, ptr->title );
XRaiseWindow( dpy, ptr->footer );
}
XSetInputFocus( dpy, ptr->client, RevertToNone, CurrentTime );
ptr->focussed = 1;
draw_name( ptr );
if ( ptr->attr.colormap != None )
XInstallColormap( dpy, ptr->attr.colormap );
for( ptr2 = clients; ptr2 != NULL; ptr2 = ptr2->next )
if ( ptr2 != ptr )
if ( ptr2->focussed )
{
ptr2->focussed = 0;
draw_name( ptr2 );
}
if ( ptr->focus )
send_message_to_client( ptr->client, focus );
}
void constrain_aspect_ratio( int *w, int *h )
{
int t, min_width, max_width, min_height, max_height, width_inc, height_inc;
width_inc = height_inc = 1;
if ( hints.flags & PResizeInc )
{
width_inc = hints.width_inc;
height_inc = hints.height_inc;
}
min_width = min_height = 1;
max_width = root_attr.width -
( move_resize_attr.border_width * 2 + move_resize_attr.x );
max_height = root_attr.height -
(( font_height + 10 ) * 1 + move_resize_attr.border_width * 2 +
move_resize_attr.y );
if ( hints.flags & PMinSize )
{
if ( hints.min_width > 0 && hints.min_width <= max_width )
min_width = hints.min_width;
if ( hints.min_height > 0 && hints.min_height <= max_height )
min_height = hints.min_height;
}
if ( hints.flags & PMaxSize )
{
if ( hints.max_width <= max_width && hints.max_width > 0 )
max_width = hints.max_width;
if ( hints.max_height <= max_height && hints.max_height > 0 )
max_height = hints.max_height;
}
if ( hints.min_aspect.x * *h > hints.min_aspect.y * *w )
{
t = hints.min_aspect.x * *h / hints.min_aspect.y - *w;
t = t / width_inc * width_inc;
if ( *w + t <= max_width )
*w += t;
else
{
t = *h - *w * hints.min_aspect.y / hints.min_aspect.x;
t = t / height_inc * height_inc;
if ( *h - t >= min_height )
*h -= t;
}
}
if ( hints.max_aspect.x * *h < hints.max_aspect.y * *w )
{
t = *w * hints.max_aspect.y / hints.max_aspect. x - *h;
t = t / height_inc * height_inc;
if ( *h + t <= max_height )
*h += t;
else
{
t = *w - hints.max_aspect.x * *h / hints.max_aspect.y;
t = t / width_inc * width_inc;
if ( *w - t >= min_width )
*w -= t;
}
}
}
void constrain_by_hints( int *w, int *h )
{
if ( hints.flags & PMinSize )
{
if ( *w < hints.min_width && hints.min_width <= root_attr.width )
*w = hints.min_width;
if ( *h < hints.min_height && hints.min_height <= root_attr.height )
*h = hints.min_height;
}
if ( hints.flags & PMaxSize )
{
if ( *w > hints.max_width )
*w = hints.max_width;
if ( *h > hints.max_height )
*h = hints.max_height;
}
if ( hints.flags & PBaseSize && hints.flags & PResizeInc )
{
*w = ( *w - hints.base_width ) /
hints.width_inc * hints.width_inc + hints.base_width;
*h = ( *h - hints.base_height ) /
hints.height_inc * hints.height_inc + hints.base_height;
}
if ( hints.flags & PAspect )
constrain_aspect_ratio( w, h );
}
struct client *find_client_by_title_or_footer( Window win )
{
struct client *ptr;
for( ptr = clients; ptr != NULL; ptr = ptr->next )
if ( ptr->title == win || ptr->footer == win )
break;
return ptr;
}
struct client *find_client_by_button( Window win )
{
struct client *ptr;
for( ptr = clients; ptr != NULL; ptr = ptr->next )
if ( ptr->f_shade == win ||
ptr->f_expand == win ||
ptr->f_close == win ||
ptr->t_shade == win ||
ptr->t_expand == win ||
ptr->t_close == win )
{
ptr->button = win;
break;
}
return ptr;
}
void show_geometry( int x, int y, int w, int h, int bw )
{
char buffer[ 64 ];
int len, t;
w -= bw * 2;
h -= bw * 2;
x += bw;
y += bw;
snprintf( buffer, sizeof( buffer ), "+%d+%d, %dx%d", x, y,
( hints.flags & PResizeInc ? w / hints.width_inc : w ),
( hints.flags & PResizeInc ? h / hints.height_inc : h ));
len = strlen( buffer );
x += ( w - ( t = 10 + XTextWidth( font, buffer, len ))) / 2;
y += ( h - ( font_height + 10 )) / 2;
h = font_height + 10;
w = t;
if ( ! position_onscreen )
{
XMapRaised( dpy, position );
position_onscreen = 1;
}
XMoveResizeWindow( dpy, position, x, y, w, h );
XClearWindow( dpy, position );
XDrawRectangle( dpy, position, gc1, 1, 1, w - 3, h - 3 );
XDrawString( dpy, position, gc1, 5, font->ascent + 5, buffer, len );
}
int confine_to_root( int *x, int *y, int *w, int *h, int bw, int resizing )
{
int tb, ret;
ret = 0;
tb = font_height + 10;
if (( *x + *w + bw ) > root_attr.width )
{
++ret;
if ( resizing )
*w = root_attr.width - ( *x + bw );
else
*x = root_attr.width - ( *w + bw );
}
if ( *y < tb )
{
*y = tb;
++ret;
}
if (( *y + *h + bw + tb ) > root_attr.height )
{
++ret;
if ( resizing )
*h = root_attr.height - ( *y + bw + tb );
else
*y = root_attr.height - ( *h + bw + tb );
}
return ret;
}
void move_resize_window( XEvent *ev )
{
int xdiff, ydiff, x, y, w, h;
/*
* Occasionally we receive a MotionNotify event after we have
* released the pointer grab, in which case we must avoid
* dereferencing a NULL client pointer. The ButtonRelease event
* which caused us to relinquish the grab, will have reset client
* to NULL.
*/
if ( client == NULL )
return;
xdiff = ev->xmotion.x_root - start.x_root;
ydiff = ev->xmotion.y_root - start.y_root;
x = move_resize_attr.x + ( start.button < 3 ? xdiff : 0 );
y = move_resize_attr.y + ( start.button < 3 ? ydiff : 0 );
w = move_resize_attr.width + ( start.button == 3 ? xdiff : 0 );
h = move_resize_attr.height + ( start.button == 3 ? ydiff : 0 );
confine_to_root( &x, &y, &w, &h, move_resize_attr.border_width * 2,
( start.button == 3 ));
if ( start.button == 3 )
{
if (( w -= bw * 2 ) <= 0 )
w = 1;
if (( h -= bw * 2 ) <= 0 )
h = 1;
hints = client->hints;
constrain_by_hints( &w, &h );
w = client->attr.width = w + bw * 2;
h = client->attr.height = h + bw * 2;
}
else
{
if ( x < 0 )
x = 0;
if ( y < 0 )
y = 0;
client->attr.x = x;
client->attr.y = y;
}
update_titlebars( ev, 0 );
show_geometry( x, y, w, h, move_resize_attr.border_width );
}
void make_buttons( struct client *ptr, int width )
{
Window close, shade, expand;
if (( shade = XCreateSimpleWindow( dpy, ptr->title,
width - ( sw + ew + cw + 5 ), 5,
sw, font_height,
0, white, black ))
== None )
{
fputs( "nickleby: could not create shade button window", stderr );
exit( 1 );
}
if (( expand = XCreateSimpleWindow( dpy, ptr->title,
width - ( ew + cw + 5 ), 5,
ew, font_height,
0, white, black ))
== None )
{
fputs( "nickleby: could not create expand button window", stderr );
exit( 1 );
}
if (( close = XCreateSimpleWindow( dpy, ptr->title,
width - ( cw + 5 ),
5, cw, font_height,
0, white, black ))
== None )
{
fputs( "nickleby: could not create close button window", stderr );
exit( 1 );
}
ptr->t_shade = shade;
ptr->t_expand = expand;
ptr->t_close = close;
if (( shade = XCreateSimpleWindow( dpy, ptr->footer,
width - ( sw + ew + cw + 5 ), 5,
sw, font_height,
0, white, black ))
== None )
{
fputs( "nickleby: could not create shade button window", stderr );
exit( 1 );
}
if (( expand = XCreateSimpleWindow( dpy, ptr->footer,
width - ( ew + cw + 5 ), 5,
ew, font_height,
0, white, black ))
== None )
{
fputs( "nickleby: could not create expand button window", stderr );
exit( 1 );
}
if (( close = XCreateSimpleWindow( dpy, ptr->footer,
width - ( cw + 5 ),
5, cw, font_height,
0, white, black ))
== None )
{
fputs( "nickleby: could not create close button window", stderr );
exit( 1 );
}
ptr->f_shade = shade;
ptr->f_expand = expand;
ptr->f_close = close;
XSelectInput( dpy, ptr->t_shade, ButtonPressMask );
XSelectInput( dpy, ptr->t_expand, ButtonPressMask );
XSelectInput( dpy, ptr->t_close, ButtonPressMask );
XSelectInput( dpy, ptr->f_shade, ButtonPressMask );
XSelectInput( dpy, ptr->f_expand, ButtonPressMask );
XSelectInput( dpy, ptr->f_close, ButtonPressMask );
XMapWindow( dpy, ptr->t_shade );
XMapWindow( dpy, ptr->t_expand );
XMapWindow( dpy, ptr->t_close );
XMapWindow( dpy, ptr->f_shade );
XMapWindow( dpy, ptr->f_expand );
XMapWindow( dpy, ptr->f_close );
}
void make_title_footer( struct client *ptr )
{
Window title, footer;
XSetWindowAttributes wa;
XWindowChanges wc;
if (( title = XCreateSimpleWindow( dpy, root, 0, 0, 50,
font_height,
0, white, black ))
== None )
{
fputs( "nickleby: could not create title window", stderr );
exit( 1 );
}
wa.override_redirect = True;
XChangeWindowAttributes( dpy, title, CWOverrideRedirect, &wa );
XSelectInput( dpy, title, ExposureMask | ButtonPressMask |
EnterWindowMask );
wc.x = ptr->attr.x;
wc.y = ptr->attr.y - ( font_height + 10 );
wc.width = ptr->attr.width + ptr->attr.border_width * 2;
wc.height = font_height + 10;
ptr->title = title;
XConfigureWindow( dpy, title, CWX | CWY | CWWidth | CWHeight, &wc );
if (( footer = XCreateSimpleWindow( dpy, root, 0, 0, 50,
font_height, 0, white, black ))
== None )
{
fputs( "nickleby: could not create footer window", stderr );
exit( 1 );
}
wa.override_redirect = True;
XChangeWindowAttributes( dpy, footer, CWOverrideRedirect, &wa );
XSelectInput( dpy, footer, ExposureMask | ButtonPressMask |
EnterWindowMask );
wc.x = ptr->attr.x;
wc.y = ptr->attr.y + ptr->attr.height + ptr->attr.border_width * 2;
ptr->footer = footer;
XConfigureWindow( dpy, footer, CWX | CWY | CWWidth | CWHeight, &wc );
XMapRaised( dpy, title );
XMapRaised( dpy, footer );
make_buttons( ptr, wc.width );
draw_name( ptr );
}
void shade_unshade_window( struct client *ptr, int title )
{
if ( ! ptr->hidden )
{
XAddToSaveSet( dpy, ptr->win );
XUnmapWindow( dpy, ptr->win );
XUnmapWindow( dpy, ( title ? ptr->footer : ptr->title ));
ptr->shaded = ( title ? 1 : 2 );
ptr->hidden = 1;
ptr->focussed = 0;
draw_name( ptr );
}
else
{
XRemoveFromSaveSet( dpy, ptr->win );
XMapWindow( dpy, ptr->win );
XMapWindow( dpy, ( title ? ptr->footer : ptr->title ));
ptr->hidden = 0;
ptr->shaded = 0;
focus_raise( ptr, 1 );
}
}
void add_client( XEvent *ev )
{
Window win;
XWindowAttributes attr;
XWMHints *wmhints;
struct client *ptr1, *ptr2, *ptr3;
long int supplied;
Atom *protos;
int np, i, w, h;
for( ptr1 = clients, ptr2 = NULL;
ptr1 != NULL;
ptr1 = ptr1->next )
{
if ( ptr1->client == ev->xmaprequest.window )
return;
ptr2 = ptr1;
}
XGetWindowAttributes( dpy, ev->xmaprequest.window, &attr );
if ( attr.override_redirect == True )
{
XMapWindow( dpy, ev->xmaprequest.window );
return;
}
if ( XGetTransientForHint( dpy, ev->xmaprequest.window, &win ))
{
for ( ptr3 = clients; ptr3 != NULL; ptr3 = ptr3->next )
if ( ptr3->client == win )
break;
/*
* Don't manage transients of unmanaged windows.
*/
if ( ptr3 == NULL )
{
XMapWindow( dpy, ev->xmaprequest.window );
XSelectInput( dpy, ev->xmaprequest.window, EnterWindowMask );
return;
}
}
if ( ptr2 == NULL )
ptr1 = clients = memory( sizeof( struct client ));
else
ptr1 = ptr2->next = memory( sizeof( struct client ));
ptr1->attr = attr;
XGetWMNormalHints( dpy, ev->xmaprequest.window, &ptr1->hints, &supplied );
np = 0;
XGetWMProtocols( dpy, ev->xmaprequest.window, &protos, &np );
ptr1->focussed = 0;
ptr1->shaded = 0;
ptr1->focus = 0;
ptr1->recursive = 0;
ptr1->workspace = workspace;
ptr1->delete = 0;
ptr1->button = None;
i = np;
while( i-- )
{
if ( protos[ i ] == focus )
ptr1->focus = 1;
else if ( protos[ i ] == delete )
ptr1->delete = 1;
}
if ( np )
XFree( protos );
ptr1->client = ev->xmaprequest.window;
ptr1->next = NULL;
ptr1->max = ptr1->hidden = 0;
ptr1->ox = ptr1->oy = ptr1->oh = ptr1->ow = -1;
ptr1->attr.x -= 1; ptr1->attr.y -= 1;
if ( ptr1->attr.x < 0 )
ptr1->attr.x = 0;
if ( ptr1->attr.y < 0 )
ptr1->attr.y = 0;
ptr1->attr.width += ptr1->attr.border_width * 2;
ptr1->attr.height += ptr1->attr.border_width * 2;
if (( ptr1->win = XCreateSimpleWindow( dpy, root,
ptr1->attr.x,
ptr1->attr.y,
ptr1->attr.width,
ptr1->attr.height,
1, black, black ))
== None )
{
fputs( "nickleby: could not create window frame", stderr );
exit( 1 );
}
/*
* We need ButtonPress events even though we don't use them. They
* prevent clicks on ptr->win border from going through to the root
* window.
*/
XSelectInput( dpy, ptr1->win, SubstructureNotifyMask | EnterWindowMask |
ButtonPressMask );
XSelectInput( dpy, ptr1->client, ColormapChangeMask | PropertyChangeMask |
EnterWindowMask );
XReparentWindow( dpy, ptr1->client, ptr1->win, 0, 0 );
XGetWindowAttributes( dpy, root, &root_attr );
if ( confine_to_root( &ptr1->attr.x, &ptr1->attr.y,
&ptr1->attr.width, &ptr1->attr.height,
2, 1 ))
{
h = ptr1->attr.height - ptr1->attr.border_width * 2;
w = ptr1->attr.width - ptr1->attr.border_width * 2;
hints = ptr1->hints;
constrain_by_hints( &w, &h );
ptr1->attr.width = w + ptr1->attr.border_width * 2;
ptr1->attr.height = h + ptr1->attr.border_width * 2;
XMoveResizeWindow( dpy, ptr1->win, ptr1->attr.x, ptr1->attr.y,
ptr1->attr.width, ptr1->attr.height );
XMoveResizeWindow( dpy, ptr1->client, 0, 0, w, h );
}
ptr1->attr.border_width = 1;
ptr1->reparenting = 1;
XMapWindow( dpy, ptr1->client );
XMapWindow( dpy, ptr1->win );
make_title_footer( ptr1 );
wmhints = XGetWMHints( dpy, ptr1->win );
if ( wmhints != NULL && wmhints->initial_state == IconicState )
shade_unshade_window( ptr1, 1 );
else
focus_raise( ptr1, 1 );
if ( wmhints != NULL )
XFree( wmhints );
}
void remove_client( Window win, int reparent )
{
struct client *ptr1, *ptr2;
for( ptr1 = clients, ptr2 = NULL;
ptr1 != NULL;
ptr1 = ptr1->next )
{
if ( ptr1->client == win )
break;
ptr2 = ptr1;
}
if ( ptr1 == NULL )
return;
if ( ptr2 == NULL )
clients = ptr1->next;
else
ptr2->next = ptr1->next;
if ( reparent )
{
XReparentWindow( dpy, ptr1->client, root, ptr1->attr.x, ptr1->attr.y );
if ( reparent == 2 && XGetTransientForHint( dpy, ptr1->client, &win ))
XSelectInput( dpy, ptr1->client, EnterWindowMask );
}
if ( position_onscreen )
{
position_onscreen = 0;
XUnmapWindow( dpy, position );
}
XUnmapWindow( dpy, ptr1->win );
XUnmapWindow( dpy, ptr1->title );
XUnmapWindow( dpy, ptr1->footer );
XDestroySubwindows( dpy, ptr1->title );
XDestroySubwindows( dpy, ptr1->footer );
XDestroyWindow( dpy, ptr1->win );
XDestroyWindow( dpy, ptr1->title );
XDestroyWindow( dpy, ptr1->footer );
free( ptr1 );
}
void install_colormap( XEvent *ev )
{
struct client *ptr;
if (( ptr = find_client_window( ev->xcolormap.window )) == NULL )
return;
if ( ev->xcolormap.new == True )
{
ptr->attr.colormap = ev->xcolormap.colormap;
XInstallColormap( dpy, ev->xcolormap.colormap );
}
}
void update_config( struct client *ptr )
{
int w, h;
XWindowAttributes wa;
XGetWindowAttributes( dpy, ptr->client, &wa );
ptr->attr.width = wa.width + wa.border_width * 2;
ptr->attr.height = wa.height + wa.border_width * 2;
confine_to_root( &ptr->attr.x, &ptr->attr.y,
&ptr->attr.width, &ptr->attr.height,
ptr->attr.border_width * 2, 0 );
XMoveResizeWindow( dpy, ptr->win, ptr->attr.x, ptr->attr.y,
ptr->attr.width, ptr->attr.height );
w = ptr->attr.width - wa.border_width * 2;
h = ptr->attr.height - wa.border_width * 2;
XMoveResizeWindow( dpy, ptr->client, 0, 0, w, h );
}
void update_titlebars( XEvent *ev, int event )
{
int title_width;
struct client *ptr;
if ( event )
{
if (( ptr = find_client_window( ev->xconfigure.window )) == NULL )
return;
update_config( ptr );
}
else
ptr = client;
title_width = ptr->attr.width + ptr->attr.border_width * 2;
XMoveResizeWindow( dpy, ptr->title,
ptr->attr.x,
ptr->attr.y - ( font_height + 10 ),
title_width,
font_height + 10 );
XMoveWindow( dpy, ptr->t_shade, title_width - tw, 5 );
XMoveWindow( dpy, ptr->t_expand, title_width - ( ew + cw + 5 ), 5 );
XMoveWindow( dpy, ptr->t_close, title_width - ( cw + 5 ), 5 );
XMoveResizeWindow( dpy, ptr->footer,
ptr->attr.x,
ptr->attr.y + ptr->attr.height +
ptr->attr.border_width * 2,
ptr->attr.width + ptr->attr.border_width * 2,
font_height + 10 );
XMoveWindow( dpy, ptr->f_shade, title_width - tw, 5 );
XMoveWindow( dpy, ptr->f_expand, title_width - ( ew + cw + 5 ), 5 );
XMoveWindow( dpy, ptr->f_close, title_width - ( cw + 5 ), 5 );
draw_name( ptr );
}
void start_move_resize( struct client *ptr, XEvent *ev )
{
if ( XGrabPointer( dpy, ptr->win, True,
PointerMotionMask | ButtonReleaseMask,
GrabModeAsync,
GrabModeAsync, None, None, CurrentTime )
== GrabSuccess )
{
client = ptr;
hints = ptr->hints;
start = ev->xbutton;
XGetWindowAttributes( dpy, client->client, &move_resize_attr );
bw = move_resize_attr.border_width;
move_resize_attr = ptr->attr;
XGetWindowAttributes( dpy, root, &root_attr );
if ( ev->xbutton.button == 3 )
{
start.x_root = ptr->attr.x + ptr->attr.width;
start.y_root = ptr->attr.y + ptr->attr.height;
/*
* Refuse to resize windows which are partially offscreen
* because we can't warp the pointer offscreen. This should
* never happen.
*/
if ( start.x_root > root_attr.width ||
start.y_root > root_attr.height )
{
XUngrabPointer( dpy, CurrentTime );
return;
}
XWarpPointer( dpy, None, ptr->win, 0, 0, 0, 0,
ptr->attr.width,
ptr->attr.height );
focus_raise( ptr, 1 );
}
show_geometry( ptr->attr.x, ptr->attr.y,
ptr->attr.width, ptr->attr.height,
ptr->attr.border_width );
}
}
void maximize( struct client *ptr )
{
int h, w;
XWindowAttributes wa;
focus_raise( ptr, 1 );
if ( ptr->max )
{
ptr->attr.x = ptr->ox;
ptr->attr.y = ptr->oy;
ptr->attr.width = ptr->ow;
ptr->attr.height = ptr->oh;
XMoveResizeWindow( dpy, ptr->win, ptr->attr.x, ptr->attr.y,
ptr->attr.width, ptr->attr.height );
XGetWindowAttributes( dpy, ptr->client, &wa );
XResizeWindow( dpy, ptr->client, ptr->attr.width - wa.border_width * 2,
ptr->attr.height - wa.border_width * 2 );
ptr->max = 0;
return;
}
XGetWindowAttributes( dpy, root, &root_attr );
ptr->ox = ptr->attr.x;
ptr->oy = ptr->attr.y;
ptr->ow = ptr->attr.width;
ptr->oh = ptr->attr.height;
ptr->max = 1;
ptr->attr.x = 0;
ptr->attr.y = font_height + 10;
ptr->attr.width = root_attr.width - ptr->attr.border_width * 2;
ptr->attr.height = root_attr.height -
( ptr->attr.border_width * 2 + ( font_height + 10 ) * 2 );
XGetWindowAttributes( dpy, ptr->client, &wa );
w = ptr->attr.width - wa.border_width * 2;
h = ptr->attr.height - wa.border_width * 2;
hints = ptr->hints;
constrain_by_hints( &w, &h );
ptr->attr.width = w + wa.border_width * 2;
ptr->attr.height = h + wa.border_width * 2;
XMoveResizeWindow( dpy, ptr->win, ptr->attr.x, ptr->attr.y,
ptr->attr.width, ptr->attr.height );
XResizeWindow( dpy, ptr->client, w, h );
}
void process_button( XEvent *ev, struct client *ptr )
{
focus_raise( ptr, 1 );
switch( ev->xbutton.state )
{
case ControlMask:
case ShiftMask:
break;
case None:
switch( ev->xbutton.button )
{
case 1:
if ( ptr->button == ptr->t_shade )
{
shade_unshade_window( ptr, 1 );
return;
}
else if ( ptr->button == ptr->f_shade )
{
shade_unshade_window( ptr, 0 );
return;
}
if ( ! ptr->hidden )
{
if ( ptr->button == ptr->t_shade )
shade_unshade_window( ptr, 1 );
else if ( ptr->button == ptr->f_shade )
shade_unshade_window( ptr, 0 );
else if ( ptr->button == ptr->t_expand ||
ptr->button == ptr->f_expand )
maximize( ptr );
else if ( ptr->button == ptr->t_close ||
ptr->button == ptr->f_close )
{
if ( ptr->delete )
send_message_to_client( ptr->client, delete );
else
XKillClient( dpy, ptr->client );
}
}
break;
case 2:
break;
case 3:
if ( ! ptr->hidden )
{
if ( ptr->button == ptr->t_shade ||
ptr->button == ptr->f_shade )
remove_client( ptr->client, 1 );
else if ( ptr->button == ptr->t_expand ||
ptr->button == ptr->f_expand )
ptr->workspace = ( ptr->workspace ? 0 : workspace );
else if ( ptr->button == ptr->t_close ||
ptr->button == ptr->f_close )
XKillClient( dpy, ptr->client );
}
break;
}
}
}
void process_title_footer( XEvent *ev )
{
struct client *ptr;
if (( ptr = find_client_by_title_or_footer( ev->xbutton.window )) == NULL )
{
if (( ptr = find_client_by_button( ev->xbutton.window )) != NULL )
process_button( ev, ptr );
return;
}
focus_raise( ptr, 1 );
switch ( ev->xbutton.state )
{
case ShiftMask:
case ControlMask:
break;
case None:
switch( ev->xbutton.button )
{
case 2:
XLowerWindow( dpy, ptr->win );
XLowerWindow( dpy, ptr->title );
XLowerWindow( dpy, ptr->footer );
break;
case 1:
case 3:
start_move_resize( ptr, ev );
break;
}
}
}
void circulate_clients()
{
struct client *ptr, *ptr2;
if ( clients == NULL || clients->next == NULL )
return;
for( ptr = clients; ptr != NULL; ptr = ptr->next )
if ( ptr->focussed )
break;
if ( ptr == NULL )
{
for ( ptr = clients; ptr != NULL; ptr = ptr->next )
if ( ! ptr->hidden )
break;
if ( ptr != NULL )
focus_raise( ptr, 1 );
}
else
{
ptr2 = ptr;
for( ptr = ptr->next; ptr2 != ptr; ptr = ptr->next )
{
if ( ptr == NULL )
ptr = clients;
if ( ! ptr->hidden )
break;
}
if ( ptr != ptr2 )
focus_raise( ptr, 1 );
}
}
void change_workspace()
{
struct client *ptr;
workspace = ( workspace == 1 ? 2 : 1 );
for( ptr = clients; ptr != NULL; ptr = ptr->next )
if ( ptr->workspace )
{
if ( ptr->workspace != workspace )
{
XUnmapWindow( dpy, ptr->win );
XUnmapWindow( dpy, ptr->title );
XUnmapWindow( dpy, ptr->footer );
XAddToSaveSet( dpy, ptr->win );
ptr->hidden = 1;
}
else
{
if ( ptr->focussed )
{
ptr->focussed = 0;
draw_name( ptr );
}
switch( ptr->shaded )
{
case 0:
XMapWindow( dpy, ptr->win );
XMapWindow( dpy, ptr->title );
XMapWindow( dpy, ptr->footer );
ptr->hidden = 0;
XRemoveFromSaveSet( dpy, ptr->win );
break;
case 1:
XMapWindow( dpy, ptr->title );
break;
case 2:
XMapWindow( dpy, ptr->footer );
break;
}
}
}
}
void process_root( XEvent *ev )
{
switch( ev->xbutton.button )
{
case 1:
if ( ev->xbutton.state == ControlMask )
exit( 0 );
else if ( ev->xbutton.state == None )
circulate_clients();
break;
case 2:
if ( ev->xbutton.state == None )
change_workspace();
break;
case 3:
if ( ev->xbutton.state == None );
switch( fork() )
{
case -1:
perror( "fork" );
break;
case 0:
close( ConnectionNumber( dpy ));
execlp( "xterm", "xterm", 0 );
perror( "execlp" );
exit( 1 );
break;
}
}
}
void process_property( XEvent *ev )
{
struct client *ptr;
long int supplied;
if (( ptr = find_client_window( ev->xproperty.window )) == NULL )
return;
switch( ev->xproperty.atom )
{
case XA_WM_NAME:
draw_name( ptr );
break;
case XA_WM_NORMAL_HINTS:
XGetWMNormalHints( dpy, ptr->client, &ptr->hints, &supplied );
break;
}
}
void process_message( XEvent *ev )
{
struct client *ptr;
if (( ptr = find_client( ev->xclient.window )) == NULL )
return;
if ( ev->xclient.message_type == state &&
ev->xclient.format == 32 )
{
if (( ev->xclient.data.l[ 0 ] == IconicState && ! ptr->hidden ) ||
( ev->xclient.data.l[ 0 ] == NormalState && ptr->hidden ))
shade_unshade_window( ptr, 1 );
if ( ev->xclient.data.l[ 0 ] == WithdrawnState )
remove_client( ptr->client, 1 );
}
}
void configure_window( XEvent *ev )
{
XWindowChanges wc;
unsigned int value_mask = 0;
if ( find_client( ev->xconfigurerequest.window ) != NULL )
return;
if ( CWX & ev->xconfigurerequest.value_mask )
{
wc.x = ev->xconfigurerequest.x;
value_mask |= CWX;
}
if ( CWY & ev->xconfigurerequest.value_mask )
{
wc.y = ev->xconfigurerequest.y;
value_mask |= CWY;
}
if ( CWWidth & ev->xconfigurerequest.value_mask )
{
wc.width = ev->xconfigurerequest.width;
value_mask |= CWWidth;
}
if ( CWHeight & ev->xconfigurerequest.value_mask )
{
wc.height = ev->xconfigurerequest.height;
value_mask |= CWHeight;
}
if ( CWBorderWidth & ev->xconfigurerequest.value_mask )
{
wc.border_width = ev->xconfigurerequest.border_width;
value_mask |= CWBorderWidth;
}
if ( CWSibling & ev->xconfigurerequest.value_mask )
{
wc.sibling = ev->xconfigurerequest.above;
value_mask |= CWSibling;
}
if ( CWStackMode & ev->xconfigurerequest.value_mask )
{
wc.stack_mode = ev->xconfigurerequest.detail;
value_mask |= CWStackMode;
}
XConfigureWindow( dpy, ev->xconfigurerequest.window, value_mask, &wc );
}
void process_events()
{
XEvent ev;
struct client *ptr;
for( ; ; )
{
XNextEvent( dpy, &ev );
switch( ev.type )
{
case ClientMessage:
process_message( &ev );
break;
case MappingNotify:
XRefreshKeyboardMapping( &ev.xmapping );
break;
case EnterNotify:
while( XCheckTypedEvent( dpy, EnterNotify, &ev ))
;
if ( client != NULL || ev.xcrossing.mode != NotifyNormal )
continue;
if (( ptr = find_client_by_title_or_footer( ev.xcrossing.window ))
!= NULL ||
( ptr = find_client( ev.xcrossing.window )) != NULL ||
( ptr = find_client_window( ev.xcrossing.window )) != NULL )
focus_raise( ptr, 0 );
else
{
for ( ptr = clients; ptr != NULL; ptr = ptr->next )
{
if ( ptr->focussed )
{
ptr->focussed = 0;
draw_name( ptr );
}
}
XSetInputFocus( dpy, ev.xcrossing.window, RevertToNone,
CurrentTime );
send_message_to_client( ev.xcrossing.window, focus );
}
break;
case Expose:
if ( ev.xexpose.count )
continue;
draw_name( find_client_by_title_or_footer( ev.xexpose.window ));
break;
case ColormapNotify:
install_colormap( &ev );
break;
case MapRequest:
add_client( &ev );
break;
case ConfigureRequest:
configure_window( &ev );
break;
case UnmapNotify:
if (( ptr = find_client_window( ev.xunmap.window )) != NULL )
{
if ( ptr->reparenting )
ptr->reparenting = 0;
else
remove_client( ev.xunmap.window, 1 );
}
break;
case DestroyNotify:
remove_client( ev.xdestroywindow.window, 0 );
break;
case ConfigureNotify:
update_titlebars( &ev, 1 );
break;
case PropertyNotify:
process_property( &ev );
break;
case ButtonPress:
if ( ev.xbutton.window == root )
process_root( &ev );
else
process_title_footer( &ev );
break;
case MotionNotify:
while( XCheckTypedEvent( dpy, MotionNotify, &ev ))
;
move_resize_window( &ev );
break;
case ButtonRelease:
XUngrabPointer( dpy, CurrentTime );
XUnmapWindow( dpy, position );
if ( ev.xbutton.button == 3 )
{
XWindowAttributes wa;
XGetWindowAttributes( dpy, client->client, &wa );
XResizeWindow( dpy, client->win, client->attr.width,
client->attr.height );
XResizeWindow( dpy, client->client,
client->attr.width - wa.border_width * 2,
client->attr.height - wa.border_width * 2 );
}
else
XMoveWindow( dpy, client->win, client->attr.x, client->attr.y );
ptr = client;
client = NULL;
draw_name( ptr );
position_onscreen = 0;
}
}
}
void initialize_gc_font()
{
XGCValues gcvalues;
XColor color;
unsigned long gcmask;
char **ptr;
black = BlackPixel( dpy, DefaultScreen( dpy ));
white = WhitePixel( dpy, DefaultScreen( dpy ));
for( ptr = font_names; *ptr; ++ptr )
if (( font = XLoadQueryFont( dpy, *ptr ) ) != NULL )
break;
if ( font == NULL )
{
fputs( "nickleby: could not create font", stderr );
exit( 1 );
}
font_height = font->ascent + font->descent;
if ( ! XAllocNamedColor( dpy, DefaultColormap( dpy, DefaultScreen( dpy )),
"gray85", &color, &color ))
gcvalues.foreground = white;
else
gcvalues.foreground = color.pixel;
gcvalues.function = GXcopy;
gcvalues.line_width = 1;
gcvalues.line_style = LineSolid;
gcvalues.cap_style = CapButt;
gcvalues.fill_style = FillSolid;
gcvalues.fill_rule = EvenOddRule;
gcvalues.background = black;
gcmask = GCFunction | GCBackground | GCForeground | GCLineWidth |
GCLineStyle | GCFillStyle | GCFillRule;
if ( ! ( gc1 = XCreateGC( dpy, root, gcmask, &gcvalues )))
{
fputs( "nickleby: could not create GC 1", stderr );
exit( 1 );
}
if ( ! XAllocNamedColor( dpy, DefaultColormap( dpy, DefaultScreen( dpy )),
"gray60", &color, &color ))
gc2 = gc1;
else
{
gcvalues.foreground = color.pixel;
if( ( ! ( gc2 = XCreateGC( dpy, root, gcmask, &gcvalues ))))
{
fputs( "nickleby: could not create GC 2", stderr );
exit( 1 );
}
XSetFont( dpy, gc2, font->fid );
}
XSetFont( dpy, gc1, font->fid );
sw = XTextWidth( font, "_ ", 4 ) + 2;
ew = XTextWidth( font, "[] ", 5 );
cw = XTextWidth( font, "|| ", 3 );
tw = cw + sw + ew + 5;
}
void manage_existing()
{
Window r, p, *c;
Atom *protos;
int np, i, w, h;
struct client *ptr;
long int supplied;
unsigned int nc;
nc = 0;
if ( ! XQueryTree( dpy, root, &r, &p, &c, &nc ))
return;
XGetWindowAttributes( dpy, root, &root_attr );
ptr = NULL;
while( nc-- )
{
XGetWindowAttributes( dpy, c[ nc ], &move_resize_attr );
if ( move_resize_attr.override_redirect == True ||
move_resize_attr.map_state == IsUnmapped )
continue;
if ( ptr == NULL )
ptr = clients = memory( sizeof( struct client ));
else
ptr = ptr->next = memory( sizeof( struct client ));
ptr->attr = move_resize_attr;
XGetWMNormalHints( dpy, c[ nc ], &ptr->hints, &supplied );
np = 0;
XGetWMProtocols( dpy, c[ nc ], &protos, &np );
ptr->focussed = 0;
ptr->focus = 0;
ptr->delete = 0;
ptr->recursive = 0;
ptr->workspace = workspace;
i = np;
while( i-- )
{
if ( protos[ i ] == focus )
ptr->focus = 1;
else if ( protos[ i ] == delete )
ptr->delete = 1;
}
if ( np )
XFree( protos );
ptr->client = c[ nc ];
ptr->next = NULL;
ptr->hidden = 0;
ptr->shaded = 0;
ptr->max = 0;
ptr->ox = ptr->oy = ptr->oh = ptr->ow = -1;
if (( ptr->attr.y -= 1 ) < 0 )
ptr->attr.y = 0;
if (( ptr->attr.x -= 1 ) < 0 )
ptr->attr.x = 0;
ptr->attr.width += ptr->attr.border_width * 2;
ptr->attr.height += ptr->attr.border_width * 2;
if (( ptr->win = XCreateSimpleWindow( dpy, root,
ptr->attr.x,
ptr->attr.y,
ptr->attr.width,
ptr->attr.height,
1, black, black ))
== None )
{
fputs( "nickleby: could not create window frame", stderr );
exit( 1 );
}
/*
* We need to receive ButtonPress events even though we don't use
* them to prevent them from going to the root window.
*/
XSelectInput( dpy, ptr->win, SubstructureNotifyMask | EnterWindowMask |
ButtonPressMask );
XSelectInput( dpy, c[ nc ], ColormapChangeMask | EnterWindowMask |
PropertyChangeMask );
XReparentWindow( dpy, ptr->client, ptr->win, 0, 0 );
XMapWindow( dpy, ptr->client );
XMapWindow( dpy, ptr->win );
XGetWindowAttributes( dpy, root, &root_attr );
if ( confine_to_root( &ptr->attr.x, &ptr->attr.y,
&ptr->attr.width, &ptr->attr.height,
2, 1 ))
{
hints = ptr->hints;
w = ptr->attr.width - ptr->attr.border_width * 2;
h = ptr->attr.height - ptr->attr.border_width * 2;
constrain_by_hints( &w, &h );
ptr->attr.width = w + ptr->attr.border_width * 2;
ptr->attr.height = h + ptr->attr.border_width * 2;
XMoveResizeWindow( dpy, ptr->win, ptr->attr.x, ptr->attr.y,
ptr->attr.width, ptr->attr.height );
XMoveResizeWindow( dpy, ptr->client, 0, 0, w, h );
}
ptr->reparenting = 1;
ptr->attr.border_width = 1;
make_title_footer( ptr );
focus_raise( ptr, 1 );
}
XFree( c );
}
void create_position()
{
XSetWindowAttributes wa;
if (( position = XCreateSimpleWindow( dpy, root, 0, 0, 50,
font_height,
0, white, black ))
== None )
{
fputs( "nickleby: could not create position window", stderr );
exit( 1 );
}
wa.override_redirect = True;
XChangeWindowAttributes( dpy, position, CWOverrideRedirect, &wa );
}
int error_handler( Display *dpy, XErrorEvent *ev )
{
#ifdef DEBUG
char buffer[ 128 ];
static char *codes[ 18 ] =
{ "Success", "BadRequest", "BadValue", "BadWindow", "BadPixmap",
"BadAtom", "BadCursor", "BadFont", "BadMatch", "BadDrawable",
"BadAccess", "BadAlloc", "BadColor", "BadGC", "BadIDChoice",
"BadName", "BadLength", "BadImplementation" };
fprintf( stderr, "nickleby: X error: %s:\n Resource ID: %lu\n",
XDisplayString( dpy ), ev->resourceid );
XGetErrorText( dpy, ev->error_code, buffer, 128 );
if ( ev->error_code > 0 && ev->error_code < 18 )
fprintf( stderr, " %s: %s\n", buffer, codes[ ev->error_code ] );
else
fprintf( stderr, " %s\n", buffer );
#endif
return 0;
}
int main( int argc, char **argv )
{
int i;
char *disp = NULL;
while(( i = getopt( argc, argv, "d:" )) != -1 )
if ( i == 'd' )
{
if (( disp = optarg ) != NULL && ! *disp )
disp = NULL;
}
if ( !( dpy = XOpenDisplay( disp )))
{
fputs( "nickleby: could not open display\n", stderr );
return 1;
}
XSetCloseDownMode( dpy, DestroyAll );
root = DefaultRootWindow( dpy );
protocols = XInternAtom( dpy, "WM_PROTOCOLS", False );
delete = XInternAtom( dpy, "WM_DELETE_WINDOW", False );
state = XInternAtom( dpy, "WM_STATE", False );
focus = XInternAtom( dpy, "WM_TAKE_FOCUS", False );
initialize_gc_font();
XSetInputFocus( dpy, None, RevertToNone, CurrentTime );
XSetErrorHandler( error_handler );
signal( SIGCHLD, SIG_IGN );
create_position();
manage_existing();
if ( ! XSelectInput( dpy, root, ButtonPressMask |
SubstructureNotifyMask |
SubstructureRedirectMask ))
{
fputs( "nickleby: could not get events on root", stderr );
exit( 1 );
}
XFlush( dpy );
process_events();
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1