/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ // // TXWindow.cxx // #ifdef HAVE_CONFIG_H #include #endif #include #include "TXWindow.h" #include #include #include #include #include #include std::list windows; Atom wmProtocols, wmDeleteWindow, wmTakeFocus; Atom xaTIMESTAMP, xaTARGETS, xaSELECTION_TIME, xaSELECTION_STRING; Atom xaCLIPBOARD; unsigned long TXWindow::black, TXWindow::white; unsigned long TXWindow::defaultFg, TXWindow::defaultBg; unsigned long TXWindow::lightBg, TXWindow::darkBg; unsigned long TXWindow::disabledFg, TXWindow::disabledBg; unsigned long TXWindow::enabledBg; unsigned long TXWindow::scrollbarBg; Colormap TXWindow::cmap = 0; GC TXWindow::defaultGC = 0; Font TXWindow::defaultFont = 0; XFontStruct* TXWindow::defaultFS = 0; Time TXWindow::cutBufferTime = 0; Pixmap TXWindow::dot = 0, TXWindow::tick = 0; const int TXWindow::dotSize = 4, TXWindow::tickSize = 8; char* TXWindow::defaultWindowClass; TXGlobalEventHandler* TXWindow::globalEventHandler = NULL; void TXWindow::init(Display* dpy, const char* defaultWindowClass_) { cmap = DefaultColormap(dpy,DefaultScreen(dpy)); wmProtocols = XInternAtom(dpy, "WM_PROTOCOLS", False); wmDeleteWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False); wmTakeFocus = XInternAtom(dpy, "WM_TAKE_FOCUS", False); xaTIMESTAMP = XInternAtom(dpy, "TIMESTAMP", False); xaTARGETS = XInternAtom(dpy, "TARGETS", False); xaSELECTION_TIME = XInternAtom(dpy, "SELECTION_TIME", False); xaSELECTION_STRING = XInternAtom(dpy, "SELECTION_STRING", False); xaCLIPBOARD = XInternAtom(dpy, "CLIPBOARD", False); XColor cols[6]; cols[0].red = cols[0].green = cols[0].blue = 0x0000; cols[1].red = cols[1].green = cols[1].blue = 0xbbbb; cols[2].red = cols[2].green = cols[2].blue = 0xeeee; cols[3].red = cols[3].green = cols[3].blue = 0x5555; cols[4].red = cols[4].green = cols[4].blue = 0x8888; cols[5].red = cols[5].green = cols[5].blue = 0xffff; getColours(dpy, cols, 6); black = defaultFg = cols[0].pixel; defaultBg = disabledBg = cols[1].pixel; lightBg = cols[2].pixel; darkBg = disabledFg = cols[3].pixel; scrollbarBg = cols[4].pixel; white = enabledBg = cols[5].pixel; defaultGC = XCreateGC(dpy, DefaultRootWindow(dpy), 0, 0); defaultFS = XLoadQueryFont(dpy, "-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*"); if (!defaultFS) { defaultFS = XLoadQueryFont(dpy, "fixed"); if (!defaultFS) { fprintf(stderr,"Failed to load any font\n"); exit(1); } } defaultFont = defaultFS->fid; XSetForeground(dpy, defaultGC, defaultFg); XSetBackground(dpy, defaultGC, defaultBg); XSetFont(dpy, defaultGC, defaultFont); XSelectInput(dpy, DefaultRootWindow(dpy), PropertyChangeMask); static unsigned char dotBits[] = { 0x06, 0x0f, 0x0f, 0x06}; dot = XCreateBitmapFromData(dpy, DefaultRootWindow(dpy), (char*)dotBits, dotSize, dotSize); static unsigned char tickBits[] = { 0x80, 0xc0, 0xe2, 0x76, 0x3e, 0x1c, 0x08, 0x00}; tick = XCreateBitmapFromData(dpy, DefaultRootWindow(dpy), (char*)tickBits, tickSize, tickSize); defaultWindowClass = rfb::strDup(defaultWindowClass_); } void TXWindow::handleXEvents(Display* dpy) { while (XPending(dpy)) { XEvent ev; XNextEvent(dpy, &ev); if (globalEventHandler) { if (globalEventHandler->handleGlobalEvent(&ev)) continue; } if (ev.type == MappingNotify) { XRefreshKeyboardMapping(&ev.xmapping); } else if (ev.type == PropertyNotify && ev.xproperty.window == DefaultRootWindow(dpy) && ev.xproperty.atom == XA_CUT_BUFFER0) { cutBufferTime = ev.xproperty.time; } else { std::list::iterator i; for (i = windows.begin(); i != windows.end(); i++) { if ((*i)->win() == ev.xany.window) (*i)->handleXEvent(&ev); } } } } TXGlobalEventHandler* TXWindow::setGlobalEventHandler(TXGlobalEventHandler* h) { TXGlobalEventHandler* old = globalEventHandler; globalEventHandler = h; return old; } void TXWindow::getColours(Display* dpy, XColor* cols, int nCols) { std::vector got; bool failed = false; int i; for (i = 0; i < nCols; i++) { if (XAllocColor(dpy, cmap, &cols[i])) { got.push_back(true); } else { got.push_back(false); failed = true; } } if (!failed) { return; } // AllocColor has failed. This is because the colormap is full. So the // only thing we can do is use the "shared" pixels in the colormap. The // code below is designed to work even when the colormap isn't full so is // more complex than it needs to be in this case. However it would be // useful one day to be able to restrict the number of colours allocated by // an application so I'm leaving it in here. // For each pixel in the colormap, try to allocate exactly its RGB values. // If this returns a different pixel then it must be a private or // unallocated pixel, so we can't use it. If it returns us the same pixel // again, it's almost certainly a shared colour, so we can use it (actually // it is possible that it was an unallocated pixel which we've now // allocated - by going through the pixels in reverse order we make this // unlikely except for the lowest unallocated pixel - this works because of // the way the X server allocates new pixels). int cmapSize = DisplayCells(dpy,DefaultScreen(dpy)); XColor* cm = new XColor[cmapSize]; std::vector shared; std::vector usedAsNearest; for (i = 0; i < cmapSize; i++) { cm[i].pixel = i; shared.push_back(false); usedAsNearest.push_back(false); } XQueryColors(dpy, cmap, cm, cmapSize); for (i = cmapSize-1; i >= 0; i--) { if (XAllocColor(dpy, cmap, &cm[i])) { if (cm[i].pixel == (unsigned long)i) { shared[i] = true; } else { XFreeColors(dpy, cmap, &cm[i].pixel, 1, 0); } } } for (int j = 0; j < nCols; j++) { unsigned long minDistance = ULONG_MAX; unsigned long nearestPixel = 0; if (!got[j]) { for (i = 0; i < cmapSize; i++) { if (shared[i]) { unsigned long rd = (cm[i].red - cols[j].red)/2; unsigned long gd = (cm[i].green - cols[j].green)/2; unsigned long bd = (cm[i].blue - cols[j].blue)/2; unsigned long distance = (rd*rd + gd*gd + bd*bd); if (distance < minDistance) { minDistance = distance; nearestPixel = i; } } } cols[j].pixel = nearestPixel; usedAsNearest[nearestPixel] = true; } } for (i = 0; i < cmapSize; i++) { if (shared[i] && !usedAsNearest[i]) { unsigned long p = i; XFreeColors(dpy, cmap, &p, 1, 0); } } } Window TXWindow::windowWithName(Display* dpy, Window top, const char* name) { char* windowName; if (XFetchName(dpy, top, &windowName)) { if (strcmp(windowName, name) == 0) { XFree(windowName); return top; } XFree(windowName); } Window* children; Window dummy; unsigned int nchildren; if (!XQueryTree(dpy, top, &dummy, &dummy, &children,&nchildren) || !children) return 0; for (int i = 0; i < (int)nchildren; i++) { Window w = windowWithName(dpy, children[i], name); if (w) { XFree((char*)children); return w; } } XFree((char*)children); return 0; } TXWindow::TXWindow(Display* dpy_, int w, int h, TXWindow* parent_, int borderWidth) : dpy(dpy_), xPad(3), yPad(3), bevel(2), parent(parent_), width_(w), height_(h), eventHandler(0), dwc(0), eventMask(0), toplevel_(false) { sizeHints.flags = 0; XSetWindowAttributes attr; attr.background_pixel = defaultBg; attr.border_pixel = 0; Window par = parent ? parent->win() : DefaultRootWindow(dpy); win_ = XCreateWindow(dpy, par, 0, 0, width_, height_, borderWidth, CopyFromParent, CopyFromParent, CopyFromParent, CWBackPixel | CWBorderPixel, &attr); if (parent) map(); windows.push_back(this); } TXWindow::~TXWindow() { windows.remove(this); XDestroyWindow(dpy, win()); } void TXWindow::toplevel(const char* name, TXDeleteWindowCallback* dwc_, int argc, char** argv, const char* windowClass, bool iconic) { toplevel_ = true; XWMHints wmHints; wmHints.flags = InputHint|StateHint; wmHints.input = True; wmHints.initial_state = iconic ? IconicState : NormalState; XClassHint classHint; if (!windowClass) windowClass = defaultWindowClass; classHint.res_name = (char*)name; classHint.res_class = (char*)windowClass; XSetWMProperties(dpy, win(), 0, 0, argv, argc, &sizeHints, &wmHints, &classHint); XStoreName(dpy, win(), name); XSetIconName(dpy, win(), name); Atom protocols[10]; int nProtocols = 0; protocols[nProtocols++] = wmTakeFocus; dwc = dwc_; if (dwc) protocols[nProtocols++] = wmDeleteWindow; XSetWMProtocols(dpy, win(), protocols, nProtocols); addEventMask(StructureNotifyMask); } void TXWindow::setName(const char* name) { XClassHint classHint; XGetClassHint(dpy, win(), &classHint); XFree(classHint.res_name); classHint.res_name = (char*)name; XSetClassHint(dpy, win(), &classHint); XFree(classHint.res_class); XStoreName(dpy, win(), name); XSetIconName(dpy, win(), name); } void TXWindow::setMaxSize(int w, int h) { sizeHints.flags |= PMaxSize; sizeHints.max_width = w; sizeHints.max_height = h; XSetWMNormalHints(dpy, win(), &sizeHints); } void TXWindow::setUSPosition(int x, int y) { sizeHints.flags |= USPosition; sizeHints.x = x; sizeHints.y = y; XSetWMNormalHints(dpy, win(), &sizeHints); move(x, y); } void TXWindow::setGeometry(const char* geom, int x, int y, int w, int h) { char defGeom[256]; sprintf(defGeom,"%dx%d+%d+%d",w,h,x,y); XWMGeometry(dpy, DefaultScreen(dpy), strEmptyToNull((char*)geom), defGeom, 0, &sizeHints, &x, &y, &w, &h, &sizeHints.win_gravity); sizeHints.flags |= PWinGravity; setUSPosition(x, y); resize(w, h); } TXEventHandler* TXWindow::setEventHandler(TXEventHandler* h) { TXEventHandler* old = eventHandler; eventHandler = h; return old; } void TXWindow::addEventMask(long mask) { eventMask |= mask; XSelectInput(dpy, win(), eventMask); } void TXWindow::removeEventMask(long mask) { eventMask &= ~mask; XSelectInput(dpy, win(), eventMask); } void TXWindow::unmap() { XUnmapWindow(dpy, win()); if (toplevel_) { XUnmapEvent ue; ue.type = UnmapNotify; ue.display = dpy; ue.event = DefaultRootWindow(dpy); ue.window = win(); ue.from_configure = False; XSendEvent(dpy, DefaultRootWindow(dpy), False, (SubstructureRedirectMask|SubstructureNotifyMask), (XEvent*)&ue); } } void TXWindow::resize(int w, int h) { //if (w == width_ && h == height_) return; XResizeWindow(dpy, win(), w, h); width_ = w; height_ = h; resizeNotify(); } void TXWindow::setBorderWidth(int bw) { XWindowChanges c; c.border_width = bw; XConfigureWindow(dpy, win(), CWBorderWidth, &c); } void TXWindow::ownSelection(Atom selection, Time time) { XSetSelectionOwner(dpy, selection, win(), time); if (XGetSelectionOwner(dpy, selection) == win()) { selectionOwner_[selection] = true; selectionOwnTime[selection] = time; } } void TXWindow::handleXEvent(XEvent* ev) { switch (ev->type) { case ClientMessage: if (ev->xclient.message_type == wmProtocols) { if ((Atom)ev->xclient.data.l[0] == wmDeleteWindow) { if (dwc) dwc->deleteWindow(this); } else if ((Atom)ev->xclient.data.l[0] == wmTakeFocus) { takeFocus(ev->xclient.data.l[1]); } } break; case ConfigureNotify: if (ev->xconfigure.width != width_ || ev->xconfigure.height != height_) { width_ = ev->xconfigure.width; height_ = ev->xconfigure.height; resizeNotify(); } break; case SelectionNotify: if (ev->xselection.property != None) { Atom type; int format; unsigned long nitems, after; unsigned char *data; XGetWindowProperty(dpy, win(), ev->xselection.property, 0, 16384, True, AnyPropertyType, &type, &format, &nitems, &after, &data); if (type != None) { selectionNotify(&ev->xselection, type, format, nitems, data); XFree(data); break; } } selectionNotify(&ev->xselection, 0, 0, 0, 0); break; case SelectionRequest: { XSelectionEvent se; se.type = SelectionNotify; se.display = ev->xselectionrequest.display; se.requestor = ev->xselectionrequest.requestor; se.selection = ev->xselectionrequest.selection; se.time = ev->xselectionrequest.time; se.target = ev->xselectionrequest.target; if (ev->xselectionrequest.property == None) ev->xselectionrequest.property = ev->xselectionrequest.target; if (!selectionOwner_[se.selection]) { se.property = None; } else { se.property = ev->xselectionrequest.property; if (se.target == xaTARGETS) { Atom targets[2]; targets[0] = xaTIMESTAMP; targets[1] = XA_STRING; XChangeProperty(dpy, se.requestor, se.property, XA_ATOM, 32, PropModeReplace, (unsigned char*)targets, 2); } else if (se.target == xaTIMESTAMP) { Time t = selectionOwnTime[se.selection]; XChangeProperty(dpy, se.requestor, se.property, XA_INTEGER, 32, PropModeReplace, (unsigned char*)&t, 1); } else if (se.target == XA_STRING) { if (!selectionRequest(se.requestor, se.selection, se.property)) se.property = None; } else { se.property = None; } } XSendEvent(dpy, se.requestor, False, 0, (XEvent*)&se); break; } case SelectionClear: selectionOwner_[ev->xselectionclear.selection] = false; break; } if (eventHandler) eventHandler->handleEvent(this, ev); } void TXWindow::drawBevel(GC gc, int x, int y, int w, int h, int b, unsigned long middle, unsigned long tl, unsigned long br, bool round) { if (round) { XGCValues gcv; gcv.line_width = b; XChangeGC(dpy, gc, GCLineWidth, &gcv); XSetForeground(dpy, gc, middle); XFillArc(dpy, win(), gc, x, y, w-b/2, h-b/2, 0, 360*64); XSetForeground(dpy, gc, tl); XDrawArc(dpy, win(), gc, x, y, w-b/2, h-b/2, 45*64, 180*64); XSetForeground(dpy, gc, br); XDrawArc(dpy, win(), gc, x, y, w-b/2, h-b/2, 225*64, 180*64); } else { XSetForeground(dpy, gc, middle); if (w-2*b > 0 && h-2*b > 0) XFillRectangle(dpy, win(), gc, x+b, y+b, w-2*b, h-2*b); XSetForeground(dpy, gc, tl); XFillRectangle(dpy, win(), gc, x, y, w, b); XFillRectangle(dpy, win(), gc, x, y, b, h); XSetForeground(dpy, gc, br); for (int i = 0; i < b; i++) { if (w-i > 0) XFillRectangle(dpy, win(), gc, x+i, y+h-1-i, w-i, 1); if (h-1-i > 0) XFillRectangle(dpy, win(), gc, x+w-1-i, y+i+1, 1, h-1-i); } } }