mirror of
https://github.com/edeproject/ede.git
synced 2023-08-10 21:13:03 +03:00
eed5749909
Also center ede-launch window.
1959 lines
53 KiB
C++
1959 lines
53 KiB
C++
//
|
|
// Client.cc for pekwm
|
|
// Copyright © 2002-2009 Claes Nästén <me{@}pekdon{.}net>
|
|
//
|
|
// client.cc for aewm++
|
|
// Copyright (C) 2000 Frank Hale <frankhale@yahoo.com>
|
|
// http://sapphire.sourceforge.net/
|
|
//
|
|
// This program is licensed under the GNU GPL.
|
|
// See the LICENSE file for more information.
|
|
//
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif // HAVE_CONFIG_H
|
|
|
|
#include <cstdio>
|
|
#include <iostream>
|
|
|
|
extern "C" {
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/Xutil.h>
|
|
#ifdef HAVE_SHAPE
|
|
#include <X11/extensions/shape.h>
|
|
#endif // HAVE_SHAPE
|
|
}
|
|
|
|
#include "Compat.hh" // setenv, unsetenv
|
|
#include "PWinObj.hh"
|
|
#include "PDecor.hh" // PDecor::TitleItem
|
|
#include "Client.hh"
|
|
#include "PScreen.hh"
|
|
#include "Config.hh"
|
|
#include "KeyGrabber.hh"
|
|
#include "Theme.hh"
|
|
#include "AutoProperties.hh"
|
|
#include "Frame.hh"
|
|
#include "Workspaces.hh"
|
|
#include "WindowManager.hh"
|
|
#include "PTexturePlain.hh"
|
|
#include "PImageIcon.hh"
|
|
#include "TextureHandler.hh"
|
|
|
|
#include "PMenu.hh"
|
|
#include "WORefMenu.hh"
|
|
|
|
using std::cerr;
|
|
using std::endl;
|
|
using std::find;
|
|
using std::list;
|
|
using std::string;
|
|
using std::vector;
|
|
using std::wstring;
|
|
|
|
list<Client*> Client::_client_list = list<Client*>();
|
|
vector<uint> Client::_clientid_list = vector<uint>();
|
|
|
|
Client::Client(Window new_client, bool is_new)
|
|
: PWinObj(),
|
|
_id(0), _size(0),
|
|
_transient(None), _strut(0), _icon(0),
|
|
_pid(0), _is_remote(false), _class_hint(0),
|
|
_alive(false), _marked(false),
|
|
_send_focus_message(false), _send_close_message(false),
|
|
_wm_hints_input(true), _cfg_request_lock(false),
|
|
_shaped(false), _extended_net_name(false)
|
|
{
|
|
// PWinObj attributes, required by validate etc.
|
|
_window = new_client;
|
|
_type = WO_CLIENT;
|
|
|
|
// Construct the client
|
|
PScreen::instance()->grabServer();
|
|
if (! validate() || ! getAndUpdateWindowAttributes()) {
|
|
PScreen::instance()->ungrabServer(true);
|
|
return;
|
|
}
|
|
|
|
// Get unique Client id
|
|
_id = findClientID();
|
|
_title.setId(_id);
|
|
_title.infoAdd(PDecor::TitleItem::INFO_ID);
|
|
|
|
if (PScreen::instance()->hasExtensionShape()) {
|
|
#ifdef HAVE_SHAPE
|
|
XShapeSelectInput(_dpy, _window, ShapeNotifyMask);
|
|
#endif // HAVE_SHAPE
|
|
}
|
|
|
|
XAddToSaveSet(_dpy, _window);
|
|
XSetWindowBorderWidth(_dpy, _window, 0);
|
|
|
|
// Load the Class hint before loading the autoprops and
|
|
// getting client title as we search for TitleRule in the autoprops
|
|
_class_hint = new ClassHint();
|
|
readClassRoleHints();
|
|
|
|
getWMNormalHints();
|
|
readName();
|
|
readIconName();
|
|
|
|
// cyclic dependency, getting the name requires quiering autoprops
|
|
_class_hint->title = _title.getReal();
|
|
|
|
// Get Autoproperties before EWMH as we need the cfg_deny
|
|
// property, however the _transient hint needs to be setup to
|
|
// avoid auto-grouping to be to greedy.
|
|
getTransientForHint();
|
|
|
|
AutoProperty *ap = readAutoprops(WindowManager::instance()->isStartup()
|
|
? APPLY_ON_NEW : APPLY_ON_START);
|
|
|
|
readHints();
|
|
|
|
// We need to set the state before acquiring a frame,
|
|
// so that Frame's state can match the state of the Client.
|
|
setInitialState();
|
|
|
|
findOrCreateFrame(ap);
|
|
|
|
// Grab keybindings and mousebutton actions
|
|
KeyGrabber::instance()->grabKeys(_window);
|
|
grabButtons();
|
|
|
|
// Tell the world about our state
|
|
updateEwmhStates();
|
|
|
|
PScreen::instance()->ungrabServer(true);
|
|
|
|
setMappedStateAndFocus(is_new, ap);
|
|
|
|
_alive = true;
|
|
|
|
findAndRaiseIfTransient();
|
|
|
|
// Finished creating the client, so now adding it to the client list.
|
|
woListAdd(this);
|
|
_wo_map[_window] = this;
|
|
_client_list.push_back(this);
|
|
}
|
|
|
|
//! @brief Client destructor
|
|
Client::~Client(void)
|
|
{
|
|
// Remove from lists
|
|
if (_transient) {
|
|
_transient->_transient_clients.remove(this);
|
|
_transient->removeObserver(this);
|
|
}
|
|
_wo_map.erase(_window);
|
|
woListRemove(this);
|
|
_client_list.remove(this);
|
|
|
|
returnClientID(_id);
|
|
|
|
PScreen::instance()->grabServer();
|
|
|
|
// removes gravity and moves it back to root if we are alive
|
|
bool focus = false;
|
|
if (_parent && (_parent->getType() == PWinObj::WO_FRAME)) {
|
|
focus = _parent->isFocused();
|
|
static_cast<PDecor*>(_parent)->removeChild(this);
|
|
}
|
|
|
|
// Focus the parent if we had focus before
|
|
if (focus && _transient) {
|
|
Frame *trans_frame = static_cast<Frame*>(_transient->getParent());
|
|
if (trans_frame->getActiveChild() == _transient) {
|
|
trans_frame->giveInputFocus();
|
|
}
|
|
}
|
|
|
|
// Clean up if the client still is alive, it'll be dead all times
|
|
// except when we exit pekwm
|
|
if (_alive) {
|
|
XUngrabButton(_dpy, AnyButton, AnyModifier, _window);
|
|
KeyGrabber::instance()->ungrabKeys(_window);
|
|
XRemoveFromSaveSet(_dpy, _window);
|
|
PWinObj::mapWindow();
|
|
}
|
|
|
|
// free names and size hint
|
|
if (_size) {
|
|
XFree(_size);
|
|
}
|
|
|
|
removeStrutHint();
|
|
|
|
if (_class_hint) {
|
|
delete _class_hint;
|
|
}
|
|
|
|
if (_icon) {
|
|
TextureHandler::instance()->returnTexture(_icon);
|
|
_icon = 0;
|
|
}
|
|
|
|
PScreen::instance()->ungrabServer(true);
|
|
}
|
|
|
|
/**
|
|
* Read basic window attributes including geometry and update the
|
|
* window attributes being listened to. Returns false if the client
|
|
* disappears during the check.
|
|
*/
|
|
bool
|
|
Client::getAndUpdateWindowAttributes(void)
|
|
{
|
|
XWindowAttributes attr;
|
|
if (! XGetWindowAttributes(_dpy, _window, &attr)) {
|
|
return false;
|
|
}
|
|
_gm.x = attr.x;
|
|
_gm.y = attr.y;
|
|
_gm.width = attr.width;
|
|
_gm.height = attr.height;
|
|
|
|
_cmap = attr.colormap;
|
|
_size = XAllocSizeHints();
|
|
|
|
XSetWindowAttributes sattr;
|
|
sattr.event_mask =
|
|
PropertyChangeMask|StructureNotifyMask|FocusChangeMask;
|
|
sattr.do_not_propagate_mask =
|
|
ButtonPressMask|ButtonReleaseMask|ButtonMotionMask;
|
|
|
|
// We don't want these masks to be propagated down to the frame
|
|
XChangeWindowAttributes(_dpy, _window, CWEventMask|CWDontPropagate, &sattr);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Find frame for client based on tagging, hints and
|
|
* autoproperties. Create a new one if not found and add the client.
|
|
*/
|
|
void
|
|
Client::findOrCreateFrame(AutoProperty *autoproperty)
|
|
{
|
|
if (! _parent) {
|
|
findTaggedFrame();
|
|
}
|
|
if (! _parent) {
|
|
findPreviousFrame();
|
|
}
|
|
|
|
// Apply Autoproperties again to override EWMH state. It's done twice as
|
|
// we need the cfg_deny property when reading the EWMH state.
|
|
if (autoproperty != 0) {
|
|
applyAutoprops(autoproperty);
|
|
if (! _parent) {
|
|
findAutoGroupFrame(autoproperty);
|
|
}
|
|
}
|
|
|
|
// if we don't have a frame already, create a new one
|
|
if (! _parent) {
|
|
_parent = new Frame(this, autoproperty);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find from client from for the currently tagged client.
|
|
*/
|
|
bool
|
|
Client::findTaggedFrame(void)
|
|
{
|
|
if (! WindowManager::instance()->isStartup()) {
|
|
return false;
|
|
}
|
|
|
|
// Check for tagged frame
|
|
Frame *frame = Frame::getTagFrame();
|
|
if (frame && frame->isMapped()) {
|
|
_parent = frame;
|
|
frame->addChild(this);
|
|
|
|
if (! Frame::getTagBehind()) {
|
|
frame->activateChild(this);
|
|
}
|
|
}
|
|
|
|
return _parent != 0;
|
|
}
|
|
|
|
/**
|
|
* Find frame for client on PEKWM_FRAME_ID hint.
|
|
*/
|
|
bool
|
|
Client::findPreviousFrame(void)
|
|
{
|
|
if (WindowManager::instance()->isStartup()) {
|
|
return false;
|
|
}
|
|
|
|
long id;
|
|
if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ID), id)) {
|
|
_parent = Frame::findFrameFromID(id);
|
|
if (_parent) {
|
|
Frame *frame = static_cast<Frame*>(_parent);
|
|
frame->addChildOrdered(this);
|
|
if (getPekwmFrameActive()) {
|
|
frame->activateChild(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
return _parent != 0;
|
|
}
|
|
|
|
/**
|
|
* Find frame for client based on autoproperties.
|
|
*/
|
|
bool
|
|
Client::findAutoGroupFrame(AutoProperty *autoproperty)
|
|
{
|
|
if (autoproperty->group_size < 0) {
|
|
return false;
|
|
}
|
|
|
|
Frame* frame = WindowManager::instance()->findGroup(autoproperty);
|
|
if (frame) {
|
|
frame->addChild(this);
|
|
|
|
if (! autoproperty->group_behind) {
|
|
frame->activateChild(this);
|
|
}
|
|
if (autoproperty->group_raise) {
|
|
frame->raise();
|
|
}
|
|
}
|
|
|
|
return _parent != 0;
|
|
}
|
|
|
|
/**
|
|
* Get the client state and set iconified and mapped flags.
|
|
*/
|
|
void
|
|
Client::setInitialState(void)
|
|
{
|
|
// Set state either specified in hint
|
|
ulong initial_state = readWmHints();
|
|
if (getWmState() == IconicState) {
|
|
_iconified = true;
|
|
}
|
|
|
|
if (_iconified || initial_state == IconicState) {
|
|
_iconified = true;
|
|
_mapped = true;
|
|
unmapWindow();
|
|
} else {
|
|
setWmState(initial_state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure the Client is (un) mapped and give input focus if requested.
|
|
*/
|
|
void
|
|
Client::setMappedStateAndFocus(bool is_new, AutoProperty *autoproperty)
|
|
{
|
|
// Make sure the window is mapped, this is done after it has been
|
|
// added to the decor/frame as otherwise IsViewable state won't
|
|
// be correct and we don't know whether or not to place the window
|
|
if (! _iconified && _parent->isMapped()) {
|
|
PWinObj::mapWindow();
|
|
}
|
|
|
|
// Let us hear what autoproperties has to say about focusing
|
|
bool do_focus = is_new ? Config::instance()->isFocusNew() : false;
|
|
if (is_new && autoproperty && autoproperty->isMask(AP_FOCUS_NEW)) {
|
|
do_focus = autoproperty->focus_new;
|
|
}
|
|
|
|
// Only can give input focus to mapped windows
|
|
if (_parent->isMapped()) {
|
|
// Ordinary focus
|
|
if (do_focus) {
|
|
_parent->giveInputFocus();
|
|
|
|
// Check if we are transient, and if we want to focus
|
|
} else if (_transient && _transient->isFocused()
|
|
&& Config::instance()->isFocusNewChild()) {
|
|
_parent->giveInputFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-lookup transient client if window is set but not client, raise over
|
|
* transient for window if found/set.
|
|
*/
|
|
void
|
|
Client::findAndRaiseIfTransient(void)
|
|
{
|
|
if (_transient_window != None && ! _transient) {
|
|
getTransientForHint();
|
|
}
|
|
|
|
if (_transient) {
|
|
Frame *frame = static_cast<Frame*>(getParent());
|
|
Frame *frame_transient = static_cast<Frame*>(_transient->getParent());
|
|
if (frame->getActiveChild() == this) {
|
|
frame->setLayer(frame_transient->getLayer() + 1);
|
|
frame->raise();
|
|
}
|
|
}
|
|
}
|
|
|
|
// START - PWinObj interface.
|
|
|
|
//! @brief Maps the window.
|
|
void
|
|
Client::mapWindow(void)
|
|
{
|
|
if (_mapped) {
|
|
return;
|
|
}
|
|
|
|
if (_iconified) {
|
|
_iconified = false;
|
|
setWmState(NormalState);
|
|
updateEwmhStates();
|
|
}
|
|
|
|
if(! _transient) {
|
|
// Unmap our transient windows if we have any
|
|
mapOrUnmapTransients(_window, false);
|
|
}
|
|
|
|
XSelectInput(_dpy, _window, NoEventMask);
|
|
PWinObj::mapWindow();
|
|
XSelectInput(_dpy, _window,
|
|
PropertyChangeMask|StructureNotifyMask|FocusChangeMask);
|
|
}
|
|
|
|
|
|
//! @brief Sets the client to WithdrawnState and unmaps it.
|
|
void
|
|
Client::unmapWindow(void)
|
|
{
|
|
if (! _mapped) {
|
|
return;
|
|
}
|
|
|
|
if (_iconified) {
|
|
// Set the state of the window
|
|
setWmState(IconicState);
|
|
updateEwmhStates();
|
|
}
|
|
|
|
XSelectInput(_dpy, _window, NoEventMask);
|
|
PWinObj::unmapWindow();
|
|
XSelectInput(_dpy, _window, PropertyChangeMask|StructureNotifyMask|FocusChangeMask);
|
|
}
|
|
|
|
//! @brief Iconifies the client and adds it to the iconmenu
|
|
void
|
|
Client::iconify(void)
|
|
{
|
|
if (_iconified) {
|
|
return;
|
|
}
|
|
|
|
_iconified = true;
|
|
if (! _transient) {
|
|
mapOrUnmapTransients(_window, true);
|
|
}
|
|
|
|
unmapWindow();
|
|
}
|
|
|
|
//! @brief Toggle client sticky state
|
|
void
|
|
Client::stick(void)
|
|
{
|
|
PWinObj::stick();
|
|
|
|
updateEwmhStates();
|
|
}
|
|
|
|
//! @brief Update the position variables.
|
|
void
|
|
Client::move(int x, int y)
|
|
{
|
|
bool request = ((_gm.x != x) || (_gm.y != y));
|
|
|
|
_gm.x = x;
|
|
_gm.y = y;
|
|
|
|
if (request) {
|
|
configureRequestSend();
|
|
}
|
|
}
|
|
|
|
//! @brief Resizes the client window to specified size.
|
|
void
|
|
Client::resize(uint width, uint height)
|
|
{
|
|
bool request = ((_gm.width != width) || (_gm.height != height));
|
|
|
|
PWinObj::resize(width, height);
|
|
|
|
if (request) {
|
|
configureRequestSend();
|
|
}
|
|
}
|
|
|
|
//! @brief Move and resizes the client window to specified size.
|
|
void
|
|
Client::moveResize(int x, int y, uint width, uint height)
|
|
{
|
|
bool request = ((_gm.x != x) || (_gm.y != y) || (_gm.width != width) || (_gm.height != height));
|
|
|
|
_gm.x = x;
|
|
_gm.y = y;
|
|
|
|
PWinObj::resize(width, height);
|
|
|
|
if (request) {
|
|
configureRequestSend();
|
|
}
|
|
}
|
|
|
|
//! @brief Sets the workspace and updates the _NET_WM_DESKTOP hint.
|
|
void
|
|
Client::setWorkspace(uint workspace)
|
|
{
|
|
if (workspace != NET_WM_STICKY_WINDOW) {
|
|
if (workspace >= Workspaces::instance()->size()) {
|
|
workspace = Workspaces::instance()->size() - 1;
|
|
}
|
|
_workspace = workspace;
|
|
|
|
if (_sticky) {
|
|
AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_DESKTOP), NET_WM_STICKY_WINDOW);
|
|
} else {
|
|
AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_DESKTOP), _workspace);
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief Gives the Client input focus.
|
|
void
|
|
Client::giveInputFocus(void)
|
|
{
|
|
if (_wm_hints_input) {
|
|
PWinObj::giveInputFocus();
|
|
}
|
|
|
|
sendTakeFocusMessage();
|
|
}
|
|
|
|
//! @brief Reparents and sets _parent member, filtering unmap events
|
|
void
|
|
Client::reparent(PWinObj *parent, int x, int y)
|
|
{
|
|
XSelectInput(_dpy, _window, NoEventMask);
|
|
PWinObj::reparent(parent, x, y);
|
|
_gm.x = parent->getX() + x;
|
|
_gm.y = parent->getY() + y;
|
|
XSelectInput(_dpy, _window,
|
|
PropertyChangeMask|StructureNotifyMask|FocusChangeMask);
|
|
}
|
|
|
|
ActionEvent*
|
|
Client::handleUnmapEvent(XUnmapEvent *ev)
|
|
{
|
|
if ((ev->window != ev->event) && (ev->send_event != true)) {
|
|
return 0;
|
|
}
|
|
|
|
// ICCCM 4.1.4 advices the window manager to trigger the transition to
|
|
// Withdrawn state on real and synthetic UnmapNotify events.
|
|
setWmState(WithdrawnState);
|
|
|
|
// Extended Window Manager Hints 1.3 specifies that a window manager
|
|
// should remove the _NET_WM_STATE property when a window is withdrawn.
|
|
AtomUtil::unsetProperty(_window, Atoms::getAtom(STATE));
|
|
|
|
// Extended Window Manager Hints 1.3 specifies that a window manager
|
|
// should remove the _NET_WM_DESKTOP property when a window is withdrawn.
|
|
// (to allow legacy applications to reuse a withdrawn window)
|
|
AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_DESKTOP));
|
|
|
|
#ifdef DEBUG
|
|
cerr << __FILE__ << "@" << __LINE__ << ": "
|
|
<< "Client(" << this << ")::handleUnmapEvent(" << ev << ")" << endl
|
|
<< " *** unmapping client, window: " << _window << endl;
|
|
#endif // DEBUG
|
|
|
|
// FIXME: Listen mask should change as this doesn't work?
|
|
_alive = false;
|
|
delete this;
|
|
|
|
return 0;
|
|
}
|
|
|
|
ActionEvent*
|
|
Client::handleMapRequest(XMapRequestEvent *ev)
|
|
{
|
|
if (_parent && dynamic_cast<PDecor *>(_parent)) {
|
|
dynamic_cast<PDecor*>(_parent)->deiconify();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// END - PWinObj interface.
|
|
|
|
// START - Observer interface
|
|
|
|
void
|
|
Client::notify(Observable *observable, Observation *observation)
|
|
{
|
|
if (observation) {
|
|
LayerObservation *layer_observation = dynamic_cast<LayerObservation*>(observation);
|
|
if (layer_observation) {
|
|
setLayer(layer_observation->layer + 1);
|
|
|
|
Frame *frame = static_cast<Frame*>(getParent());
|
|
if (frame->getActiveChild() == this) {
|
|
frame->setLayer(layer_observation->layer + 1);
|
|
frame->raise();
|
|
}
|
|
}
|
|
} else {
|
|
Client *client = static_cast<Client*>(observable);
|
|
if (client == _transient) {
|
|
_transient = 0;
|
|
} else {
|
|
_transient_clients.remove(client);
|
|
}
|
|
}
|
|
}
|
|
|
|
// END - Observer interface
|
|
|
|
//! @brief Finds the Client which holds the Window w.
|
|
//! @param win Window to search for.
|
|
//! @return Pointer to the client if found, else 0
|
|
Client*
|
|
Client::findClient(Window win)
|
|
{
|
|
// Validate input window.
|
|
if ((win == None) || (win == PScreen::instance()->getRoot())) {
|
|
return 0;
|
|
}
|
|
|
|
list<Client*>::iterator it(_client_list.begin());
|
|
for (; it != _client_list.end(); ++it) {
|
|
if (win == (*it)->getWindow()
|
|
|| ((*it)->getParent() && (*((*it)->getParent()) == win))) {
|
|
return (*it);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//! @brief Finds the Client of Window win.
|
|
//! @param win Window to search for.
|
|
Client*
|
|
Client::findClientFromWindow(Window win)
|
|
{
|
|
// Validate input window.
|
|
if (! win || win == PScreen::instance()->getRoot()) {
|
|
return 0;
|
|
}
|
|
|
|
list<Client*>::iterator it(_client_list.begin());
|
|
for(; it != _client_list.end(); ++it) {
|
|
if (win == (*it)->getWindow()) {
|
|
return (*it);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//! @brief Finds Client with equal ClassHint.
|
|
//! @param class_hint ClassHint to search for.
|
|
//! @return Client if found, else 0.
|
|
Client*
|
|
Client::findClientFromHint(const ClassHint *class_hint)
|
|
{
|
|
list<Client*>::iterator it(_client_list.begin());
|
|
for (; it != _client_list.end(); ++it) {
|
|
if (*class_hint == *(*it)->getClassHint()) {
|
|
return *it;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//! @brief Finds Client with id.
|
|
//! @param id ID to search for.
|
|
//! @return Client if found, else 0.
|
|
Client*
|
|
Client::findClientFromID(uint id)
|
|
{
|
|
list<Client*>::iterator it(_client_list.begin());
|
|
for (; it != _client_list.end(); ++it) {
|
|
if ((*it)->getClientID() == id) {
|
|
return *it;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Insert all clients with the transient for set to win.
|
|
*/
|
|
void
|
|
Client::findFamilyFromWindow(std::list<Client*> &client_list, Window win)
|
|
{
|
|
list<Client*>::iterator it(Client::client_begin());
|
|
for (; it != Client::client_end(); ++it) {
|
|
if ((*it)->getTransientClientWindow() == win) {
|
|
client_list.push_back(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* (Un)Maps all windows having transient_for set to win
|
|
*/
|
|
void
|
|
Client::mapOrUnmapTransients(Window win, bool hide)
|
|
{
|
|
list<Client*> client_list;
|
|
findFamilyFromWindow(client_list, win);
|
|
|
|
list<Client*>::iterator it(client_list.begin());
|
|
for (; it != client_list.end(); ++it) {
|
|
if (static_cast<Frame*>((*it)->getParent())->getActiveChild() == *it) {
|
|
if (hide) {
|
|
(*it)->getParent()->iconify();
|
|
} else {
|
|
(*it)->getParent()->mapWindow();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief Checks if the window has any Destroy or Unmap notifys.
|
|
bool
|
|
Client::validate(void)
|
|
{
|
|
XSync(_dpy, false);
|
|
|
|
XEvent ev;
|
|
if (XCheckTypedWindowEvent(_dpy, _window, DestroyNotify, &ev)
|
|
|| XCheckTypedWindowEvent(_dpy, _window, UnmapNotify, &ev)) {
|
|
XPutBackEvent(_dpy, &ev);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//! @brief Checks if the window has attribute IsViewable set
|
|
bool
|
|
Client::isViewable(void)
|
|
{
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(_dpy, _window, &attr);
|
|
|
|
return (attr.map_state == IsViewable);
|
|
}
|
|
|
|
//! @brief Grabs all the mouse button actions on the client.
|
|
void
|
|
Client::grabButtons(void)
|
|
{
|
|
// Make sure we don't have any buttons grabbed.
|
|
XUngrabButton(_dpy, AnyButton, AnyModifier, _window);
|
|
|
|
list<ActionEvent> *actions = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME);
|
|
list<ActionEvent>::iterator it(actions->begin());
|
|
for (; it != actions->end(); ++it) {
|
|
if ((it->type == MOUSE_EVENT_PRESS) || (it->type == MOUSE_EVENT_RELEASE)) {
|
|
// No need to grab mod less events, replied with the frame
|
|
if ((it->mod == 0) || (it->mod == MOD_ANY)) {
|
|
continue;
|
|
}
|
|
|
|
grabButton(it->sym, it->mod,
|
|
ButtonPressMask|ButtonReleaseMask,
|
|
_window, None);
|
|
} else if (it->type == MOUSE_EVENT_MOTION) {
|
|
// FIXME: Add support for MOD_ANY
|
|
grabButton(it->sym, it->mod,
|
|
ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
|
|
_window, None);
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief Sets CfgDeny state on Client
|
|
void
|
|
Client::setStateCfgDeny(StateAction sa, uint deny)
|
|
{
|
|
if (! ActionUtil::needToggle(sa, _state.cfg_deny&deny)) {
|
|
return;
|
|
}
|
|
|
|
if (_state.cfg_deny&deny) {
|
|
_state.cfg_deny &= ~deny;
|
|
} else {
|
|
_state.cfg_deny |= deny;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read "all" hints required during creation of Client.
|
|
*/
|
|
void
|
|
Client::readHints(void)
|
|
{
|
|
readMwmHints();
|
|
readEwmhHints();
|
|
readPekwmHints();
|
|
readIcon();
|
|
readClientPid();
|
|
readClientRemote();
|
|
getWMProtocols();
|
|
}
|
|
|
|
/**
|
|
* Read WM Hints, return the initial state of the window.
|
|
*/
|
|
ulong
|
|
Client::readWmHints(void)
|
|
{
|
|
ulong initial_state = NormalState;
|
|
XWMHints* hints = XGetWMHints(_dpy, _window);
|
|
if (hints) {
|
|
// get the input focus mode
|
|
if (hints->flags&InputHint) { // FIXME: More logic needed
|
|
_wm_hints_input = hints->input;
|
|
}
|
|
|
|
// Get initial state of the window
|
|
if (hints->flags&StateHint) {
|
|
initial_state = hints->initial_state;
|
|
}
|
|
|
|
XFree(hints);
|
|
}
|
|
return initial_state;
|
|
}
|
|
|
|
/**
|
|
* Read the WM_CLASS hint and set information on the _class_hint used
|
|
* in matching auto properties.
|
|
*/
|
|
void
|
|
Client::readClassRoleHints(void)
|
|
{
|
|
// class hint
|
|
XClassHint class_hint;
|
|
if (XGetClassHint(_dpy, _window, &class_hint)) {
|
|
_class_hint->h_name = Util::to_wide_str(class_hint.res_name);
|
|
_class_hint->h_class = Util::to_wide_str(class_hint.res_class);
|
|
XFree(class_hint.res_name);
|
|
XFree(class_hint.res_class);
|
|
}
|
|
|
|
// wm window role
|
|
string role;
|
|
AtomUtil::getString(_window, Atoms::getAtom(WM_WINDOW_ROLE), role);
|
|
|
|
_class_hint->h_role = Util::to_wide_str(role);
|
|
}
|
|
|
|
//! @brief Loads the Clients state from EWMH atoms.
|
|
void
|
|
Client::readEwmhHints(void)
|
|
{
|
|
// which workspace do we belong to?
|
|
long workspace = -1;
|
|
AtomUtil::getLong(_window, Atoms::getAtom(NET_WM_DESKTOP), workspace);
|
|
if (workspace < 0) {
|
|
_workspace = Workspaces::instance()->getActive();
|
|
AtomUtil::setLong(_window, Atoms::getAtom(NET_WM_DESKTOP), _workspace);
|
|
} else {
|
|
_workspace = workspace;
|
|
}
|
|
|
|
// try to figure out what kind of window we are and alter it accordingly
|
|
int items;
|
|
Atom *atoms = 0;
|
|
|
|
AtomName window_type = WINDOW_TYPE;
|
|
atoms = (Atom*) AtomUtil::getEwmhPropData(_window, Atoms::getAtom(WINDOW_TYPE), XA_ATOM, items);
|
|
if (atoms) {
|
|
for (int i = 0; window_type == WINDOW_TYPE && i < items; ++i) {
|
|
if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DESKTOP)) {
|
|
window_type = WINDOW_TYPE_DESKTOP;
|
|
} else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DOCK)) {
|
|
window_type = WINDOW_TYPE_DOCK;
|
|
} else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_TOOLBAR)) {
|
|
window_type = WINDOW_TYPE_TOOLBAR;
|
|
} else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_MENU)) {
|
|
window_type = WINDOW_TYPE_MENU;
|
|
} else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_UTILITY)) {
|
|
window_type = WINDOW_TYPE_UTILITY;
|
|
} else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_DIALOG)) {
|
|
window_type = WINDOW_TYPE_DIALOG;
|
|
} else if (atoms[i] == Atoms::getAtom(WINDOW_TYPE_SPLASH)) {
|
|
window_type = WINDOW_TYPE_SPLASH;
|
|
}
|
|
}
|
|
|
|
XFree(atoms);
|
|
}
|
|
|
|
// Set window type to WINDOW_TYPE_NORMAL if it did not match
|
|
if (window_type == WINDOW_TYPE) {
|
|
window_type = WINDOW_TYPE_NORMAL;
|
|
AtomUtil::setAtom(_window, Atoms::getAtom(WINDOW_TYPE), Atoms::getAtom(WINDOW_TYPE_NORMAL));
|
|
}
|
|
|
|
// Apply autoproperties for window type
|
|
AutoProperty *auto_property = AutoProperties::instance()->findWindowTypeProperty(window_type);
|
|
if (auto_property) {
|
|
applyAutoprops(auto_property);
|
|
}
|
|
|
|
// The _NET_WM_STATE overrides the _NET_WM_TYPE
|
|
NetWMStates win_states;
|
|
if (getEwmhStates(win_states)) {
|
|
if (win_states.hidden) _iconified = true;
|
|
if (win_states.shaded) _state.shaded = true;
|
|
if (win_states.max_vert) _state.maximized_vert = true;
|
|
if (win_states.max_horz) _state.maximized_horz = true;
|
|
if (win_states.skip_taskbar) _state.skip |= SKIP_TASKBAR;
|
|
if (win_states.skip_pager) _state.skip |= SKIP_PAGER;
|
|
if (win_states.sticky) _sticky = true;
|
|
if (win_states.above) {
|
|
setLayer(LAYER_ABOVE_DOCK);
|
|
}
|
|
if (win_states.below) {
|
|
setLayer(LAYER_BELOW);
|
|
}
|
|
if (win_states.fullscreen) _state.fullscreen = true;
|
|
}
|
|
|
|
// check if we have a strut
|
|
getStrutHint();
|
|
}
|
|
|
|
//! @brief Loads the Clients state from MWM atoms.
|
|
void
|
|
Client::readMwmHints(void)
|
|
{
|
|
MwmHints *mwm_hints = getMwmHints(_window);
|
|
|
|
if (mwm_hints) {
|
|
if (mwm_hints->flags&MWM_HINTS_FUNCTIONS) {
|
|
bool state = ! (mwm_hints->functions&MWM_FUNC_ALL);
|
|
|
|
_actions.resize = (mwm_hints->functions&MWM_FUNC_RESIZE) ? state : ! state;
|
|
_actions.move = (mwm_hints->functions&MWM_FUNC_MOVE) ? state : ! state;
|
|
_actions.minimize = (mwm_hints->functions&MWM_FUNC_ICONIFY) ? state : ! state;
|
|
_actions.close = (mwm_hints->functions&MWM_FUNC_CLOSE) ? state : ! state;
|
|
_actions.maximize_vert = (mwm_hints->functions&MWM_FUNC_MAXIMIZE) ? state : ! state;
|
|
_actions.maximize_horz = (mwm_hints->functions&MWM_FUNC_MAXIMIZE) ? state : ! state;
|
|
}
|
|
|
|
// Check decoration flags
|
|
if (mwm_hints->flags & MWM_HINTS_DECORATIONS
|
|
&& ! (mwm_hints->decorations & MWM_DECOR_ALL)) {
|
|
if (! (mwm_hints->decorations & MWM_DECOR_TITLE)) {
|
|
setTitlebar(false);
|
|
}
|
|
if (! (mwm_hints->decorations & MWM_DECOR_BORDER)) {
|
|
setBorder(false);
|
|
}
|
|
|
|
// Do not handle HANDLE, MENU, ICONFIY or MAXIMIZE. Maybe
|
|
// one should set the allowed actions for the client based
|
|
// on this but that might be annoying so ignoring these.
|
|
}
|
|
|
|
XFree(mwm_hints);
|
|
}
|
|
}
|
|
|
|
//! @brief Reads non-standard pekwm hints
|
|
void
|
|
Client::readPekwmHints(void)
|
|
{
|
|
long value;
|
|
string str;
|
|
|
|
// Get decor state
|
|
if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_DECOR), value)) {
|
|
_state.decor = value;
|
|
}
|
|
// Get skip state
|
|
if (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_SKIP), value)) {
|
|
_state.skip = value;
|
|
}
|
|
|
|
// Get custom title
|
|
if (AtomUtil::getString(_window, Atoms::getAtom(PEKWM_TITLE), str)) {
|
|
_title.setUser(Util::to_wide_str(str));
|
|
}
|
|
|
|
_state.initial_frame_order = getPekwmFrameOrder();
|
|
}
|
|
|
|
//! @brief Read _NET_WM_ICON from client window.
|
|
void
|
|
Client::readIcon(void)
|
|
{
|
|
PImageIcon *image = new PImageIcon(_dpy);
|
|
if (image->loadFromWindow(_window)) {
|
|
if (! _icon) {
|
|
_icon = new PTextureImage(_dpy);
|
|
TextureHandler::instance()->referenceTexture(_icon);
|
|
}
|
|
|
|
_icon->setImage(image);
|
|
} else {
|
|
if (image) {
|
|
delete image;
|
|
}
|
|
|
|
if (_icon) {
|
|
TextureHandler::instance()->returnTexture(_icon);
|
|
_icon = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief Tries to find a AutoProp for the current client.
|
|
//! @param type Defaults to 0
|
|
//! @return AutoProperty if any is found, else 0.
|
|
AutoProperty*
|
|
Client::readAutoprops(uint type)
|
|
{
|
|
AutoProperty *data =
|
|
AutoProperties::instance()->findAutoProperty(_class_hint, _workspace, type);
|
|
|
|
if (data) {
|
|
// Make sure transient state matches
|
|
if (isTransient()
|
|
? data->isApplyOn(APPLY_ON_TRANSIENT|APPLY_ON_TRANSIENT_ONLY)
|
|
: ! data->isApplyOn(APPLY_ON_TRANSIENT_ONLY)) {
|
|
applyAutoprops(data);
|
|
} else {
|
|
data = 0;
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
//! @brief Applies AutoPropery to this Client.
|
|
void
|
|
Client::applyAutoprops(AutoProperty *ap)
|
|
{
|
|
// Set the correct group of the window
|
|
_class_hint->group = ap->group_name;
|
|
|
|
// We only apply grouping if it's a new client or if we are restarting
|
|
// and have APPLY_ON_START set
|
|
if (ap->isMask(AP_STICKY))
|
|
_sticky = ap->sticky;
|
|
if (ap->isMask(AP_SHADED))
|
|
_state.shaded = ap->shaded;
|
|
if (ap->isMask(AP_MAXIMIZED_VERTICAL))
|
|
_state.maximized_vert = ap->maximized_vertical;
|
|
if (ap->isMask(AP_MAXIMIZED_HORIZONTAL))
|
|
_state.maximized_horz = ap->maximized_horizontal;
|
|
if (ap->isMask(AP_FULLSCREEN))
|
|
_state.fullscreen = ap->fullscreen;
|
|
if (ap->isMask(AP_ICONIFIED))
|
|
_iconified = ap->iconified;
|
|
if (ap->isMask(AP_TITLEBAR))
|
|
setTitlebar(ap->titlebar);
|
|
if (ap->isMask(AP_BORDER))
|
|
setBorder(ap->border);
|
|
if (ap->isMask(AP_LAYER) && (ap->layer <= LAYER_MENU)) {
|
|
setLayer(ap->layer);
|
|
}
|
|
if (ap->isMask(AP_SKIP))
|
|
_state.skip = ap->skip;
|
|
if (ap->isMask(AP_FOCUSABLE))
|
|
_focusable = ap->focusable;
|
|
if (ap->isMask(AP_WORKSPACE)) {
|
|
_workspace = ap->workspace;
|
|
}
|
|
if (ap->isMask(AP_CFG_DENY)) {
|
|
_state.cfg_deny = ap->cfg_deny;
|
|
}
|
|
if (ap->isMask(AP_ALLOWED_ACTIONS)) {
|
|
applyActionAccessMask(ap->allowed_actions, true);
|
|
}
|
|
if (ap->isMask(AP_DISALLOWED_ACTIONS)) {
|
|
applyActionAccessMask(ap->disallowed_actions, false);
|
|
}
|
|
#ifdef OPACITY
|
|
if (ap->isMask(AP_OPACITY)) {
|
|
setOpacity(ap->focus_opacity, ap->unfocus_opacity);
|
|
}
|
|
#endif // OPACITY
|
|
}
|
|
|
|
void
|
|
Client::applyActionAccessMask(uint mask, bool value)
|
|
{
|
|
if (mask & ACTION_ACCESS_MOVE) {
|
|
_actions.move = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_RESIZE) {
|
|
_actions.resize = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_MINIMIZE) {
|
|
_actions.minimize = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_SHADE) {
|
|
_actions.shade = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_STICK) {
|
|
_actions.stick = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_MAXIMIZE_HORZ) {
|
|
_actions.maximize_horz = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_MAXIMIZE_VERT) {
|
|
_actions.maximize_vert = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_FULLSCREEN) {
|
|
_actions.fullscreen = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_CHANGE_DESKTOP) {
|
|
_actions.change_desktop = value;
|
|
}
|
|
if (mask & ACTION_ACCESS_CLOSE) {
|
|
_actions.close = value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read _NET_WM_PID.
|
|
*/
|
|
void
|
|
Client::readClientPid(void)
|
|
{
|
|
AtomUtil::getLong(_window, Atoms::getAtom(NET_WM_PID), _pid);
|
|
}
|
|
|
|
/**
|
|
* Read WM_CLIENT_MACHINE and check against local hostname and set
|
|
* _is_remote if it does not match.
|
|
*/
|
|
void
|
|
Client::readClientRemote(void)
|
|
{
|
|
string client_machine;
|
|
if (AtomUtil::getTextProperty(_window, XA_WM_CLIENT_MACHINE, client_machine)) {
|
|
_is_remote = Util::getHostname() != client_machine;
|
|
}
|
|
}
|
|
|
|
//! @brief Finds free Client ID.
|
|
//! @return First free Client ID.
|
|
uint
|
|
Client::findClientID(void)
|
|
{
|
|
uint id = 0;
|
|
|
|
if (_clientid_list.size()) {
|
|
// Check for used Frame IDs
|
|
id = _clientid_list.back();
|
|
_clientid_list.pop_back();
|
|
} else {
|
|
// No free, get next number (Client is not in list when this is called.)
|
|
id = _client_list.size() + 1;
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
//! @brief Returns Client ID to used client id list.
|
|
//! @param id ID to return.
|
|
void
|
|
Client::returnClientID(uint id)
|
|
{
|
|
vector<uint>::iterator it(_clientid_list.begin());
|
|
for (; it != _clientid_list.end() && id < *it; ++it)
|
|
;
|
|
_clientid_list.insert(it, id);
|
|
}
|
|
|
|
/**
|
|
* Tries to get the NET_WM name, else fall back to WM_NAME
|
|
*/
|
|
void
|
|
Client::readName(void)
|
|
{
|
|
// Read title, bail out if it fails.
|
|
wstring title;
|
|
if (! AtomUtil::getUtf8String(_window, Atoms::getAtom(NET_WM_NAME), title)) {
|
|
string mb_title;
|
|
if (! AtomUtil::getTextProperty(_window, XA_WM_NAME, mb_title)) {
|
|
return;
|
|
}
|
|
title = Util::to_wide_str(mb_title);
|
|
}
|
|
|
|
// Mirror it on the visible
|
|
_title.setCustom(L"");
|
|
_title.setCount(titleFindID(title));
|
|
_title.setReal(title);
|
|
|
|
// Apply title rules and find unique name, doesn't apply on
|
|
// user-set titles
|
|
if (titleApplyRule(title)) {
|
|
_title.setCustom(title);
|
|
AtomUtil::setUtf8String(_window, Atoms::getAtom(NET_WM_VISIBLE_NAME), title);
|
|
} else {
|
|
AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_VISIBLE_NAME));
|
|
}
|
|
}
|
|
|
|
//! @brief Searches for an TitleRule and if found, applies it
|
|
//! @param title Title to apply rule on.
|
|
//! @return true if rule was applied, else false.
|
|
bool
|
|
Client::titleApplyRule(std::wstring &title)
|
|
{
|
|
_class_hint->title = title;
|
|
TitleProperty *data = AutoProperties::instance()->findTitleProperty(_class_hint);
|
|
|
|
if (data) {
|
|
return data->getTitleRule().ed_s(title);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//! @brief Searches for a unique ID within Clients having the same title
|
|
//! @param title Title of client to find ID for.
|
|
//! @return Number of clients with that id.
|
|
uint
|
|
Client::titleFindID(std::wstring &title)
|
|
{
|
|
// Do not search for unique IDs if it is not enabled.
|
|
if (! Config::instance()->getClientUniqueName()) {
|
|
return 0;
|
|
}
|
|
|
|
uint id_found = 0;
|
|
list<uint> ids_used;
|
|
|
|
list<Client*>::iterator it = _client_list.begin();
|
|
for (; it != _client_list.end(); ++it) {
|
|
if (*it != this) {
|
|
if ((*it)->getTitle()->getReal() == title) {
|
|
ids_used.push_back((*it)->getTitle()->getCount());
|
|
}
|
|
}
|
|
}
|
|
|
|
// more than one client having this name
|
|
if (ids_used.size() > 0) {
|
|
ids_used.sort();
|
|
|
|
list<uint>::iterator ui_it( ids_used.begin());
|
|
for (uint i = 0; ui_it != ids_used.end(); ++i, ++ui_it) {
|
|
if (i < *ui_it) {
|
|
id_found = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ui_it == ids_used.end()) {
|
|
id_found = ids_used.size();
|
|
}
|
|
}
|
|
|
|
return id_found;
|
|
}
|
|
|
|
/**
|
|
* Get the clients icon name to be displayed when the client is
|
|
* iconified.
|
|
*/
|
|
void
|
|
Client::readIconName(void)
|
|
{
|
|
wstring icon_name;
|
|
|
|
if (! AtomUtil::getUtf8String(_window, Atoms::getAtom(NET_WM_ICON_NAME), icon_name)) {
|
|
string mb_icon_name;
|
|
if (AtomUtil::getTextProperty(_window, XA_WM_ICON_NAME, mb_icon_name)) {
|
|
icon_name = Util::to_wide_str(mb_icon_name);
|
|
}
|
|
}
|
|
|
|
// Set real name
|
|
_icon_name.setReal(icon_name);
|
|
_icon_name.setCustom(icon_name);
|
|
|
|
if (_icon_name.getVisible() == _icon_name.getReal()) {
|
|
AtomUtil::unsetProperty(_window, Atoms::getAtom(NET_WM_VISIBLE_ICON_NAME));
|
|
} else {
|
|
AtomUtil::setUtf8String(_window, Atoms::getAtom(NET_WM_VISIBLE_ICON_NAME), icon_name);
|
|
}
|
|
}
|
|
|
|
//! @brief Sets the WM_STATE of the client to state
|
|
//! @param state State to set.
|
|
void
|
|
Client::setWmState(ulong state)
|
|
{
|
|
ulong data[2];
|
|
|
|
data[0] = state;
|
|
data[1] = None; // No Icon
|
|
|
|
XChangeProperty(_dpy, _window,
|
|
Atoms::getAtom(WM_STATE),
|
|
Atoms::getAtom(WM_STATE),
|
|
32, PropModeReplace, (uchar*) data, 2);
|
|
}
|
|
|
|
// If we can't find a wm_state we're going to have to assume
|
|
// Withdrawn. This is not exactly optimal, since we can't really
|
|
// distinguish between the case where no WM has run yet and when the
|
|
// state was explicitly removed (Clients are allowed to either set the
|
|
// atom to Withdrawn or just remove it... yuck.)
|
|
long
|
|
Client::getWmState(void)
|
|
{
|
|
Atom real_type;
|
|
int real_format;
|
|
long *data, state = WithdrawnState;
|
|
ulong items_read, items_left;
|
|
uchar *udata;
|
|
|
|
int status =
|
|
XGetWindowProperty(_dpy, _window, Atoms::getAtom(WM_STATE),
|
|
0L, 2L, False, Atoms::getAtom(WM_STATE),
|
|
&real_type, &real_format, &items_read, &items_left,
|
|
&udata);
|
|
if ((status == Success) && items_read) {
|
|
data = reinterpret_cast<long*>(udata);
|
|
state = *data;
|
|
XFree(udata);
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
//! @brief Send XConfigureEvent, letting the client know about changes
|
|
void
|
|
Client::configureRequestSend(void)
|
|
{
|
|
if (_cfg_request_lock) {
|
|
return;
|
|
}
|
|
|
|
XConfigureEvent e;
|
|
|
|
e.type = ConfigureNotify;
|
|
e.event = _window;
|
|
e.window = _window;
|
|
e.x = _gm.x;
|
|
e.y = _gm.y;
|
|
e.width = _gm.width;
|
|
e.height = _gm.height;
|
|
e.border_width = 0;
|
|
e.above = None;
|
|
e.override_redirect = False;
|
|
|
|
XSendEvent(_dpy, _window, false, StructureNotifyMask, (XEvent *) &e);
|
|
}
|
|
|
|
//! @brief Send a TAKE_FOCUS client message to the client (if requested by it).
|
|
void Client::sendTakeFocusMessage(void)
|
|
{
|
|
if (_send_focus_message) {
|
|
{
|
|
XEvent ev;
|
|
XChangeProperty(_dpy, RootWindow(_dpy, DefaultScreen(_dpy)), XA_PRIMARY, XA_STRING, 8, PropModeAppend, 0, 0);
|
|
XWindowEvent(_dpy, RootWindow(_dpy, DefaultScreen(_dpy)), PropertyChangeMask, &ev);
|
|
PScreen::instance()->setLastEventTime(ev.xproperty.time);
|
|
}
|
|
sendXMessage(_window,
|
|
Atoms::getAtom(WM_PROTOCOLS), NoEventMask,
|
|
Atoms::getAtom(WM_TAKE_FOCUS),
|
|
PScreen::instance()->getLastEventTime());
|
|
}
|
|
}
|
|
|
|
//! @brief Grabs a button on the window win
|
|
//! Grabs the button button, with the mod mod and mask mask on the window win
|
|
//! and cursor curs with "all" possible extra modifiers
|
|
void
|
|
Client::grabButton(int button, int mod, int mask, Window win, Cursor curs)
|
|
{
|
|
uint num_lock = PScreen::instance()->getNumLock();
|
|
uint scroll_lock = PScreen::instance()->getScrollLock();
|
|
|
|
XGrabButton(_dpy, button, mod,
|
|
win, true, mask, GrabModeAsync, GrabModeAsync, None, curs);
|
|
XGrabButton(_dpy, button, mod|LockMask,
|
|
win, true, mask, GrabModeAsync, GrabModeAsync, None, curs);
|
|
|
|
if (num_lock) {
|
|
XGrabButton(_dpy, button, mod|num_lock,
|
|
win, true, mask, GrabModeAsync, GrabModeAsync, None, curs);
|
|
XGrabButton(_dpy, button, mod|num_lock|LockMask,
|
|
win, true, mask, GrabModeAsync, GrabModeAsync, None, curs);
|
|
}
|
|
if (scroll_lock) {
|
|
XGrabButton(_dpy, button, mod|scroll_lock,
|
|
win, true, mask, GrabModeAsync, GrabModeAsync, None, curs);
|
|
XGrabButton(_dpy, button, mod|scroll_lock|LockMask,
|
|
win, true, mask, GrabModeAsync, GrabModeAsync, None, curs);
|
|
}
|
|
if (num_lock && scroll_lock) {
|
|
XGrabButton(_dpy, button, mod|num_lock|scroll_lock,
|
|
win, true, mask, GrabModeAsync, GrabModeAsync, None, curs);
|
|
XGrabButton(_dpy, button, mod|num_lock|scroll_lock|LockMask,
|
|
win, true, mask, GrabModeAsync, GrabModeAsync, None, curs);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggles the clients always on top state
|
|
*/
|
|
void
|
|
Client::alwaysOnTop(bool top)
|
|
{
|
|
setLayer(top ? LAYER_ONTOP : LAYER_NORMAL);
|
|
updateEwmhStates();
|
|
}
|
|
|
|
/**
|
|
* Toggles the clients always below state.
|
|
*/
|
|
void
|
|
Client::alwaysBelow(bool below)
|
|
{
|
|
setLayer(below ? LAYER_BELOW : LAYER_NORMAL);
|
|
updateEwmhStates();
|
|
}
|
|
|
|
//! @brief Sets the skip state, and updates the _PEKWM_FRAME_SKIP atom
|
|
void
|
|
Client::setSkip(uint skip)
|
|
{
|
|
_state.skip = skip;
|
|
AtomUtil::setLong(_window, Atoms::getAtom(PEKWM_FRAME_SKIP), _state.skip);
|
|
}
|
|
|
|
/**
|
|
* Set demands attention state, this should be unset when client
|
|
* recieves focus. This is ignored if client has focus.
|
|
*/
|
|
void
|
|
Client::setStateDemandsAttention(StateAction sa, bool attention)
|
|
{
|
|
// FIXME: Demands attention state.
|
|
}
|
|
|
|
//! @brief Sends an WM_DELETE message to the client, else kills it.
|
|
void
|
|
Client::close(void)
|
|
{
|
|
if (_send_close_message) {
|
|
sendXMessage(_window, Atoms::getAtom(WM_PROTOCOLS), NoEventMask,
|
|
Atoms::getAtom(WM_DELETE_WINDOW), CurrentTime);
|
|
} else {
|
|
kill();
|
|
}
|
|
}
|
|
|
|
//! @brief Kills the client using XKillClient
|
|
void
|
|
Client::kill(void)
|
|
{
|
|
XKillClient(_dpy, _window);
|
|
}
|
|
|
|
//! @brief Sets the position based on P or U position.
|
|
//! @return Returns true on success, else false.
|
|
bool
|
|
Client::setPUPosition(void)
|
|
{
|
|
if ((_size->flags&PPosition) || (_size->flags&USPosition)) {
|
|
if (_gm.x == 0) {
|
|
_gm.x = _size->x;
|
|
}
|
|
if (_gm.y == 0) {
|
|
_gm.y = _size->y;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//! @brief Get the closest size confirming to the aspect ratio and ResizeInc (if applicable)
|
|
//! @param r_w Pointer to put the new width in
|
|
//! @param r_h Pointer to put the new height in
|
|
//! @param w Width to calculate from
|
|
//! @param h Height to calculate from
|
|
//! @return true if width/height have changed
|
|
bool
|
|
Client::getAspectSize(uint *r_w, uint *r_h, uint w, uint h)
|
|
{
|
|
// see ICCCM 4.1.2.3 for PAspect and {min,max}_aspect
|
|
if (_size->flags & PAspect && Config::instance()->isHonourAspectRatio()) {
|
|
// shorthand
|
|
const uint amin_x = _size->min_aspect.x;
|
|
const uint amin_y = _size->min_aspect.y;
|
|
const uint amax_x = _size->max_aspect.x;
|
|
const uint amax_y = _size->max_aspect.y;
|
|
|
|
uint base_w = 0, base_h = 0;
|
|
|
|
// If PBaseSize is specified, base_{width,height} should be subtracted
|
|
// before checking the aspect ratio (c.f. ICCCM). The additional checks avoid
|
|
// underflows in w and h. Keep in mind that _size->base_{width,height} are
|
|
// guaranteed to be non-negative by getWMNormalHints().
|
|
if (_size->flags & PBaseSize) {
|
|
if (static_cast<uint>(_size->base_width) < w) {
|
|
base_w = _size->base_width;
|
|
w -= base_w;
|
|
}
|
|
if (static_cast<uint>(_size->base_height) < h) {
|
|
base_h = _size->base_height;
|
|
h -= base_h;
|
|
}
|
|
}
|
|
|
|
double tmp;
|
|
|
|
// We have to ensure: min_aspect.x/min_aspect.y <= w/h <= max_aspect.x/max_aspect.y
|
|
|
|
// How do we calculate the new r_w, r_h?
|
|
// Consider the plane spanned by width and height. The points with one specific
|
|
// aspect ratio form a line and (w,h) is a point. So, we simply calculate the
|
|
// point on the line that is closest to (w, h) under the Euclidean metric.
|
|
|
|
// Lesson learned doing this: It is good to look at a different implementation
|
|
// (thanks fluxbox!) and then let a friend go over your own calculation to
|
|
// tell you what your mistake is (thanks Robert!). ;-)
|
|
|
|
// Check if w/h is less than amin_x/amin_y
|
|
if (w * amin_y < h * amin_x) {
|
|
tmp = ((double)(w * amin_x + h * amin_y)) /
|
|
((double)(amin_x * amin_x + amin_y * amin_y));
|
|
|
|
w = static_cast<uint>(amin_x * tmp) + base_w;
|
|
h = static_cast<uint>(amin_y * tmp) + base_h;
|
|
|
|
// Check if w/h is greater than amax_x/amax_y
|
|
} else if (w * amax_y > amax_x * h) {
|
|
tmp = ((double)(w * amax_x + h * amax_y)) /
|
|
((double)(amax_x * amax_x + amax_y * amax_y));
|
|
|
|
w = static_cast<uint>(amax_x * tmp) + base_w;
|
|
h = static_cast<uint>(amax_y * tmp) + base_h;
|
|
}
|
|
|
|
getIncSize(r_w, r_h, w, h, false);
|
|
return true;
|
|
}
|
|
return getIncSize(r_w, r_h, w, h);
|
|
}
|
|
|
|
//! @brief Get the size closest to the ResizeInc incrementer
|
|
//! @param r_w Pointer to put the new width in
|
|
//! @param r_h Pointer to put the new height in
|
|
//! @param w Width to calculate from
|
|
//! @param h Height to calculate from
|
|
//! @param incr If true, increase w,h to fulfil PResizeInc (instead of decreasing them)
|
|
bool
|
|
Client::getIncSize(uint *r_w, uint *r_h, uint w, uint h, bool incr)
|
|
{
|
|
uint basex, basey;
|
|
|
|
if (_size->flags&PResizeInc) {
|
|
basex = (_size->flags&PBaseSize)
|
|
? _size->base_width
|
|
: (_size->flags&PMinSize) ? _size->min_width : 0;
|
|
|
|
basey = (_size->flags&PBaseSize)
|
|
? _size->base_height
|
|
: (_size->flags&PMinSize) ? _size->min_height : 0;
|
|
|
|
if (w-basex < 0 || h-basey<0) {
|
|
basex=basey=0;
|
|
}
|
|
|
|
uint dw = (w - basex) % _size->width_inc;
|
|
uint dh = (h - basey) % _size->height_inc;
|
|
|
|
*r_w = w - dw + ((incr && dw)?_size->width_inc:0);
|
|
*r_h = h - dh + ((incr && dh)?_size->height_inc:0);
|
|
return true;
|
|
}
|
|
|
|
*r_w = w;
|
|
*r_h = h;
|
|
return false;
|
|
}
|
|
|
|
//! @brief Gets a MwmHint structure from a window. Doesn't free memory.
|
|
Client::MwmHints*
|
|
Client::getMwmHints(Window win)
|
|
{
|
|
Atom real_type; int real_format;
|
|
ulong items_read, items_left;
|
|
MwmHints *data = 0;
|
|
uchar *udata;
|
|
|
|
Atom hints_atom = Atoms::getAtom(MOTIF_WM_HINTS);
|
|
|
|
int status = XGetWindowProperty(_dpy, win, hints_atom, 0L, 20L, False, hints_atom,
|
|
&real_type, &real_format, &items_read, &items_left, &udata);
|
|
|
|
if (status == Success) {
|
|
if (items_read < MWM_HINTS_NUM) {
|
|
XFree(udata);
|
|
udata = 0;
|
|
}
|
|
} else {
|
|
udata = 0;
|
|
}
|
|
|
|
if (udata) {
|
|
data = reinterpret_cast<MwmHints*>(udata);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// This happens when a window is iconified and destroys itself. An
|
|
// Unmap event wouldn't happen in that case because the window is
|
|
// already unmapped.
|
|
void
|
|
Client::handleDestroyEvent(XDestroyWindowEvent *e)
|
|
{
|
|
_alive = false;
|
|
delete this;
|
|
}
|
|
|
|
void
|
|
Client::handleColormapChange(XColormapEvent *e)
|
|
{
|
|
if (e->c_new) {
|
|
_cmap = e->colormap;
|
|
XInstallColormap(_dpy, _cmap);
|
|
}
|
|
}
|
|
|
|
int
|
|
Client::sendXMessage(Window window, Atom atom, long mask,
|
|
long v1, long v2, long v3, long v4, long v5)
|
|
{
|
|
XEvent e;
|
|
|
|
e.type = e.xclient.type = ClientMessage;
|
|
e.xclient.display = _dpy;
|
|
e.xclient.window = window;
|
|
e.xclient.format = 32;
|
|
e.xclient.message_type = atom;
|
|
|
|
e.xclient.data.l[0] = v1;
|
|
e.xclient.data.l[1] = v2;
|
|
e.xclient.data.l[2] = v3;
|
|
e.xclient.data.l[3] = v4;
|
|
e.xclient.data.l[4] = v5;
|
|
|
|
return XSendEvent(_dpy, window, false, mask, &e);
|
|
}
|
|
|
|
//! @brief
|
|
bool
|
|
Client::getEwmhStates(NetWMStates &win_states)
|
|
{
|
|
int num = 0;
|
|
Atom *states;
|
|
states = (Atom*)
|
|
AtomUtil::getEwmhPropData(_window, Atoms::getAtom(STATE),
|
|
XA_ATOM, num);
|
|
|
|
if (states) {
|
|
for (int i = 0; i < num; ++i) {
|
|
if (states[i] == Atoms::getAtom(STATE_MODAL)) {
|
|
win_states.modal = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_STICKY)) {
|
|
win_states.sticky = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_MAXIMIZED_VERT)
|
|
&& ! isCfgDeny(CFG_DENY_STATE_MAXIMIZED_VERT)) {
|
|
win_states.max_vert = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_MAXIMIZED_HORZ)
|
|
&& ! isCfgDeny(CFG_DENY_STATE_MAXIMIZED_HORZ)) {
|
|
win_states.max_horz = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_SHADED)) {
|
|
win_states.shaded = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_SKIP_TASKBAR)) {
|
|
win_states.skip_taskbar = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_SKIP_PAGER)) {
|
|
win_states.skip_pager = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_DEMANDS_ATTENTION)) {
|
|
win_states.demands_attention = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_HIDDEN)
|
|
&& ! isCfgDeny(CFG_DENY_STATE_HIDDEN)) {
|
|
win_states.hidden = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_FULLSCREEN)
|
|
&& ! isCfgDeny(CFG_DENY_STATE_FULLSCREEN)) {
|
|
win_states.fullscreen = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_ABOVE)
|
|
&& ! isCfgDeny(CFG_DENY_STATE_ABOVE)) {
|
|
win_states.above = true;
|
|
} else if (states[i] == Atoms::getAtom(STATE_BELOW)
|
|
&& ! isCfgDeny(CFG_DENY_STATE_BELOW)) {
|
|
win_states.below = true;
|
|
}
|
|
}
|
|
|
|
XFree(states);
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//! @brief Tells the world about our states, such as shaded etc.
|
|
void
|
|
Client::updateEwmhStates(void)
|
|
{
|
|
list<Atom> states;
|
|
|
|
if (false) // we don't yet support modal state
|
|
states.push_back(Atoms::getAtom(STATE_MODAL));
|
|
if (_sticky)
|
|
states.push_back(Atoms::getAtom(STATE_STICKY));
|
|
if (_state.maximized_vert)
|
|
states.push_back(Atoms::getAtom(STATE_MAXIMIZED_VERT));
|
|
if (_state.maximized_horz)
|
|
states.push_back(Atoms::getAtom(STATE_MAXIMIZED_HORZ));
|
|
if (_state.shaded)
|
|
states.push_back(Atoms::getAtom(STATE_SHADED));
|
|
if (isSkip(SKIP_TASKBAR))
|
|
states.push_back(Atoms::getAtom(STATE_SKIP_TASKBAR));
|
|
if (isSkip(SKIP_PAGER))
|
|
states.push_back(Atoms::getAtom(STATE_SKIP_PAGER));
|
|
if (_iconified)
|
|
states.push_back(Atoms::getAtom(STATE_HIDDEN));
|
|
if (_state.fullscreen)
|
|
states.push_back(Atoms::getAtom(STATE_FULLSCREEN));
|
|
if (getLayer() == LAYER_ABOVE_DOCK) {
|
|
states.push_back(Atoms::getAtom(STATE_ABOVE));
|
|
}
|
|
if (getLayer() == LAYER_BELOW) {
|
|
states.push_back(Atoms::getAtom(STATE_BELOW));
|
|
}
|
|
|
|
Atom *atoms = new Atom[(states.size() > 0) ? states.size() : 1];
|
|
if (states.size() > 0) {
|
|
copy(states.begin(), states.end(), atoms);
|
|
}
|
|
AtomUtil::setAtoms(_window, Atoms::getAtom(STATE), atoms, states.size());
|
|
delete [] atoms;
|
|
}
|
|
|
|
void
|
|
Client::getWMNormalHints(void)
|
|
{
|
|
long dummy;
|
|
XGetWMNormalHints(_dpy, _window, _size, &dummy);
|
|
|
|
// let's do some sanity checking
|
|
if (_size->flags & PBaseSize) {
|
|
if (_size->base_width<0 || _size->base_height<0) {
|
|
_size->base_width = _size->base_height = 0;
|
|
_size->flags &= ~PBaseSize;
|
|
}
|
|
}
|
|
if (_size->flags & PAspect) {
|
|
if (_size->min_aspect.x < 0 || _size->min_aspect.y < 0 ||
|
|
_size->max_aspect.x < 0 || _size->max_aspect.y < 0) {
|
|
|
|
_size->min_aspect.x = _size->min_aspect.y = 0;
|
|
_size->max_aspect.x = _size->max_aspect.y = 0;
|
|
_size->flags &= ~PAspect;
|
|
}
|
|
}
|
|
if (_size->flags & PResizeInc) {
|
|
if (_size->width_inc <= 0 || _size->height_inc <= 0) {
|
|
_size->width_inc = 0;
|
|
_size->height_inc = 0;
|
|
_size->flags &= ~PBaseSize;
|
|
}
|
|
}
|
|
|
|
if (_size->flags & PMaxSize) {
|
|
if (_size->max_width <= 0 || _size->max_height <= 0 ||
|
|
((_size->flags & PMinSize) &&
|
|
(_size->max_width < _size->min_width ||
|
|
_size->max_height < _size->min_height) )) {
|
|
_size->max_width = 0;
|
|
_size->max_height = 0;
|
|
_size->flags &= ~PMaxSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
Client::getWMProtocols(void)
|
|
{
|
|
int count;
|
|
Atom *protocols;
|
|
|
|
if (XGetWMProtocols(_dpy, _window, &protocols, &count) != 0) {
|
|
for (int i = 0; i < count; ++i) {
|
|
if (protocols[i] == Atoms::getAtom(WM_TAKE_FOCUS)) {
|
|
_send_focus_message = true;
|
|
} else if (protocols[i] == Atoms::getAtom(WM_DELETE_WINDOW)) {
|
|
_send_close_message = true;
|
|
}
|
|
}
|
|
|
|
XFree(protocols);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read WM_TRANSIENT_FOR hint.
|
|
*/
|
|
void
|
|
Client::getTransientForHint(void)
|
|
{
|
|
if (! _transient) {
|
|
XGetTransientForHint(_dpy, _window, &_transient_window);
|
|
|
|
if (_transient_window != None) {
|
|
_transient = findClientFromWindow(_transient_window);
|
|
if (_transient) {
|
|
_transient->_transient_clients.push_back(this);
|
|
_transient->addObserver(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
Client::getStrutHint(void)
|
|
{
|
|
int num = 0;
|
|
long *strut = static_cast<long*>(AtomUtil::getEwmhPropData(_window,
|
|
Atoms::getAtom(NET_WM_STRUT),
|
|
XA_CARDINAL, num));
|
|
if (strut) {
|
|
if (_strut) {
|
|
PScreen::instance()->removeStrut(_strut);
|
|
} else {
|
|
_strut = new Strut();
|
|
}
|
|
|
|
*_strut = strut;
|
|
PScreen::instance()->addStrut(_strut);
|
|
|
|
XFree(strut);
|
|
|
|
} else if (_strut) {
|
|
PScreen::instance()->removeStrut(_strut);
|
|
delete _strut;
|
|
_strut = 0;
|
|
}
|
|
}
|
|
|
|
//! @brief
|
|
void
|
|
Client::removeStrutHint(void)
|
|
{
|
|
if (! _strut)
|
|
return;
|
|
|
|
PScreen::instance()->removeStrut(_strut);
|
|
delete _strut;
|
|
_strut = 0;
|
|
}
|
|
|
|
/**
|
|
* Get _PEKWM_FRAME_ORDER hint from client, return < 0 on failure.
|
|
*/
|
|
long
|
|
Client::getPekwmFrameOrder(void)
|
|
{
|
|
long num = -1;
|
|
AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ORDER), num);
|
|
return num;
|
|
}
|
|
|
|
/**
|
|
* Update _PEKWM_FRAME_ORDER hint on client window.
|
|
*/
|
|
void
|
|
Client::setPekwmFrameOrder(long num)
|
|
{
|
|
AtomUtil::setLong(_window, Atoms::getAtom(PEKWM_FRAME_ORDER), num);
|
|
}
|
|
|
|
/**
|
|
* Get _PEKWM_FRAME_ACTIVE hint from client window, return true if
|
|
* client is treated as active.
|
|
*/
|
|
bool
|
|
Client::getPekwmFrameActive(void)
|
|
{
|
|
long act = 0;
|
|
return (AtomUtil::getLong(_window, Atoms::getAtom(PEKWM_FRAME_ACTIVE), act)
|
|
&& act == 1);
|
|
}
|
|
|
|
/**
|
|
* Set _PEKWM_FRAME_ACTIVE hint on client window.
|
|
*/
|
|
void
|
|
Client::setPekwmFrameActive(bool act)
|
|
{
|
|
AtomUtil::setLong(_window, Atoms::getAtom(PEKWM_FRAME_ACTIVE), act ? 1 : 0);
|
|
}
|
|
|
|
/**
|
|
* Update the environment with CLIENT_PID and CLIENT_WINDOW.
|
|
*/
|
|
void
|
|
Client::setClientEnvironment(Client *client)
|
|
{
|
|
if (client) {
|
|
setenv("CLIENT_PID", Util::to_string<long>(client->isRemote() ? -1 : client->getPid()).c_str(), 1);
|
|
setenv("CLIENT_WINDOW", Util::to_string<Window>(client->getWindow()).c_str(), 1);
|
|
} else {
|
|
unsetenv("CLIENT_PID");
|
|
unsetenv("CLIENT_WINDOW");
|
|
}
|
|
}
|