diff --git a/configuration/init.lua b/configuration/init.lua index 30b897b..b40f2e0 100644 --- a/configuration/init.lua +++ b/configuration/init.lua @@ -12,6 +12,8 @@ rc_configuration.menu = require 'rc.configuration.menu' rc_configuration.rules = require 'rc.configuration.rules' +rc_configuration.prompt_commands = require 'rc.configuration.prompt_commands' + rc_configuration.tag_layouts = require 'rc.configuration.tag_layouts' return rc_configuration diff --git a/configuration/prompt_commands.lua b/configuration/prompt_commands.lua new file mode 100644 index 0000000..40ca2d8 --- /dev/null +++ b/configuration/prompt_commands.lua @@ -0,0 +1,50 @@ +local atag = require 'awful.tag' +local layout_suit = require 'awful.layout.suit' + +local commands = {} + +commands['o'] = { + callback = function (parameters) + local tag_name = parameters[1] or 'New-Tag' + + atag.add(tag_name, { + layout = layout_suit.tile + }):view_only() + end +} + +commands['O'] = { + callback = function (parameters) + local aspawn = require 'awful.spawn' + + local application = parameters[1] + local tag_name = parameters[2] or application + + local t = atag.add(tag_name, { + layout = layout_suit.tile, + volatile = true, + }) + t:view_only() + aspawn(application, { tag = t }) + end +} + +commands['q'] = { + callback = function () + local ascreen = require 'awful.screen' + + local tags = ascreen.focused().selected_tags + + for _,tag in ipairs(tags) do + tag.volatile = true + + for _,client in ipairs(tag:clients()) do + client:kill() + end + + tag:delete() + end + end +} + +return commands diff --git a/theme/assets/icons/apps.svg b/theme/assets/icons/apps.svg new file mode 100644 index 0000000..479fbca --- /dev/null +++ b/theme/assets/icons/apps.svg @@ -0,0 +1 @@ + diff --git a/theme/assets/icons/battery-outline.svg b/theme/assets/icons/battery-outline.svg new file mode 100644 index 0000000..85cb00b --- /dev/null +++ b/theme/assets/icons/battery-outline.svg @@ -0,0 +1 @@ + diff --git a/theme/init.lua b/theme/init.lua index bcb47f7..12a182f 100644 --- a/theme/init.lua +++ b/theme/init.lua @@ -7,13 +7,52 @@ local icons_dir = assets_dir .. 'icons/' local theme = {} -theme.border_color_normal = '#0000ff' +--- Basic +theme.font = 'Noto Mono 9' --- Tags theme.hometag_master_width_factor = 0.65 --- Icons theme.icon_hometag = icons_dir .. 'home-circle.svg' +theme.icon_apps = icons_dir .. 'apps.svg' +theme.icon_battery_outline = icons_dir .. 'battery-outline.svg' + +--- Wibar +-- My wibar is a transparent dock at the bottom of the screen. +-- Height and width ahve to be set by the constructor to use screen DPI and +-- screen percentage values. +theme.wibar_stretch = false +theme.wibar_border_width = 0 +theme.wibar_border_color = nil +theme.wibar_ontop = false +theme.wibar_opacity = 1 +theme.wibar_type = 'dock' +theme.wibar_bg = '#0000' + +--- Spacing and margins +-- Naming convention: +-- __ +theme.spacing = 8 +theme.margin = 4 +theme.padding = 4 + +theme.bar_height = 32 + (theme.margin * 2) -- This is the outer size of the bar +theme.bar_width = '80%' -- Make the bar size relative to the screen +theme.bar_margin_x = theme.margin * 2 +theme.bar_margin_y = theme.margin +theme.bar_padding_x = theme.padding * 2 +theme.bar_padding_y = 0 + +theme.bar_box_margin_x = 0 +theme.bar_box_margin_y = theme.margin +theme.bar_box_padding_x = theme.padding * 2 +theme.bar_box_padding_y = theme.padding +theme.bar_box_spacing = theme.spacing + +--- Widgets specific +theme.bg_systray = '#455A64' +theme.systray_icon_spacing = 3 --- Wallpaper theme.wallpaper = function (screen) diff --git a/ui/desktop_decoration/bar.lua b/ui/desktop_decoration/bar.lua index 0f12403..264982f 100644 --- a/ui/desktop_decoration/bar.lua +++ b/ui/desktop_decoration/bar.lua @@ -1,27 +1,92 @@ local awibar = require 'awful.wibar' local awful = require 'awful' +local battery_widget = require 'battery-widget' local beautiful = require 'beautiful' -local wibox = require 'wibox' +local container_background = require 'wibox.container.background' +local container_margin = require 'wibox.container.margin' +local container_place = require 'wibox.container.place' + +local gshape = require 'gears.shape' + +local layout_align = require 'wibox.layout.align' +local layout_fixed = require 'wibox.layout.fixed' + +local systray = require 'wibox.widget.systray' +local textclock = require 'wibox.widget.textclock' +local widget = require 'wibox.widget' + +local mybattery = require 'rc.ui.desktop_decoration.widgets.battery' +local mycommands = require 'rc.configuration.prompt_commands' local mymainmenu = require 'rc.ui.menu.mymainmenu' +local myprompt = require 'rc.ui.desktop_decoration.widgets.prompt' +local mytaglist = require 'MyTagListWidget' local capi = { - client = _G.client, screen = _G.screen } -local modkey = 'Mod4' +local abs = math.abs +local dpi = beautiful.xresources.apply_dpi -local bar = { _private = { instances = {} }, mt = {} } - -local function get_screen_id(screen) - local s = capi.screen[screen] or screen +local function get_screen_id (screen) + local s = capi.screen[screen or 1] return s.index end --- Get the bar instance for a given screen -function bar:instance(screen) +--- Build a widget box in the bar. +-- A widget box is a combinaision of "shaped" `wibox.container/background` and +-- `wibox.container.margin` to create this nice round-colored-buttons I like. +-- @tparam args table +-- @tparam args.screen The screen where the widget will be drawn. +-- @tparam args.widget The widget to draw. +-- @tparam[opt] args.bg The box's background. +-- @tpram[opt] args.fg The box's foreground (used mainly for textbox text color). +-- @treturn wibox.container.background The builded widget. +local function build_widget (args) + local screen_id = get_screen_id(args.screen) + + -- Callback for the shape function. + -- If the widget is almost a square: draw a circle. Otherwise, draw a + -- rounded_bar. + local shape_callback = function (cr, width, height) + local shape = gshape.circle + + -- 10 is an arbitrary value I found after some tests :shrug: + if abs(width - height) > 10 then + shape = gshape.rounded_bar + end + + return shape(cr, width, height) + end + + local box_widget = widget { + widget = container_background, + shape = shape_callback, + bg = args.bg, + fg = args.fg, + { + widget = container_margin, + draw_empty = false, + top = dpi(beautiful.bar_box_padding_y, screen_id), + bottom = dpi(beautiful.bar_box_padding_y, screen_id), + right = dpi(beautiful.bar_box_padding_x, screen_id), + left = dpi(beautiful.bar_box_padding_x, screen_id), + args.widget + } + } + + return box_widget +end + +local bar = { _private = { instances = {} }, mt = {} } + +--- Get the bar instance for a given screen. +-- If no instance was found, we build a new one. +-- @tparam screen screen|integer The bar's screen. +-- @treturn wibox.wibar +function bar:instance (screen) local screen_id = get_screen_id(screen) local instance = self._private.instances[screen_id] @@ -36,94 +101,124 @@ end function bar.new (screen) local my_bar = {} + my_bar.launcher = build_widget { + screen = screen, + bg = '#2196F3', + widget = awful.widget.launcher { + image = beautiful.icon_apps, + menu = mymainmenu() + } + } + + my_bar.textclock = build_widget { + bg = '#FF5722', + fg = '#ECEFF1', + widget = textclock('%l:%M %p') + } + + my_bar.promptbox = myprompt { + commands = mycommands + } + + -- This widget needs to be reworded and integrated inside the project. + my_bar.taglist = mytaglist.new { + screen = screen + } + + my_bar.battery = build_widget { + screen = screen, + bg = '#673AB7', + widget = mybattery { + screen = screen, + device_path = battery_widget.get_BAT0_device_path(), + color = '#ECEFF1' + } + } + + my_bar.systray = build_widget { + screen = screen, + bg = '#455A64', + widget = systray() + } + my_bar.wibar = awibar { screen = screen, - position = 'top' - } - - my_bar.launcher = awful.widget.launcher { - image = beautiful.awesome_icon, - menu = mymainmenu() - } - - my_bar.keyboardlayout = awful.widget.keyboardlayout() - - my_bar.textclock = wibox.widget.textclock() - - my_bar.promptbox = awful.widget.prompt() - - my_bar.layoutbox = awful.widget.layoutbox { - screen = screen, - buttons = { - 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), - } - } - - my_bar.taglist = awful.widget.taglist { - screen = screen, - filter = awful.widget.taglist.filter.all, - buttons = { - awful.button({}, 1, function(tag) - tag:view_only() - end), - awful.button({ modkey }, 1, function(tag) - if capi.client.focus then - capi.client.focus:move_to_tag(tag) - end - end), - awful.button({}, 3, awful.tag.viewtoggle), - awful.button({ modkey }, 3, function(tag) - if capi.client.focus then - capi.client.focus:toggle_tag(tag) - end - end), - awful.button({ }, 4, function(tag) - awful.tag.viewprev(tag.screen) - end), - awful.button({ }, 5, function(tag) - awful.tag.viewnext(tag.screen) - end) - } - } - - my_bar.tasklist = awful.widget.tasklist { - screen = screen, - filter = awful.widget.tasklist.filter.currenttags, - buttons = { - awful.button({ }, 1, function (c) - c:activate { context = "tasklist", action = "toggle_minimization" } - 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), - } + position = 'bottom', + height = dpi(beautiful.bar_height, screen), + width = beautiful.bar_width -- Width is a percentage of the screen size } my_bar.wibar:setup { - layout = wibox.layout.align.horizontal, - { -- Left widgets - layout = wibox.layout.fixed.horizontal, - my_bar.launcher, - my_bar.taglist, - my_bar.promptbox, - }, - screen.mytasklist, -- Middle widget - { -- Right widgets - layout = wibox.layout.fixed.horizontal, - my_bar.keyboardlayout, - wibox.widget.systray(), - my_bar.textclock, - my_bar.layoutbox, + -- Bar margins + widget = container_margin, + top = dpi(beautiful.bar_margin_y, screen), + bottom = dpi(beautiful.bar_margin_y, screen), + right = dpi(beautiful.bar_margin_x, screen), + left = dpi(beautiful.bar_margin_x, screen), + { + -- Physical bar + widget = container_background, + bg = '#263238D9', + shape = function (cr, width, height) + gshape.rounded_rect(cr, width, height, 10) + end, + { + -- Bar paddings + widget = container_margin, + top = dpi(beautiful.bar_padding_y, screen), + bottom = dpi(beautiful.bar_padding_y, screen), + right = dpi(beautiful.bar_padding_x, screen), + left = dpi(beautiful.bar_padding_x, screen), + { + -- Bar content + layout = layout_align.horizontal, + { + -- Left side of the bar, box margins + widget = container_margin, + top = dpi(beautiful.bar_box_margin_y, screen), + bottom = dpi(beautiful.bar_box_margin_y, screen), + right = dpi(beautiful.bar_box_margin_x, screen), + left = dpi(beautiful.bar_box_margin_x, screen), + { + -- Left widget boxes + layout = layout_fixed.horizontal, + spacing = dpi(beautiful.bar_box_spacing, screen), + my_bar.launcher, + my_bar.promptbox + } + }, + + -- Middle widget is the custom taglist + -- it doesn't need box/margins/... + { + widget = container_place, + my_bar.taglist + }, + { + -- Right side of the bar, box margins + widget = container_margin, + top = dpi(beautiful.bar_box_margin_y, screen), + bottom = dpi(beautiful.bar_box_margin_y, screen), + right = dpi(beautiful.bar_box_margin_x, screen), + left = dpi(beautiful.bar_box_margin_x, screen), + { + -- Right widget boxes + layout = layout_fixed.horizontal, + spacing = dpi(beautiful.bar_box_spacing, screen), + my_bar.systray, + my_bar.battery, + my_bar.textclock + } + } + } + } } } return my_bar end -function bar.mt:__call(...) +function bar.mt:__call (...) return self:instance(...) end diff --git a/ui/desktop_decoration/widgets/battery.lua b/ui/desktop_decoration/widgets/battery.lua new file mode 100644 index 0000000..547a665 --- /dev/null +++ b/ui/desktop_decoration/widgets/battery.lua @@ -0,0 +1,100 @@ +--------------------------------------------------------------------------- +-- An implementation of my battery_widget widget with a dynamicly generated +-- icon based on the battery percentage level. +-- +-- Find more about my battery_widget widget at +-- https://github.com/Aire-One/awesome-battery_widget +-- +-- @author Aire-One +-- @copyright 2020 Aire-One +--------------------------------------------------------------------------- +local lgi = require 'lgi' +local cairo = lgi.cairo +local rsvg = lgi.Rsvg + +local atooltip = require 'awful.tooltip' +local beautiful = require 'beautiful' +local gcolor = require 'gears.color' +local imagebox = require 'wibox.widget.imagebox' + +local battery_widget = require 'battery-widget' + +local my_battery = {} +local mt = {} + +--- Generate a drawing for the battery icon. +-- @tparam number percentage The percentage of battery remaining. +-- @tparam gears.color|string color The color of the drawing. +-- @treturn cairo.ImageSurface The generated surface for the drawing. +function my_battery.draw_battery (percentage, color) + local svg_handle = rsvg.Handle.new_from_file(beautiful.icon_battery_outline) + if not svg_handle then return end + local surface = cairo.ImageSurface.create(cairo.Format.ARGB32, 24, 24) + local cr = cairo.Context(surface) + svg_handle:render_cairo(cr) + + local max_height = 14 + local x = 8 + local width = 8 + local height = percentage / 100 * max_height + local y = 6 + max_height - height + + cr:set_source(gcolor(color)) + cr:rectangle(x, y, width, height) + cr:fill() + + return surface +end + +--- Update handler for the battery widget. This is the function called +-- by the signal system on battery update. +-- @tparam my_battery widget The battery widget to update. +-- @tparam UPowerGLib.Device device The UPower device to monitor. +function my_battery.update (widget, device) + widget.image = my_battery.draw_battery(device.percentage, widget.color) +end + +--- Give the widget template for the battery widget. +-- (It's a basic `wibox.widget.imagebox` for my battery implementation) +function my_battery.widget_template () + local widget = imagebox() + widget.resize = true + + return widget +end + +--- Constructor of my battery widget! +-- @tparam table args Table of parameters. +-- @tparam screen|number args.screen The screen of the wodget. +-- @tparam string args.device_path The path of the UPower device to monitor. +-- @tparam gears.color|string args.color Color to use to draw the battery. +-- @treturn my_battery The instantiated widget. +function my_battery.new (args) + local widget = battery_widget { + screen = args.screen, + device_path = args.device_path, + widget_template = my_battery.widget_template(), + instant_update = true + } + + widget.color = args.color + + widget:connect_signal('upower::update', function (w, device) + my_battery.update(w, device) + end) + + widget.tooltip = atooltip { + objects = { widget }, + timer_function = function () + return string.format('%3d', widget.device.percentage) .. '%' + end + } + + return widget +end + +function mt.__call (self, ...) + return my_battery.new(...) +end + +return setmetatable(my_battery, mt) diff --git a/ui/desktop_decoration/widgets/prompt.lua b/ui/desktop_decoration/widgets/prompt.lua new file mode 100644 index 0000000..69017e1 --- /dev/null +++ b/ui/desktop_decoration/widgets/prompt.lua @@ -0,0 +1,67 @@ +local acompletion = require 'awful.completion' +local aspawn = require 'awful.spawn' +local gstring = require 'gears.string' +local gtable = require 'gears.table' +local prompt = require 'awful.widget.prompt' + +local string = string + +local function completion_cb (self, command_before_comp, cur_pos_before_comp, ncomp) + return acompletion.generic( + command_before_comp, cur_pos_before_comp, ncomp, + self.completion_keywords) +end + +local function exe_cb (self, input) + -- Exit if the input is empty + if not input or #input == 0 then + return + end + + -- Trim + input = string.gsub(input, "^%s*(.-)%s*$", "%1") + + -- If the input is not a VI command, spawn it + if input:sub(1,1) ~= ':' then + aspawn(input) + return + end + + -- Parse the custom command + local command, parameters = input:gmatch(':([%w-]+)%s*(.*)')() + command = command:sub(1,1) + parameters = gstring.split(parameters, '%s') + + -- Quit if the command doesn't exist + if not self.commands[command] then + print('":' .. command .. '" not reconized as a command') + return + end + + self.commands[command].callback(parameters) +end + +local my_prompt = { mt = {} } + +function my_prompt.new (args) + local prompt_widget = nil + prompt_widget = prompt { + prompt = ' ', -- needs the 3 spaces + completion_callback = function (...) return completion_cb(prompt_widget, ...) end, + exe_callback = function (...) return exe_cb(prompt_widget, ...) end + } + + prompt_widget.commands = args.commands + prompt_widget.completion_keywords = gtable.join( + -- TODO: find applications list + gtable.find_keys(args.commands, function () return true end, false), + args.completion_keywords or {}) + + return prompt_widget +end + +function my_prompt.mt.__call (self, ...) + return my_prompt.new(...) +end + +return setmetatable(my_prompt, my_prompt.mt)