diff --git a/config/awesome/elemental/window_switcher.lua b/config/awesome/elemental/window_switcher.lua new file mode 100644 index 0000000..b104ce9 --- /dev/null +++ b/config/awesome/elemental/window_switcher.lua @@ -0,0 +1,274 @@ +local awful = require("awful") +local wibox = require("wibox") +local beautiful = require("beautiful") +local helpers = require("helpers") +local keys = require("keys") + +-- TODO ability to switch to minimized clients: +-- Might need to ditch the "easy" tasklist approach for something manual + +local window_switcher_margin = dpi(10) +local item_size = dpi(50) + +-- Set up text symbols and colors to be used instead of awful.widget.clienticon +-- Based on the client's `class` property +local class_icons = { + -- Terminals + ['kitty'] = { symbol = "", color = x.color5 }, + ['Alacritty'] = { symbol = "", color = x.color5 }, + ['Termite'] = { symbol = "", color = x.color5 }, + ['URxvt'] = { symbol = "", color = x.color5 }, + ['st'] = { symbol = "", color = x.color5 }, + ['st-256color'] = { symbol = "", color = x.color5 }, + + -- Image viewers + ['feh'] = { symbol = "", color = x.color1 }, + ['Sxiv'] = { symbol = "", color = x.color1 }, + + -- General + ['TelegramDesktop'] = { symbol = "", color = x.color4 }, + ['Firefox'] = { symbol = "", color = x.color3 }, + ['Chromium'] = { symbol = "", color = x.color4 }, + ['Chromium-browser'] = { symbol = "", color = x.color4 }, + ['Steam'] = { symbol = "", color = x.color2 }, + ['Lutris'] = { symbol = "", color = x.color6 }, + ['editor'] = { symbol = "", color = x.color5 }, + ['Emacs'] = { symbol = "", color = x.color5 }, + ['email'] = { symbol = "", color = x.color6 }, + ['music'] = { symbol = "", color = x.color6 }, + ['mpv'] = { symbol = "", color = x.color6 }, + ['KeePassXC'] = { symbol = "", color = x.color1 }, + ['Gucharmap'] = { symbol = "", color = x.color6 }, + ['Pavucontrol'] = { symbol = "", color = x.color4 }, + ['htop'] = { symbol = "", color = x.color2 }, + ['Screenruler'] = { symbol = "", color = x.color3 }, + ['Galculator'] = { symbol = "", color = x.color2 }, + + -- File managers + ['Thunar'] = { symbol = "", color = x.color3 }, + ['Nemo'] = { symbol = "", color = x.color3 }, + + ['Gimp'] = { symbol = "", color = x.color4 }, + ['Inkscape'] = { symbol = "", color = x.color2 }, + ['Gpick'] = { symbol = "", color = x.color6 }, + + -- Default + ['_'] = { symbol = "", color = x.color7.."99" } +} + +awful.screen.connect_for_each_screen(function(s) + s.window_switcher = wibox({ + width = dpi(500), + height = dpi(100), -- Will change dynamically + visible = false, + ontop = true, + screen = s, + bg = "#00000000", + fg = x.foreground + }) + awful.placement.centered(s.window_switcher, { honor_workarea = true, honor_padding = true }) + + -- Tasklist + s.window_switcher_tasklist = awful.widget.tasklist { + screen = s, + filter = awful.widget.tasklist.filter.currenttags, + buttons = keys.tasklist_buttons, + style = { + font = beautiful.tasklist_font, + -- font = "sans 10 medium", + bg = x.color0, + }, + layout = { + layout = wibox.layout.fixed.vertical + }, + widget_template = { + { + -- Standard icon (from beautiful.icon_theme) + -- { + -- awful.widget.clienticon, + -- margins = 5, + -- widget = wibox.container.margin + -- }, + -- Text icon + { + id = 'text_icon', + font = 'icomoon 25', + forced_width = dpi(50), + align = "center", + valign = "center", + widget = wibox.widget.textbox, + }, + { + { + id = 'text_role', + align = "center", + widget = wibox.widget.textbox, + }, + left = dpi(6), + right = dpi(14), + -- Add margins to top and bottom in order to force the + -- text to be on a single line, if needed. Might need + -- to adjust them according to font size. + top = dpi(14), + bottom = dpi(14), + widget = wibox.container.margin + }, + layout = wibox.layout.fixed.horizontal + }, + forced_height = item_size, + id = "bg_role", + widget = wibox.container.background, + create_callback = function(self, c, _, __) + local i + if class_icons[c.class] then + i = c.class + else + i = '_' + end + self:get_children_by_id('text_icon')[1].markup = helpers.colorize_text(class_icons[i].symbol, class_icons[i].color) + end, + }, + } + + s.window_switcher:setup { + { + s.window_switcher_tasklist, + margins = window_switcher_margin, + widget = wibox.container.margin + }, + bg = x.color0, + shape = helpers.rrect(beautiful.border_radius), + widget = wibox.container.background + } +end) + +-- Keygrabber configuration +local window_switcher_grabber +local function window_switcher_hide() + -- Add currently focused client to history + if client.focus then + awful.client.focus.history.add(client.focus) + client.focus:raise() + end + -- Resume recording focus history + awful.client.focus.history.enable_tracking() + -- Stop and hide window_switcher + local s = awful.screen.focused() + awful.keygrabber.stop(window_switcher_grabber) + s.window_switcher.visible = false +end + +-- Helper functions for keybinds +local window_search = function() + window_switcher_hide() + awful.spawn.with_shell("rofi -show windowcd") +end + +local function get_num_clients(s) + local clients = s.selected_tag:clients() + local num_clients = 0 + for _, c in pairs(clients) do + if not c.skip_taskbar then + num_clients = num_clients + 1 + end + end + + return num_clients +end + +local close = function() + if client.focus then + client.focus:kill() + local s = awful.screen.focused() + local num_clients = get_num_clients(s) + -- Hide or adjust size and position if needed + if num_clients > 1 then + s.window_switcher.height = s.window_switcher.height - item_size + awful.placement.centered(s.window_switcher, { honor_workarea = true, honor_padding = true }) + else + window_switcher_hide() + end + end +end + +local unminimize = function() + local c = awful.client.restore() + -- Focus restored client + if c then + client.focus = c + c:raise() + end +end + +local cycle = function(direction) + awful.client.focus.byidx(direction) + if client.focus then + client.focus:raise() + end +end + +-- Set up keybinds +-- Single keys only +local keybinds = { + ['Escape'] = window_switcher_hide, + ['Tab'] = function() cycle(1) end, + -- (Un)Minimize + ['n'] = function() if client.focus then client.focus.minimized = true end end, + ['N'] = unminimize, + ['u'] = unminimize, -- `u` for up + -- Close + ['d'] = close, + ['q'] = close, + -- Move with vim keys + ['j'] = function() cycle(1) end, + ['k'] = function() cycle(-1) end, + -- Move with arrow keys + ['Down'] = function() cycle(1) end, + ['Up'] = function() cycle(-1) end, + -- Space + [' '] = window_search +} + +function window_switcher_show(s) + local num_clients = get_num_clients(s) + + if num_clients == 0 or (num_clients == 1 and client.focus) then + return + end + + -- Adjust size and position + s.window_switcher.height = num_clients * item_size + window_switcher_margin * 2 + awful.placement.centered(s.window_switcher, { honor_workarea = true, honor_padding = true }) + + -- Go to previously focused client (in the tag) + awful.client.focus.history.previous() + if client.focus then + client.focus:raise() + end + + -- Stop recording focus history + awful.client.focus.history.disable_tracking() + + -- Start the keygrabber + window_switcher_grabber = awful.keygrabber.run(function(modifiers, key, event) + if event == "release" then + -- Hide if the modifier was released + -- We try to match Super or Alt or Control since we do not know which keybind is + -- used to activate the window switcher (the keybind is set by the user in keys.lua) + if key:match("Super") or key:match("Alt") or key:match("Control") then + window_switcher_hide() + end + -- Do nothing + return + end + + -- Run function attached to key, if it exists + if keybinds[key] then + keybinds[key]() + end + end) + + -- Finally make the window switcher wibox visible + s.window_switcher.visible = true +end + diff --git a/config/awesome/keys.lua b/config/awesome/keys.lua index c8b67a6..9c6fee7 100644 --- a/config/awesome/keys.lua +++ b/config/awesome/keys.lua @@ -131,19 +131,25 @@ keys.globalkeys = gears.table.join( end, {description = "focus right", group = "client"}), - -- Focus client by index (cycle through clients) + -- Window switcher awful.key({ superkey }, "Tab", function () - awful.client.focus.byidx( 1) + window_switcher_show(awful.screen.focused()) end, - {description = "focus next by index", group = "client"} - ), - awful.key({ superkey, shiftkey }, "Tab", + {description = "activate window switcher", group = "client"}), + + -- Focus client by index (cycle through clients) + awful.key({ superkey }, "z", + function () + awful.client.focus.byidx(1) + end, + {description = "focus next by index", group = "client"}), + + awful.key({ superkey, shiftkey }, "z", function () awful.client.focus.byidx(-1) end, - {description = "focus previous by index", group = "client"} - ), + {description = "focus next by index", group = "client"}), -- Gaps awful.key({ superkey, shiftkey }, "minus", @@ -217,14 +223,6 @@ keys.globalkeys = gears.table.join( end, {description = "jump to urgent client", group = "client"}), - awful.key({ superkey, }, "z", - function () - awful.client.focus.history.previous() - if client.focus then - client.focus:raise() - end - end, - {description = "go back", group = "client"}), awful.key({ superkey, }, "x", function () awful.tag.history.restore() @@ -727,10 +725,8 @@ keys.clientkeys = gears.table.join( -- Minimize awful.key({ superkey, }, "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 , + end, {description = "minimize", group = "client"}), -- Maximize diff --git a/config/awesome/rc.lua b/config/awesome/rc.lua index 093632c..1ee49cd 100644 --- a/config/awesome/rc.lua +++ b/config/awesome/rc.lua @@ -202,6 +202,8 @@ require("elemental.dashboard") require("elemental.lock_screen") -- App drawer require("elemental.app_drawer") +-- Window switcher +require("elemental.window_switcher") -- >> Daemons -- Most widgets that display system/external info depend on evil.