/** * @file dock.c * @author Joe Wingbermuehle * @date 2006 * * @brief Dock functions. * */ #include "jwm.h" #include "dock.h" #include "tray.h" #include "main.h" #include "error.h" #include "color.h" #define SYSTEM_TRAY_REQUEST_DOCK 0 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 #define SYSTEM_TRAY_ORIENTATION_HORZ 0 #define SYSTEM_TRAY_ORIENTATION_VERT 1 /** Structure to represent a docked window. */ typedef struct DockNode { Window window; int needs_reparent; struct DockNode *next; } DockNode; /** Structure to represent a dock tray component. */ typedef struct DockType { TrayComponentType *cp; Window window; DockNode *nodes; } DockType; static const char *BASE_SELECTION_NAME = "_NET_SYSTEM_TRAY_S%d"; static const char *ORIENTATION_ATOM = "_NET_SYSTEM_TRAY_ORIENTATION"; static DockType *dock = NULL; static int owner = 0; static Atom dockAtom; static unsigned long orientation; static void SetSize(TrayComponentType *cp, int width, int height); static void Create(TrayComponentType *cp); static void Resize(TrayComponentType *cp); static void DockWindow(Window win); static int UndockWindow(Window win); static void UpdateDock(); /** Initialize dock data. */ void InitializeDock() { } /** Startup the dock. */ void StartupDock() { char *selectionName; if(!dock) { /* No dock has been requested. */ return; } if(!dock->cp) { /* The Dock item has been removed from the configuration. */ JXDestroyWindow(display, dock->window); Release(dock); dock = NULL; return; } if(dock->window == None) { /* No dock yet. */ /* Get the selection atom. */ selectionName = AllocateStack(strlen(BASE_SELECTION_NAME) + 1); sprintf(selectionName, BASE_SELECTION_NAME, rootScreen); dockAtom = JXInternAtom(display, selectionName, False); ReleaseStack(selectionName); /* The location and size of the window doesn't matter here. */ dock->window = JXCreateSimpleWindow(display, rootWindow, /* x, y, width, height */ 0, 0, 1, 1, /* border_size, border_color */ 0, 0, /* background */ colors[COLOR_TRAY_BG]); JXSelectInput(display, dock->window, SubstructureNotifyMask | SubstructureRedirectMask | PointerMotionMask | PointerMotionHintMask); } dock->cp->window = dock->window; } /** Shutdown the dock. */ void ShutdownDock() { DockNode *np; if(dock) { if(shouldRestart) { /* If restarting we just reparent the dock window to the root * window. We need to keep the dock around and visible so that * we don't cause problems with the docked windows. * It seems every application handles docking differently... */ JXReparentWindow(display, dock->window, rootWindow, 0, 0); } else { /* JWM is exiting. */ /* Release memory used by the dock list. */ while(dock->nodes) { np = dock->nodes->next; JXReparentWindow(display, dock->nodes->window, rootWindow, 0, 0); Release(dock->nodes); dock->nodes = np; } /* Release the selection. */ if(owner) { JXSetSelectionOwner(display, dockAtom, None, CurrentTime); } /* Destroy the dock window. */ JXDestroyWindow(display, dock->window); } } } /** Destroy dock data. */ void DestroyDock() { if(dock) { if(shouldRestart) { dock->cp = NULL; } else { Release(dock); dock = NULL; } } } /** Create a dock component. */ TrayComponentType * CreateDock() { TrayComponentType *cp; if(dock != NULL && dock->cp != NULL) { Warning("only one Dock allowed"); return NULL; } else if(dock == NULL) { dock = Allocate(sizeof(DockType)); dock->nodes = NULL; dock->window = None; } cp = CreateTrayComponent(); cp->object = dock; dock->cp = cp; cp->requestedWidth = 1; cp->requestedHeight = 1; cp->SetSize = SetSize; cp->Create = Create; cp->Resize = Resize; return cp; } /** Set the size of a dock component. */ void SetSize(TrayComponentType *cp, int width, int height) { int count; DockNode *np; Assert(cp); Assert(dock); count = 0; for(np = dock->nodes; np; np = np->next) { ++count; } if(width == 0) { if(count > 0) { cp->width = count * height; cp->requestedWidth = cp->width; } else { cp->width = 1; cp->requestedWidth = 1; } } else if(height == 0) { if(count > 0) { cp->height = count * width; cp->requestedHeight = cp->height; } else { cp->height = 1; cp->requestedHeight = 1; } } } /** Initialize a dock component. */ void Create(TrayComponentType *cp) { XEvent event; Atom orientationAtom; Assert(cp); /* Map the dock window. */ if(cp->window != None) { JXResizeWindow(display, cp->window, cp->width, cp->height); JXMapRaised(display, cp->window); } /* Set the orientation. */ orientationAtom = JXInternAtom(display, ORIENTATION_ATOM, False); if(cp->height == 1) { orientation = SYSTEM_TRAY_ORIENTATION_VERT; } else { orientation = SYSTEM_TRAY_ORIENTATION_HORZ; } JXChangeProperty(display, dock->cp->window, orientationAtom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&orientation, 1); /* Get the selection if we don't already own it. * If we did already own it, getting it again would cause problems * with some clients due to the way restarts are handled. */ if(!owner) { owner = 1; JXSetSelectionOwner(display, dockAtom, dock->cp->window, CurrentTime); if(JXGetSelectionOwner(display, dockAtom) != dock->cp->window) { owner = 0; Warning("could not acquire system tray selection"); } else { memset(&event, 0, sizeof(event)); event.xclient.type = ClientMessage; event.xclient.window = rootWindow; event.xclient.message_type = JXInternAtom(display, "MANAGER", False); event.xclient.format = 32; event.xclient.data.l[0] = CurrentTime; event.xclient.data.l[1] = dockAtom; event.xclient.data.l[2] = dock->cp->window; event.xclient.data.l[3] = 0; event.xclient.data.l[4] = 0; JXSendEvent(display, rootWindow, False, StructureNotifyMask, &event); } } } /** Resize a dock component. */ void Resize(TrayComponentType *cp) { Assert(cp); JXResizeWindow(display, cp->window, cp->width, cp->height); UpdateDock(); } /** Handle a dock event. */ void HandleDockEvent(const XClientMessageEvent *event) { Assert(event); switch(event->data.l[1]) { case SYSTEM_TRAY_REQUEST_DOCK: DockWindow(event->data.l[2]); break; case SYSTEM_TRAY_BEGIN_MESSAGE: break; case SYSTEM_TRAY_CANCEL_MESSAGE: break; default: Debug("invalid opcode in dock event"); break; } } /** Handle a resize request event. */ int HandleDockResizeRequest(const XResizeRequestEvent *event) { DockNode *np; Assert(event); if(!dock) { return 0; } for(np = dock->nodes; np; np = np->next) { if(np->window == event->window) { JXResizeWindow(display, np->window, event->width, event->height); UpdateDock(); return 1; } } return 0; } /** Handle a configure request event. */ int HandleDockConfigureRequest(const XConfigureRequestEvent *event) { XWindowChanges wc; DockNode *np; Assert(event); if(!dock) { return 0; } for(np = dock->nodes; np; np = np->next) { if(np->window == event->window) { wc.stack_mode = event->detail; wc.sibling = event->above; wc.border_width = event->border_width; wc.x = event->x; wc.y = event->y; wc.width = event->width; wc.height = event->height; JXConfigureWindow(display, np->window, event->value_mask, &wc); UpdateDock(); return 1; } } return 0; } /** Handle a reparent notify event. */ int HandleDockReparentNotify(const XReparentEvent *event) { DockNode *np; int handled; Assert(event); /* Just return if there is no dock. */ if(!dock) { return 0; } /* Check each docked window. */ handled = 0; for(np = dock->nodes; np; np = np->next) { if(np->window == event->window) { if(event->parent != dock->cp->window) { /* For some reason the application reparented the window. * We make note of this condition and reparent every time * the dock is updated. Unfortunately we can't do this for * all applications because some won't deal with it. */ np->needs_reparent = 1; handled = 1; } } } /* Layout the stuff on the dock again if something happened. */ if(handled) { UpdateDock(); } return handled; } /** Handle a destroy event. */ int HandleDockDestroy(Window win) { if(dock) { return UndockWindow(win); } else { return 0; } } /** Handle a selection clear event. */ int HandleDockSelectionClear(const XSelectionClearEvent *event) { if(event->selection == dockAtom) { Debug("lost _NET_SYSTEM_TRAY selection"); owner = 0; } return 0; } /** Add a window to the dock. */ void DockWindow(Window win) { DockNode *np; Assert(dock); /* Make sure we have a valid window to add. */ if(win == None) { return; } /* If this window is already docked ignore it. */ for(np = dock->nodes; np; np = np->next) { if(np->window == win) { return; } } /* Add the window to our list. */ np = Allocate(sizeof(DockNode)); np->window = win; np->needs_reparent = 0; np->next = dock->nodes; dock->nodes = np; /* Update the requested size. */ if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) { if(dock->cp->requestedWidth > 1) { dock->cp->requestedWidth += dock->cp->height; } else { dock->cp->requestedWidth = dock->cp->height; } } else { if(dock->cp->requestedHeight > 1) { dock->cp->requestedHeight += dock->cp->width; } else { dock->cp->requestedHeight = dock->cp->width; } } /* It's safe to reparent at (0, 0) since we call * ResizeTray which will invoke the Resize callback. */ JXAddToSaveSet(display, win); JXSelectInput(display, win, StructureNotifyMask | ResizeRedirectMask | PointerMotionMask | PointerMotionHintMask); JXReparentWindow(display, win, dock->cp->window, 0, 0); JXMapRaised(display, win); /* Resize the tray containing the dock. */ ResizeTray(dock->cp->tray); } /** Remove a window from the dock. */ int UndockWindow(Window win) { DockNode *np; DockNode *last; Assert(dock); last = NULL; for(np = dock->nodes; np; np = np->next) { if(np->window == win) { /* Remove the window from our list. */ if(last) { last->next = np->next; } else { dock->nodes = np->next; } Release(np); /* Update the requested size. */ if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) { dock->cp->requestedWidth -= dock->cp->height; if(dock->cp->requestedWidth <= 0) { dock->cp->requestedWidth = 1; } } else { dock->cp->requestedHeight -= dock->cp->width; if(dock->cp->requestedHeight <= 0) { dock->cp->requestedHeight = 1; } } /* Resize the tray. */ ResizeTray(dock->cp->tray); return 1; } last = np; } return 0; } /** Layout items on the dock. */ void UpdateDock() { XWindowAttributes attr; DockNode *np; int x, y; int width, height; int xoffset, yoffset; int itemWidth, itemHeight; double ratio; Assert(dock); /* Determine the size of items in the dock. */ if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) { itemWidth = dock->cp->height; itemHeight = dock->cp->height; } else { itemHeight = dock->cp->width; itemWidth = dock->cp->width; } x = 0; y = 0; for(np = dock->nodes; np; np = np->next) { xoffset = 0; yoffset = 0; width = itemWidth; height = itemHeight; if(JXGetWindowAttributes(display, np->window, &attr)) { ratio = (double)attr.width / attr.height; if(ratio > 1.0) { if(width > attr.width) { width = attr.width; } height = width / ratio; } else { if(height > attr.height) { height = attr.height; } width = height * ratio; } xoffset = (itemWidth - width) / 2; yoffset = (itemHeight - height) / 2; } JXMoveResizeWindow(display, np->window, x + xoffset, y + yoffset, width, height); /* Reparent if this window likes to go other places. */ if(np->needs_reparent) { JXReparentWindow(display, np->window, dock->cp->window, x + xoffset, y + yoffset); } if(orientation == SYSTEM_TRAY_ORIENTATION_HORZ) { x += itemWidth; } else { y += itemHeight; } } }