/**
 * @file   Screen.cc
 * @author David Reveman <david@waimea.org>
 * @date   25-Jul-2001 23:26:22
 *
 * @brief Implementation of WaScreen and ScreenEdge classes
 *
 * A WaScreen object handles one X server screen. A ScreenEdge is a
 * transperant window placed at the edge of the screen, good to use for
 * virtual screen scrolling.
 *
 * Copyright (C) David Reveman. All rights reserved.
 *
 */

#ifdef    HAVE_CONFIG_H
#  include "../config.h"
#endif // HAVE_CONFIG_H

extern "C" {
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>

#ifdef    RANDR
#  include <X11/extensions/Xrandr.h>
#endif // RANDR

#ifdef    HAVE_STDIO_H
#  include <stdio.h>
#endif // HAVE_STDIO_H

#ifdef    HAVE_UNISTD_H
#  include <sys/types.h>
#  include <sys/wait.h>
#  include <unistd.h>
#endif // HAVE_UNISTD_H

#ifdef    STDC_HEADERS
#  include <stdlib.h>
#endif // STDC_HEADERS

#ifdef    HAVE_SIGNAL_H
#  include <signal.h>
#endif // HAVE_SIGNAL_H
}

#include <iostream>
using std::cerr;
using std::cout;
using std::endl;

#include "Screen.hh"

/**
 * @fn    WaScreen(Display *d, int scrn_number, Waimea *wa)
 * @brief Constructor for WaScreen class
 *
 * Sets root window input mask. Creates new image control object and reads
 * style file. Then we create fonts, colors and renders common images.
 * Last thing we do in this functions is to create WaWindows for all windows
 * that should be managed.
 *
 * @param d The display
 * @param scrn_number Screen to manage
 * @param wa Waimea object
 */
WaScreen::WaScreen(Display *d, int scrn_number, Waimea *wa) :
    WindowObject(0, RootType) {
    Window ro, pa, *children;
    int eventmask, i;
    unsigned int nchild;
    XSetWindowAttributes attrib_set;
    char *__m_wastrdup_tmp;
    
    display = d;
    screen_number = scrn_number;
    id = RootWindow(display, screen_number);
    visual = DefaultVisual(display, screen_number);
    colormap = DefaultColormap(display, screen_number);
    screen_depth = DefaultDepth(display, screen_number);
    width = DisplayWidth(display, screen_number);
    height = DisplayHeight(display, screen_number);
    
    waimea = wa;
    net = waimea->net;
    rh = wa->rh;
    focus = true;
    shutdown = false;

    default_font.xft = false;
    default_font.font = __m_wastrdup("fixed");

    XSync(display, false);
    if (! (pdisplay = XOpenDisplay(wa->options->display))) {
        ERROR << "can't open display: " << wa->options->display << endl;
        exit(1);
    }    
    
#ifdef PIXMAP
    imlib_context = imlib_context_new();
    imlib_context_push(imlib_context);
    imlib_context_set_display(pdisplay);
    imlib_context_set_drawable(RootWindow(pdisplay, screen_number));
    imlib_context_set_colormap(DefaultColormap(pdisplay, screen_number));
    imlib_context_set_visual(DefaultVisual(pdisplay, screen_number));
    imlib_context_set_anti_alias(1);
    imlib_context_pop();
#endif // PIXMAP
    
    eventmask = SubstructureRedirectMask | StructureNotifyMask |
        PropertyChangeMask | ColormapChangeMask | KeyPressMask |
        KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
        EnterWindowMask | LeaveWindowMask | FocusChangeMask;

    sprintf(displaystring, "DISPLAY=%s", DisplayString(display));
    sprintf(displaystring + strlen(displaystring) - 1, "%d", screen_number);

    XSetErrorHandler((XErrorHandler) wmrunningerror);
    XSelectInput(display, id, eventmask);
    XSync(display, false);
    XSync(pdisplay, false);
    XSetErrorHandler((XErrorHandler) xerrorhandler);
    if (waimea->wmerr) {
        cerr << "waimea: warning: another window manager is running on " <<
            displaystring + 8 << endl;
        return;
    }
	
    v_x = v_y = 0;

#ifdef RENDER
    int event_basep, error_basep;
    render_extension =
      XRenderQueryExtension(pdisplay, &event_basep, &error_basep);
#endif // RENDER

#ifdef RANDR
    XRRSelectInput(display, id, RRScreenChangeNotifyMask);
#endif // RANDR

    rh->LoadConfig(this);
    
    current_desktop = new Desktop(0, width, height);
    desktop_list.push_back(current_desktop);
    net->SetWorkarea(this);
    
    for (unsigned int i = 1; config.desktops > i; i++)
        desktop_list.push_back(new Desktop(i, width, height));

    waimea->window_table.insert(make_pair(id, this));
    
    attrib_set.override_redirect = true;
    wm_check = XCreateWindow(display, id, 0, 0, 1, 1, 0,
                             CopyFromParent, InputOnly, CopyFromParent,
                             CWOverrideRedirect, &attrib_set);
    net->SetSupportedWMCheck(this, wm_check);
    net->SetSupported(this);
        
    rh->LoadMenus(this);

    ic = new WaImageControl(pdisplay, this, config.image_dither,
                            config.colors_per_channel, config.cache_max);
    ic->installRootColormap();

    rh->LoadStyle(this);
    rh->LoadActions(this);

    CreateFonts();
    CreateColors();
    RenderCommonImages();
    XDefineCursor(display, id, waimea->session_cursor);
    
    v_xmax = (config.virtual_x - 1) * width;
    v_ymax = (config.virtual_y - 1) * height;
    west = new ScreenEdge(this, 0, 0, 2, height, WEdgeType);
    west->SetActionlist(&config.weacts);
    east = new ScreenEdge(this, width - 2, 0, 2, height, EEdgeType);
    east->SetActionlist(&config.eeacts);
    north = new ScreenEdge(this, 0, 0, width, 2, NEdgeType);
    north->SetActionlist(&config.neacts);
    south = new ScreenEdge(this, 0, height - 2, width, 2, SEdgeType);
    south->SetActionlist(&config.seacts);
    
    net->SetDesktopGeometry(this);
    net->SetNumberOfDesktops(this);
    net->GetCurrentDesktop(this);
    net->SetCurrentDesktop(this);
    net->GetDesktopViewPort(this);
    net->SetDesktopViewPort(this);

#ifdef RENDER
    if (render_extension) {
      net->GetXRootPMapId(this);
      ic->setXRootPMapId((xrootpmap_id)? true: false);
    }
#endif // RENDER

    list<DockStyle *>::iterator dit = wstyle.dockstyles.begin();
    for (; dit != wstyle.dockstyles.end(); ++dit) {
        docks.push_back(new DockappHandler(this, *dit));
    }
    
    window_menu = new WindowMenu();
    wamenu_list.push_back(window_menu);
    
    list<WaMenu *>::iterator mit = wamenu_list.begin();
    for (; mit != wamenu_list.end(); ++mit)
    	(*mit)->Build(this);
    
    XWindowAttributes attr;
    XQueryTree(display, id, &ro, &pa, &children, &nchild);
    for (i = 0; i < (int) nchild; ++i) {
        bool status = false;
        XGrabServer(display);
        if (validatedrawable(id)) {
            XGetWindowAttributes(display, children[i], &attr);
            status = true;
        }
        XUngrabServer(display);
        if (status && (! attr.override_redirect) &&
            (attr.map_state == IsViewable)) {
            if (net->IsSystrayWindow(children[i])) {
                if (! (waimea->FindWin(children[i], SystrayType))) {
                    XGrabServer(display);
                    if (validatedrawable(children[i])) {
                        XSelectInput(display, children[i],
                                     StructureNotifyMask);
                    }
                    XUngrabServer(display);
                    SystrayWindow *stw = new SystrayWindow(children[i], this);
                    waimea->window_table.insert(make_pair(children[i], stw));
                    systray_window_list.push_back(children[i]);
                    net->SetSystrayWindows(this);
                }
                continue;
            }
            XWMHints *wm_hints = NULL;
            XGrabServer(display);
            if (validatedrawable(children[i])) {
                wm_hints = XGetWMHints(display, children[i]);
            }
            XUngrabServer(display);
            if ((wm_hints) && (wm_hints->flags & StateHint) &&
                (wm_hints->initial_state == WithdrawnState)) {
                AddDockapp(children[i]);
            }
            else if ((waimea->window_table.find(children[i]))
                     == waimea->window_table.end()) {
                WaWindow *newwin = new WaWindow(children[i], this);
                if (waimea->FindWin(children[i], WindowType))
                    newwin->net->SetState(newwin, NormalState);
            }
            if (wm_hints) XFree(wm_hints);
        }
    }
    XFree(children);
    net->GetClientListStacking(this);
    net->SetClientList(this);
    net->SetClientListStacking(this);
    net->GetActiveWindow(this);

    actionlist = &config.rootacts;

    delete [] config.style_file;
    delete [] config.action_file;
}

