ede/pekwm/Frame.cc

2326 lines
61 KiB
C++

//
// Frame.cc for pekwm
// Copyright © 2002-2009 Claes Nästén <me@pekdon.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 <algorithm>
#include <cstdio>
#include <cwchar>
#include <functional>
#include <iostream>
extern "C" {
#include <X11/Xatom.h>
}
#include "PWinObj.hh"
#include "PDecor.hh"
#include "Frame.hh"
#include "Compat.hh"
#include "PScreen.hh"
#include "Config.hh"
#include "ActionHandler.hh"
#include "AutoProperties.hh"
#include "Client.hh"
#include "ScreenResources.hh"
#include "StatusWindow.hh"
#include "Workspaces.hh"
#include "WindowManager.hh"
#include "KeyGrabber.hh"
#include "PMenu.hh"
#include "Theme.hh"
using std::cerr;
using std::endl;
using std::find;
using std::list;
using std::mem_fun;
using std::string;
using std::vector;
using std::wstring;
using std::swprintf;
list<Frame*> Frame::_frame_list = list<Frame*>();
vector<uint> Frame::_frameid_list = vector<uint>();
Frame* Frame::_tag_frame = 0;
bool Frame::_tag_behind = false;
//! @brief Frame constructor
Frame::Frame(Client *client, AutoProperty *ap)
: PDecor(WindowManager::instance()->getTheme(),
Frame::getClientDecorName(client), client->getWindow()),
_id(0), _client(0), _class_hint(0),
_non_fullscreen_decor_state(0), _non_fullscreen_layer(LAYER_NORMAL)
{
// setup basic pointers
_scr = WindowManager::instance()->getScreen();
// PWinObj attributes
_type = WO_FRAME;
// PDecor attributes
_decor_cfg_child_move_overloaded = true;
_decor_cfg_bpr_replay_pointer = true;
_decor_cfg_bpr_al_title = MOUSE_ACTION_LIST_TITLE_FRAME;
_decor_cfg_bpr_al_child = MOUSE_ACTION_LIST_CHILD_FRAME;
// grab buttons so that we can reply them
for (uint i = 0; i < BUTTON_NO; ++i) {
XGrabButton(_dpy, i, AnyModifier, _window,
True, ButtonPressMask|ButtonReleaseMask,
GrabModeSync, GrabModeAsync, None, None);
}
// get unique id of the frame, if the client didn't have an id
if (! WindowManager::instance()->isStartup()) {
long id;
if (AtomUtil::getLong(client->getWindow(), Atoms::getAtom(PEKWM_FRAME_ID), id)) {
_id = id;
}
} else {
_id = findFrameID();
}
// get the clients class_hint
_class_hint = new ClassHint();
*_class_hint = *client->getClassHint();
_scr->grabServer();
// We don't send any ConfigurRequests during setup, we send one when we
// are finished to minimize traffic and confusion
client->setConfigureRequestLock(true);
// Before setting position and size up we make sure the decor state
// of the client match the decore state of the framewidget
if (! client->hasBorder()) {
setBorder(STATE_UNSET);
}
if (! client->hasTitlebar()) {
setTitlebar(STATE_UNSET);
}
// We first get the size of the window as it will be needed when placing
// the window with the help of WinGravity.
resizeChild(client->getWidth(), client->getHeight());
// setup position
bool place = false;
if (client->isViewable() || client->isPlaced()) {
moveChild(client->getX(), client->getY());
} else if (client->setPUPosition()) {
int x, y;
calcGravityPosition(client->getXSizeHints()->win_gravity, client->getX(), client->getY(), x, y);
move(x, y);
} else {
place = Config::instance()->isPlaceNew();
}
// override both position and size with autoproperties
bool ap_geometry = false;
if (ap) {
if (ap->isMask(AP_FRAME_GEOMETRY|AP_CLIENT_GEOMETRY)) {
ap_geometry = true;
setupAPGeometry(client, ap);
if (ap->frame_gm_mask&(XValue|YValue) || ap->client_gm_mask&(XValue|YValue)) {
place = false;
}
}
if (ap->isMask(AP_PLACE_NEW)) {
place = ap->place_new;
}
}
// still need a position?
if (place) {
Workspaces::instance()->placeWo(this, client->getTransientClientWindow());
}
_old_gm = _gm;
_non_fullscreen_decor_state = client->getDecorState();
_non_fullscreen_layer = client->getLayer();
_scr->ungrabServer(true); // ungrab and sync
// now insert the client in the frame we created.
addChild(client);
// needs to be done before the workspace insert and after the client
// has been inserted, in order for layer settings to be propagated
setLayer(client->getLayer());
// I add these to the list before I insert the client into the frame to
// be able to skip an extra updateClientList
_frame_list.push_back(this);
WindowManager::instance()->addToFrameList(this);
Workspaces::instance()->insert(this);
activateChild(client);
// set the window states, shaded, maximized...
getState(client);
if (! _client->hasStrut() && ! ap_geometry) {
if (fixGeometry()) {
moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
}
}
client->setConfigureRequestLock(false);
client->configureRequestSend();
// Figure out if we should be hidden or not, do not read autoprops
PDecor::setWorkspace(_client->getWorkspace());
woListAdd(this);
_wo_map[_window] = this;
}
//! @brief Frame destructor
Frame::~Frame(void)
{
// remove from lists
_wo_map.erase(_window);
woListRemove(this);
_frame_list.remove(this);
Workspaces::instance()->remove(this);
WindowManager::instance()->removeFromFrameList(this);
if (_tag_frame == this) {
_tag_frame = 0;
}
returnFrameID(_id);
if (_class_hint) {
delete _class_hint;
}
Workspaces::instance()->updateClientList();
Workspaces::instance()->updateClientStackingList();
}
// START - PWinObj interface.
//! @brief Iconifies the Frame.
void
Frame::iconify(void)
{
if (_iconified) {
return;
}
_iconified = true;
unmapWindow();
}
//! @brief Toggles the Frame's sticky state
void
Frame::stick(void)
{
_client->setSticky(_sticky); // FIXME: FRAME
_client->stick();
_sticky = ! _sticky;
// make sure it's visible/hidden
PDecor::setWorkspace(Workspaces::instance()->getActive());
}
void
Frame::raise(void)
{
PDecor::raise();
if (_client->transient_size()) {
Frame *frame;
list<Client*>::iterator it(_client->transient_begin());
for (; it != _client->transient_end(); ++it) {
frame = static_cast<Frame*>((*it)->getParent());
if (frame != this && frame->getActiveClient() == *it) {
frame->raise();
}
}
}
}
//! @brief Sets workspace on frame, wrapper to allow autoproperty loading
void
Frame::setWorkspace(unsigned int workspace)
{
// Duplicate the behavior done in PDecor::setWorkspace to have a sane
// value on _workspace and not NET_WM_STICKY_WINDOW.
if (workspace != NET_WM_STICKY_WINDOW) {
// First we set the workspace, then load autoproperties for possible
// overrun of workspace and then set the workspace.
_workspace = workspace;
readAutoprops(APPLY_ON_WORKSPACE);
workspace = _workspace;
}
PDecor::setWorkspace(workspace);
}
void
Frame::setLayer(unsigned int layer)
{
PDecor::setLayer(layer);
if (_client) {
LayerObservation *observation = new LayerObservation(Layer(layer));
_client->notifyObservers(observation);
delete observation;
}
}
// event handlers
ActionEvent*
Frame::handleMotionEvent(XMotionEvent *ev)
{
// This is true when we have a title button pressed and then we don't want
// to be able to drag windows around, therefore we ignore the event
if (_button) {
return 0;
}
ActionEvent *ae = 0;
uint button = _scr->getButtonFromState(ev->state);
if (ev->window == getTitleWindow()) {
ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION,
Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_TITLE_FRAME));
} else if (ev->window == _client->getWindow()) {
ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION,
Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME));
} else {
uint pos = getBorderPosition(ev->subwindow);
// If ev->subwindow wasn't one of the border windows, perhaps ev->window is.
if (pos == BORDER_NO_POS) {
pos = getBorderPosition(ev->window);
}
if (pos != BORDER_NO_POS) {
list<ActionEvent> *bl = Config::instance()->getBorderListFromPosition(pos);
ae = ActionHandler::findMouseAction(button, ev->state, MOUSE_EVENT_MOTION, bl);
}
}
// check motion threshold
if (ae && (ae->threshold > 0)) {
if (! ActionHandler::checkAEThreshold(ev->x_root, ev->y_root,
_pointer_x, _pointer_y, ae->threshold)) {
ae = 0;
}
}
return ae;
}
//! @brief
ActionEvent*
Frame::handleEnterEvent(XCrossingEvent *ev)
{
// Run event handler to get hoovering to work but ignore action
// returned.
PDecor::handleEnterEvent(ev);
ActionEvent *ae = 0;
list<ActionEvent> *al = 0;
if (ev->window == getTitleWindow()) {
al = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_TITLE_FRAME);
} else if (ev->subwindow == _client->getWindow()) {
al = Config::instance()->getMouseActionList(MOUSE_ACTION_LIST_CHILD_FRAME);
} else {
uint pos = getBorderPosition(ev->window);
if (pos != BORDER_NO_POS) {
al = Config::instance()->getBorderListFromPosition(pos);
}
}
if (al) {
ae = ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_ENTER, al);
}
return ae;
}
//! @brief
ActionEvent*
Frame::handleLeaveEvent(XCrossingEvent *ev)
{
// Run event handler to get hoovering to work but ignore action
// returned.
PDecor::handleLeaveEvent(ev);
ActionEvent *ae;
MouseActionListName ln = MOUSE_ACTION_LIST_TITLE_FRAME;
if (ev->window == _client->getWindow()) {
ln = MOUSE_ACTION_LIST_CHILD_FRAME;
}
ae = ActionHandler::findMouseAction(BUTTON_ANY, ev->state, MOUSE_EVENT_LEAVE,
Config::instance()->getMouseActionList(ln));
return ae;
}
ActionEvent*
Frame::handleMapRequest(XMapRequestEvent *ev)
{
if (! _client || (ev->window != _client->getWindow())) {
return 0;
}
if (! _sticky && (_workspace != Workspaces::instance()->getActive())) {
#ifdef DEBUG
cerr << __FILE__ << "@" << __LINE__ << ": "
<< "Ignoring MapRequest, not on current workspace!" << endl;
#endif // DEBUG
return 0;
}
mapWindow();
return 0;
}
ActionEvent*
Frame::handleUnmapEvent(XUnmapEvent *ev)
{
list<PWinObj*>::iterator it(_child_list.begin());
for (; it != _child_list.end(); ++it) {
if (*(*it) == ev->window) {
(*it)->handleUnmapEvent(ev);
break;
}
}
return 0;
}
// END - PWinObj interface.
void
Frame::handleShapeEvent(XAnyEvent *ev)
{
#ifdef HAVE_SHAPE
if (! _client || (ev->window != _client->getWindow())) {
return;
}
_client->setShaped(setShape());
#endif // HAVE_SHAPE
}
// START - PDecor interface.
bool
Frame::allowMove(void) const
{
return _client ? _client->allowMove() : true;
}
/**
* Return active client, or 0 if no clients or active child is not a Client.
*/
Client*
Frame::getActiveClient(void)
{
if (getActiveChild() && getActiveChild()->getType() == WO_CLIENT) {
return static_cast<Client*>(getActiveChild());
} else {
return 0;
}
}
//! @brief Adds child to the frame.
void
Frame::addChild (PWinObj *child, std::list<PWinObj*>::iterator *it)
{
PDecor::addChild(child, it);
AtomUtil::setLong(child->getWindow(), Atoms::getAtom(PEKWM_FRAME_ID), _id);
child->lower();
}
/**
* Add child preserving order from the previous pekwm run.
*/
void
Frame::addChildOrdered (Client *child)
{
Client *client;
list<PWinObj*>::iterator it(_child_list.begin());
for (; it != _child_list.end(); ++it) {
client = static_cast<Client*>(*it);
if (child->getInitialFrameOrder() < client->getInitialFrameOrder()) {
break;
}
}
addChild(child, &it);
}
//! @brief Removes child from the frame.
void
Frame::removeChild (PWinObj *child, bool do_delete)
{
PDecor::removeChild(child, do_delete);
}
/**
* Activates child in Frame, updating it's state and re-loading decor
* rules to match updated title.
*/
void
Frame::activateChild (PWinObj *child)
{
// FIXME: Update default decoration for this child, can change
// decoration from DEFAULT to REMOTE or WARNING
// Sync the frame state with the client only if we already had a client
if (_client && _client != child) {
applyState(static_cast<Client*>(child));
}
_client = static_cast<Client*>(child);
PDecor::activateChild(child);
// setShape uses current active child, so we need to activate the
// child before setting shape
if (PScreen::instance()->hasExtensionShape()) {
_client->setShaped(setShape());
}
#ifdef OPACITY
setOpacity(_client);
#endif // OPACITY
if (_focused) {
child->giveInputFocus();
}
// Reload decor rules if needed.
handleTitleChange(_client);
Workspaces::instance()->updateClientList();
Workspaces::instance()->updateClientStackingList();
}
/**
* Called when child order is updated, re-sets the titles and updates
* the _PEKWM_FRAME hints.
*/
void
Frame::updatedChildOrder(void)
{
titleClear();
Client *client;
list<PWinObj*>::iterator it(_child_list.begin());
for (long num = 0; it != _child_list.end(); ++num, ++it) {
client = static_cast<Client*>(*it);
client->setPekwmFrameOrder(num);
titleAdd(client->getTitle());
}
updatedActiveChild();
}
/**
* Set the active title and update the _PEKWM_FRAME hints.
*/
void
Frame::updatedActiveChild(void)
{
titleSetActive(0);
list<PWinObj*>::iterator it(_child_list.begin());
for (uint i = 0; it != _child_list.end(); ++i, ++it) {
static_cast<Client*>(*it)->setPekwmFrameActive(_child == *it);
if (_child == *it) {
titleSetActive(i);
}
}
renderTitle();
}
//! @brief
void
Frame::getDecorInfo(wchar_t *buf, uint size)
{
uint width, height;
if (_client) {
calcSizeInCells(width, height);
} else {
width = _gm.width;
height = _gm.height;
}
swprintf(buf, size, L"%d+%d+%d+%d", width, height, _gm.x, _gm.y);
}
//! @brief
void
Frame::setShaded(StateAction sa)
{
bool shaded = isShaded();
PDecor::setShaded(sa);
if (shaded != isShaded()) {
_client->setShade(isShaded());
_client->updateEwmhStates();
}
}
//! @brief
int
Frame::resizeHorzStep(int diff) const
{
if (! _client) {
return diff;
}
int diff_ret = 0;
uint min = _gm.width - getChildWidth();
if (min == 0) { // borderless windows, we don't want X errors
min = 1;
}
const XSizeHints *hints = _client->getXSizeHints(); // convenience
// if we have ResizeInc hint set we use it instead of pixel diff
if (hints->flags&PResizeInc) {
if (diff > 0) {
diff_ret = hints->width_inc;
} else if ((_gm.width - hints->width_inc) >= min) {
diff_ret = -hints->width_inc;
}
} else if ((_gm.width + diff) >= min) {
diff_ret = diff;
}
// check max/min size hints
if (diff > 0) {
if ((hints->flags&PMaxSize) &&
((getChildWidth() + diff) > unsigned(hints->max_width))) {
diff_ret = _gm.width - hints->max_width + min;
}
} else if ((hints->flags&PMinSize) &&
((getChildWidth() + diff) < unsigned(hints->min_width))) {
diff_ret = _gm.width - hints->min_width + min;
}
return diff_ret;
}
//! @brief
int
Frame::resizeVertStep(int diff) const
{
if (! _client) {
return diff;
}
int diff_ret = 0;
uint min = _gm.height - getChildHeight();
if (min == 0) { // borderless windows, we don't want X errors
min = 1;
}
const XSizeHints *hints = _client->getXSizeHints(); // convenience
// if we have ResizeInc hint set we use it instead of pixel diff
if (hints->flags&PResizeInc) {
if (diff > 0) {
diff_ret = hints->height_inc;
} else if ((_gm.height - hints->height_inc) >= min) {
diff_ret = -hints->height_inc;
}
} else {
diff_ret = diff;
}
// check max/min size hints
if (diff > 0) {
if ((hints->flags&PMaxSize) &&
((getChildHeight() + diff) > unsigned(hints->max_height))) {
diff_ret = _gm.height - hints->max_height + min;
}
} else if ((hints->flags&PMinSize) &&
((getChildHeight() + diff) < unsigned(hints->min_height))) {
diff_ret = _gm.height - hints->min_width + min;
}
return diff_ret;
}
// END - PDecor interface.
//! @brief Sets _PEKWM_FRAME_ID on all children in the frame
void
Frame::setId(uint id)
{
_id = id;
list<PWinObj*>::iterator it(_child_list.begin());
long atom = Atoms::getAtom(PEKWM_FRAME_ID);
for (; it != _child_list.end(); ++it) {
AtomUtil::setLong((*it)->getWindow(), atom, id);
}
}
//! @brief Gets the state from the Client
void
Frame::getState(Client *cl)
{
if (! cl)
return;
bool b_client_iconified = cl->isIconified ();
if (_sticky != cl->isSticky())
_sticky = ! _sticky;
if (_maximized_horz != cl->isMaximizedHorz())
setStateMaximized(STATE_TOGGLE, true, false, false);
if (_maximized_vert != cl->isMaximizedVert())
setStateMaximized(STATE_TOGGLE, false, true, false);
if (isShaded() != cl->isShaded())
setShaded(STATE_TOGGLE);
if (getLayer() != cl->getLayer()) {
setLayer(cl->getLayer());
}
if (_workspace != cl->getWorkspace())
PDecor::setWorkspace(cl->getWorkspace());
// We need to set border and titlebar before setting fullscreen, as
// fullscreen will unset border and titlebar if needed.
if (hasBorder() != _client->hasBorder())
setBorder(STATE_TOGGLE);
if (hasTitlebar() != _client->hasTitlebar())
setTitlebar(STATE_TOGGLE);
if (_fullscreen != cl->isFullscreen())
setStateFullscreen(STATE_TOGGLE);
if (_iconified != b_client_iconified) {
if (_iconified) {
mapWindow();
} else {
iconify();
}
}
if (_skip != cl->getSkip()) {
setSkip(cl->getSkip());
}
}
//! @brief Applies the frame's state on the Client
void
Frame::applyState(Client *cl)
{
if (! cl) {
return;
}
cl->setSticky(_sticky);
cl->setMaximizedHorz(_maximized_horz);
cl->setMaximizedVert(_maximized_vert);
cl->setShade(isShaded());
cl->setWorkspace(_workspace);
cl->setLayer(getLayer());
// fix border / titlebar state
cl->setBorder(hasBorder());
cl->setTitlebar(hasTitlebar());
// make sure the window has the correct mapped state
if (_mapped != cl->isMapped()) {
if (! _mapped) {
cl->unmapWindow();
} else {
cl->mapWindow();
}
}
cl->updateEwmhStates();
}
//! @brief Sets skip state.
void
Frame::setSkip(uint skip)
{
PDecor::setSkip(skip);
// Propagate changes on client as well
if (_client) {
_client->setSkip(skip);
}
}
//! @brief Find Frame with Window
//! @param win Window to search for.
//! @return Frame if found, else 0.
Frame*
Frame::findFrameFromWindow(Window win)
{
// Validate input window.
if ((win == None) || (win == PScreen::instance()->getRoot())) {
return 0;
}
list<Frame*>::iterator it(_frame_list.begin());
for(; it !=_frame_list.end(); ++it) {
if (win == (*it)->getWindow()) { // operator == does more than that
return (*it);
}
}
return 0;
}
//! @brief Find Frame with id.
//! @param id ID to search for.
//! @return Frame if found, else 0.
Frame*
Frame::findFrameFromID(uint id)
{
list<Frame*>::iterator it(Frame::frame_begin());
for (; it != Frame::frame_end(); ++it) {
if ((*it)->getId() == id) {
return (*it);
}
}
return 0;
}
//! @brief
void
Frame::setupAPGeometry(Client *client, AutoProperty *ap)
{
// frame geomtry overides client geometry
// get client geometry
if (ap->isMask(AP_CLIENT_GEOMETRY)) {
Geometry gm(client->_gm);
applyAPGeometry(gm, ap->client_gm, ap->client_gm_mask);
if (ap->client_gm_mask&(XValue|YValue)) {
moveChild(gm.x, gm.y);
}
if(ap->client_gm_mask&(WidthValue|HeightValue)) {
resizeChild(gm.width, gm.height);
}
}
// get frame geometry
if (ap->isMask(AP_FRAME_GEOMETRY)) {
applyAPGeometry(_gm, ap->frame_gm, ap->frame_gm_mask);
if (ap->frame_gm_mask&(XValue|YValue)) {
move(_gm.x, _gm.y);
}
if (ap->frame_gm_mask&(WidthValue|HeightValue)) {
resize(_gm.width, _gm.height);
}
}
}
//! @brief Apply autoproperties geometry.
//! @param gm Geometry to modify.
//! @param ap_gm Geometry to get values from.
//! @param mask Geometry mask.
void
Frame::applyAPGeometry(Geometry &gm, const Geometry &ap_gm, int mask)
{
// Read size before position so negative position works, if size is
// < 1 consider it to be full screen size.
if (mask&WidthValue) {
if (ap_gm.width < 1) {
gm.width = _scr->getWidth();
} else {
gm.width = ap_gm.width;
}
}
if (mask&HeightValue) {
if (ap_gm.height < 1) {
} else {
gm.height = ap_gm.height;
}
}
// Read position
if (mask&XValue) {
gm.x = ap_gm.x;
if (mask&XNegative) {
gm.x += _scr->getWidth() - gm.width;
}
}
if (mask&YValue) {
gm.y = ap_gm.y;
if (mask&YNegative) {
gm.y += _scr->getHeight() - gm.height;
}
}
}
//! @brief Finds free Frame ID.
//! @return First free Frame ID.
uint
Frame::findFrameID(void)
{
uint id = 0;
if (_frameid_list.size()) {
// Check for used Frame IDs
id = _frameid_list.back();
_frameid_list.pop_back();
} else {
// No free, get next number (Frame is not in list when this is called.)
id = _frame_list.size() + 1;
}
return id;
}
//! @brief Returns Frame ID to used frame id list.
//! @param id ID to return.
void
Frame::returnFrameID(uint id)
{
vector<uint>::iterator it(_frameid_list.begin());
for (; it != _frameid_list.end() && id < *it; ++it)
;
_frameid_list.insert(it, id);
}
/**
* Return decor name matching clients property, defaults to
* PDecor::DEFAULT_DECOR_NAME
*/
std::string
Frame::getClientDecorName(Client *client)
{
DecorProperty *prop = WindowManager::instance()->getAutoProperties()->findDecorProperty(client->getClassHint());
return prop ? prop->getName() : PDecor::DEFAULT_DECOR_NAME;
}
//! @brief Resets Frame IDs.
void
Frame::resetFrameIDs(void)
{
list<Frame*>::iterator it(_frame_list.begin());
for (uint id = 1; it != _frame_list.end(); ++id, ++it) {
(*it)->setId(id);
}
}
//! @brief Removes the client from the Frame and creates a new Frame for it
void
Frame::detachClient(Client *client)
{
if (client->getParent() != this) {
return;
}
if (_child_list.size() > 1) {
removeChild(client);
client->move(_gm.x, _gm.y + borderTop());
Frame *frame = new Frame(client, 0);
client->setParent(frame);
client->setWorkspace(Workspaces::instance()->getActive());
setFocused(false);
}
}
//! @brief Makes sure the frame doesn't cover any struts / the harbour.
bool
Frame::fixGeometry(void)
{
Geometry head, before;
PScreen::instance()->getHeadInfoWithEdge(getNearestHead(), head);
before = _gm;
// fix size
if (_gm.width > head.width) {
_gm.width = head.width;
} if (_gm.height > head.height) {
_gm.height = head.height;
}
// fix position
if (_gm.x < head.x) {
_gm.x = head.x;
} else if ((_gm.x + _gm.width) > (head.x + head.width)) {
_gm.x = head.x + head.width - _gm.width;
}
if (_gm.y < head.y) {
_gm.y = head.y;
} else if ((_gm.y + _gm.height) > (head.y + head.height)) {
_gm.y = head.y + head.height - _gm.height;
}
return (_gm != before);
}
//! @brief Initiates grouping move, based on a XMotionEvent.
void
Frame::doGroupingDrag(XMotionEvent *ev, Client *client, bool behind) // FIXME: rewrite
{
if (! client) {
return;
}
int o_x, o_y;
o_x = ev ? ev->x_root : 0;
o_y = ev ? ev->y_root : 0;
wstring name(L"Grouping ");
if (client->getTitle()->getVisible().size() > 0) {
name += client->getTitle()->getVisible();
} else {
name += L"No Name";
}
bool status = _scr->grabPointer(_scr->getRoot(),
ButtonReleaseMask|PointerMotionMask, None);
if (status != true) {
return;
}
StatusWindow *sw = StatusWindow::instance();
sw->draw(name); // resize window and render bg
sw->move(o_x, o_y);
sw->mapWindowRaised();
sw->draw(name); // redraw after map
XEvent e;
while (true) { // this breaks when we get an button release
XMaskEvent(_dpy, PointerMotionMask|ButtonReleaseMask, &e);
switch (e.type) {
case MotionNotify:
// update the position
o_x = e.xmotion.x_root;
o_y = e.xmotion.y_root;
sw->move(o_x, o_y);
sw->draw(name);
break;
case ButtonRelease:
sw->unmapWindow();
_scr->ungrabPointer();
Client *search = 0;
// only group if we have grouping turned on
if (WindowManager::instance()->isAllowGrouping()) {
int x, y;
Window win;
// find the frame we dropped the client on
XTranslateCoordinates(_dpy, _scr->getRoot(), _scr->getRoot(),
e.xmotion.x_root, e.xmotion.y_root,
&x, &y, &win);
search = Client::findClient(win);
}
// if we found a client, and it's not in the current frame and
// it has a "normal" ( make configurable? ) layer we group
if (search && search->getParent() &&
(search->getParent() != this) &&
(search->getLayer() > LAYER_BELOW) &&
(search->getLayer() < LAYER_ONTOP)) {
// if we currently have focus and the frame exists after we remove
// this client we need to redraw it as unfocused
bool focus = behind ? false : (_child_list.size() > 1);
removeChild(client);
Frame *frame = static_cast<Frame*>(search->getParent());
frame->addChild(client);
if (! behind) {
frame->activateChild(client);
frame->giveInputFocus();
}
if (focus) {
setFocused(false);
}
} else if (_child_list.size() > 1) {
// if we have more than one client in the frame detach this one
removeChild(client);
client->move(e.xmotion.x_root, e.xmotion.y_root);
Frame *frame = new Frame(client, 0);
client->setParent(frame);
// make sure the client ends up on the current workspace
client->setWorkspace(Workspaces::instance()->getActive());
// make sure it get's focus
setFocused(false);
frame->giveInputFocus();
}
return;
}
}
}
//! @brief Initiates resizing of a window based on motion event
void
Frame::doResize(XMotionEvent *ev)
{
if (! ev) {
return;
}
// figure out which part of the window we are in
bool left = false, top = false;
if (ev->x < signed(_gm.width / 2)) {
left = true;
}
if (ev->y < signed(_gm.height / 2)) {
top = true;
}
doResize(left, true, top, true);
}
//! @brief Initiates resizing of a window based border position
void
Frame::doResize(BorderPosition pos)
{
bool x = false, y = false;
bool left = false, top = false, resize = true;
switch (pos) {
case BORDER_TOP_LEFT:
x = y = left = top = true;
break;
case BORDER_TOP:
y = top = true;
break;
case BORDER_TOP_RIGHT:
x = y = top = true;
break;
case BORDER_LEFT:
x = left = true;
break;
case BORDER_RIGHT:
x = true;
break;
case BORDER_BOTTOM_LEFT:
x = y = left = true;
break;
case BORDER_BOTTOM:
y = true;
break;
case BORDER_BOTTOM_RIGHT:
x = y = true;
break;
default:
resize = false;
break;
}
if (resize) {
doResize(left, x, top, y);
}
}
//! @brief Resizes the frame by handling MotionNotify events.
void
Frame::doResize(bool left, bool x, bool top, bool y)
{
if (! _client->allowResize() || isShaded()) {
return;
}
if (! _scr->grabPointer(_scr->getRoot(), ButtonMotionMask|ButtonReleaseMask,
ScreenResources::instance()->getCursor(ScreenResources::CURSOR_RESIZE))) {
return;
}
setShaded(STATE_UNSET); // make sure the frame isn't shaded
setStateFullscreen(STATE_UNSET);
// Initialize variables
int start_x, new_x;
int start_y, new_y;
uint last_width, old_width;
uint last_height, old_height;
start_x = new_x = left ? _gm.x : (_gm.x + _gm.width);
start_y = new_y = top ? _gm.y : (_gm.y + _gm.height);
last_width = old_width = _gm.width;
last_height = old_height = _gm.height;
// the basepoint of our window
_click_x = left ? (_gm.x + _gm.width) : _gm.x;
_click_y = top ? (_gm.y + _gm.height) : _gm.y;
int pointer_x = _gm.x, pointer_y = _gm.y;
_scr->getMousePosition(pointer_x, pointer_y);
wchar_t buf[128];
getDecorInfo(buf, 128);
bool center_on_root = Config::instance()->isShowStatusWindowOnRoot();
StatusWindow *sw = StatusWindow::instance();
if (Config::instance()->isShowStatusWindow()) {
sw->draw(buf, true, center_on_root ? 0 : &_gm);
sw->mapWindowRaised();
sw->draw(buf, true, center_on_root ? 0 : &_gm);
}
bool outline = ! Config::instance()->getOpaqueResize();
// grab server, we don't want invert traces
if (outline) {
_scr->grabServer();
}
const long resize_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask;
XEvent ev;
bool exit = false;
while (exit != true) {
if (outline) {
drawOutline(_gm);
}
XMaskEvent(_dpy, resize_mask, &ev);
if (outline) {
drawOutline(_gm); // clear
}
switch (ev.type) {
case MotionNotify:
// Flush all pointer motion, no need to redraw and redraw.
PScreen::removeMotionEvents();
if (x) {
new_x = start_x - pointer_x + ev.xmotion.x;
}
if (y) {
new_y = start_y - pointer_y + ev.xmotion.y;
}
recalcResizeDrag(new_x, new_y, left, top);
getDecorInfo(buf, 128);
if (Config::instance()->isShowStatusWindow()) {
sw->draw(buf, true, center_on_root ? 0 : &_gm);
}
// only updated when needed when in opaque mode
if (! outline) {
if ((old_width != _gm.width) || (old_height != _gm.height)) {
moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
}
old_width = _gm.width;
old_height = _gm.height;
}
break;
case ButtonRelease:
exit = true;
XPutBackEvent(_dpy, &ev);
break;
}
}
if (Config::instance()->isShowStatusWindow()) {
sw->unmapWindow();
}
_scr->ungrabPointer();
// Make sure the state isn't set to maximized after we've resized.
if (_maximized_horz || _maximized_vert) {
_maximized_horz = false;
_maximized_vert = false;
_client->setMaximizedHorz(false);
_client->setMaximizedVert(false);
_client->updateEwmhStates();
}
if (outline) {
moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
_scr->ungrabServer(true);
}
}
//! @brief Updates the width, height of the frame when resizing it.
void
Frame::recalcResizeDrag(int nx, int ny, bool left, bool top)
{
uint brdr_lr = borderLeft() + borderRight();
uint brdr_tb = borderTop() + borderBottom();
if (left) {
if (nx >= signed(_click_x - brdr_lr))
nx = _click_x - brdr_lr - 1;
} else {
if (nx <= signed(_click_x + brdr_lr))
nx = _click_x + brdr_lr + 1;
}
if (top) {
if (ny >= signed(_click_y - getTitleHeight() - brdr_tb))
ny = _click_y - getTitleHeight() - brdr_tb - 1;
} else {
if (ny <= signed(_click_y + getTitleHeight() + brdr_tb))
ny = _click_y + getTitleHeight() + brdr_tb + 1;
}
uint width = left ? (_click_x - nx) : (nx - _click_x);
uint height = top ? (_click_y - ny) : (ny - _click_y);
width -= brdr_lr;
height -= brdr_tb + getTitleHeight();
_client->getAspectSize(&width, &height, width, height);
const XSizeHints *hints = _client->getXSizeHints();
// check so we aren't overriding min or max size
if (hints->flags & PMinSize) {
if (signed(width) < hints->min_width)
width = hints->min_width;
if (signed(height) < hints->min_height)
height = hints->min_height;
}
if (hints->flags & PMaxSize) {
if (signed(width) > hints->max_width)
width = hints->max_width;
if (signed(height) > hints->max_height)
height = hints->max_height;
}
_gm.width = width + brdr_lr;
_gm.height = height + getTitleHeight() + brdr_tb;
_gm.x = left ? (_click_x - _gm.width) : _click_x;
_gm.y = top ? (_click_y - _gm.height) : _click_y;
}
//! @brief Moves the Frame to the screen edge ori ( considering struts )
void
Frame::moveToEdge(OrientationType ori)
{
uint head_nr;
Geometry head, real_head;
head_nr = getNearestHead();
_scr->getHeadInfo(head_nr, real_head);
_scr->getHeadInfoWithEdge(head_nr, head);
switch (ori) {
case TOP_LEFT:
_gm.x = head.x;
_gm.y = head.y;
break;
case TOP_EDGE:
_gm.y = head.y;
break;
case TOP_CENTER_EDGE:
_gm.x = real_head.x + ((real_head.width - _gm.width) / 2);
_gm.y = head.y;
break;
case TOP_RIGHT:
_gm.x = head.x + head.width - _gm.width;
_gm.y = head.y;
break;
case BOTTOM_RIGHT:
_gm.x = head.x + head.width - _gm.width;
_gm.y = head.y + head.height - _gm.height;
break;
case BOTTOM_EDGE:
_gm.y = head.y + head.height - _gm.height;
break;
case BOTTOM_CENTER_EDGE:
_gm.x = real_head.x + ((real_head.width - _gm.width) / 2);
_gm.y = head.y + head.height - _gm.height;
break;
case BOTTOM_LEFT:
_gm.x = head.x;
_gm.y = head.y + head.height - _gm.height;
break;
case LEFT_EDGE:
_gm.x = head.x;
break;
case LEFT_CENTER_EDGE:
_gm.x = head.x;
_gm.y = real_head.y + ((real_head.height - _gm.height) / 2);
break;
case RIGHT_EDGE:
_gm.x = head.x + head.width - _gm.width;
break;
case RIGHT_CENTER_EDGE:
_gm.x = head.x + head.width - _gm.width;
_gm.y = real_head.y + ((real_head.height - _gm.height) / 2);
break;
case CENTER:
_gm.x = real_head.x + ((real_head.width - _gm.width) / 2);
_gm.y = real_head.y + ((real_head.height - _gm.height) / 2);
default:
// DO NOTHING
break;
}
move(_gm.x, _gm.y);
}
//! @brief Updates all inactive childrens geometry and state
void
Frame::updateInactiveChildInfo(void)
{
if (! _client) {
return;
}
list<PWinObj*>::iterator it(_child_list.begin());
for (; it != _child_list.end(); ++it) {
if (*it != _client) {
applyState(static_cast<Client*>(*it));
(*it)->resize(getChildWidth(), getChildHeight());
}
}
}
// STATE actions begin
//! @brief Toggles current clients max size
//! @param sa State to set
//! @param horz Include horizontal in (de)maximize
//! @param vert Include vertcical in (de)maximize
//! @param fill Limit size by other frame boundaries ( defaults to false )
void
Frame::setStateMaximized(StateAction sa, bool horz, bool vert, bool fill)
{
// we don't want to maximize transients
if (_client->isTransient()) {
return;
}
setShaded(STATE_UNSET);
setStateFullscreen(STATE_UNSET);
// make sure the two states are in sync if toggling
if ((horz == vert) && (sa == STATE_TOGGLE)) {
if (_maximized_horz != _maximized_vert) {
horz = ! _maximized_horz;
vert = ! _maximized_vert;
}
}
XSizeHints *size_hint = _client->getXSizeHints(); // convenience
Geometry head;
_scr->getHeadInfoWithEdge(getNearestHead(), head);
int max_x, max_r, max_y, max_b;
max_x = head.x;
max_r = head.width + head.x;
max_y = head.y;
max_b = head.height + head.y;
if (fill) {
getMaxBounds(max_x, max_r, max_y, max_b);
// make sure vert and horz gets set if fill is on
sa = STATE_SET;
}
if (horz && (fill || _client->allowMaximizeHorz())) {
// maximize
if ((sa == STATE_SET) ||
((sa == STATE_TOGGLE) && ! _maximized_horz)) {
uint h_decor = _gm.width - getChildWidth();
if (! fill) {
_old_gm.x = _gm.x;
_old_gm.width = _gm.width;
}
_gm.x = max_x;
_gm.width = max_r - max_x;
if ((size_hint->flags&PMaxSize) &&
(_gm.width > (size_hint->max_width + h_decor))) {
_gm.width = size_hint->max_width + h_decor;
}
// demaximize
} else if ((sa == STATE_UNSET) ||
((sa == STATE_TOGGLE) && _maximized_horz)) {
_gm.x = _old_gm.x;
_gm.width = _old_gm.width;
}
// we unset the maximized state if we use maxfill
_maximized_horz = fill ? false : ! _maximized_horz;
_client->setMaximizedHorz(_maximized_horz);
}
if (vert && (fill || _client->allowMaximizeVert())) {
// maximize
if ((sa == STATE_SET) ||
((sa == STATE_TOGGLE) && ! _maximized_vert)) {
uint v_decor = _gm.height - getChildHeight();
if (! fill) {
_old_gm.y = _gm.y;
_old_gm.height = _gm.height;
}
_gm.y = max_y;
_gm.height = max_b - max_y;
if ((size_hint->flags&PMaxSize) &&
(_gm.height > (size_hint->max_height + v_decor))) {
_gm.height = size_hint->max_height + v_decor;
}
// demaximize
} else if ((sa == STATE_UNSET) ||
((sa == STATE_TOGGLE) && _maximized_vert)) {
_gm.y = _old_gm.y;
_gm.height = _old_gm.height;
}
// we unset the maximized state if we use maxfill
_maximized_vert = fill ? false : ! _maximized_vert;
_client->setMaximizedVert(_maximized_vert);
}
fixGeometry(); // harbour already considered
downSize(_gm, true, true); // keep x and keep y ( make conform to inc size )
moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
_client->updateEwmhStates();
}
//! @brief Set fullscreen state
void
Frame::setStateFullscreen(StateAction sa)
{
if (! ActionUtil::needToggle(sa, _fullscreen)) {
return;
}
bool lock = _client->setConfigureRequestLock(true);
if (_fullscreen) {
if ((_non_fullscreen_decor_state&DECOR_BORDER) != hasBorder()) {
setBorder(STATE_TOGGLE);
}
if ((_non_fullscreen_decor_state&DECOR_TITLEBAR) != hasTitlebar()) {
setTitlebar(STATE_TOGGLE);
}
_gm = _old_gm;
} else {
_old_gm = _gm;
_non_fullscreen_decor_state = _client->getDecorState();
_non_fullscreen_layer = _client->getLayer();
setBorder(STATE_UNSET);
setTitlebar(STATE_UNSET);
Geometry head;
uint nr = getNearestHead();
_scr->getHeadInfo(nr, head);
_gm = head;
}
_fullscreen = !_fullscreen;
_client->setFullscreen(_fullscreen);
moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
// Re-stack window if fullscreen is above other windows.
if (Config::instance()->isFullscreenAbove()) {
setLayer(_fullscreen ? LAYER_ABOVE_DOCK : _non_fullscreen_layer);
raise();
}
_client->setConfigureRequestLock(lock);
_client->configureRequestSend();
_client->updateEwmhStates();
}
//! @brief
void
Frame::setStateSticky(StateAction sa)
{
if (ActionUtil::needToggle(sa, _sticky)) {
stick();
}
}
void
Frame::setStateAlwaysOnTop(StateAction sa)
{
if (! ActionUtil::needToggle(sa, getLayer() == LAYER_ONTOP)) {
return;
}
_client->alwaysOnTop(getLayer() < LAYER_ONTOP);
setLayer(_client->getLayer());
raise();
}
void
Frame::setStateAlwaysBelow(StateAction sa)
{
if (! ActionUtil::needToggle(sa, getLayer() == LAYER_BELOW)) {
return;
}
_client->alwaysBelow(getLayer() > LAYER_BELOW);
setLayer(_client->getLayer());
lower();
}
//! @brief Hides/Shows the border depending on _client
//! @param sa State to set
void
Frame::setStateDecorBorder(StateAction sa)
{
bool border = hasBorder();
setBorder(sa);
// state changed, update client and atom state
if (border != hasBorder()) {
_client->setBorder(hasBorder());
// update the _PEKWM_FRAME_DECOR hint
AtomUtil::setLong(_client->getWindow(),
Atoms::getAtom(PEKWM_FRAME_DECOR),
_client->getDecorState());
}
}
//! @brief Hides/Shows the titlebar depending on _client
//! @param sa State to set
void
Frame::setStateDecorTitlebar(StateAction sa)
{
bool titlebar = hasTitlebar();
setTitlebar(sa);
// state changed, update client and atom state
if (titlebar != hasTitlebar()) {
_client->setTitlebar(hasTitlebar());
AtomUtil::setLong(_client->getWindow(),
Atoms::getAtom(PEKWM_FRAME_DECOR),
_client->getDecorState());
}
}
//! @brief
void
Frame::setStateIconified(StateAction sa)
{
if (! ActionUtil::needToggle(sa, _iconified)) {
return;
}
if (_iconified) {
mapWindow();
} else {
iconify();
}
}
//! (Un)Sets the tagged Frame.
//!
//! @param sa Set/Unset or Toggle the state.
//! @param behind Should tagged actions be behind (non-focused).
void
Frame::setStateTagged(StateAction sa, bool behind)
{
if (ActionUtil::needToggle(sa, (_tag_frame != 0))) {
_tag_frame = (this == _tag_frame) ? 0 : this;
_tag_behind = behind;
}
}
//! @brief
void
Frame::setStateSkip(StateAction sa, uint skip)
{
if (! ActionUtil::needToggle(sa, _skip&skip)) {
return;
}
if (_skip&skip) {
_skip &= ~skip;
} else {
_skip |= skip;
}
setSkip(_skip);
}
//! @brief Sets client title
void
Frame::setStateTitle(StateAction sa, Client *client, const std::wstring &title)
{
if (sa == STATE_SET) {
client->getTitle()->setUser(title);
} else if (sa == STATE_UNSET) {
client->getTitle()->setUser(L"");
client->readName();
} else {
if (client->getTitle()->isUserSet()) {
client->getTitle()->setUser(L"");
} else {
client->getTitle()->setUser(title);
}
}
// Set PEKWM_TITLE atom to preserve title on client between sessions.
AtomUtil::setString(client->getWindow(),
Atoms::getAtom(PEKWM_TITLE),
Util::to_mb_str(client->getTitle()->getUser()));
renderTitle();
}
//! @brief Sets clients marked state.
//! @param sa
//! @param client
void
Frame::setStateMarked(StateAction sa, Client *client)
{
if (! client || ! ActionUtil::needToggle(sa, client->isMarked())) {
return;
}
// Set marked state and re-render title to update visual marker.
client->setStateMarked(sa);
renderTitle();
}
#ifdef OPACITY
void
Frame::setStateOpaque(StateAction sa)
{
if (! ActionUtil::needToggle(sa, _opaque)) {
return;
}
_client->setOpaque(!_opaque);
setOpaque(!_opaque);
}
#endif // OPACITY
// STATE actions end
//! @brief
void
Frame::getMaxBounds(int &max_x,int &max_r, int &max_y, int &max_b)
{
int f_r, f_b;
int x, y, h, w, r, b;
f_r = getRX();
f_b = getBY();
list<Frame*>::iterator it = _frame_list.begin();
for (; it != _frame_list.end(); ++it) {
if (! (*it)->isMapped()) {
continue;
}
x = (*it)->getX();
y = (*it)->getY();
h = (*it)->getHeight();
w = (*it)->getWidth();
r = (*it)->getRX();
b = (*it)->getBY();
// update max borders when other frame border lies between
// this border and prior max border (originally screen/head edge)
if ((r >= max_x) && (r <= _gm.x) && ! ((y >= f_b) || (b <= _gm.y))) {
max_x = r;
}
if ((x <= max_r) && (x >= f_r) && ! ((y >= f_b) || (b <= _gm.y))) {
max_r = x;
}
if ((b >= max_y) && (b <= _gm.y) && ! ((x >= f_r) || (r <= _gm.x))) {
max_y = b;
}
if ((y <= max_b) && (y >= f_b) && ! ((x >= f_r) || (r <= _gm.x))) {
max_b = y;
}
}
}
//! @brief
void
Frame::growDirection(uint direction)
{
Geometry head;
_scr->getHeadInfoWithEdge(getNearestHead(), head);
switch (direction) {
case DIRECTION_UP:
_gm.height = getBY() - head.y;
_gm.y = head.y;
break;
case DIRECTION_DOWN:
_gm.height = head.y + head.height - _gm.y;
break;
case DIRECTION_LEFT:
_gm.width = getRX() - head.x;
_gm.x = head.x;
break;
case DIRECTION_RIGHT:
_gm.width = head.x + head.width - _gm.x;
break;
default:
break;
}
downSize(_gm, (direction != DIRECTION_LEFT), (direction != DIRECTION_UP));
moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
}
//! @brief Closes the frame and all clients in it
void
Frame::close(void)
{
list<PWinObj*>::iterator it(_child_list.begin());
for (; it != _child_list.end(); ++it) {
static_cast<Client*>(*it)->close();
}
}
//! @brief Reads autoprops for the active client.
//! @param type Defaults to APPLY_ON_RELOAD
void
Frame::readAutoprops(uint type)
{
if ((type != APPLY_ON_RELOAD) && (type != APPLY_ON_WORKSPACE))
return;
_class_hint->title = _client->getTitle()->getReal();
AutoProperty *data =
AutoProperties::instance()->findAutoProperty(_class_hint, _workspace, type);
_class_hint->title = L"";
if (! data) {
return;
}
// Set the correct group of the window
_class_hint->group = data->group_name;
if (_class_hint == _client->getClassHint()
&& (_client->isTransient()
&& ! data->isApplyOn(APPLY_ON_TRANSIENT))) {
return;
}
if (data->isMask(AP_STICKY) && _sticky != data->sticky) {
stick();
}
if (data->isMask(AP_SHADED) && (isShaded() != data->shaded)) {
setShaded(STATE_UNSET);
}
if (data->isMask(AP_MAXIMIZED_HORIZONTAL)
&& (_maximized_horz != data->maximized_horizontal)) {
setStateMaximized(STATE_TOGGLE, true, false, false);
}
if (data->isMask(AP_MAXIMIZED_VERTICAL)
&& (_maximized_vert != data->maximized_vertical)) {
setStateMaximized(STATE_TOGGLE, false, true, false);
}
if (data->isMask(AP_FULLSCREEN) && (_fullscreen != data->fullscreen)) {
setStateFullscreen(STATE_TOGGLE);
}
if (data->isMask(AP_ICONIFIED) && (_iconified != data->iconified)) {
if (_iconified) {
mapWindow();
} else {
iconify();
}
}
if (data->isMask(AP_WORKSPACE)) {
// In order to avoid eternal recursion, the workspace is only set here
// and then actually called PDecor::setWorkspace from Frame::setWorkspace
if (type == APPLY_ON_WORKSPACE) {
_workspace = data->workspace;
} else if (_workspace != data->workspace) {
// Call PDecor directly to avoid recursion.
PDecor::setWorkspace(data->workspace);
}
}
if (data->isMask(AP_SHADED) && (isShaded() != data->shaded)) {
setShaded(STATE_TOGGLE);
}
if (data->isMask(AP_LAYER) && (data->layer <= LAYER_MENU)) {
_client->setLayer(data->layer);
raise(); // restack the frame
}
if (data->isMask(AP_FRAME_GEOMETRY|AP_CLIENT_GEOMETRY)) {
setupAPGeometry(_client, data);
// Apply changes
moveResize(_gm.x, _gm.y, _gm.width, _gm.height);
}
if (data->isMask(AP_BORDER) && (hasBorder() != data->border)) {
setStateDecorBorder(STATE_TOGGLE);
}
if (data->isMask(AP_TITLEBAR) && (hasTitlebar() != data->titlebar)) {
setStateDecorTitlebar(STATE_TOGGLE);
}
if (data->isMask(AP_SKIP)) {
_client->setSkip(data->skip);
setSkip(_client->getSkip());
}
if (data->isMask(AP_FOCUSABLE)) {
_client->setFocusable(data->focusable);
}
}
//! @brief Figure out how large the frame is in cells.
void
Frame::calcSizeInCells(uint &width, uint &height)
{
const XSizeHints *hints = _client->getXSizeHints();
if (hints->flags&PResizeInc) {
width = (getChildWidth() - hints->base_width) / hints->width_inc;
height = (getChildHeight() - hints->base_height) / hints->height_inc;
} else {
width = _gm.width;
height = _gm.height;
}
}
//! @brief Calculates position based on current gravity
void
Frame::calcGravityPosition(int gravity, int x, int y, int &g_x, int &g_y)
{
switch (gravity) {
case NorthEastGravity: // outside border corner
g_x = x - _gm.x;
g_y = y;
break;
case SouthWestGravity: // outside border corner
g_x = x;
g_y = y - _gm.y;
break;
case SouthEastGravity: // outside border corner
g_x = x - _gm.x;
g_y = y - _gm.y;
break;
case NorthGravity: // outside border center
g_x = x - (_gm.x / 2);
g_y = y;
break;
case SouthGravity: // outside border center
g_x = x - (_gm.x / 2);
g_y = y - _gm.y;
break;
case WestGravity: // outside border center
g_x = x;
g_y = y - (_gm.y / 2);
break;
case EastGravity: // outside border center
g_x = x - _gm.x;
g_y = y - (_gm.y / 2);
break;
case CenterGravity: // center of window
g_x = x - (_gm.x / 2);
g_y = y - (_gm.y / 2);
break;
case StaticGravity: // client top left
g_x = x - (_gm.width - getChildWidth());
g_y = y - (_gm.height - getChildHeight());
break;
case NorthWestGravity: // outside border corner
default:
g_x = x;
g_y = y;
break;
}
}
/**
* Makes gm conform to _clients width and height inc.
*/
void
Frame::downSize(Geometry &gm, bool keep_x, bool keep_y)
{
XSizeHints *size_hint = _client->getXSizeHints(); // convenience
// conform to width_inc
if (size_hint->flags&PResizeInc) {
int o_r = getRX();
int b_x = (size_hint->flags&PBaseSize)
? size_hint->base_width
: (size_hint->flags&PMinSize) ? size_hint->min_width : 0;
gm.width -= (getChildWidth() - b_x) % size_hint->width_inc;
if (! keep_x) {
gm.x = o_r - gm.width;
}
}
// conform to height_inc
if (size_hint->flags&PResizeInc) {
int o_b = getBY();
int b_y = (size_hint->flags&PBaseSize)
? size_hint->base_height
: (size_hint->flags&PMinSize) ? size_hint->min_height : 0;
gm.height -= (getChildHeight() - b_y) % size_hint->height_inc;
if (! keep_y) {
gm.y = o_b - gm.height;
}
}
}
// Below this Client message handling is done
//! @brief Handle XConfgiureRequestEvents
//! @todo Should we send a ConfigureRequest back to the Client if ignoring?
void
Frame::handleConfigureRequest(XConfigureRequestEvent *ev, Client *client)
{
if (client != _client) {
return; // only handle the active client's events
}
// Handled geometry, this is handled seperatley due to fullscreen
// detection
handleConfigureRequestGeometry(ev, client);
// update the stacking
if (! client->isCfgDeny(CFG_DENY_STACKING)) {
if (ev->value_mask&CWStackMode) {
if (ev->value_mask&CWSibling) {
switch(ev->detail) {
case Above:
Workspaces::instance()->stackAbove(this, ev->above);
break;
case Below:
Workspaces::instance()->stackBelow(this, ev->above);
break;
case TopIf:
case BottomIf:
// FIXME: What does occlude mean?
break;
}
} else {
switch(ev->detail) { // FIXME: Is this broken?
case Above:
raise();
break;
case Below:
lower();
break;
case TopIf:
case BottomIf:
// FIXME: Why does the manual say that it should care about siblings
// even if we don't have any specified?
break;
}
}
}
}
}
/**
* Handle size and position part of configure request, detects
* fullscreen mode if detection is enabled.
*/
void
Frame::handleConfigureRequestGeometry(XConfigureRequestEvent *ev, Client *client)
{
if (isRequestGeometryFullscreen(ev, client)) {
client->configureRequestSend();
return;
}
bool change_geometry = false;
if (! client->isCfgDeny(CFG_DENY_SIZE)
&& (ev->value_mask & (CWWidth|CWHeight))) {
resizeChild(ev->width, ev->height);
_client->setShaped(setShape());
change_geometry = true;
}
if (! client->isCfgDeny(CFG_DENY_POSITION)
&& (ev->value_mask & (CWX|CWY)) ) {
calcGravityPosition(_client->getXSizeHints()->win_gravity,
ev->x, ev->y, _gm.x, _gm.y);
move(_gm.x, _gm.y);
change_geometry = true;
}
// Remove fullscreen state if client changes it size
if (change_geometry && Config::instance()->isFullscreenDetect()) {
setStateFullscreen(STATE_UNSET);
}
}
/**
* Check if requested size if "fullscreen"
*/
bool
Frame::isRequestGeometryFullscreen(XConfigureRequestEvent *ev, Client *client)
{
bool is_fullscreen = false;
if (! client->isCfgDeny(CFG_DENY_SIZE)
&& ! client->isCfgDeny(CFG_DENY_POSITION)) {
int nearest_head = _scr->getNearestHead(ev->x, ev->y);
Geometry gm_request(ev->x, ev->y, ev->width, ev->height);
Geometry gm_screen(_scr->getScreenGeometry());
Geometry gm_head(_scr->getHeadGeometry(nearest_head));
if (gm_request == gm_screen || gm_request == gm_head) {
is_fullscreen = true;
} else {
downSize(gm_screen, true, true);
downSize(gm_head, true, true);
if (gm_request == gm_screen || gm_request == gm_head) {
is_fullscreen = true;
}
}
if (is_fullscreen && Config::instance()->isFullscreenDetect()) {
setStateFullscreen(STATE_SET);
}
}
return is_fullscreen;
}
/**
* Handle client message.
*/
void
Frame::handleClientMessage(XClientMessageEvent *ev, Client *client)
{
if (ev->message_type == Atoms::getAtom(STATE)) {
handleClientStateMessage(ev, client);
} else if (ev->message_type == Atoms::getAtom(NET_ACTIVE_WINDOW)) {
if (! client->isCfgDeny(CFG_DENY_ACTIVE_WINDOW)) {
// Active child if it's not the active child
if (client != _client) {
activateChild(client);
}
// If we aren't mapped we check if we make sure we're on the right
// workspace and then map the window.
if (! _mapped) {
if (_workspace != Workspaces::instance()->getActive() &&
!isSticky()) {
Workspaces::instance()->setWorkspace(_workspace, false);
}
mapWindow();
}
// Seems as if raising the window is implied in activating it
raise();
giveInputFocus();
}
} else if (ev->message_type == Atoms::getAtom(NET_CLOSE_WINDOW)) {
client->close();
} else if (ev->message_type == Atoms::getAtom(NET_WM_DESKTOP)) {
if (client == _client) {
setWorkspace(ev->data.l[0]);
}
} else if (ev->message_type == Atoms::getAtom(WM_CHANGE_STATE) &&
(ev->format == 32) && (ev->data.l[0] == IconicState)) {
if (client == _client) {
iconify();
}
}
}
/**
* Handle _NET_WM_STATE atom.
*/
void
Frame::handleClientStateMessage(XClientMessageEvent *ev, Client *client)
{
StateAction sa = getStateActionFromMessage(ev);
handleStateAtom(sa, ev->data.l[1], client);
if (ev->data.l[2] != 0) {
handleStateAtom(sa, ev->data.l[2], client);
}
client->updateEwmhStates();
}
/**
* Get StateAction from NET_WM atom.
*/
StateAction
Frame::getStateActionFromMessage(XClientMessageEvent *ev)
{
StateAction sa = STATE_SET;
if (ev->data.l[0]== NET_WM_STATE_REMOVE) {
sa = STATE_UNSET;
} else if (ev->data.l[0]== NET_WM_STATE_ADD) {
sa = STATE_SET;
} else if (ev->data.l[0]== NET_WM_STATE_TOGGLE) {
sa = STATE_TOGGLE;
}
return sa;
}
/**
* Handle state atom for client.
*/
void
Frame::handleStateAtom(StateAction sa, Atom atom, Client *client)
{
if (client == _client) {
handleCurrentClientStateAtom(sa, atom, client);
}
switch (atom) {
case STATE_SKIP_TASKBAR:
client->setStateSkip(sa, SKIP_TASKBAR);
break;
case STATE_SKIP_PAGER:
client->setStateSkip(sa, SKIP_PAGER);
break;
case STATE_DEMANDS_ATTENTION:
client->setStateDemandsAttention(sa, true);
break;
}
}
/**
* Handle state atom for actions that apply only on active client.
*/
void
Frame::handleCurrentClientStateAtom(StateAction sa, Atom atom, Client *client)
{
if (atom == Atoms::getAtom(STATE_STICKY)) {
setStateSticky(sa);
}
if (atom == Atoms::getAtom(STATE_MAXIMIZED_HORZ)
&& ! client->isCfgDeny(CFG_DENY_STATE_MAXIMIZED_HORZ)) {
setStateMaximized(sa, true, false, false);
}
if (atom == Atoms::getAtom(STATE_MAXIMIZED_VERT)
&& ! client->isCfgDeny(CFG_DENY_STATE_MAXIMIZED_VERT)) {
setStateMaximized(sa, false, true, false);
}
if (atom == Atoms::getAtom(STATE_SHADED)) {
setShaded(sa);
}
if (atom == Atoms::getAtom(STATE_HIDDEN)
&& ! client->isCfgDeny(CFG_DENY_STATE_HIDDEN)) {
setStateIconified(sa);
}
if (atom == Atoms::getAtom(STATE_FULLSCREEN)
&& ! client->isCfgDeny(CFG_DENY_STATE_FULLSCREEN)) {
setStateFullscreen(sa);
}
if (atom == Atoms::getAtom(STATE_ABOVE)
&& ! client->isCfgDeny(CFG_DENY_STATE_ABOVE)) {
setStateAlwaysOnTop(sa);
}
if (atom == Atoms::getAtom(STATE_BELOW)
&& ! client->isCfgDeny(CFG_DENY_STATE_BELOW)) {
setStateAlwaysBelow(sa);
}
}
//! @brief
void
Frame::handlePropertyChange(XPropertyEvent *ev, Client *client)
{
if (ev->atom == Atoms::getAtom(NET_WM_DESKTOP)) {
if (client == _client) {
long workspace;
if (AtomUtil::getLong(client->getWindow(),
Atoms::getAtom(NET_WM_DESKTOP), workspace)) {
if (workspace != signed(_workspace))
setWorkspace(workspace);
}
}
} else if (ev->atom == Atoms::getAtom(NET_WM_STRUT)) {
client->getStrutHint();
} else if (ev->atom == Atoms::getAtom(NET_WM_NAME) || ev->atom == XA_WM_NAME) {
handleTitleChange(client);
} else if (ev->atom == XA_WM_NORMAL_HINTS) {
client->getWMNormalHints();
} else if (ev->atom == XA_WM_TRANSIENT_FOR) {
client->getTransientForHint();
}
}
/**
* Handle title change, find decoration rules based on changed title
* and update if changed.
*/
void
Frame::handleTitleChange(Client *client)
{
// Update title
client->readName();
bool require_render = true;
if (client == _client) {
string new_decor_name(getClientDecorName(client));
if (new_decor_name != _decor_name) {
require_render = ! setDecor(new_decor_name);
}
}
// Render title if decoration was not updated
if (require_render) {
renderTitle();
}
}