diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2b4805 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/wallpaper.txt diff --git a/extras/udev/README b/extras/udev/README new file mode 100644 index 0000000..59acb2d --- /dev/null +++ b/extras/udev/README @@ -0,0 +1,5 @@ +# How to enable backlight brightness controls +1. Create "video" group if it doesn't exist +2. Add your users to the "video" group +3. Place the backlight.rules file into /etc/udev/rules.d +4. Reboot diff --git a/extras/udev/backlight.rules b/extras/udev/backlight.rules new file mode 100644 index 0000000..7330687 --- /dev/null +++ b/extras/udev/backlight.rules @@ -0,0 +1,3 @@ +# Rules to make backlight writable by Video group +ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chgrp video /sys/class/backlight/%k/brightness" +ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chmod g+w /sys/class/backlight/%k/brightness" diff --git a/libs/awmtk2.lua b/libs/awmtk2.lua index e50b111..a92cb3f 100644 --- a/libs/awmtk2.lua +++ b/libs/awmtk2.lua @@ -145,6 +145,14 @@ awmtk.proto_style.wibar = awmtk.create_delta("wibar", { awmtk.proto_style.menu = awmtk.create_delta("menu", { margins = 1 }, awmtk.proto_style,awmtk.proto_style.base) + +awmtk.proto_style.center = awmtk.create_delta("center", { + margins = 1 +}, awmtk.proto_style,awmtk.proto_style.base) + +awmtk.proto_style.slider = awmtk.create_delta("slider", { + margins = 1 +}, awmtk.proto_style,awmtk.proto_style.base) -- }}} -- {{{ Generic templates @@ -258,7 +266,8 @@ awmtk.proto_templates = { font = style.article.font, align = options.font_align or style.article.font_align, - valign = "center" + valign = style.article.title_valign or "center", + align = style.article.title_align or "left" }, (options.description and { markup = options.description or "", @@ -267,7 +276,8 @@ awmtk.proto_templates = { font = style.article.small_font, align = options.small_font_align or style.article.small_font_align, - valign = "center" + valign = style.article.desc_valign or "center", + align = style.article.desc_align or "left" }), spacing = style.article.spacing, layout = wibox.layout.flex.vertical @@ -278,6 +288,26 @@ awmtk.proto_templates = { end end, + center = function(style) + return function(layout,options) + options = options or {} + return awmtk.merge({ + { + layout, + strategy = "exact", + height = options.height or + style.center.height, + width = options.width or + style.center.width, + widget = wibox.container.constraint + }, + widget = wibox.container.place, + valign = "center", + halign = "center" + },options or {}) + end + end, + popup = function(style) -- Popup is a distinct template designed to accomodate the "root" of -- a popup, allowing one to add titlebars to popups, for example. @@ -343,6 +373,29 @@ awmtk.proto_templates = { top = style.wibar.top },options or {}) end + end, + + slider = function(style) + -- Slider widget but wired to work with the AWMTK2 namespace system + return function(args) + return awmtk.merge({ + handle_shape = style.slider.handle_shape or style.slider.shape, + handle_color = style.slider.bg_normal, + handle_margins = style.slider.handle_margins, + handle_width = style.slider.handle_width, + handle_border_color = style.slider.handle_border_color, + handle_border_width = style.slider.handle_border_width, + bar_shape = style.slider.bar_shape or style.slider.shape, + bar_height = style.slider.bar_height, + bar_color = style.slider.bg_focus, + bar_margins = style.slider.bar_margins, + bar_border_width = style.slider.bar_border_width, + bar_border_color = style.slider.bar_border_color, + forced_width = style.slider.width, + forced_height = style.slider.height, + widget = wibox.widget.slider + },args or {}) + end end } diff --git a/libs/pager.lua b/libs/pager.lua new file mode 100644 index 0000000..f614875 --- /dev/null +++ b/libs/pager.lua @@ -0,0 +1,36 @@ +local awful = require("awful") +local wibox = require("wibox") + +return function(widget,list,max_elements) + if not tostring(widget):match("wibox.layout") then + error("Cannot attach pager to widget that isn't a layout") + end + local new_pager = { + page = widget, + list = list, + index = 0, + max = max_elements + } + function new_pager:update() + self.page:reset() + for i = 1+(self.max*self.index),self.max+(self.max*self.index) do + if self.list[i] then + self.page:add(self.list[i]) + end + end + end + function new_pager:next() + if #list >= (self.index+1)*self.max then + self.index = self.index + 1 + new_pager:update() + end + end + function new_pager:prev() + if self.index > 0 then + self.index = self.index - 1 + new_pager:update() + end + end + new_pager:update() + return new_pager +end diff --git a/libs/parsers.lua b/libs/parsers.lua new file mode 100644 index 0000000..97c1aeb --- /dev/null +++ b/libs/parsers.lua @@ -0,0 +1,57 @@ +-- General collection of parsers for various linux utilities +local parsers = {} +parsers.upower = function(data) + -- this shit was born in a fever dream, that's the only reasonable explanation i can give to this. + local struct = {} + local layer = 1 + local leaf = struct + local history = {} + data:gsub("\n?\r?([%s]*)([^\n]*)",function(block,line) + if block == "" then block = " " end + block = block:len() + if line:match("%S") then + if block > layer then + history[layer] = leaf + local pos = #leaf + local key = leaf[pos] + table.remove(leaf,pos) + leaf[key] = {} + leaf = leaf[key] + layer = block + end + if block < layer then + leaf = history[block] + layer = block + end + table.insert(leaf,line) + end + end) + local function prettify(t) + for k,v in ipairs(t) do + if v:find(":") then + t[v:match("^(.-):")] = v:match("^.-:%s*(.*)") + t[k] = nil + end + end + for k,v in pairs(t) do + if type(v) == "table" then + t[k] = prettify(v) + end + end + return t + end + return prettify(struct) +end +return parsers + + + + + + + + + + + + diff --git a/libs/syscontrol.lua b/libs/syscontrol.lua new file mode 100644 index 0000000..2cea2c6 --- /dev/null +++ b/libs/syscontrol.lua @@ -0,0 +1,109 @@ +-- Pure lua module for controlling, reading and enumerating devices through sysfs +-- Because in Linux we trust. +local syscontrol = { + power_supply = {}, + backlight = {}, + hwmon = {} +} +syscontrol.backlight.enumerate = function() + local lshandler = io.popen("ls -1 /sys/class/backlight","r") + if lshandler then + local out = lshandler:read("*a") + lshandler:close() + local devices = {} + out:gsub("(%S*)\n",function(a) + table.insert(devices,a) + end) + return devices + else + return nil, "Unable to ls /sys/class/backlight" + end +end +syscontrol.backlight.read_attribs = function(dev_name) + local fhandler,err = io.open("/sys/class/backlight/"..dev_name.."/brightness","r") + if fhandler then + local bhandler = io.open("/sys/class/backlight/"..dev_name.."/max_brightness","r") + local device = { + name = dev_name, + full_path = "/sys/class/backlight/"..dev_name, + brightness = tonumber(fhandler:read("*a")), + max_brightness = tonumber(bhandler:read("*a")), + writable = false + } + fhandler:close() + local wfhandler = io.open("/sys/class/backlight/"..dev_name.."/brightness","w") + if wfhandler then + wfhandler:close() + device.writable = true + end + return device + else + return nil, err + end +end +syscontrol.backlight.set_brightness = function(device,brightness) + if device.writable then + brightness = tonumber(brightness) + if (brightness < 0) or (brightness > device.max_brightness) then + return nil, "Brightness exceeds given limits" + end + local fhandler = io.open(device.full_path.."/brightness","w") + fhandler:write(tostring(brightness)) + fhandler:close() + return true + else + return nil, "Device is not writable" + end +end +syscontrol.power_supply.enumerate = function() + local lshandler = io.popen("ls -1 /sys/class/power_supply","r") + if lshandler then + local out = lshandler:read("*a") + lshandler:close() + local devices = {} + out:gsub("(%S*)\n",function(a) + table.insert(devices,a) + end) + return devices + else + return nil, "Unable to ls /sys/class/power_supply" + end +end +syscontrol.power_supply.read_attribs = function(dev_name) + local fhandler,err = io.open("/sys/class/power_supply/"..dev_name.."/type","r") + if fhandler then + local dtype = fhandler:read("*a"):match("%S*") + fhandler:close() + local full_path = "/sys/class/power_supply/"..dev_name + local device = { + type = dtype, + full_path = full_path, + name = dev_name + } + if dtype == "Mains" then + local online_f = io.open(full_path.."/online","r") + device.online = (online_f:read("*a"):match("%S*") == "1") + online_f:close() + elseif dtype == "Battery" then + local capacity_f = io.open(full_path.."/capacity","r") + local model_f = io.open(full_path.."/model_name","r") + local energy_full_design_f = io.open(full_path.."/energy_full_design","r") + local energy_full_f = io.open(full_path.."/energy_full","r") + local charging_f = io.open(full_path.."/status","r") + device.model = model_f:read("*a"):match("[^\n]*") + device.energy_full_design = tonumber(energy_full_design_f:read("*a")) + device.energy_full = tonumber(energy_full_f:read("*a")) + device.quality = (device.energy_full/device.energy_full_design)*100 + device.capacity = tonumber(capacity_f:read("*a")) + device.charging = (charging_f:read("*a"):match("%S*") == "Charging") + capacity_f:close() + model_f:close() + energy_full_design_f:close() + energy_full_f:close() + end + return device + else + return nil, err + end +end +return syscontrol diff --git a/libs/syscontrol_test.lua b/libs/syscontrol_test.lua new file mode 100644 index 0000000..c04255c --- /dev/null +++ b/libs/syscontrol_test.lua @@ -0,0 +1,22 @@ +-- Who needs upower and xbacklight anyways? +local syscontrol = require("syscontrol") +local devices = syscontrol.power_supply.enumerate() +for k,v in pairs(devices) do + local data = syscontrol.power_supply.read_attribs(v) + print("Device: ") + for kk,vv in pairs(data) do + print(kk,vv) + end +end +local backlight_devices = syscontrol.backlight.enumerate() +for k,v in pairs(backlight_devices) do + local data = syscontrol.backlight.read_attribs(v) + print("Backlight device: ") + for kk,vv in pairs(data) do + print(kk,vv) + end + if data.writable then + syscontrol.backlight.set_brightness(data,data.max_brightness) + end +end + diff --git a/rc_dynamic_tags.lua b/rc_dynamic_tags.lua deleted file mode 100644 index 7848e31..0000000 --- a/rc_dynamic_tags.lua +++ /dev/null @@ -1,571 +0,0 @@ --- If LuaRocks is installed, make sure that packages installed through it are --- found (e.g. lgi). If LuaRocks is not installed, do nothing. -pcall(require, "luarocks.loader") - --- Standard awesome library -local gears = require("gears") -local awful = require("awful") -require("awful.autofocus") --- Widget and layout library -local wibox = require("wibox") --- Theme handling library -local beautiful = require("beautiful") --- Notification library -local naughty = require("naughty") -local menubar = require("menubar") -local hotkeys_popup = require("awful.hotkeys_popup") --- Enable hotkeys help widget for VIM and other apps --- when client with a matching name is opened: -require("awful.hotkeys_popup.keys") - --- {{{ Error handling --- Check if awesome encountered an error during startup and fell back to --- another config (This code will only ever execute for the fallback config) -if awesome.startup_errors then - naughty.notify({ preset = naughty.config.presets.critical, - title = "Oops, there were errors during startup!", - text = awesome.startup_errors }) -end - --- Handle runtime errors after startup -do - local in_error = false - awesome.connect_signal("debug::error", function (err) - -- Make sure we don't go into an endless error loop - if in_error then return end - in_error = true - - naughty.notify({ preset = naughty.config.presets.critical, - title = "Oops, an error happened!", - text = tostring(err) }) - in_error = false - end) -end --- }}} - --- {{{ Variable definitions --- Themes define colours, icons, font and wallpapers. -beautiful.init(gears.filesystem.get_themes_dir() .. "default/theme.lua") - --- This is used later as the default terminal and editor to run. -terminal = "xterm" -editor = os.getenv("EDITOR") or "nano" -editor_cmd = terminal .. " -e " .. editor - --- Default modkey. --- Usually, Mod4 is the key with a logo between Control and Alt. --- If you do not like this or do not have such a key, --- I suggest you to remap Mod4 to another key using xmodmap or other tools. --- However, you can use another modifier like Mod1, but it may interact with others. -modkey = "Mod4" - --- Table of layouts to cover with awful.layout.inc, order matters. -awful.layout.layouts = { - awful.layout.suit.floating, - awful.layout.suit.tile, - awful.layout.suit.tile.left, - awful.layout.suit.tile.bottom, - awful.layout.suit.tile.top, - awful.layout.suit.fair, - awful.layout.suit.fair.horizontal, - awful.layout.suit.spiral, - awful.layout.suit.spiral.dwindle, - awful.layout.suit.max, - awful.layout.suit.max.fullscreen, - awful.layout.suit.magnifier, - awful.layout.suit.corner.nw, - -- awful.layout.suit.corner.ne, - -- awful.layout.suit.corner.sw, - -- awful.layout.suit.corner.se, -} --- }}} - --- {{{ Menu --- Create a launcher widget and a main menu -myawesomemenu = { - { "hotkeys", function() hotkeys_popup.show_help(nil, awful.screen.focused()) end }, - { "manual", terminal .. " -e man awesome" }, - { "edit config", editor_cmd .. " " .. awesome.conffile }, - { "restart", awesome.restart }, - { "quit", function() awesome.quit() end }, -} - -mymainmenu = awful.menu({ items = { { "awesome", myawesomemenu, beautiful.awesome_icon }, - { "open terminal", terminal } - } - }) - -mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon, - menu = mymainmenu }) - --- Menubar configuration -menubar.utils.terminal = terminal -- Set the terminal for applications that require it --- }}} - --- Keyboard map indicator and switcher -mykeyboardlayout = awful.widget.keyboardlayout() - --- {{{ Wibar --- Create a textclock widget -mytextclock = wibox.widget.textclock() - --- Create a wibox for each screen and add it -local taglist_buttons = gears.table.join( - awful.button({ }, 1, function(t) t:view_only() end), - awful.button({ modkey }, 1, function(t) - if client.focus then - client.focus:move_to_tag(t) - end - end), - awful.button({ }, 3, awful.tag.viewtoggle), - awful.button({ modkey }, 3, function(t) - if client.focus then - client.focus:toggle_tag(t) - end - end), - awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end), - awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end) - ) - -local tasklist_buttons = gears.table.join( - awful.button({ }, 1, function (c) - if c == client.focus then - c.minimized = true - else - c:emit_signal( - "request::activate", - "tasklist", - {raise = true} - ) - end - end), - awful.button({ }, 3, function() - awful.menu.client_list({ theme = { width = 250 } }) - end), - awful.button({ }, 4, function () - awful.client.focus.byidx(1) - end), - awful.button({ }, 5, function () - awful.client.focus.byidx(-1) - end)) - -local function set_wallpaper(s) - -- Wallpaper - if beautiful.wallpaper then - local wallpaper = beautiful.wallpaper - -- If wallpaper is a function, call it with the screen - if type(wallpaper) == "function" then - wallpaper = wallpaper(s) - end - gears.wallpaper.maximized(wallpaper, s, true) - end -end - --- Re-set wallpaper when a screen's geometry changes (e.g. different resolution) -screen.connect_signal("property::geometry", set_wallpaper) - -awful.screen.connect_for_each_screen(function(s) - -- Wallpaper - set_wallpaper(s) - - -- Each screen has its own tag table. - awful.tag({ "5", "6", "7", "8", "9" }, s, awful.layout.layouts[1]) - - -- Create a promptbox for each screen - s.mypromptbox = awful.widget.prompt() - -- Create an imagebox widget which will contain an icon indicating which layout we're using. - -- We need one layoutbox per screen. - s.mylayoutbox = awful.widget.layoutbox(s) - s.mylayoutbox:buttons(gears.table.join( - awful.button({ }, 1, function () awful.layout.inc( 1) end), - awful.button({ }, 3, function () awful.layout.inc(-1) end), - awful.button({ }, 4, function () awful.layout.inc( 1) end), - awful.button({ }, 5, function () awful.layout.inc(-1) end))) - -- Create a taglist widget - s.mytaglist = awful.widget.taglist { - screen = s, - filter = awful.widget.taglist.filter.all, - buttons = taglist_buttons - } - - -- Create a tasklist widget - s.mytasklist = awful.widget.tasklist { - screen = s, - filter = awful.widget.tasklist.filter.currenttags, - buttons = tasklist_buttons - } - - -- Create the wibox - s.mywibox = awful.wibar({ position = "top", screen = s }) - - -- Add widgets to the wibox - s.mywibox:setup { - layout = wibox.layout.align.horizontal, - { -- Left widgets - layout = wibox.layout.fixed.horizontal, - mylauncher, - s.mytaglist, - s.mypromptbox, - }, - s.mytasklist, -- Middle widget - { -- Right widgets - layout = wibox.layout.fixed.horizontal, - mykeyboardlayout, - wibox.widget.systray(), - mytextclock, - s.mylayoutbox, - }, - } -end) --- }}} - --- {{{ Mouse bindings -root.buttons(gears.table.join( - awful.button({ }, 3, function () mymainmenu:toggle() end), - awful.button({ }, 4, awful.tag.viewnext), - awful.button({ }, 5, awful.tag.viewprev) -)) --- }}} - --- {{{ Key bindings -globalkeys = gears.table.join( - awful.key({ modkey, }, "s", hotkeys_popup.show_help, - {description="show help", group="awesome"}), - awful.key({ modkey, }, "Left", awful.tag.viewprev, - {description = "view previous", group = "tag"}), - awful.key({ modkey, }, "Right", awful.tag.viewnext, - {description = "view next", group = "tag"}), - awful.key({ modkey, }, "Escape", awful.tag.history.restore, - {description = "go back", group = "tag"}), - - awful.key({ modkey, }, "j", - function () - awful.client.focus.byidx( 1) - end, - {description = "focus next by index", group = "client"} - ), - awful.key({ modkey, }, "k", - function () - awful.client.focus.byidx(-1) - end, - {description = "focus previous by index", group = "client"} - ), - awful.key({ modkey, }, "w", function () mymainmenu:show() end, - {description = "show main menu", group = "awesome"}), - - -- Layout manipulation - awful.key({ modkey, "Shift" }, "j", function () awful.client.swap.byidx( 1) end, - {description = "swap with next client by index", group = "client"}), - awful.key({ modkey, "Shift" }, "k", function () awful.client.swap.byidx( -1) end, - {description = "swap with previous client by index", group = "client"}), - awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative( 1) end, - {description = "focus the next screen", group = "screen"}), - awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative(-1) end, - {description = "focus the previous screen", group = "screen"}), - awful.key({ modkey, }, "u", awful.client.urgent.jumpto, - {description = "jump to urgent client", group = "client"}), - awful.key({ modkey, }, "Tab", - function () - awful.client.focus.history.previous() - if client.focus then - client.focus:raise() - end - end, - {description = "go back", group = "client"}), - - -- Standard program - awful.key({ modkey, }, "Return", function () awful.spawn(terminal) end, - {description = "open a terminal", group = "launcher"}), - awful.key({ modkey, "Control" }, "r", awesome.restart, - {description = "reload awesome", group = "awesome"}), - awful.key({ modkey, "Shift" }, "q", awesome.quit, - {description = "quit awesome", group = "awesome"}), - - awful.key({ modkey, }, "l", function () awful.tag.incmwfact( 0.05) end, - {description = "increase master width factor", group = "layout"}), - awful.key({ modkey, }, "h", function () awful.tag.incmwfact(-0.05) end, - {description = "decrease master width factor", group = "layout"}), - awful.key({ modkey, "Shift" }, "h", function () awful.tag.incnmaster( 1, nil, true) end, - {description = "increase the number of master clients", group = "layout"}), - awful.key({ modkey, "Shift" }, "l", function () awful.tag.incnmaster(-1, nil, true) end, - {description = "decrease the number of master clients", group = "layout"}), - awful.key({ modkey, "Control" }, "h", function () awful.tag.incncol( 1, nil, true) end, - {description = "increase the number of columns", group = "layout"}), - awful.key({ modkey, "Control" }, "l", function () awful.tag.incncol(-1, nil, true) end, - {description = "decrease the number of columns", group = "layout"}), - awful.key({ modkey, }, "space", function () awful.layout.inc( 1) end, - {description = "select next", group = "layout"}), - awful.key({ modkey, "Shift" }, "space", function () awful.layout.inc(-1) end, - {description = "select previous", group = "layout"}), - - awful.key({ modkey, "Control" }, "n", - function () - local c = awful.client.restore() - -- Focus restored client - if c then - c:emit_signal( - "request::activate", "key.unminimize", {raise = true} - ) - end - end, - {description = "restore minimized", group = "client"}), - - -- Prompt - awful.key({ modkey }, "r", function () awful.screen.focused().mypromptbox:run() end, - {description = "run prompt", group = "launcher"}), - - awful.key({ modkey }, "x", - function () - awful.prompt.run { - prompt = "Run Lua code: ", - textbox = awful.screen.focused().mypromptbox.widget, - exe_callback = awful.util.eval, - history_path = awful.util.get_cache_dir() .. "/history_eval" - } - end, - {description = "lua execute prompt", group = "awesome"}), - -- Menubar - awful.key({ modkey }, "p", function() menubar.show() end, - {description = "show the menubar", group = "launcher"}) -) - -clientkeys = gears.table.join( - awful.key({ modkey, }, "f", - function (c) - c.fullscreen = not c.fullscreen - c:raise() - end, - {description = "toggle fullscreen", group = "client"}), - awful.key({ modkey, "Shift" }, "c", function (c) c:kill() end, - {description = "close", group = "client"}), - awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle , - {description = "toggle floating", group = "client"}), - awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end, - {description = "move to master", group = "client"}), - awful.key({ modkey, }, "o", function (c) c:move_to_screen() end, - {description = "move to screen", group = "client"}), - awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end, - {description = "toggle keep on top", group = "client"}), - awful.key({ modkey, }, "n", - function (c) - -- The client currently has the input focus, so it cannot be - -- minimized, since minimized clients can't have the focus. - c.minimized = true - end , - {description = "minimize", group = "client"}), - awful.key({ modkey, }, "m", - function (c) - c.maximized = not c.maximized - c:raise() - end , - {description = "(un)maximize", group = "client"}), - awful.key({ modkey, "Control" }, "m", - function (c) - c.maximized_vertical = not c.maximized_vertical - c:raise() - end , - {description = "(un)maximize vertically", group = "client"}), - awful.key({ modkey, "Shift" }, "m", - function (c) - c.maximized_horizontal = not c.maximized_horizontal - c:raise() - end , - {description = "(un)maximize horizontally", group = "client"}) -) - -awful.screen.connect_for_each_screen(function(s) - for _, t in pairs(s.tags) do - t:delete() - end - awful.tag({ "1", "2", "3", "4", "5" }, s, awful.layout.layouts[1]) -end) --- Bind all key numbers to tags. --- Be careful: we use keycodes to make it work on any keyboard layout. --- This should map on the top row of your keyboard, usually 1 to 9. -for i = 1, 5 do - globalkeys = gears.table.join(globalkeys, - -- View tag only. - awful.key({ modkey }, "#" .. i + 9, - function () - local screen = awful.screen.focused() - local tag = screen.tags[i] - if tag then - tag:view_only() - end - end, - {description = "view tag #"..i, group = "tag"}), - -- Toggle tag display. - awful.key({ modkey, "Control" }, "#" .. i + 9, - function () - local screen = awful.screen.focused() - local tag = screen.tags[i] - if tag then - awful.tag.viewtoggle(tag) - end - end, - {description = "toggle tag #" .. i, group = "tag"}), - -- Move client to tag. - awful.key({ modkey, "Shift" }, "#" .. i + 9, - function () - if client.focus then - local tag = client.focus.screen.tags[i] - if tag then - client.focus:move_to_tag(tag) - end - end - end, - {description = "move focused client to tag #"..i, group = "tag"}), - -- Toggle tag on focused client. - awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9, - function () - if client.focus then - local tag = client.focus.screen.tags[i] - if tag then - client.focus:toggle_tag(tag) - end - end - end, - {description = "toggle focused client on tag #" .. i, group = "tag"}) - ) -end - -clientbuttons = gears.table.join( - awful.button({ }, 1, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - end), - awful.button({ modkey }, 1, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - awful.mouse.client.move(c) - end), - awful.button({ modkey }, 3, function (c) - c:emit_signal("request::activate", "mouse_click", {raise = true}) - awful.mouse.client.resize(c) - end) -) - --- Set keys -root.keys(globalkeys) --- }}} - --- {{{ Rules --- Rules to apply to new clients (through the "manage" signal). -awful.rules.rules = { - -- All clients will match this rule. - { rule = { }, - properties = { border_width = beautiful.border_width, - border_color = beautiful.border_normal, - focus = awful.client.focus.filter, - raise = true, - keys = clientkeys, - buttons = clientbuttons, - screen = awful.screen.preferred, - placement = awful.placement.no_overlap+awful.placement.no_offscreen - } - }, - - -- Floating clients. - { rule_any = { - instance = { - "DTA", -- Firefox addon DownThemAll. - "copyq", -- Includes session name in class. - "pinentry", - }, - class = { - "Arandr", - "Blueman-manager", - "Gpick", - "Kruler", - "MessageWin", -- kalarm. - "Sxiv", - "Tor Browser", -- Needs a fixed window size to avoid fingerprinting by screen size. - "Wpa_gui", - "veromix", - "xtightvncviewer"}, - - -- Note that the name property shown in xprop might be set slightly after creation of the client - -- and the name shown there might not match defined rules here. - name = { - "Event Tester", -- xev. - }, - role = { - "AlarmWindow", -- Thunderbird's calendar. - "ConfigManager", -- Thunderbird's about:config. - "pop-up", -- e.g. Google Chrome's (detached) Developer Tools. - } - }, properties = { floating = true }}, - - -- Add titlebars to normal clients and dialogs - { rule_any = {type = { "normal", "dialog" } - }, properties = { titlebars_enabled = true } - }, - - -- Set Firefox to always map on the tag named "2" on screen 1. - -- { rule = { class = "Firefox" }, - -- properties = { screen = 1, tag = "2" } }, -} --- }}} - --- {{{ Signals --- Signal function to execute when a new client appears. -client.connect_signal("manage", function (c) - -- Set the windows at the slave, - -- i.e. put it at the end of others instead of setting it master. - -- if not awesome.startup then awful.client.setslave(c) end - - if awesome.startup - and not c.size_hints.user_position - and not c.size_hints.program_position then - -- Prevent clients from being unreachable after screen count changes. - awful.placement.no_offscreen(c) - end -end) - --- Add a titlebar if titlebars_enabled is set to true in the rules. -client.connect_signal("request::titlebars", function(c) - -- buttons for the titlebar - local buttons = gears.table.join( - awful.button({ }, 1, function() - c:emit_signal("request::activate", "titlebar", {raise = true}) - awful.mouse.client.move(c) - end), - awful.button({ }, 3, function() - c:emit_signal("request::activate", "titlebar", {raise = true}) - awful.mouse.client.resize(c) - end) - ) - - awful.titlebar(c) : setup { - { -- Left - awful.titlebar.widget.iconwidget(c), - buttons = buttons, - layout = wibox.layout.fixed.horizontal - }, - { -- Middle - { -- Title - align = "center", - widget = awful.titlebar.widget.titlewidget(c) - }, - buttons = buttons, - layout = wibox.layout.flex.horizontal - }, - { -- Right - awful.titlebar.widget.floatingbutton (c), - awful.titlebar.widget.maximizedbutton(c), - awful.titlebar.widget.stickybutton (c), - awful.titlebar.widget.ontopbutton (c), - awful.titlebar.widget.closebutton (c), - layout = wibox.layout.fixed.horizontal() - }, - layout = wibox.layout.align.horizontal - } -end) - --- Enable sloppy focus, so that focus follows mouse. -client.connect_signal("mouse::enter", function(c) - c:emit_signal("request::activate", "mouse_enter", {raise = false}) -end) - -client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end) -client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end) --- }}} - diff --git a/themes/reno98/config/wibar_top.json b/themes/reno98/config/wibar_top.json index d1d6c26..d121107 100644 --- a/themes/reno98/config/wibar_top.json +++ b/themes/reno98/config/wibar_top.json @@ -7,9 +7,16 @@ ], "right": [ + { "widget": "widgets.notifications", + "screen": true + }, + { "widget": "widgets.wallpapers", + "screen": true + }, { "widget": "widgets.base.subpanel", "layout": { "list": [ + { "widget": "widgets.battery" }, { "widget": "widgets.base.systray" }, { "widget": "widgets.base.clock" } ] diff --git a/themes/reno98/icons/ac-adapter-symbolic.png b/themes/reno98/icons/ac-adapter-symbolic.png new file mode 100644 index 0000000..cdf53b7 Binary files /dev/null and b/themes/reno98/icons/ac-adapter-symbolic.png differ diff --git a/themes/reno98/icons/backlight-symbolic.png b/themes/reno98/icons/backlight-symbolic.png new file mode 100644 index 0000000..828f571 Binary files /dev/null and b/themes/reno98/icons/backlight-symbolic.png differ diff --git a/themes/reno98/icons/battery-caution-charging-symbolic.png b/themes/reno98/icons/battery-caution-charging-symbolic.png new file mode 100644 index 0000000..be0746b Binary files /dev/null and b/themes/reno98/icons/battery-caution-charging-symbolic.png differ diff --git a/themes/reno98/icons/battery-caution-symbolic.png b/themes/reno98/icons/battery-caution-symbolic.png new file mode 100644 index 0000000..25e3572 Binary files /dev/null and b/themes/reno98/icons/battery-caution-symbolic.png differ diff --git a/themes/reno98/icons/battery-empty-charging-symbolic.png b/themes/reno98/icons/battery-empty-charging-symbolic.png new file mode 100644 index 0000000..6628ab9 Binary files /dev/null and b/themes/reno98/icons/battery-empty-charging-symbolic.png differ diff --git a/themes/reno98/icons/battery-empty-symbolic.png b/themes/reno98/icons/battery-empty-symbolic.png new file mode 100644 index 0000000..7b88252 Binary files /dev/null and b/themes/reno98/icons/battery-empty-symbolic.png differ diff --git a/themes/reno98/icons/battery-full-charged-symbolic.png b/themes/reno98/icons/battery-full-charged-symbolic.png new file mode 100644 index 0000000..4735283 Binary files /dev/null and b/themes/reno98/icons/battery-full-charged-symbolic.png differ diff --git a/themes/reno98/icons/battery-full-charging-symbolic.png b/themes/reno98/icons/battery-full-charging-symbolic.png new file mode 100644 index 0000000..c299736 Binary files /dev/null and b/themes/reno98/icons/battery-full-charging-symbolic.png differ diff --git a/themes/reno98/icons/battery-full-symbolic.png b/themes/reno98/icons/battery-full-symbolic.png new file mode 100644 index 0000000..4735283 Binary files /dev/null and b/themes/reno98/icons/battery-full-symbolic.png differ diff --git a/themes/reno98/icons/battery-good-charging-symbolic.png b/themes/reno98/icons/battery-good-charging-symbolic.png new file mode 100644 index 0000000..364fc94 Binary files /dev/null and b/themes/reno98/icons/battery-good-charging-symbolic.png differ diff --git a/themes/reno98/icons/battery-good-symbolic.png b/themes/reno98/icons/battery-good-symbolic.png new file mode 100644 index 0000000..c9edfb0 Binary files /dev/null and b/themes/reno98/icons/battery-good-symbolic.png differ diff --git a/themes/reno98/icons/battery-low-charging-symbolic.png b/themes/reno98/icons/battery-low-charging-symbolic.png new file mode 100644 index 0000000..f1a7544 Binary files /dev/null and b/themes/reno98/icons/battery-low-charging-symbolic.png differ diff --git a/themes/reno98/icons/battery-low-symbolic.png b/themes/reno98/icons/battery-low-symbolic.png new file mode 100644 index 0000000..5f26e83 Binary files /dev/null and b/themes/reno98/icons/battery-low-symbolic.png differ diff --git a/themes/reno98/icons/battery-missing-symbolic.png b/themes/reno98/icons/battery-missing-symbolic.png new file mode 100644 index 0000000..37d01b3 Binary files /dev/null and b/themes/reno98/icons/battery-missing-symbolic.png differ diff --git a/themes/reno98/icons/battery.png b/themes/reno98/icons/battery.png new file mode 100644 index 0000000..4735283 Binary files /dev/null and b/themes/reno98/icons/battery.png differ diff --git a/themes/reno98/icons/battery.svg b/themes/reno98/icons/battery.svg new file mode 100644 index 0000000..07cce1d --- /dev/null +++ b/themes/reno98/icons/battery.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/themes/reno98/icons/notifications-area-symbolic.png b/themes/reno98/icons/notifications-area-symbolic.png new file mode 100644 index 0000000..ceb0578 Binary files /dev/null and b/themes/reno98/icons/notifications-area-symbolic.png differ diff --git a/themes/reno98/icons/wallpapers.png b/themes/reno98/icons/wallpapers.png new file mode 100644 index 0000000..49e84e7 Binary files /dev/null and b/themes/reno98/icons/wallpapers.png differ diff --git a/themes/reno98/theme.lua b/themes/reno98/theme.lua index 163b6af..11aafbf 100644 --- a/themes/reno98/theme.lua +++ b/themes/reno98/theme.lua @@ -89,6 +89,7 @@ theme.titlebar_maximized_button_normal_active = themes_path.."reno98/titlebar/ma theme.titlebar_maximized_button_focus_active = themes_path.."reno98/titlebar/maximized_focus_active.png" theme.wallpaper = themes_path.."reno98/background.png" +theme.wallpapers_icon = themes_path.."reno98/icons/wallpapers.png" -- You can use your own layout icons like this: theme.layout_fairh = themes_path.."reno98/layouts/fairhw.png" @@ -290,6 +291,28 @@ end -- from /usr/share/icons and /usr/share/icons/hicolor will be used. theme.icon_theme = "Chicago95" +-- Icons +local icons = { + "battery-caution-charging-symbolic", + "battery-empty-charging-symbolic", + "battery-full-charging-symbolic", + "battery-good-charging-symbolic", + "battery-low-charging-symbolic" +} +for k,v in pairs(icons) do + theme[v] = themes_path.."reno98/icons/"..v..".png" + theme[v:gsub("-charging","")] = themes_path.."reno98/icons/"..v:gsub("-charging","")..".png" +end +theme["battery-full-charged-symbolic"] = themes_path.."reno98/icons/battery-full-charged-symbolic.png" +theme["battery-missing-symbolic"] = themes_path.."reno98/icons/battery-missing-symbolic.png" +theme["ac-adapter-symbolic"] = themes_path.."reno98/icons/ac-adapter-symbolic.png" +theme["backlight-symbolic"] = themes_path.."reno98/icons/backlight-symbolic.png" +theme["notifications-area-symbolic"] = themes_path.."reno98/icons/notifications-area-symbolic.png" + +-- Notification popups settings +theme.notification_width = 200 +theme.notification_height = 60 + -- Default icon for clients -- This one has to be baked as a surface to avoid memory leaks theme.icon_default = gears.surface(themes_path.."reno98/icons/unknown-app.png") @@ -340,6 +363,35 @@ theme.widgets = { end, bgimage = theme.bgimage_outset, stretch = true + }, + slider = { + shape = function(cr,width,height) + return gears.shape.rounded_rect(cr,width,height,0) + end, + height = 20, + width = 140, + handle_width = 8, + handle_border_color = theme.bg_focus, + handle_border_width = 2, + bar_height = 6 + } + }, + battery = { + button = { + margins = 0, + onpress = function() end, + onrelease = function() end, + bgimage = function() end + } + }, + notifications = { + button = { + height = 60, + width = 200 + }, + article = { + title_valign = "top", + desc_valign = "top" } }, clock = { diff --git a/widgets/battery.lua b/widgets/battery.lua new file mode 100644 index 0000000..92a9198 --- /dev/null +++ b/widgets/battery.lua @@ -0,0 +1,213 @@ +local awful = require("awful") +local beautiful = require("beautiful") +local gears = require("gears") +local wibox = require("wibox") +local awmtk2 = require("awmtk2") +local syscontrol = require("syscontrol") + +local function get_virtual_icon(data) + local count = 0 + local cumulative = 0 + local name = "battery-" + for k,v in pairs(data) do + if type(v) == "number" then + cumulative = cumulative + v + count = count + 1 + end + end + local percentage = math.floor((cumulative/(count*100))*100) + if percentage < 15 then + name = name.."caution-" + elseif percentage < 30 then + name = name.."low-" + elseif percentage < 60 then + name = name.."good-" + else + name = name.."full-" + end + if data["charge"] then + name = name.."charging-" + end + return beautiful[name.."symbolic"],percentage +end + +return function(args) + local style = awmtk2.create_style("battery",awmtk2.default,args.style) + local templates = awmtk2.create_template_lib("battery",awmtk2.templates,args.templates) + local t = awmtk2.build_templates(templates,style) + -- set up popup layout + local layout = wibox.widget({ + layout = wibox.layout.fixed.vertical, + spacing = style.base.spacing + }) + -- create popup + local popup = awful.popup(t.popup(layout)) + -- create battery widget + local battery_widget = wibox.widget(t.button({ + { + image = beautiful["battery-missing-symbolic"], + resize = true, + widget = wibox.widget.imagebox, + id = "virtual_id" + }, + (args.percentage and { + markup = "0%", + id = "percentage_id", + widget = wibox.widget.textbox + }), + layout = wibox.layout.fixed.horizontal, + spacing = style.base.spacing + })) + battery_widget:connect_signal("button::press",style.button.onpress) + battery_widget:connect_signal("button::release",style.button.onrelease) + battery_widget:connect_signal("button::press",function(self,x,y,button) + if button == 1 then + popup.visible = (not popup.visible) + if popup.visible then + popup:move_next_to(mouse.current_widget_geometry) + end + end + end) + + local widget_map = {} + local percentage_map = {} + -- {{{ Power supply devices + local power_devices = syscontrol.power_supply.enumerate() + for _,device in pairs(power_devices) do + local data = syscontrol.power_supply.read_attribs(device) + if data.type == "Battery" then + widget_map[data.name] = wibox.widget(t.container({ + t.article({ + icon = get_virtual_icon({ + data.capacity, + charge = data.charging + }), + icon_id = "battery_icon", + title = "Battery ("..data.model..")", + }), + t.textbox({ + markup = ("Capacity: %d%%"):format(data.capacity), + id = "capacity_id" + }), + t.textbox({ + markup = ("Quality: %.4f%%"):format(data.quality), + id = "quality_id", + }), + layout = wibox.layout.fixed.vertical + })) + layout:add(widget_map[data.name]) + percentage_map[data.name] = data.capacity + elseif data.type == "Mains" then + widget_map[data.name] = wibox.widget(t.container({ + t.article({ + icon = beautiful["ac-adapter-symbolic"], + title = "Power supply", + }), + t.textbox({ + markup = "Powered: "..tostring(data.online), + id = "online_id", + }), + layout = wibox.layout.fixed.vertical + })) + layout:add(widget_map[data.name]) + percentage_map["charge"] = data.online + end + end + local function update_virtual_battery() + local icon = battery_widget:get_children_by_id("virtual_id")[1] + local percentage = battery_widget:get_children_by_id("percentage_id")[1] + local capacity + icon.image,capacity = get_virtual_icon(percentage_map) + if percentage then + percentage:set_markup(tostring(capacity).."%") + end + end + update_virtual_battery() + -- Update loop + gears.timer({ + timeout = args.power_polling or 2, + autostart = true, + callback = function() + for k,v in pairs(power_devices) do + local data,err = syscontrol.power_supply.read_attribs(v) + if data and data.type == "Mains" then + local w = widget_map[data.name] + local online = w:get_children_by_id("online_id")[1] + online:set_markup("Powered: "..tostring(data.online)) + percentage_map["charge"] = data.online + elseif data and data.type == "Battery" then + local w = widget_map[data.name] + local icon = w:get_children_by_id("battery_icon")[1] + local capacity = w:get_children_by_id("capacity_id")[1] + local quality = w:get_children_by_id("quality_id")[1] + icon.image = get_virtual_icon({ + data.capacity, + charge = data.charging + }) + capacity:set_markup(("Capacity: %d%%"):format(data.capacity)) + quality:set_markup(("Quality: %.4f%%"):format(data.quality)) + percentage_map[data.name] = data.capacity + else + print(err) + end + update_virtual_battery() + end + end + }) + -- }}} + -- {{{ Backlight + local backlight_devices = syscontrol.backlight.enumerate() + for k,v in pairs(backlight_devices) do + local data = syscontrol.backlight.read_attribs(v) + if data then + widget_map[data.name] = wibox.widget(t.container({ + t.article({ + icon = beautiful["backlight-symbolic"], + title = "Backlight", + }), + t.textbox({ + markup = "Brightness: "..tostring(data.brightness), + id = "brightness_id" + }), + t.textbox({ + markup = "Max brightness: "..tostring(data.max_brightness), + id = "max_brightness" + }), + (data.writable and t.slider({ + minimum = data.max_brightness*0.05, + maximum = data.max_brightness, + value = data.brightness, + id = "slider" + })), + layout = wibox.layout.fixed.vertical + })) + if data.writable then + local slider = widget_map[data.name]:get_children_by_id("slider")[1] + slider:connect_signal("widget::redraw_needed",function(self) + local value = self.value + syscontrol.backlight.set_brightness(data,math.floor(value)) + end) + end + layout:add(widget_map[data.name]) + end + end + -- Update loop + gears.timer({ + timeout = args.backlight_polling or 2, + autostart = true, + callback = function() + for k,v in pairs(backlight_devices) do + local data,err = syscontrol.backlight.read_attribs(v) + if data then + local w = widget_map[data.name] + local online = w:get_children_by_id("brightness_id")[1] + online:set_markup("Brightness: "..tostring(data.brightness)) + else + print(err) + end + end + end + }) + -- }}} + return battery_widget +end diff --git a/widgets/notifications.lua b/widgets/notifications.lua new file mode 100644 index 0000000..b25cb8a --- /dev/null +++ b/widgets/notifications.lua @@ -0,0 +1,69 @@ +local awful = require("awful") +local pager = require("pager") +local beautiful = require("beautiful") +local gears = require("gears") +local wibox = require("wibox") +local awmtk2 = require("awmtk2") +local dkjson = require("dkjson") +local naughty = require("naughty") + +return function(args) + local style = awmtk2.create_style("notifications",awmtk2.default,args.style) + local templates = awmtk2.create_template_lib("notifications",awmtk2.templates,args.templates) + local t = awmtk2.build_templates(templates,style) + local layout = wibox.widget({ + layout = wibox.layout.fixed.vertical, + spacing = style.base.spacing + }) + local test_xclip = os.execute("xclip -version") + local result = ((test_xclip ~= 0) or (test_xclip ~= true)) + local popup = awful.popup(t.popup({ + t.textbox({ + markup = "Click to copy text" + }), + ((not result) and t.textbox({ + markup = "(xclip is not installed)" + })), + t.container(layout), + layout = wibox.layout.fixed.vertical + },{ + visible = false + })) + naughty.config.notify_callback = function(update_args) + local w = wibox.widget(t.button(t.article({ + icon = update_args.icon, + title = update_args.title or "(No title)", + description = update_args.text + }),{ + forced_height = style.button.height, + forced_width = style.button.width + })) + w:connect_signal("button::press",style.button.onpress) + w:connect_signal("button::release",style.button.onrelease) + w:connect_signal("button::press",function(self) + clip = io.open("/tmp/clip","w") + clip:write(update_args.text) + clip:close() + awful.spawn.with_shell("cat /tmp/clip | xclip -selection clipboard") + end) + layout:insert(1,w) + return update_args + end + -- create popup button + local clip_widget = wibox.widget(t.button({ + image = beautiful["notifications-area-symbolic"], + resize = true, + widget = wibox.widget.imagebox, + })) + clip_widget:connect_signal("button::press",style.button.onpress) + clip_widget:connect_signal("button::release",style.button.onrelease) + clip_widget:connect_signal("button::press",function(self,x,y,button) + if button == 1 then + popup.visible = (not popup.visible) + if popup.visible then + popup:move_next_to(mouse.current_widget_geometry) + end + end + end) + return clip_widget +end diff --git a/widgets/rootmenu.lua b/widgets/rootmenu.lua index 7ecfe9b..ba58695 100644 --- a/widgets/rootmenu.lua +++ b/widgets/rootmenu.lua @@ -40,7 +40,6 @@ return function(args) } )).widget) for _,layout in pairs(root_menu.widget:get_children_by_id("menu_root")) do - print(layout) for _,button in pairs(layout.children) do button:connect_signal("cascade::kill",function() root_menu.visible = false diff --git a/widgets/wallpapers.lua b/widgets/wallpapers.lua new file mode 100644 index 0000000..d9b0d20 --- /dev/null +++ b/widgets/wallpapers.lua @@ -0,0 +1,113 @@ +local awful = require("awful") +local pager = require("pager") +local beautiful = require("beautiful") +local gears = require("gears") +local wibox = require("wibox") +local awmtk2 = require("awmtk2") +local thumbnailer = require("thumbnail") + +local function ls(path) + local ls_data = io.popen("find "..path.." -maxdepth 1 -type f \\( -name \\*.jpg -o -name \\*.png \\) -exec realpath {} \\;") + local output = {} + if ls_data then + ls_data:read("*a"):gsub("[^\n]+",function(capt) + table.insert(output,capt) + end) + ls_data:close() + return output + end + error("Failed to process directory "..path) +end + +return function(args) + local style = awmtk2.create_style("wallpapers",awmtk2.default,args.style) + local templates = awmtk2.create_template_lib("wallpapers",awmtk2.templates,args.templates) + local t = awmtk2.build_templates(templates,style) + -- set wallpaper + local fhandler = io.open(root_path.."/wallpaper.txt","r") + if fhandler then + local wallpaper_path = fhandler:read("*a") + gears.wallpaper.maximized(wallpaper_path,args.screen) + else + -- try to set wallpaper from theme settings + if beautiful.wallpaper then + gears.wallpaper.maximized(beautiful.wallpaper,args.screen) + end + end + -- update wallpaper preference and set wallpaper + local function set_wallpaper(s) + local handler = io.open(root_path.."/wallpaper.txt","w") + handler:write(s) + handler:close() + gears.wallpaper.maximized(s,args.screen) + end + if not args.path then + args.path = os.getenv("HOME").."/Pictures" + end + -- read wallpapers from wallpaper directory + local image_list = ls(args.path) + -- generate thumbnails to save memory + thumbnailer.generate(args.path,args.path.."/.thumbnails",60) + -- create a layout for wallpaper buttons + local layout = wibox.widget({ + layout = wibox.layout.grid, + forced_num_cols = args.columns or 4, + homogenous = true, + expand = true, + orientation = "vertical", + spacing = style.base.spacing + }) + -- set up a pager for having multiple pages of wallpapers + local pager = pager(layout,{},((args.rows and args.columns) and args.rows*args.columns) or 20) + -- add wallpaper buttons + for k,v in pairs(image_list) do + local new_button = wibox.widget(t.button(t.center({ + image = v, + resize = true, + widget = wibox.widget.imagebox + },{ + height = args.height or 60, + width = args.width or 100 + }))) + new_button:connect_signal("button::press",style.button.onpress) + new_button:connect_signal("button::release",style.button.onrelease) + new_button:connect_signal("button::press",function(self,x,y,button) + if button == 1 then + set_wallpaper(v) + elseif button == 4 then + pager:prev() + elseif button == 5 then + pager:next() + end + end) + table.insert(pager.list,new_button) + end + -- update pager + pager:update() + -- create layout popup + local popup = awful.popup(t.popup(t.container(layout),{ + visible = false + })) + popup:connect_signal("button::press",function(self,x,y,button) + if button == 3 then + popup.visible = false + end + end) + -- create popup button + local clip_widget = wibox.widget(t.button({ + image = beautiful.wallpapers_icon, + resize = true, + widget = wibox.widget.imagebox + })) + clip_widget:connect_signal("button::press",style.button.onpress) + clip_widget:connect_signal("button::release",style.button.onrelease) + clip_widget:connect_signal("button::press",function(self,x,y,button) + if button == 1 then + popup.visible = (not popup.visible) + if popup.visible then + popup:move_next_to(mouse.current_widget_geometry) + end + end + end) + return clip_widget +end