/**
 * @fn    ~WaScreen(void)
 * @brief Destructor for WaScreen class
 *
 * Deletes all created colors and fonts.
 */
WaScreen::~WaScreen(void) {
    shutdown = true;
    XSelectInput(display, id, NoEventMask);
    net->DeleteSupported(this);
    XDestroyWindow(display, wm_check);

    LISTDEL(docks);

    WaWindow **delstack = new WaWindow*[wawindow_list.size()];
    int stackp = 0;

    list<WaWindow *>::reverse_iterator rwit = wawindow_list.rbegin();
    for (; rwit != wawindow_list.rend(); rwit++)
        if ((*rwit)->flags.forcedatbottom)
            delstack[stackp++] = ((WaWindow *) *rwit);
    list<WaWindow *>::iterator wit = wawindow_list_stacking_aab.begin();
    for (; wit != wawindow_list_stacking_aab.end(); wit++)
        delstack[stackp++] = *wit;
    list<WindowObject *>::reverse_iterator rwoit = wa_list_stacking.rbegin();
    for (; rwoit != wa_list_stacking.rend(); rwoit++)
        if ((*rwoit)->type == WindowType)
            delstack[stackp++] = ((WaWindow *) *rwoit);
    rwit = wawindow_list_stacking_aot.rbegin();
    for (; rwit != wawindow_list_stacking_aot.rend(); rwit++)
        delstack[stackp++] = ((WaWindow *) *rwit);

    for (int i = 0; i < stackp; i++)
        delete delstack[i];

    delete [] delstack;
    
    LISTCLEAR(wawindow_list);
    LISTCLEAR(wa_list_stacking);
    LISTCLEAR(wawindow_list_stacking_aab);
    LISTCLEAR(wawindow_list_stacking_aot);
    LISTCLEAR(wawindow_list_map_order);
    LISTCLEAR(always_on_top_list);
    LISTCLEAR(always_at_bottom_list);

    LISTDEL(strut_list);
    
    list<ButtonStyle *>::iterator bit = wstyle.buttonstyles.begin();
    for (; bit != wstyle.buttonstyles.end(); ++bit) {
        if ((*bit)->fg) {            
            XFreeGC(display, (*bit)->g_focused);
            XFreeGC(display, (*bit)->g_unfocused);
            XFreeGC(display, (*bit)->g_pressed);
        }
    }

#ifdef PIXMAP
    imlib_context_free(imlib_context);
#endif // PIXMAP
    
    while (! wstyle.dockstyles.empty()) {
        while (! wstyle.dockstyles.back()->order.empty()) {
            delete wstyle.dockstyles.back()->order.back();
            wstyle.dockstyles.back()->order.pop_back();
            wstyle.dockstyles.back()->order_type.pop_back();
        }
        delete wstyle.dockstyles.back();
        wstyle.dockstyles.pop_back();
    }
    
    ACTLISTCLEAR(config.frameacts);
    ACTLISTCLEAR(config.awinacts);
    ACTLISTCLEAR(config.pwinacts);
    ACTLISTCLEAR(config.titleacts);
    ACTLISTCLEAR(config.labelacts);
    ACTLISTCLEAR(config.handleacts);
    ACTLISTCLEAR(config.rgacts);
    ACTLISTCLEAR(config.lgacts);
    ACTLISTCLEAR(config.rootacts);
    ACTLISTCLEAR(config.weacts);
    ACTLISTCLEAR(config.eeacts);
    ACTLISTCLEAR(config.neacts);
    ACTLISTCLEAR(config.seacts);
    ACTLISTCLEAR(config.mtacts);
    ACTLISTCLEAR(config.miacts);
    ACTLISTCLEAR(config.msacts);
    ACTLISTCLEAR(config.mcbacts);
    int i;
    for (i = 0; i < wstyle.b_num; i++) {
        ACTLISTPTRCLEAR(config.bacts[i]);
        delete config.bacts[i];
    }
    delete [] config.bacts;

    LISTDEL(config.ext_frameacts);
    LISTDEL(config.ext_awinacts);
    LISTDEL(config.ext_pwinacts);
    LISTDEL(config.ext_titleacts);
    LISTDEL(config.ext_labelacts);
    LISTDEL(config.ext_handleacts);
    LISTDEL(config.ext_rgacts);
    LISTDEL(config.ext_lgacts);
    for (i = 0; i < wstyle.b_num; i++) {
        LISTPTRDEL(config.ext_bacts[i]);
        delete config.ext_bacts[i];
    }
    delete [] config.ext_bacts;

    delete west;
    delete east;
    delete north;
    delete south;
    delete ic;

    delete [] config.menu_file;
    delete [] mstyle.bullet;
    delete [] mstyle.checkbox_true;
    delete [] mstyle.checkbox_false;

    LISTDEL(wstyle.buttonstyles);    
    
    XSync(display, false);
    XSync(pdisplay, false);
    XCloseDisplay(pdisplay);
    waimea->window_table.erase(id);
}

/**
 * @fn    WaRaiseWindow(Window win)
 * @brief Raise window
 *
 * Raises a window in the display stack keeping alwaysontop windows, always
 * on top. To update the stacking order so that alwaysontop windows are on top
 * of all other windows we call this function with zero as win argument. 
 *
 * @param win Window to Raise, or 0 for alwaysontop windows update
 */
void WaScreen::WaRaiseWindow(Window win) {
    int i;
    bool in_list = false;
    
    if (always_on_top_list.size() || wawindow_list_stacking_aot.size() ||
        wamenu_list_stacking_aot.size()) {
        Window *stack = new Window[always_on_top_list.size() +
                                  wawindow_list_stacking_aot.size() +
                                  wamenu_list_stacking_aot.size() +
                                  ((win)? 1: 0)];
        
        list<Window>::iterator it = always_on_top_list.begin();
        for (i = 0; it != always_on_top_list.end(); ++it) {
            if (*it == win) in_list = true;
            stack[i++] = *it;
        }
        list<WaMenu *>::iterator mit = wamenu_list_stacking_aot.begin();
        for (; mit != wamenu_list_stacking_aot.end(); ++mit) {
            if ((*mit)->frame == win) in_list = true;
            stack[i++] = (*mit)->frame;
        }
        list<WaWindow *>::iterator wit = wawindow_list_stacking_aot.begin();
        for (; wit != wawindow_list_stacking_aot.end(); ++wit) {
            if ((*wit)->frame->id == win) in_list = true;
            stack[i++] = (*wit)->frame->id;
        }
        if (win && ! in_list) stack[i++] = win;
        
        XRaiseWindow(display, stack[0]);
        XRestackWindows(display, stack, i);
        
        delete [] stack;
    } else
        if (win) {
            XGrabServer(display);
            if (validatedrawable(win))
                XRaiseWindow(display, win);
            XUngrabServer(display);
        }
}

/**
 * @fn    WaLowerWindow(Window win)
 * @brief Lower window
 *
 * Lowers a window in the display stack keeping alwaysatbottom windows, always
 * at bottom.
 *
 * @param win Window to Lower, or 0 for alwaysatbottom windows update
 */
