local wibox = require("wibox") local awful = require("awful") local gears = require("gears") local Gio = require("lgi").Gio local iconTheme = require("lgi").require("Gtk", "3.0").IconTheme.get_default() local beautiful = require("beautiful") local helpers = require("helpers") local animation = require("mods.animation") local dpi = beautiful.xresources.apply_dpi awful.screen.connect_for_each_screen(function(s) local launcherdisplay = wibox { width = dpi(565), shape = helpers.rrect(12), height = dpi(620), bg = beautiful.bg, ontop = true, visible = false } local prompt = wibox.widget { { image = helpers.cropSurface(3.42, gears.surface.load_uncached(beautiful.wallpaper)), opacity = 0.9, forced_height = dpi(140), clip_shape = helpers.rrect(10), forced_width = dpi(480), widget = wibox.widget.imagebox }, { { { { { markup = "", forced_height = 15, id = "txt", font = "Rubik 14", widget = wibox.widget.textbox, }, { markup = "Search...", forced_height = 15, id = "placeholder", font = "Rubik 14", widget = wibox.widget.textbox, }, layout = wibox.layout.stack }, widget = wibox.container.margin, margins = 20, }, forced_width = 300, shape = helpers.rrect(8), widget = wibox.container.background, bg = beautiful.mbg }, widget = wibox.container.place, halign = "center", valgn = "center", }, layout = wibox.layout.stack } local entries = wibox.widget { homogeneous = false, expand = false, forced_num_cols = 1, spacing = 4, layout = wibox.layout.grid } local createPowerButton = function(icon, color, command) return wibox.widget { { { { markup = helpers.colorizeText(icon, color), align = "center", font = beautiful.icon .. " 20", widget = wibox.widget.textbox, }, margins = 14, widget = wibox.container.margin }, widget = wibox.container.background, bg = color .. '11', shape = helpers.rrect(4) }, widget = wibox.container.place, halign = "center", buttons = { awful.button({}, 1, function() awesome.emit_signal("quit::search") awesome.emit_signal("quit::launcher") awful.spawn.with_shell(command) end) }, } end launcherdisplay:setup { { { { { { widget = wibox.widget.imagebox, image = beautiful.nixos, forced_height = 40, forced_width = 40, resize = true, }, widget = wibox.container.place, halign = "center" }, widget = wibox.container.margin, top = 15 }, nil, { { createPowerButton("󰐥", beautiful.red, "poweroff"), createPowerButton("󰌾", beautiful.blue, "lock"), createPowerButton("󰦛", beautiful.green, "reboot"), spacing = 10, layout = wibox.layout.fixed.vertical }, widget = wibox.container.margin, margins = 10, }, layout = wibox.layout.align.vertical }, widget = wibox.container.background, bg = beautiful.mbg }, { { prompt, spacing = 10, entries, layout = wibox.layout.fixed.vertical }, left = 10, right = 10, bottom = 10, top = 10, widget = wibox.container.margin }, nil, spacing = 0, layout = wibox.layout.align.horizontal } -- Functions local function next(entries) if index_entry ~= #filtered then index_entry = index_entry + 1 if index_entry > index_start + 5 then index_start = index_start + 1 end end end local function back(entries) if index_entry ~= 1 then index_entry = index_entry - 1 if index_entry < index_start then index_start = index_start - 1 end end end local function gen() local entries = {} for _, entry in ipairs(Gio.AppInfo.get_all()) do if entry:should_show() then local name = entry:get_name():gsub("&", "&"):gsub("<", "<"):gsub("'", "'") local icon = entry:get_icon() local path if icon then path = icon:to_string() if not path:find("/") then local icon_info = iconTheme:lookup_icon(path, dpi(48), 0) local p = icon_info and icon_info:get_filename() path = p end end table.insert( entries, { name = name, appinfo = entry, icon = path or '' } ) end end return entries end local function filter(cmd) filtered = {} regfiltered = {} -- Filter entries for _, entry in ipairs(unfiltered) do if entry.name:lower():sub(1, cmd:len()) == cmd:lower() then table.insert(filtered, entry) elseif entry.name:lower():match(cmd:lower()) then table.insert(regfiltered, entry) end end -- Sort entries table.sort(filtered, function(a, b) return a.name:lower() < b.name:lower() end) table.sort(regfiltered, function(a, b) return a.name:lower() < b.name:lower() end) -- Merge entries for i = 1, #regfiltered do filtered[#filtered + 1] = regfiltered[i] end -- Clear entries entries:reset() -- Add filtered entries for i, entry in ipairs(filtered) do local widget = wibox.widget { { { { image = entry.icon, clip_shape = helpers.rrect(10), forced_height = dpi(32), forced_width = dpi(32), valign = 'center', widget = wibox.widget.imagebox }, { markup = entry.name, id = "name", font = beautiful.sans .. " 12", widget = wibox.widget.textbox }, spacing = 20, layout = wibox.layout.fixed.horizontal, }, margins = dpi(15), widget = wibox.container.margin }, forced_width = 470, forced_height = 72, widget = wibox.container.background } if index_start <= i and i <= index_start + 5 then entries:add(widget) end if i == index_entry then widget.bg = beautiful.blue .. "09" widget:get_children_by_id("name")[1].markup = helpers.colorizeText(entry.name, beautiful.blue) end end -- Fix position if index_entry > #filtered then index_entry, index_start = 1, 1 elseif index_entry < 1 then index_entry = 1 end collectgarbage("collect") end local exclude = { "Shift_R", "Shift_L", "Super_R", "Super_L", "Tab", "Alt_R", "Alt_L", "Ctrl_L", "Ctrl_R", "CapsLock", "Home", "Down", "Up", "Left", "Right" } local function has_value(tab, val) for _, value in ipairs(tab) do if value == val then return true end end return false end local prompt_grabber = awful.keygrabber({ auto_start = true, stop_event = "release", keypressed_callback = function(self, mod, key, command) local addition = '' if key == "Escape" then awesome.emit_signal("quit::search") awesome.emit_signal("quit::launcher") elseif key == "BackSpace" then prompt:get_children_by_id('txt')[1].markup = prompt:get_children_by_id('txt')[1].markup:sub(1, -2) filter(prompt:get_children_by_id('txt')[1].markup) elseif key == "Return" then local entry = filtered[index_entry] if entry then entry.appinfo:launch() else awful.spawn.with_shell(prompt:get_children_by_id('txt')[1].markup) end awesome.emit_signal("quit::search") awesome.emit_signal("quit::launcher") elseif key == "Up" then back(entries) elseif key == "Down" then next(entries) elseif has_value(exclude, key) then addition = '' else addition = key end prompt:get_children_by_id('txt')[1].markup = prompt:get_children_by_id('txt')[1].markup .. addition filter(prompt:get_children_by_id('txt')[1].markup) if string.len(prompt:get_children_by_id('txt')[1].markup) > 0 then prompt:get_children_by_id('placeholder')[1].markup = '' else prompt:get_children_by_id('placeholder')[1].markup = 'Search...' end end, }) awesome.connect_signal("toggle::search", function() prompt_grabber:start() end) awesome.connect_signal("quit::search", function() prompt_grabber:stop() prompt:get_children_by_id('txt')[1].markup = "" end) local function open() -- Reset index and page index_start, index_entry = 1, 1 -- Get entries unfiltered = gen() filter("") -- Prompt awesome.emit_signal("toggle::search") end awesome.connect_signal("quit::launcher", function() launcherdisplay.visible = false end) awesome.connect_signal("toggle::launcher", function() open() if launcherdisplay.visible then awesome.emit_signal("quit::search") end launcherdisplay.visible = not launcherdisplay.visible awful.placement.bottom_left( launcherdisplay, { honor_workarea = true, margins = { bottom = 20, left = 20 } } ) end) end)