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