void WaScreen::WaLowerWindow(Window win) {
    int i;
    bool end = false;
    list<WaMenu *>::iterator mit;
    list<WaWindow *>::iterator wit;
    list<WindowObject *>::iterator woit;
    
    Window *stack = new Window[always_on_top_list.size() +
                              wawindow_list_stacking_aot.size() +
                              wamenu_list_stacking_aot.size() +
                              wa_list_stacking.size() +
                              wawindow_list_stacking_aab.size() +
                              wamenu_list_stacking_aab.size() +
                              always_at_bottom_list.size()];
        
    list<Window>::iterator it = always_on_top_list.begin();
    for (i = 0; it != always_on_top_list.end(); ++it) {
        if (*it == win) { end = true; break; } 
        stack[i++] = *it;
    }
    if (! end) {
        mit = wamenu_list_stacking_aot.begin();
        for (; mit != wamenu_list_stacking_aot.end(); ++mit) {
            if ((*mit)->frame == win) { end = true; break; }
            stack[i++] = (*mit)->frame;
        }
    }
    if (! end) {
        wit = wawindow_list_stacking_aot.begin();
        for (; wit != wawindow_list_stacking_aot.end(); ++wit) {
            if ((*wit)->frame->id == win) { end = true; break; }
            stack[i++] = (*wit)->frame->id;
        }
    }
    if (! end) {
        woit = wa_list_stacking.begin();
        for (; woit != wa_list_stacking.end(); ++woit) {
            if ((*woit)->type == WindowType) {
                if (((WaWindow *) (*woit))->frame->id == win) {
                    end = true; break;
                }
                stack[i++] = ((WaWindow *) (*woit))->frame->id;
            }
            else if ((*woit)->type == MenuType) {
                if (((WaMenu *) (*woit))->frame == win) {
                    end = true; break;
                }
                stack[i++] = ((WaMenu *) (*woit))->frame;
            }
        }
    }
    if (! end) {
        wit = wawindow_list_stacking_aab.begin();
        for (; wit != wawindow_list_stacking_aab.end(); ++wit) {
            if ((*wit)->frame->id == win) { end = true; break; }
            stack[i++] = (*wit)->frame->id;
        }
    }
    if (! end) {
        mit = wamenu_list_stacking_aab.begin();
        for (; mit != wamenu_list_stacking_aab.end(); ++mit) {
            if ((*mit)->frame == win) { end = true; break; }
            stack[i++] = (*mit)->frame;
        }
    }
    if (! end) {
        it = always_at_bottom_list.begin();
        for (; it != always_at_bottom_list.end(); ++it) {
            if (*it == win) { end = true; break; }
            stack[i++] = *it;
        }
    }
    XRaiseWindow(display, stack[0]);
    XRestackWindows(display, stack, i);
        
    delete [] stack;
}

/**
 * @fn    UpdateCheckboxes(int type)
 * @brief Updates menu checkboxes
 *
 * Redraws all checkbox menu items to make sure they are synchronized with
 * their given flag.
 *
 * @param type Type of checkboxes to update
 */
void WaScreen::UpdateCheckboxes(int type) {
    list<WaMenuItem *>::iterator miit;

    if (! waimea->eh) return;
    
    list<WaMenu *>::iterator mit = wamenu_list.begin();
    for (; mit != wamenu_list.end(); ++mit) {
        (*mit)->cb_db_upd = false;
        miit = (*mit)->item_list.begin();
        for (; miit != (*mit)->item_list.end(); ++miit) {
            if ((*miit)->cb == type && (*miit)->menu->mapped)
                (*miit)->Render();
        }
        if ((*mit)->cb_db_upd && config.db)
            (*mit)->Render();
    }
}

/**
 * @fn    GetMenuNamed(char *menu)
 * @brief Find a menu
 *
 * Searches through menu list after a menu named as 'menu' parameter.
 *
 * @param menu menu name to use for search
 *
 * @return Pointer to menu object if a menu was found, if no menu was found
 *         NULL is returned
 */
WaMenu *WaScreen::GetMenuNamed(char *menu) {
    WaMenu *dmenu;
    int i;

    if (! menu) return NULL;
   
    list<WaMenu *>::iterator menu_it = wamenu_list.begin();
    for (; menu_it != wamenu_list.end(); ++menu_it)
        if (! strcmp((*menu_it)->name, menu))
            return *menu_it;
            
    for (i = 0; menu[i] != '\0' && menu[i] != '!'; i++);
    if (menu[i] == '!' && menu[i + 1] != '\0') {
        dmenu = CreateDynamicMenu(menu);
        return dmenu;
    }
    
    WARNING << "`" << menu << "' unknown menu" << endl;
    return NULL;
} 

/**
 * @fn    CreateDynamicMenu(char *name)
 * @brief Creates a dynamic menu
 *
 * Executes command line and parses standard out as a menu file.
 *
 * @param name Name of dynamic menu to create
 *
 * @return Created menu, NULL if error occured inmenu parsing
 */
WaMenu *WaScreen::CreateDynamicMenu(char *name) {
    char *tmp_argv[128];
    int m_pipe[2];
    WaMenu *dmenu;
    int pid, status, i;
    char *allocname = NULL;
    char *__m_wastrdup_tmp;

    for (i = 0; name[i] != '\0' && name[i] != '!'; i++);
    if (name[i] == '!' && name[i + 1] != '\0') {
        allocname = __m_wastrdup(&name[i + 1]);
        commandline_to_argv(allocname, tmp_argv);
    } else 
        return NULL;

    if (pipe(m_pipe) < 0) {
        WARNING;
        perror("pipe");
    }
    else {
        struct sigaction action;
        
        action.sa_handler = SIG_DFL;
        action.sa_mask = sigset_t();
        action.sa_flags = 0;
        sigaction(SIGCHLD, &action, NULL);
        pid = fork();
        if (pid == 0) {
            dup2(m_pipe[1], STDOUT_FILENO);
            close(m_pipe[0]);
            close(m_pipe[1]);
            putenv(waimea->pathenv);
            if (execvp(*tmp_argv, tmp_argv) < 0)
                WARNING << *tmp_argv << ": command not found" << endl;
            close(STDOUT_FILENO);
            exit(127);
        }
        close(m_pipe[1]);
        rh->linenr = 0;
        delete [] config.menu_file;
        config.menu_file = new char[strlen(*tmp_argv) + 8];
        sprintf(config.menu_file, "%s:STDOUT", *tmp_argv);
        dmenu = new WaMenu(name);
        dmenu->dynamic = dmenu->dynamic_root = true;
        FILE *fd = fdopen(m_pipe[0], "r");
        dmenu = rh->ParseMenu(dmenu, fd, this);
        fclose(fd);
        if (waitpid(pid, &status, 0) == -1) {
            WARNING; 
            perror("waitpid");
        }
        action.sa_handler = signalhandler;
        action.sa_flags = SA_NOCLDSTOP | SA_NODEFER;
        sigaction(SIGCHLD, &action, NULL);
        if (dmenu != NULL) {
            dmenu->Build(this);
            if (allocname) delete [] allocname;
            
            return dmenu;
        }
    }
    if (allocname) delete [] allocname;
    return NULL;
}

/**
 * @fn    CreateFonts(void)
 * @brief Open fonts
 *
 * Opens all fonts and sets frame height.
 */
