Add titlebars on the C side

This commit makes it possible to add titlebars to a client. These titlebars are
drawables.

The drawin's input handling is moved to the drawable. This allows it to use the
same code for drawin and titlebar input handling, although there are lots of
differences between the two on the C side.

On the lua side, a new wibox.drawable module is created which handles all the
drawable-specific magic and which can also be used for titlebars.

Signed-off-by: Uli Schlachter <psychon@znc.in>
This commit is contained in:
Uli Schlachter 2012-10-14 17:20:24 +02:00
parent f0512eeaab
commit 41ef107b88
7 changed files with 570 additions and 250 deletions

55
event.c
View File

@ -220,10 +220,12 @@ event_handle_button(xcb_button_press_event_t *ev)
ev->event_y -= drawin->geometry.y; ev->event_y -= drawin->geometry.y;
} }
/* Push the drawin */ /* Push the drawable */
luaA_object_push(globalconf.L, drawin); luaA_object_push(globalconf.L, drawin);
luaA_object_push_item(globalconf.L, -1, drawin->drawable);
/* and handle the button raw button event */ /* and handle the button raw button event */
event_emit_button(ev); event_emit_button(ev);
lua_pop(globalconf.L, 1);
/* check if any button object matches */ /* check if any button object matches */
event_button_callback(ev, &drawin->buttons, -1, 1, NULL); event_button_callback(ev, &drawin->buttons, -1, 1, NULL);
} }
@ -232,6 +234,19 @@ event_handle_button(xcb_button_press_event_t *ev)
luaA_object_push(globalconf.L, c); luaA_object_push(globalconf.L, c);
/* And handle the button raw button event */ /* And handle the button raw button event */
event_emit_button(ev); event_emit_button(ev);
/* then check if a titlebar was "hit" */
int x = ev->event_x, y = ev->event_y;
drawable_t *d = client_get_drawable_offset(c, &x, &y);
if (d)
{
/* Copy the event so that we can fake x/y */
xcb_button_press_event_t event = *ev;
event.event_x = x;
event.event_y = y;
luaA_object_push_item(globalconf.L, -1, d);
event_emit_button(&event);
lua_pop(globalconf.L, 1);
}
/* then check if any button objects match */ /* then check if any button objects match */
event_button_callback(ev, &c->buttons, -1, 1, NULL); event_button_callback(ev, &c->buttons, -1, 1, NULL);
xcb_allow_events(globalconf.connection, xcb_allow_events(globalconf.connection,
@ -382,16 +397,29 @@ event_handle_motionnotify(xcb_motion_notify_event_t *ev)
lua_pushnumber(globalconf.L, ev->event_x); lua_pushnumber(globalconf.L, ev->event_x);
lua_pushnumber(globalconf.L, ev->event_y); lua_pushnumber(globalconf.L, ev->event_y);
luaA_object_emit_signal(globalconf.L, -3, "mouse::move", 2); luaA_object_emit_signal(globalconf.L, -3, "mouse::move", 2);
/* now check if a titlebar was "hit" */
int x = ev->event_x, y = ev->event_y;
drawable_t *d = client_get_drawable_offset(c, &x, &y);
if (d)
{
luaA_object_push_item(globalconf.L, -1, d);
lua_pushnumber(globalconf.L, x);
lua_pushnumber(globalconf.L, y);
luaA_object_emit_signal(globalconf.L, -3, "mouse::move", 2);
lua_pop(globalconf.L, 1);
}
lua_pop(globalconf.L, 1); lua_pop(globalconf.L, 1);
} }
if((w = drawin_getbywin(ev->event))) if((w = drawin_getbywin(ev->event)))
{ {
luaA_object_push(globalconf.L, w); luaA_object_push(globalconf.L, w);
luaA_object_push_item(globalconf.L, -1, w->drawable);
lua_pushnumber(globalconf.L, ev->event_x); lua_pushnumber(globalconf.L, ev->event_x);
lua_pushnumber(globalconf.L, ev->event_y); lua_pushnumber(globalconf.L, ev->event_y);
luaA_object_emit_signal(globalconf.L, -3, "mouse::move", 2); luaA_object_emit_signal(globalconf.L, -3, "mouse::move", 2);
lua_pop(globalconf.L, 1); lua_pop(globalconf.L, 2);
} }
} }
@ -413,14 +441,22 @@ event_handle_leavenotify(xcb_leave_notify_event_t *ev)
{ {
luaA_object_push(globalconf.L, c); luaA_object_push(globalconf.L, c);
luaA_object_emit_signal(globalconf.L, -1, "mouse::leave", 0); luaA_object_emit_signal(globalconf.L, -1, "mouse::leave", 0);
drawable_t *d = client_get_drawable(c, ev->event_x, ev->event_y);
if (d)
{
luaA_object_push_item(globalconf.L, -1, d);
luaA_object_emit_signal(globalconf.L, -1, "mouse::leave", 0);
lua_pop(globalconf.L, 1);
}
lua_pop(globalconf.L, 1); lua_pop(globalconf.L, 1);
} }
if((drawin = drawin_getbywin(ev->event))) if((drawin = drawin_getbywin(ev->event)))
{ {
luaA_object_push(globalconf.L, drawin); luaA_object_push(globalconf.L, drawin);
luaA_object_push_item(globalconf.L, -1, drawin->drawable);
luaA_object_emit_signal(globalconf.L, -1, "mouse::leave", 0); luaA_object_emit_signal(globalconf.L, -1, "mouse::leave", 0);
lua_pop(globalconf.L, 1); lua_pop(globalconf.L, 2);
} }
} }
@ -441,14 +477,22 @@ event_handle_enternotify(xcb_enter_notify_event_t *ev)
if((drawin = drawin_getbywin(ev->event))) if((drawin = drawin_getbywin(ev->event)))
{ {
luaA_object_push(globalconf.L, drawin); luaA_object_push(globalconf.L, drawin);
luaA_object_push_item(globalconf.L, -1, drawin->drawable);
luaA_object_emit_signal(globalconf.L, -1, "mouse::enter", 0); luaA_object_emit_signal(globalconf.L, -1, "mouse::enter", 0);
lua_pop(globalconf.L, 1); lua_pop(globalconf.L, 2);
} }
if((c = client_getbyframewin(ev->event))) if((c = client_getbyframewin(ev->event)))
{ {
luaA_object_push(globalconf.L, c); luaA_object_push(globalconf.L, c);
luaA_object_emit_signal(globalconf.L, -1, "mouse::enter", 0); luaA_object_emit_signal(globalconf.L, -1, "mouse::enter", 0);
drawable_t *d = client_get_drawable(c, ev->event_x, ev->event_y);
if (d)
{
luaA_object_push_item(globalconf.L, -1, d);
luaA_object_emit_signal(globalconf.L, -1, "mouse::enter", 0);
lua_pop(globalconf.L, 1);
}
lua_pop(globalconf.L, 1); lua_pop(globalconf.L, 1);
} }
} }
@ -496,11 +540,14 @@ static void
event_handle_expose(xcb_expose_event_t *ev) event_handle_expose(xcb_expose_event_t *ev)
{ {
drawin_t *drawin; drawin_t *drawin;
client_t *client;
if((drawin = drawin_getbywin(ev->window))) if((drawin = drawin_getbywin(ev->window)))
drawin_refresh_pixmap_partial(drawin, drawin_refresh_pixmap_partial(drawin,
ev->x, ev->y, ev->x, ev->y,
ev->width, ev->height); ev->width, ev->height);
if ((client = client_getbyframewin(ev->window)))
client_refresh(client);
} }
/** The key press event handler. /** The key press event handler.

246
lib/wibox/drawable.lua.in Normal file
View File

@ -0,0 +1,246 @@
---------------------------------------------------------------------------
-- @author Uli Schlachter
-- @copyright 2012 Uli Schlachter
-- @release @AWESOME_VERSION@
---------------------------------------------------------------------------
local drawable = {}
local capi = {
awesome = awesome,
root = root
}
local beautiful = require("beautiful")
local cairo = require("lgi").cairo
local color = require("gears.color")
local object = require("gears.object")
local sort = require("gears.sort")
local surface = require("gears.surface")
local function do_redraw(self)
local cr = cairo.Context(surface(self.drawable.surface))
local geom = self.drawable:geometry();
local x, y, width, height = geom.x, geom.y, geom.width, geom.height
-- Draw the background
cr:save()
-- This is pseudo-transparency: We draw the wallpaper in the background
local wallpaper = surface(capi.root.wallpaper())
if wallpaper then
cr.operator = cairo.Operator.SOURCE
cr:set_source_surface(wallpaper, -x, -y)
cr:paint()
end
cr.operator = cairo.Operator.OVER
cr:set_source(self.background_color)
cr:paint()
cr:restore()
-- Draw the widget
self._widget_geometries = {}
if self.widget then
cr:set_source(self.foreground_color)
self.widget:draw(self.widget_arg, cr, width, height)
self:widget_at(self.widget, 0, 0, width, height)
end
self.drawable:refresh()
end
--- Register a widget's position.
-- This is internal, don't call it yourself! Only wibox.layout.base.draw_widget
-- is allowed to call this.
function drawable:widget_at(widget, x, y, width, height)
local t = {
widget = widget,
x = x, y = y,
width = width, height = height
}
table.insert(self._widget_geometries, t)
end
--- Find a widget by a point.
-- The drawable must have drawn itself at least once for this to work.
-- @param x X coordinate of the point
-- @param y Y coordinate of the point
-- @return A sorted table with all widgets that contain the given point. The
-- widgets are sorted by relevance.
function drawable:find_widgets(x, y)
local matches = {}
-- Find all widgets that contain the point
for k, v in pairs(self._widget_geometries) do
local match = true
if v.x > x or v.x + v.width <= x then match = false end
if v.y > y or v.y + v.height <= y then match = false end
if match then
table.insert(matches, v)
end
end
-- Sort the matches by area, the assumption here is that widgets don't
-- overlap and so smaller widgets are "more specific".
local function cmp(a, b)
local area_a = a.width * a.height
local area_b = b.width * b.height
return area_a < area_b
end
sort(matches, cmp)
return matches
end
--- Set the widget that the drawable displays
function drawable:set_widget(widget)
if self.widget then
-- Disconnect from the old widget so that we aren't updated due to it
self.widget:disconnect_signal("widget::updated", self.draw)
end
self.widget = widget
if widget then
widget:connect_signal("widget::updated", self.draw)
end
-- Make sure the widget gets drawn
self.draw()
end
--- Set the background of the drawable
-- @param drawable The drawable to use
-- @param c The background to use. This must either be a cairo pattern object,
-- nil or a string that gears.color() understands.
function drawable:set_bg(c)
local c = c or "#000000"
if type(c) == "string" or type(c) == "table" then
c = color(c)
end
self.background_color = c
self.draw()
end
--- Set the foreground of the drawable
-- @param drawable The drawable to use
-- @param c The foreground to use. This must either be a cairo pattern object,
-- nil or a string that gears.color() understands.
function drawable:set_fg(c)
local c = c or "#FFFFFF"
if type(c) == "string" or type(c) == "table" then
c = color(c)
end
self.foreground_color = c
self.draw()
end
local function emit_difference(name, list, skip)
local function in_table(table, val)
for k, v in pairs(table) do
if v == val then
return true
end
end
return false
end
for k, v in pairs(list) do
if not in_table(skip, v) then
v:emit_signal(name)
end
end
end
local function handle_leave(_drawable)
emit_difference("mouse::leave", _drawable._widgets_under_mouse, {})
_drawable._widgets_under_mouse = {}
end
local function handle_motion(_drawable, x, y)
if x < 0 or y < 0 or x > _drawable.drawable:geometry().width or y > _drawable.drawable:geometry().height then
return handle_leave(_drawable)
end
-- Build a plain list of all widgets on that point
local widgets_list = _drawable:find_widgets(x, y)
local widgets = {}
for k, v in pairs(widgets_list) do
widgets[#widgets + 1] = v.widget
end
-- First, "leave" all widgets that were left
emit_difference("mouse::leave", _drawable._widgets_under_mouse, widgets)
-- Then enter some widgets
emit_difference("mouse::enter", widgets, _drawable._widgets_under_mouse)
_drawable._widgets_under_mouse = widgets
end
function drawable.new(d, widget_arg)
local ret = object()
ret.drawable = d
ret.widget_arg = widget_arg or ret
for k, v in pairs(drawable) do
if type(v) == "function" then
ret[k] = v
end
end
-- Only redraw a drawable once, even when we get told to do so multiple times.
ret._redraw_pending = false
ret._do_redraw = function()
ret._redraw_pending = false
capi.awesome.disconnect_signal("refresh", ret._do_redraw)
do_redraw(ret)
end
-- Connect our signal when we need a redraw
ret.draw = function()
if not ret._redraw_pending then
capi.awesome.connect_signal("refresh", ret._do_redraw)
ret._redraw_pending = true
end
end
capi.awesome.connect_signal("wallpaper_changed", ret.draw)
d:connect_signal("property::x", ret.draw)
d:connect_signal("property::y", ret.draw)
d:connect_signal("property::width", ret.draw)
d:connect_signal("property::height", ret.draw)
-- Set the default background
ret:set_bg(beautiful.bg_normal)
ret:set_fg(beautiful.fg_normal)
-- Initialize internals
ret._widget_geometries = {}
ret._widgets_under_mouse = {}
local function button_signal(name)
d:connect_signal(name, function(d, x, y, ...)
local widgets = ret:find_widgets(x, y)
for k, v in pairs(widgets) do
-- Calculate x/y inside of the widget
local lx = x - v.x
local ly = y - v.y
v.widget:emit_signal(name, lx, ly, ...)
end
end)
end
button_signal("button::press")
button_signal("button::release")
d:connect_signal("mouse::move", function(_, x, y) handle_motion(ret, x, y) end)
d:connect_signal("mouse::leave", function() handle_leave(ret) end)
-- Make sure the drawable is drawn at least once
ret.draw()
return ret
end
--- Handling of drawables. A drawable is something that can be drawn to.
-- @class table
-- @name drawable
return setmetatable(drawable, { __call = function(_, ...) return drawable.new(...) end })
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -27,100 +27,11 @@ local cairo = require("lgi").cairo
local wibox = { mt = {} } local wibox = { mt = {} }
wibox.layout = require("wibox.layout") wibox.layout = require("wibox.layout")
wibox.widget = require("wibox.widget") wibox.widget = require("wibox.widget")
wibox.drawable = require("wibox.drawable")
local function do_redraw(_wibox)
if not _wibox.drawin.visible then
return
end
local geom = _wibox.drawin:geometry()
local cr = cairo.Context(surface(_wibox.drawin.drawable.surface))
-- Draw the background
cr:save()
-- This is pseudo-transparency: We draw the wallpaper in the background
local wallpaper = surface(capi.root.wallpaper())
if wallpaper then
cr.operator = cairo.Operator.SOURCE
cr:set_source_surface(wallpaper, -_wibox.drawin.x, -_wibox.drawin.y)
cr:paint()
end
cr.operator = cairo.Operator.OVER
cr:set_source(_wibox.background_color)
cr:paint()
cr:restore()
-- Draw the widget
_wibox._widget_geometries = {}
if _wibox.widget and not _wibox.widget.__fake_widget then
cr:set_source(_wibox.foreground_color)
_wibox.widget:draw(_wibox, cr, geom.width, geom.height)
_wibox:widget_at(_wibox.widget, 0, 0, geom.width, geom.height)
end
_wibox.drawin.drawable:refresh()
end
--- Register a widget's position.
-- This is internal, don't call it yourself! Only wibox.layout.base.draw_widget
-- is allowed to call this.
function wibox.widget_at(_wibox, widget, x, y, width, height)
local t = {
widget = widget,
x = x, y = y,
width = width, height = height
}
table.insert(_wibox._widget_geometries, t)
end
--- Find a widget by a point.
-- The wibox must have drawn itself at least once for this to work.
-- @param wibox The wibox to look at
-- @param x X coordinate of the point
-- @param y Y coordinate of the point
-- @return A sorted table with all widgets that contain the given point. The
-- widgets are sorted by relevance.
function wibox.find_widgets(_wibox, x, y)
local matches = {}
-- Find all widgets that contain the point
for k, v in pairs(_wibox._widget_geometries) do
local match = true
if v.x > x or v.x + v.width <= x then match = false end
if v.y > y or v.y + v.height <= y then match = false end
if match then
table.insert(matches, v)
end
end
-- Sort the matches by area, the assumption here is that widgets don't
-- overlap and so smaller widgets are "more specific".
local function cmp(a, b)
local area_a = a.width * a.height
local area_b = b.width * b.height
return area_a < area_b
end
sort(matches, cmp)
return matches
end
--- Set the widget that the wibox displays --- Set the widget that the wibox displays
function wibox.set_widget(_wibox, widget) function wibox.set_widget(_wibox, widget)
if _wibox.widget and not _wibox.widget.__fake_widget then _wibox._drawable:set_widget(widget)
-- Disconnect from the old widget so that we aren't updated due to it
_wibox.widget:disconnect_signal("widget::updated", _wibox.draw)
end
if not widget then
_wibox.widget = { __fake_widget = true }
else
_wibox.widget = widget
widget:connect_signal("widget::updated", _wibox.draw)
end
-- Make sure the wibox is updated
_wibox.draw()
end end
--- Set the background of the wibox --- Set the background of the wibox
@ -128,12 +39,7 @@ end
-- @param c The background to use. This must either be a cairo pattern object, -- @param c The background to use. This must either be a cairo pattern object,
-- nil or a string that gears.color() understands. -- nil or a string that gears.color() understands.
function wibox.set_bg(_wibox, c) function wibox.set_bg(_wibox, c)
local c = c or "#000000" _wibox._drawable:set_bg(c)
if type(c) == "string" or type(c) == "table" then
c = color(c)
end
_wibox.background_color = c
_wibox.draw()
end end
--- Set the foreground of the wibox --- Set the foreground of the wibox
@ -141,12 +47,7 @@ end
-- @param c The foreground to use. This must either be a cairo pattern object, -- @param c The foreground to use. This must either be a cairo pattern object,
-- nil or a string that gears.color() understands. -- nil or a string that gears.color() understands.
function wibox.set_fg(_wibox, c) function wibox.set_fg(_wibox, c)
local c = c or "#FFFFFF" _wibox._drawable:set_fg(c)
if type(c) == "string" or type(c) == "table" then
c = color(c)
end
_wibox.foreground_color = c
_wibox.draw()
end end
--- Helper function to make wibox:buttons() work as expected --- Helper function to make wibox:buttons() work as expected
@ -164,48 +65,6 @@ function wibox.geometry(box, ...)
return box.drawin:geometry(...) return box.drawin:geometry(...)
end end
local function emit_difference(name, list, skip)
local function in_table(table, val)
for k, v in pairs(table) do
if v == val then
return true
end
end
return false
end
for k, v in pairs(list) do
if not in_table(skip, v) then
v:emit_signal(name)
end
end
end
local function handle_leave(_wibox)
emit_difference("mouse::leave", _wibox._widgets_under_mouse, {})
_wibox._widgets_under_mouse = {}
end
local function handle_motion(_wibox, x, y)
if x < 0 or y < 0 or x > _wibox.drawin.width or y > _wibox.drawin.height then
return handle_leave(_wibox)
end
-- Build a plain list of all widgets on that point
local widgets_list = _wibox:find_widgets(x, y)
local widgets = {}
for k, v in pairs(widgets_list) do
widgets[#widgets + 1] = v.widget
end
-- First, "leave" all widgets that were left
emit_difference("mouse::leave", _wibox._widgets_under_mouse, widgets)
-- Then enter some widgets
emit_difference("mouse::enter", widgets, _wibox._widgets_under_mouse)
_wibox._widgets_under_mouse = widgets
end
local function setup_signals(_wibox) local function setup_signals(_wibox)
local w = _wibox.drawin local w = _wibox.drawin
@ -216,9 +75,6 @@ local function setup_signals(_wibox)
_wibox:emit_signal(name, ...) _wibox:emit_signal(name, ...)
end) end)
end end
clone_signal("mouse::enter")
clone_signal("mouse::leave")
clone_signal("mouse::move")
clone_signal("property::border_color") clone_signal("property::border_color")
clone_signal("property::border_width") clone_signal("property::border_width")
clone_signal("property::buttons") clone_signal("property::buttons")
@ -232,42 +88,13 @@ local function setup_signals(_wibox)
clone_signal("property::width") clone_signal("property::width")
clone_signal("property::x") clone_signal("property::x")
clone_signal("property::y") clone_signal("property::y")
-- Update the wibox when its geometry changes
w:connect_signal("property::height", _wibox.draw)
w:connect_signal("property::width", _wibox.draw)
w:connect_signal("property::visible", function()
if w.visible then
capi.awesome.connect_signal("wallpaper_changed", _wibox.draw)
_wibox.draw()
else
capi.awesome.disconnect_signal("wallpaper_changed", _wibox.draw)
end
end)
local function button_signal(name)
w:connect_signal(name, function(w, x, y, ...)
local widgets = _wibox:find_widgets(x, y)
for k, v in pairs(widgets) do
-- Calculate x/y inside of the widget
local lx = x - v.x
local ly = y - v.y
v.widget:emit_signal(name, lx, ly, ...)
end
end)
end
button_signal("button::press")
button_signal("button::release")
_wibox:connect_signal("mouse::move", handle_motion)
_wibox:connect_signal("mouse::leave", handle_leave)
end end
local function new(args) local function new(args)
local ret = object() local ret = object()
local w = capi.drawin(args) local w = capi.drawin(args)
ret.drawin = w ret.drawin = w
ret._drawable = wibox.drawable(w.drawable, ret)
for k, v in pairs(wibox) do for k, v in pairs(wibox) do
if type(v) == "function" then if type(v) == "function" then
@ -275,24 +102,11 @@ local function new(args)
end end
end end
-- This is to make sure that the wibox will only be redrawn once even when
-- we receive multiple widget::updated signals.
ret._redraw_pending = false
ret._do_redraw = function()
ret._redraw_pending = false
capi.awesome.disconnect_signal("refresh", ret._do_redraw)
do_redraw(ret)
end
-- Connect our signal when we need a redraw
ret.draw = function()
if not ret._redraw_pending then
capi.awesome.connect_signal("refresh", ret._do_redraw)
ret._redraw_pending = true
end
end
setup_signals(ret) setup_signals(ret)
ret.draw = ret._drawable.draw
ret.widget_at = function(_, widget, x, y, width, height)
return ret._drawable:widget_at(widget, x, y, width, height)
end
-- Set the default background -- Set the default background
ret:set_bg(args.bg or beautiful.bg_normal) ret:set_bg(args.bg or beautiful.bg_normal)
@ -301,11 +115,6 @@ local function new(args)
-- Make sure the wibox is drawn at least once -- Make sure the wibox is drawn at least once
ret.draw() ret.draw()
-- Due to the metatable below, we need this trick
ret.widget = { __fake_widget = true }
ret._widget_geometries = {}
ret._widgets_under_mouse = {}
-- Redirect all non-existing indexes to the "real" drawin -- Redirect all non-existing indexes to the "real" drawin
setmetatable(ret, { setmetatable(ret, {
__index = w, __index = w,

View File

@ -21,6 +21,7 @@
#include <xcb/xcb_atom.h> #include <xcb/xcb_atom.h>
#include <xcb/xcb_image.h> #include <xcb/xcb_image.h>
#include <cairo-xcb.h>
#include "objects/tag.h" #include "objects/tag.h"
#include "ewmh.h" #include "ewmh.h"
@ -33,6 +34,9 @@
#include "common/atoms.h" #include "common/atoms.h"
#include "common/xutil.h" #include "common/xutil.h"
static area_t titlebar_get_area(client_t *c, client_titlebar_t bar);
static drawable_t *titlebar_get_drawable(lua_State *L, client_t *c, int cl_idx, client_titlebar_t bar);
/** Collect a client. /** Collect a client.
* \param L The Lua VM state. * \param L The Lua VM state.
* \return The number of element pushed on stack. * \return The number of element pushed on stack.
@ -580,6 +584,96 @@ HANDLE_GEOM(height)
lua_pop(globalconf.L, 1); lua_pop(globalconf.L, 1);
} }
static void
client_resize_do(client_t *c, area_t geometry)
{
bool send_notice = false;
screen_t *new_screen = screen_getbycoord(geometry.x, geometry.y);
if(c->geometry.width == geometry.width
&& c->geometry.height == geometry.height)
send_notice = true;
/* Also store geometry including border */
area_t old_geometry = c->geometry;
c->geometry = geometry;
/* Ignore all spurious enter/leave notify events */
client_ignore_enterleave_events();
/* Configure the client for its new size */
area_t real_geometry = geometry;
real_geometry.x = c->titlebar[CLIENT_TITLEBAR_LEFT].size;
real_geometry.y = c->titlebar[CLIENT_TITLEBAR_TOP].size;
real_geometry.width -= c->titlebar[CLIENT_TITLEBAR_LEFT].size;
real_geometry.width -= c->titlebar[CLIENT_TITLEBAR_RIGHT].size;
real_geometry.height -= c->titlebar[CLIENT_TITLEBAR_TOP].size;
real_geometry.height -= c->titlebar[CLIENT_TITLEBAR_BOTTOM].size;
xcb_configure_window(globalconf.connection, c->window,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
(uint32_t[]) { real_geometry.x, real_geometry.y, real_geometry.width, real_geometry.height });
xcb_configure_window(globalconf.connection, c->frame_window,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
(uint32_t[]) { geometry.x, geometry.y, geometry.width, geometry.height });
if(send_notice)
/* We are moving without changing the size, see ICCCM 4.2.3 */
xwindow_configure(c->window, geometry, c->border_width);
client_restore_enterleave_events();
screen_client_moveto(c, new_screen, false);
luaA_object_push(globalconf.L, c);
luaA_object_emit_signal(globalconf.L, -1, "property::geometry", 0);
if (old_geometry.x != geometry.x)
luaA_object_emit_signal(globalconf.L, -1, "property::x", 0);
if (old_geometry.y != geometry.y)
luaA_object_emit_signal(globalconf.L, -1, "property::y", 0);
if (old_geometry.width != geometry.width)
luaA_object_emit_signal(globalconf.L, -1, "property::width", 0);
if (old_geometry.height != geometry.height)
luaA_object_emit_signal(globalconf.L, -1, "property::height", 0);
lua_pop(globalconf.L, 1);
/* Update all titlebars */
for (client_titlebar_t bar = CLIENT_TITLEBAR_TOP; bar < CLIENT_TITLEBAR_COUNT; bar++) {
if (c->titlebar[bar].drawable == NULL && c->titlebar[bar].size == 0)
continue;
luaA_object_push(globalconf.L, c);
drawable_t *drawable = titlebar_get_drawable(globalconf.L, c, -1, bar);
/* Get rid of the old state */
drawable_set_surface(drawable, NULL);
if (c->titlebar[bar].pixmap != XCB_NONE)
xcb_free_pixmap(globalconf.connection, c->titlebar[bar].pixmap);
/* And get us some new state */
area_t area = titlebar_get_area(c, bar);
if (c->titlebar[bar].size != 0)
{
c->titlebar[bar].pixmap = xcb_generate_id(globalconf.connection);
xcb_create_pixmap(globalconf.connection, globalconf.default_depth, c->titlebar[bar].pixmap,
globalconf.screen->root, area.width, area.height);
cairo_surface_t *surface = cairo_xcb_surface_create(globalconf.connection,
c->titlebar[bar].pixmap, globalconf.visual,
area.width, area.height);
drawable_set_surface(drawable, surface);
}
/* Convert to global coordinates */
area.x += geometry.x;
area.y += geometry.y;
luaA_object_push_item(globalconf.L, -1, drawable);
drawable_set_geometry(drawable, -1, area);
/* Pop the client and the drawable */
lua_pop(globalconf.L, 2);
}
}
/** Resize client window. /** Resize client window.
* The sizes given as parameters are with borders! * The sizes given as parameters are with borders!
* \param c Client to resize. * \param c Client to resize.
@ -604,6 +698,11 @@ client_resize(client_t *c, area_t geometry)
if(geometry.y + geometry.height < 0) if(geometry.y + geometry.height < 0)
geometry.y = 0; geometry.y = 0;
if(geometry.width < c->titlebar[CLIENT_TITLEBAR_LEFT].size + c->titlebar[CLIENT_TITLEBAR_RIGHT].size)
return false;
if(geometry.height < c->titlebar[CLIENT_TITLEBAR_TOP].size + c->titlebar[CLIENT_TITLEBAR_BOTTOM].size)
return false;
if(geometry.width == 0 || geometry.height == 0) if(geometry.width == 0 || geometry.height == 0)
return false; return false;
@ -612,46 +711,7 @@ client_resize(client_t *c, area_t geometry)
|| c->geometry.width != geometry.width || c->geometry.width != geometry.width
|| c->geometry.height != geometry.height) || c->geometry.height != geometry.height)
{ {
bool send_notice = false; client_resize_do(c, geometry);
screen_t *new_screen = screen_getbycoord(geometry.x, geometry.y);
if(c->geometry.width == geometry.width
&& c->geometry.height == geometry.height)
send_notice = true;
/* Also store geometry including border */
area_t old_geometry = c->geometry;
c->geometry = geometry;
/* Ignore all spurious enter/leave notify events */
client_ignore_enterleave_events();
xcb_configure_window(globalconf.connection, c->window,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
(uint32_t[]) { geometry.width, geometry.height });
xcb_configure_window(globalconf.connection, c->frame_window,
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
(uint32_t[]) { geometry.x, geometry.y, geometry.width, geometry.height });
if(send_notice)
/* We are moving without changing the size, see ICCCM 4.2.3 */
xwindow_configure(c->window, geometry, c->border_width);
client_restore_enterleave_events();
screen_client_moveto(c, new_screen, false);
luaA_object_push(globalconf.L, c);
luaA_object_emit_signal(globalconf.L, -1, "property::geometry", 0);
if (old_geometry.x != geometry.x)
luaA_object_emit_signal(globalconf.L, -1, "property::x", 0);
if (old_geometry.y != geometry.y)
luaA_object_emit_signal(globalconf.L, -1, "property::y", 0);
if (old_geometry.width != geometry.width)
luaA_object_emit_signal(globalconf.L, -1, "property::width", 0);
if (old_geometry.height != geometry.height)
luaA_object_emit_signal(globalconf.L, -1, "property::height", 0);
lua_pop(globalconf.L, 1);
return true; return true;
} }
@ -1215,6 +1275,136 @@ luaA_client_unmanage(lua_State *L)
return 0; return 0;
} }
static area_t
titlebar_get_area(client_t *c, client_titlebar_t bar)
{
area_t result = c->geometry;
result.x = result.y = 0;
// Let's try some ascii art:
// ---------------------------
// | Top |
// |-------------------------|
// |L| |R|
// |e| |i|
// |f| |g|
// |t| |h|
// | | |t|
// |-------------------------|
// | Bottom |
// ---------------------------
switch (bar) {
case CLIENT_TITLEBAR_BOTTOM:
result.y = c->geometry.height - c->titlebar[bar].size;
/* Fall through */
case CLIENT_TITLEBAR_TOP:
result.height = c->titlebar[bar].size;
break;
case CLIENT_TITLEBAR_RIGHT:
result.x = c->geometry.width - c->titlebar[bar].size;
/* Fall through */
case CLIENT_TITLEBAR_LEFT:
result.y = c->titlebar[CLIENT_TITLEBAR_TOP].size;
result.width = c->titlebar[bar].size;
result.height -= c->titlebar[CLIENT_TITLEBAR_TOP].size;
result.height -= c->titlebar[CLIENT_TITLEBAR_BOTTOM].size;
break;
default:
fatal("Unknown titlebar kind %d\n", (int) bar);
}
return result;
}
drawable_t *
client_get_drawable_offset(client_t *c, int *x, int *y)
{
for (client_titlebar_t bar = CLIENT_TITLEBAR_TOP; bar < CLIENT_TITLEBAR_COUNT; bar++) {
area_t area = titlebar_get_area(c, bar);
if (AREA_LEFT(area) > *x || AREA_RIGHT(area) <= *x)
continue;
if (AREA_TOP(area) > *y || AREA_BOTTOM(area) <= *y)
continue;
*x -= area.x;
*y -= area.y;
return c->titlebar[bar].drawable;
}
return NULL;
}
drawable_t *
client_get_drawable(client_t *c, int x, int y)
{
return client_get_drawable_offset(c, &x, &y);
}
void
client_refresh(client_t *c)
{
for (client_titlebar_t bar = CLIENT_TITLEBAR_TOP; bar < CLIENT_TITLEBAR_COUNT; bar++) {
if (c->titlebar[bar].drawable == NULL || c->titlebar[bar].drawable->surface == NULL)
continue;
area_t area = titlebar_get_area(c, bar);
cairo_surface_flush(c->titlebar[bar].drawable->surface);
xcb_copy_area(globalconf.connection, c->titlebar[bar].pixmap, c->frame_window,
globalconf.gc, 0, 0, area.x, area.y, area.width, area.height);
}
}
static drawable_t *
titlebar_get_drawable(lua_State *L, client_t *c, int cl_idx, client_titlebar_t bar)
{
if (c->titlebar[bar].drawable == NULL)
{
cl_idx = luaA_absindex(L, cl_idx);
drawable_allocator(L, NULL, (drawable_refresh_callback *) client_refresh, c);
c->titlebar[bar].drawable = luaA_object_ref_item(L, cl_idx, -1);
}
return c->titlebar[bar].drawable;
}
static void
titlebar_resize(client_t *c, client_titlebar_t bar, int size)
{
if (size < 0)
return;
if (size == c->titlebar[bar].size)
return;
/* Now resize the client (and titlebars!) suitably */
c->titlebar[bar].size = size;
client_resize_do(c, c->geometry);
}
#define HANDLE_TITLEBAR(name, index) \
static int \
luaA_client_titlebar_ ## name(lua_State *L) \
{ \
client_t *c = luaA_checkudata(L, 1, &client_class); \
\
if (lua_gettop(L) == 2) \
{ \
if (lua_isnil(L, 2)) \
titlebar_resize(c, index, 0); \
else \
titlebar_resize(c, index, luaL_checknumber(L, 2)); \
} \
\
luaA_object_push_item(L, 1, titlebar_get_drawable(L, c, 1, index)); \
lua_pushnumber(L, c->titlebar[index].size); \
return 2; \
}
HANDLE_TITLEBAR(top, CLIENT_TITLEBAR_TOP)
HANDLE_TITLEBAR(right, CLIENT_TITLEBAR_RIGHT)
HANDLE_TITLEBAR(bottom, CLIENT_TITLEBAR_BOTTOM)
HANDLE_TITLEBAR(left, CLIENT_TITLEBAR_LEFT)
/** Return client geometry. /** Return client geometry.
* \param L The Lua VM state. * \param L The Lua VM state.
* \return The number of elements pushed on stack. * \return The number of elements pushed on stack.
@ -1684,6 +1874,10 @@ client_class_setup(lua_State *L)
{ "raise", luaA_client_raise }, { "raise", luaA_client_raise },
{ "lower", luaA_client_lower }, { "lower", luaA_client_lower },
{ "unmanage", luaA_client_unmanage }, { "unmanage", luaA_client_unmanage },
{ "titlebar_top", luaA_client_titlebar_top },
{ "titlebar_right", luaA_client_titlebar_right },
{ "titlebar_bottom", luaA_client_titlebar_bottom },
{ "titlebar_left", luaA_client_titlebar_left },
{ NULL, NULL } { NULL, NULL }
}; };
@ -1813,8 +2007,11 @@ client_class_setup(lua_State *L)
signal_add(&client_class.signals, "focus"); signal_add(&client_class.signals, "focus");
signal_add(&client_class.signals, "list"); signal_add(&client_class.signals, "list");
signal_add(&client_class.signals, "manage"); signal_add(&client_class.signals, "manage");
signal_add(&client_class.signals, "button::press");
signal_add(&client_class.signals, "button::release");
signal_add(&client_class.signals, "mouse::enter"); signal_add(&client_class.signals, "mouse::enter");
signal_add(&client_class.signals, "mouse::leave"); signal_add(&client_class.signals, "mouse::leave");
signal_add(&client_class.signals, "mouse::move");
signal_add(&client_class.signals, "property::above"); signal_add(&client_class.signals, "property::above");
signal_add(&client_class.signals, "property::below"); signal_add(&client_class.signals, "property::below");
signal_add(&client_class.signals, "property::class"); signal_add(&client_class.signals, "property::class");

View File

@ -27,6 +27,7 @@
#include "draw.h" #include "draw.h"
#include "banning.h" #include "banning.h"
#include "objects/window.h" #include "objects/window.h"
#include "objects/drawable.h"
#include "common/luaobject.h" #include "common/luaobject.h"
#define CLIENT_SELECT_INPUT_EVENT_MASK (XCB_EVENT_MASK_STRUCTURE_NOTIFY \ #define CLIENT_SELECT_INPUT_EVENT_MASK (XCB_EVENT_MASK_STRUCTURE_NOTIFY \
@ -36,7 +37,20 @@
#define FRAME_SELECT_INPUT_EVENT_MASK (XCB_EVENT_MASK_STRUCTURE_NOTIFY \ #define FRAME_SELECT_INPUT_EVENT_MASK (XCB_EVENT_MASK_STRUCTURE_NOTIFY \
| XCB_EVENT_MASK_ENTER_WINDOW \ | XCB_EVENT_MASK_ENTER_WINDOW \
| XCB_EVENT_MASK_LEAVE_WINDOW \ | XCB_EVENT_MASK_LEAVE_WINDOW \
| XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT) | XCB_EVENT_MASK_EXPOSURE \
| XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT \
| XCB_EVENT_MASK_POINTER_MOTION \
| XCB_EVENT_MASK_BUTTON_PRESS \
| XCB_EVENT_MASK_BUTTON_RELEASE)
typedef enum {
CLIENT_TITLEBAR_TOP = 0,
CLIENT_TITLEBAR_RIGHT = 1,
CLIENT_TITLEBAR_BOTTOM = 2,
CLIENT_TITLEBAR_LEFT = 3,
/* This is not a valid value, but the number of valid values */
CLIENT_TITLEBAR_COUNT = 4
} client_titlebar_t;
/** client_t type */ /** client_t type */
struct client_t struct client_t
@ -101,6 +115,15 @@ struct client_t
uint32_t pid; uint32_t pid;
/** Window it is transient for */ /** Window it is transient for */
client_t *transient_for; client_t *transient_for;
/** Titelbar information */
struct {
/** The size of this bar. */
uint16_t size;
/** The pixmap for double buffering. */
xcb_pixmap_t pixmap;
/** The drawable for this bar. */
drawable_t *drawable;
} titlebar[CLIENT_TITLEBAR_COUNT];
}; };
ARRAY_FUNCS(client_t *, client, DO_NOTHING) ARRAY_FUNCS(client_t *, client, DO_NOTHING)
@ -150,7 +173,10 @@ void client_focus_refresh(void);
bool client_hasproto(client_t *, xcb_atom_t); bool client_hasproto(client_t *, xcb_atom_t);
void client_ignore_enterleave_events(void); void client_ignore_enterleave_events(void);
void client_restore_enterleave_events(void); void client_restore_enterleave_events(void);
void client_refresh(client_t *);
void client_class_setup(lua_State *); void client_class_setup(lua_State *);
drawable_t *client_get_drawable(client_t *, int, int);
drawable_t *client_get_drawable_offset(client_t *, int *, int *);
/** Put client on top of the stack. /** Put client on top of the stack.
* \param c The client to raise. * \param c The client to raise.

View File

@ -592,8 +592,6 @@ drawin_class_setup(lua_State *L)
(lua_class_propfunc_t) luaA_window_get_type, (lua_class_propfunc_t) luaA_window_get_type,
(lua_class_propfunc_t) luaA_window_set_type); (lua_class_propfunc_t) luaA_window_set_type);
signal_add(&drawin_class.signals, "mouse::enter");
signal_add(&drawin_class.signals, "mouse::leave");
signal_add(&drawin_class.signals, "property::border_width"); signal_add(&drawin_class.signals, "property::border_width");
signal_add(&drawin_class.signals, "property::cursor"); signal_add(&drawin_class.signals, "property::cursor");
signal_add(&drawin_class.signals, "property::height"); signal_add(&drawin_class.signals, "property::height");

View File

@ -391,9 +391,6 @@ window_class_setup(lua_State *L)
signal_add(&window_class.signals, "property::opacity"); signal_add(&window_class.signals, "property::opacity");
signal_add(&window_class.signals, "property::struts"); signal_add(&window_class.signals, "property::struts");
signal_add(&window_class.signals, "property::type"); signal_add(&window_class.signals, "property::type");
signal_add(&window_class.signals, "button::press");
signal_add(&window_class.signals, "button::release");
signal_add(&window_class.signals, "mouse::move");
} }
// vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80