From 41ef107b888e660cc9e4b112bbc3a88621506db0 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sun, 14 Oct 2012 17:20:24 +0200 Subject: [PATCH] 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 --- event.c | 55 +++++++- lib/wibox/drawable.lua.in | 246 +++++++++++++++++++++++++++++++++ lib/wibox/init.lua.in | 209 ++-------------------------- objects/client.c | 277 ++++++++++++++++++++++++++++++++------ objects/client.h | 28 +++- objects/drawin.c | 2 - objects/window.c | 3 - 7 files changed, 570 insertions(+), 250 deletions(-) create mode 100644 lib/wibox/drawable.lua.in diff --git a/event.c b/event.c index 03bc5d819..d2823718b 100644 --- a/event.c +++ b/event.c @@ -220,10 +220,12 @@ event_handle_button(xcb_button_press_event_t *ev) ev->event_y -= drawin->geometry.y; } - /* Push the drawin */ + /* Push the drawable */ luaA_object_push(globalconf.L, drawin); + luaA_object_push_item(globalconf.L, -1, drawin->drawable); /* and handle the button raw button event */ event_emit_button(ev); + lua_pop(globalconf.L, 1); /* check if any button object matches */ 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); /* And handle the button raw button event */ 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 */ event_button_callback(ev, &c->buttons, -1, 1, NULL); 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_y); 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); } if((w = drawin_getbywin(ev->event))) { 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_y); 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_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); } if((drawin = drawin_getbywin(ev->event))) { 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); - 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))) { 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); - lua_pop(globalconf.L, 1); + lua_pop(globalconf.L, 2); } if((c = client_getbyframewin(ev->event))) { luaA_object_push(globalconf.L, c); 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); } } @@ -496,11 +540,14 @@ static void event_handle_expose(xcb_expose_event_t *ev) { drawin_t *drawin; + client_t *client; if((drawin = drawin_getbywin(ev->window))) drawin_refresh_pixmap_partial(drawin, ev->x, ev->y, ev->width, ev->height); + if ((client = client_getbyframewin(ev->window))) + client_refresh(client); } /** The key press event handler. diff --git a/lib/wibox/drawable.lua.in b/lib/wibox/drawable.lua.in new file mode 100644 index 000000000..0e306bada --- /dev/null +++ b/lib/wibox/drawable.lua.in @@ -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 diff --git a/lib/wibox/init.lua.in b/lib/wibox/init.lua.in index 70523ef7a..d7747af4e 100644 --- a/lib/wibox/init.lua.in +++ b/lib/wibox/init.lua.in @@ -27,100 +27,11 @@ local cairo = require("lgi").cairo local wibox = { mt = {} } wibox.layout = require("wibox.layout") wibox.widget = require("wibox.widget") - -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 +wibox.drawable = require("wibox.drawable") --- Set the widget that the wibox displays function wibox.set_widget(_wibox, widget) - if _wibox.widget and not _wibox.widget.__fake_widget then - -- 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() + _wibox._drawable:set_widget(widget) end --- 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, -- nil or a string that gears.color() understands. function wibox.set_bg(_wibox, c) - local c = c or "#000000" - if type(c) == "string" or type(c) == "table" then - c = color(c) - end - _wibox.background_color = c - _wibox.draw() + _wibox._drawable:set_bg(c) end --- 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, -- nil or a string that gears.color() understands. function wibox.set_fg(_wibox, c) - local c = c or "#FFFFFF" - if type(c) == "string" or type(c) == "table" then - c = color(c) - end - _wibox.foreground_color = c - _wibox.draw() + _wibox._drawable:set_fg(c) end --- Helper function to make wibox:buttons() work as expected @@ -164,48 +65,6 @@ function wibox.geometry(box, ...) return box.drawin:geometry(...) 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 w = _wibox.drawin @@ -216,9 +75,6 @@ local function setup_signals(_wibox) _wibox:emit_signal(name, ...) end) end - clone_signal("mouse::enter") - clone_signal("mouse::leave") - clone_signal("mouse::move") clone_signal("property::border_color") clone_signal("property::border_width") clone_signal("property::buttons") @@ -232,42 +88,13 @@ local function setup_signals(_wibox) clone_signal("property::width") clone_signal("property::x") 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 local function new(args) local ret = object() local w = capi.drawin(args) - ret.drawin = w + ret._drawable = wibox.drawable(w.drawable, ret) for k, v in pairs(wibox) do if type(v) == "function" then @@ -275,24 +102,11 @@ local function new(args) 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) + 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 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 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 setmetatable(ret, { __index = w, diff --git a/objects/client.c b/objects/client.c index 34c6c04f2..29d4d8d81 100644 --- a/objects/client.c +++ b/objects/client.c @@ -21,6 +21,7 @@ #include #include +#include #include "objects/tag.h" #include "ewmh.h" @@ -33,6 +34,9 @@ #include "common/atoms.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. * \param L The Lua VM state. * \return The number of element pushed on stack. @@ -580,6 +584,96 @@ HANDLE_GEOM(height) 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. * The sizes given as parameters are with borders! * \param c Client to resize. @@ -604,6 +698,11 @@ client_resize(client_t *c, area_t geometry) if(geometry.y + geometry.height < 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) return false; @@ -612,46 +711,7 @@ client_resize(client_t *c, area_t geometry) || c->geometry.width != geometry.width || c->geometry.height != geometry.height) { - 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(); - - 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); + client_resize_do(c, geometry); return true; } @@ -1215,6 +1275,136 @@ luaA_client_unmanage(lua_State *L) 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. * \param L The Lua VM state. * \return The number of elements pushed on stack. @@ -1684,6 +1874,10 @@ client_class_setup(lua_State *L) { "raise", luaA_client_raise }, { "lower", luaA_client_lower }, { "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 } }; @@ -1813,8 +2007,11 @@ client_class_setup(lua_State *L) signal_add(&client_class.signals, "focus"); signal_add(&client_class.signals, "list"); 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::leave"); + signal_add(&client_class.signals, "mouse::move"); signal_add(&client_class.signals, "property::above"); signal_add(&client_class.signals, "property::below"); signal_add(&client_class.signals, "property::class"); diff --git a/objects/client.h b/objects/client.h index 4846b4f0a..1fac20255 100644 --- a/objects/client.h +++ b/objects/client.h @@ -27,6 +27,7 @@ #include "draw.h" #include "banning.h" #include "objects/window.h" +#include "objects/drawable.h" #include "common/luaobject.h" #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 \ | XCB_EVENT_MASK_ENTER_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 */ struct client_t @@ -101,6 +115,15 @@ struct client_t uint32_t pid; /** Window it is 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) @@ -150,7 +173,10 @@ void client_focus_refresh(void); bool client_hasproto(client_t *, xcb_atom_t); void client_ignore_enterleave_events(void); void client_restore_enterleave_events(void); +void client_refresh(client_t *); 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. * \param c The client to raise. diff --git a/objects/drawin.c b/objects/drawin.c index 047998048..8250fd0c0 100644 --- a/objects/drawin.c +++ b/objects/drawin.c @@ -592,8 +592,6 @@ drawin_class_setup(lua_State *L) (lua_class_propfunc_t) luaA_window_get_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::cursor"); signal_add(&drawin_class.signals, "property::height"); diff --git a/objects/window.c b/objects/window.c index 691335d9b..a904615f3 100644 --- a/objects/window.c +++ b/objects/window.c @@ -391,9 +391,6 @@ window_class_setup(lua_State *L) signal_add(&window_class.signals, "property::opacity"); signal_add(&window_class.signals, "property::struts"); 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