void WaScreen::CreateFonts(void) {
    bool set_mih;
    
    if (! mstyle.item_height) set_mih = true;
    else set_mih = false;

    if (default_font.Open(display, screen_number, NULL) == -1) {
        ERROR << "failed loading default font" << endl;
        exit(1);
    }
    
    int height;
    height = wstyle.wa_font.Open(display, screen_number, &default_font);
    if (! wstyle.title_height) wstyle.title_height = height + 4;

    int tmp_sx = wstyle.wa_font_u.shodow_off_x;
    int tmp_sy = wstyle.wa_font_u.shodow_off_y;
    memcpy(&wstyle.wa_font_u, &wstyle.wa_font, sizeof(wstyle.wa_font));
    wstyle.wa_font_u.shodow_off_x = tmp_sx;
    wstyle.wa_font_u.shodow_off_y = tmp_sy;
    
    height = mstyle.wa_f_font.Open(display, screen_number, &default_font);
    if (set_mih) mstyle.item_height = height + 2;

    tmp_sx = mstyle.wa_fh_font.shodow_off_x;
    tmp_sy = mstyle.wa_fh_font.shodow_off_y;
    memcpy(&mstyle.wa_fh_font, &mstyle.wa_f_font, sizeof(mstyle.wa_f_font));
    mstyle.wa_fh_font.shodow_off_x = tmp_sx;
    mstyle.wa_fh_font.shodow_off_y = tmp_sy;

    height = mstyle.wa_b_font.Open(display, screen_number, &default_font);
    if (set_mih && mstyle.item_height < (unsigned int) (height + 2))
        mstyle.item_height = height + 2;

    memcpy(&mstyle.wa_bh_font, &mstyle.wa_b_font, sizeof(mstyle.wa_b_font));
    mstyle.wa_bh_font.shodow_off_x = tmp_sx;
    mstyle.wa_bh_font.shodow_off_y = tmp_sy;

    height = mstyle.wa_ct_font.Open(display, screen_number, &default_font);
    if (set_mih && mstyle.item_height < (unsigned int) (height + 2))
        mstyle.item_height = height + 2;

    memcpy(&mstyle.wa_cth_font, &mstyle.wa_ct_font, sizeof(mstyle.wa_ct_font));
    mstyle.wa_cth_font.shodow_off_x = tmp_sx;
    mstyle.wa_cth_font.shodow_off_y = tmp_sy;

    height = mstyle.wa_cf_font.Open(display, screen_number, &default_font);
    if (set_mih && mstyle.item_height < (unsigned int) (height + 2))
        mstyle.item_height = height + 2;

    memcpy(&mstyle.wa_cfh_font, &mstyle.wa_cf_font, sizeof(mstyle.wa_cf_font));
    mstyle.wa_cfh_font.shodow_off_x = tmp_sx;
    mstyle.wa_cfh_font.shodow_off_y = tmp_sy;

    height = mstyle.wa_t_font.Open(display, screen_number, &default_font);
    if (! mstyle.title_height) mstyle.title_height = height + 2;
    
    if (wstyle.title_height < 10) mstyle.title_height = 10;
    if (mstyle.title_height < 4) mstyle.title_height = 4;
    if (mstyle.item_height < 4) mstyle.item_height = 4;
    
    wstyle.y_pos = (wstyle.title_height / 2 - 2) +
        wstyle.wa_font.diff / 2 + wstyle.wa_font.diff % 2;
    mstyle.f_y_pos = (mstyle.item_height / 2) +
        mstyle.wa_f_font.diff / 2 + mstyle.wa_f_font.diff % 2;
    mstyle.t_y_pos = (mstyle.title_height / 2) +
        mstyle.wa_t_font.diff / 2 + mstyle.wa_t_font.diff % 2;
    mstyle.b_y_pos = (mstyle.item_height / 2) +
        mstyle.wa_b_font.diff / 2 + mstyle.wa_b_font.diff % 2;
    mstyle.ct_y_pos = (mstyle.item_height / 2) +
        mstyle.wa_ct_font.diff / 2 + mstyle.wa_ct_font.diff % 2;
    mstyle.cf_y_pos = (mstyle.item_height / 2) +
        mstyle.wa_cf_font.diff / 2 + mstyle.wa_cf_font.diff % 2;
}

/**
 * @fn    CreateColors(void)
 * @brief Creates all colors
 *
 * Creates all color GCs.
 */
void WaScreen::CreateColors(void) {
    XGCValues gcv;
    
    list<ButtonStyle *>::iterator bit = wstyle.buttonstyles.begin();
    for (; bit != wstyle.buttonstyles.end(); ++bit) {
        if ((*bit)->fg) {
            gcv.foreground = (*bit)->c_focused.getPixel();
            (*bit)->g_focused = XCreateGC(display, id, GCForeground, &gcv);
            gcv.foreground = (*bit)->c_unfocused.getPixel();
            (*bit)->g_unfocused = XCreateGC(display, id, GCForeground, &gcv);
            gcv.foreground = (*bit)->c_pressed.getPixel();
            (*bit)->g_pressed = XCreateGC(display, id, GCForeground, &gcv);
            gcv.foreground = (*bit)->c_focused2.getPixel();
            (*bit)->g_focused2 = XCreateGC(display, id, GCForeground, &gcv);
            gcv.foreground = (*bit)->c_unfocused2.getPixel();
            (*bit)->g_unfocused2 = XCreateGC(display, id, GCForeground, &gcv);
            gcv.foreground = (*bit)->c_pressed2.getPixel();
            (*bit)->g_pressed2 = XCreateGC(display, id, GCForeground, &gcv);
        }
    }
    wstyle.wa_font.AllocColor(display, id, &wstyle.l_text_focus,
                              &wstyle.l_text_focus_s);
    wstyle.wa_font_u.AllocColor(display, id, &wstyle.l_text_unfocus,
                                &wstyle.l_text_unfocus_s);

    mstyle.wa_t_font.AllocColor(display, id, &mstyle.t_text,
                                &mstyle.t_text_s);
    
    mstyle.wa_f_font.AllocColor(display, id, &mstyle.f_text,
                                &mstyle.f_text_s);
    mstyle.wa_fh_font.AllocColor(display, id, &mstyle.f_hilite_text,
                                 &mstyle.f_hilite_text_s);

    mstyle.wa_b_font.AllocColor(display, id, &mstyle.f_text,
                                &mstyle.f_text_s);
    mstyle.wa_bh_font.AllocColor(display, id, &mstyle.f_hilite_text,
                                 &mstyle.f_hilite_text_s);

    mstyle.wa_ct_font.AllocColor(display, id, &mstyle.f_text,
                                 &mstyle.f_text_s);
    mstyle.wa_cth_font.AllocColor(display, id, &mstyle.f_hilite_text,
                                  &mstyle.f_hilite_text_s);

    mstyle.wa_cf_font.AllocColor(display, id, &mstyle.f_text,
                                 &mstyle.f_text_s);
    mstyle.wa_cfh_font.AllocColor(display, id, &mstyle.f_hilite_text,
                                  &mstyle.f_hilite_text_s);
}

/**
 * @fn    RenderCommonImages(void)
 * @brief Render common images
 *
 * Render images which are common for all windows.
 */
void WaScreen::RenderCommonImages(void) {
    WaTexture *texture;
    list<ButtonStyle *>::iterator bit = wstyle.buttonstyles.begin();
    for (; bit != wstyle.buttonstyles.end(); ++bit) {
        texture = &(*bit)->t_focused;
        if (texture->getTexture() == (WaImage_Flat | WaImage_Solid)) {
            (*bit)->p_focused = None;
            (*bit)->c_focused = texture->getColor()->getPixel();
        } else
            (*bit)->p_focused = ic->renderImage(wstyle.title_height - 4,
                                                wstyle.title_height - 4,
                                                texture);

        texture = &(*bit)->t_unfocused;
        if (texture->getTexture() == (WaImage_Flat | WaImage_Solid)) {
            (*bit)->p_unfocused = None;
            (*bit)->c_unfocused = texture->getColor()->getPixel();
        } else
            (*bit)->p_unfocused = ic->renderImage(wstyle.title_height - 4,
                                                  wstyle.title_height - 4,
                                                  texture);

        texture = &(*bit)->t_pressed;
        if (texture->getTexture() == (WaImage_Flat | WaImage_Solid)) {
            (*bit)->p_pressed = None;
            (*bit)->c_pressed = texture->getColor()->getPixel();
        } else
            (*bit)->p_pressed = ic->renderImage(wstyle.title_height - 4,
                                                wstyle.title_height - 4,
                                                texture);
        
        texture = &(*bit)->t_focused2;
        if (texture->getTexture() == (WaImage_Flat | WaImage_Solid)) {
            (*bit)->p_focused2 = None;
            (*bit)->c_focused2 = texture->getColor()->getPixel();
        } else
            (*bit)->p_focused2 = ic->renderImage(wstyle.title_height - 4,
                                                 wstyle.title_height - 4,
                                                 texture);

        texture = &(*bit)->t_unfocused2;
        if (texture->getTexture() == (WaImage_Flat | WaImage_Solid)) {
            (*bit)->p_unfocused2 = None;
            (*bit)->c_unfocused2 = texture->getColor()->getPixel();
        } else
            (*bit)->p_unfocused2 = ic->renderImage(wstyle.title_height - 4,
                                                   wstyle.title_height - 4,
                                                   texture);

        texture = &(*bit)->t_pressed2;
        if (texture->getTexture() == (WaImage_Flat | WaImage_Solid)) {
            (*bit)->p_pressed2 = None;
            (*bit)->c_pressed2 = texture->getColor()->getPixel();
        } else
            (*bit)->p_pressed2 = ic->renderImage(wstyle.title_height - 4,
                                                 wstyle.title_height - 4,
                                                 texture);
    }
    
    texture = &wstyle.g_focus;
    if (texture->getTexture() == (WaImage_Flat | WaImage_Solid)) {
        fgrip = None;
        fgrip_pixel = texture->getColor()->getPixel();
    } else
        fgrip = ic->renderImage(25, wstyle.handle_width, texture);

    texture = &wstyle.g_unfocus;
    if (texture->getTexture() == (WaImage_Flat | WaImage_Solid)) {
        ugrip = None;
        ugrip_pixel = texture->getColor()->getPixel();
    } else
        ugrip = ic->renderImage(25, wstyle.handle_width, texture);
}

