/* * 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 #include #include #include #include #include #include #include #include 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; }