/**************************************************************************
*
* Tint2 panel
*
* Copyright (C) 2007 Pål Staurland (staura@gmail.com)
* Modified (C) 2008 thierry lorthiois (lorthiois@bbsoft.fr)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**************************************************************************/

#include <X11/extensions/Xdamage.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/Xrender.h>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "common.h"
#include "config.h"
#include "server.h"
#include "signals.h"
#include "window.h"

Server server;

int server_catch_error(Display *d, XErrorEvent *ev)
{
    g_queue_push_tail (server.errors, GINT_TO_POINTER (ev->error_code));
    server.err_n++;
    return 0;
}

static char *atom_names [NUM_ATOMS]
= {
#define _atom_name(name) [name] = #name ,

    _atom_name (_XROOTPMAP_ID)
    _atom_name (_XROOTMAP_ID)
    _atom_name (_NET_CURRENT_DESKTOP)
    _atom_name (_NET_NUMBER_OF_DESKTOPS)
    _atom_name (_NET_DESKTOP_NAMES)
    _atom_name (_NET_DESKTOP_GEOMETRY)
    _atom_name (_NET_DESKTOP_VIEWPORT)
    _atom_name (_NET_WORKAREA)
    _atom_name (_NET_ACTIVE_WINDOW)
    _atom_name (_NET_WM_WINDOW_TYPE)
    _atom_name (_NET_WM_STATE_SKIP_PAGER)
    _atom_name (_NET_WM_STATE_SKIP_TASKBAR)
    _atom_name (_NET_WM_STATE_STICKY)
    _atom_name (_NET_WM_STATE_DEMANDS_ATTENTION)
    _atom_name (_NET_WM_WINDOW_TYPE_DOCK)
    _atom_name (_NET_WM_WINDOW_TYPE_DESKTOP)
    _atom_name (_NET_WM_WINDOW_TYPE_TOOLBAR)
    _atom_name (_NET_WM_WINDOW_TYPE_MENU)
    _atom_name (_NET_WM_WINDOW_TYPE_SPLASH)
    _atom_name (_NET_WM_WINDOW_TYPE_DIALOG)
    _atom_name (_NET_WM_WINDOW_TYPE_NORMAL)
    _atom_name (_NET_WM_DESKTOP)
    _atom_name (WM_STATE)
    _atom_name (_NET_WM_STATE)
    _atom_name (_NET_WM_STATE_MAXIMIZED_VERT)
    _atom_name (_NET_WM_STATE_MAXIMIZED_HORZ)
    _atom_name (_NET_WM_STATE_SHADED)
    _atom_name (_NET_WM_STATE_HIDDEN)
    _atom_name (_NET_WM_STATE_BELOW)
    _atom_name (_NET_WM_STATE_ABOVE)
    _atom_name (_NET_WM_STATE_MODAL)
    _atom_name (_NET_CLIENT_LIST)
    _atom_name (_NET_WM_NAME)
    _atom_name (_NET_WM_VISIBLE_NAME)
    _atom_name (_NET_WM_STRUT)
    _atom_name (_NET_WM_ICON)
    _atom_name (_NET_WM_ICON_GEOMETRY)
    _atom_name (_NET_WM_ICON_NAME)
    _atom_name (_NET_CLOSE_WINDOW)
    _atom_name (UTF8_STRING)
    _atom_name (_NET_SUPPORTING_WM_CHECK)
    _atom_name (_NET_WM_CM_S0)
    _atom_name (_NET_WM_STRUT_PARTIAL)
    _atom_name (WM_NAME)
    _atom_name (__SWM_VROOT)
    _atom_name (_MOTIF_WM_HINTS)
    _atom_name (WM_HINTS)

    // systray protocol
    _atom_name (_NET_SYSTEM_TRAY_SCREEN)
    _atom_name (_NET_SYSTEM_TRAY_OPCODE)
    _atom_name (MANAGER)
    _atom_name (_NET_SYSTEM_TRAY_MESSAGE_DATA)
    _atom_name (_NET_SYSTEM_TRAY_ORIENTATION)
    _atom_name (_NET_SYSTEM_TRAY_ICON_SIZE)
    _atom_name (_NET_SYSTEM_TRAY_PADDING)
    _atom_name (_XEMBED)
    _atom_name (_XEMBED_INFO)
    _atom_name (_NET_WM_PID)

    // XSettings
    _atom_name (_XSETTINGS_SCREEN)
    _atom_name (_XSETTINGS_SETTINGS)

    // drag 'n' drop
    _atom_name (XdndAware)
    _atom_name (XdndEnter)
    _atom_name (XdndPosition)
    _atom_name (XdndStatus)
    _atom_name (XdndDrop)
    _atom_name (XdndLeave)
    _atom_name (XdndSelection)
    _atom_name (XdndTypeList)
    _atom_name (XdndActionCopy)
    _atom_name (XdndFinished)
    _atom_name (TARGETS)

    // tint2 atoms
    _atom_name (_TINT2_REFRESH_EXECP)

#undef _atom_name
};