/**
 * @fn    UpdateWorkarea(void)
 * @brief Update workarea
 *
 * Updates workarea, all maximized windows are maximizied to the new
 * workarea.
 */
void WaScreen::UpdateWorkarea(void) {
    int old_x = current_desktop->workarea.x,
        old_y = current_desktop->workarea.y,
        old_width = current_desktop->workarea.width,
        old_height = current_desktop->workarea.height;
    
    current_desktop->workarea.x = current_desktop->workarea.y = 0;
    current_desktop->workarea.width = width;
    current_desktop->workarea.height = height;

    list<WMstrut *>::iterator it = strut_list.begin();
    for (; it != strut_list.end(); ++it) {
        WindowObject *wo = waimea->FindWin((*it)->window,
                                           WindowType | DockHandlerType);
        if (wo) {
            if (wo->type == WindowType) {
                if (! (((WaWindow *) wo)->desktop_mask &
                       (1L << current_desktop->number)))
                    continue;
            } else if (wo->type == DockHandlerType) {
                if (! (((DockappHandler *) wo)->style->desktop_mask &
                       (1L << current_desktop->number)))
                    continue;
            }
        } else
            continue;
        
        if ((*it)->left > current_desktop->workarea.x)
            current_desktop->workarea.x = (*it)->left;
        if ((*it)->top > current_desktop->workarea.y)
            current_desktop->workarea.y = (*it)->top;
        if ((width - (*it)->right) < current_desktop->workarea.width)
            current_desktop->workarea.width = width - (*it)->right;
        if ((height - (*it)->bottom) < current_desktop->workarea.height)
            current_desktop->workarea.height = height - (*it)->bottom;
    }
    current_desktop->workarea.width = current_desktop->workarea.width -
        current_desktop->workarea.x;
    current_desktop->workarea.height = current_desktop->workarea.height -
        current_desktop->workarea.y;
    
    int res_x, res_y, res_w, res_h;
    if (old_x != current_desktop->workarea.x ||
        old_y != current_desktop->workarea.y ||
        old_width != current_desktop->workarea.width ||
        old_height != current_desktop->workarea.height) {
        net->SetWorkarea(this);
        
        list<WaWindow *>::iterator wa_it = wawindow_list.begin();
        for (; wa_it != wawindow_list.end(); ++wa_it) {
            if (! ((*wa_it)->desktop_mask &
                   (1L << current_desktop->number))) break;
            if ((*wa_it)->flags.max) {
                (*wa_it)->flags.max = false;
                res_x = (*wa_it)->restore_max.x;
                res_y = (*wa_it)->restore_max.y;
                res_w = (*wa_it)->restore_max.width;
                res_h = (*wa_it)->restore_max.height;
                (*wa_it)->_Maximize((*wa_it)->restore_max.misc0,
                                    (*wa_it)->restore_max.misc1);
                (*wa_it)->restore_max.x = res_x;
                (*wa_it)->restore_max.y = res_y;
                (*wa_it)->restore_max.width = res_w;
                (*wa_it)->restore_max.height = res_h;
            }
        }
    }
}

/**
 * @fn    GetWorkareaSize(int *x, int *y, int *w, int *h)
 * @brief Calculates real workarea size
 *
 * Returns the real workarea size. Calculated from the xinerama area where the
 * pointer is currently located and the current desktops workarea. If xinerama
 * isn't used then the real workarea is the current desktops workarea.
 *
 * @param x Returns the real workarea X value
 * @param y Returns the workarea Y value
 * @param w Returns the workarea width value
 * @param h Returns the workarea height value
 */
void WaScreen::GetWorkareaSize(int *x, int *y, int *w, int *h) {
    *x = current_desktop->workarea.x;
    *y = current_desktop->workarea.y;
    *w = current_desktop->workarea.width;
    *h = current_desktop->workarea.height;
    
#ifdef XINERAMA
    Window win;
    int px, py, i;
    unsigned int ui;

    if (waimea->xinerama && waimea->xinerama_info) {
        XQueryPointer(display, id, &win, &win, &px, &py, &i, &i, &ui);
        for (i = 0; i < waimea->xinerama_info_num; ++i) {
            if (px > waimea->xinerama_info[i].x_org &&
                px < (waimea->xinerama_info[i].x_org +
                      waimea->xinerama_info[i].width) &&
                py > waimea->xinerama_info[i].y_org &&
                py < (waimea->xinerama_info[i].y_org +
                      waimea->xinerama_info[i].height)) {
                int diff = waimea->xinerama_info[i].x_org - *x;
                int xt = waimea->xinerama_info[i].width;
                if (diff > 0) {
                    *w -= diff;
                    *x = waimea->xinerama_info[i].x_org;
                } else xt += diff;
                if (*w > xt) *w = xt;

                diff = waimea->xinerama_info[i].y_org - *y;
                xt = waimea->xinerama_info[i].height;
                if (diff > 0) {
                    *h -= diff;
                    *y = waimea->xinerama_info[i].y_org;
                } else xt += diff;
                if (*h > xt) *h = xt;
                break;
            }
        }
    }
#endif // XINERAMA
    
}

/**
 * @fn    MoveViewportTo(int x, int y)
 * @brief Move viewport to position
 *
 * Moves the virtual viewport to position (x, y). This is done by moving all
 * windows relative to the viewport change.
 *
 * @param x New x viewport
 * @param y New y viewport
 */
void WaScreen::MoveViewportTo(int x, int y) {
    if (x > v_xmax) x = v_xmax;
    else if (x < 0) x = 0;
    if (y > v_ymax) y = v_ymax;
    else if (y < 0) y = 0;
    
    int x_move = - (x - v_x);
    int y_move = - (y - v_y);
    v_x = x;
    v_y = y;

    list<WaWindow *>::iterator it = wawindow_list.begin();
    for (; it != wawindow_list.end(); ++it) {
        if (! (*it)->flags.sticky) {
            int old_x = (*it)->attrib.x;
            int old_y = (*it)->attrib.y;
            (*it)->attrib.x = (*it)->attrib.x + x_move;
            (*it)->attrib.y = (*it)->attrib.y + y_move;
            
            if ((((*it)->attrib.x + (*it)->attrib.width) > 0 &&
                 (*it)->attrib.x < width) && 
                (((*it)->attrib.y + (*it)->attrib.height) > 0 &&
                 (*it)->attrib.y < height))
                (*it)->RedrawWindow(true);
            else {
                if (((old_x + (*it)->attrib.width) > 0 && old_x < width) && 
                    ((old_y + (*it)->attrib.height) > 0 && old_y < height))
                    (*it)->RedrawWindow();
                else {
                    (*it)->dontsend = true;
                    (*it)->RedrawWindow();
                    (*it)->dontsend = false;
                    net->SetVirtualPos(*it);
                }
            }
        }
    }
    list<WaMenu *>::iterator it2 = wamenu_list.begin();
    for (; it2 != wamenu_list.end(); ++it2) {
        if ((*it2)->mapped && (! (*it2)->root_menu))
            (*it2)->Move(x_move, y_move);
    }
    net->SetDesktopViewPort(this);
}

/**
 * @fn    MoveViewport(int direction)
 * @brief Move viewport one screen in specified direction
 *
 * Moves viewport one screen in direction specified by direction parameter.
 * Direction can be one of WestDirection, EastDirection, NorthDirection and
 * SouthDirection.
 *
 * @param direction Direction to move viewport
 */
void WaScreen::MoveViewport(int direction) {
    int vd;
    
    switch (direction) {
        case WestDirection:
            if (v_x > 0) {
                if ((v_x - width) < 0) vd = v_x;
                else vd = width;
                XWarpPointer(display, None, None, 0, 0, 0, 0, vd - 6, 0);
                MoveViewportTo(v_x - vd, v_y);
            }
            break;
        case EastDirection:
            if (v_x < v_xmax) {
                if ((v_x + width) > v_xmax) vd = v_xmax - v_x;
                else vd = width;
                XWarpPointer(display, None, None, 0, 0, 0, 0, 6 - vd, 0);
                MoveViewportTo(v_x + vd, v_y);
            }
            break;
        case NorthDirection:
            if (v_y > 0) {
                if ((v_y - height) < 0) vd = v_y;
                else vd = height;
                XWarpPointer(display, None, None, 0, 0, 0, 0, 0, vd - 6);
                MoveViewportTo(v_x, v_y - vd);
            }
            break;
        case SouthDirection:
            if (v_y < v_ymax) {
                if ((v_y + height) > v_ymax) vd = v_ymax - v_y;
                else vd = height;
                XWarpPointer(display, None, None, 0, 0, 0, 0, 0, 6 - vd);
                MoveViewportTo(v_x, v_y + vd);
            }
    }
}

