/* Clementine Window Manager
Copyright 2002 Dave Berton <db@mosey.org>
based on aewm Copyright 1998-2001 Decklin Foster <decklin@red-bean.com>
This program is free software; see LICENSE for details. */
#include "windowmanager.h"
#include "painter.h"
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define XK_MISCELLANY
#include <X11/keysymdef.h>
/* global window manager */
WindowManager* windowManager = 0;
/* Modes for find_client */
#define WINDOW 0
#define FRAME 1
#define DEF_NEW1 "rxvt"
#define DEF_NEW2 ""
#define DEF_NEW3 "rxvt"
WindowManager::WindowManager()
: WindowSystem(),
menu(0),
active(0)
{
opt_new1 = DEF_NEW1;
opt_new2 = DEF_NEW2;
opt_new3 = DEF_NEW3;
if ( painter()->rootCommand().length() )
exec( (char*)painter()->rootCommand().c_str() );
}
/* After pulling my hair out trying to find some way to tell if a
* window is still valid, I've decided to instead carefully ignore any
* errors raised by this function. We know that the X calls are, and
* we know the only reason why they could fail -- a window has removed
* itself completely before the Unmap and Destroy events get through
* the queue to us. It's not absolutely perfect, but it works.
*
* The 'withdrawing' argument specifes if the client is actually
* (destroying itself||being destroyed by us) or if we are merely
* cleaning up its data structures when we exit mid-session. */
WindowManager::~WindowManager()
{
shutdown();
}
void WindowManager::shutdown()
{
#ifdef DEBUG
std::cout << "WindowManager shutting down" << std::endl;
dumpClients();
#endif
unsigned int nwins, i;
Window dummyw1, dummyw2, *wins;
Client *c;
queryTree( &dummyw1, &dummyw2, &wins, &nwins);
grabServer();
ignoreErrors();
for (i = 0; i < nwins; i++) {
c = findClient(wins[i], FRAME);
if (c) {
c->map();
std::vector<Client*>::iterator it = std::find(clients.begin(),
clients.end(),
c);
assert( it != clients.end() );
clients.erase( it );
delete c;
}
}
sync();
unignoreErrors();
ungrabServer();
xFree(wins);
assert( clients.size() == 0 );
//assert( icons.size() == 0 ); db more
}
void WindowManager::exec(char *cmd)
{
pid_t pid = fork();
switch (pid) {
case 0:
execlp("/bin/sh", "sh", "-c", cmd, NULL);
err("exec failed, cleaning up child");
exit(1);
case -1:
err("can't fork");
}
}
void WindowManager::withdraw( Client* c )
{
grabServer();
ignoreErrors();
#ifdef DEBUG
err("withdrawing " + c->name());
#endif
if ( active && c == active )
active = 0;
std::vector<Client*>::iterator it = std::find(clients.begin(),
clients.end(),
c);
assert( it != clients.end() );
clients.erase( it );
delete c;
sync();
unignoreErrors();
ungrabServer();
}
/* Set up a client structure for the new (not-yet-mapped) window. The
* confusing bit is that we have to ignore 2 unmap events if the
* client was already mapped but has IconicState set (for instance,
* when we are the second window manager in a session). That's
* because there's one for the reparent (which happens on all viewable
* windows) and then another for the unmapping itself. */
void WindowManager::newWindow( Window w, bool viewable,
int x, int y, int width, int height,
long colormap)
{
Client *c;
grabServer();
c = new Client( this, w, viewable, x, y, width, height, colormap );
if ( c->state() == IconicState )
icons.push_back( c );
else
clients.push_back( c );
#ifdef DEBUG
dump(c);
#endif
sync();
ungrabServer();
}
void WindowManager::newIcon( Window w, bool viewable,
int x, int y, int width, int height,
long colormap)
{
Client *c = findClient(w, WINDOW);
if (c) {
/* add to icons */
std::vector<Client*>::iterator it = std::find( icons.begin(),
icons.end(),
c);
assert( it == icons.end() );
icons.push_back( c );
/* remove from clients */
it = std::find(clients.begin(),
clients.end(),
c);
assert( it != clients.end() );
clients.erase( it );
} else {
assert(false);
}
#ifdef DEBUG
dumpClients();
#endif
}
#ifdef DEBUG
/* Bleh, stupid macro names. I'm not feeling creative today. */
#define SHOW(name) \
case name: return #name;
const char * WindowManager::showState(Client *c)
{
switch ( c->state() ) {
SHOW(WithdrawnState)
SHOW(NormalState)
SHOW(IconicState)
default: return "unknown state";
}
}
/*
const char * WindowManager::showGrav(Client *c)
{
if (!c->size || !(c->size->flags & PWinGravity))
return "no grav (NW)";
switch (c->size->win_gravity) {
SHOW(UnmapGravity)
SHOW(NorthWestGravity)
SHOW(NorthGravity)
SHOW(NorthEastGravity)
SHOW(WestGravity)
SHOW(CenterGravity)
SHOW(EastGravity)
SHOW(SouthWestGravity)
SHOW(SouthGravity)
SHOW(SouthEastGravity)
SHOW(StaticGravity)
default: return "unknown grav";
}
}
*/
void WindowManager::dump(Client *c)
{
err( c->name() + std::string( "\n\t" ) +
/*std::string( showState(c) )+*/ std::string( ", ignore\n" ));
// db more
/* +
convertToString( c->ignore_unmap ) + std::string( "\tframe " ) +
convertToString( c->frame ) + std::string( ", win " ) +
convertToString( c->window ) + std::string( ", geom " ) +
convertToString( c->width ) + std::string( "," ) +
convertToString( c->height ) + std::string( "," ) +
convertToString( c->x ) + std::string( "," ) +
convertToString( c->y ));
*/
}
void WindowManager::dumpClients()
{
std::cout << "CLIENTS:-----------------" << std::endl;
std::vector<Client*>::iterator it = clients.begin();
for( ; it != clients.end(); ++it )
dump(*it);
std::cout << "ICONS:-------------------" << std::endl;
it = icons.begin();
for( ; it != icons.end(); ++it )
dump(*it);
std::cout << "-------------------------" << std::endl;
}
#endif
bool WindowManager::managedWindow( Window w )
{
return findClient( w, WINDOW ) != 0;
}
Client* WindowManager::findClient(Window w, int mode)
{
Client *c;
std::vector<Client*>::const_iterator it = clients.begin();
for( ; it != clients.end(); ++it ) {
c = *it;
if ( mode == FRAME ) {
if ( c->isFrame( w ) ||
c->isCloseButton( w ) ||
c->isIconizeButton( w ) ||
c->isTitlebar( w ) )
return c;
} else { /* WINDOW */
if ( c->isWindow( w ) )
return c;
}
}
return 0;
}
/* find a client based on client window or frame window */
Client* WindowManager::findClient(Window w)
{
Client* c = findClient( w, FRAME );
if ( c )
return c;
return findClient( w, WINDOW );
}
void WindowManager::takeButtonPress( Window w, unsigned int button,
bool mod1,
int x, int y )
{
if ( menu && menu->isWindow( w ) ) {
int pointer_x;
int pointer_y;
pointerPosition(&pointer_x, &pointer_y);
int item = menu->item( x,y );
delete menu;
menu = 0;
assert( item <= (int)icons.size() );
Client* c = icons[item];
assert(c);
c->mapRaised();
c->redraw( true );
clients.push_back( c );
icons.erase( std::find( icons.begin(), icons.end(), c ) );
#ifdef DEBUG
dumpClients();
#endif
} else {
if ( menu ) {
delete menu;
menu = 0;
}
Client *c = findClient(w);
if ( c )
c->takeButtonPress( w, button, mod1, x, y );
}
}
void WindowManager::takeButtonRelease( Window w, unsigned int button,
bool mod1,
int x, int y )
{
Client *c = findClient(w);
if ( c )
c->takeButtonRelease( w, button, mod1, x, y );
}
void WindowManager::takeRootButton( unsigned int button,
bool mod1,
int x, int y )
{
if ( menu ) {
delete menu;
menu = 0;
}
switch (button) {
case Button1:
exec(opt_new1);
break;
case Button2:
if ( button == 2 ) {
shutdown();
WindowSystem::shutdown();
const char* cmd = "/usr/X11R6/bin/clementine"; // db more, path?
execlp("/bin/sh", "sh", "-c", cmd, NULL);
err("exec failed, cleaning up child");
exit(1);
}
case Button3:
if ( icons.size() )
menu = new Menu( this, icons, x, y );
break;
}
}
/* Because we are redirecting the root window, we get ConfigureRequest
* events from both clients we're handling and ones that we aren't.
* For clients we manage, we need to fiddle with the frame and the
* client window, and for unmanaged windows we have to pass along
* everything unchanged. Thankfully, we can reuse (a) the
* XWindowChanges struct and (c) the code to configure the client
* window in both cases.
*
* Most of the assignments here are going to be garbage, but only the
* ones that are masked in by e->value_mask will be looked at by the X
* server. */
void WindowManager::takeConfigureRequest( Window w, int x, int y,
int width, int height,
unsigned int valuemask,
Window sibling, int stack_mode )
{
int x1,y1;
Client *c = findClient(w, WINDOW);
if (c) {
c->takeConfigureRequest( x, y, width, height, valuemask, sibling,
stack_mode );
/* start setting up the next call */
x1 = 0;
y1 = c->titleHeight();
} else {
x1 = x;
y1 = y;
}
configure( w, x1, y1, width, height, valuemask, sibling, stack_mode );
}
/* Two possiblilies if a client is asking to be mapped. One is that
* it's a new window, so we handle that if it isn't in our clients
* list anywhere. The other is that it already exists and wants to
* de-iconify, which is simple to take care of. */
void WindowManager::takeMap( Window w )
{
Client *c = findClient(w, WINDOW);
if (!c)
throw ("Client: bad takeMap request");
else
c->mapRaised();
}
/* See aewm.h for the intro to this one. If this is a window we
* unmapped ourselves, decrement c->ignore_unmap and casually go on as
* if nothing had happened. If the window unmapped itself from under
* our feet, however, get rid of it.
*
* If you spend a lot of time with -DDEBUG on, you'll realize that
* because most clients unmap and destroy themselves at once, they're
* gone before we even get the Unmap event, never mind the Destroy
* one. This will necessitate some extra caution in remove_client.
*
* Personally, I think that if Map events are intercepted, Unmap
* events should be intercepted too. No use arguing with a standard
* that's almost as old as I am though. :-( */
void WindowManager::takeUnmap( Window w )
{
Client *c = findClient(w, WINDOW);
if (!c) {
return;
}
if ( c->checkUnmap() ) {
withdraw(c);
}
}
/* This happens when a window is iconified and destroys itself. An
* Unmap event wouldn't happen in that case because the window is
* already unmapped. */
void WindowManager::takeDestroy( Window w )
{
Client *c = findClient(w, WINDOW);
if (!c) {
return;
}
withdraw(c);
}
/* All that we have cached is the name and the size hints, so we only
* have to check for those here. A change in the name means we have to
* immediately wipe out the old name and redraw; size hints only get
* used when we need them. */
void WindowManager::takeNameChange( Window w, std::string name )
{
Client *c = findClient(w, WINDOW);
if (!c)
return;
c->setName(name);
}
/* X's default focus policy is follows-mouse, but we have to set it
* anyway because some sloppily written clients assume that (a) they
* can set the focus whenever they want or (b) that they don't have
* the focus unless the keyboard is grabbed to them. OTOH it does
* allow us to keep the previous focus when pointing at the root,
* which is nice.
*
* We also implement a colormap-follows-mouse policy here. That, on
* the third hand, is *not* X's default. */
void WindowManager::takeFocus( Window w )
{
Client *c = findClient(w, FRAME);
if (!c)
return;
if ( active && active != c ) {
active->redraw( false );
}
active = c;
c->takeFocus();
c->redraw( true );
}
/* Here's part 2 of our colormap policy: when a client installs a new
* colormap on itself, set the display's colormap to that. Arguably,
* this is bad, because we should only set the colormap if that client
* has the focus. However, clients don't usually set colormaps at
* random when you're not interacting with them, so I think we're
* safe. If you have an 8-bit display and this doesn't work for you,
* by all means yell at me, but very few people have 8-bit displays
* these days. */
void WindowManager::takeNewColormap( Window w, long colormap )
{
Client *c = findClient(w, WINDOW);
if (c)
c->installColormap( colormap );
}
/* If we were covered by multiple windows, we will usually get
* multiple expose events, so ignore them unless e->count (the number
* of outstanding exposes) is zero. */
void WindowManager::takeExpose( Window w )
{
if ( menu )
menu->expose();
else {
Client *c = findClient(w);
if (c)
c->redraw();
}
}
void WindowManager::takeMotion( Window w, int x, int y )
{
if ( menu && menu->isWindow( w ) ) {
int pointer_x;
int pointer_y;
pointerPosition(&pointer_x, &pointer_y);
menu->pointerMotion( pointer_x, pointer_y );
}
}
void WindowManager::takeKeyPress( Window w, unsigned int state,
KeySym keysym )
{
if ( menu ) {
if ( keysym == XK_Escape ) {
delete menu;
menu = 0;
}
}
if ( keysym == XK_Tab && clients.size() ) {
if ( clients.size() == 1 )
(*clients.begin())->redraw( true );
}
}
syntax highlighted by Code2HTML, v. 0.9.1