void server_init_atoms()
{
    gchar * s_NET_SYSTEM_TRAY_SCREEN = strdup_printf( NULL, "_NET_SYSTEM_TRAY_S%d", server.screen);
    gchar * s_XSETTINGS_SCREEN       = strdup_printf( NULL, "_XSETTINGS_S%d",       server.screen);

    atom_names [_NET_SYSTEM_TRAY_SCREEN] = s_NET_SYSTEM_TRAY_SCREEN;
    atom_names [_XSETTINGS_SCREEN]       = s_XSETTINGS_SCREEN;
    XInternAtoms(   server.display,
                    atom_names,
                    71, False, (Atom *)&server.atom);

    free( s_NET_SYSTEM_TRAY_SCREEN);
    free( s_XSETTINGS_SCREEN);
}

const char *GetAtomName(Display *disp, Atom a)
{
    return a == None ? "None" : XGetAtomName(disp, a);
}

void cleanup_server()
{
    if (server.colormap)
        XFreeColormap(server.display, server.colormap);
    server.colormap = 0;
    if (server.colormap32)
        XFreeColormap(server.display, server.colormap32);
    server.colormap32 = 0;
    if (server.monitors) {
        for (int i = 0; i < server.num_monitors; ++i)
        {
            g_strfreev(server.monitors[i].names);
            server.monitors[i].names = NULL;
        }
        free_and_null( server.monitors);
    }
    if (server.gc)
        XFreeGC(server.display, server.gc);
    server.gc = NULL;
    server.disable_transparency = FALSE;
#ifdef HAVE_SN
    if (server.pids)
        g_tree_destroy(server.pids);
    server.pids = NULL;
#endif
}

