From d7db3f0d0f8db2317e6ebee27db99e8326ef1141 Mon Sep 17 00:00:00 2001 From: Sanel Zukan Date: Fri, 28 Dec 2007 10:58:56 +0000 Subject: [PATCH] Added composite stuff (alpha stage, of course). A lot of things are pending for implementation like region translucency, shadows, fading... Per window translucency is in, but now working as expected. --- evoke/ClassHack.c | 17 + evoke/ClassHack.h | 36 + evoke/Composite.cpp | 1289 +++++++++++++++++++++++++++++++++++ evoke/Composite.h | 60 ++ evoke/EvokeService.cpp | 17 +- evoke/EvokeService.h | 4 + evoke/Jamfile | 5 +- evoke/Spawn.cpp | 2 +- evoke/Splash.cpp | 3 +- evoke/evoke.cpp | 7 + evoke/test/opacity_test.cpp | 46 ++ 11 files changed, 1482 insertions(+), 4 deletions(-) create mode 100644 evoke/ClassHack.c create mode 100644 evoke/ClassHack.h create mode 100644 evoke/Composite.cpp create mode 100644 evoke/Composite.h create mode 100644 evoke/test/opacity_test.cpp diff --git a/evoke/ClassHack.c b/evoke/ClassHack.c new file mode 100644 index 0000000..e25023a --- /dev/null +++ b/evoke/ClassHack.c @@ -0,0 +1,17 @@ +/* + * $Id$ + * + * Evoke, head honcho of everything + * Part of Equinox Desktop Environment (EDE). + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licensed under terms of the + * GNU General Public License version 2 or newer. + * See COPYING for details. + */ + +#include "ClassHack.h" + +int get_attributes_class_hack(XWindowAttributes* attr) { + return attr->class; +} diff --git a/evoke/ClassHack.h b/evoke/ClassHack.h new file mode 100644 index 0000000..a0d514e --- /dev/null +++ b/evoke/ClassHack.h @@ -0,0 +1,36 @@ +/* + * $Id$ + * + * Evoke, head honcho of everything + * Part of Equinox Desktop Environment (EDE). + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licensed under terms of the + * GNU General Public License version 2 or newer. + * See COPYING for details. + */ + +#ifndef __CLASSHACK_H__ +#define __CLASSHACK_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * A stupid hack to extract value from 'class' member in XWindowAttributes structure. + * Calling it in regular C++ translation unit will yield compilation error (sic) + * so we must force it to be seen as regular C source. + * + * Does X developers ever heard for C++ language ?@#?#!@ + */ + +int get_attributes_class_hack(XWindowAttributes* attr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/evoke/Composite.cpp b/evoke/Composite.cpp new file mode 100644 index 0000000..d9a5e6d --- /dev/null +++ b/evoke/Composite.cpp @@ -0,0 +1,1289 @@ +/* + * $Id$ + * + * Evoke, head honcho of everything + * Part of Equinox Desktop Environment (EDE). + * Based on xcompmgr (c) 2003 Keith Packard. + * Copyright (c) 2007 EDE Authors. + * + * This program is licensed under terms of the + * GNU General Public License version 2 or newer. + * See COPYING for details. + */ + +#include "Composite.h" +#include "ClassHack.h" + +#include + +#include +#include + +#include // memcpy +#include // malloc, realloc + +#include +#include + +#define TRANSLUCENT 0xe0000000 +#define OPAQUE 0xffffffff + +#define REFRESH_TIMEOUT 0.05 + +#define WIN_MODE_SOLID 0 +#define WIN_MODE_TRANS 1 +#define WIN_MODE_ARGB 2 + +static int render_error; +static int damage_error; +static int xfixes_error; +static bool have_name_pixmap = false; +static int composite_opcode; +static int damage_event; +static bool another_is_running; + + +Atom _XA_NET_WM_WINDOW_OPACITY; +Atom _XA_NET_WM_WINDOW_TYPE; +Atom _XA_NET_WM_WINDOW_TYPE_DESKTOP; +Atom _XA_NET_WM_WINDOW_TYPE_DOCK; +Atom _XA_NET_WM_WINDOW_TYPE_TOOLBAR; +Atom _XA_NET_WM_WINDOW_TYPE_MENU; +Atom _XA_NET_WM_WINDOW_TYPE_UTILITY; +Atom _XA_NET_WM_WINDOW_TYPE_SPLASH; +Atom _XA_NET_WM_WINDOW_TYPE_DIALOG; +Atom _XA_NET_WM_WINDOW_TYPE_NORMAL; + +static char* backgroundProps[] = { + "_XROOTPMAP_ID", + "_XSETROOT_ID", + 0 +}; + +//#define HAVE_NAME_WINDOW_PIXMAP 1 + +struct CWindow { + Window id; +#if HAVE_NAME_WINDOW_PIXMAP + Pixmap pixmap; +#endif + XWindowAttributes attr; + int mode; + int damaged; + Damage damage; + + Picture picture; + Picture picture_alpha; + Picture picture_shadow; + + XserverRegion border_size; + XserverRegion extents; + + Picture shadow; + int shadow_dx; + int shadow_dy; + int shadow_w; + int shadow_h; + unsigned int opacity; + + Atom window_type; + + unsigned long damage_sequence; // sequence when damage was created + + // for drawing translucent windows + XserverRegion border_clip; +}; + +XserverRegion allDamage; +bool fadeWindows = true; +bool clipChanged = false; +bool excludeDockShadows = true; +Picture rootBuffer = 0; +Picture rootPicture = 0; +Picture rootTile = 0; +Picture blackPicture = 0; + +int shadowRadius = 12; +int shadowOffsetX = -15; +int shadowOffsetY = -15; +double shadowOpacity = .75; + +bool hasNamePixmap = true; + +Composite* global_composite; + +void idle_cb(void* c) { + Composite* comp = (Composite*)c; + + comp->paint_all(allDamage); + + allDamage = None; + clipChanged = false; + + Fl::repeat_timeout(REFRESH_TIMEOUT, idle_cb, c); +} + +int xerror_handler(Display* display, XErrorEvent* xev) { + if(xev->request_code == composite_opcode && xev->minor_code == X_CompositeRedirectSubwindows) { + EWARNING(ESTRLOC ": Another composite manager is running\n"); + another_is_running = true; + return 1; + } + + char* name = NULL; + int o = xev->error_code - xfixes_error; + switch(o) { + case BadRegion: + name = "BadRegion"; + break; + default: + break; + } + + o = xev->error_code - damage_error; + switch(o) { + case BadDamage: + name = "BadDamage"; + break; + default: + break; + } + + o = xev->error_code - render_error; + switch(o) { + case BadPictFormat: + name = "BadPictFormat"; + break; + case BadPicture: + name = "BadPicture"; + break; + case BadPictOp: + name = "BadPictOp"; + break; + case BadGlyphSet: + name = "BadGlyphSet"; + break; + case BadGlyph: + name = "BadGlyph"; + break; + default: + break; + } + + EDEBUG(ESTRLOC ": (%s) : error %i request %i minor %i serial %i\n", + (name ? name : "unknown"), xev->error_code, xev->request_code, xev->minor_code, xev->serial); + +#if 0 + char buff[128]; + XGetErrorDatabaseText(fl_display, "XlibMessage", "XError", "", buff, 128); + EDEBUG("%s", buff); + + XGetErrorText(fl_display, xev->error_code, buff, 128); + EDEBUG(" :%s\n", buff); + + XGetErrorDatabaseText(fl_display, "XlibMessage", "MajorCode", "%d", buff, 128); + EDEBUG(" "); + EDEBUG(buff, xev->request_code); + EDEBUG("\n"); + + XGetErrorDatabaseText(fl_display, "XlibMessage", "MinorCode", "%d", buff, 128); + EDEBUG(" "); + EDEBUG(buff, xev->minor_code); + EDEBUG("\n"); + + XGetErrorDatabaseText(fl_display, "XlibMessage", "ResourceID", "%d", buff, 128); + EDEBUG(" "); + EDEBUG(buff, xev->resourceid); + EDEBUG("\n"); +#endif + + return 0; +} + +void set_ignore(unsigned long sequence) { + // TODO +} + +unsigned int get_opacity_property(CWindow* win, unsigned int dflt) { + Atom actual; + int format; + unsigned long n, left; + unsigned char* data = NULL; + + int ret = XGetWindowProperty(fl_display, win->id, _XA_NET_WM_WINDOW_OPACITY, 0L, 1L, False, + XA_CARDINAL, &actual, &format, &n, &left, &data); + + if(ret == Success && data != NULL) { + unsigned int p; + // TODO: replace memcpy call + memcpy(&p, data, sizeof(unsigned int)); + XFree(data); + EDEBUG(":) Opacity for %i = %i\n", win->id, p); + return p; + } + + EDEBUG("Opacity for %i = %i\n", win->id, dflt); + + return dflt; +} + +double get_opacity_percent(CWindow* win, double dflt) { + unsigned int opacity = get_opacity_property(win, (unsigned int)(OPAQUE * dflt)); + return opacity * 1.0 / OPAQUE; +} + +const char* get_window_label(Window win) { + XTextProperty tp; + if(XGetWMName(fl_display, win, &tp) != 0) + return (const char*)tp.value; + else + return "(none)"; +} + +Atom get_window_type_property(Window win) { + Atom actual; + int format; + unsigned long n, left; + unsigned char* data = NULL; + + int ret = XGetWindowProperty(fl_display, win, _XA_NET_WM_WINDOW_TYPE, 0L, 1L, False, + XA_ATOM, &actual, &format, &n, &left, &data); + + if(ret == Success && data != NULL) { + Atom a; + // TODO: replace memcpy call + memcpy(&a, data, sizeof(Atom)); + XFree(data); + return a; + } + + return _XA_NET_WM_WINDOW_TYPE_NORMAL; +} + +Atom determine_window_type(Window win) { + Atom type = get_window_type_property(win); + if(type != _XA_NET_WM_WINDOW_TYPE_NORMAL) + return type; + + // scan children + Window root_return, parent_return; + Window* children = NULL; + unsigned int nchildren; + + if(!XQueryTree(fl_display, win, &root_return, &parent_return, &children, &nchildren)) { + if(children) + XFree(children); + return _XA_NET_WM_WINDOW_TYPE_NORMAL; + } + + for(unsigned int i = 0; i < nchildren; i++) { + type = determine_window_type(children[i]); + if(type != _XA_NET_WM_WINDOW_TYPE_NORMAL) + return type; + } + + if(children) + XFree(children); + return _XA_NET_WM_WINDOW_TYPE_NORMAL; +} + +void add_damage(XserverRegion damage) { + if(allDamage) { + XFixesUnionRegion(fl_display, allDamage, allDamage, damage); + XFixesDestroyRegion(fl_display, damage); + } else + allDamage = damage; +} + +void determine_mode(CWindow* win) { + if(win->picture_alpha) { + XRenderFreePicture(fl_display, win->picture_alpha); + win->picture_alpha = None; + } + + if(win->picture_shadow) { + XRenderFreePicture(fl_display, win->picture_shadow); + win->picture_shadow = None; + } + + XRenderPictFormat* format; + if(get_attributes_class_hack(&win->attr) == InputOnly) + format = 0; + else + format = XRenderFindVisualFormat(fl_display, win->attr.visual); + + if(format && format->type == PictTypeDirect && format->direct.alphaMask) + win->mode = WIN_MODE_ARGB; + else if(win->opacity != OPAQUE) + win->mode = WIN_MODE_TRANS; + else + win->mode = WIN_MODE_SOLID; + + if(win->extents) { + XserverRegion damage = XFixesCreateRegion(fl_display, 0, 0); + XFixesCopyRegion(fl_display, damage, win->extents); + add_damage(damage); + } +} + +// TODO: make this a member +XserverRegion set_border_size(CWindow* win) { + /* + * if window doesn't exist anymore, this will generate an error + * as well as not generate a region. Perhaps a better XFixes + * architecture would be to have a request that copies instead + * of creates, that way you'd just end up with an empty region + * instead of an invalid XID. + */ + set_ignore(NextRequest(fl_display)); + XserverRegion border = XFixesCreateRegionFromWindow(fl_display, win->id, WindowRegionBounding); + + // translate this + set_ignore(NextRequest(fl_display)); + XFixesTranslateRegion(fl_display, border, + win->attr.x + win->attr.border_width, + win->attr.y + win->attr.border_width); + + return border; +} + +// TODO: make this a part of paint_root() +Picture root_tile(void) { + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char* prop; + Pixmap pixmap = None; + Atom pixmap_atom = XInternAtom(fl_display, "PIXMAP", False); + Window root = RootWindow(fl_display, fl_screen); + bool fill; + + for(int i = 0; backgroundProps[i]; i++) { + if(XGetWindowProperty(fl_display, root, XInternAtom(fl_display, backgroundProps[i], False), + 0, 4, False, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success && + actual_type == pixmap_atom && + actual_format == 32 && + nitems == 1) { + memcpy(&pixmap, prop, 4); + XFree(prop); + fill = false; + break; + } + } + + if(!pixmap) { + pixmap = XCreatePixmap(fl_display, root, 1, 1, DefaultDepth(fl_display, fl_screen)); + fill = true; + } + + XRenderPictureAttributes pa; + pa.repeat = True; + + Picture picture = XRenderCreatePicture(fl_display, pixmap, + XRenderFindVisualFormat(fl_display, DefaultVisual(fl_display, fl_screen)), + CPRepeat, &pa); + + if(fill) { + XRenderColor c; + c.red = c.green = c.blue = 0x8080; + c.alpha = 0xffff; + XRenderFillRectangle(fl_display, PictOpSrc, picture, &c, 0, 0, 1, 1); + } + + return picture; +} + +Picture solid_picture(bool argb, double a, double r, double g, double b) { + Pixmap pixmap = XCreatePixmap(fl_display, RootWindow(fl_display, fl_screen), 1, 1, argb ? 32 : 8); + if(!pixmap) + return None; + + XRenderPictureAttributes pa; + pa.repeat = True; + + Picture picture = XRenderCreatePicture(fl_display, pixmap, + XRenderFindStandardFormat(fl_display, argb ? PictStandardARGB32 : PictStandardA8), + CPRepeat, &pa); + + if(!picture) { + XFreePixmap(fl_display, pixmap); + return None; + } + + XRenderColor color; + color.alpha = a * 0xffff; + color.red = r * 0xffff; + color.green = g * 0xffff; + color.blue = b * 0xffff; + + XRenderFillRectangle(fl_display, PictOpSrc, picture, &color, 0, 0, 1, 1); + XFreePixmap(fl_display, pixmap); + return picture; +} + + +Composite::Composite() : manual_redirect(true) { +} + +Composite::~Composite() { + EDEBUG("Composite::~Composite()\n"); +} + +bool Composite::init(void) { + another_is_running = false; + + global_composite = this; + + // set error handler first + XSetErrorHandler(xerror_handler); + + int render_event; + if(!XRenderQueryExtension(fl_display, &render_event, &render_error)) { + EWARNING(ESTRLOC ": No render extension\n"); + return false; + } + + // check Composite extension + int composite_event, composite_error; + if(!XQueryExtension(fl_display, COMPOSITE_NAME, &composite_opcode, &composite_event, &composite_error)) { + EWARNING(ESTRLOC ": No composite extension\n"); + return false; + } + + int composite_major, composite_minor; + XCompositeQueryVersion(fl_display, &composite_major, &composite_minor); + EDEBUG(ESTRLOC ": Setting up XComposite version %i.%i\n", composite_major, composite_minor); + + if(composite_major > 0 || composite_minor >= 2) + have_name_pixmap = true; + + // check XDamage extension + if(!XDamageQueryExtension(fl_display, &damage_event, &damage_error)) { + EWARNING(ESTRLOC ": No damage extension\n"); + return false; + } + + // check XFixes extension + int xfixes_event; + if(!XFixesQueryExtension(fl_display, &xfixes_event, &xfixes_error)) { + EWARNING(ESTRLOC ": No XFixes extension\n"); + return false; + } + + _XA_NET_WM_WINDOW_OPACITY = XInternAtom(fl_display, "_NET_WM_WINDOW_OPACITY", False); + _XA_NET_WM_WINDOW_TYPE = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE", False); + _XA_NET_WM_WINDOW_TYPE_DESKTOP = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE_DESKTOP", False); + _XA_NET_WM_WINDOW_TYPE_DOCK = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE_DOCK", False); + _XA_NET_WM_WINDOW_TYPE_TOOLBAR = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE_TOOLBAR", False); + _XA_NET_WM_WINDOW_TYPE_MENU = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE_MENU", False); + _XA_NET_WM_WINDOW_TYPE_UTILITY = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); + _XA_NET_WM_WINDOW_TYPE_SPLASH = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE_SPLASH", False); + _XA_NET_WM_WINDOW_TYPE_DIALOG = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE_DIALOG", False); + _XA_NET_WM_WINDOW_TYPE_NORMAL = XInternAtom(fl_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); + + allDamage = None; + clipChanged = true; + + XRenderPictureAttributes pa; + pa.subwindow_mode = IncludeInferiors; + rootPicture = XRenderCreatePicture(fl_display, RootWindow(fl_display, fl_screen), + XRenderFindVisualFormat(fl_display, DefaultVisual(fl_display, fl_screen)), CPSubwindowMode, &pa); + + blackPicture = solid_picture(true, 1, 0, 0, 0); + + // now redirect all windows to the Composite + XGrabServer(fl_display); + + /* + * If another manager is running, XCompositeRedirectSubwindows() will report it in xerror_handler(). + * This only works if another-manager/evoke are both set in CompositeRedirectManual mode + */ + if(!manual_redirect) + XCompositeRedirectSubwindows(fl_display, RootWindow(fl_display, fl_screen), CompositeRedirectAutomatic); + else { + XCompositeRedirectSubwindows(fl_display, RootWindow(fl_display, fl_screen), CompositeRedirectManual); +#if 0 + // Commented, since already selected in main evoke code (evoke.cpp). + XSelectInput(fl_display, RootWindow(fl_display, fl_screen), + SubstructureNotifyMask | ExposureMask | StructureNotifyMask | PropertyChangeMask); +#endif + + Window root_ret, parent_ret; + Window* children; + unsigned int nchildren; + + XQueryTree(fl_display, RootWindow(fl_display, fl_screen), &root_ret, &parent_ret, &children, &nchildren); + for(unsigned int i = 0; i < nchildren; i++) + add_window(children[i], i ? children[i-1] : None); + + XFree(children); + } + + XUngrabServer(fl_display); + + /* + * draw all windows now, but only if we manualy manage them + * since window_list will be empty + */ + if(manual_redirect) { + paint_all(None); // XXX: probably not needed since will be called in idle_cb() + Fl::add_timeout(REFRESH_TIMEOUT, idle_cb, this); + } + + return true; +} + +void Composite::add_window(Window id, Window previous) { + CWindow* new_win = new CWindow; + new_win->id = id; + + set_ignore(NextRequest(fl_display)); + + if(!XGetWindowAttributes(fl_display, id, &new_win->attr)) { + delete new_win; + return; + } + + new_win->damaged = 0; +#if HAVE_NAME_WINDOW_PIXMAP + new_win->pixmap = None; +#endif + new_win->picture = None; + + if(get_attributes_class_hack(&(new_win->attr)) == InputOnly) { + new_win->damage_sequence = 0; + new_win->damage = None; + } else { + new_win->damage_sequence = NextRequest(fl_display); + new_win->damage = XDamageCreate(fl_display, id, XDamageReportNonEmpty); + } + + new_win->picture_alpha = None; + new_win->picture_shadow = None; + new_win->border_size = None; + new_win->extents = None; + new_win->shadow = None; + new_win->shadow_dx = None; + new_win->shadow_dy = None; + new_win->shadow_w = None; + new_win->shadow_h = None; + new_win->opacity = get_opacity_property(new_win, OPAQUE); + new_win->border_clip = None; + + new_win->window_type = determine_window_type(new_win->id); + determine_mode(new_win); + + // put window at the top + window_list.push_front(new_win); + + // map it if was mapped before + if(new_win->attr.map_state == IsViewable) + map_window(new_win->id, new_win->damage_sequence - 1, true); +} + +void Composite::map_window(Window id, unsigned long sequence, bool fade) { + CWindow* win = find_window(id); + if(!win) + return; + + win->attr.map_state = IsViewable; + + // this needs to be here or we loose transparency messages + XSelectInput(fl_display, win->id, PropertyChangeMask); + win->damaged = 0; + + /* + * XXX: a hack, not present in xcompmgr due main event + * loop changes + */ + repair_window(win); + + // TODO: fading support +} + +void Composite::unmap_window(Window id, bool fade) { + CWindow* win = find_window(id); + if(!win) + return; + win->attr.map_state = IsUnmapped; +#if HAVE_NAME_WINDOW_PIXMAP + if(win->pixmap && fade && fadeWindows) { + // TODO: fading support + } else +#endif + finish_unmap_window(win); +} + +void Composite::finish_unmap_window(CWindow* win) { + win->damaged = 0; + if(win->extents != None) { + add_damage(win->extents); // destroy region + win->extents = None; + } + +#if HAVE_NAME_WINDOW_PIXMAP + if(win->pixmap) { + XFreePixmap(fl_display, win->pixmap); + win->pixmap = None; + } +#endif + + if(win->picture) { + set_ignore(NextRequest(fl_display)); + XRenderFreePicture(fl_display, win->picture); + win->picture = None; + } + + // don't care about properties anymore + set_ignore(NextRequest(fl_display)); + XSelectInput(fl_display, win->id, 0); + + if(win->border_size) { + set_ignore(NextRequest(fl_display)); + XFixesDestroyRegion(fl_display, win->border_size); + win->border_size = None; + } + + if(win->shadow) { + XRenderFreePicture(fl_display, win->shadow); + win->shadow = None; + } + + if(win->border_clip) { + XFixesDestroyRegion(fl_display, win->border_clip); + win->border_clip = None; + } + + clipChanged = true; +} + +void Composite::configure_window(const XConfigureEvent* ce) { + CWindow* win = find_window(ce->window); + if(!win) { + if(ce->window == RootWindow(fl_display, fl_screen)) { + if(rootBuffer) { + XRenderFreePicture(fl_display, rootBuffer); + rootBuffer = None; + } + + // TODO: here should be updated + // root_width and root_height + } + + return; + } + + XserverRegion damage = None; + + damage = XFixesCreateRegion(fl_display, 0, 0); + if(win->extents != None) + XFixesCopyRegion(fl_display, damage, win->extents); + + win->attr.x = ce->x; + win->attr.y = ce->y; + if(win->attr.width != ce->width || win->attr.height != ce->height) { +#if HAVE_NAME_WINDOW_PIXMAP + if(win->pixmap) { + XFreePixmap(fl_display, win->pixmap); + win->pixmap = None; + if(win->picture) { + XRenderFreePicture(fl_display, win->picture); + win->picture = None; + } + } +#endif + if(win->shadow) { + XRenderFreePicture(fl_display, win->shadow); + win->shadow = None; + } + } + + win->attr.width = ce->width; + win->attr.height = ce->height; + win->attr.border_width = ce->border_width; + win->attr.override_redirect = ce->override_redirect; + +#if 0 + EDEBUG("--- before restack() ---\n"); + CWindowListIter first = window_list.begin(); + for(; first != window_list.end(); ++first) + EDEBUG("0x%x ", (*first)->id); + EDEBUG("\n"); + + EDEBUG("ME: 0x%x ABOVE: 0x%x\n", win->id, ce->above); +#endif + restack_window(win, ce->above); + +#if 0 + EDEBUG("--- after restack() ---\n"); + first = window_list.begin(); + for(; first != window_list.end(); ++first) + EDEBUG("0x%x ", (*first)->id); + EDEBUG("\n"); +#endif + + if(damage) { + XserverRegion extents = window_extents(win); + XFixesUnionRegion(fl_display, damage, damage, extents); + XFixesDestroyRegion(fl_display, extents); + add_damage(damage); + } + + clipChanged = true; +} + +void Composite::restack_window(CWindow* win, Window new_above) { + CWindowListIter it = window_list.begin(), it_end = window_list.end(); + while(it != it_end) { + if(*it == win) + break; + ++it; + } + + Window old_above; + if(it == it_end) // not found + old_above = None; + else { + ++it; // get the next one + old_above = (*it)->id; + } + + if(old_above != new_above) { + --it; // return to our window + EASSERT(*it == win && "Wrong code"); + window_list.erase(it); + /* + it = window_list.begin(); + it_end = window_list.end(); + + // find our window and remove it from current position + while(it != it_end) { + if(*it == win) { + window_list.erase(it); + break; + } + ++it; + }*/ + + it = window_list.begin(); + + // now found new_above and insert our window + while(it != it_end) { + if((*it)->id == new_above) { + break; + } + ++it; + } + // now insert where iterator was stopped (if not found 'new_above') it will insert at the end + window_list.insert(it, win); + } +} + +void Composite::property_notify(const XPropertyEvent* pe) { + for(int i = 0; backgroundProps[i]; i++) { + if(pe->atom == XInternAtom(fl_display, backgroundProps[i], False)) { + if(rootTile) { + XClearArea(fl_display, RootWindow(fl_display, fl_screen), 0, 0, 0, 0, True); + XRenderFreePicture(fl_display, rootTile); + rootTile = None; + break; + } + } + } + + // check if Trans property was changed + if(pe->atom == _XA_NET_WM_WINDOW_OPACITY) { + // reset mode and redraw window + CWindow* win = find_window(pe->window); + if(win) { + // TODO: fading support + win->opacity = get_opacity_property(win, OPAQUE); + determine_mode(win); + if(win->shadow) { + XRenderFreePicture(fl_display, win->shadow); + win->shadow = None; + win->extents = window_extents(win); + } + } + } +} + +#if 0 +static XRectangle* expose_rects = 0; +static int size_expose = 0; +static int n_expose = 0; +#endif + +void Composite::expose_event(const XExposeEvent* ee) { +#if 0 + if(ee->window != RootWindow(fl_display, fl_screen)) + return; + + int more = ee->count + 1; + + if(n_expose == size_expose) { + if(expose_rects) { + expose_rects = (XRectangle*)realloc(expose_rects, (size_expose + more) * sizeof(XRectangle)); + size_expose += more; + } else { + expose_rects = (XRectangle*)malloc(more * sizeof(XRectangle)); + size_expose = more; + } + } + + expose_rects[n_expose].x = ee->x; + expose_rects[n_expose].y = ee->y; + expose_rects[n_expose].width = ee->width; + expose_rects[n_expose].height = ee->height; + n_expose++; + + if(ee->count == 0) { + // expose root + XserverRegion region = XFixesCreateRegion(fl_display, expose_rects, n_expose); + add_damage(region); + n_expose = 0; + } +#endif + + XRectangle rect[1]; + rect[0].x = ee->x; + rect[0].y = ee->y; + rect[0].width = ee->width; + rect[0].height = ee->height; + + XserverRegion region = XFixesCreateRegion(fl_display, rect, 1); + add_damage(region); +} + +void Composite::reparent_notify(const XReparentEvent* re) { + if(re->parent == RootWindow(fl_display, fl_screen)) + add_window(re->window, 0); + else + destroy_window(re->window, false, true); +} + +void Composite::circulate_window(const XCirculateEvent* ce) { + CWindow* win = find_window(ce->window); + if(!win) + return; + + Window new_above; + if(ce->place == PlaceOnTop) + new_above = (*window_list.begin())->id; + else + new_above = None; + restack_window(win, new_above); + clipChanged = true; +} + +CWindow* Composite::find_window(Window id) { + if(window_list.size() == 0) + return NULL; + + CWindowListIter it = window_list.begin(), it_end = window_list.end(); + + while(it != it_end) { + if((*it)->id == id) + return *it; + ++it; + } + + return NULL; +} + +XserverRegion Composite::window_extents(CWindow* win) { + XRectangle r; + r.x = win->attr.x; + r.y = win->attr.y; + r.width = win->attr.width + win->attr.border_width * 2; + r.height = win->attr.height + win->attr.border_width * 2; + + if(!(win->window_type == _XA_NET_WM_WINDOW_TYPE_DOCK && excludeDockShadows)) { + // TODO: check this in xcompmgr since server shadows are not used, only client one + if(win->mode != WIN_MODE_ARGB) { + win->shadow_dx = shadowOffsetX; + win->shadow_dx = shadowOffsetX; + + // make shadow if not already made + if(!win->shadow) { + double opacity = shadowOpacity; + + if(win->mode == WIN_MODE_TRANS) + opacity = opacity * ((double)win->opacity)/((double)OPAQUE); +/* + win->shadow = shadow_picture(opacity, win->picture_alpha, + win->attr.width + win->attr.border_width * 2, + win->attr.height + win->attr.border_width * 2, + &win->shadow_w, &win->shadow_h); +*/ + } + } + + XRectangle sr; + sr.x = win->attr.x + win->shadow_dx; + sr.y = win->attr.y + win->shadow_dy; + sr.width = win->shadow_w; + sr.height = win->shadow_h; + + if(sr.x < r.x) { + r.width = (r.x + r.width) - sr.x; + r.x = sr.x; + } + + if(sr.y < r.y) { + r.height = (r.y + r.height) - sr.y; + r.y = sr.y; + } + + if(sr.x + sr.width > r.x + r.width) + r.width = sr.x + sr.width - r.x; + if(sr.y + sr.height > r.y + r.height) + r.height = sr.y + sr.height - r.y; + } + + return XFixesCreateRegion(fl_display, &r, 1); +} + +void Composite::paint_root(void) { + if(!rootTile) + rootTile = root_tile(); + + XRenderComposite(fl_display, PictOpSrc, rootTile, None, rootBuffer, 0, 0, 0, 0, 0, 0, + DisplayWidth(fl_display, fl_screen), + DisplayHeight(fl_display, fl_screen)); +} + +void Composite::paint_all(XserverRegion region) { + //EDEBUG(ESTRLOC ": PAINT %i windows\n", window_list.size()); + + // if region is None repaint all screen + if(!region) { + XRectangle r; + r.x = 0; + r.y = 0; + // TODO: DisplayWidth/DisplayHeight should be calculated in init() + r.width = DisplayWidth(fl_display, fl_screen); + r.height = DisplayHeight(fl_display, fl_screen); + + region = XFixesCreateRegion(fl_display, &r, 1); + } + + if(!rootBuffer) { + Pixmap root_pix = XCreatePixmap(fl_display, RootWindow(fl_display, fl_screen), + DisplayWidth(fl_display, fl_screen), + DisplayHeight(fl_display, fl_screen), + DefaultDepth(fl_display, fl_screen)); + + rootBuffer = XRenderCreatePicture(fl_display, root_pix, + XRenderFindVisualFormat(fl_display, DefaultVisual(fl_display, fl_screen)), 0, 0); + + XFreePixmap(fl_display, root_pix); + } + + XFixesSetPictureClipRegion(fl_display, rootPicture, 0, 0, region); + + // paint from top to bottom + CWindowListIter it = window_list.begin(), it_end = window_list.end(); + for(; it != it_end; ++it) { + CWindow* win = *it; + + // never painted, ignore it + if(!win->damaged) + continue; + + // invisible, ignore it + if(win->attr.x + win->attr.width < 1 || win->attr.y + win->attr.height < 1 + || win->attr.x >= DisplayWidth(fl_display, fl_screen) + || win->attr.y >= DisplayHeight(fl_display, fl_screen)) { + continue; + } + + if(!win->picture) { + Drawable draw = win->id; +#ifdef HAVE_NAME_WINDOW_PIXMAP + if(hasNamePixmap && !win->pixmap) + win->pixmap = XCompositeNameWindowPixmap(fl_display, win->id); + if(win->pixmap) + draw = win->pixmap; +#endif + XRenderPictureAttributes pa; + XRenderPictFormat* format = XRenderFindVisualFormat(fl_display, win->attr.visual); + + pa.subwindow_mode = IncludeInferiors; + win->picture = XRenderCreatePicture(fl_display, draw, format, CPSubwindowMode, &pa); + } + + if(clipChanged) { + if(win->border_size) { + set_ignore(NextRequest(fl_display)); + XFixesDestroyRegion(fl_display, win->border_size); + win->border_size = None; + } + + if(win->extents) { + XFixesDestroyRegion(fl_display, win->extents); + win->extents = None; + } + + if(win->border_clip) { + XFixesDestroyRegion(fl_display, win->border_clip); + win->border_clip = None; + } + } + + if(!win->border_size) + win->border_size = set_border_size(win); + + if(!win->extents) + win->extents = window_extents(win); + + // EDEBUG("mode :%i\n", win->mode); + + if(win->mode == WIN_MODE_SOLID) { + int x, y, wid, hei; +#if HAVE_NAME_WINDOW_PIXMAP + x = win->attr.x; + y = win->attr.y; + wid = win->attr.width + win->attr.border_width * 2; + hei = win->attr.height + win->attr.border_width * 2; +#else + x = win->attr.x + win->attr.border_width; + y = win->attr.y + win->attr.border_width; + wid = win->attr.width; + hei = win->attr.height; +#endif + XFixesSetPictureClipRegion(fl_display, rootBuffer, 0, 0, region); + set_ignore(NextRequest(fl_display)); + XFixesSubtractRegion(fl_display, region, region, win->border_size); + set_ignore(NextRequest(fl_display)); + + // EDEBUG(ESTRLOC ": Composite on %i x:%i y:%i w:%i h:%i\n", win->id, x, y, wid, hei); + + XRenderComposite(fl_display, PictOpSrc, win->picture, None, rootBuffer, + 0, 0, 0, 0, x, y, wid, hei); + } + + if(!win->border_clip) { + win->border_clip = XFixesCreateRegion(fl_display, 0, 0); + XFixesCopyRegion(fl_display, win->border_clip, region); + } + } + + XFixesSetPictureClipRegion(fl_display, rootBuffer, 0, 0, region); + paint_root(); + + /* + * paint from bottom to top + * use counter since checking only iterator will not pick up first element + */ + it = window_list.end(); + it_end = window_list.begin(); + --it; // decrease iterator so it pointing to the last element + + for(unsigned int i = 0; i < window_list.size(); i++) { + CWindow* win = *it; + + XFixesSetPictureClipRegion(fl_display, rootBuffer, 0, 0, win->border_clip); + + // TODO: server shadows + // go and directly try to draw client shadows + // don't draw them on desktop +#if 0 + if(win->shadow && win->window_type != _XA_NET_WM_WINDOW_TYPE_DESKTOP) { + XRenderComposite(fl_display, PictOpOver, blackPicture, win->shadow, rootBuffer, + 0, 0, 0, 0, + win->attr.x + win->shadow_dx, + win->attr.y + win->shadow_dy, + win->shadow_w, win->shadow_h); + } +#endif + + if(win->opacity != OPAQUE && !win->picture_alpha) + win->picture_alpha = solid_picture(false, (double)win->opacity / OPAQUE, 0, 0, 0); + + // TODO: check this, xcompmgr have the same code for WIN_MODE_TRANS and WIN_MODE_ARGB + if(win->mode == WIN_MODE_TRANS || win->mode == WIN_MODE_ARGB) { + EDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!\n"); + int x, y, wid, hei; +#if HAVE_NAME_WINDOW_PIXMAP + x = win->attr.x; + y = win->attr.y; + wid = win->attr.width + win->attr.border_width * 2; + hei = win->attr.height + win->attr.border_width * 2; +#else + x = win->attr.x + win->attr.border_width; + y = win->attr.y + win->attr.border_width; + wid = win->attr.width; + hei = win->attr.height; +#endif + set_ignore(NextRequest(fl_display)); + XRenderComposite(fl_display, PictOpOver, win->picture, win->picture_alpha, rootBuffer, + 0, 0, 0, 0, + x, y, wid, hei); + } + + // XXX: a lot of errors here ? + if(win->border_clip != None) { + XFixesDestroyRegion(fl_display, win->border_clip); + win->border_clip = None; + } + + // this will assure we catch first element too + if(it != it_end) + --it; + } + + XFixesDestroyRegion(fl_display, region); + + if(rootBuffer != rootPicture) { + XFixesSetPictureClipRegion(fl_display, rootBuffer, 0, 0, None); + XRenderComposite(fl_display, PictOpSrc, rootBuffer, None, rootPicture, + 0, 0, 0, 0, 0, 0, + DisplayWidth(fl_display, fl_screen), + DisplayHeight(fl_display, fl_screen)); + } +} + +void Composite::damage_window(XDamageNotifyEvent* de) { + CWindow* win = find_window(de->drawable); + if(!win) + return; + + repair_window(win); +} + +void Composite::repair_window(CWindow* win) { + XserverRegion parts; + + EDEBUG("Repairing %i (%i) (area: x:%i y:%i w:%i h:%i)\n", win->id, win->damaged, + win->attr.x, + win->attr.y, + win->attr.width, + win->attr.height); + + if(!win->damaged) { + parts = window_extents(win); + set_ignore(NextRequest(fl_display)); + XDamageSubtract(fl_display, win->damage, None, None); + } else { + parts = XFixesCreateRegion(fl_display, 0, 0); + set_ignore(NextRequest(fl_display)); + XDamageSubtract(fl_display, win->damage, None, parts); + + XFixesTranslateRegion(fl_display, parts, + win->attr.x + win->attr.border_width, + win->attr.y + win->attr.border_width); + + // TODO: server shadows + } + + if(parts == None) + EDEBUG("parts == None\n"); + + add_damage(parts); + win->damaged = 1; +} + +void Composite::destroy_window(Window id, bool gone, bool fade) { +#if HAVE_NAME_WINDOW_PIXMAP + CWindow* win = find_window(id); + + if(win && win->pixmap && fade && fadeWindows) { + // TODO: fading support + } else +#endif + finish_destroy_window(id, gone); +} + +void Composite::finish_destroy_window(Window id, bool gone) { + CWindowListIter it = window_list.begin(), it_end = window_list.end(); + + while(it != it_end) { + if((*it)->id == id) { + CWindow* win = *it; + + if(!gone) + finish_unmap_window(win); + if(win->picture) { + set_ignore(NextRequest(fl_display)); + XRenderFreePicture(fl_display, win->picture); + win->picture = None; + } + + if(win->picture_alpha) { + XRenderFreePicture(fl_display, win->picture_alpha); + win->picture_alpha = None; + } + + if(win->picture_shadow) { + XRenderFreePicture(fl_display, win->picture_shadow); + win->picture_shadow = None; + } + + if(win->damage != None) { + set_ignore(NextRequest(fl_display)); + XDamageDestroy(fl_display, win->damage); + win->damage = None; + } + + // TODO: fading support + window_list.erase(it); + delete win; + break; + } + + ++it; + } +} + +void Composite::handle_xevents(const XEvent* xev) { + if(another_is_running || !manual_redirect) + return; + + switch(xev->type) { + case CreateNotify: + EDEBUG(ESTRLOC ": CreateNotify from composite\n"); + add_window(xev->xcreatewindow.window, 0); + break; + case ConfigureNotify: + EDEBUG(ESTRLOC ": ConfigureNotify from composite\n"); + configure_window(&xev->xconfigure); + break; + case DestroyNotify: + EDEBUG(ESTRLOC ": DestroyNotify from composite\n"); + destroy_window(xev->xdestroywindow.window, true, true); + break; + case MapNotify: + EDEBUG(ESTRLOC ": MapNotify from composite\n"); + map_window(xev->xmap.window, xev->xmap.serial, true); + break; + case UnmapNotify: + EDEBUG(ESTRLOC ": UnmapNotify from composite\n"); + unmap_window(xev->xunmap.window, true); + break; + case ReparentNotify: + EDEBUG(ESTRLOC ": ReparentNotify from composite\n"); + reparent_notify(&xev->xreparent); + break; + case CirculateNotify: + EDEBUG(ESTRLOC ": CirculateNotify from composite\n"); + circulate_window(&xev->xcirculate); + break; + case Expose: + EDEBUG(ESTRLOC ": Expose from composite\n"); + expose_event(&xev->xexpose); + break; + case PropertyNotify: + EDEBUG(ESTRLOC ": PropertyNotify from composite\n"); + property_notify(&xev->xproperty); + break; + default: + if(xev->type == damage_event + XDamageNotify) { + EDEBUG("---------> %i <---------\n", damage_event); + damage_window((XDamageNotifyEvent*)xev); + } + break; + } +} diff --git a/evoke/Composite.h b/evoke/Composite.h new file mode 100644 index 0000000..18e69bf --- /dev/null +++ b/evoke/Composite.h @@ -0,0 +1,60 @@ +/* + * $Id$ + * + * Evoke, head honcho of everything + * Part of Equinox Desktop Environment (EDE). + * Based on xcompmgr (c) 2003 Keith Packard. + * Copyright (c) 2000-2007 EDE Authors. + * + * This program is licensed under terms of the + * GNU General Public License version 2 or newer. + * See COPYING for details. + */ + +#ifndef __COMPOSITE_H__ +#define __COMPOSITE_H__ + +#include +#include // XserverRegion already included in Xdamage.h +#include + +struct CWindow; +typedef edelib::list CWindowList; +typedef edelib::list::iterator CWindowListIter; + +class Composite { + private: + bool manual_redirect; + + CWindowList window_list; + void add_window(Window id, Window previous); + void map_window(Window id, unsigned long sequence, bool fade); + void unmap_window(Window id, bool fade); + void finish_unmap_window(CWindow* win); + CWindow* find_window(Window id); + XserverRegion window_extents(CWindow* win); + + void configure_window(const XConfigureEvent* ce); + void restack_window(CWindow* win, Window new_above); + void property_notify(const XPropertyEvent* pe); + void expose_event(const XExposeEvent* ee); + void reparent_notify(const XReparentEvent* re); + void circulate_window(const XCirculateEvent* ce); + + void damage_window(XDamageNotifyEvent* de); + void repair_window(CWindow* win); + void destroy_window(Window id, bool gone, bool fade); + void finish_destroy_window(Window id, bool gone); + + void paint_root(void); + + public: + Composite(); + ~Composite(); + bool init(void); + void handle_xevents(const XEvent* xev); + + void paint_all(XserverRegion region); +}; + +#endif diff --git a/evoke/EvokeService.cpp b/evoke/EvokeService.cpp index 7fcb564..4bafa30 100644 --- a/evoke/EvokeService.cpp +++ b/evoke/EvokeService.cpp @@ -199,7 +199,7 @@ void wake_up_cb(int fd, void* v) { } EvokeService::EvokeService() : - is_running(0), logfile(NULL), xsm(NULL), pidfile(NULL), lockfile(NULL) { + is_running(0), logfile(NULL), xsm(NULL), composite(NULL), pidfile(NULL), lockfile(NULL) { wake_up_pipe[0] = wake_up_pipe[1] = -1; //quit_child_pid = quit_child_ret = -1; @@ -209,6 +209,8 @@ EvokeService::~EvokeService() { if(logfile) delete logfile; + delete composite; + stop_xsettings_manager(true); if(lockfile) { @@ -499,6 +501,15 @@ void EvokeService::stop_xsettings_manager(bool serialize) { xsm = NULL; } +void EvokeService::init_composite(void) { + composite = new Composite(); + + if(!composite->init()) { + delete composite; + composite = NULL; + } +} + void EvokeService::setup_atoms(Display* d) { // with them must be send '1' or property will be ignored (except _EDE_EVOKE_SPAWN) _ede_shutdown_all = XInternAtom(d, "_EDE_EVOKE_SHUTDOWN_ALL", False); @@ -731,6 +742,10 @@ int EvokeService::handle(const XEvent* xev) { EVOKE_LOG("XSETTINGS manager shutdown\n"); stop_xsettings_manager(true); } + + if(composite) + composite->handle_xevents(xev); + #if 0 else if(xev->type == MapNotify) { puts("================="); diff --git a/evoke/EvokeService.h b/evoke/EvokeService.h index 1940e15..2431c61 100644 --- a/evoke/EvokeService.h +++ b/evoke/EvokeService.h @@ -15,6 +15,7 @@ #include "Log.h" #include "Xsm.h" +#include "Composite.h" #include #include @@ -57,6 +58,7 @@ class EvokeService { bool is_running; Log* logfile; Xsm* xsm; + Composite* composite; char* pidfile; char* lockfile; @@ -87,6 +89,8 @@ class EvokeService { void init_xsettings_manager(void); void stop_xsettings_manager(bool serialize); + void init_composite(void); + int handle(const XEvent* ev); Log* log(void) { return logfile; } diff --git a/evoke/Jamfile b/evoke/Jamfile index fe7483e..ce2dea1 100644 --- a/evoke/Jamfile +++ b/evoke/Jamfile @@ -15,6 +15,8 @@ ObjectC++Flags evoke.cpp : -DUSE_SIGHUP ; SOURCE = evoke.cpp EvokeService.cpp + Composite.cpp + ClassHack.c Xsm.cpp Spawn.cpp Splash.cpp @@ -23,11 +25,12 @@ SOURCE = evoke.cpp Crash.cpp Autostart.cpp ; -LinkAgainst evoke : -lao -lvorbis -lvorbisfile ; +LinkAgainst evoke : -lXcomposite -lXdamage -lXfixes -lXrender -lao -lvorbis -lvorbisfile ; #LinkAgainst evoke : -lpthread ; EdeProgram evoke : $(SOURCE) ; FltkProgramBare test/evoke_test : test/evoke_test.cpp ; FltkProgramBare test/stress_test : test/stress_test.cpp ; +FltkProgramBare test/opacity_test : test/opacity_test.cpp ; #TranslationStrings locale : $(SOURCE) ; EdeManual doc/evoke.txt ; diff --git a/evoke/Spawn.cpp b/evoke/Spawn.cpp index e448ddc..1fc2c0c 100644 --- a/evoke/Spawn.cpp +++ b/evoke/Spawn.cpp @@ -125,7 +125,7 @@ int spawn_program(const char* cmd, SignalWatch wf, pid_t* child_pid_ret, const c */ if(child_pid_ret) *child_pid_ret = pid; - + // unblock SIGCHLD sigprocmask(SIG_SETMASK, &old_mask, NULL); diff --git a/evoke/Splash.cpp b/evoke/Splash.cpp index f9ec8d5..ad96ab4 100644 --- a/evoke/Splash.cpp +++ b/evoke/Splash.cpp @@ -241,9 +241,10 @@ bool Splash::next_client(void) { msgbox->copy_label(buff); redraw(); - if(!dry_run) + if(!dry_run) { spawn_program(cmd, service_watcher_cb); //spawn_program(cmd); + } ++clist_it; ++counter; diff --git a/evoke/evoke.cpp b/evoke/evoke.cpp index 8a957b2..b849349 100644 --- a/evoke/evoke.cpp +++ b/evoke/evoke.cpp @@ -175,6 +175,7 @@ int main(int argc, char** argv) { service->setup_atoms(fl_display); service->init_xsettings_manager(); + service->init_composite(); signal(SIGINT, quit_signal); signal(SIGTERM, quit_signal); @@ -193,7 +194,13 @@ int main(int argc, char** argv) { service->start(); +#if 0 XSelectInput(fl_display, RootWindow(fl_display, fl_screen), PropertyChangeMask | SubstructureNotifyMask | ClientMessage); +#endif + + // composite engine included too + XSelectInput(fl_display, RootWindow(fl_display, fl_screen), + SubstructureNotifyMask | ExposureMask | StructureNotifyMask | PropertyChangeMask | ClientMessage); /* * Register event listener and run in infinite loop. Loop will be diff --git a/evoke/test/opacity_test.cpp b/evoke/test/opacity_test.cpp new file mode 100644 index 0000000..58a5c30 --- /dev/null +++ b/evoke/test/opacity_test.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include + +#include + +Fl_Window* win; +Fl_Value_Slider* slider; +Atom opacity_atom; + +void slider_cb(Fl_Widget*, void*) { + printf("callback %i\n", (int)slider->value()); + //int v = (int)slider->value(); + //unsigned int v = (unsigned int)0xe0000000; + unsigned int v = (unsigned int)4; + XChangeProperty(fl_display, fl_xid(win), opacity_atom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&v, 1); + XFlush(fl_display); +} + +int main(void) { + fl_open_display(); + + opacity_atom = XInternAtom(fl_display, "_NET_WM_WINDOW_OPACITY", False); + + win = new Fl_Window(240, 175, "Opacity test"); + win->begin(); + Fl_Box* sbox = new Fl_Box(10, 9, 220, 102, "sample text"); + sbox->box(FL_ENGRAVED_BOX); + sbox->color((Fl_Color)186); + sbox->labelcolor((Fl_Color)23); + + slider = new Fl_Value_Slider(10, 142, 220, 18, "Opacity percent:"); + slider->type(1); + slider->step(1); + slider->maximum(100); + slider->minimum(1); + slider->value(100); + slider->callback(slider_cb); + slider->align(FL_ALIGN_TOP_LEFT); + win->end(); + win->show(); + slider_cb(0,0); + return Fl::run(); +}