commit 311db796c68e520e5b6d28829d6aaa4bfbcd1536 Author: Matthias Ettrich Date: Thu Aug 19 23:26:42 1999 +0000 Say hello to kwin. WARNING: NOT USABLE YET. See README. svn path=/trunk/kdebase/kwin/; revision=27871 diff --git a/README b/README new file mode 100644 index 0000000000..71c80ce1ae --- /dev/null +++ b/README @@ -0,0 +1,20 @@ +Fri Aug 20 01:30:50 CEST 1999 + +This is the beginning of kwin, kwm next generation. + +WARNING: this thing is hardly usable now, neither ICCCM nor KDE +compliant yet! + +All it has is a context menu that allows you to switch between two +decoration styles, KDE classic and an experimental style. + +Please don't work on the code, I'll finish it during my summer +vacations (four weeks from now on). + +kwin was only commited to allow people like Mosfet to have a look at +the Client API (and StdClient) to write nifty new themable decorations. + +Have fun, + + Matthias + diff --git a/atoms.cpp b/atoms.cpp new file mode 100644 index 0000000000..6b96134406 --- /dev/null +++ b/atoms.cpp @@ -0,0 +1,15 @@ +#include +#include "atoms.h" + +Atoms::Atoms() +{ + + //TODO use XInternAtoms instead to avoid roundtrips + wm_protocols = XInternAtom(qt_xdisplay(), "WM_PROTOCOLS", FALSE); + wm_delete_window = XInternAtom(qt_xdisplay(), "WM_DELETE_WINDOW", FALSE); + wm_take_focus = XInternAtom(qt_xdisplay(), "WM_TAKE_FOCUS", FALSE); + + // compatibility + kwm_win_icon = XInternAtom(qt_xdisplay(), "KWM_WIN_ICON", FALSE); + +} diff --git a/atoms.h b/atoms.h new file mode 100644 index 0000000000..437dd3cb39 --- /dev/null +++ b/atoms.h @@ -0,0 +1,19 @@ +#ifndef ATOMS_H +#define ATOMS_H +#include + +class Atoms { +public: + Atoms(); + + Atom wm_protocols; + Atom wm_delete_window; + Atom wm_take_focus; + Atom kwm_win_icon; // compatibility + +}; + + +extern Atoms* atoms; + +#endif diff --git a/beclient.cpp b/beclient.cpp new file mode 100644 index 0000000000..331a831025 --- /dev/null +++ b/beclient.cpp @@ -0,0 +1,213 @@ +#include "beclient.h" +#include +#include +#include +#include +#include +#include +#include +#include "workspace.h" + + + +static const char * size_xpm[] = { +/* width height num_colors chars_per_pixel */ +"16 16 3 1", +/* colors */ +" s None c None", +". c #707070", +"X c white", +/* pixels */ +" ", +" ....... ", +" .XXXXXX ", +" .X .X ", +" .X .X....... ", +" .X .XXXXXXXX ", +" .X .X .X ", +" .X....X .X ", +" .XXXXXX .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X........X ", +" .XXXXXXXXXX ", +" ", +" "}; + +static QPixmap* size_pix = 0; +static bool pixmaps_created = FALSE; + +static void create_pixmaps() +{ + if ( pixmaps_created ) + return; + size_pix = new QPixmap( size_xpm ); +} + + + +BeClient::BeClient( Workspace *ws, WId w, QWidget *parent, const char *name ) + : Client( ws, w, parent, name, WResizeNoErase ) +{ + create_pixmaps(); + + QFont f = font(); + f.setBold( TRUE ); + setFont( f ); + + QGridLayout* g = new QGridLayout( this, 0, 0, 2 ); + g->addRowSpacing(1, 2); + g->setRowStretch( 2, 10 ); + g->addWidget( windowWrapper(), 2, 1 ); + g->addColSpacing(0, 2); + g->addColSpacing(2, 2); + g->addRowSpacing(3, 2); + + + + QHBoxLayout* hb = new QHBoxLayout; + g->addLayout( hb, 0, 1 ); + int fh = QMAX( 16, fontMetrics().lineSpacing()); + titlebar = new QSpacerItem(40, fh, QSizePolicy::Preferred, + QSizePolicy::Minimum ); + hb->addItem( titlebar ); + + hb->addStretch(); + +} + + +BeClient::~BeClient() +{ +} + + +void BeClient::resizeEvent( QResizeEvent* e) +{ + Client::resizeEvent( e ); + doShape(); + if ( isVisibleToTLW() ) { + // manual clearing without the titlebar (we selected WResizeNoErase ) + QPainter p( this ); + QRect t = titlebar->geometry(); + t.setTop( 0 ); + t.setLeft( 0 ); + QRegion r = rect(); + r = r.subtract( t ); + p.setClipRegion( r ); + p.eraseRect( rect() ); + } +} + +/*!\reimp + */ +void BeClient::captionChange( const QString& ) +{ + doShape(); + repaint(); +} + +void BeClient::paintEvent( QPaintEvent* ) +{ + QPainter p( this ); + QRect bar ( 0, 0, titlebar->geometry().right()+1, titlebar->geometry().bottom() ); + qDrawWinPanel( &p, 0, bar.bottom()+2, width(), height() - bar.bottom()-2, colorGroup(), FALSE ); + qDrawWinPanel( &p, 2, bar.bottom()+4, width()-4, height() - bar.bottom()-6, colorGroup(), TRUE ); + QRect t = titlebar->geometry(); + + bar.setBottom( bar.bottom() + 3 ); + p.setClipRect( bar ); + bar.setBottom( bar.bottom() + 2 ); + if ( isActive() ) { + QPalette pal( QColor(248,204,0) ); + qDrawWinPanel( &p, bar, pal.normal(), FALSE, &pal.brush(QPalette::Normal, QColorGroup::Background ) ); + } + else + qDrawWinPanel( &p, bar, colorGroup(), FALSE, &colorGroup().brush( QColorGroup::Background ) ); + p.setClipping( FALSE ); + + p.drawPixmap( t.right() - 20, t.center().y()-8, *size_pix ); + p.drawPixmap( t.left() +4, t.center().y()-miniIcon().height()/2, miniIcon() ); + t.setLeft( t.left() + 20 +10); + p.drawText( t, AlignLeft|AlignVCenter, caption() ); +} + + +void BeClient::showEvent( QShowEvent* e) +{ + Client::showEvent( e ); + doShape(); + repaint(); +} + +void BeClient::doShape() +{ + QFontMetrics fm = fontMetrics(); + int cap = 20+20+10+10+fm.boundingRect(caption() ).width(); + titlebar->changeSize( QMIN( width(), cap), QMAX( 16, fm.lineSpacing()), + QSizePolicy::Preferred, QSizePolicy::Minimum ); + layout()->activate(); //#### this is broken!!!!! PAUL!!!!! + +// // // do it manually: #######remove this for Qt-2.01 + titlebar->setGeometry( QRect( titlebar->geometry().x(), titlebar->geometry().y(), + titlebar->sizeHint().width(), titlebar->sizeHint().height() ) ); + QRegion r( rect() ); + r = r.subtract( QRect( QPoint( titlebar->geometry().right()+1, 0), QPoint( width(), titlebar->geometry().bottom()) ) ); + setMask( r ); +} + +/*!\reimp + */ +void BeClient::activeChange( bool /* act */ ) +{ + repaint( 0, 0, width(), titlebar->geometry().bottom()+3, FALSE ); +} + +/*!\reimp + */ +Client::MousePosition BeClient::mousePosition( const QPoint& p ) const +{ + const int range = 16; + const int border = 4; + + int ly = titlebar->geometry().bottom(); + int lx = titlebar->geometry().right(); + if ( p.x() > titlebar->geometry().right() ) { + + if ( p.y() <= ly + range && p.x() >= width()-range) + return TopRight; + else if ( p.y() <= ly + border ) + return Top; + } else if ( p.y() < ly ) { + if ( p.y() > border && p.x() < lx - border ) + return Client::mousePosition( p ); + if ( p.y() < range && p.x() > lx - range ) + return TopRight; + else if ( p.x() > lx-border ) + return Right; + } + + return Client::mousePosition( p ); +} + + +void BeClient::mousePressEvent( QMouseEvent * e ) +{ + + Client::mousePressEvent( e ); +} + +void BeClient::mouseReleaseEvent( QMouseEvent * e ) +{ + workspace()->makeFullScreen( this ); + Client::mouseReleaseEvent( e ); +} + + +void BeClient::mouseDoubleClickEvent( QMouseEvent * e ) +{ + if ( titlebar->geometry().contains( e->pos() ) ) + setShade( !isShade() ); + workspace()->requestFocus( this ); +} diff --git a/beclient.h b/beclient.h new file mode 100644 index 0000000000..7437b0680e --- /dev/null +++ b/beclient.h @@ -0,0 +1,37 @@ +#ifndef BECLIENT_H +#define BECLIENT_H +#include "client.h" +class QToolButton; +class QLabel; +class QSpacerItem; + + +class BeClient : public Client +{ + Q_OBJECT +public: + BeClient( Workspace *ws, WId w, QWidget *parent=0, const char *name=0 ); + ~BeClient(); + +protected: + void resizeEvent( QResizeEvent* ); + void paintEvent( QPaintEvent* ); + void mousePressEvent( QMouseEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + void mouseDoubleClickEvent( QMouseEvent * e ); + + void captionChange( const QString& name ); + + void showEvent( QShowEvent* ); + void activeChange( bool ); + + MousePosition mousePosition( const QPoint& p ) const; + +private: + QSpacerItem* titlebar; + void doShape(); +}; + + + +#endif diff --git a/client.cpp b/client.cpp new file mode 100644 index 0000000000..52a5a67303 --- /dev/null +++ b/client.cpp @@ -0,0 +1,1464 @@ +#include +#include +#include +#include +#include +#include "workspace.h" +#include "client.h" +#include "atoms.h" +#include +#include +#include +#include +#include + +extern Atom qt_wm_state; + + +static void sendClientMessage(Window w, Atom a, long x){ + XEvent ev; + long mask; + + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = w; + ev.xclient.message_type = a; + ev.xclient.format = 32; + ev.xclient.data.l[0] = x; + ev.xclient.data.l[1] = CurrentTime; + mask = 0L; + if (w == qt_xrootwin()) + mask = SubstructureRedirectMask; /* magic! */ + XSendEvent(qt_xdisplay(), w, False, mask, &ev); +} + + +static int _getprop(Window w, Atom a, Atom type, long len, unsigned char **p){ + Atom real_type; + int format; + unsigned long n, extra; + int status; + + status = XGetWindowProperty(qt_xdisplay(), w, a, 0L, len, False, type, &real_type, &format, &n, &extra, p); + if (status != Success || *p == 0) + return -1; + if (n == 0) + XFree((char*) *p); + /* could check real_type, format, extra here... */ + return n; +} +static bool getDoubleProperty(Window w, Atom a, long &result1, long &result2){ + long *p = 0; + + if (_getprop(w, a, a, 2L, (unsigned char**)&p) <= 0){ + return FALSE; + } + + result1 = p[0]; + result2 = p[1]; + XFree((char *) p); + return TRUE; +} +QPixmap KWM::miniIcon(Window w, int width, int height){ + QPixmap result; + Pixmap p = None; + Pixmap p_mask = None; + + long tmp[2] = {None, None}; + if (!getDoubleProperty(w, atoms->kwm_win_icon, tmp[0], tmp[1])){ + XWMHints *hints = XGetWMHints(qt_xdisplay(), w); + if (hints && (hints->flags & IconPixmapHint)){ + p = hints->icon_pixmap; + } + if (hints && (hints->flags & IconMaskHint)){ + p_mask = hints->icon_mask; + } + if (hints) + XFree((char*)hints); + } + else { + p = (Pixmap) tmp[0]; + p_mask = (Pixmap) tmp[1]; + } + + if (p != None){ + Window root; + int x, y; + unsigned int w = 0; + unsigned int h = 0; + unsigned int border_w, depth; + XGetGeometry(qt_xdisplay(), p, + &root, + &x, &y, &w, &h, &border_w, &depth); + if (w > 0 && h > 0){ + QPixmap pm(w, h, depth); + XCopyArea(qt_xdisplay(), p, pm.handle(), + qt_xget_temp_gc(depth==1), + 0, 0, w, h, 0, 0); + if (p_mask != None){ + QBitmap bm(w, h); + XCopyArea(qt_xdisplay(), p_mask, bm.handle(), + qt_xget_temp_gc(TRUE), + 0, 0, w, h, 0, 0); + pm.setMask(bm); + } + if (width > 0 && height > 0 && (w > (unsigned int)width + || h > (unsigned int) height)){ + // scale + QWMatrix m; + m.scale(width/(float)w, height/(float)h); + result = pm.xForm(m); + } + else + result = pm; + } + } + else { + XWMHints *hints = XGetWMHints(qt_xdisplay(), w); + if (hints && + (hints->flags & WindowGroupHint) + && hints->window_group != None + && hints->window_group != w){ + Window wg = hints->window_group; + XFree((char*)hints); + return miniIcon(wg, width, height); + } + if (hints) + XFree((char*)hints); + Window trans = None; + if (XGetTransientForHint(qt_xdisplay(), w, &trans)){ + if (trans != w) + return miniIcon(trans, width, height); + } + } + return result; +} + +QPixmap KWM::icon(Window w, int width, int height){ + QPixmap result; + Pixmap p = None; + Pixmap p_mask = None; + + XWMHints *hints = XGetWMHints(qt_xdisplay(), w); + if (hints && (hints->flags & IconPixmapHint)){ + p = hints->icon_pixmap; + } + if (hints && (hints->flags & IconMaskHint)){ + p_mask = hints->icon_mask; + } + if (hints) + XFree((char*)hints); + + if (p != None){ + Window root; + int x, y; + unsigned int w = 0; + unsigned int h = 0; + unsigned int border_w, depth; + XGetGeometry(qt_xdisplay(), p, + &root, + &x, &y, &w, &h, &border_w, &depth); + if (w > 0 && h > 0){ + QPixmap pm(w, h, depth); + XCopyArea(qt_xdisplay(), p, pm.handle(), + qt_xget_temp_gc(depth==1), + 0, 0, w, h, 0, 0); + if (p_mask != None){ + QBitmap bm(w, h); + XCopyArea(qt_xdisplay(), p_mask, bm.handle(), + qt_xget_temp_gc(TRUE), + 0, 0, w, h, 0, 0); + pm.setMask(bm); + } + if (width > 0 && height > 0 && (w > (unsigned int)width + || h > (unsigned int) height)){ + // scale + QWMatrix m; + m.scale(width/(float)w, height/(float)h); + result = pm.xForm(m); + } + else + result = pm; + } + } + else { + XWMHints *hints = XGetWMHints(qt_xdisplay(), w); + if (hints && + (hints->flags & WindowGroupHint) + && hints->window_group != None + && hints->window_group != w){ + XFree((char*)hints); + return icon(hints->window_group, width, height); + } + if (hints) + XFree((char*)hints); + Window trans = None; + if (XGetTransientForHint(qt_xdisplay(), w, &trans)){ + if (trans != w) + return icon(trans, width, height); + } + } + return result; +} + +/*! + \class WindowWrapper client.h + + \brief The WindowWrapper class encapsulates a client's managed + window. + + There's not much to know about this class, it's completley handled by + the abstract class Client. You get access to the window wrapper with + Client:.windowWrapper(). The big advantage of WindowWrapper is, + that you can use just like a normal QWidget, although it encapsulates + an X window that belongs to another application. + + In particular, this means adding a client's windowWrapper() to a + QLayout for the geometry management. See StdClient for an example + on how to do this. + + */ + + +WindowWrapper::WindowWrapper( WId w, Client *parent, const char* name) + : QWidget( parent, name ) +{ + win = w; + setMouseTracking( TRUE ); + + // we don't want the window to be destroyed when we are destroyed + XAddToSaveSet(qt_xdisplay(), win ); + + // overwrite Qt-defaults because we need SubstructureNotifyMask + XSelectInput( qt_xdisplay(), winId(), + KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + KeymapStateMask | + ButtonMotionMask | + PointerMotionMask | // need this, too! + EnterWindowMask | LeaveWindowMask | + FocusChangeMask | + ExposureMask | + StructureNotifyMask | + SubstructureRedirectMask | + SubstructureNotifyMask + ); + + XSelectInput( qt_xdisplay(), w, + FocusChangeMask | + PropertyChangeMask + ); + + // set the border width to 0 + XWindowChanges wc; + wc.border_width = 0; + XConfigureWindow( qt_xdisplay(), win, CWBorderWidth, &wc ); + + // finally, get the window + XReparentWindow( qt_xdisplay(), win, winId(), 0, 0 ); + + // install a passive grab to catch mouse button events + XGrabButton(qt_xdisplay(), AnyButton, AnyModifier, winId(), FALSE, + ButtonPressMask, GrabModeSync, GrabModeSync, + None, None ); + +} + +WindowWrapper::~WindowWrapper() +{ + releaseWindow(); +} + +QSize WindowWrapper::sizeHint() const +{ + return size(); +} + +QSizePolicy WindowWrapper::sizePolicy() const +{ + return QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); +} + + +void WindowWrapper::resizeEvent( QResizeEvent * ) +{ + if ( win ) + XMoveResizeWindow( qt_xdisplay(), win, + 0, 0, width(), height() ); +} + +void WindowWrapper::showEvent( QShowEvent* ) +{ + if ( win ) { + XMoveResizeWindow( qt_xdisplay(), win, + 0, 0, width(), height() ); + XMapRaised( qt_xdisplay(), win ); + } +} +void WindowWrapper::hideEvent( QHideEvent* ) +{ + if ( win ) + XUnmapWindow( qt_xdisplay(), win ); +} + +void WindowWrapper::invalidateWindow() +{ + win = 0; +} + +/*! + Releases the window. The client has done its job and the window is still existing. + */ +void WindowWrapper::releaseWindow() +{ + if ( win ) { + XReparentWindow( qt_xdisplay(), win, + ((Client*)parentWidget())->workspace()->rootWin(), + parentWidget()->x(), + parentWidget()->y() ); + XRemoveFromSaveSet(qt_xdisplay(), win ); + invalidateWindow(); + } +} + + + +void WindowWrapper::mousePressEvent( QMouseEvent* ) +{ + XAllowEvents( qt_xdisplay(), ReplayPointer, lastMouseEventTime ); +} + +void WindowWrapper::mouseReleaseEvent( QMouseEvent* ) +{ + XAllowEvents( qt_xdisplay(), ReplayPointer, lastMouseEventTime ); +} + +void WindowWrapper::mouseMoveEvent( QMouseEvent* ) +{ + //#### we sometimes get these but shouldn't..... Maybe a Qt problem + //XAllowEvents( qt_xdisplay(), ReplayPointer, lastMouseEventTime ); +} + + +bool WindowWrapper::x11Event( XEvent * e) +{ + switch ( e->type ) { + case ButtonPress: + case ButtonRelease: + lastMouseEventTime = e->xbutton.time; + if ( e->xbutton.button > 3 ) { + XAllowEvents( qt_xdisplay(), ReplayPointer, lastMouseEventTime ); + return TRUE; + } + break; + case MotionNotify: + lastMouseEventTime = e->xmotion.time; + break; + default: + break; + } + return FALSE; +} + + +/*! + \class Client client.h + + \brief The Client class encapsulates a window decoration frame. + + TODO + +*/ + +/*! + Creates a client on workspace \a ws for window \a w. + */ +Client::Client( Workspace *ws, WId w, QWidget *parent, const char *name, WFlags f ) + : QWidget( parent, name, f | WStyle_Customize | WStyle_NoBorder ) +{ + + wspace = ws; + win = w; + XWindowAttributes attr; + if (XGetWindowAttributes(qt_xdisplay(), win, &attr)){ + original_geometry.setRect(attr.x, attr.y, attr.width, attr.height ); + } + wwrap = new WindowWrapper( w, this ); + wwrap->installEventFilter( this ); + + // set the initial mapping state + setMappingState( WithdrawnState ); + desk = -1; // and no desktop yet + + mode = Nowhere; + buttonDown = FALSE; + setMouseTracking( TRUE ); + + active = FALSE; + shaded = FALSE; + transient_for = None; + is_sticky = FALSE; + + ignore_unmap = 0; + + getIcons(); + getWindowProtocols(); + getWmNormalHints(); // get xSizeHint + fetchName(); + + if ( !XGetTransientForHint( qt_xdisplay(), (Window) win, (Window*) &transient_for ) ) + transient_for = None; +} + +/*! + Destructor, nothing special. + */ +Client::~Client() +{ + releaseWindow(); +} + + +/*! + Manages the clients. This means handling the very first maprequest: + reparenting, initial geometry, initial state, placement, etc. + */ +void Client::manage( bool isMapped ) +{ + + layout()->setResizeMode( QLayout::Minimum ); + QRect geom( original_geometry ); + bool placementDone = FALSE; + + if ( isMapped ) + placementDone = TRUE; + else { + if ( xSizeHint.flags & PPosition || xSizeHint.flags & USPosition ) { + // support for obsolete hints + if ( xSizeHint.x != 0 && geom.x() == 0 ) + geom.setRect( xSizeHint.x, geom.y(), geom.width(), geom.height() ); + if ( xSizeHint.y != 0 && geom.y() == 0 ) + geom.setRect( geom.x(), xSizeHint.y, geom.width(), geom.height() ); + placementDone = TRUE; + } + if ( xSizeHint.flags & USSize || xSizeHint.flags & PSize ) { + if ( xSizeHint.width != 0 ) + geom.setWidth( xSizeHint.width ); + if ( xSizeHint.height != 0 ) + geom.setHeight( xSizeHint.height ); + } + } + + // the clever activate() trick is necessary + layout()->activate(); + resize ( sizeForWindowSize( geom.size() ) ); + layout()->activate(); + + move( geom.x(), geom.y() ); + gravitate( FALSE ); + + if ( !placementDone ) { + workspace()->doPlacement( this ); + placementDone = TRUE; + } + + // ### TODO check XGetWMHints() for initial mapping state, icon, etc. pp. + // assume window wants to be visible on the current desktop + desk = workspace()->currentDesktop(); + setMappingState( NormalState ); + desk = workspace()->currentDesktop(); + show(); + + if ( options->focusPolicyIsReasonable() ) + workspace()->requestFocus( this ); + + // ignore unmap notify send by the xserver cause the window was already mapped + if ( isMapped ) + ignore_unmap++; + +} + + +/*! + Gets the client's normal WM hints and reconfigures itself respectively. + */ +void Client::getWmNormalHints() +{ + // TODO keep in mind changing of fix size! if !isWithdrawn()! + long msize; + if (XGetWMNormalHints(qt_xdisplay(), win, &xSizeHint, &msize) == 0 + || xSizeHint.flags == 0) + xSizeHint.flags = PSize; /* not specified */ +} + +/*! + Fetches the window's caption (WM_NAME property). It will be + stored in the client's caption(). + */ +void Client::fetchName() +{ + char* name = 0; + if ( XFetchName( qt_xdisplay(), win, &name ) && name ) { + QString s = QString::fromLatin1( name ); + if ( s != caption() ) { + setCaption( QString::fromLatin1( name ) ); + if ( !isWithdrawn() ) + captionChange( caption() ); + } + XFree( name ); + } +} + +/*! + Sets the client window's mapping state. Possible values are + WithdrawnState, IconicState, NormalState. + */ +void Client::setMappingState(int s){ + if ( !win) + return; + unsigned long data[2]; + data[0] = (unsigned long) s; + data[1] = (unsigned long) None; + + state = s; + XChangeProperty(qt_xdisplay(), win, qt_wm_state, qt_wm_state, 32, + PropModeReplace, (unsigned char *)data, 2); +} + + +/*! + General handler for XEvents concerning the client window + */ +bool Client::windowEvent( XEvent * e) +{ + switch (e->type) { + case UnmapNotify: + return unmapNotify( e->xunmap ); + case MapRequest: + return mapRequest( e->xmaprequest ); + case ConfigureRequest: + return configureRequest( e->xconfigurerequest ); + case PropertyNotify: + return propertyNotify( e->xproperty ); + case ButtonPress: + case ButtonRelease: + XAllowEvents( qt_xdisplay(), e->xbutton.time, ReplayPointer ); + break; + case FocusIn: + if ( e->xfocus.mode == NotifyUngrab ) + break; // we don't care + if ( e->xfocus.detail == NotifyPointer ) + break; // we don't care + setActive( TRUE ); + break; + case FocusOut: + if ( e->xfocus.mode == NotifyGrab ) + break; // we don't care + if ( isShade() ) + break; // we neither + if ( e->xfocus.detail != NotifyNonlinear ) + return TRUE; // hack for motif apps like netscape + setActive( FALSE ); + break; + default: + break; + } + + return TRUE; // we accept everything :-) +} + + +/*! + Handles map requests of the client window + */ +bool Client::mapRequest( XMapRequestEvent& /* e */ ) +{ + switch ( mappingState() ) { + case WithdrawnState: + manage(); + break; + case IconicState: + show(); + break; + case NormalState: + show(); // for safety + break; + } + + return TRUE; +} + + +/*! + Handles unmap notify events of the client window + */ +bool Client::unmapNotify( XUnmapEvent& e ) +{ + + if ( ignore_unmap ) { + ignore_unmap--; + return TRUE; + } + + + switch ( mappingState() ) { + case IconicState: + // only react on sent events, all others are produced by us + if ( e.send_event ) + withdraw(); + break; + case NormalState: + if ( !windowWrapper()->isVisible() && !e.send_event ) + return TRUE; // this event was produced by us as well + + // maybe we will be destroyed soon. Check this first. + XEvent ev; + QApplication::syncX(); + if ( XCheckTypedWindowEvent (qt_xdisplay(), windowWrapper()->winId(), + DestroyNotify, &ev) ){ + workspace()->destroyClient( this ); + return TRUE; + } + // fall through + case WithdrawnState: // however that has been possible.... + withdraw(); + break; + } + return TRUE; +} + +/*! + Withdraws the window and destroys the client afterwards + */ +void Client::withdraw() +{ + setMappingState( WithdrawnState ); + releaseWindow(); + workspace()->destroyClient( this ); +} + +/*! + Handles configure requests of the client window + */ +bool Client::configureRequest( XConfigureRequestEvent& e ) +{ + if ( isShade() ) + setShade( FALSE ); + + if ( e.value_mask & CWBorderWidth ) { + // first, get rid of a window border + XWindowChanges wc; + unsigned int value_mask = 0; + + wc.border_width = 0; + value_mask = CWBorderWidth; + XConfigureWindow( qt_xdisplay(), win, value_mask, & wc ); + } + + if ( e.value_mask & (CWX | CWY ) ) { + int nx = x(); + int ny = y(); + if ( e.value_mask & CWX ) + nx = e.x; + if ( e.value_mask & CWY ) + ny = e.y; + move( nx, ny ); + } + + if ( e.value_mask & (CWWidth | CWHeight ) ) { + int nw = windowWrapper()->width(); + int nh = windowWrapper()->height(); + if ( e.value_mask & CWWidth ) + nw = e.width; + if ( e.value_mask & CWHeight ) + nh = e.height; + resize( sizeForWindowSize( QSize( nw, nh ) ) ); + } + + // TODO handle stacking! + + sendSynteticConfigureNotify(); + return TRUE; +} + + +/*! + Handles property changes of the client window + */ +bool Client::propertyNotify( XPropertyEvent& e ) +{ + switch ( e.atom ) { + case XA_WM_NORMAL_HINTS: + getWmNormalHints(); + break; + case XA_WM_NAME: + fetchName(); + break; + case XA_WM_TRANSIENT_FOR: + if ( !XGetTransientForHint( qt_xdisplay(), (Window) win, (Window*) &transient_for ) ) + transient_for = None; + break; + case XA_WM_HINTS: + getIcons(); + break; + default: + if ( e.atom == atoms->wm_protocols ) + getWindowProtocols(); + else if ( e.atom == atoms->kwm_win_icon ) { + getIcons(); + } + + break; + } + return TRUE; +} + + +/*! + Auxiliary function to inform the client about the current window + configuration. + + */ +void Client::sendSynteticConfigureNotify() +{ + XConfigureEvent c; + c.type = ConfigureNotify; + c.event = win; + c.window = win; + c.x = x() + windowWrapper()->x(); + c.y = y() + windowWrapper()->y(); + c.width = windowWrapper()->width(); + c.height = windowWrapper()->height(); + c.border_width = 0; + XSendEvent( qt_xdisplay(), c.event, TRUE, NoEventMask, (XEvent*)&c ); +} + + + +/*! + Adjust the frame size \a frame according to he window's size hints. + */ +QSize Client::adjustedSize( const QSize& frame) const +{ + // first, get the window size for the given frame size s + + QSize wsize( frame.width() - ( width() - wwrap->width() ), + frame.height() - ( height() - wwrap->height() ) ); + + return sizeForWindowSize( wsize ); +} + +/*! + Calculate the appropriate frame size for the given window size \a + wsize. + + \a wsize is adapted according to the window's size hints (minimum, + maximum and incremental size changes). + + */ +QSize Client::sizeForWindowSize( const QSize& wsize, bool ignore_height) const +{ + int w = wsize.width(); + int h = wsize.height(); + if (w<1) w = 1; + if (h<1) h = 1; + + int bw = 0; + int bh = 0; + + if (xSizeHint.flags & PBaseSize) { + bw = xSizeHint.base_width; + bh = xSizeHint.base_height; + if (w < xSizeHint.base_width) + w = xSizeHint.base_width; + if (h < xSizeHint.base_height) + h = xSizeHint.base_height; + } + + if (xSizeHint.flags & PResizeInc) { + if ( xSizeHint.width_inc > 0 ) { + int sx = (w - bw) / xSizeHint.width_inc; + w = bw + sx * xSizeHint.width_inc; + } + if ( xSizeHint.height_inc > 0 ) { + int sy = (h - bh) / xSizeHint.height_inc; + h = bh + sy * xSizeHint.height_inc; + } + } + + int ww = wwrap->width(); + int wh = 0; + if ( !wwrap->testWState( WState_ForceHide ) ) + wh = wwrap->height(); + + return QSize( QMIN( QMAX( width() - ww + w, minimumWidth() ), + maximumWidth() ), + ignore_height? height()-wh : QMIN( QMAX( height() - wh + h, minimumHeight() ), + maximumHeight() ) ); +} + + +/*! + Reimplemented to provide move/resize + */ +void Client::mousePressEvent( QMouseEvent * e) +{ + if ( e->button() == LeftButton ) { + if ( options->focusPolicyIsReasonable() ) + workspace()->requestFocus( this ); + workspace()->raiseClient( this ); + mouseMoveEvent( e ); + buttonDown = TRUE; + moveOffset = e->pos(); + invertedMoveOffset = rect().bottomRight() - e->pos(); + } + else if ( e->button() == RightButton ) { + workspace()->showPopup( e->globalPos(), this ); + } +} + +/*! + Reimplemented to provide move/resize + */ +void Client::mouseReleaseEvent( QMouseEvent * e) +{ + if ( e->button() == LeftButton ) { + buttonDown = FALSE; + } +} + +/*! + Reimplemented to provide move/resize + */ +void Client::mouseMoveEvent( QMouseEvent * e) +{ + if ( !buttonDown ) { + mode = mousePosition( e->pos() ); + setMouseCursor( mode ); + return; + } + + if ( mode != Center && shaded ) { + wwrap->show(); + workspace()->requestFocus( this ); + shaded = FALSE; + } + + QPoint globalPos = e->pos() + geometry().topLeft(); + + // TODO for MDI this has to be based on the parent window! +// QPoint p = parentWidget()->mapFromGlobal( e->globalPos() ); + +// if ( !parentWidget()->rect().contains(p) ) { +// if ( p.x() < 0 ) +// p.rx() = 0; +// if ( p.y() < 0 ) +// p.ry() = 0; +// if ( p.x() > parentWidget()->width() ) +// p.rx() = parentWidget()->width(); +// if ( p.y() > parentWidget()->height() ) +// p.ry() = parentWidget()->height(); +// } + +// if ( testWState(WState_ConfigPending) ) +// return; + + QPoint p = globalPos + invertedMoveOffset; + + QPoint pp = globalPos - moveOffset; + + QSize mpsize( geometry().right() - pp.x() + 1, geometry().bottom() - pp.y() + 1 ); + mpsize = adjustedSize( mpsize ); + QPoint mp( geometry().right() - mpsize.width() + 1, + geometry().bottom() - mpsize.height() + 1 ); + + QRect geom = geometry(); + switch ( mode ) { + case TopLeft: + geom = QRect( mp, geometry().bottomRight() ) ; + break; + case BottomRight: + geom = QRect( geometry().topLeft(), p ) ; + break; + case BottomLeft: + geom = QRect( QPoint(mp.x(), geometry().y() ), QPoint( geometry().right(), p.y()) ) ; + break; + case TopRight: + geom = QRect( QPoint(geometry().x(), mp.y() ), QPoint( p.x(), geometry().bottom()) ) ; + break; + case Top: + geom = QRect( QPoint( geometry().left(), mp.y() ), geometry().bottomRight() ) ; + break; + case Bottom: + geom = QRect( geometry().topLeft(), QPoint( geometry().right(), p.y() ) ) ; + break; + case Left: + geom = QRect( QPoint( mp.x(), geometry().top() ), geometry().bottomRight() ) ; + break; + case Right: + geom = QRect( geometry().topLeft(), QPoint( p.x(), geometry().bottom() ) ) ; + break; + case Center: + geom.moveTopLeft( pp ); + break; + default: + break; + } + + if ( geom.size() != size() ) { + geom.setSize( adjustedSize( geom.size() ) ); + setGeometry( geom ); + } + else if ( geom.topLeft() != geometry().topLeft() ) + move( geom.topLeft() ); + +} + +/*! + Reimplemented to provide move/resize + */ +void Client::enterEvent( QEvent * ) +{ +} + +/*! + Reimplemented to provide move/resize + */ +void Client::leaveEvent( QEvent * ) +{ + if ( !buttonDown ) + setCursor( arrowCursor ); +} + + +/*! + Reimplemented to inform the client about the new window position. + */ +void Client::moveEvent( QMoveEvent * ) +{ + sendSynteticConfigureNotify(); +} + + +/*!\reimp + */ +void Client::showEvent( QShowEvent* ) +{ + setMappingState( NormalState ); + windowWrapper()->show();// ########## hack for qt < 2.1 + } + +/*! + Reimplemented to hide the window wrapper as well. Also informs the + workspace. + */ +void Client::hideEvent( QHideEvent* ) +{ + windowWrapper()->hide();// ########## hack for qt < 2.1 + workspace()->clientHidden( this ); +} + + + +/*! + Late initialialize the client after the window has been managed. + + Ensure to call the superclasses init() implementation when subclassing. + */ +void Client::init() +{ +} + +/*!\fn captionChange( const QString& name ) + + Indicates that the caption (the window title) changed to \a + name. Subclasses shall then repaint the title string in a clever, + fast mannor. The default implementation calls repaint( FALSE ); + */ +void Client::captionChange( const QString& ) +{ + repaint( FALSE ); +} + + +/*!\fn activeChange( bool act ) + + Indicates that the activation state changed to \a act. Subclasses + may want to indicate the new state graphically in a clever, fast + mannor. The default implementation calls repaint( FALSE ); + */ +void Client::activeChange( bool ) +{ + repaint( FALSE ); +} + +/*! + Indicates that the application's icon changed to \a act. Subclasses + may want to indicate the new state graphically in a clever, fast + mannor. The default implementation calls repaint( FALSE ); + */ +void Client::iconChange() +{ + repaint( FALSE ); +} + + +/*!\fn maximizeChange( bool max ) + + Indicates that the window was maximized or demaximized. \a max is + set respectively. Subclasses may want to indicate the new state + graphically, for example with a different icon. + */ +void Client::maximizeChange( bool ) +{ +} + +/*!\fn stickyChange( bool sticky ) + + Indicates that the window was made sticky or unsticky. \a sticky is + set respectively. Subclasses may want to indicate the new state + graphically, for example with a different icon. + */ +void Client::stickyChange( bool ) +{ +} + + + +/*! + Paints a client window. + + The default implementation does nothing. To be implemented by subclasses. + */ +void Client::paintEvent( QPaintEvent * ) +{ +} + + + +/*! + Releases the window. The client has done its job and the window is still existing. + */ +void Client::releaseWindow() +{ + if ( win ) { + gravitate( TRUE ); + windowWrapper()->releaseWindow(); + win = 0; + } +} + +/*! + Invalidates the window to avoid the client accessing it again. + + This function is called by the workspace when the window has been + destroyed. + */ +void Client::invalidateWindow() +{ + win = 0; + windowWrapper()->invalidateWindow(); +} + +/*! + Returns the minimum size. This function differs from QWidget::minimumSize() + and is to be preferred + */ +QSize Client::minimumSize() const +{ + return QSize( minimumWidth(), minimumHeight() ); +} +/*! + Returns the minimum width. This function differs from QWidget::minimumWidth() + and is to be preferred + */ +int Client::minimumWidth() const +{ + if (xSizeHint.flags & PMinSize) + return QMAX( width() - wwrap->width() + xSizeHint.min_width, + QWidget::minimumWidth() ); + else + return QWidget::minimumWidth(); +} +/*! + Returns the minimum height. This function differs from QWidget::minimumHeight() + and is to be preferred + */ +int Client::minimumHeight() const +{ + if (xSizeHint.flags & PMinSize) + return QMAX( height() - wwrap->height() + xSizeHint.min_height, + QWidget::minimumHeight() ); + else + return QWidget::minimumHeight(); +} + +/*! + Returns the maximum size. This function differs from QWidget::maximumSize() + and is to be preferred + */ +QSize Client::maximumSize() const +{ + return QSize( maximumWidth(), maximumHeight() ); +} +/*! + Returns the maximum width. This function differs from QWidget::maximumWidth() + and is to be preferred + */ +int Client::maximumWidth() const +{ + if (xSizeHint.flags & PMaxSize) + return QMIN( width() - wwrap->width() + xSizeHint.max_width, + QWidget::maximumWidth() ); + else + return QWidget::maximumWidth(); +} +/*! + Returns the maximum height. This function differs from QWidget::maximumHeight() + and is to be preferred + */ +int Client::maximumHeight() const +{ + if (xSizeHint.flags & PMaxSize) + return QMIN( height() - wwrap->height() + xSizeHint.max_height, + QWidget::maximumHeight() ); + else + return QWidget::maximumHeight(); +} + + +void Client::iconify() +{ + setMappingState( IconicState ); + hide(); + // TODO animation (virtual function) +} + +void Client::closeWindow() +{ + if ( Pdeletewindow ){ + sendClientMessage( win, atoms->wm_protocols, atoms->wm_delete_window); + } + else { + // client will not react on wm_delete_window. We have not choice + // but destroy his connection to the XServer. + XKillClient(qt_xdisplay(), win ); + workspace()->destroyClient( this ); + } +} + +void Client::maximize( MaximizeMode /*m*/) +{ + if ( geom_restore.isNull() ) { + geom_restore = geometry(); + setGeometry( workspace()->geometry() ); + maximizeChange( TRUE ); + } + else { + setGeometry( geom_restore ); + QRect invalid; + geom_restore = invalid; + maximizeChange( FALSE ); + } +} + + +void Client::toggleSticky() +{ + setSticky( !isSticky() ); +} + +void Client::maximize() +{ + maximize( MaximizeFull ); +} + + +void Client::fullScreen() +{ + workspace()->makeFullScreen( this ); +} + + + +/*! + Catch events of the WindowWrapper + */ +bool Client::eventFilter( QObject *o, QEvent * e) +{ + if ( o != wwrap ) + return FALSE; + switch ( e->type() ) { + case QEvent::MouseButtonPress: + if ( options->focusPolicyIsReasonable() ) + workspace()->requestFocus( this ); + workspace()->raiseClient( this ); + break; + case QEvent::MouseButtonRelease: + break; + default: + break; + } + + return FALSE; +} + +void Client::gravitate( bool invert ){ + int gravity, dx, dy; + dx = dy = 0; + + gravity = NorthWestGravity; + if ( xSizeHint.flags & PWinGravity) + gravity = xSizeHint.win_gravity; + + switch (gravity) { + case NorthWestGravity: + dx = 0; + dy = 0; + break; + case NorthGravity: + dx = -windowWrapper()->x(); + dy = 0; + break; + case NorthEastGravity: + dx = -( width() - windowWrapper()->width() ); + dy = 0; + break; + case WestGravity: + dx = 0; + dy = -windowWrapper()->y(); + break; + case CenterGravity: + case StaticGravity: + dx = -windowWrapper()->x(); + dy = -windowWrapper()->y(); + break; + case EastGravity: + dx = -( width() - windowWrapper()->width() ); + dy = -windowWrapper()->y(); + break; + case SouthWestGravity: + dx = 0; + dy = -( height() - windowWrapper()->height() ); + break; + case SouthGravity: + dx = -windowWrapper()->x(); + dy = -( height() - windowWrapper()->height() ); + break; + case SouthEastGravity: + dx = -( width() - windowWrapper()->width() - 1 ); + dy = -( height() - windowWrapper()->height() - 1 ); + break; + } + if (invert) + move( x() - dx, y() - dy ); + else + move( x() + dx, y() + dy ); +} + +/*! + Reimplement to handle crossing events (qt should provide xroot, yroot) +*/ +bool Client::x11Event( XEvent * e) +{ + if ( e->type == EnterNotify ) { + if ( options->focusPolicy != Options::ClickToFocus ) + workspace()->requestFocus( this ); + return TRUE; + } + if ( e->type == LeaveNotify ) { + if ( !buttonDown ) + setCursor( arrowCursor ); + if ( options->focusPolicy == Options::FocusStricklyUnderMouse ) { + if ( isActive() && !rect().contains( QPoint( e->xcrossing.x, e->xcrossing.y ) ) ) + workspace()->requestFocus( 0 ) ; + } + return TRUE; + } + return FALSE; +} + + +/*! + Returns a logical mouse position for the cursor position \a + p. Possible positions are: Nowhere, TopLeft , BottomRight, + BottomLeft, TopRight, Top, Bottom, Left, Right, Center + */ +Client::MousePosition Client::mousePosition( const QPoint& p ) const +{ + const int range = 16; + const int border = 4; + + MousePosition m = Nowhere; + + + if ( ( p.x() > border && p.x() < width() - border ) + && ( p.y() > border && p.y() < height() - border ) ) + return Center; + + if ( p.y() <= range && p.x() <= range) + m = TopLeft; + else if ( p.y() >= height()-range && p.x() >= width()-range) + m = BottomRight; + else if ( p.y() >= height()-range && p.x() <= range) + m = BottomLeft; + else if ( p.y() <= range && p.x() >= width()-range) + m = TopRight; + else if ( p.y() <= border ) + m = Top; + else if ( p.y() >= height()-border ) + m = Bottom; + else if ( p.x() <= border ) + m = Left; + else if ( p.x() >= width()-border ) + m = Right; + else + m = Center; + return m; +} + +/*! + Sets an appropriate cursor shape for the logical mouse position \a m + + \sa QWidget::setCursor() + */ +void Client::setMouseCursor( MousePosition m ) +{ + switch ( m ) { + case TopLeft: + case BottomRight: + setCursor( sizeFDiagCursor ); + break; + case BottomLeft: + case TopRight: + setCursor( sizeBDiagCursor ); + break; + case Top: + case Bottom: + setCursor( sizeVerCursor ); + break; + case Left: + case Right: + setCursor( sizeHorCursor ); + break; + default: + setCursor( arrowCursor ); + break; + } +} + + +bool Client::isShade() const +{ + return shaded; +} + +void Client::setShade( bool s ) +{ + if ( shaded == s ) + return; + + shaded = s; + + if (shaded ) { + QSize s( sizeForWindowSize( QSize( windowWrapper()->width(), 0), TRUE ) ); + windowWrapper()->hide(); + resize (s ); + } else { + QSize s( sizeForWindowSize( windowWrapper()->size() ) ); + windowWrapper()->show(); + layout()->activate(); + resize ( s ); + if ( isActive() ) + workspace()->requestFocus( this ); + } +} + + +/*! + Sets the client's active state to \a act. + + This function does only change the visual appearance of the client, + it does not change the focus setting. Use + Workspace::activateClient() or Workspace::requestFocus() instead. + + If a client receives or looses the focus, it calls setActive() on + its own. + + */ +void Client::setActive( bool act) +{ + if ( active == act ) + return; + + active = act; + if ( active ) + workspace()->setActiveClient( this ); + activeChange( active ); +} + + +/*! + Sets the window's sticky property to b + */ +void Client::setSticky( bool b ) +{ + if ( is_sticky == b ) + return; + is_sticky = b; + if ( !is_sticky ) + desk = workspace()->currentDesktop(); + stickyChange( is_sticky ); +} + + +void Client::getIcons() +{ + icon_pix = KWM::icon( win, 32, 32 ); // TODO sizes from workspace + miniicon_pix = KWM::miniIcon( win, 16, 16 ); + if ( !isWithdrawn() ) + iconChange(); +} + +void Client::getWindowProtocols(){ + Atom *p; + int i,n; + + Pdeletewindow = 0; + Ptakefocus = 0; + + if (XGetWMProtocols(qt_xdisplay(), win, &p, &n)){ + for (i = 0; i < n; i++) + if (p[i] == atoms->wm_delete_window) + Pdeletewindow = 1; + else if (p[i] == atoms->wm_take_focus) + Ptakefocus = 1; + if (n>0) + XFree(p); + } +} + +/*! + Puts the focus on this window. Clients should never calls this + themselves, instead they should use Workspace::requestFocus(). + */ +void Client::takeFocus() +{ + if ( !Ptakefocus ) + XSetInputFocus( qt_xdisplay(), win, RevertToPointerRoot, CurrentTime ); + else + sendClientMessage(win, atoms->wm_protocols, atoms->wm_take_focus); +} + + +NoBorderClient::NoBorderClient( Workspace *ws, WId w, QWidget *parent=0, const char *name=0 ) + : Client( ws, w, parent, name ) +{ + QGridLayout* g = new QGridLayout( this, 0, 0, 0 ); + g->addWidget( windowWrapper(), 0, 0 ); +} + +NoBorderClient::~NoBorderClient() +{ +} + diff --git a/client.h b/client.h new file mode 100644 index 0000000000..1571cc0789 --- /dev/null +++ b/client.h @@ -0,0 +1,285 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include "options.h" +#include +#include +#include +#include +#include +#include + +class Workspace; +class Client; + +class KWM +{ +public: + static QPixmap miniIcon(Window w, int width=0, int height=0); + static QPixmap icon(Window w, int width=0, int height=0); +}; + +class WindowWrapper : public QWidget +{ + Q_OBJECT +public: + WindowWrapper( WId w, Client *parent=0, const char* name=0); + ~WindowWrapper(); + + inline WId window() const; + void releaseWindow(); + void invalidateWindow(); + QSize sizeHint() const; + QSizePolicy sizePolicy() const; + +protected: + void resizeEvent( QResizeEvent * ); + void showEvent( QShowEvent* ); + void hideEvent( QHideEvent* ); + void mousePressEvent( QMouseEvent* ); + void mouseReleaseEvent( QMouseEvent* ); + void mouseMoveEvent( QMouseEvent* ); + bool x11Event( XEvent * ); // X11 event + +private: + WId win; + Time lastMouseEventTime; +}; + +inline WId WindowWrapper::window() const +{ + return win; +} + + + +class Client : public QWidget +{ + Q_OBJECT +public: + Client( Workspace *ws, WId w, QWidget *parent=0, const char *name=0, WFlags f = 0); + ~Client(); + + inline WId window() const; + inline WindowWrapper* windowWrapper() const; + inline Workspace* workspace() const; + void releaseWindow(); + void invalidateWindow(); + inline WId transientFor() const; + + virtual bool windowEvent( XEvent * ); + + void manage( bool isMapped = FALSE ); + + void setMappingState( int s ); + int mappingState() const; + + void requestActivation(); + void withdraw(); + + QSize adjustedSize( const QSize& ) const; + QSize minimumSize() const; + int minimumWidth() const; + int minimumHeight() const; + QSize maximumSize() const; + int maximumWidth() const; + int maximumHeight() const; + + inline QPixmap icon() const; + inline QPixmap miniIcon() const; + + + // is the window in withdrawn state? + bool isWithdrawn(){ + return state == WithdrawnState; + } + // is the window in iconic state? + bool isIconified(){ + return state == IconicState; + } + // is the window in normal state? + bool isNormal(){ + return state == NormalState; + } + + inline bool isActive() const; + void setActive( bool ); + + int desktop() const; + bool isOnDesktop( int d ) const; + + bool isShade() const; + virtual void setShade( bool ); + + inline bool isMaximized() const; + enum MaximizeMode { MaximizeVertical, MaximizeHorizontal, MaximizeFull }; + + inline bool isSticky() const; + void setSticky( bool ); + + void takeFocus(); + +public slots: + void iconify(); + void closeWindow(); + void maximize( MaximizeMode ); + void maximize(); + void fullScreen(); + void toggleSticky(); + +protected: + void paintEvent( QPaintEvent * ); + void mousePressEvent( QMouseEvent * ); + void mouseReleaseEvent( QMouseEvent * ); + void mouseMoveEvent( QMouseEvent * ); + void enterEvent( QEvent * ); + void leaveEvent( QEvent * ); + void moveEvent( QMoveEvent * ); + void showEvent( QShowEvent* ); + void hideEvent( QHideEvent* ); + bool x11Event( XEvent * ); // X11 event + + bool eventFilter( QObject *, QEvent * ); + + + virtual void init(); + virtual void captionChange( const QString& name ); + virtual void iconChange(); + virtual void activeChange( bool ); + virtual void maximizeChange( bool ); + virtual void stickyChange( bool ); + + + enum MousePosition { + Nowhere, TopLeft , BottomRight, BottomLeft, TopRight, Top, Bottom, Left, Right, Center + }; + + virtual MousePosition mousePosition( const QPoint& ) const; + virtual void setMouseCursor( MousePosition m ); + + // handlers for X11 events + bool mapRequest( XMapRequestEvent& e ); + bool unmapNotify( XUnmapEvent& e ); + bool configureRequest( XConfigureRequestEvent& e ); + bool propertyNotify( XPropertyEvent& e ); + +private: + QSize sizeForWindowSize( const QSize&, bool ignore_height = FALSE ) const; + void getWmNormalHints(); + void fetchName(); + void gravitate( bool invert ); + + + WId win; + WindowWrapper* wwrap; + Workspace* wspace; + int desk; + bool buttonDown; + MousePosition mode; + QPoint moveOffset; + QPoint invertedMoveOffset; + QSize clientSize; + XSizeHints xSizeHint; + void sendSynteticConfigureNotify(); + int state; + bool active; + int ignore_unmap; + QRect original_geometry; + bool shaded; + WId transient_for; + bool is_sticky; + void getIcons(); + void getWindowProtocols(); + uint Pdeletewindow :1; // does the window understand the DeleteWindow protocol? + uint Ptakefocus :1;// does the window understand the TakeFocus protocol? + QPixmap icon_pix; + QPixmap miniicon_pix; + QRect geom_restore; +}; + +inline WId Client::window() const +{ + return win; +} + +inline WindowWrapper* Client::windowWrapper() const +{ + return wwrap; +} + +inline Workspace* Client::workspace() const +{ + return wspace; +} + +inline WId Client::transientFor() const +{ + return transient_for; +} + +inline int Client::mappingState() const +{ + return state; +} + + +inline bool Client::isActive() const +{ + return active; +} + +/*! + Returns the virtual desktop within the workspace() the client window + is located in, -1 if it isn't located on any special desktop. This may be + if the window wasn't mapped yet or if the window is sticky. Do not use + desktop() directly, use isOnDesktop() instead. + */ +inline int Client::desktop() const +{ + return desk; + } + +/*! + Returns whether the client is on visible or iconified on the virtual + desktop \a d. This is always TRUE for sticky clients. + */ +inline bool Client::isOnDesktop( int d ) const +{ + return desk == d || desk == -1 || isSticky(); +} + + +inline QPixmap Client::icon() const +{ + return icon_pix; +} + +inline QPixmap Client::miniIcon() const +{ + return miniicon_pix; +} + + +/*! + Is the client maximized? + */ +inline bool Client::isMaximized() const +{ + return !geom_restore.isNull(); +} + +inline bool Client::isSticky() const +{ + return is_sticky; +} + + +class NoBorderClient : public Client +{ + Q_OBJECT +public: + NoBorderClient( Workspace *ws, WId w, QWidget *parent=0, const char *name=0 ); + ~NoBorderClient(); +}; + +#endif diff --git a/kwin b/kwin new file mode 100755 index 0000000000..24bdbe8651 Binary files /dev/null and b/kwin differ diff --git a/kwin.pro b/kwin.pro new file mode 100644 index 0000000000..8895e7e2c7 --- /dev/null +++ b/kwin.pro @@ -0,0 +1,18 @@ +TEMPLATE = app +CONFIG = qt warn_on release +HEADERS = atoms.h \ + beclient.h \ + client.h \ + main.h \ + options.h \ + stdclient.h \ + tabbox.h \ + workspace.h +SOURCES = atoms.cpp \ + beclient.cpp \ + client.cpp \ + main.cpp \ + stdclient.cpp \ + tabbox.cpp \ + workspace.cpp +TARGET = kwin diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000000..f2fb0e4e10 --- /dev/null +++ b/main.cpp @@ -0,0 +1,113 @@ +#include "main.h" +#include "options.h" +#include "atoms.h" +#include "workspace.h" +#include +#include +#include +#include +#include +#include +#include +#define INT8 _X11INT8 +#define INT32 _X11INT32 +#include +#undef INT8 +#undef INT32 + +#define i18n(x) (x) + +Options* options; +Atoms* atoms; + +static bool initting = FALSE; +int x11ErrorHandler(Display *d, XErrorEvent *e){ + char msg[80], req[80], number[80]; + bool ignore_badwindow = FALSE; //maybe temporary + + if (initting && + ( + e->request_code == X_ChangeWindowAttributes + || e->request_code == X_GrabKey + ) + && (e->error_code == BadAccess)) { + fprintf(stderr, i18n("kwin: it looks like there's already a window manager running. kwin not started\n")); + exit(1); + } + + if (ignore_badwindow && (e->error_code == BadWindow || e->error_code == BadColor)) + return 0; + + XGetErrorText(d, e->error_code, msg, sizeof(msg)); + sprintf(number, "%d", e->request_code); + XGetErrorDatabaseText(d, "XRequest", number, "", req, sizeof(req)); + + fprintf(stderr, "kwin: %s(0x%lx): %s\n", req, e->resourceid, msg); + + if (initting) { + fprintf(stderr, i18n("kwin: failure during initialisation; aborting\n")); + exit(1); + } + return 0; +} + +Application::Application( int &argc, char *argv[] ) +: QApplication( argc, argv ) +{ + initting = TRUE; + options = new Options; + atoms = new Atoms; + + // install X11 error handler + XSetErrorHandler( x11ErrorHandler ); + + // create a workspace. + workspaces += new Workspace(); + initting = FALSE; + if ( argc > 1 ) { + QString s = argv[1]; + int i = s.toInt(); + workspaces += new Workspace( (WId ) i ); + } + + syncX(); + initting = FALSE; +} + + +Application::~Application() +{ + for ( WorkspaceList::Iterator it = workspaces.begin(); it != workspaces.end(); ++it) { + delete (*it); + } +} + +bool Application::x11EventFilter( XEvent *e ) +{ + for ( WorkspaceList::Iterator it = workspaces.begin(); it != workspaces.end(); ++it) { + if ( (*it)->workspaceEvent( e ) ) + return TRUE; + } + return FALSE; +} + + + +static void sighandler(int) { + QApplication::exit(); +} + +int main( int argc, char * argv[] ) { + + if (signal(SIGTERM, sighandler) == SIG_IGN) + signal(SIGTERM, SIG_IGN); + if (signal(SIGINT, sighandler) == SIG_IGN) + signal(SIGINT, SIG_IGN); + if (signal(SIGHUP, sighandler) == SIG_IGN) + signal(SIGHUP, SIG_IGN); + + Application a( argc, argv ); + fcntl(ConnectionNumber(qt_xdisplay()), F_SETFD, 1); + + return a.exec(); +} diff --git a/main.h b/main.h new file mode 100644 index 0000000000..5bb44fa4d6 --- /dev/null +++ b/main.h @@ -0,0 +1,23 @@ +#ifndef MAIN_H +#define MAIN_H + +#include +#include "workspace.h" + +typedef QValueList WorkspaceList; +class Application : public QApplication +{ +public: + Application( int &argc, char **argv ); + ~Application(); + +protected: + bool x11EventFilter( XEvent * ); + +private: + WorkspaceList workspaces; +}; + + + +#endif diff --git a/options.h b/options.h new file mode 100644 index 0000000000..764f23fe06 --- /dev/null +++ b/options.h @@ -0,0 +1,47 @@ +#ifndef OPTIONS_H +#define OPTIONS_H + + +class Options { +public: + + /*! + Different focus policies: +
    + +
  • ClickToFocus - Clicking into a window activates it. This is + also the default. + +
  • FocusFollowsMouse - Moving the mouse pointer actively onto a + window activates it. + +
  • FocusUnderMouse - The window that happens to be under the + mouse pointer becomes active. + +
  • FocusStricklyUnderMouse - Only the window under the mouse + pointer is active. If the mouse points nowhere, nothing has the + focus. In practice, this is the same as FocusUnderMouse, since + kdesktop can take the focus. + + Note that FocusUnderMouse and FocusStricklyUnderMouse are not + particulary useful. They are only provided for old-fashined + die-hard UNIX people ;-) + +
