fluxbox/src/SystemTray.cc
Thomas Lübking 2c66471126 Replay toolbar button events
NOTICE!!!! THIS IS HIGHLY EXPERIMENTAL!

The patch alters the button grab mode to GrabSync
in order to ReplayPointer the event.
THIS CAN FREEZE ANY INPUT TO FLUXBOX!!!

The toolbar (and other things?) grab buttons in order to
handle MouseN events for the entire bar, INCLUDING all child
windows.

This causes two problems:
1. The bar handles events which are not meant for fluxbox at all
   (but the systray icons)
BUG: 940

2. The bar will intercept (and suck) *every* press, even if only
   doubleclicks are desired
BUG: 949

The problem with this patch is that an oversight here has the potential
to completely freeze input event processing in fluxbox (ie. the process
needs to be killed from outside), SO IT NEEDS TESTING!
As much as possible.
2016-08-27 09:36:19 +02:00

604 lines
19 KiB
C++

// SystemTray.cc
// Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
//
// 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 or substantial portions of the Software.
//
// 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 OR COPYRIGHT HOLDERS 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 "SystemTray.hh"
#include "FbTk/EventManager.hh"
#include "FbTk/ImageControl.hh"
#include "FbTk/TextUtils.hh"
#include "FbTk/MemFun.hh"
#include "AtomHandler.hh"
#include "fluxbox.hh"
#include "WinClient.hh"
#include "Screen.hh"
#include "ButtonTheme.hh"
#include "Debug.hh"
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <string>
using std::string;
using std::endl;
using std::hex;
using std::dec;
namespace {
void getScreenCoordinates(Window win, int x, int y, int &screen_x, int &screen_y) {
XWindowAttributes attr;
if (XGetWindowAttributes(FbTk::App::instance()->display(), win, &attr) == 0) {
return;
}
Window unused_win;
Window parent_win;
Window root_win = 0;
Window* unused_childs = 0;
unsigned int unused_number;
XQueryTree(FbTk::App::instance()->display(), win,
&root_win,
&parent_win,
&unused_childs, &unused_number);
if (unused_childs != 0) {
XFree(unused_childs);
}
XTranslateCoordinates(FbTk::App::instance()->display(),
parent_win, root_win,
x, y,
&screen_x, &screen_y, &unused_win);
}
};
static SystemTray *s_theoneandonly = 0;
/// helper class for tray windows, so we dont call XDestroyWindow
class SystemTray::TrayWindow : public FbTk::FbWindow {
public:
TrayWindow(Window win, bool using_xembed):FbTk::FbWindow(win), m_visible(false), m_xembedded(using_xembed) {
setEventMask(PropertyChangeMask);
}
bool isVisible() { return m_visible; }
bool isXEmbedded() { return m_xembedded; }
void show() {
if (!m_visible) {
m_visible = true;
FbTk::FbWindow::show();
}
}
void hide() {
if (m_visible) {
m_visible = false;
FbTk::FbWindow::hide();
}
}
/* Flags for _XEMBED_INFO */
#define XEMBED_MAPPED (1 << 0)
bool getMappedDefault() const {
Atom actual_type;
int actual_format;
unsigned long nitems, bytes_after;
unsigned long *prop;
Atom embed_info = SystemTray::getXEmbedInfoAtom();
if (property(embed_info, 0l, 2l, false, embed_info,
&actual_type, &actual_format, &nitems, &bytes_after,
(unsigned char **) &prop) && prop != 0) {
XFree(static_cast<void *>(prop));
fbdbg << "(SystemTray::TrayWindow::getMappedDefault(): XEMBED_MAPPED = "
<< (bool)(static_cast<unsigned long>(prop[1]) & XEMBED_MAPPED)
<< endl;
}
return true;
}
private:
bool m_visible;
bool m_xembedded; // using xembed protocol? (i.e. unmap when done)
};
/// handles clientmessage event and notifies systemtray
class SystemTrayHandler: public AtomHandler {
public:
SystemTrayHandler(SystemTray &tray):m_tray(tray) {
}
// client message is the only thing we care about
bool checkClientMessage(const XClientMessageEvent &ce,
BScreen * screen, WinClient * const winclient) {
// must be on the same screen
if ((screen && screen->screenNumber() != m_tray.window().screenNumber()) ||
(winclient && winclient->screenNumber() != m_tray.window().screenNumber()) )
return false;
return m_tray.clientMessage(ce);
}
void initForScreen(BScreen &screen) { };
void setupFrame(FluxboxWindow &win) { };
void setupClient(WinClient &winclient) {
// must be on the same screen
if (winclient.screenNumber() != m_tray.window().screenNumber())
return;
// we dont want a managed window
if (winclient.fbwindow() != 0)
return;
// if not kde dockapp...
if (!winclient.screen().isKdeDockapp(winclient.window()))
return;
// if not our screen...
if (winclient.screenNumber() != m_tray.window().screenNumber())
return;
winclient.setEventMask(StructureNotifyMask |
SubstructureNotifyMask | EnterWindowMask);
m_tray.addClient(winclient.window(), false);
};
void updateWorkarea(BScreen &) { }
void updateFocusedWindow(BScreen &, Window) { }
void updateClientList(BScreen &screen) { };
void updateWorkspaceNames(BScreen &screen) { };
void updateCurrentWorkspace(BScreen &screen) { };
void updateWorkspaceCount(BScreen &screen) { };
void updateFrameClose(FluxboxWindow &win) { };
void updateClientClose(WinClient &winclient) { };
void updateWorkspace(FluxboxWindow &win) { };
void updateState(FluxboxWindow &win) { };
void updateHints(FluxboxWindow &win) { };
void updateLayer(FluxboxWindow &win) { };
virtual bool propertyNotify(WinClient &winclient, Atom the_property) { return false; }
private:
SystemTray &m_tray;
};
SystemTray::SystemTray(const FbTk::FbWindow& parent,
FbTk::ThemeProxy<ToolTheme> &theme, BScreen& screen):
ToolbarItem(ToolbarItem::FIXED),
m_window(parent, 0, 0, 1, 1, ExposureMask | ButtonPressMask | ButtonReleaseMask |
SubstructureNotifyMask | SubstructureRedirectMask),
m_theme(theme),
m_screen(screen),
m_pixmap(0), m_num_visible_clients(0),
m_selection_owner(m_window, 0, 0, 1, 1, SubstructureNotifyMask, false, false, CopyFromParent, InputOnly) {
FbTk::EventManager::instance()->add(*this, m_window);
FbTk::EventManager::instance()->add(*this, m_selection_owner);
// setup signals
join(m_theme->reconfigSig(), FbTk::MemFun(*this, &SystemTray::update));
join(screen.bgChangeSig(),
FbTk::MemFunIgnoreArgs(*this, &SystemTray::update));
Fluxbox* fluxbox = Fluxbox::instance();
Display *disp = fluxbox->display();
// get selection owner and see if it's free
string atom_name = getNetSystemTrayAtom(m_window.screenNumber());
Atom tray_atom = XInternAtom(disp, atom_name.c_str(), False);
Window owner = XGetSelectionOwner(disp, tray_atom);
if (owner != 0) {
fbdbg<<"(SystemTray(const FbTk::FbWindow)): can't set owner!"<<endl;
return; // the're can't be more than one owner
}
// ok, it was free. Lets set owner
fbdbg<<"(SystemTray(const FbTk::FbWindow)): SETTING OWNER!"<<endl;
// set owner
XSetSelectionOwner(disp, tray_atom, m_selection_owner.window(), CurrentTime);
s_theoneandonly = this;
m_handler.reset(new SystemTrayHandler(*this));
m_handler.get()->setName(atom_name);
fluxbox->addAtomHandler(m_handler.get());
// send selection owner msg
Window root_window = m_screen.rootWindow().window();
XEvent ce;
ce.xclient.type = ClientMessage;
ce.xclient.message_type = XInternAtom(disp, "MANAGER", False);
ce.xclient.display = disp;
ce.xclient.window = root_window;
ce.xclient.format = 32;
ce.xclient.data.l[0] = CurrentTime; // timestamp
ce.xclient.data.l[1] = tray_atom; // manager selection atom
ce.xclient.data.l[2] = m_selection_owner.window(); // the window owning the selection
ce.xclient.data.l[3] = 0l; // selection specific data
ce.xclient.data.l[4] = 0l; // selection specific data
XSendEvent(disp, root_window, false, StructureNotifyMask, &ce);
update();
}
SystemTray::~SystemTray() {
// remove us, else fluxbox might delete the memory too
if (s_theoneandonly == this)
s_theoneandonly = 0;
Fluxbox* fluxbox = Fluxbox::instance();
fluxbox->removeAtomHandler(m_handler.get());
Display *disp = fluxbox->display();
// get selection owner and see if it's free
string atom_name = getNetSystemTrayAtom(m_window.screenNumber());
Atom tray_atom = XInternAtom(disp, atom_name.c_str(), False);
// Properly give up selection.
XSetSelectionOwner(disp, tray_atom, None, CurrentTime);
removeAllClients();
if (m_pixmap)
m_screen.imageControl().removeImage(m_pixmap);
// ~FbWindow cleans EventManager
}
void SystemTray::move(int x, int y) {
m_window.move(x, y);
}
void SystemTray::resize(unsigned int width, unsigned int height) {
if (width != m_window.width() ||
height != m_window.height()) {
m_window.resize(width, height);
if (m_num_visible_clients)
rearrangeClients();
resizeSig().emit();
}
}
void SystemTray::moveResize(int x, int y,
unsigned int width, unsigned int height) {
if (width != m_window.width() ||
height != m_window.height()) {
m_window.moveResize(x, y, width, height);
if (m_num_visible_clients)
rearrangeClients();
resizeSig().emit();
} else {
move(x, y);
}
}
void SystemTray::hide() {
m_window.hide();
}
void SystemTray::show() {
update();
m_window.show();
}
unsigned int SystemTray::width() const {
if (orientation() == FbTk::ROT90 || orientation() == FbTk::ROT270)
return m_window.width();
return m_num_visible_clients * (height() + 2 * m_theme->border().width());
}
unsigned int SystemTray::height() const {
if (orientation() == FbTk::ROT0 || orientation() == FbTk::ROT180)
return m_window.height();
return m_num_visible_clients * (width() + 2 * m_theme->border().width());
}
unsigned int SystemTray::borderWidth() const {
return m_window.borderWidth();
}
bool SystemTray::clientMessage(const XClientMessageEvent &event) {
static const int SYSTEM_TRAY_REQUEST_DOCK = 0;
// static const int SYSTEM_TRAY_BEGIN_MESSAGE = 1;
// static const int SYSTEM_TRAY_CANCEL_MESSAGE = 2;
static Atom systray_opcode_atom = XInternAtom(FbTk::App::instance()->display(), "_NET_SYSTEM_TRAY_OPCODE", False);
if (event.message_type == systray_opcode_atom) {
int type = event.data.l[1];
if (type == SYSTEM_TRAY_REQUEST_DOCK) {
fbdbg<<"SystemTray::clientMessage(const XClientMessageEvent): SYSTEM_TRAY_REQUEST_DOCK"<<endl;
fbdbg<<"window = event.data.l[2] = "<<event.data.l[2]<<endl;
addClient(event.data.l[2], true);
}
/*
else if (type == SYSTEM_TRAY_BEGIN_MESSAGE)
fbdbg<<"BEGIN MESSAGE"<<endl;
else if (type == SYSTEM_TRAY_CANCEL_MESSAGE)
fbdbg<<"CANCEL MESSAGE"<<endl;
*/
return true;
}
return false;
}
SystemTray::ClientList::iterator SystemTray::findClient(Window win) {
ClientList::iterator it = m_clients.begin();
ClientList::iterator it_end = m_clients.end();
for (; it != it_end; ++it) {
if ((*it)->window() == win)
break;
}
return it;
}
void SystemTray::addClient(Window win, bool using_xembed) {
if (win == 0)
return;
ClientList::iterator it = findClient(win);
if (it != m_clients.end())
return;
Display *disp = Fluxbox::instance()->display();
// make sure we have the same screen number
XWindowAttributes attr;
attr.screen = 0;
if (XGetWindowAttributes(disp, win, &attr) != 0 &&
attr.screen != 0 &&
XScreenNumberOfScreen(attr.screen) != window().screenNumber()) {
return;
}
TrayWindow *traywin = new TrayWindow(win, using_xembed);
fbdbg<<"SystemTray::addClient(Window): 0x"<<hex<<win<<dec<<endl;
m_clients.push_back(traywin);
FbTk::EventManager::instance()->add(*this, win);
traywin->reparent(m_window, 0, 0);
traywin->addToSaveSet();
if (using_xembed) {
static Atom xembed_atom = XInternAtom(disp, "_XEMBED", False);
#define XEMBED_EMBEDDED_NOTIFY 0
// send embedded message
XEvent ce;
ce.xclient.type = ClientMessage;
ce.xclient.message_type = xembed_atom;
ce.xclient.display = disp;
ce.xclient.window = win;
ce.xclient.format = 32;
ce.xclient.data.l[0] = CurrentTime; // timestamp
ce.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY;
ce.xclient.data.l[2] = 0l; // The protocol version we support
ce.xclient.data.l[3] = m_window.window(); // the window owning the selection
ce.xclient.data.l[4] = 0l; // unused
XSendEvent(disp, win, false, NoEventMask, &ce);
}
if (traywin->getMappedDefault())
showClient(traywin);
}
void SystemTray::removeClient(Window win, bool destroyed) {
ClientList::iterator tray_it = findClient(win);
if (tray_it == m_clients.end())
return;
fbdbg<<"(SystemTray::removeClient(Window)): 0x"<<hex<<win<<dec<<endl;
TrayWindow *traywin = *tray_it;
m_clients.erase(tray_it);
if (!destroyed) {
traywin->setEventMask(NoEventMask);
traywin->removeFromSaveSet();
}
hideClient(traywin, destroyed);
delete traywin;
}
void SystemTray::exposeEvent(XExposeEvent &event) {
m_window.clear();
}
void SystemTray::handleEvent(XEvent &event) {
if (event.type == DestroyNotify) {
removeClient(event.xdestroywindow.window, true);
} else if (event.type == ReparentNotify && event.xreparent.parent != m_window.window()) {
removeClient(event.xreparent.window, false);
} else if (event.type == UnmapNotify && event.xany.send_event) {
// we ignore server-generated events, which can occur
// on restart. The ICCCM says that a client must send
// a synthetic event for the withdrawn state
ClientList::iterator it = findClient(event.xunmap.window);
if (it != m_clients.end())
hideClient(*it);
} else if (event.type == ConfigureNotify) {
// we got configurenotify from an client
// check and see if we need to update it's size
// and we must reposition and resize them to fit
// our toolbar
ClientList::iterator it = findClient(event.xconfigure.window);
if (it != m_clients.end()) {
if (static_cast<unsigned int>(event.xconfigure.width) != (*it)->width() ||
static_cast<unsigned int>(event.xconfigure.height) != (*it)->height()) {
// the position might differ so we update from our local
// copy of position
XMoveResizeWindow(FbTk::App::instance()->display(), (*it)->window(),
(*it)->x(), (*it)->y(),
(*it)->width(), (*it)->height());
// this was why gaim wasn't centring the icon
(*it)->sendConfigureNotify(0, 0, (*it)->width(), (*it)->height());
// so toolbar know that we changed size
// done inside this loop, because otherwise we can get into nasty looping
resizeSig().emit();
}
}
} else if (event.type == PropertyNotify) {
ClientList::iterator it = findClient(event.xproperty.window);
if (it != m_clients.end()) {
if (event.xproperty.atom == getXEmbedInfoAtom()) {
if ((*it)->getMappedDefault())
showClient(*it);
else
hideClient(*it);
}
}
}
}
void SystemTray::rearrangeClients() {
unsigned int w_rot0 = width(), h_rot0 = height();
const unsigned int bw = m_theme->border().width();
FbTk::translateSize(orientation(), w_rot0, h_rot0);
unsigned int trayw = m_num_visible_clients*h_rot0 + bw, trayh = h_rot0;
FbTk::translateSize(orientation(), trayw, trayh);
resize(trayw, trayh);
update();
// move and resize clients
ClientList::iterator client_it = m_clients.begin();
ClientList::iterator client_it_end = m_clients.end();
int next_x = bw;
for (; client_it != client_it_end; ++client_it) {
if (!(*client_it)->isVisible())
continue;
int x = next_x, y = bw;
next_x += h_rot0+bw;
translateCoords(orientation(), x, y, w_rot0, h_rot0);
translatePosition(orientation(), x, y, h_rot0, h_rot0, 0);
int screen_x = 0, screen_y = 0;
getScreenCoordinates((*client_it)->window(), (*client_it)->x(), (*client_it)->y(), screen_x, screen_y);
(*client_it)->moveResize(x, y, h_rot0, h_rot0);
(*client_it)->sendConfigureNotify(screen_x, screen_y, h_rot0, h_rot0);
}
}
void SystemTray::removeAllClients() {
BScreen *screen = Fluxbox::instance()->findScreen(window().screenNumber());
while (!m_clients.empty()) {
TrayWindow * traywin = m_clients.back();
traywin->setEventMask(NoEventMask);
if (traywin->isXEmbedded())
traywin->hide();
if (screen)
traywin->reparent(screen->rootWindow(), 0, 0, false);
traywin->removeFromSaveSet();
delete traywin;
m_clients.pop_back();
}
m_num_visible_clients = 0;
}
void SystemTray::hideClient(TrayWindow *traywin, bool destroyed) {
if (!traywin || !traywin->isVisible())
return;
if (!destroyed)
traywin->hide();
m_num_visible_clients--;
rearrangeClients();
}
void SystemTray::showClient(TrayWindow *traywin) {
if (!traywin || traywin->isVisible())
return;
if (!m_num_visible_clients)
show();
traywin->show();
m_num_visible_clients++;
rearrangeClients();
}
void SystemTray::update() {
if (!m_theme->texture().usePixmap()) {
m_window.setBackgroundColor(m_theme->texture().color());
}
else {
if(m_pixmap)
m_screen.imageControl().removeImage(m_pixmap);
m_pixmap = m_screen.imageControl().renderImage(width(), height(),
m_theme->texture(), orientation());
m_window.setBackgroundPixmap(m_pixmap);
}
ClientList::iterator client_it = m_clients.begin();
ClientList::iterator client_it_end = m_clients.end();
for (; client_it != client_it_end; ++client_it) {
// maybe not the best solution (yet), force a refresh of the
// background of the client
if (!(*client_it)->isVisible())
continue;
(*client_it)->hide();
(*client_it)->show();
}
}
Atom SystemTray::getXEmbedInfoAtom() {
static Atom theatom = XInternAtom(Fluxbox::instance()->display(), "_XEMBED_INFO", False);
return theatom;
}
string SystemTray::getNetSystemTrayAtom(int screen_nr) {
string atom_name("_NET_SYSTEM_TRAY_S");
atom_name += FbTk::StringUtil::number2String(screen_nr);
return atom_name;
}
bool SystemTray::doesControl(Window win) {
if (win == None || !s_theoneandonly)
return false;
return win == s_theoneandonly->window().window() ||
s_theoneandonly->findClient(win) != s_theoneandonly->m_clients.end();
}