/** 
 * @fn    ViewportFixedMove(XEvent *, WaAction *ac)
 * @brief Fixed viewport move
 *
 * Parses the action parameter as an X offset string. The X and Y
 * offset values defines the fixed pixel position to move the
 * viewport to.
 *
 * @param ac WaAction object
 */ 
void WaScreen::ViewportFixedMove(XEvent *, WaAction *ac) {
    int x, y, mask; 
    unsigned int w = 0, h = 0;
                            
    if (! ac->param) return;
   
    mask = XParseGeometry(ac->param, &x, &y, &w, &h);
    if (mask & XNegative) x = v_xmax + x;
    if (mask & YNegative) y = v_ymax + y;
    MoveViewportTo(x, y); 
}

/**
 * @fn    ViewportRelativeMove(XEvent *, WaAction *ac)
 * @brief Relative viewport move
 *
 * Parses the action parameter as an X offset string. The X and Y
 * offset values defines the number of pixels to move the viewport.
 *
 * @param ac WaAction object
 */
void WaScreen::ViewportRelativeMove(XEvent *, WaAction *ac) {
    int x, y, mask;
    unsigned int w = 0, h = 0;
                  
    if (! ac->param) return;
   
    mask = XParseGeometry(ac->param, &x, &y, &w, &h);
    MoveViewportTo(v_x + x, v_y + y);
}

/**
 * @fn    ViewportMove(XEvent *e, WaAction *)
 * @brief Move viewport after mouse movement
 *
 * Moves viewport after mouse motion events.
 *
 * @param e XEvent causing function call
 */
void WaScreen::ViewportMove(XEvent *e, WaAction *) {
    XEvent event;
    int px, py, i;
    list<XEvent *> *maprequest_list;
    Window w;
    unsigned int ui;
    list<WaWindow *>::iterator it;

    if (waimea->eh->move_resize != EndMoveResizeType) return;
    waimea->eh->move_resize = MoveOpaqueType;
    
    XQueryPointer(display, id, &w, &w, &px, &py, &i, &i, &ui);
    
    maprequest_list = new list<XEvent *>;
    XGrabPointer(display, id, true, ButtonReleaseMask | ButtonPressMask |
                 PointerMotionMask | EnterWindowMask | LeaveWindowMask,
                 GrabModeAsync, GrabModeAsync, None, waimea->move_cursor,
                 CurrentTime);
    XGrabKeyboard(display, id, true, GrabModeAsync, GrabModeAsync,
                  CurrentTime);
    for (;;) {
        it = wawindow_list.begin();
        for (; it != wawindow_list.end(); ++it) {
            (*it)->dontsend = true;
        }
        waimea->eh->EventLoop(waimea->eh->menu_viewport_move_return_mask,
                              &event);
        switch (event.type) {
            case MotionNotify: {
                while (XCheckTypedWindowEvent(display, event.xmotion.window,
                                              MotionNotify, &event));
                int x = v_x - (event.xmotion.x_root - px);
                int y = v_y - (event.xmotion.y_root - py);
                
                if (x > v_xmax) x = v_xmax;
                else if (x < 0) x = 0;
                if (y > v_ymax) y = v_ymax;
                else if (y < 0) y = 0;
                
                int x_move = - (x - v_x);
                int y_move = - (y - v_y);
                v_x = x;
                v_y = y;

                list<WaWindow *>::iterator it = wawindow_list.begin();
                for (; it != wawindow_list.end(); ++it) {
                    if (! (*it)->flags.sticky) {
                        int old_x = (*it)->attrib.x;
                        int old_y = (*it)->attrib.y;
                        (*it)->attrib.x = (*it)->attrib.x + x_move;
                        (*it)->attrib.y = (*it)->attrib.y + y_move;
            
                        if ((((*it)->attrib.x + (*it)->attrib.width) > 0 &&
                             (*it)->attrib.x < width) && 
                            (((*it)->attrib.y + (*it)->attrib.height) > 0 &&
                             (*it)->attrib.y < height))
                            (*it)->RedrawWindow();
                        else {
                            if (((old_x + (*it)->attrib.width) > 0 &&
                                 old_x < width) && 
                                ((old_y + (*it)->attrib.height) > 0 &&
                                 old_y < height))
                                (*it)->RedrawWindow();
                        }
                    }
                }
                list<WaMenu *>::iterator it2 = wamenu_list.begin();
                for (; it2 != wamenu_list.end(); ++it2) {
                    if ((*it2)->mapped && (! (*it2)->root_menu))
                        (*it2)->Move(x_move, y_move

#ifdef RENDER
                                     , !config.lazy_trans
#endif // RENDER

                                     );
                }
                px = event.xmotion.x_root;
                py = event.xmotion.y_root;
            } break;
            case LeaveNotify:
            case EnterNotify:
                break;
            case MapRequest:
                maprequest_list->push_front(&event); break;
            case ButtonPress:
            case ButtonRelease:
                event.xbutton.window = id;
            case KeyPress:
            case KeyRelease:
                if (event.type == KeyPress || event.type == KeyRelease)
                    event.xkey.window = id;
                waimea->eh->HandleEvent(&event);
                if (waimea->eh->move_resize != EndMoveResizeType) break;
                while (! maprequest_list->empty()) {
                    XPutBackEvent(display, maprequest_list->front());
                    maprequest_list->pop_front();
                }
                delete maprequest_list;
                it = wawindow_list.begin();
                for (; it != wawindow_list.end(); ++it) {
                    (*it)->dontsend = false;
                    net->SetVirtualPos(*it);
                    if ((((*it)->attrib.x + (*it)->attrib.width) > 0 &&
                         (*it)->attrib.x < width) && 
                        (((*it)->attrib.y + (*it)->attrib.height) > 0 &&
                         (*it)->attrib.y < height)) {
                        
#ifdef RENDER
                        if (config.lazy_trans) {
                            (*it)->render_if_opacity = true;
                            (*it)->DrawTitlebar();
                            (*it)->DrawHandlebar();
                            (*it)->render_if_opacity = false;
                        }
#endif // RENDER

                        (*it)->SendConfig();
                    }
                }

#ifdef RENDER
                if (config.lazy_trans) {
                    list<WaMenu *>::iterator it2 = wamenu_list.begin();
                    for (; it2 != wamenu_list.end(); ++it2) {
                        if ((*it2)->mapped && (! (*it2)->root_menu))
                            (*it2)->Move(0, 0, true);
                    }
                }
#endif // RENDER

                net->SetDesktopViewPort(this);
                XUngrabKeyboard(display, CurrentTime);
                XUngrabPointer(display, CurrentTime);
                return;
        }
    }
}

/**
 * @fn    EndMoveResize(XEvent *e, WaAction *)
 * @brief Ends move
 *
 * Ends viewport moving process.
 */
void WaScreen::EndMoveResize(XEvent *, WaAction *) {
    waimea->eh->move_resize = EndMoveResizeType;
}

/**
 * @fn    Focus(XEvent *, WaAction *)
 * @brief Set input focus to root image
 *
 * Sets the keyboard input focus to the WaScreens root window.
 */
void WaScreen::Focus(XEvent *, WaAction *) {
    focus = true;
    XSetInputFocus(display, id, RevertToPointerRoot, CurrentTime);
}

/**
 * @fn    MenuMap(XEvent *e, WaAction *ac)
 * @brief Maps a menu
 *
 * Maps a menu at the current mouse position.
 *
 * @param ac WaAction object
 * @param bool True if we should focus first item in menu
 */
void WaScreen::MenuMap(XEvent *, WaAction *ac, bool focus) {
    Window w;
    int i, x, y;
    unsigned int ui;
    WaMenu *menu = GetMenuNamed(ac->param);

    if (! menu) return;
    if (waimea->eh->move_resize != EndMoveResizeType) return;

    int workx, worky, workw, workh;
    GetWorkareaSize(&workx, &worky, &workw, &workh);

    if (XQueryPointer(display, id, &w, &w, &x, &y, &i, &i, &ui)) {
        if (menu->tasksw) menu->Build(this);
        menu->rf = this;
        menu->ftype = MenuRFuncMask;
        if ((y + menu->height + mstyle.border_width * 2) >
            (unsigned int) (workh + worky))
           y -= (menu->height + mstyle.border_width * 2);
        if ((x + menu->width + mstyle.border_width * 2) >
            (unsigned int) (workw + workx))
            x -= (menu->width + mstyle.border_width * 2);
        menu->Map(x, y);
        if (focus) menu->FocusFirst();
    }
}

