fluxbox/src/SystemTray.cc

562 lines
18 KiB
C++
Raw Normal View History

2003-08-15 13:48:50 +00:00
// SystemTray.cc
2006-02-16 06:53:05 +00:00
// Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
2003-08-15 13:48:50 +00:00
//
// 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"
2009-05-25 04:47:15 +00:00
#include "FbTk/SimpleObserver.hh"
2003-08-15 13:48:50 +00:00
#include "AtomHandler.hh"
#include "fluxbox.hh"
2004-04-19 22:48:19 +00:00
#include "WinClient.hh"
#include "Screen.hh"
#include "ButtonTheme.hh"
#include "Debug.hh"
2003-08-15 13:48:50 +00:00
2003-08-16 12:12:21 +00:00
#include <X11/Xutil.h>
2004-04-19 22:48:19 +00:00
#include <X11/Xatom.h>
2003-08-16 12:12:21 +00:00
2003-08-15 13:48:50 +00:00
#include <string>
2006-10-30 19:31:15 +00:00
using std::string;
2006-10-30 19:31:15 +00:00
using std::endl;
using std::hex;
using std::dec;
2003-08-15 13:48:50 +00:00
/// helper class for tray windows, so we dont call XDestroyWindow
class TrayWindow: public FbTk::FbWindow {
public:
2007-08-06 04:19:48 +00:00
TrayWindow(Window win, bool using_xembed):FbTk::FbWindow(win), m_visible(false), m_xembedded(using_xembed) {
setEventMask(PropertyChangeMask);
}
2007-08-04 17:14:13 +00:00
bool isVisible() { return m_visible; }
2007-08-06 04:19:48 +00:00
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();
}
}
2007-08-04 17:14:13 +00:00
2007-08-06 04:19:48 +00:00
/* 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;
bool mapped = false;
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) {
mapped = (bool)(static_cast<unsigned long>(prop[1]) & XEMBED_MAPPED);
XFree(static_cast<void *>(prop));
fbdbg<<"(SystemTray::TrayWindow::getMappedDefault(): XEMBED_MAPPED = "<<mapped<<endl;
2007-08-06 04:19:48 +00:00
}
return true;
}
private:
bool m_visible;
2007-08-06 04:19:48 +00:00
bool m_xembedded; // using xembed protocol? (i.e. unmap when done)
2003-08-15 13:48:50 +00:00
};
/// 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,
2003-08-15 13:48:50 +00:00
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;
2003-08-15 13:48:50 +00:00
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;
2004-04-19 22:48:19 +00:00
// we dont want a managed window
if (winclient.fbwindow() != 0)
return;
// if not kde dockapp...
if (!winclient.screen().isKdeDockapp(winclient.window()))
return;
2004-06-20 10:29:51 +00:00
// if not our screen...
if (winclient.screenNumber() != m_tray.window().screenNumber())
return;
2004-04-19 22:48:19 +00:00
winclient.setEventMask(StructureNotifyMask |
SubstructureNotifyMask | EnterWindowMask);
2007-08-06 04:19:48 +00:00
m_tray.addClient(winclient.window(), false);
2004-04-19 22:48:19 +00:00
};
2003-08-15 13:48:50 +00:00
2004-01-19 18:26:25 +00:00
void updateWorkarea(BScreen &) { }
void updateFocusedWindow(BScreen &, Window) { }
2003-08-15 13:48:50 +00:00
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;
};
2008-01-05 01:39:19 +00:00
SystemTray::SystemTray(const FbTk::FbWindow& parent,
2008-04-15 08:55:14 +00:00
FbTk::ThemeProxy<ToolTheme> &theme, BScreen& screen):
2003-08-15 13:48:50 +00:00
ToolbarItem(ToolbarItem::FIXED),
m_window(parent, 0, 0, 1, 1, ExposureMask | ButtonPressMask | ButtonReleaseMask |
SubstructureNotifyMask | SubstructureRedirectMask),
m_theme(theme),
m_screen(screen),
2007-08-04 17:14:13 +00:00
m_pixmap(0), m_num_visible_clients(0),
m_selection_owner(m_window, 0, 0, 1, 1, SubstructureNotifyMask, false, false, CopyFromParent, InputOnly) {
2003-08-15 13:48:50 +00:00
FbTk::EventManager::instance()->add(*this, m_window);
2007-08-04 17:14:13 +00:00
FbTk::EventManager::instance()->add(*this, m_selection_owner);
// setup signals
m_observer.reset(makeObserver(*this, &SystemTray::update));
m_theme->reconfigSig().attach(m_observer.get());
join(screen.bgChangeSig(),
FbTk::MemFun(*this, &SystemTray::updateForScreen));
2003-08-15 13:48:50 +00:00
Fluxbox* fluxbox = Fluxbox::instance();
Display *disp = fluxbox->display();
2004-06-20 10:29:51 +00:00
2003-08-15 13:48:50 +00:00
// get selection owner and see if it's free
string atom_name = getNetSystemTrayAtom(m_window.screenNumber());
2003-08-15 13:48:50 +00:00
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;
2003-08-15 13:48:50 +00:00
return; // the're can't be more than one owner
}
2003-08-15 13:48:50 +00:00
// ok, it was free. Lets set owner
fbdbg<<"(SystemTray(const FbTk::FbWindow)): SETTING OWNER!"<<endl;
2003-08-15 13:48:50 +00:00
// set owner
2007-08-04 17:14:13 +00:00
XSetSelectionOwner(disp, tray_atom, m_selection_owner.window(), CurrentTime);
2003-08-15 13:48:50 +00:00
m_handler.reset(new SystemTrayHandler(*this));
fluxbox->addAtomHandler(m_handler.get(), atom_name);
2003-08-15 13:48:50 +00:00
// send selection owner msg
Window root_window = m_screen.rootWindow().window();
2003-08-15 13:48:50 +00:00
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
2007-08-06 04:19:48 +00:00
ce.xclient.data.l[2] = m_selection_owner.window(); // the window owning the selection
2003-08-15 13:48:50 +00:00
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();
2003-08-15 13:48:50 +00:00
}
SystemTray::~SystemTray() {
// remove us, else fluxbox might delete the memory too
2007-08-04 17:14:13 +00:00
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());
2007-08-04 17:14:13 +00:00
Atom tray_atom = XInternAtom(disp, atom_name.c_str(), False);
// Properly give up selection.
XSetSelectionOwner(disp, tray_atom, None, CurrentTime);
2003-08-15 13:48:50 +00:00
removeAllClients();
if (m_pixmap)
m_screen.imageControl().removeImage(m_pixmap);
2004-05-04 14:33:38 +00:00
// ~FbWindow cleans EventManager
2003-08-15 13:48:50 +00:00
}
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()) {
2004-06-20 10:29:51 +00:00
m_window.resize(width, height);
if (m_num_visible_clients)
2004-06-20 10:29:51 +00:00
rearrangeClients();
resizeSig().notify();
2003-08-15 13:48:50 +00:00
}
}
void SystemTray::moveResize(int x, int y,
unsigned int width, unsigned int height) {
2004-06-20 10:29:51 +00:00
if (width != m_window.width() ||
height != m_window.height()) {
m_window.moveResize(x, y, width, height);
if (m_num_visible_clients)
2004-06-20 10:29:51 +00:00
rearrangeClients();
resizeSig().notify();
2004-06-20 10:29:51 +00:00
} else {
move(x, y);
}
2003-08-15 13:48:50 +00:00
}
void SystemTray::hide() {
m_window.hide();
}
void SystemTray::show() {
update();
2003-08-15 13:48:50 +00:00
m_window.show();
}
unsigned int SystemTray::width() const {
if (orientation() == FbTk::ROT90 || orientation() == FbTk::ROT270)
return m_window.width();
2008-01-05 01:39:19 +00:00
return m_num_visible_clients * (height() - 2 * m_theme->border().width());
2003-08-15 13:48:50 +00:00
}
unsigned int SystemTray::height() const {
if (orientation() == FbTk::ROT0 || orientation() == FbTk::ROT180)
return m_window.height();
2008-01-05 01:39:19 +00:00
return m_num_visible_clients * (width() - 2 * m_theme->border().width());
2003-08-15 13:48:50 +00:00
}
unsigned int SystemTray::borderWidth() const {
return m_window.borderWidth();
}
bool SystemTray::clientMessage(const XClientMessageEvent &event) {
static const int SYSTEM_TRAY_REQUEST_DOCK = 0;
2003-08-27 00:11:24 +00:00
// static const int SYSTEM_TRAY_BEGIN_MESSAGE = 1;
// static const int SYSTEM_TRAY_CANCEL_MESSAGE = 2;
2007-08-06 04:19:48 +00:00
static Atom systray_opcode_atom = XInternAtom(FbTk::App::instance()->display(), "_NET_SYSTEM_TRAY_OPCODE", False);
2003-08-15 13:48:50 +00:00
2007-08-06 04:19:48 +00:00
if (event.message_type == systray_opcode_atom) {
2003-08-15 13:48:50 +00:00
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;
2007-08-06 04:19:48 +00:00
addClient(event.data.l[2], true);
2003-08-15 13:48:50 +00:00
}
2003-08-27 00:11:24 +00:00
/*
2003-08-15 13:48:50 +00:00
else if (type == SYSTEM_TRAY_BEGIN_MESSAGE)
fbdbg<<"BEGIN MESSAGE"<<endl;
2003-08-15 13:48:50 +00:00
else if (type == SYSTEM_TRAY_CANCEL_MESSAGE)
fbdbg<<"CANCEL MESSAGE"<<endl;
2003-08-27 00:11:24 +00:00
*/
2003-08-15 13:48:50 +00:00
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;
}
2007-08-06 04:19:48 +00:00
void SystemTray::addClient(Window win, bool using_xembed) {
2003-08-15 13:48:50 +00:00
if (win == 0)
return;
ClientList::iterator it = findClient(win);
if (it != m_clients.end())
return;
2007-08-06 04:19:48 +00:00
Display *disp = Fluxbox::instance()->display();
2004-09-01 08:46:55 +00:00
// make sure we have the same screen number
XWindowAttributes attr;
attr.screen = 0;
2007-08-06 04:19:48 +00:00
if (XGetWindowAttributes(disp, win, &attr) != 0 &&
attr.screen != 0 &&
2004-09-01 08:46:55 +00:00
XScreenNumberOfScreen(attr.screen) != window().screenNumber()) {
return;
}
2004-09-01 08:46:55 +00:00
2007-08-06 04:19:48 +00:00
TrayWindow *traywin = new TrayWindow(win, using_xembed);
2004-09-01 08:46:55 +00:00
fbdbg<<"SystemTray::addClient(Window): 0x"<<hex<<win<<dec<<endl;
2003-08-15 13:48:50 +00:00
m_clients.push_back(traywin);
FbTk::EventManager::instance()->add(*this, win);
traywin->reparent(m_window, 0, 0);
2007-08-04 17:14:13 +00:00
traywin->addToSaveSet();
2007-08-06 04:19:48 +00:00
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);
2003-08-15 13:48:50 +00:00
}
2007-08-04 17:14:13 +00:00
void SystemTray::removeClient(Window win, bool destroyed) {
2003-08-15 13:48:50 +00:00
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;
2003-08-15 13:48:50 +00:00
m_clients.erase(tray_it);
2007-08-04 17:14:13 +00:00
if (!destroyed) {
traywin->setEventMask(NoEventMask);
traywin->removeFromSaveSet();
}
hideClient(traywin, destroyed);
2003-08-15 13:48:50 +00:00
delete traywin;
}
void SystemTray::exposeEvent(XExposeEvent &event) {
m_window.clear();
}
void SystemTray::handleEvent(XEvent &event) {
2003-08-28 13:44:58 +00:00
if (event.type == DestroyNotify) {
2007-08-04 17:14:13 +00:00
removeClient(event.xdestroywindow.window, true);
} else if (event.type == ReparentNotify && event.xreparent.parent != m_window.window()) {
2007-08-04 17:14:13 +00:00
removeClient(event.xreparent.window, false);
} else if (event.type == UnmapNotify && event.xany.send_event) {
2004-04-19 22:48:19 +00:00
// 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);
2003-08-28 13:44:58 +00:00
} else if (event.type == ConfigureNotify) {
2003-08-15 13:48:50 +00:00
// 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
2003-08-15 13:48:50 +00:00
ClientList::iterator it = findClient(event.xconfigure.window);
if (it != m_clients.end()) {
if (static_cast<unsigned int>(event.xconfigure.width) != (*it)->width() ||
2004-06-20 10:29:51 +00:00
static_cast<unsigned int>(event.xconfigure.height) != (*it)->height()) {
// the position might differ so we update from our local
// copy of position
2006-10-30 19:31:15 +00:00
XMoveResizeWindow(FbTk::App::instance()->display(), (*it)->window(),
2005-05-17 11:24:50 +00:00
(*it)->x(), (*it)->y(),
(*it)->width(), (*it)->height());
2005-05-17 11:24:50 +00:00
2005-04-26 04:18:10 +00:00
// 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().notify();
2004-06-20 10:29:51 +00:00
}
2003-08-15 13:48:50 +00:00
}
} else if (event.type == PropertyNotify) {
ClientList::iterator it = findClient(event.xproperty.window);
if (it != m_clients.end()) {
2007-08-06 04:19:48 +00:00
if (event.xproperty.atom == getXEmbedInfoAtom()) {
if ((*it)->getMappedDefault())
showClient(*it);
else
hideClient(*it);
}
}
2007-08-06 04:19:48 +00:00
}
2003-08-15 13:48:50 +00:00
}
void SystemTray::rearrangeClients() {
unsigned int w_rot0 = width(), h_rot0 = height();
2008-01-05 01:39:19 +00:00
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();
2005-04-26 04:18:10 +00:00
// move and resize clients
2003-08-15 13:48:50 +00:00
ClientList::iterator client_it = m_clients.begin();
ClientList::iterator client_it_end = m_clients.end();
2005-04-26 04:18:10 +00:00
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);
(*client_it)->moveResize(x, y, h_rot0, h_rot0);
(*client_it)->sendConfigureNotify(x, y, h_rot0, h_rot0);
2003-08-15 13:48:50 +00:00
}
}
void SystemTray::removeAllClients() {
BScreen *screen = Fluxbox::instance()->findScreen(window().screenNumber());
2003-08-15 13:48:50 +00:00
while (!m_clients.empty()) {
2007-08-06 04:19:48 +00:00
TrayWindow * traywin = m_clients.back();
traywin->setEventMask(NoEventMask);
if (traywin->isXEmbedded())
traywin->hide();
if (screen)
2007-08-06 04:19:48 +00:00
traywin->reparent(screen->rootWindow(), 0, 0, false);
traywin->removeFromSaveSet();
delete traywin;
2003-08-15 13:48:50 +00:00
m_clients.pop_back();
}
m_num_visible_clients = 0;
}
2007-08-04 17:14:13 +00:00
void SystemTray::hideClient(TrayWindow *traywin, bool destroyed) {
if (!traywin || !traywin->isVisible())
return;
2007-08-04 17:14:13 +00:00
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();
2003-08-15 13:48:50 +00:00
}
void SystemTray::update() {
2008-01-05 01:39:19 +00:00
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(),
2008-01-05 01:39:19 +00:00
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();
}
}
2007-08-06 04:19:48 +00:00
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;
2007-08-06 04:19:48 +00:00
}