awesome/client.c
Pierre Habouzit b4b381e947 Finish markup parsing rewrite to avoid mallocs.
For that matter, use elements as a filter for elements we care about, and
let the hook implement whatever it needs without duplicating everything.

The resulting algorithm is still O(n²) where n is the number of filtered
elements (3 at most right now), which isn't bad if we don't need to get
too many elements, but at least it's not quadratic in the number of
attributes anymore.

Speedup improvements could be done using gperf btw.

Signed-off-by: Pierre Habouzit <madcoder@debian.org>
2008-06-22 17:45:45 +02:00

1457 lines
41 KiB
C

/*
* client.c - client management
*
* Copyright © 2007-2008 Julien Danjou <julien@danjou.info>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdio.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_atom.h>
#include "client.h"
#include "tag.h"
#include "window.h"
#include "focus.h"
#include "ewmh.h"
#include "widget.h"
#include "screen.h"
#include "titlebar.h"
#include "lua.h"
#include "stack.h"
#include "mouse.h"
#include "layouts/floating.h"
#include "common/markup.h"
#include "common/xutil.h"
#include "common/xscreen.h"
extern awesome_t globalconf;
/** Create a new client userdata.
* \param L The Lua VM state.
* \param p A client pointer.
* \param The number of elements pushed on the stack.
*/
int
luaA_client_userdata_new(lua_State *L, client_t *p)
{
client_t **pp = lua_newuserdata(L, sizeof(client_t *));
*pp = p;
return luaA_settype(L, "client");
}
DO_LUA_EQ(client_t, client, "client")
/** Load windows properties, restoring client's tag
* and floating state before awesome was restarted if any.
* \todo This may bug if number of tags is != than before.
* \param c A client pointer.
* \param screen A virtual screen.
* \return True if client had property, false otherwise.
*/
static bool
client_loadprops(client_t * c, screen_t *screen)
{
int i, ntags = 0;
tag_t *tag;
char *prop = NULL;
bool result = false;
for(tag = screen->tags; tag; tag = tag->next)
ntags++;
if(xutil_gettextprop(globalconf.connection, c->win, &globalconf.atoms,
xutil_intern_atom_reply(globalconf.connection, &globalconf.atoms,
xutil_intern_atom(globalconf.connection,
&globalconf.atoms,
"_AWESOME_PROPERTIES")),
&prop))
{
for(i = 0, tag = screen->tags; tag && i < ntags && prop[i]; i++, tag = tag->next)
if(prop[i] == '1')
{
tag_client(c, tag);
result = true;
}
else
untag_client(c, tag);
if(prop[i])
client_setfloating(c, prop[i] == '1',
(prop[i + 1] >= 0 && prop[i + 1] <= LAYER_FULLSCREEN) ? atoi(&prop[i + 1]) : prop[i] == '1' ? LAYER_FLOAT : LAYER_TILE);
}
p_delete(&prop);
return result;
}
/** Check if client supports protocol WM_DELETE_WINDOW.
* \param win The window.
* \return True if client has WM_DELETE_WINDOW, false otherwise.
*/
static bool
window_isprotodel(xcb_window_t win)
{
uint32_t i, n;
xcb_atom_t wm_delete_win_atom;
xcb_atom_t *protocols;
bool ret = false;
if(xcb_get_wm_protocols(globalconf.connection, win, &n, &protocols))
{
wm_delete_win_atom = xutil_intern_atom_reply(globalconf.connection, &globalconf.atoms,
xutil_intern_atom(globalconf.connection,
&globalconf.atoms,
"WM_DELETE_WINDOW"));
for(i = 0; !ret && i < n; i++)
if(protocols[i] == wm_delete_win_atom)
ret = true;
p_delete(&protocols);
}
return ret;
}
/** Returns true if a client is tagged
* with one of the tags of the specified screen.
* \param c The client to check.
* \param screen Virtual screen number.
* \return true if the client is visible, false otherwise.
*/
bool
client_isvisible(client_t *c, int screen)
{
tag_t *tag;
if(c && !c->ishidden && c->screen == screen)
for(tag = globalconf.screens[screen].tags; tag; tag = tag->next)
if(tag->selected && is_client_tagged(c, tag))
return true;
return false;
}
/** Get a client by its window.
* \param w The client window to find.
* \return A client pointer if found, NULL otherwise.
*/
client_t *
client_getbywin(xcb_window_t w)
{
client_t *c;
for(c = globalconf.clients; c && c->win != w; c = c->next);
return c;
}
/** Update client name attribute with its new title.
* \param c The client.
*/
void
client_updatetitle(client_t *c)
{
char *name;
xutil_intern_atom_request_t net_wm_name_q;
xcb_atom_t net_wm_name;
net_wm_name_q = xutil_intern_atom(globalconf.connection, &globalconf.atoms, "_NET_WM_NAME");
net_wm_name = xutil_intern_atom_reply(globalconf.connection, &globalconf.atoms, net_wm_name_q);
if(!xutil_gettextprop(globalconf.connection, c->win, &globalconf.atoms, net_wm_name, &name))
if(!xutil_gettextprop(globalconf.connection, c->win, &globalconf.atoms, WM_NAME, &name))
return;
p_delete(&c->name);
a_iso2utf8(name, &c->name);
/* call hook */
luaA_client_userdata_new(globalconf.L, c);
luaA_dofunction(globalconf.L, globalconf.hooks.titleupdate, 1);
widget_invalidate_cache(c->screen, WIDGET_CACHE_CLIENTS);
}
/** Unfocus a client.
* \param c The client.
*/
static void
client_unfocus(client_t *c)
{
/* Call hook */
luaA_client_userdata_new(globalconf.L, globalconf.focus->client);
luaA_dofunction(globalconf.L, globalconf.hooks.unfocus, 1);
focus_client_push(NULL);
widget_invalidate_cache(c->screen, WIDGET_CACHE_CLIENTS);
ewmh_update_net_active_window(c->phys_screen);
}
/** Ban client and unmap it.
* \param c The client.
*/
void
client_ban(client_t *c)
{
if(globalconf.focus->client == c)
client_unfocus(c);
xcb_unmap_window(globalconf.connection, c->win);
if(c->ishidden)
window_setstate(c->win, XCB_WM_ICONIC_STATE);
else
window_setstate(c->win, XCB_WM_WITHDRAWN_STATE);
if(c->titlebar && c->titlebar->position && c->titlebar->sw)
xcb_unmap_window(globalconf.connection, c->titlebar->sw->window);
}
/** Give focus to client, or to first client if client is NULL.
* \param c The client or NULL.
* \param screen Virtual screen number.
* \return True if a window (even root) has received focus, false otherwise.
*/
bool
client_focus(client_t *c, int screen)
{
int phys_screen;
/* if c is NULL or invisible, take next client in the focus history */
if((!c || (c && (!client_isvisible(c, screen))))
&& !(c = focus_get_current_client(screen)))
/* if c is still NULL take next client in the stack */
for(c = globalconf.clients; c && (c->skip || !client_isvisible(c, screen)); c = c->next);
/* unfocus current selected client */
if(globalconf.focus->client)
client_unfocus(globalconf.focus->client);
if(c)
{
/* unban the client before focusing or it will fail */
client_unban(c);
/* save sel in focus history */
focus_client_push(c);
xcb_set_input_focus(globalconf.connection, XCB_INPUT_FOCUS_POINTER_ROOT,
c->win, XCB_CURRENT_TIME);
phys_screen = c->phys_screen;
/* Some layouts use focused client differently, so call them back. */
globalconf.screens[c->screen].need_arrange = true;
/* execute hook */
luaA_client_userdata_new(globalconf.L, globalconf.focus->client);
luaA_dofunction(globalconf.L, globalconf.hooks.focus, 1);
}
else
{
phys_screen = screen_virttophys(screen);
xcb_set_input_focus(globalconf.connection,
XCB_INPUT_FOCUS_POINTER_ROOT,
xutil_screen_get(globalconf.connection, phys_screen)->root,
XCB_CURRENT_TIME);
}
ewmh_update_net_active_window(phys_screen);
widget_invalidate_cache(screen, WIDGET_CACHE_CLIENTS);
return true;
}
/** Restack clients and puts c in top of its layer.
* \param c The client to stack on top of others.
* \todo It might be worth stopping to restack everyone and only stack `c'
* relatively to the first matching in the list.
*/
void
client_raise(client_t *c)
{
uint32_t config_win_vals[2];
client_node_t *node;
layer_t layer;
statusbar_t *sb;
int screen;
config_win_vals[0] = XCB_NONE;
config_win_vals[1] = XCB_STACK_MODE_BELOW;
/* Push c on top of the stack. */
stack_client_push(c);
for(screen = 0; screen < globalconf.screens_info->nscreen; screen++)
for(sb = globalconf.screens[screen].statusbar; sb; sb = sb->next)
{
xcb_configure_window(globalconf.connection,
sb->sw->window,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE,
config_win_vals);
config_win_vals[0] = sb->sw->window;
}
for(layer = LAYER_OUTOFSPACE - 1; layer >= LAYER_DESKTOP; layer--)
for(node = globalconf.stack; node; node = node->next)
if(node->client->layer == layer)
{
if(node->client->titlebar
&& node->client->titlebar->sw
&& node->client->titlebar->position)
{
xcb_configure_window(globalconf.connection,
node->client->titlebar->sw->window,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE,
config_win_vals);
config_win_vals[0] = node->client->titlebar->sw->window;
}
xcb_configure_window(globalconf.connection, node->client->win,
XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE,
config_win_vals);
config_win_vals[0] = node->client->win;
}
}
/** Manage a new client.
* \param w The window.
* \param wgeom Window geometry.
* \param screen Virtual screen number where to manage client.
*/
void
client_manage(xcb_window_t w, xcb_get_geometry_reply_t *wgeom, int screen)
{
client_t *c, *t = NULL;
xcb_window_t trans;
bool rettrans, retloadprops;
tag_t *tag;
xcb_size_hints_t *u_size_hints;
const uint32_t select_input_val[] = {
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_ENTER_WINDOW };
c = p_new(client_t, 1);
c->screen = screen_get_bycoord(globalconf.screens_info, screen, wgeom->x, wgeom->y);
if(globalconf.screens_info->xinerama_is_active)
c->phys_screen = globalconf.default_screen;
else
c->phys_screen = c->screen;
/* Initial values */
c->win = w;
c->geometry.x = c->f_geometry.x = c->m_geometry.x = wgeom->x;
c->geometry.y = c->f_geometry.y = c->m_geometry.y = wgeom->y;
c->geometry.width = c->f_geometry.width = c->m_geometry.width = wgeom->width;
c->geometry.height = c->f_geometry.height = c->m_geometry.height = wgeom->height;
c->oldborder = wgeom->border_width;
c->layer = c->oldlayer = LAYER_TILE;
/* update hints */
u_size_hints = client_updatesizehints(c);
client_updatewmhints(c);
/* Try to load props if any */
if(!(retloadprops = client_loadprops(c, &globalconf.screens[screen])))
screen_client_moveto(c, screen, true);
/* Then check clients hints */
ewmh_check_client_hints(c);
/* check for transient and set tags like its parent */
if((rettrans = xutil_get_transient_for_hint(globalconf.connection, w, &trans))
&& (t = client_getbywin(trans)))
for(tag = globalconf.screens[c->screen].tags; tag; tag = tag->next)
if(is_client_tagged(t, tag))
tag_client(c, tag);
/* should be floating if transsient or fixed */
if(rettrans || c->isfixed)
client_setfloating(c, true, c->layer != LAYER_TILE ? c->layer : LAYER_FLOAT);
if(globalconf.floating_placement
&& !retloadprops
&& u_size_hints
&& !(xcb_size_hints_get_flags(u_size_hints) & (XCB_SIZE_US_POSITION_HINT |
XCB_SIZE_P_POSITION_HINT)))
{
if(c->isfloating)
client_resize(c, globalconf.floating_placement(c), false);
else
c->f_geometry = globalconf.floating_placement(c);
}
if(u_size_hints)
xcb_free_size_hints(u_size_hints);
xcb_change_window_attributes(globalconf.connection, w, XCB_CW_EVENT_MASK,
select_input_val);
/* Push client in client list */
client_list_push(&globalconf.clients, c);
/* Append client in history: it'll be last. */
focus_client_append(c);
/* Push client in stack */
client_raise(c);
/* update window title */
client_updatetitle(c);
ewmh_update_net_client_list(c->phys_screen);
widget_invalidate_cache(c->screen, WIDGET_CACHE_CLIENTS);
/* call hook */
luaA_client_userdata_new(globalconf.L, c);
luaA_dofunction(globalconf.L, globalconf.hooks.manage, 1);
}
/** Compute client geometry with respect to its geometry hints.
* \param c The client.
* \param geometry The geometry that the client might receive.
* \return The geometry the client must take respecting its hints.
*/
area_t
client_geometry_hints(client_t *c, area_t geometry)
{
double dx, dy, max, min, ratio;
if(c->minay > 0 && c->maxay > 0 && (geometry.height - c->baseh) > 0
&& (geometry.width - c->basew) > 0)
{
dx = (double) (geometry.width - c->basew);
dy = (double) (geometry.height - c->baseh);
min = (double) (c->minax) / (double) (c->minay);
max = (double) (c->maxax) / (double) (c->maxay);
ratio = dx / dy;
if(max > 0 && min > 0 && ratio > 0)
{
if(ratio < min)
{
dy = (dx * min + dy) / (min * min + 1);
dx = dy * min;
geometry.width = (int) dx + c->basew;
geometry.height = (int) dy + c->baseh;
}
else if(ratio > max)
{
dy = (dx * min + dy) / (max * max + 1);
dx = dy * min;
geometry.width = (int) dx + c->basew;
geometry.height = (int) dy + c->baseh;
}
}
}
if(c->minw && geometry.width < c->minw)
geometry.width = c->minw;
if(c->minh && geometry.height < c->minh)
geometry.height = c->minh;
if(c->maxw && geometry.width > c->maxw)
geometry.width = c->maxw;
if(c->maxh && geometry.height > c->maxh)
geometry.height = c->maxh;
if(c->incw)
geometry.width -= (geometry.width - c->basew) % c->incw;
if(c->inch)
geometry.height -= (geometry.height - c->baseh) % c->inch;
return geometry;
}
/** Resize client window.
* \param c Client to resize.
* \param geometry New window geometry.
* \param hints Use size hints.
* \return True if the client has been resized.
*/
bool
client_resize(client_t *c, area_t geometry, bool hints)
{
int new_screen;
area_t area;
layout_t *layout = layout_get_current(c->screen);
bool resized = false;
/* Values to configure a window is an array where values are
* stored according to 'value_mask' */
uint32_t values[5];
if(c->titlebar && !c->ismoving && !c->isfloating && layout != layout_floating)
geometry = titlebar_geometry_remove(c->titlebar, c->border, geometry);
if(hints)
geometry = client_geometry_hints(c, geometry);
if(geometry.width <= 0 || geometry.height <= 0)
return false;
/* offscreen appearance fixes */
area = display_area_get(c->phys_screen, NULL,
&globalconf.screens[c->screen].padding);
if(geometry.x > area.width)
geometry.x = area.width - geometry.width - 2 * c->border;
if(geometry.y > area.height)
geometry.y = area.height - geometry.height - 2 * c->border;
if(geometry.x + geometry.width + 2 * c->border < 0)
geometry.x = 0;
if(geometry.y + geometry.height + 2 * c->border < 0)
geometry.y = 0;
if(c->geometry.x != geometry.x || c->geometry.y != geometry.y
|| c->geometry.width != geometry.width || c->geometry.height != geometry.height)
{
new_screen =
screen_get_bycoord(globalconf.screens_info, c->screen, geometry.x, geometry.y);
c->geometry.x = values[0] = geometry.x;
c->geometry.width = values[2] = geometry.width;
c->geometry.y = values[1] = geometry.y;
c->geometry.height = values[3] = geometry.height;
values[4] = c->border;
/* save the floating geometry if the window is floating but not
* maximized */
if(c->ismoving || c->isfloating
|| layout_get_current(new_screen) == layout_floating)
{
titlebar_update_geometry_floating(c);
if(!c->ismax)
c->f_geometry = geometry;
}
xcb_configure_window(globalconf.connection, c->win,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
XCB_CONFIG_WINDOW_BORDER_WIDTH,
values);
window_configure(c->win, geometry, c->border);
if(c->screen != new_screen)
screen_client_moveto(c, new_screen, false);
resized = true;
}
/* call it again like it was floating,
* we want it to be sticked to the window */
if(!c->ismoving && !c->isfloating && layout != layout_floating)
titlebar_update_geometry_floating(c);
return resized;
}
/** Set a clinet floating.
* \param c The client.
* \param floating Set floating, or not.
* \param layer Layer to put the floating window onto.
*/
void
client_setfloating(client_t *c, bool floating, layer_t layer)
{
if(c->isfloating != floating)
{
if((c->isfloating = floating))
client_resize(c, c->f_geometry, false);
else if(c->ismax)
{
c->ismax = false;
client_resize(c, c->m_geometry, false);
}
if(client_isvisible(c, c->screen))
globalconf.screens[c->screen].need_arrange = true;
widget_invalidate_cache(c->screen, WIDGET_CACHE_CLIENTS);
if(floating)
{
c->oldlayer = c->layer;
c->layer = layer;
}
else
c->layer = c->oldlayer;
client_raise(c);
client_saveprops(c);
}
}
/** Save client properties as an X property.
* \param c The client.
*/
void
client_saveprops(client_t *c)
{
int i = 0, ntags = 0;
char *prop;
tag_t *tag;
xutil_intern_atom_request_t atom_q;
atom_q = xutil_intern_atom(globalconf.connection, &globalconf.atoms, "_AWESOME_PROPERTIES");
for(tag = globalconf.screens[c->screen].tags; tag; tag = tag->next)
ntags++;
prop = p_new(char, ntags + 3);
for(tag = globalconf.screens[c->screen].tags; tag; tag = tag->next, i++)
prop[i] = is_client_tagged(c, tag) ? '1' : '0';
prop[i] = c->isfloating ? '1' : '0';
sprintf(&prop[++i], "%d", c->layer);
prop[++i] = '\0';
xcb_change_property(globalconf.connection, XCB_PROP_MODE_REPLACE, c->win,
xutil_intern_atom_reply(globalconf.connection, &globalconf.atoms, atom_q),
STRING, 8, i, (unsigned char *) prop);
p_delete(&prop);
}
/** Unban a client.
* \param c The client.
*/
void
client_unban(client_t *c)
{
xcb_map_window(globalconf.connection, c->win);
window_setstate(c->win, XCB_WM_NORMAL_STATE);
if(c->titlebar && c->titlebar->sw && c->titlebar->position)
xcb_map_window(globalconf.connection, c->titlebar->sw->window);
}
/** Unmanage a client.
* \param c The client.
*/
void
client_unmanage(client_t *c)
{
tag_t *tag;
/* call hook */
luaA_client_userdata_new(globalconf.L, c);
luaA_dofunction(globalconf.L, globalconf.hooks.unmanage, 1);
/* The server grab construct avoids race conditions. */
xcb_grab_server(globalconf.connection);
xcb_configure_window(globalconf.connection, c->win,
XCB_CONFIG_WINDOW_BORDER_WIDTH,
(uint32_t *) &c->oldborder);
/* remove client everywhere */
client_list_detach(&globalconf.clients, c);
focus_client_delete(c);
stack_client_delete(c);
for(tag = globalconf.screens[c->screen].tags; tag; tag = tag->next)
untag_client(c, tag);
if(globalconf.focus->client == c)
client_focus(NULL, c->screen);
xcb_ungrab_button(globalconf.connection, XCB_BUTTON_INDEX_ANY, c->win, ANY_MODIFIER);
window_setstate(c->win, XCB_WM_WITHDRAWN_STATE);
xcb_aux_sync(globalconf.connection);
xcb_ungrab_server(globalconf.connection);
if(c->titlebar)
{
simplewindow_delete(&c->titlebar->sw);
titlebar_unref(&c->titlebar);
}
ewmh_update_net_client_list(c->phys_screen);
p_delete(&c);
}
/** Update the WM hints of a client.
* \param c The client.
*/
void
client_updatewmhints(client_t *c)
{
xcb_wm_hints_t *wmh = NULL;
uint32_t wm_hints_flags;
if((wmh = xcb_get_wm_hints(globalconf.connection, c->win)))
{
wm_hints_flags = xcb_wm_hints_get_flags(wmh);
if((c->isurgent = (wm_hints_flags & XCB_WM_X_URGENCY_HINT)))
{
/* execute hook */
luaA_client_userdata_new(globalconf.L, c);
luaA_dofunction(globalconf.L, globalconf.hooks.urgent, 1);
widget_invalidate_cache(c->screen, WIDGET_CACHE_CLIENTS);
}
if((wm_hints_flags & XCB_WM_STATE_HINT) &&
(xcb_wm_hints_get_initial_state(wmh) == XCB_WM_WITHDRAWN_STATE))
{
client_setborder(c, 0);
c->skip = true;
}
xcb_free_wm_hints(wmh);
}
}
/** Update the size hintz of a client.
* \param c The client.
* \return A pointer to a xcb_size_hints_t.
*/
xcb_size_hints_t *
client_updatesizehints(client_t *c)
{
long msize;
xcb_size_hints_t *size;
uint32_t size_flags;
if(!(size = xcb_get_wm_normal_hints(globalconf.connection, c->win, &msize)))
return NULL;
size_flags = xcb_size_hints_get_flags(size);
if((size_flags & XCB_SIZE_P_SIZE_HINT))
xcb_size_hints_get_base_size(size, &c->basew, &c->baseh);
else if((size_flags & XCB_SIZE_P_MIN_SIZE_HINT))
xcb_size_hints_get_min_size(size, &c->basew, &c->baseh);
else
c->basew = c->baseh = 0;
if((size_flags & XCB_SIZE_P_RESIZE_INC_HINT))
xcb_size_hints_get_increase(size, &c->incw, &c->inch);
else
c->incw = c->inch = 0;
if((size_flags & XCB_SIZE_P_MAX_SIZE_HINT))
xcb_size_hints_get_max_size(size, &c->maxw, &c->maxh);
else
c->maxw = c->maxh = 0;
if((size_flags & XCB_SIZE_P_MIN_SIZE_HINT))
xcb_size_hints_get_min_size(size, &c->minw, &c->minh);
else if((size_flags & XCB_SIZE_BASE_SIZE_HINT))
xcb_size_hints_get_base_size(size, &c->minw, &c->minh);
else
c->minw = c->minh = 0;
if((size_flags & XCB_SIZE_P_ASPECT_HINT))
{
xcb_size_hints_get_min_aspect(size, &c->minax, &c->minay);
xcb_size_hints_get_max_aspect(size, &c->maxax, &c->maxay);
}
else
c->minax = c->maxax = c->minay = c->maxay = 0;
if(c->maxw && c->minw && c->maxh && c->minh
&& c->maxw == c->minw && c->maxh == c->minh)
c->isfixed = true;
c->hassizehints = !(!c->basew && !c->baseh && !c->incw && !c->inch
&& !c->maxw && !c->maxh && !c->minw && !c->minh
&& !c->minax && !c->maxax && !c->minax && !c->minay);
return size;
}
/** Markup parsing hook.
* For now only substitute <title />
*/
static void
client_markup_on_elem(markup_parser_data_t *p, const char *elem,
const char **names, const char **values)
{
assert (!strcmp(elem, "title"));
buffer_add_xmlescaped(&p->text, p->priv);
}
/** Parse a markup string which contains special markup sequence relative to a
* client, i.e. its title, etc.
* \param c The client concerned by the markup string.
* \param str The markup string.
* \param len The string length.
*/
char *
client_markup_parse(client_t *c, const char *str, ssize_t len)
{
static char const * const elements[] = { "title", NULL };
markup_parser_data_t p = {
.elements = elements,
.priv = c->name,
.on_element = client_markup_on_elem,
};
char *ret;
markup_parser_data_init(&p);
if(markup_parse(&p, str, len))
ret = buffer_detach(&p.text);
else
ret = a_strdup(str);
markup_parser_data_wipe(&p);
return ret;
}
/** Kill a client via a WM_DELETE_WINDOW request or XKillClient if not
* supported.
* \param c The client to kill.
*/
void
client_kill(client_t *c)
{
xcb_client_message_event_t ev;
xutil_intern_atom_request_t wm_protocols_q, wm_delete_window_q;
if(window_isprotodel(c->win))
{
wm_protocols_q = xutil_intern_atom(globalconf.connection, &globalconf.atoms, "WM_PROTOCOLS");
wm_delete_window_q = xutil_intern_atom(globalconf.connection, &globalconf.atoms, "WM_DELETE_WINDOW");
/* Initialize all of event's fields first */
p_clear(&ev, 1);
ev.response_type = XCB_CLIENT_MESSAGE;
ev.window = c->win;
ev.format = 32;
ev.data.data32[1] = XCB_CURRENT_TIME;
ev.type = xutil_intern_atom_reply(globalconf.connection, &globalconf.atoms, wm_protocols_q);
ev.data.data32[0] = xutil_intern_atom_reply(globalconf.connection, &globalconf.atoms, wm_delete_window_q);
xcb_send_event(globalconf.connection, false, c->win,
XCB_EVENT_MASK_NO_EVENT, (char *) &ev);
}
else
xcb_kill_client(globalconf.connection, c->win);
}
/** Get all clients into a table.
* \param L The Lua VM state.
* \luastack
* \lreturn A table with all clients.
*/
static int
luaA_client_get(lua_State *L)
{
int i = 1;
client_t *c;
lua_newtable(L);
for(c = globalconf.clients; c; c = c->next)
{
luaA_client_userdata_new(globalconf.L, c);
lua_rawseti(L, -2, i++);
}
return 1;
}
/** Add mouse bindings over clients's window.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A button binding.
*/
static int
luaA_client_mouse_add(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
button_t **b = luaA_checkudata(L, 2, "mouse");
button_list_push(&(*c)->buttons, *b);
button_ref(b);
return 0;
}
/** Remove mouse bindings over clients's window.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A button binding.
*/
static int
luaA_client_mouse_remove(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
button_t **b = luaA_checkudata(L, 1, "mouse");
button_list_detach(&(*c)->buttons, *b);
button_unref(b);
return 0;
}
/** Get only visible clients for a screen.
* \param L The Lua VM state.
* \luastack
* \lparam A screen number.
* \lreturn A table with all visible clients for this screen.
*/
static int
luaA_client_visible_get(lua_State *L)
{
int i = 1;
client_t *c;
int screen = luaL_checknumber(L, 1) - 1;
luaA_checkscreen(screen);
lua_newtable(L);
for(c = globalconf.clients; c; c = c->next)
if(!c->skip && !c->ishidden && client_isvisible(c, screen))
{
luaA_client_userdata_new(globalconf.L, c);
lua_rawseti(L, -2, i++);
}
return 1;
}
/** Get the currently focused client.
* \param L The Lua VM state.
* \luastack
* \lreturn The currently focused client.
*/
static int
luaA_client_focus_get(lua_State *L __attribute__ ((unused)))
{
if(globalconf.focus->client)
return luaA_client_userdata_new(globalconf.L, globalconf.focus->client);
return 0;
}
/** Set client border width.
* \param c The client.
* \param width The border width.
*/
void
client_setborder(client_t *c, int width)
{
uint32_t w = width;
if((c->noborder && width > 0) || width == c->border || width < 0)
return;
c->border = width;
xcb_configure_window(globalconf.connection, c->win,
XCB_CONFIG_WINDOW_BORDER_WIDTH, &w);
globalconf.screens[c->screen].need_arrange = true;
}
/** Set the client border width and color.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A table with `width' key for the border width in pixel and `color' key
* for the border color.
*/
static int
luaA_client_border_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
int width = luaA_getopt_number(L, 2, "width", (*c)->border);
const char *colorstr = luaA_getopt_string(L, 2, "color", NULL);
xcolor_t color;
client_setborder(*c, width);
if(colorstr
&& xcolor_new(globalconf.connection, (*c)->phys_screen, colorstr, &color))
xcb_change_window_attributes(globalconf.connection, (*c)->win, XCB_CW_BORDER_PIXEL,
&color.pixel);
return 0;
}
/** Move the client to another screen.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A screen number.
*/
static int
luaA_client_screen_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
int screen = luaL_checknumber(L, 2) - 1;
luaA_checkscreen(screen);
screen_client_moveto(*c, screen, true);
return 0;
}
/** Get the screen number the client is onto.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lreturn A screen number.
*/
static int
luaA_client_screen_get(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
lua_pushnumber(L, 1 + (*c)->screen);
return 1;
}
/** Tag a client with a specified tag.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A tag object.
* \lparam A boolean value: true to add this tag to clients, false to remove.
*/
static int
luaA_client_tag(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
tag_t **tag = luaA_checkudata(L, 2, "tag");
bool tag_the_client = luaA_checkboolean(L, 3);
if((*tag)->screen != (*c)->screen)
luaL_error(L, "tag and client are on different screens");
if(tag_the_client)
tag_client(*c, *tag);
else
untag_client(*c, *tag);
return 0;
}
/** Check if a client is tagged with the specified tag.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A tag object.
* \lreturn A boolean value, true if the client is tagged with this tag, false
* otherwise.
*/
static int
luaA_client_istagged(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
tag_t **tag = luaA_checkudata(L, 2, "tag");
lua_pushboolean(L, is_client_tagged(*c, *tag));
return 1;
}
/** Get the client coordinates on the display.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lreturn A table with keys `width', `height', `x' and `y'.
*/
static int
luaA_client_coords_get(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
lua_newtable(L);
lua_pushnumber(L, (*c)->geometry.width);
lua_setfield(L, -2, "width");
lua_pushnumber(L, (*c)->geometry.height);
lua_setfield(L, -2, "height");
lua_pushnumber(L, (*c)->geometry.x);
lua_setfield(L, -2, "x");
lua_pushnumber(L, (*c)->geometry.y);
lua_setfield(L, -2, "y");
return 1;
}
/** Set client coordinates. This only operates if the client is floating.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A table with keys: x, y, width, height.
*/
static int
luaA_client_coords_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
area_t geometry;
if((*c)->isfloating || layout_get_current((*c)->screen) == layout_floating)
{
luaA_checktable(L, 2);
geometry.x = luaA_getopt_number(L, 2, "x", (*c)->geometry.x);
geometry.y = luaA_getopt_number(L, 2, "y", (*c)->geometry.y);
geometry.width = luaA_getopt_number(L, 2, "width", (*c)->geometry.width);
geometry.height = luaA_getopt_number(L, 2, "height", (*c)->geometry.height);
client_resize(*c, geometry, false);
}
return 0;
}
/** Set the client opacity.
* Note: this requires an external composite manager.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A floating value between 0 and 1.
*/
static int
luaA_client_opacity_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
double opacity = luaL_checknumber(L, 2);
if(opacity == -1 || (opacity >= 0 && opacity <= 1))
window_settrans((*c)->win, opacity);
return 0;
}
/** Kill a client.
* \param L The Lua VM state.
*
* \luastack
* \lvalue A client.
*/
static int
luaA_client_kill(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
client_kill(*c);
return 0;
}
/** Swap a client with another one.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A client to swap with.
*/
static int
luaA_client_swap(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
client_t **swap = luaA_checkudata(L, 2, "client");
client_list_swap(&globalconf.clients, *swap, *c);
globalconf.screens[(*c)->screen].need_arrange = true;
globalconf.screens[(*swap)->screen].need_arrange = true;
widget_invalidate_cache((*c)->screen, WIDGET_CACHE_CLIENTS);
widget_invalidate_cache((*swap)->screen, WIDGET_CACHE_CLIENTS);
return 0;
}
/** Focus a client.
* \param L The Lua VM state.
*
* \luastack
* \lvalue A client.
*/
static int
luaA_client_focus_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
client_focus(*c, (*c)->screen);
return 0;
}
/** Raise a client on top of others which are on the same layer.
* \param L The Lua VM state.
*
* \luastack
* \lvalue A client.
*/
static int
luaA_client_raise(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
client_raise(*c);
return 0;
}
/** Set the client floating attribute.
* \param L The Lua VM state.
* \luastack
* \lparam A client.
* \lparam A boolean, true to set, false to unset.
*/
static int
luaA_client_floating_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
bool f = luaA_checkboolean(L, 2);
client_setfloating(*c, f, (*c)->layer == LAYER_FLOAT ? LAYER_TILE : LAYER_FLOAT);
return 0;
}
/** Check if a client has the floating attribute.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lreturn A boolean, true if the client has the floating attribute set, false
* otherwise.
*/
static int
luaA_client_floating_get(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
lua_pushboolean(L, (*c)->isfloating);
return 1;
}
/** Redraw a client by unmapping and mapping it quickly.
* \param L The Lua VM state.
*
* \luastack
* \lvalue A client.
*/
static int
luaA_client_redraw(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
xcb_unmap_window(globalconf.connection, (*c)->win);
xcb_map_window(globalconf.connection, (*c)->win);
return 0;
}
/** Return a formated string for a client.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lreturn A string.
*/
static int
luaA_client_tostring(lua_State *L)
{
client_t **p = luaA_checkudata(L, 1, "client");
lua_pushfstring(L, "[client udata(%p) name(%s)]", *p, (*p)->name);
return 1;
}
/** Get the client name.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lreturn A string with the client class.
*/
static int
luaA_client_class_get(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
class_hint_t *hint=xutil_get_class_hint(globalconf.connection, (*c)->win);
if (hint)
lua_pushstring(L, hint->res_class);
else
luaL_error(L, "Unable to get the class property for client");
return 1;
}
/** Set the default icon for this client.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A path to an icon image, or nil to remove.
*/
static int
luaA_client_icon_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
const char *icon = luaL_optstring(L, 2, NULL);
p_delete(&(*c)->icon_path);
(*c)->icon_path = a_strdup(icon);
return 0;
}
/** Get the client name.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lreturn A string with the client name.
*/
static int
luaA_client_name_get(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
lua_pushstring(L, (*c)->name);
return 1;
}
/** Change the client name. It'll change it only from awesome point of view.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A string with the new client name.
*/
static int
luaA_client_name_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
const char *name = luaL_checkstring(L, 2);
p_delete(&(*c)->name);
a_iso2utf8(name, &(*c)->name);
return 0;
}
/** Set the client's titlebar.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lparam A titlebar.
*/
static int
luaA_client_titlebar_set(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
titlebar_t **t = NULL;
if(lua_gettop(L) == 2 && !lua_isnil(L, 2))
{
t = luaA_checkudata(L, 2, "titlebar");
if(client_getbytitlebar(*t))
luaL_error(L, "titlebar is already used by another client");
}
/* If client had a titlebar, unref it */
if((*c)->titlebar)
{
simplewindow_delete(&(*c)->titlebar->sw);
titlebar_unref(&(*c)->titlebar);
}
if(t)
{
/* Attach titlebar to client */
(*c)->titlebar = *t;
titlebar_ref(t);
titlebar_init(*c);
}
else
(*c)->titlebar = NULL;
if((*c)->isfloating || layout_get_current((*c)->screen) == layout_floating)
titlebar_update_geometry_floating(*c);
else
globalconf.screens[(*c)->screen].need_arrange = true;
return 0;
}
/** Get the titlebar of a client.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
* \lreturn A titlebar or nil if the client has no titlebar.
*/
static int
luaA_client_titlebar_get(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
if((*c)->titlebar)
return luaA_titlebar_userdata_new(globalconf.L, (*c)->titlebar);
return 0;
}
/** Stop managing a client.
* \param L The Lua VM state.
* \luastack
* \lvalue A client.
*/
static int
luaA_client_unmanage(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
client_unmanage(*c);
return 0;
}
/** Hide a client.
* \param L The Lua VM state.
*
* \luastack
* \lvalue A client.
*/
static int
luaA_client_hide(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
(*c)->ishidden = true;
globalconf.screens[(*c)->screen].need_arrange = true;
return 0;
}
/** Unhide a client.
* \param L The Lua VM state.
*
* \luastack
* \lvalue A client.
*/
static int
luaA_client_unhide(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
(*c)->ishidden = false;
globalconf.screens[(*c)->screen].need_arrange = true;
return 0;
}
/** Guess if a client has been hidden.
* \param L The Lua VM state.
*
* \luastack
* \lvalue A client.
* \lreturn A boolean, true if the client has been hidden with hide(), false
* otherwise.
*/
static int
luaA_client_ishidden(lua_State *L)
{
client_t **c = luaA_checkudata(L, 1, "client");
lua_pushboolean(L, (*c)->ishidden);
return 1;
}
const struct luaL_reg awesome_client_methods[] =
{
{ "get", luaA_client_get },
{ "focus_get", luaA_client_focus_get },
{ "visible_get", luaA_client_visible_get },
{ NULL, NULL }
};
const struct luaL_reg awesome_client_meta[] =
{
{ "titlebar_set", luaA_client_titlebar_set },
{ "titlebar_get", luaA_client_titlebar_get },
{ "name_get", luaA_client_name_get },
{ "name_set", luaA_client_name_set },
{ "screen_set", luaA_client_screen_set },
{ "screen_get", luaA_client_screen_get },
{ "border_set", luaA_client_border_set },
{ "tag", luaA_client_tag },
{ "istagged", luaA_client_istagged },
{ "coords_get", luaA_client_coords_get },
{ "coords_set", luaA_client_coords_set },
{ "opacity_set", luaA_client_opacity_set },
{ "kill", luaA_client_kill },
{ "swap", luaA_client_swap },
{ "focus_set", luaA_client_focus_set },
{ "raise", luaA_client_raise },
{ "redraw", luaA_client_redraw },
{ "floating_set", luaA_client_floating_set },
{ "floating_get", luaA_client_floating_get },
{ "icon_set", luaA_client_icon_set },
{ "class_get", luaA_client_class_get },
{ "mouse_resize", luaA_client_mouse_resize },
{ "mouse_move", luaA_client_mouse_move },
{ "unmanage", luaA_client_unmanage },
{ "hide", luaA_client_hide },
{ "unhide", luaA_client_unhide },
{ "ishidden", luaA_client_ishidden },
{ "mouse_add", luaA_client_mouse_add },
{ "mouse_remove", luaA_client_mouse_remove },
{ "__eq", luaA_client_eq },
{ "__tostring", luaA_client_tostring },
{ NULL, NULL }
};
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80