/**
 * @fn    MenuRemap(XEvent *e, WaAction *ac)
 * @brief Maps a menu
 *
 * Remaps a menu at the current mouse position.
 *
 * @param ac WaAction object
 * @param bool True if we should focus first item in menu
 */
void WaScreen::MenuRemap(XEvent *, WaAction *ac, bool focus) {
    Window w;
    int i, x, y;
    unsigned int ui;
    WaMenu *menu = GetMenuNamed(ac->param);

    if (! menu) return;
    if (menu->dynamic && menu->mapped) {
        menu->Unmap(menu->has_focus);
        if (! (menu = CreateDynamicMenu(ac->param))) return;
    }
    if (waimea->eh->move_resize != EndMoveResizeType) return;

    int workx, worky, workw, workh;
    GetWorkareaSize(&workx, &worky, &workw, &workh);
    
    if (XQueryPointer(display, id, &w, &w, &x, &y, &i, &i, &ui)) {
        if (menu->tasksw) menu->Build(this);
        menu->rf = this;
        menu->ftype = MenuRFuncMask;
        if ((y + menu->height + mstyle.border_width * 2) >
            (unsigned int) (workh + worky))
           y -= (menu->height + mstyle.border_width * 2);
        if ((x + menu->width + mstyle.border_width * 2) >
            (unsigned int) (workw + workx))
            x -= (menu->width + mstyle.border_width * 2);
        menu->ignore = true;
        menu->ReMap(x, y);
        menu->ignore = false;
        if (focus) menu->FocusFirst();
    }
}

/**
 * @fn    MenuUnmap(XEvent *, WaAction *ac, bool focus)
 * @brief Unmaps menu
 *
 * Unmaps a menu and its linked submenus.
 *
 * @param ac WaAction object
 * @param bool True if we should focus root item
 */
void WaScreen::MenuUnmap(XEvent *, WaAction *ac, bool focus) {
    WaMenu *menu = GetMenuNamed(ac->param);

    if (! menu) return;
    if (waimea->eh->move_resize != EndMoveResizeType) return;
    
    menu->Unmap(focus);
    menu->UnmapSubmenus(focus);
}

/**
 * @fn    Restart(XEvent *, WaAction *)
 * @brief Restarts window manager
 *
 * Restarts window manager by deleting all objects and executing the program
 * file again.
 *
 * @param ac WaAction object
 */
void WaScreen::Restart(XEvent *, WaAction *ac) {
    if (ac->param)
        restart(ac->param);
    else
        restart(NULL);
}

/**
 * @fn    Exit(XEvent *, WaAction *)
 * @brief Shutdowns window manager
 *
 * Shutdowns window manager. Returning successful status.
 */
void WaScreen::Exit(XEvent *, WaAction *) {
    quit(EXIT_SUCCESS);
}

/**
 * @fn    TaskSwitcher(XEvent *, WaAction *)
 * @brief Maps task switcher menu
 *
 * Maps task switcher menu at middle of screen and sets input focus to
 * first window in list.
 */
void WaScreen::TaskSwitcher(XEvent *, WaAction *) {
    if (waimea->eh->move_resize != EndMoveResizeType) return;
    if (wawindow_list.empty()) return;

    int workx, worky, workw, workh;
    GetWorkareaSize(&workx, &worky, &workw, &workh);
    
    window_menu->Build(this);
    window_menu->ReMap(workx + (workw / 2 - window_menu->width / 2),
                       worky + (workh / 2 - window_menu->height / 2));
    window_menu->FocusFirst();
}

/**
 * @fn    PreviousTask(XEvent *e, WaAction *ac)
 * @brief Switches to previous task
 *
 * Switches to the previous focused window.
 *
 * @param e X event that have occurred
 * @param ac WaAction object
 */
void WaScreen::PreviousTask(XEvent *e, WaAction *ac) {
    if (waimea->eh->move_resize != EndMoveResizeType) return;
    if (wawindow_list.size() < 2) return;
    
    list<WaWindow *>::iterator it = wawindow_list.begin();
    it++;
    (*it)->Raise(e, ac);
    (*it)->FocusVis(e, ac);
}

/**
 * @fn    NextTask(XEvent *e, WaAction *ac)
 * @brief Switches to next task
 *
 * Switches to the window that haven't had focus for longest time.
 *
 * @param e X event that have occurred
 * @param ac WaAction object
 */
void WaScreen::NextTask(XEvent *e, WaAction *ac) {
    if (waimea->eh->move_resize != EndMoveResizeType) return;
    if (wawindow_list.size() < 2) return;
    
    wawindow_list.back()->Raise(e, ac);
    wawindow_list.back()->FocusVis(e, ac);
}

/**
 * @fn    PointerFixedWarp(XEvent *e, WaAction *ac)
 * @brief Warps pointer to fixed position
 *
 * Parses the action parameter as an X offset string. The X and Y
 * offset values defines the fixed pixel position to warp the 
 * pointer to.
 *
 * @param ac WaAction object
 */
void WaScreen::PointerFixedWarp(XEvent *, WaAction *ac) {
    int x, y, mask, i, o_x, o_y;
    unsigned int ui, w, h;
    Window dw;
    
    mask = XParseGeometry(ac->param, &x, &y, &w, &h);
    if (mask & XNegative) x = width + x;
    if (mask & YNegative) y = height + y;
    XQueryPointer(display, id, &dw, &dw, &o_x, &o_y, &i, &i, &ui);
    x = x - o_x;
    y = y - o_y;
    XWarpPointer(display, None, None, 0, 0, 0, 0, x, y);
}

/** 
 * @fn    PointerRelativeWarp(XEvent *e, WaAction *ac)
 * @brief Relative pointer warp
 *
 * Parses the action parameter as an X offset string. The X and Y
 * offset values defines the number of pixels to warp the pointer.
 * 
 * @param ac WaAction object
 */
void WaScreen::PointerRelativeWarp(XEvent *, WaAction *ac) {
    int x, y, mask;
    unsigned int w, h;
                    
    mask = XParseGeometry(ac->param, &x, &y, &w, &h);
    XWarpPointer(display, None, None, 0, 0, 0, 0, x, y);
}

/** 
 * @fn    GoToDesktop(unsigned int number)
 * @brief Go to desktop
 *
 * Sets current desktop to the one specified by number parameter.
 * 
 * @param number Desktop number to go to
 */
void WaScreen::GoToDesktop(unsigned int number) {
    list<Desktop *>::iterator dit = desktop_list.begin();
    for (; dit != desktop_list.end(); dit++)
        if ((unsigned int) (*dit)->number == number) break;
    
    if (dit != desktop_list.end() && *dit != current_desktop) {
        Window oldf = (Window) 0;
        if (waimea->eh) oldf = waimea->eh->focused;
        XSetInputFocus(display, id, RevertToPointerRoot, CurrentTime);
        (*dit)->workarea.x = current_desktop->workarea.x;
        (*dit)->workarea.y = current_desktop->workarea.y;
        (*dit)->workarea.width = current_desktop->workarea.width;
        (*dit)->workarea.height = current_desktop->workarea.height;
        current_desktop = (*dit);

        list<WaWindow *>::iterator it = wawindow_list.begin();
        for (; it != wawindow_list.end(); ++it) {
            if ((*it)->desktop_mask & (1L << current_desktop->number)) {
                (*it)->Show();
                net->SetDesktop(*it);
            }
            else
                (*it)->Hide();
        }

        if (oldf) {
            WaWindow *ww = (WaWindow *) waimea->FindWin(oldf,
                                                        WindowType);
            if (ww) {
                if (ww->desktop_mask &
                    (1L << current_desktop->number)) {
                    ww->Focus(false);
                }
            }
        }
        
        list<DockappHandler *>::iterator dock_it = docks.begin();
        for (; dock_it != docks.end(); ++dock_it) {
            if ((*dock_it)->style->desktop_mask &
                (1L << current_desktop->number)) {
                if ((*dock_it)->hidden) {
                    XMapWindow(display, (*dock_it)->id);
                    (*dock_it)->hidden = false;
                    (*dock_it)->Render();
                }
            } else if (! (*dock_it)->hidden) {
                XUnmapWindow(display, (*dock_it)->id);
                (*dock_it)->hidden = true;
            }
        }
        UpdateWorkarea();
        net->SetCurrentDesktop(this);
    } else
        if (dit == desktop_list.end())
            WARNING << "bad desktop id `" << number << "', desktop " <<
                number << " doesn't exist" << endl;
}

