e16/src/systray.c

504 lines
12 KiB
C

/*
* Copyright (C) 2004-2021 Kim Woelders
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies of the Software, its documentation and marketing & publicity
* materials, and acknowledgment shall be given in the documentation, materials
* and software packages that this Software was used.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <X11/Xlib.h>
#include "E.h"
#include "container.h"
#include "events.h"
#include "ewins.h"
#include "hints.h"
#include "xprop.h"
#include "xwin.h"
#define DEBUG_SYSTRAY 0
/* Systray object info */
typedef struct {
Win win;
char mapped;
} SWin;
#define StObjGetWin(o) (((SWin*)(o))->win)
#define StObjIsMapped(o) (((SWin*)(o))->mapped)
/* XEmbed atoms */
static EX_Atom E_XA__XEMBED = 0;
static EX_Atom E_XA__XEMBED_INFO = 0;
/* Systray atoms */
static EX_Atom _NET_SYSTEM_TRAY_OPCODE = 0;
static EX_Atom _NET_SYSTEM_TRAY_MESSAGE_DATA = 0;
/* Systray selection */
static ESelection *systray_sel = NULL;
static void SystrayItemEvent(Win win, XEvent * ev, void *prm);
#define SYSTEM_TRAY_REQUEST_DOCK 0
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
/* _XEMBED client message */
#define XEMBED_EMBEDDED_NOTIFY 0
/* _XEMBED_INFO property */
#define XEMBED_MAPPED (1 << 0)
static int
SystrayGetXembedInfo(EX_Window xwin, unsigned int *info)
{
int num;
EGrabServer();
if (!EXWindowOk(xwin))
{
/* Invalid window */
num = -1;
goto done;
}
num = ex_window_prop_xid_get(xwin, E_XA__XEMBED_INFO,
E_XA__XEMBED_INFO, info, 2);
if (num < 2)
{
/* Property invalid or not there. */
info[0] = 0; /* Set protocol version 0 */
info[1] = XEMBED_MAPPED; /* Set mapped */
num = 0;
}
done:
EUngrabServer();
return num;
}
/*
* Return index, -1 if not found.
*/
static int
SystrayObjFind(Container *ct, EX_Window xwin)
{
int i;
for (i = 0; i < ct->num_objs; i++)
if (xwin == WinGetXwin(StObjGetWin(ct->objs[i].obj)))
return i;
return -1;
}
static Win
SystrayObjManage(Container *ct, EX_Window xwin)
{
Win win;
#if DEBUG_SYSTRAY
Eprintf("%s: %#x\n", __func__, xwin);
#endif
win = ERegisterWindow(xwin, NULL);
if (!win)
return win;
ESelectInput(win, StructureNotifyMask | PropertyChangeMask);
EventCallbackRegister(win, SystrayItemEvent, ct);
EReparentWindow(win, ct->icon_win, 0, 0);
XAddToSaveSet(disp, xwin);
return win;
}
static void
SystrayObjUnmanage(Container *ct __UNUSED__, Win win, int gone)
{
#if DEBUG_SYSTRAY
Eprintf("%s: %#x gone=%d\n", __func__, WinGetXwin(win), gone);
#endif
if (!gone)
{
ESelectInput(win, NoEventMask);
EUnmapWindow(win);
EReparentWindow(win, VROOT, 0, 0);
XRemoveFromSaveSet(disp, WinGetXwin(win));
}
EventCallbackUnregister(win, SystrayItemEvent, ct);
EUnregisterWindow(win);
}
static void
SystrayObjAdd(Container *ct, EX_Window xwin)
{
SWin *swin = NULL;
Win win;
unsigned int xembed_info[2];
/* Not if already there */
if (SystrayObjFind(ct, xwin) >= 0)
return;
EGrabServer();
switch (SystrayGetXembedInfo(xwin, xembed_info))
{
case -1: /* Error - assume invalid window */
Eprintf("%s: %#x: Hmm.. Invalid window? Ignoring.\n", __func__, xwin);
goto bail_out;
case 0: /* Assume broken - proceed anyway */
Eprintf("%s: %#x: Hmm.. No _XEMBED_INFO?\n", __func__, xwin);
break;
default:
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: %#x: _XEMBED_INFO: %u %u\n", __func__, xwin,
xembed_info[0], xembed_info[1]);
break;
}
swin = EMALLOC(SWin, 1);
if (!swin)
goto bail_out;
if (ContainerObjectAdd(ct, swin) < 0)
goto bail_out;
win = SystrayObjManage(ct, xwin);
if (!win)
goto bail_out;
swin->win = win;
swin->mapped = (xembed_info[1] & XEMBED_MAPPED) != 0;
if (swin->mapped)
EMapWindow(win);
/* TBD - Always set protocol version as reported by client */
ex_client_message32_send(xwin, E_XA__XEMBED, NoEventMask,
CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0,
xwin, xembed_info[0]);
EUngrabServer();
return; /* Success */
bail_out:
EUngrabServer();
if (!swin)
return;
ContainerObjectDel(ct, swin);
Efree(swin);
}
static void
SystrayObjDel(Container *ct, Win win, int gone)
{
int i;
SWin *swin;
i = SystrayObjFind(ct, WinGetXwin(win));
if (i < 0)
return;
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: %#x\n", __func__, WinGetXwin(win));
swin = (SWin *) ct->objs[i].obj;
ContainerObjectDel(ct, swin);
if (disp)
SystrayObjUnmanage(ct, swin->win, gone);
Efree(swin);
}
static void
SystrayObjMapUnmap(Container *ct, EX_Window xwin)
{
int i, map;
SWin *swin;
unsigned int xembed_info[2];
i = SystrayObjFind(ct, xwin);
if (i < 0)
return;
swin = (SWin *) ct->objs[i].obj;
if (SystrayGetXembedInfo(xwin, xembed_info) >= 0)
{
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: %#x: _XEMBED_INFO: %u %u\n", __func__, xwin,
xembed_info[0], xembed_info[1]);
map = (xembed_info[1] & XEMBED_MAPPED) != 0;
if (map == swin->mapped)
return;
if (map)
EMapWindow(swin->win);
else
EUnmapWindow(swin->win);
}
else
{
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: %#x: _XEMBED_INFO: gone?\n", __func__, xwin);
map = 0;
if (map == swin->mapped)
return;
}
swin->mapped = map;
ContainerRedraw(ct);
}
static void
SystrayEventClientMessage(Container *ct, XClientMessageEvent *ev)
{
EX_Window xwin;
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: ev->type=%ld ev->data.l: %#lx %#lx %#lx %#lx\n", __func__,
ev->message_type,
ev->data.l[0], ev->data.l[1], ev->data.l[2], ev->data.l[3]);
if (ev->message_type == _NET_SYSTEM_TRAY_OPCODE)
{
xwin = ev->data.l[2];
if (xwin == NoXID)
goto done;
SystrayObjAdd(ct, xwin);
}
else if (ev->message_type == _NET_SYSTEM_TRAY_MESSAGE_DATA)
{
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: Got data message\n", __func__);
}
done:
;
}
static void
SystrayEventClientProperty(Container *ct, XPropertyEvent *ev)
{
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: %#lx %ld\n", __func__, ev->window, ev->atom);
if (ev->atom == E_XA__XEMBED_INFO)
{
SystrayObjMapUnmap(ct, ev->window);
}
}
static void
SystraySelectionEvent(Win win __UNUSED__, XEvent *ev, void *prm)
{
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: %2d %#lx\n", __func__, ev->type, ev->xany.window);
switch (ev->type)
{
default:
Eprintf("%s: ??? %2d %#lx\n", __func__, ev->type, ev->xany.window);
break;
case SelectionClear:
DialogOK(_("Systray Error!"), _("Systray went elsewhere?!?"));
SelectionRelease(systray_sel);
systray_sel = NoXID;
EwinHide(((Container *) prm)->ewin);
break;
case ClientMessage:
SystrayEventClientMessage((Container *) prm, &(ev->xclient));
break;
}
}
static void
SystrayEvent(Win _win __UNUSED__, XEvent *ev, void *prm __UNUSED__)
{
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: %2d %#lx\n", __func__, ev->type, ev->xany.window);
#if 0 /* FIXME - Need this one at all? ConfigureRequest? */
EX_Window xwin;
switch (ev->type)
{
case MapNotify:
EWindowSync(ELookupXwin(ev->xmap.window));
ContainerRedraw(prm);
break;
case DestroyNotify:
xwin = ev->xdestroywindow.window;
goto do_terminate;
case ReparentNotify:
case EX_EVENT_REPARENT_GONE:
/* Terminate if reparenting away from systray */
if (ev->xreparent.parent == ev->xreparent.event)
break;
xwin = ev->xreparent.window;
goto do_terminate;
do_terminate:
SystrayObjDel(prm, xwin);
break;
}
#endif
}
static void
SystrayItemEvent(Win win, XEvent *ev, void *prm)
{
Container *ct = (Container *) prm;
if (EDebug(EDBUG_TYPE_ICONBOX))
Eprintf("%s: %2d %#lx\n", __func__, ev->type, ev->xany.window);
switch (ev->type)
{
case MapNotify:
EWindowSync(win);
ContainerRedraw(ct);
break;
case DestroyNotify:
goto do_terminate;
case ReparentNotify:
case EX_EVENT_REPARENT_GONE:
/* Terminate if reparenting away from systray */
if (ev->xreparent.parent == WinGetXwin(ct->icon_win))
break;
goto do_terminate;
case ClientMessage:
SystrayEventClientMessage(ct, &(ev->xclient));
break;
case PropertyNotify:
SystrayEventClientProperty(ct, &(ev->xproperty));
break;
do_terminate:
SystrayObjDel(ct, win, ev->type != ReparentNotify);
ContainerRedraw(ct);
break;
}
}
static void
SystrayInit(Container *ct)
{
Win win;
E_XA__XEMBED = ex_atom_get("_XEMBED");
E_XA__XEMBED_INFO = ex_atom_get("_XEMBED_INFO");
_NET_SYSTEM_TRAY_OPCODE = ex_atom_get("_NET_SYSTEM_TRAY_OPCODE");
_NET_SYSTEM_TRAY_MESSAGE_DATA =
ex_atom_get("_NET_SYSTEM_TRAY_MESSAGE_DATA");
/* Acquire selection */
if (systray_sel)
{
DialogOK(_("Systray Error!"), _("Only one systray is allowed"));
return;
}
systray_sel =
SelectionAcquire("_NET_SYSTEM_TRAY_S", SystraySelectionEvent, ct);
if (!systray_sel)
{
DialogOK(_("Systray Error!"), _("Could not activate systray"));
return;
}
win = ct->icon_win;
ESelectInputChange(win, SubstructureRedirectMask, 0);
EventCallbackRegister(win, SystrayEvent, ct);
/* Container parameter setup */
ct->wm_name = "Systray";
ct->menu_title = _("Systray Options");
ct->dlg_title = _("Systray Settings");
ct->iconsize = 32;
}
static void
SystrayExit(Container *ct, int wm_exit __UNUSED__)
{
SelectionRelease(systray_sel);
systray_sel = NoXID;
EventCallbackUnregister(ct->win, SystrayEvent, ct);
while (ct->num_objs)
{
SystrayObjDel(ct, StObjGetWin(ct->objs[0].obj), 0);
}
}
static void
SystrayObjSizeCalc(Container *ct, ContainerObject *cto)
{
/* Inner size */
if (StObjIsMapped(cto->obj))
cto->wi = cto->hi = ct->iconsize;
else
cto->wi = cto->hi = 0;
}
static void
SystrayObjPlace(Container *ct __UNUSED__, ContainerObject *cto,
EImage *im __UNUSED__)
{
if (StObjIsMapped(cto->obj))
{
EMoveResizeWindow(StObjGetWin(cto->obj), cto->xi, cto->yi, cto->wi,
cto->hi);
/* This seems to fix rendering for ceratin apps which seem to expect
* expose events after resize (e.g. opera) */
ESync(0);
EClearWindowExpose(StObjGetWin(cto->obj));
}
}
extern const ContainerOps SystrayOps;
const ContainerOps SystrayOps = {
SystrayInit,
SystrayExit,
NULL,
NULL,
SystrayObjSizeCalc,
SystrayObjPlace,
};