+ */ + enum FocusPolicy { ClickToFocus, FocusFollowsMouse, FocusUnderMouse, FocusStricklyUnderMouse }; + FocusPolicy focusPolicy; + + bool focusPolicyIsReasonable() { + return focusPolicy == ClickToFocus || focusPolicy == FocusFollowsMouse; + } + + Options(){ + focusPolicy = ClickToFocus; + } + +}; + +extern Options* options; + +#endif diff --git a/stdclient.cpp b/stdclient.cpp new file mode 100644 index 0000000000..73503bf63d --- /dev/null +++ b/stdclient.cpp @@ -0,0 +1,334 @@ +#include "stdclient.h" +#include +#include +#include +#include +#include +#include +#include +#include "workspace.h" + + +static const char * close_xpm[] = { +/* width height num_colors chars_per_pixel */ +"16 16 3 1", +/* colors */ +" s None c None", +". c white", +"X c #707070", +/* pixels */ +" ", +" ", +" .X .X ", +" .XX .XX ", +" .XX .XX ", +" .XX .XX ", +" .XX.XX ", +" .XXX ", +" .XXX ", +" .XX.XX ", +" .XX .XX ", +" .XX .XX ", +" .XX .XX ", +" .X .X ", +" ", +" "}; + + +static const char * maximize_xpm[] = { +/* width height num_colors chars_per_pixel */ +"16 16 3 1", +/* colors */ +" s None c None", +". c white", +"X c #707070", +/* pixels */ +" ", +" ", +" ........... ", +" .XXXXXXXXXX ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X........X ", +" .XXXXXXXXXX ", +" ", +" ", +" "}; + + +static const char * minimize_xpm[] = { +/* width height num_colors chars_per_pixel */ +"16 16 3 1", +/* colors */ +" s None c None", +". c white", +"X c #707070", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" ", +" ... ", +" . X ", +" .XX ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; + +static const char * normalize_xpm[] = { +/* width height num_colors chars_per_pixel */ +"16 16 3 1", +/* colors */ +" s None c None", +". c #707070", +"X c white", +/* pixels */ +" ", +" ", +" ........... ", +" .XXXXXXXXXX ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X .X ", +" .X........X ", +" .XXXXXXXXXX ", +" ", +" ", +" "}; + +static const char * pinup_xpm[] = { +/* width height num_colors chars_per_pixel */ +"16 16 4 1", +/* colors */ +" s None c None", +". c #707070", +"X c white", +"o c #a0a0a0", +/* pixels */ +" ", +" ", +" ", +" .. . ", +" .X. .. ", +" .XX...X. ", +"XXXXXX.oXoXoX. ", +"oooooo.oXoXoX. ", +".......oo.o.o. ", +" .o...... ", +" ... .. ", +" .. . ", +" ", +" ", +" ", +" "}; + +static const char * pindown_xpm[] = { +/* width height num_colors chars_per_pixel */ +"16 16 4 1", +/* colors */ +" s None c None", +". c #707070", +"X c white", +"o c #a0a0a0", +/* pixels */ +" ", +" ", +" .... ", +" ..XXXX. ", +" ...XXXXXX. ", +" .X.XXXooo. ", +" .XX.XXooo.. ", +" .XX..Xoo... ", +" .XXXX..... ", +" .XXXoooo.. ", +" .Xoooo... ", +" .oooo... ", +" ...... ", +" ", +" ", +" "}; + +static QPixmap* close_pix = 0; +static QPixmap* maximize_pix = 0; +static QPixmap* minimize_pix = 0; +static QPixmap* normalize_pix = 0; +static QPixmap* pinup_pix = 0; +static QPixmap* pindown_pix = 0; +static bool pixmaps_created = FALSE; + +static void create_pixmaps() +{ + if ( pixmaps_created ) + return; + close_pix = new QPixmap( close_xpm ); + maximize_pix = new QPixmap( maximize_xpm ); + minimize_pix = new QPixmap( minimize_xpm ); + normalize_pix = new QPixmap( normalize_xpm ); + pinup_pix = new QPixmap( pinup_xpm ); + pindown_pix = new QPixmap( pindown_xpm ); + +} + + +StdClient::StdClient( Workspace *ws, WId w, QWidget *parent, const char *name ) + : Client( ws, w, parent, name, WResizeNoErase ) +{ + create_pixmaps(); + + QFont f = font(); + f.setBold( TRUE ); + setFont( f ); + + QGridLayout* g = new QGridLayout( this, 0, 0, 2 ); + g->setRowStretch( 1, 10 ); + g->addWidget( windowWrapper(), 1, 1 ); + g->addColSpacing(0, 2); + g->addColSpacing(2, 2); + g->addRowSpacing(2, 2); + + + button[0] = new QToolButton( this ); + button[1] = new QToolButton( this ); + button[2] = new QToolButton( this ); + button[3] = new QToolButton( this ); + button[4] = new QToolButton( this ); + button[5] = new QToolButton( this ); + + QHBoxLayout* hb = new QHBoxLayout; + g->addLayout( hb, 0, 1 ); + hb->addWidget( button[0] ); + hb->addWidget( button[1] ); + hb->addWidget( button[2] ); + + int fh = fontMetrics().lineSpacing(); + + titlebar = new QSpacerItem(10, fh, QSizePolicy::Expanding, + QSizePolicy::Minimum ); + hb->addItem( titlebar ); + + hb->addWidget( button[3] ); + hb->addWidget( button[4] ); + hb->addWidget( button[5] ); + + for ( int i = 0; i < 6; i++) { + button[i]->setMouseTracking( TRUE ); + button[i]->setFixedSize( 20, 20 ); + } + + button[0]->setIconSet( miniIcon() ); + button[1]->setIconSet( isSticky()?*pindown_pix:*pinup_pix ); + connect( button[1], SIGNAL( clicked() ), this, ( SLOT( toggleSticky() ) ) ); + button[2]->hide(); + + button[3]->setIconSet( *minimize_pix ); + connect( button[3], SIGNAL( clicked() ), this, ( SLOT( iconify() ) ) ); + button[4]->setIconSet( *maximize_pix ); + connect( button[4], SIGNAL( clicked() ), this, ( SLOT( maximize() ) ) ); + button[5]->setIconSet( *close_pix ); + connect( button[5], SIGNAL( clicked() ), this, ( SLOT( closeWindow() ) ) ); + +} + + +StdClient::~StdClient() +{ +} + + +void StdClient::resizeEvent( QResizeEvent* e) +{ + Client::resizeEvent( e ); + + if ( isVisibleToTLW() ) { + // manual clearing without the titlebar (we selected WResizeNoErase ) + QPainter p( this ); + QRect t = titlebar->geometry(); + t.setTop( 0 ); + QRegion r = rect(); + r = r.subtract( t ); + p.setClipRegion( r ); + p.eraseRect( rect() ); + } +} + +/*!\reimp + */ +void StdClient::captionChange( const QString& ) +{ + repaint( titlebar->geometry(), FALSE ); +} + + +/*!\reimp + */ +void StdClient::maximizeChange( bool m ) +{ + button[4]->setIconSet( m?*normalize_pix:*maximize_pix ); +} + + +/*!\reimp + */ +void StdClient::stickyChange( bool s) +{ + button[1]->setIconSet( s?*pindown_pix:*pinup_pix ); +} + +void StdClient::paintEvent( QPaintEvent* ) +{ + QPainter p( this ); + QRect t = titlebar->geometry(); + t.setTop( 0 ); + QRegion r = rect(); + r = r.subtract( t ); + p.setClipRegion( r ); + qDrawWinPanel( &p, rect(), colorGroup() ); + p.setClipping( FALSE ); + p.fillRect( t, isActive()?darkBlue:gray ); + qDrawShadePanel( &p, t.x(), t.y(), t.width(), t.height(), + colorGroup(), TRUE ); + + t.setTop( 2 ); + t.setLeft( t.left() + 4 ); + t.setRight( t.right() - 2 ); + + p.setPen( colorGroup().light() ); + p.drawText( t, AlignLeft|AlignVCenter, caption() ); +} + + +void StdClient::mouseDoubleClickEvent( QMouseEvent * e ) +{ + if ( titlebar->geometry().contains( e->pos() ) ) + setShade( !isShade() ); + workspace()->requestFocus( this ); +} + + +void StdClient::init() +{ + button[0]->setIconSet( miniIcon() ); + + // ### TODO transient etc. + } + +void StdClient::iconChange() +{ + button[0]->setIconSet( miniIcon() ); + button[0]->repaint( FALSE ); +} diff --git a/stdclient.h b/stdclient.h new file mode 100644 index 0000000000..7b75757bec --- /dev/null +++ b/stdclient.h @@ -0,0 +1,33 @@ +#ifndef STDCLIENT_H +#define STDCLIENT_H +#include "client.h" +class QToolButton; +class QLabel; +class QSpacerItem; + +class StdClient : public Client +{ + Q_OBJECT +public: + StdClient( Workspace *ws, WId w, QWidget *parent=0, const char *name=0 ); + ~StdClient(); + +protected: + void resizeEvent( QResizeEvent* ); + void paintEvent( QPaintEvent* ); + + void mouseDoubleClickEvent( QMouseEvent * ); + + void init(); + void captionChange( const QString& name ); + void iconChange(); + void maximizeChange( bool ); + void stickyChange( bool ); + +private: + QToolButton* button[6]; + QSpacerItem* titlebar; +}; + + +#endif diff --git a/tabbox.cpp b/tabbox.cpp new file mode 100644 index 0000000000..845b7357ff --- /dev/null +++ b/tabbox.cpp @@ -0,0 +1,179 @@ +#include "tabbox.h" +#include "workspace.h" +#include "client.h" +#include + +const bool options_traverse_all = FALSE; // TODO + +TabBox::TabBox( Workspace *ws, const char *name=0 ) + : QWidget( 0, name, WStyle_Customize | WStyle_NoBorder ) +{ + wspace = ws; + reset(); +} + +TabBox::~TabBox() +{ +} + + +/*! + Sets the current mode to \a mode, either DesktopMode or WindowsMode + + \sa mode() + */ +void TabBox::setMode( Mode mode ) +{ + m = mode; +} + + +/*! + Resets the tab box to display the active client in WindowsMode, or the + current desktop in DesktopMode + */ +void TabBox::reset() +{ + QFont f = font(); + f.setBold( TRUE ); + f.setPointSize( 14 ); + setFont( f ); + + + // TODO icons etc. + setGeometry( qApp->desktop()->width()/4, + qApp->desktop()->height()/2-fontMetrics().height()*2, + qApp->desktop()->width()/2, fontMetrics().height()*4 ); + + if ( mode() == WindowsMode ) { + client = workspace()->activeClient(); + // todo build window list, consider options_traverse_all + } + else { // DesktopMode + desk = wspace->currentDesktop(); + } + +} + + +/*! + Shows the next or previous item, depending on \a next + */ +void TabBox::nextPrev( bool next) +{ + if ( mode() == WindowsMode ) { + Client* sign = client; + do { + if (client != sign && !sign) + sign = client; + if ( next ) + client = workspace()->nextClient(client); + else + client = workspace()->previousClient(client); + } while (client != sign && client && + !options_traverse_all && + !client->isOnDesktop(workspace()->currentDesktop())); + + if (!options_traverse_all && client + && !client->isOnDesktop(workspace()->currentDesktop())) + client = 0; + } + else { // DesktopMode + if ( next ) { + desk++; + if ( desk > wspace->numberOfDesktops() ) + desk = 1; + } else { + desk--; + if ( desk < 1 ) + desk = wspace->numberOfDesktops(); + } + } + + paintContents(); +} + + + +/*! + Returns the currently displayed client ( only works in WindowsMode ). + Returns 0 if no client is displayed. + */ +Client* TabBox::currentClient() +{ + if ( mode() != WindowsMode ) + return 0; + return client; +} + +/*! + Returns the currently displayed virtual desktop ( only works in + DesktopMode ) + Returns -1 if no desktop is displayed. + */ +int TabBox::currentDesktop() +{ + if ( mode() != DesktopMode ) + return -1; + return desk; +} + + +/*! + Reimplemented to raise the tab box as well + */ +void TabBox::showEvent( QShowEvent* ) +{ + raise(); +} + + +/*! + Paints the tab box + */ +void TabBox::paintEvent( QPaintEvent* ) +{ + { + QPainter p( this ); + style().drawPanel( &p, 0, 0, width(), height(), colorGroup(), FALSE ); + style().drawPanel( &p, 4, 4, width()-8, height()-8, colorGroup(), TRUE ); + } + paintContents(); +} + +/*! + Paints the contents of the tab box. Used in paintEvent() and + whenever the contents changes. + */ +void TabBox::paintContents() +{ + QPainter p( this ); + QRect r(6, 6, width()-12, height()-12 ); + p.fillRect( r, colorGroup().brush( QColorGroup::Background ) ); + if ( mode () == WindowsMode ) { + if ( currentClient() ) { + QString s; + if (!client->isOnDesktop(workspace()->currentDesktop())){ + //TODO s = KWM::getDesktopName(client->desktop); + s.append(": "); + } + + if (client->isIconified()) + s += QString("(")+client->caption()+")"; + else + s += client->caption(); + if ( p.fontMetrics().width( s ) > r.width() ) + p.drawText( r, AlignLeft, s ); + else + p.drawText( r, AlignCenter, s ); + + } + else { + p.drawText( r, AlignCenter, "*** No Tasks ***" ); + } + } else { // DesktopMode + QString s; + s.setNum( desk ); + p.drawText( r, AlignCenter, s ); + } +} diff --git a/tabbox.h b/tabbox.h new file mode 100644 index 0000000000..d6ca7ae863 --- /dev/null +++ b/tabbox.h @@ -0,0 +1,63 @@ +#ifndef TABBOX_H +#define TABBOX_H +#include + +class Workspace; +class Client; + +typedef QValueList ClientList; + +class TabBox : public QWidget +{ + Q_OBJECT +public: + TabBox( Workspace *ws, const char *name=0 ); + ~TabBox(); + + Client* currentClient(); + int currentDesktop(); + + enum Mode { DesktopMode, WindowsMode }; + void setMode( Mode mode ); + Mode mode() const; + + void reset(); + void nextPrev( bool next = TRUE); + + Workspace* workspace() const; + +protected: + void paintEvent( QPaintEvent* ); + void showEvent( QShowEvent* ); + void paintContents(); + +private: + Client* client; + Mode m; + Workspace* wspace; + ClientList clients; + int desk; +// QValueList labels; + +}; + + +/*! + Returns the tab box' workspace + */ +inline Workspace* TabBox::workspace() const +{ + return wspace; +} + +/*! + Returns the current mode, either DesktopMode or WindowsMode + + \sa setMode() + */ +inline TabBox::Mode TabBox::mode() const +{ + return m; +} + +#endif diff --git a/workspace.cpp b/workspace.cpp new file mode 100644 index 0000000000..15eae53c56 --- /dev/null +++ b/workspace.cpp @@ -0,0 +1,859 @@ +#include "workspace.h" +#include "client.h" +#include "stdclient.h" +#include "beclient.h" +#include "tabbox.h" +#include "atoms.h" +#include +#include +#include +#include +#include +#include + + + +static Client* clientFactory( Workspace *ws, WId w ) +{ + // hack TODO hints + char* name = 0; + QString s; + if ( XFetchName( qt_xdisplay(), (Window) w, &name ) && name ) { + s = QString::fromLatin1( name ); + XFree( name ); + } + if ( s == "desktop") { + Client * c = new NoBorderClient( ws, w); + ws->setDesktopClient( c ); + return c; + } + + return new StdClient( ws, w ); +} + +Workspace::Workspace() +{ + root = qt_xrootwin(); // no MDI for now + + (void) QApplication::desktop(); // trigger creation of desktop widget + + // select windowmanager privileges + XSelectInput(qt_xdisplay(), root, + KeyPressMask | + PropertyChangeMask | + ColormapChangeMask | + SubstructureRedirectMask | + SubstructureNotifyMask + ); + + init(); + control_grab = FALSE; + tab_grab = FALSE; + tab_box = new TabBox( this ); + grabKey(XK_Tab, Mod1Mask); + grabKey(XK_Tab, Mod1Mask | ShiftMask); + grabKey(XK_Tab, ControlMask); + grabKey(XK_Tab, ControlMask | ShiftMask); + +} + +Workspace::Workspace( WId rootwin ) +{ + qDebug("create MDI workspace for %d", rootwin ); + root = rootwin; + + // select windowmanager privileges + XSelectInput(qt_xdisplay(), root, + KeyPressMask | + PropertyChangeMask | + ColormapChangeMask | + SubstructureRedirectMask | + SubstructureNotifyMask + ); + + init(); + control_grab = FALSE; + tab_grab = FALSE; + tab_box = new TabBox( this ); + grabKey(XK_Tab, Mod1Mask); + grabKey(XK_Tab, Mod1Mask | ShiftMask); + grabKey(XK_Tab, ControlMask); + grabKey(XK_Tab, ControlMask | ShiftMask); + +} + +void Workspace::init() +{ + tab_box = 0; + active_client = 0; + should_get_focus = 0; + desktop_client = 0; + current_desktop = 1; + + unsigned int i, nwins; + Window dw1, dw2, *wins; + XWindowAttributes attr; + + XGrabServer( qt_xdisplay() ); + XQueryTree(qt_xdisplay(), root, &dw1, &dw2, &wins, &nwins); + for (i = 0; i < nwins; i++) { + XGetWindowAttributes(qt_xdisplay(), wins[i], &attr); + if (attr.override_redirect ) + continue; + if (attr.map_state != IsUnmapped) { + Client* c = clientFactory( this, wins[i] ); + clients.append( c ); + if ( c != desktop_client ) + stacking_order.append( c ); + focus_chain.append( c ); + c->manage( TRUE ); + if ( c == desktop_client ) + setDesktopClient( c ); + if ( root != qt_xrootwin() ) { + // TODO may use QWidget:.create + qDebug(" create a mdi client"); + XReparentWindow( qt_xdisplay(), c->winId(), root, 0, 0 ); + c->move(0,0); + } + } + } + XFree((void *) wins); + XUngrabServer( qt_xdisplay() ); + popup = 0; +} + +Workspace::~Workspace() +{ + for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { + delete (*it); + } + delete tab_box; + delete popup; +} + +/*! + Handles workspace specific XEvents + */ +bool Workspace::workspaceEvent( XEvent * e ) +{ + Client * c = findClient( e->xany.window ); + if ( c ) + return c->windowEvent( e ); + + switch (e->type) { + case ButtonPress: + case ButtonRelease: + break; + case UnmapNotify: + // this is special due to + // SubstructureRedirectMask. e->xany.window is the window the + // event is reported to. Take care not to confuse Qt. + c = findClient( e->xunmap.window ); + + if ( c ) + return c->windowEvent( e ); + + if ( e->xunmap.event != e->xunmap.window ) // hide wm typical event from Qt + return TRUE; + case ReparentNotify: + //do not confuse Qt with these events. After all, _we_ are the + //window manager who does the reparenting. + return true; + case DestroyNotify: + return destroyClient( findClient( e->xdestroywindow.window ) ); + case MapRequest: + if ( e->xmaprequest.parent == root ) { + c = findClient( e->xmaprequest.window ); + if ( !c ) { + c = clientFactory( this, e->xmaprequest.window ); + if ( root != qt_xrootwin() ) { + // TODO may use QWidget:.create + XReparentWindow( qt_xdisplay(), c->winId(), root, 0, 0 ); + } + clients.append( c ); + if ( c != desktop_client ) + stacking_order.append( c ); + } + bool result = c->windowEvent( e ); + if ( c == desktop_client ) + setDesktopClient( c ); + return result; + } + break; + case ConfigureRequest: + if ( e->xconfigurerequest.parent == root ) { + XWindowChanges wc; + unsigned int value_mask = 0; + wc.border_width = 0; + wc.x = e->xconfigurerequest.x; + wc.y = e->xconfigurerequest.y; + wc.width = e->xconfigurerequest.width; + wc.height = e->xconfigurerequest.height; + wc.sibling = None; + wc.stack_mode = Above; + value_mask = e->xconfigurerequest.value_mask | CWBorderWidth; + XConfigureWindow( qt_xdisplay(), e->xconfigurerequest.window, value_mask, & wc ); + + XWindowAttributes attr; + if (XGetWindowAttributes(qt_xdisplay(), e->xconfigurerequest.window, &attr)){ + // send a synthetic configure notify in any case (even if we didn't change anything) + XConfigureEvent c; + c.type = ConfigureNotify; + c.event = e->xconfigurerequest.window; + c.window = e->xconfigurerequest.window; + c.x = attr.x; + c.y = attr.y; + c.width = attr.width; + c.height = attr.height; + c.border_width = 0; + XSendEvent( qt_xdisplay(), c.event, TRUE, NoEventMask, (XEvent*)&c ); + } + return TRUE; + } + else { + c = findClient( e->xconfigurerequest.window ); + if ( c ) + return c->windowEvent( e ); + } + + break; + case KeyPress: + return keyPress(e->xkey); + break; + case KeyRelease: + return keyRelease(e->xkey); + break; + case FocusIn: + break; + case FocusOut: + break; + default: + break; + } + return FALSE; +} + +/*! + Finds the client that embedds the window \a w + */ +Client* Workspace::findClient( WId w ) const +{ + for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { + if ( (*it)->window() == w ) + return *it; + } + return 0; +} + +/*! + Returns the workspace's geometry + */ +QRect Workspace::geometry() const +{ + if ( root == qt_xrootwin() ) + return QRect( QPoint(0, 0), QApplication::desktop()->size() ); + else { + // todo caching, keep track of configure notify etc. + QRect r; + XWindowAttributes attr; + if (XGetWindowAttributes(qt_xdisplay(), root, &attr)){ + r.setRect(0, 0, attr.width, attr.height ); + } + return r; + } +} + + +/* + Destroys the client \a c + */ +bool Workspace::destroyClient( Client* c) +{ + if ( !c ) + return FALSE; + clients.remove( c ); + stacking_order.remove( c ); + focus_chain.remove( c ); + c->invalidateWindow(); + delete c; + clientHidden( c ); + return TRUE; +} + + + +/*! + Auxiliary function to release a passive keyboard grab + */ +void Workspace::freeKeyboard(bool pass){ + if (!pass) + XAllowEvents(qt_xdisplay(), AsyncKeyboard, CurrentTime); + else + XAllowEvents(qt_xdisplay(), ReplayKeyboard, CurrentTime); + QApplication::syncX(); +} + +/*! + Handles alt-tab / control-tab + */ +bool Workspace::keyPress(XKeyEvent key) +{ + if ( root != qt_xrootwin() ) + return FALSE; + int kc = XKeycodeToKeysym(qt_xdisplay(), key.keycode, 0); + int km = key.state & (ControlMask | Mod1Mask | ShiftMask); + + const bool options_alt_tab_mode_is_CDE_style = FALSE; // TODO + + if (!control_grab){ + + if( (kc == XK_Tab) && + ( km == (Mod1Mask | ShiftMask) + || km == (Mod1Mask) + )){ + if (!tab_grab){ + if (options_alt_tab_mode_is_CDE_style ){ + // CDE style raise / lower + Client* c = topClientOnDesktop(); + Client* nc = c; + if (km & ShiftMask){ + do { + nc = previousStaticClient(nc); + } while (nc && nc != c && + (!nc->isOnDesktop(currentDesktop()) || + nc->isIconified())); + + } + else + do { + nc = nextStaticClient(nc); + } while (nc && nc != c && + (!nc->isOnDesktop(currentDesktop()) || + nc->isIconified())); + if (c && c != nc) + ;//TODO lowerClient(c); + if (nc) + activateClient( nc ); + freeKeyboard(FALSE); + return TRUE; + } + XGrabKeyboard(qt_xdisplay(), + root, FALSE, + GrabModeAsync, GrabModeAsync, + CurrentTime); + tab_grab = TRUE; + tab_box->setMode( TabBox::WindowsMode ); + tab_box->reset(); + } + tab_box->nextPrev( (km & ShiftMask) == 0 ); + tab_box->show(); + } + } + + if (!tab_grab){ + + + if( (kc == XK_Tab) && + ( km == (ControlMask | ShiftMask) + || km == (ControlMask) + )){ +//TODO if (!options.ControlTab){ +// freeKeyboard(TRUE); +// return TRUE; +// } + if (!control_grab){ + XGrabKeyboard(qt_xdisplay(), + root, FALSE, + GrabModeAsync, GrabModeAsync, + CurrentTime); + control_grab = TRUE; + tab_box->setMode( TabBox::DesktopMode ); + tab_box->reset(); + } + tab_box->nextPrev( (km & ShiftMask) == 0 ); + tab_box->show(); + } + } + + if (control_grab || tab_grab){ + if (kc == XK_Escape){ + XUngrabKeyboard(qt_xdisplay(), CurrentTime); + tab_box->hide(); + tab_grab = FALSE; + control_grab = FALSE; + return TRUE; + } + return FALSE; + } + + freeKeyboard(FALSE); + return FALSE; +} + +/*! + Handles alt-tab / control-tab + */ +bool Workspace::keyRelease(XKeyEvent key) +{ + if ( root != qt_xrootwin() ) + return FALSE; + int i; + if (tab_grab){ + XModifierKeymap* xmk = XGetModifierMapping(qt_xdisplay()); + for (i=0; imax_keypermod; i++) + if (xmk->modifiermap[xmk->max_keypermod * Mod1MapIndex + i] + == key.keycode){ + XUngrabKeyboard(qt_xdisplay(), CurrentTime); + tab_box->hide(); + tab_grab = false; + if ( tab_box->currentClient() ){ + + activateClient( tab_box->currentClient() ); + } + } + } + if (control_grab){ + XModifierKeymap* xmk = XGetModifierMapping(qt_xdisplay()); + for (i=0; imax_keypermod; i++) + if (xmk->modifiermap[xmk->max_keypermod * ControlMapIndex + i] + == key.keycode){ + XUngrabKeyboard(qt_xdisplay(), CurrentTime); + tab_box->hide(); + control_grab = False; + if ( tab_box->currentDesktop() != -1 ) + switchDesktop( tab_box->currentDesktop() ); + } + } + return FALSE; +} + +/*! + auxiliary functions to travers all clients according the focus + order. Useful for kwm´s Alt-tab feature. +*/ +Client* Workspace::nextClient( Client* c ) const +{ + if ( focus_chain.isEmpty() ) + return 0; + ClientList::ConstIterator it = focus_chain.find( c ); + if ( it == focus_chain.end() ) + return focus_chain.last(); + if ( it == focus_chain.begin() ) + return focus_chain.last(); + --it; + return *it; +} + +/*! + auxiliary functions to travers all clients according the focus + order. Useful for kwm´s Alt-tab feature. +*/ +Client* Workspace::previousClient( Client* c ) const +{ + if ( focus_chain.isEmpty() ) + return 0; + ClientList::ConstIterator it = focus_chain.find( c ); + if ( it == focus_chain.end() ) + return focus_chain.first(); + ++it; + if ( it == focus_chain.end() ) + return focus_chain.first(); + return *it; +} + +/*! + auxiliary functions to travers all clients according the static + order. Useful for the CDE-style Alt-tab feature. +*/ +Client* Workspace::nextStaticClient( Client* c ) const +{ + if ( clients.isEmpty() ) + return 0; + ClientList::ConstIterator it = clients.find( c ); + if ( it == clients.end() ) + return clients.first(); + ++it; + if ( it == clients.end() ) + return clients.first(); + return *it; +} +/*! + auxiliary functions to travers all clients according the static + order. Useful for the CDE-style Alt-tab feature. +*/ +Client* Workspace::previousStaticClient( Client* c ) const +{ + if ( clients.isEmpty() ) + return 0; + ClientList::ConstIterator it = clients.find( c ); + if ( it == clients.end() ) + return clients.last(); + if ( it == clients.begin() ) + return clients.last(); + --it; + return *it; +} + + +/*! + Returns topmost visible client within the specified layer range on + the current desktop, or 0 if no clients are visible. \a fromLayer has to + be smaller than \a toLayer. + */ +Client* Workspace::topClientOnDesktop( int fromLayer, int toLayer) const +{ + fromLayer = toLayer = 0; + return 0; +} + +/* + Grabs the keysymbol \a keysym with the given modifiers \a mod + plus all possibile combinations of Lock and NumLock + */ +void Workspace::grabKey(KeySym keysym, unsigned int mod){ + static int NumLockMask = 0; + if (!keysym||!XKeysymToKeycode(qt_xdisplay(), keysym)) return; + if (!NumLockMask){ + XModifierKeymap* xmk = XGetModifierMapping(qt_xdisplay()); + int i; + for (i=0; i<8; i++){ + if (xmk->modifiermap[xmk->max_keypermod * i] == + XKeysymToKeycode(qt_xdisplay(), XK_Num_Lock)) + NumLockMask = (1<setActive( FALSE ); + active_client = c; + if ( active_client ) { + focus_chain.remove( c ); + focus_chain.append( c ); + } +} + + +/*! + Tries to activate the client \a c. This function performs what you + expect when clicking the respective entry in a taskbar: showing and + raising the client (this may imply switching to the another virtual + desktop) and putting the focus onto it. Once X really gave focus to + the client window as requested, the client itself will call + setActiveClient() and the operation is complete. This may not happen + with certain focus policies, though. + + \sa setActiveClient(), requestFocus() + */ +void Workspace::activateClient( Client* c) +{ + if (!c->isOnDesktop(currentDesktop()) ) { + // TODO switch desktop + } + raiseClient( c ); + c->show(); + if ( options->focusPolicyIsReasonable() ) + requestFocus( c ); +} + + +/*! + Tries to activate the client by asking X for the input focus. This + function does not perform any show, raise or desktop switching. See + Workspace::activateClient() instead. + + \sa Workspace::activateClient() + */ +void Workspace::requestFocus( Client* c) +{ + + //TODO will be different for non-root clients. (subclassing?) + if ( !c ) { + focusToNull(); + return; + } + + if ( c->isVisible() && !c->isShade() ) { + c->takeFocus(); + should_get_focus = c; + } else if ( c->isShade() ) { + // client cannot accept focus, but at least the window should be active (window menu, et. al. ) + focusToNull(); + c->setActive( TRUE ); + } +} + + +/*! + Informs the workspace that the client \a c has been hidden. If it + was the active client, the workspace activates another one. + + \a c may already be destroyed + */ +void Workspace::clientHidden( Client* c ) +{ + if ( c == active_client || ( !active_client && c == should_get_focus ) ) { + active_client = 0; + should_get_focus = 0; + if ( clients.contains( c ) ) { + focus_chain.remove( c ); + focus_chain.prepend( c ); + } + if ( options->focusPolicyIsReasonable() ) { + for ( ClientList::ConstIterator it = focus_chain.fromLast(); it != focus_chain.begin(); --it) { + if ( (*it)->isVisible() ) { + requestFocus( *it ); + break; + } + } + } + } +} + + +void Workspace::showPopup( const QPoint& pos, Client* c) +{ + // experimental!!! + + if ( !popup ) { + popup = new QPopupMenu; + + // I wish I could use qt-2.1 features here..... grmblll + QPopupMenu* deco = new QPopupMenu( popup ); + deco->insertItem("KDE Classic", 100 ); + deco->insertItem("Be-like style", 101 ); + + popup->insertItem("Decoration", deco ); + } + popup_client = c; + // TODO customize popup for the client + int ret = popup->exec( pos ); + + switch( ret ) { + case 100: + setDecoration( 0 ); + break; + case 101: + setDecoration( 1 ); + break; + default: + break; + } + + popup_client = 0; + ret = 0; +} + + +/*! + Places the client \a c according to the workspace's layout policy + */ +void Workspace::doPlacement( Client* c ) +{ + randomPlacement( c ); +} + +/*! + Place the client \a c according to a simply "random" placement algorithm. + */ +void Workspace::randomPlacement(Client* c){ + const int step = 24; + static int px = step; + static int py = 2 * step; + int tx,ty; + + QRect maxRect = geometry(); // TODO + + if (px < maxRect.x()) + px = maxRect.x(); + if (py < maxRect.y()) + py = maxRect.y(); + + px += step; + py += 2*step; + + if (px > maxRect.width()/2) + px = maxRect.x() + step; + if (py > maxRect.height()/2) + py = maxRect.y() + step; + tx = px; + ty = py; + if (tx + c->width() > maxRect.right()){ + tx = maxRect.right() - c->width(); + if (tx < 0) + tx = 0; + px = maxRect.x(); + } + if (ty + c->height() > maxRect.bottom()){ + ty = maxRect.bottom() - c->height(); + if (ty < 0) + ty = 0; + py = maxRect.y(); + } + c->move( tx, ty ); +} + + + +/*! + Raises the client \a c taking layers, transient windows and window + groups into account. + */ +void Workspace::raiseClient( Client* c ) +{ + if ( !c ) + return; + if ( c == desktop_client ) + return; // deny + + Window* new_stack = new Window[ stacking_order.count()+1]; + + stacking_order.remove( c ); + stacking_order.append( c ); + + ClientList saveset; + saveset.append( c ); + raiseTransientsOf(saveset, c ); + + int i = 0; + for ( ClientList::ConstIterator it = stacking_order.fromLast(); it != stacking_order.end(); --it) { + new_stack[i++] = (*it)->winId(); + } + XRaiseWindow(qt_xdisplay(), new_stack[0]); + XRestackWindows(qt_xdisplay(), new_stack, i); + delete [] new_stack; + + if ( c->transientFor() ) + raiseClient( findClient( c->transientFor() ) ); +} + + + +/*! + Private auxiliary function used in raiseClient() + */ +void Workspace::raiseTransientsOf( ClientList& safeset, Client* c ) +{ + ClientList local = stacking_order; + for ( ClientList::ConstIterator it = local.begin(); it != local.end(); ++it) { + if ( (*it)->transientFor() == c->window() && !safeset.contains( *it ) ) { + safeset.append( *it ); + stacking_order.remove( *it ); + stacking_order.append( *it ); + raiseTransientsOf( safeset, *it ); + } + } +} + +/*! + Puts the focus on a dummy winodw + */ +void Workspace::focusToNull(){ + static Window w = 0; + int mask; + XSetWindowAttributes attr; + if (w == 0) { + mask = CWOverrideRedirect; + attr.override_redirect = 1; + w = XCreateWindow(qt_xdisplay(), qt_xrootwin(), 0, 0, 1, 1, 0, CopyFromParent, + InputOnly, CopyFromParent, mask, &attr); + XMapWindow(qt_xdisplay(), w); + } + XSetInputFocus(qt_xdisplay(), w, RevertToPointerRoot, CurrentTime ); + //colormapFocus(0); TODO +} + +void Workspace::setDesktopClient( Client* c) +{ + desktop_client = c; + if ( desktop_client ) { + desktop_client->lower(); + desktop_client->setGeometry( geometry() ); + } +} + +void Workspace::switchDesktop( int new_desktop ){ + if (new_desktop == current_desktop ) + return; + + /* + optimized Desktop switching: unmapping done from back to front + mapping done from front to back => less exposure events + */ + + for ( ClientList::ConstIterator it = stacking_order.fromLast(); it != stacking_order.end(); --it) { + if ( (*it)->isVisible() && !(*it)->isOnDesktop( new_desktop ) ) { + (*it)->hide(); + } + } + + for ( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) { + if ( (*it)->isOnDesktop( new_desktop ) ) { + (*it)->show(); + //XMapWindow( qt_xdisplay(), (*it)->winId() ); + } + } + + current_desktop = new_desktop; +} + + +void Workspace::makeFullScreen( Client* ) +{ + // not yet implemented +} + + +// experimental +void Workspace::setDecoration( int deco ) +{ + if ( !popup_client ) + return; + Client* c = popup_client; + WId w = c->window(); + clients.remove( c ); + stacking_order.remove( c ); + focus_chain.remove( c ); + bool mapped = c->isVisible(); + c->hide(); + c->releaseWindow(); + switch ( deco ) { + case 1: + c = new BeClient( this, w); + break; + default: + c = new StdClient( this, w ); + } + clients.append( c ); + stacking_order.append( c ); + c->manage( mapped ); + activateClient( c ); +} + diff --git a/workspace.h b/workspace.h new file mode 100644 index 0000000000..8091d7a8a9 --- /dev/null +++ b/workspace.h @@ -0,0 +1,126 @@ +#ifndef WORKSPACE_H +#define WORKSPACE_H + +#include +#include +#include +#include +#include + +class Client; +class TabBox; + +typedef QValueList ClientList; + +class Workspace : public QObject +{ + Q_OBJECT +public: + Workspace(); + Workspace( WId rootwin ); + virtual ~Workspace(); + + virtual bool workspaceEvent( XEvent * ); + + Client* findClient( WId w ) const; + + QRect geometry() const; + + bool destroyClient( Client* ); + + WId rootWin() const; + + Client* activeClient() const; + void setActiveClient( Client* ); + void activateClient( Client* ); + void requestFocus( Client* c); + + void doPlacement( Client* c ); + void raiseClient( Client* c ); + + void clientHidden( Client* ); + + int currentDesktop() const; + int numberOfDesktops() const; + + void grabKey(KeySym keysym, unsigned int mod); + + Client* nextClient(Client*) const; + Client* previousClient(Client*) const; + Client* nextStaticClient(Client*) const; + Client* previousStaticClient(Client*) const; + + //#### TODO right layers as default + Client* topClientOnDesktop( int fromLayer = 0, int toLayer = 0) const; + + + void showPopup( const QPoint&, Client* ); + + void setDesktopClient( Client* ); + void switchDesktop( int new_desktop ); + + void makeFullScreen( Client* ); + +protected: + bool keyPress( XKeyEvent key ); + bool keyRelease( XKeyEvent key ); + +private: + void init(); + WId root; + ClientList clients; + ClientList stacking_order; + ClientList focus_chain; + Client* active_client; + bool control_grab; + bool tab_grab; + TabBox* tab_box; + void freeKeyboard(bool pass); + QPopupMenu *popup; + Client* should_get_focus; + + void raiseTransientsOf( ClientList& safeset, Client* c ); + void randomPlacement(Client* c); + + void focusToNull(); + Client* desktop_client; + int current_desktop; + + Client* popup_client; + + //experimental + void setDecoration( int deco ); +}; + +inline WId Workspace::rootWin() const +{ + return root; +} + +/*! + Returns the active client, i.e. the client that has the focus (or None if no + client has the focus) + */ +inline Client* Workspace::activeClient() const +{ + return active_client; +} + + +/*! + Returns the current virtual desktop of this workspace + */ +inline int Workspace::currentDesktop() const +{ + return current_desktop; +} + +/*! + Returns the number of virtual desktops of this workspace + */ +inline int Workspace::numberOfDesktops() const +{ + return 4; +} + +#endif