void send_event32(Window win, Atom at, long data1, long data2, long data3)
{
    XEvent event = {
        .xclient = {
            .type = ClientMessage,
            .serial = 0,
            .send_event = True,
            .display = server.display,
            .window = win,
            .message_type = at,
            .format = 32,
            .data.l = { data1, data2, data3, 0, 0 }
        }
    };
    XSendEvent(server.display, server.root_win, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
}

int get_property32(Window win, Atom at, Atom type)
{
    unsigned char *prop_value;
    int data;

    prop_value = get_property (win, at, type, NULL);
    if (prop_value) {
        data = ((gulong *)prop_value)[0];
        XFree (prop_value);
        return data;
    }
    return 0;
}

void *get_property(Window win, Atom at, Atom type, int *num_results)
{
    Atom type_ret;
    int format_ret = 0;
    unsigned long nitems_ret = 0;
    unsigned long bafter_ret = 0;
    unsigned char *prop_value;

    if (!win)
        return NULL;

    int result = XGetWindowProperty(server.display,
                                    win,
                                    at,
                                    0,
                                    0x7fffffff,
                                    False,
                                    type,
                                    &type_ret,
                                    &format_ret,
                                    &nitems_ret,
                                    &bafter_ret,
                                    &prop_value);

    // Send fill_color resultcount
    if (num_results)
        *num_results = (int)nitems_ret;

    if (result == Success) {
        if (type == AnyPropertyType || type == type_ret)
            return prop_value;
        XFree( prop_value);
    }
    return NULL;
}

void get_root_pixmap()
{
    Pixmap ret = None;

    Atom pixmap_atoms[] = {server.atom [_XROOTPMAP_ID], server.atom [_XROOTMAP_ID]};
    for (int i = 0; i < ARRAY_SIZE (pixmap_atoms); ++i)
    {
        Pixmap *res = (unsigned long *)get_property(server.root_win, pixmap_atoms[i], XA_PIXMAP, NULL);
        if (res) {
            ret = *res;
            XFree(res);
            break;
        }
    }
    server.root_pmap = ret;

    if (server.root_pmap == None)
        fprintf(stderr, "tint2: pixmap background detection failed\n");
    else {
        XGCValues gcv;
        gcv.ts_x_origin = 0;
        gcv.ts_y_origin = 0;
        gcv.fill_style = FillTiled;
        unsigned mask = GCTileStipXOrigin | GCTileStipYOrigin | GCFillStyle | GCTile;

        gcv.tile = server.root_pmap;
        XChangeGC(server.display, server.gc, mask, &gcv);
    }
}

int compare_monitor_pos(const void *monitor1, const void *monitor2)
{
    const Monitor *m1 = monitor1;
    const Monitor *m2 = monitor2;
    return  m1->x < m2->x ? -1
        :   m1->x > m2->x ? 1
        :   m1->y < m2->y ? -1
        :   m1->y > m2->y;
}

int monitors_inside_monitor (const void *least, const void *greater)
{
    const Monitor *m1 = least;
    const Monitor *m2 = greater;
    
    // test if m1 inside m2
    return (m1->x >= m2->x     && m1->y >= m2->y    &&
            m1->x + m1->width  <= m2->x + m2->width &&
            m1->y + m1->height <= m2->y + m2->height
           ) ? 1 : -1;
}

void sort_monitors()
{
    qsort(server.monitors, server.num_monitors, sizeof(Monitor), compare_monitor_pos);
}

int get_dpi(XRRCrtcInfo *crtc, XRROutputInfo *output)
{
    double width = output->mm_width;
    double height = output->mm_height;
    double x_res = crtc->width;
    double y_res = crtc->height;

    if (width > 0 && height > 0) {
        int dpi_x = x_res / width * 25.4;
        int dpi_y = y_res / height * 25.4;
        return MAX(dpi_x, dpi_y);
    }
    return 0;
}

void get_monitors()
{
    if (XineramaIsActive(server.display))
    {
        int num_monitors;
        XineramaScreenInfo *info = XineramaQueryScreens(server.display, &num_monitors);
        XRRScreenResources *res = XRRGetScreenResourcesCurrent(server.display, server.root_win);
        RROutput primary_output = XRRGetOutputPrimary(server.display, server.root_win);
        if (res && res->ncrtc >= num_monitors)
        {
            // use xrandr to identify monitors (does not work with proprietery nvidia drivers)
            fprintf(stderr, "tint2: xRandr: Found crtc's: %d\n", res->ncrtc);
            server.monitors = calloc(res->ncrtc, sizeof(Monitor));
            num_monitors = 0;
            for (int i = 0; i < res->ncrtc; ++i)
            {
                XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(server.display, res, res->crtcs[i]);
                // Ignore empty crtc
                if (!crtc_info->width || !crtc_info->height)
                {
                    fprintf(stderr, "tint2: xRandr: crtc %d seems disabled\n", i);
                    XRRFreeCrtcInfo(crtc_info);
                    continue;
                }
                Monitor *mon = server.monitors + num_monitors++;
                mon->x = crtc_info->x;
                mon->y = crtc_info->y;
                mon->width = crtc_info->width;
                mon->height = crtc_info->height;
                mon->names = calloc((crtc_info->noutput + 1), sizeof(gchar *));
                mon->dpi = 96;
                for (int j = 0; j < crtc_info->noutput; ++j)
                {
                    XRROutputInfo *output_info = XRRGetOutputInfo(server.display, res, crtc_info->outputs[j]);
                    mon->names[j] = g_strdup(output_info->name);
                    mon->primary = crtc_info->outputs[j] == primary_output;
                    int dpi = get_dpi(crtc_info, output_info);
                    if (dpi)
                        mon->dpi = dpi;
                    fprintf(stderr,
                            BLUE "tint2: xRandr: Linking output %s with crtc %d, resolution %dx%d, DPI %d" RESET "\n",
                            output_info->name,
                            i,
                            mon->width,
                            mon->height,
                            mon->dpi);
                    XRRFreeOutputInfo(output_info);
                }
                mon->names[crtc_info->noutput] = NULL;
                XRRFreeCrtcInfo(crtc_info);
            }
        }
        else if (info && num_monitors > 0)
        {
            server.monitors = calloc(num_monitors, sizeof(Monitor));
            for (int i = 0; i < num_monitors; i++)
            {
                server.monitors[i].x = info[i].x_org;
                server.monitors[i].y = info[i].y_org;
                server.monitors[i].width = info[i].width;
                server.monitors[i].height = info[i].height;
                server.monitors[i].names = NULL;
                server.monitors[i].dpi = 96;
            }
        }

        // Sort monitors by inclusion
        qsort(server.monitors, num_monitors, sizeof(Monitor), monitors_inside_monitor);

        // Remove monitors included in other ones
        int i = 0;
        while (i < num_monitors) {
            for (int j = 0; j < i; j++)
            {
                if (monitors_inside_monitor (&server.monitors[i], &server.monitors[j]) > 0)
                    goto next;
            }
            i++;
        }
    next:
        for (int j = i; j < num_monitors; ++j)
            if (server.monitors[j].names)
                g_strfreev(server.monitors[j].names);
        server.num_monitors = i;
        server.monitors = realloc(server.monitors, server.num_monitors * sizeof(Monitor));
        qsort(server.monitors, server.num_monitors, sizeof(Monitor), compare_monitor_pos);

        if (res)
            XRRFreeScreenResources(res);
        XFree(info);
    }
    if (!server.num_monitors)
    {
        server.num_monitors = 1;
        server.monitors = calloc(1, sizeof(Monitor));
        server.monitors[0].x = server.monitors[0].y = 0;
        server.monitors[0].width = DisplayWidth(server.display, server.screen);
        server.monitors[0].height = DisplayHeight(server.display, server.screen);
        server.monitors[0].names = 0;
        server.monitors[0].dpi = 96;
    }
}

void print_monitors()
{
    fprintf(stderr, "tint2: Number of monitors: %d\n", server.num_monitors);
    for (int i = 0; i < server.num_monitors; i++)
    {
        fprintf(stderr,
                "Monitor %d: x = %d, y = %d, w = %d, h = %d\n",
                i + 1,
                server.monitors[i].x,
                server.monitors[i].y,
                server.monitors[i].width,
                server.monitors[i].height);
    }
}

void server_get_number_of_desktops()
{
    if (server.viewports)
        free_and_null (server.viewports);

    server.num_desktops = get_property32(server.root_win, server.atom [_NET_NUMBER_OF_DESKTOPS], XA_CARDINAL);
    if (server.num_desktops > 1)
        return;

    long *work_area_size = get_property(server.root_win, server.atom [_NET_WORKAREA], XA_CARDINAL, NULL);
    if (!work_area_size)
        return;
    int work_area_width = work_area_size[0] + work_area_size[2];
    int work_area_height = work_area_size[1] + work_area_size[3];
    XFree(work_area_size);

    long *x_screen_size = get_property(server.root_win, server.atom [_NET_DESKTOP_GEOMETRY], XA_CARDINAL, NULL);
    if (!x_screen_size)
        return;
    int x_screen_width = x_screen_size[0];
    int x_screen_height = x_screen_size[1];
    XFree(x_screen_size);

    int num_viewports = MAX(x_screen_width / work_area_width, 1) * MAX(x_screen_height / work_area_height, 1);
    if (num_viewports <= 1) {
        server.num_desktops = 1;
        return;
    }

    server.viewports = calloc(num_viewports, sizeof(Viewport));
    int k = 0;
    for (int imax = MAX(x_screen_height / work_area_height, 1), i = 0; i < imax; i++)
        for (int jmax = MAX(x_screen_width / work_area_width, 1), j = 0; j < jmax; j++)
        {
            server.viewports[k].x = j * work_area_width;
            server.viewports[k].y = i * work_area_height;
            server.viewports[k].width = work_area_width;
            server.viewports[k].height = work_area_height;
            k++;
        }

    server.num_desktops = num_viewports;
}

GSList *get_desktop_names()
{
    GSList  *tail = NULL,
            *list = NULL;

    if (server.viewports) {
        for (int j = 0; j < server.num_desktops; j++)
            g_slist_append_tail( list, tail, strdup_printf( NULL, "%d", j + 1));
        return list;
    }

    int count;
    gchar *data_ptr = get_property(server.root_win, server.atom [_NET_DESKTOP_NAMES], server.atom [UTF8_STRING], &count);
    if (data_ptr)
    {
        gchar   *ptr  = data_ptr,
                *eptr = data_ptr + count - 1;

        tail = list = g_slist_append( NULL, strdup( data_ptr));
        while (( ptr = memchr (ptr, '\0', eptr - ptr) ))
            g_slist_append_tail( list, tail, strdup( ++ptr));
        XFree(data_ptr);
    }
    return list;
}

int get_current_desktop()
{
    if (!server.viewports)
        return MAX(0, MIN(  server.num_desktops - 1,
                            get_property32(server.root_win, server.atom [_NET_CURRENT_DESKTOP], XA_CARDINAL) ));

    /*******************************/
    long *work_area_size = get_property(server.root_win, server.atom [_NET_WORKAREA], XA_CARDINAL, NULL);
    if (!work_area_size)
        return 0;
    int work_area_width = work_area_size[0] + work_area_size[2];
    int work_area_height = work_area_size[1] + work_area_size[3];
    XFree(work_area_size);

    if (work_area_width <= 0 || work_area_height <= 0)
        return 0;

    /*******************************/
    long *viewport = get_property(server.root_win, server.atom [_NET_DESKTOP_VIEWPORT], XA_CARDINAL, NULL);
    if (!viewport)
        return 0;
    int viewport_x = viewport[0];
    int viewport_y = viewport[1];
    XFree(viewport);

    /*******************************/
    long *x_screen_size = get_property(server.root_win, server.atom [_NET_DESKTOP_GEOMETRY], XA_CARDINAL, NULL);
    if (!x_screen_size)
        return 0;
    int x_screen_width = x_screen_size[0];
    XFree(x_screen_size);

    /*******************************/
    int ncols = x_screen_width / work_area_width;

    //	fprintf(stderr, "tint2: \n");
    //	fprintf(stderr, "tint2: Work area size: %d x %d\n", work_area_width, work_area_height);
    //	fprintf(stderr, "tint2: Viewport pos: %d x %d\n", viewport_x, viewport_y);
    //	fprintf(stderr, "tint2: Viewport i: %d\n", (viewport_y / work_area_height) * ncols + viewport_x /
    //work_area_width);

    int result = (viewport_y / work_area_height) * ncols + viewport_x / work_area_width;
    return MAX(0, MIN(server.num_desktops - 1, result));
}

void change_desktop(int desktop)
{
    if (!server.viewports)
        send_event32(server.root_win, server.atom [_NET_CURRENT_DESKTOP], desktop, 0, 0);
    else
        send_event32(server.root_win,
                     server.atom [_NET_DESKTOP_VIEWPORT],
                     server.viewports[desktop].x,
                     server.viewports[desktop].y,
                     0);
}

void get_desktops()
// detect number of desktops
// wait 15s to leave some time for window manager startup
{
    for (int i = 0; i < 15; i++)
    {
        server_get_number_of_desktops();
        if (server.num_desktops > 0)
            break;
        sleep(1);
    }
    if (server.num_desktops == 0) {
        server.num_desktops = 1;
        fprintf(stderr, "tint2: warning : WM doesn't respect NETWM specs. tint2 default to 1 desktop.\n");
    }
    server.desktop = get_current_desktop();
}

void server_init_visual()
{
    Visual *visual = NULL;

    // inspired by freedesktops fdclock ;)
    XVisualInfo templ = {.screen = server.screen, .depth = 32, .class = TrueColor};
    int nvi;
    XVisualInfo *xvi = XGetVisualInfo(  server.display,
                                        VisualScreenMask | VisualDepthMask | VisualClassMask,
                                        &templ, &nvi);
    if (xvi) {
        XRenderPictFormat *format;
        for (int i = 0; i < nvi; i++)
        {
            format = XRenderFindVisualFormat(server.display, xvi[i].visual);
            if (format->type == PictTypeDirect && format->direct.alphaMask) {
                visual = xvi[i].visual;
                break;
            }
        }
    }
    XFree(xvi);

    if (server.colormap)
        XFreeColormap(server.display, server.colormap);
    if (server.colormap32)
        XFreeColormap(server.display, server.colormap32);

    if (visual) {
        server.visual32 = visual;
        server.colormap32 = XCreateColormap(server.display, server.root_win, visual, AllocNone);
    }

    // check composite manager
    server.composite_manager = GET_COMPOSITE_MANAGER();

    if (!server.disable_transparency && visual && server.composite_manager != None && !snapshot_path)
    {
        XSetWindowAttributes attrs;
        attrs.event_mask = StructureNotifyMask;
        XChangeWindowAttributes(server.display, server.composite_manager, CWEventMask, &attrs);

        server.real_transparency = TRUE;
        server.depth = 32;
        fprintf(stderr, "tint2: real transparency on... depth: %d\n", server.depth);
        server.colormap = server.colormap32;
        server.visual = visual;
    }
    else
    {
        // no composite manager or snapshot mode => fake transparency
        server.real_transparency = FALSE;
        server.depth = DefaultDepth(server.display, server.screen);
        fprintf(stderr, "tint2: real transparency off.... depth: %d\n", server.depth);
        server.colormap = DefaultColormap(server.display, server.screen);
        server.visual = DefaultVisual(server.display, server.screen);
    }
}

void server_init_xdamage()
{
    XDamageQueryExtension(server.display, &server.xdamage_event_type, &server.xdamage_event_error_type);
    server.xdamage_event_type += XDamageNotify;
    server.xdamage_event_error_type += XDamageNotify;
}

void forward_click(XEvent *e)
// Forward mouse click to the desktop window
{
    // forward the click to the desktop window (thanks conky)
    XUngrabPointer(server.display, e->xbutton.time);
    e->xbutton.window = server.root_win;
    // icewm doesn't open under the mouse.
    // and xfce doesn't open at all.
    e->xbutton.x = e->xbutton.x_root;
    e->xbutton.y = e->xbutton.y_root;
    // fprintf(stderr, "tint2: **** %d, %d\n", e->xbutton.x, e->xbutton.y);
    // XSetInputFocus(server.display, e->xbutton.window, RevertToParent, e->xbutton.time);
    XSendEvent(server.display, e->xbutton.window, False, ButtonPressMask, e);
}

void handle_crash(const char *reason)
{
#ifndef DISABLE_BACKTRACE
    char path[4096];
    sprintf(path, "%s/.tint2-crash.log", get_home_dir());
    int log_fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
    log_string(log_fd, RED "tint2: crashed, reason: ");
    log_string(log_fd, reason);
    log_string(log_fd, RESET "\n");
    dump_backtrace(log_fd);
    log_string(log_fd, RED "Please create a bug report with this log output." RESET "\n");
    close(log_fd);
#endif
}

int x11_io_error(Display *display)
{
    handle_crash("X11 I/O error");
    return 0;
}

#ifdef HAVE_SN
static int error_trap_depth = 0;

void error_trap_push(SnDisplay *display, Display *xdisplay)
{
    ++error_trap_depth;
}

void error_trap_pop(SnDisplay *display, Display *xdisplay)
{
    if (error_trap_depth == 0) {
        fprintf(stderr, "tint2: Error trap underflow!\n");
        return;
    }

    XSync(xdisplay, False); /* get all errors out of the queue */
    --error_trap_depth;
}
#endif // HAVE_SN