/** 
 * @fn    GoToDesktop(XEvent *, WaAction *ac)
 * @brief Go to desktop
 *
 * Sets current desktop to the one specified by action parameter.
 * 
 * @param ac WaAction object
 */
void WaScreen::GoToDesktop(XEvent *, WaAction *ac) {
    if (ac->param) GoToDesktop((unsigned int) atoi(ac->param));
}

/** 
 * @fn    NextDesktop(XEvent *, WaAction *)
 * @brief Go to next desktop
 *
 * Sets current desktop to desktop with ID number current ID + 1. If
 * current ID + 1 doesn't exist then current desktop is set to desktop with
 * ID 0.
 * 
 * @param ac WaAction object
 */
void WaScreen::NextDesktop(XEvent *, WaAction *) {
    if (current_desktop->number + 1 == config.desktops)
        GoToDesktop(0);
    else
        GoToDesktop(current_desktop->number + 1);
}

/** 
 * @fn    PreviousDesktop(XEvent *, WaAction *)
 * @brief Go to previous desktop
 *
 * Sets current desktop to desktop with ID number current ID - 1. If
 * current ID - 1 is negative then current desktop is set to desktop with
 * highest desktop number.
 * 
 * @param ac WaAction object
 */
void WaScreen::PreviousDesktop(XEvent *, WaAction *) {
    if (current_desktop->number == 0)
        GoToDesktop(config.desktops - 1);
    else
        GoToDesktop(current_desktop->number - 1);
}

/**
 * @fn    EvAct(XEvent *e, EventDetail *ed, list<WaAction *> *acts)
 * @brief Calls WaScreen function
 *
 * Tries to match an occurred X event with the actions in an action list.
 * If we have a match then we execute that action.
 *
 * @param e X event that have occurred
 * @param ed Event details
 * @param acts List with actions to match event with
 */
void WaScreen::EvAct(XEvent *e, EventDetail *ed, list<WaAction *> *acts) {
    if (waimea->eh->move_resize != EndMoveResizeType)
        ed->mod |= MoveResizeMask;
    list<WaAction *>::iterator it = acts->begin();
    for (; it != acts->end(); ++it) {
        if (eventmatch(*it, ed)) {
            if ((*it)->delay.tv_sec || (*it)->delay.tv_usec) {
                Interrupt *i = new Interrupt(*it, e, id);
                waimea->timer->AddInterrupt(i);
            }
            else {
                if ((*it)->exec)
                    waexec((*it)->exec, displaystring);
                else
                    ((*this).*((*it)->rootfunc))(e, *it);
            }
        }
    }
}

/**
 * @fn    AddDockapp(Window window)
 * @brief Add dockapp to dockapp system
 *
 * Inserts the dockapp into the right dockapp holder.
 *
 * @param window Window ID of dockapp window
 */
void WaScreen::AddDockapp(Window window) {
    Dockapp *da;
    XClassHint *c_hint = XAllocClassHint();
    int have_hints = XGetClassHint(display, window, c_hint);
    char *title;
    if (! XFetchName(display, window, &title))
        title = NULL;

    list<Regex *>::iterator reg_it;
    list<int>::iterator regt_it;
    list<DockappHandler *>::iterator dock_it = docks.begin();
    for (; dock_it != docks.end(); ++dock_it) {
        if (have_hints) {
            reg_it = (*dock_it)->style->order.begin();
            regt_it = (*dock_it)->style->order_type.begin();
            for (; reg_it != (*dock_it)->style->order.end();
                 ++reg_it, ++regt_it) {
                if ((*regt_it == NameMatchType) &&
                    ((*reg_it)->Match(c_hint->res_name))) {
                    da = new Dockapp(window, *dock_it);
                    da->c_hint = c_hint;
                    da->title = title;
                    (*dock_it)->Update();
                    return;
                }
            }
            reg_it = (*dock_it)->style->order.begin();
            regt_it = (*dock_it)->style->order_type.begin();
            for (; reg_it != (*dock_it)->style->order.end();
                 ++reg_it, ++regt_it) {
                if (have_hints && (*regt_it == ClassMatchType) &&
                    ((*reg_it)->Match(c_hint->res_class))) {
                    da = new Dockapp(window, *dock_it);
                    da->c_hint = c_hint;
                    da->title = title;
                    (*dock_it)->Update();
                    return;
                }
            }
        }
        if (title) {
            reg_it = (*dock_it)->style->order.begin();
            regt_it = (*dock_it)->style->order_type.begin();
            for (; reg_it != (*dock_it)->style->order.end();
                 ++reg_it, ++regt_it) {
                if ((*regt_it == TitleMatchType) &&
                    ((*reg_it)->Match(title))) {
                    da = new Dockapp(window, *dock_it);
                    da->c_hint = c_hint;
                    da->title = title;
                    (*dock_it)->Update();
                    return;
                }
            }
        }
    }
    DockappHandler *lastd = docks.back();
    da = new Dockapp(window, lastd);
    da->c_hint = NULL;
    da->title = NULL;
    lastd->Update();
    if (have_hints) {
        XFree(c_hint->res_name);
        XFree(c_hint->res_class);
    }
    XFree(c_hint);
}

#ifdef RANDR
/**
 * @fn    RRUpdate(void)
 * @brief Update screen size
 *
 * Updates screen edge positions/sizes, dockappholder positions and
 * workarea size.
 */
void WaScreen::RRUpdate(void) {
    v_xmax = (config.virtual_x - 1) * width;
    v_ymax = (config.virtual_y - 1) * height;

    XMoveResizeWindow(display, west->id, 0, 0, 2, height);
    XMoveResizeWindow(display, east->id, width - 2, 0, 2, height);
    XMoveResizeWindow(display, north->id, 0, 0, width, 2);
    XMoveResizeWindow(display, south->id, 0, height - 2, width, 2);

    list<DockappHandler *>::iterator dit = docks.begin();
    for (; dit != docks.end(); ++dit)
        (*dit)->Update();

    UpdateWorkarea();

    net->SetDesktopGeometry(this);
}
#endif // RANDR

/**
 * @fn    ScreenEdge(WaScreen *wascrn, int x, int y, int width, int height,
 *                   int type) : WindowObject(0, type)
 * @brief Constructor for ScreenEdge class
 *
 * Creates an always on top screen edge window.
 *
 * @param wascrn WaScreen Object
 * @param x X position
 * @param y Y position
 * @param width Width of ScreenEdge window
 * @param height Height of ScreenEdge window
 * @param type Type of WindowObject
 */
ScreenEdge::ScreenEdge(WaScreen *wascrn, int x, int y, int width, int height,
                       int type) : WindowObject(0, type) {
    XSetWindowAttributes attrib_set;
    
    wa = wascrn;
    attrib_set.override_redirect = true;
    attrib_set.event_mask = EnterWindowMask | LeaveWindowMask |
        ButtonPressMask | ButtonReleaseMask;
    
    id = XCreateWindow(wa->display, wa->id, x, y, width, height, 0,
                       CopyFromParent, InputOnly, CopyFromParent,
                       CWOverrideRedirect | CWEventMask, &attrib_set);

    wa->waimea->net->wXDNDMakeAwareness(id);
}

/**
 * @fn    ScreenEdge::SetActionlist(list<WaAction *> *list)
 * @brief Sets actionlist
 *
 * Sets screenedge actionlist and if list is other than empty screenedge
 * window is mapped.
 *
 * @param list Actionlist to set
 */
void ScreenEdge::SetActionlist(list<WaAction *> *list) {
    actionlist = list;
    if (! actionlist->empty()) {
        XMapWindow(wa->display, id);
        wa->always_on_top_list.push_back(id);
        wa->WaRaiseWindow(0);
        wa->waimea->window_table.insert(make_pair(id, this));
    }
}

/**
 * @fn    ~ScreenEdge(void)
 * @brief Destructor for ScreenEdge class
 *
 * Destroys ScreenEdge window
 */
ScreenEdge::~ScreenEdge(void) {
    if (! actionlist->empty()) {
        wa->always_on_top_list.remove(id);
        wa->waimea->window_table.erase(id);
    }
    XDestroyWindow(wa->display, id);
}


syntax highlighted by Code2HTML, v. 0.9.1