2021-11-03 22:38:50 +01:00
|
|
|
local Gio = require("lgi").Gio
|
|
|
|
local awful = require("awful")
|
|
|
|
local gobject = require("gears.object")
|
|
|
|
local gtable = require("gears.table")
|
|
|
|
local gtimer = require("gears.timer")
|
|
|
|
local wibox = require("wibox")
|
|
|
|
local beautiful = require("beautiful")
|
2023-02-19 17:01:01 +01:00
|
|
|
local prompt_widget = require(... .. ".prompt")
|
2021-11-03 22:38:50 +01:00
|
|
|
local dpi = beautiful.xresources.apply_dpi
|
|
|
|
local string = string
|
|
|
|
local table = table
|
|
|
|
local math = math
|
2021-11-05 04:38:54 +01:00
|
|
|
local ipairs = ipairs
|
|
|
|
local capi = { screen = screen, mouse = mouse }
|
|
|
|
local path = ...
|
2023-02-14 01:31:36 +01:00
|
|
|
local helpers = require(tostring(path):match(".*bling") .. ".helpers")
|
2021-11-03 22:38:50 +01:00
|
|
|
|
|
|
|
local app_launcher = { mt = {} }
|
|
|
|
|
2023-02-04 23:53:50 +01:00
|
|
|
local KILL_OLD_INOTIFY_SCRIPT = [[ ps x | grep "inotifywait -e modify /usr/share/applications" | grep -v grep | awk '{print $1}' | xargs kill ]]
|
|
|
|
local INOTIFY_SCRIPT = [[ bash -c "while (inotifywait -e modify /usr/share/applications -qq) do echo; done" ]]
|
2023-02-04 23:47:12 +01:00
|
|
|
local AWESOME_SENSIBLE_TERMINAL_PATH = debug.getinfo(1).source:match("@?(.*/)") ..
|
|
|
|
"awesome-sensible-terminal"
|
2021-11-05 04:38:54 +01:00
|
|
|
|
2023-02-19 16:45:24 +01:00
|
|
|
local function default_value(value, default)
|
|
|
|
if value == nil then
|
|
|
|
return default
|
|
|
|
else
|
|
|
|
return value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-11-05 04:38:54 +01:00
|
|
|
local function string_levenshtein(str1, str2)
|
|
|
|
local len1 = string.len(str1)
|
|
|
|
local len2 = string.len(str2)
|
|
|
|
local matrix = {}
|
|
|
|
local cost = 0
|
|
|
|
|
|
|
|
-- quick cut-offs to save time
|
|
|
|
if (len1 == 0) then
|
|
|
|
return len2
|
|
|
|
elseif (len2 == 0) then
|
|
|
|
return len1
|
|
|
|
elseif (str1 == str2) then
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
|
|
|
|
-- initialise the base matrix values
|
|
|
|
for i = 0, len1, 1 do
|
|
|
|
matrix[i] = {}
|
|
|
|
matrix[i][0] = i
|
|
|
|
end
|
|
|
|
for j = 0, len2, 1 do
|
|
|
|
matrix[0][j] = j
|
|
|
|
end
|
|
|
|
|
|
|
|
-- actual Levenshtein algorithm
|
|
|
|
for i = 1, len1, 1 do
|
|
|
|
for j = 1, len2, 1 do
|
|
|
|
if (str1:byte(i) == str2:byte(j)) then
|
|
|
|
cost = 0
|
|
|
|
else
|
|
|
|
cost = 1
|
|
|
|
end
|
|
|
|
|
|
|
|
matrix[i][j] = math.min(matrix[i-1][j] + 1, matrix[i][j-1] + 1, matrix[i-1][j-1] + cost)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- return the last value - this is the Levenshtein distance
|
|
|
|
return matrix[len1][len2]
|
|
|
|
end
|
|
|
|
|
2021-11-08 21:42:38 +01:00
|
|
|
local function has_value(tab, val)
|
2023-02-24 19:39:21 +01:00
|
|
|
for _, value in ipairs(tab) do
|
|
|
|
if val:lower():find(value:lower(), 1, true) then
|
2021-11-08 21:42:38 +01:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2023-02-12 17:14:35 +01:00
|
|
|
local function app_widget(self, app)
|
|
|
|
local widget = nil
|
2021-11-08 21:42:38 +01:00
|
|
|
|
2023-02-05 01:17:46 +01:00
|
|
|
if self.app_template == nil then
|
2023-02-12 17:14:35 +01:00
|
|
|
widget = wibox.widget
|
2021-11-03 22:38:50 +01:00
|
|
|
{
|
2023-02-05 01:17:46 +01:00
|
|
|
widget = wibox.container.background,
|
2023-02-19 16:33:53 +01:00
|
|
|
forced_width = dpi(300),
|
|
|
|
forced_height = dpi(120),
|
2023-02-05 01:17:46 +01:00
|
|
|
bg = self.app_normal_color,
|
2021-11-03 22:38:50 +01:00
|
|
|
{
|
2023-02-05 01:17:46 +01:00
|
|
|
widget = wibox.container.margin,
|
2023-02-19 16:33:53 +01:00
|
|
|
margins = dpi(10),
|
2021-11-08 21:42:38 +01:00
|
|
|
{
|
2023-02-20 02:39:42 +01:00
|
|
|
layout = wibox.layout.fixed.vertical,
|
|
|
|
spacing = dpi(10),
|
2021-11-08 21:42:38 +01:00
|
|
|
{
|
2023-02-20 02:39:42 +01:00
|
|
|
widget = wibox.container.place,
|
|
|
|
halign = "center",
|
|
|
|
valign = "center",
|
|
|
|
{
|
|
|
|
widget = wibox.widget.imagebox,
|
|
|
|
id = "icon_role",
|
|
|
|
forced_width = dpi(70),
|
|
|
|
forced_height = dpi(70),
|
|
|
|
image = app.icon
|
|
|
|
},
|
2023-02-05 01:17:46 +01:00
|
|
|
},
|
2023-02-20 02:39:42 +01:00
|
|
|
{
|
|
|
|
widget = wibox.container.place,
|
|
|
|
halign = "center",
|
|
|
|
valign = "center",
|
|
|
|
{
|
|
|
|
widget = wibox.widget.textbox,
|
|
|
|
id = "name_role",
|
|
|
|
markup = string.format("<span foreground='%s'>%s</span>", self.app_name_normal_color, app.name)
|
|
|
|
}
|
|
|
|
}
|
2023-02-05 01:17:46 +01:00
|
|
|
}
|
2021-11-03 22:38:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-12 17:14:35 +01:00
|
|
|
widget:connect_signal("mouse::enter", function()
|
2023-02-05 01:17:46 +01:00
|
|
|
local widget = capi.mouse.current_wibox
|
|
|
|
if widget then
|
|
|
|
widget.cursor = "hand2"
|
|
|
|
end
|
|
|
|
end)
|
2021-11-03 22:38:50 +01:00
|
|
|
|
2023-02-12 17:14:35 +01:00
|
|
|
widget:connect_signal("mouse::leave", function()
|
2023-02-05 01:17:46 +01:00
|
|
|
local widget = capi.mouse.current_wibox
|
|
|
|
if widget then
|
|
|
|
widget.cursor = "left_ptr"
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
else
|
2023-02-12 17:14:35 +01:00
|
|
|
widget = self.app_template(app)
|
2023-02-05 01:17:46 +01:00
|
|
|
end
|
2021-11-05 04:38:54 +01:00
|
|
|
|
2023-02-12 17:14:35 +01:00
|
|
|
widget:connect_signal("button::press", function(app, _, __, button)
|
2021-11-05 04:38:54 +01:00
|
|
|
if button == 1 then
|
|
|
|
if self._private.active_widget == app or not self.select_before_spawn then
|
2023-02-05 00:28:51 +01:00
|
|
|
app:spawn()
|
2021-11-05 04:38:54 +01:00
|
|
|
else
|
2023-02-05 14:33:13 +01:00
|
|
|
app:select()
|
2021-11-05 04:38:54 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2023-02-07 13:42:17 +01:00
|
|
|
local _self = self
|
2023-02-12 17:14:35 +01:00
|
|
|
function widget:spawn()
|
2023-02-05 01:17:46 +01:00
|
|
|
if app.terminal == true then
|
2023-02-21 04:01:16 +01:00
|
|
|
awful.spawn.with_shell(AWESOME_SENSIBLE_TERMINAL_PATH .. " -e " .. app.exec)
|
2023-02-05 01:17:46 +01:00
|
|
|
else
|
2023-02-21 04:01:16 +01:00
|
|
|
awful.spawn(app.exec)
|
2023-02-05 01:17:46 +01:00
|
|
|
end
|
|
|
|
|
2023-02-07 13:42:17 +01:00
|
|
|
if _self.hide_on_launch then
|
|
|
|
_self:hide()
|
2023-02-05 01:17:46 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-12 17:14:35 +01:00
|
|
|
function widget:select()
|
2023-02-05 14:33:13 +01:00
|
|
|
if _self._private.active_widget then
|
|
|
|
_self._private.active_widget:unselect()
|
|
|
|
end
|
|
|
|
_self._private.active_widget = self
|
|
|
|
self:emit_signal("selected")
|
|
|
|
self.selected = true
|
|
|
|
|
|
|
|
if _self.app_template == nil then
|
2023-02-20 02:39:42 +01:00
|
|
|
widget.bg = _self.app_selected_color
|
2023-02-05 14:33:13 +01:00
|
|
|
local name_widget = self:get_children_by_id("name_role")[1]
|
2023-02-20 02:39:42 +01:00
|
|
|
name_widget.markup = string.format("<span foreground='%s'>%s</span>", _self.app_name_selected_color, name_widget.text)
|
2023-02-05 14:33:13 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-12 17:14:35 +01:00
|
|
|
function widget:unselect()
|
2023-02-05 14:33:13 +01:00
|
|
|
self:emit_signal("unselected")
|
|
|
|
self.selected = false
|
|
|
|
_self._private.active_widget = nil
|
|
|
|
|
|
|
|
if _self.app_template == nil then
|
2023-02-20 02:39:42 +01:00
|
|
|
widget.bg = _self.app_normal_color
|
2023-02-05 14:33:13 +01:00
|
|
|
local name_widget = self:get_children_by_id("name_role")[1]
|
2023-02-20 02:39:42 +01:00
|
|
|
name_widget.markup = string.format("<span foreground='%s'>%s</span>", _self.app_name_normal_color, name_widget.text)
|
2023-02-05 14:33:13 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-24 18:53:53 +01:00
|
|
|
app.spawn = widget.spawn
|
|
|
|
app.select = widget.select
|
|
|
|
app.unselect = widget.unselect
|
|
|
|
|
2023-02-12 17:14:35 +01:00
|
|
|
return widget
|
2021-11-03 22:38:50 +01:00
|
|
|
end
|
|
|
|
|
2023-02-24 19:42:51 +01:00
|
|
|
local function search(self)
|
|
|
|
local text = self._private.text
|
2023-02-24 22:03:56 +01:00
|
|
|
local old_pos = self:get_grid():get_widget_position(self._private.active_widget)
|
2021-11-05 04:38:54 +01:00
|
|
|
|
2023-02-21 03:30:34 +01:00
|
|
|
-- Reset all the matched apps
|
|
|
|
self._private.matched_apps = {}
|
2021-11-03 22:38:50 +01:00
|
|
|
-- Remove all the grid widgets
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_grid():reset()
|
2021-11-03 22:38:50 +01:00
|
|
|
|
2021-11-05 04:38:54 +01:00
|
|
|
if text == "" then
|
2023-02-21 03:30:34 +01:00
|
|
|
self._private.matched_apps = self._private.all_apps
|
2021-11-05 04:38:54 +01:00
|
|
|
else
|
2023-02-24 18:53:59 +01:00
|
|
|
for _, app in ipairs(self._private.all_apps) do
|
2021-11-05 04:38:54 +01:00
|
|
|
text = text:gsub( "%W", "" )
|
|
|
|
|
|
|
|
-- Check if there's a match by the app name or app command
|
2023-02-21 03:30:34 +01:00
|
|
|
if string.find(app.name:lower(), text:lower(), 1, true) ~= nil or
|
2023-02-21 04:01:16 +01:00
|
|
|
self.search_commands and string.find(app.exec, text:lower(), 1, true) ~= nil
|
2021-11-05 04:38:54 +01:00
|
|
|
then
|
2023-02-21 04:01:16 +01:00
|
|
|
table.insert(self._private.matched_apps, app)
|
2021-11-03 22:38:50 +01:00
|
|
|
end
|
|
|
|
end
|
2021-11-05 04:38:54 +01:00
|
|
|
|
|
|
|
-- Sort by string similarity
|
2023-02-21 03:30:34 +01:00
|
|
|
table.sort(self._private.matched_apps, function(a, b)
|
2021-11-05 04:38:54 +01:00
|
|
|
return string_levenshtein(text, a.name) < string_levenshtein(text, b.name)
|
|
|
|
end)
|
|
|
|
end
|
2023-02-24 19:38:32 +01:00
|
|
|
for _, app in ipairs(self._private.matched_apps) do
|
2021-11-05 04:38:54 +01:00
|
|
|
-- Only add the widgets for apps that are part of the first page
|
2023-02-24 22:03:56 +01:00
|
|
|
if #self:get_grid().children + 1 <= self._private.max_apps_per_page then
|
|
|
|
self:get_grid():add(app_widget(self, app))
|
2021-11-05 04:38:54 +01:00
|
|
|
end
|
2021-11-03 22:38:50 +01:00
|
|
|
end
|
|
|
|
|
2023-02-21 03:30:34 +01:00
|
|
|
-- Recalculate the apps per page based on the current matched apps
|
|
|
|
self._private.apps_per_page = math.min(#self._private.matched_apps, self._private.max_apps_per_page)
|
2021-11-03 22:38:50 +01:00
|
|
|
|
|
|
|
-- Recalculate the pages count based on the current apps per page
|
2023-02-21 03:30:34 +01:00
|
|
|
self._private.pages_count = math.ceil(math.max(1, #self._private.matched_apps) / math.max(1, self._private.apps_per_page))
|
2021-11-03 22:38:50 +01:00
|
|
|
|
2021-11-05 04:38:54 +01:00
|
|
|
-- Page should be 1 after a search
|
|
|
|
self._private.current_page = 1
|
|
|
|
|
2021-11-03 22:38:50 +01:00
|
|
|
-- This is an option to mimic rofi behaviour where after a search
|
|
|
|
-- it will reselect the app whose index is the same as the app index that was previously selected
|
2023-02-21 03:30:34 +01:00
|
|
|
-- and if matched_apps.length < current_index it will instead select the app with the greatest index
|
2021-11-03 22:38:50 +01:00
|
|
|
if self.try_to_keep_index_after_searching then
|
2023-02-24 22:03:56 +01:00
|
|
|
if self:get_grid():get_widgets_at(old_pos.row, old_pos.col) == nil then
|
|
|
|
local app = self:get_grid().children[#self:get_grid().children]
|
2023-02-05 14:33:13 +01:00
|
|
|
app:select()
|
|
|
|
else
|
2023-02-24 22:03:56 +01:00
|
|
|
local app = self:get_grid():get_widgets_at(old_pos.row, old_pos.col)[1]
|
2023-02-05 14:33:13 +01:00
|
|
|
app:select()
|
2021-11-05 04:38:54 +01:00
|
|
|
end
|
2021-11-03 22:38:50 +01:00
|
|
|
-- Otherwise select the first app on the list
|
2023-02-24 22:03:56 +01:00
|
|
|
elseif #self:get_grid().children > 0 then
|
|
|
|
local app = self:get_grid():get_widgets_at(1, 1)[1]
|
2023-02-05 14:33:13 +01:00
|
|
|
app:select()
|
2021-11-03 22:38:50 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-05 14:45:31 +01:00
|
|
|
local function page_forward(self, dir)
|
2023-02-04 23:44:24 +01:00
|
|
|
local min_app_index_to_include = 0
|
|
|
|
local max_app_index_to_include = self._private.apps_per_page
|
|
|
|
|
|
|
|
if self._private.current_page < self._private.pages_count then
|
|
|
|
min_app_index_to_include = self._private.apps_per_page * self._private.current_page
|
|
|
|
self._private.current_page = self._private.current_page + 1
|
|
|
|
max_app_index_to_include = self._private.apps_per_page * self._private.current_page
|
2023-02-21 03:30:34 +01:00
|
|
|
elseif self.wrap_page_scrolling and #self._private.matched_apps >= self._private.max_apps_per_page then
|
2023-02-04 23:44:24 +01:00
|
|
|
self._private.current_page = 1
|
|
|
|
min_app_index_to_include = 0
|
|
|
|
max_app_index_to_include = self._private.apps_per_page
|
2021-11-08 21:42:38 +01:00
|
|
|
elseif self.wrap_app_scrolling then
|
2023-02-24 22:03:56 +01:00
|
|
|
local app = self:get_grid():get_widgets_at(1, 1)[1]
|
2023-02-05 14:33:13 +01:00
|
|
|
app:select()
|
2021-11-08 21:42:38 +01:00
|
|
|
return
|
|
|
|
else
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
local pos = self:get_grid():get_widget_position(self._private.active_widget)
|
2021-11-08 21:42:38 +01:00
|
|
|
|
|
|
|
-- Remove the current page apps from the grid
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_grid():reset()
|
2021-11-08 21:42:38 +01:00
|
|
|
|
2023-02-24 19:38:32 +01:00
|
|
|
for index, app in ipairs(self._private.matched_apps) do
|
2021-11-08 21:42:38 +01:00
|
|
|
-- Only add widgets that are between this range (part of the current page)
|
|
|
|
if index > min_app_index_to_include and index <= max_app_index_to_include then
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_grid():add(app_widget(self, app))
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-04 23:44:24 +01:00
|
|
|
if self._private.current_page > 1 or self.wrap_page_scrolling then
|
2023-02-24 21:18:13 +01:00
|
|
|
local app = nil
|
2023-02-05 14:45:31 +01:00
|
|
|
if dir == "down" then
|
2023-02-24 22:03:56 +01:00
|
|
|
app = self:get_grid():get_widgets_at(1, 1)[1]
|
2023-02-24 21:18:13 +01:00
|
|
|
elseif dir == "right" then
|
2023-02-24 22:03:56 +01:00
|
|
|
app = self:get_grid():get_widgets_at(pos.row, 1)
|
2023-02-24 21:27:06 +01:00
|
|
|
if app then
|
|
|
|
app = app[1]
|
|
|
|
end
|
2023-02-05 14:33:13 +01:00
|
|
|
if app == nil then
|
2023-02-24 22:03:56 +01:00
|
|
|
app = self:get_grid().children[#self:get_grid().children]
|
2023-02-05 14:03:02 +01:00
|
|
|
end
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
2023-02-24 21:18:13 +01:00
|
|
|
app:select()
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-05 14:45:31 +01:00
|
|
|
local function page_backward(self, dir)
|
2023-02-04 23:44:24 +01:00
|
|
|
if self._private.current_page > 1 then
|
|
|
|
self._private.current_page = self._private.current_page - 1
|
2023-02-21 03:30:34 +01:00
|
|
|
elseif self.wrap_page_scrolling and #self._private.matched_apps >= self._private.max_apps_per_page then
|
2023-02-04 23:44:24 +01:00
|
|
|
self._private.current_page = self._private.pages_count
|
2021-11-08 21:42:38 +01:00
|
|
|
elseif self.wrap_app_scrolling then
|
2023-02-24 22:03:56 +01:00
|
|
|
local app = self:get_grid().children[#self:get_grid().children]
|
2023-02-05 14:33:13 +01:00
|
|
|
app:select()
|
2021-11-08 21:42:38 +01:00
|
|
|
return
|
|
|
|
else
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
local pos = self:get_grid():get_widget_position(self._private.active_widget)
|
2021-11-08 21:42:38 +01:00
|
|
|
|
|
|
|
-- Remove the current page apps from the grid
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_grid():reset()
|
2021-11-08 21:42:38 +01:00
|
|
|
|
2023-02-04 23:44:24 +01:00
|
|
|
local max_app_index_to_include = self._private.apps_per_page * self._private.current_page
|
|
|
|
local min_app_index_to_include = max_app_index_to_include - self._private.apps_per_page
|
|
|
|
|
2023-02-24 19:38:32 +01:00
|
|
|
for index, app in ipairs(self._private.matched_apps) do
|
2021-11-08 21:42:38 +01:00
|
|
|
-- Only add widgets that are between this range (part of the current page)
|
|
|
|
if index > min_app_index_to_include and index <= max_app_index_to_include then
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_grid():add(app_widget(self, app))
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-24 21:18:13 +01:00
|
|
|
local app = nil
|
2023-02-04 23:44:24 +01:00
|
|
|
if self._private.current_page < self._private.pages_count then
|
2023-02-05 14:45:31 +01:00
|
|
|
if dir == "up" then
|
2023-02-24 22:03:56 +01:00
|
|
|
app = self:get_grid().children[#self:get_grid().children]
|
2021-11-08 21:42:38 +01:00
|
|
|
else
|
2023-02-04 23:44:24 +01:00
|
|
|
-- Keep the same row from last page
|
2023-02-24 22:03:56 +01:00
|
|
|
local _, columns = self:get_grid():get_dimension()
|
|
|
|
app = self:get_grid():get_widgets_at(pos.row, columns)[1]
|
2023-02-04 23:44:24 +01:00
|
|
|
end
|
|
|
|
elseif self.wrap_page_scrolling then
|
2023-02-24 22:03:56 +01:00
|
|
|
app = self:get_grid().children[#self:get_grid().children]
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
2023-02-24 21:18:13 +01:00
|
|
|
app:select()
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
|
|
|
|
2023-02-05 14:45:13 +01:00
|
|
|
local function scroll(self, dir)
|
2023-02-24 22:03:56 +01:00
|
|
|
if #self:get_grid().children < 1 then
|
2023-02-05 14:45:13 +01:00
|
|
|
self._private.active_widget = nil
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
local pos = self:get_grid():get_widget_position(self._private.active_widget)
|
2023-02-05 14:45:13 +01:00
|
|
|
local can_scroll = false
|
|
|
|
local step_size = 0
|
|
|
|
local if_cant_scroll_func = nil
|
|
|
|
|
|
|
|
if dir == "up" then
|
2023-02-24 22:03:56 +01:00
|
|
|
can_scroll = self:get_grid():index(self._private.active_widget) > 1
|
2023-02-05 14:45:13 +01:00
|
|
|
step_size = -1
|
|
|
|
if_cant_scroll_func = function() page_backward(self, "up") end
|
|
|
|
elseif dir == "down" then
|
2023-02-24 22:03:56 +01:00
|
|
|
can_scroll = self:get_grid():index(self._private.active_widget) < #self:get_grid().children
|
2023-02-05 14:45:13 +01:00
|
|
|
step_size = 1
|
|
|
|
if_cant_scroll_func = function() page_forward(self, "down") end
|
|
|
|
elseif dir == "left" then
|
2023-02-24 22:03:56 +01:00
|
|
|
can_scroll = self:get_grid():get_widgets_at(pos.row, pos.col - 1) ~= nil
|
|
|
|
step_size = -self:get_grid().forced_num_rows
|
2023-02-05 14:45:13 +01:00
|
|
|
if_cant_scroll_func = function() page_backward(self, "left") end
|
|
|
|
elseif dir == "right" then
|
2023-02-24 22:03:56 +01:00
|
|
|
can_scroll = self:get_grid():get_widgets_at(pos.row, pos.col + 1) ~= nil
|
|
|
|
step_size = self:get_grid().forced_num_cols
|
2023-02-05 14:45:13 +01:00
|
|
|
if_cant_scroll_func = function() page_forward(self, "right") end
|
|
|
|
end
|
|
|
|
|
|
|
|
if can_scroll then
|
2023-02-24 22:03:56 +01:00
|
|
|
local app = gtable.cycle_value(self:get_grid().children, self._private.active_widget, step_size)
|
2023-02-05 14:45:13 +01:00
|
|
|
app:select()
|
|
|
|
else
|
|
|
|
if_cant_scroll_func()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-24 19:39:47 +01:00
|
|
|
local function sort_apps(self)
|
|
|
|
table.sort(self._private.all_apps, function(a, b)
|
|
|
|
local is_a_favorite = has_value(self.favorites, a.id)
|
|
|
|
local is_b_favorite = has_value(self.favorites, b.id)
|
|
|
|
|
|
|
|
-- Sort the favorite apps first
|
|
|
|
if is_a_favorite and not is_b_favorite then
|
|
|
|
return true
|
|
|
|
elseif not is_a_favorite and is_b_favorite then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Sort alphabetically if specified
|
|
|
|
if self.sort_alphabetically then
|
|
|
|
return a.name:lower() < b.name:lower()
|
|
|
|
elseif self.reverse_sort_alphabetically then
|
|
|
|
return b.name:lower() > a.name:lower()
|
|
|
|
else
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2021-11-08 21:42:38 +01:00
|
|
|
local function generate_apps(self)
|
2023-02-21 03:30:34 +01:00
|
|
|
self._private.all_apps = {}
|
|
|
|
self._private.matched_apps = {}
|
2021-11-08 21:42:38 +01:00
|
|
|
|
|
|
|
local app_info = Gio.AppInfo
|
|
|
|
local apps = app_info.get_all()
|
|
|
|
for _, app in ipairs(apps) do
|
2023-02-21 04:01:16 +01:00
|
|
|
if app:should_show() then
|
|
|
|
local id = app:get_id()
|
|
|
|
local desktop_app_info = Gio.DesktopAppInfo.new(id)
|
|
|
|
local name = desktop_app_info:get_string("Name")
|
|
|
|
local exec = desktop_app_info:get_string("Exec")
|
2021-11-08 21:42:38 +01:00
|
|
|
|
|
|
|
-- Check if this app should be skipped, depanding on the skip_names / skip_commands table
|
2023-02-21 04:01:16 +01:00
|
|
|
if not has_value(self.skip_names, name) and not has_value(self.skip_commands, exec) then
|
2021-11-08 21:42:38 +01:00
|
|
|
-- Check if this app should be skipped becuase it's iconless depanding on skip_empty_icons
|
2023-02-21 04:01:16 +01:00
|
|
|
local icon = helpers.icon_theme.get_gicon_path(app_info.get_icon(app), self.icon_theme, self.icon_size)
|
2021-11-08 21:42:38 +01:00
|
|
|
if icon ~= "" or self.skip_empty_icons == false then
|
|
|
|
if icon == "" then
|
|
|
|
if self.default_app_icon_name ~= nil then
|
2023-02-14 01:26:18 +01:00
|
|
|
icon = helpers.icon_theme.get_icon_path(self.default_app_icon_name, self.icon_theme, self.icon_size)
|
2021-11-08 21:42:38 +01:00
|
|
|
elseif self.default_app_icon_path ~= nil then
|
|
|
|
icon = self.default_app_icon_path
|
|
|
|
else
|
2023-02-14 01:26:18 +01:00
|
|
|
icon = helpers.icon_theme.choose_icon(
|
|
|
|
{"application-all", "application", "application-default-icon", "app"},
|
|
|
|
self.icon_theme, self.icon_size)
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-21 03:30:34 +01:00
|
|
|
table.insert(self._private.all_apps, {
|
2023-02-21 04:07:07 +01:00
|
|
|
desktop_app_info = desktop_app_info,
|
2023-02-21 04:01:16 +01:00
|
|
|
path = desktop_app_info:get_filename(),
|
|
|
|
id = id,
|
2021-11-08 21:42:38 +01:00
|
|
|
name = name,
|
2023-02-21 04:01:16 +01:00
|
|
|
generic_name = desktop_app_info:get_string("GenericName"),
|
|
|
|
startup_wm_class = desktop_app_info:get_startup_wm_class(),
|
|
|
|
keywords = desktop_app_info:get_string("Keywords"),
|
|
|
|
icon = icon,
|
|
|
|
icon_name = desktop_app_info:get_string("Icon"),
|
|
|
|
terminal = desktop_app_info:get_string("Terminal") == "true" and true or false,
|
|
|
|
exec = exec,
|
2021-11-08 21:42:38 +01:00
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2023-02-24 19:39:47 +01:00
|
|
|
|
|
|
|
sort_apps(self)
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
|
|
|
|
2023-02-19 17:01:01 +01:00
|
|
|
local function build_widget(self)
|
2023-02-20 01:28:47 +01:00
|
|
|
local widget = self.widget_template
|
2023-02-20 02:39:42 +01:00
|
|
|
if widget == nil then
|
2023-02-20 01:28:47 +01:00
|
|
|
self._private.prompt = wibox.widget
|
2023-02-19 17:01:01 +01:00
|
|
|
{
|
2023-02-20 01:28:47 +01:00
|
|
|
widget = prompt_widget,
|
2023-02-20 03:33:23 +01:00
|
|
|
always_on = true,
|
2023-02-20 01:28:47 +01:00
|
|
|
reset_on_stop = self.reset_on_hide,
|
2023-02-20 02:39:42 +01:00
|
|
|
icon_font = self.prompt_icon_font,
|
|
|
|
icon_size = self.prompt_icon_size,
|
|
|
|
icon_color = self.prompt_icon_color,
|
|
|
|
icon = self.prompt_icon,
|
|
|
|
label_font = self.prompt_label_font,
|
|
|
|
label_size = self.prompt_label_size,
|
|
|
|
label_color = self.prompt_label_color,
|
|
|
|
label = self.prompt_label,
|
|
|
|
text_font = self.prompt_text_font,
|
|
|
|
text_size = self.prompt_text_size,
|
|
|
|
text_color = self.prompt_text_color,
|
2023-02-19 17:01:01 +01:00
|
|
|
}
|
2023-02-20 01:28:47 +01:00
|
|
|
self._private.grid = wibox.widget
|
|
|
|
{
|
|
|
|
layout = wibox.layout.grid,
|
|
|
|
orientation = "horizontal",
|
|
|
|
homogeneous = true,
|
2023-02-20 03:54:20 +01:00
|
|
|
spacing = dpi(30),
|
2023-02-20 01:28:47 +01:00
|
|
|
forced_num_cols = self.apps_per_column,
|
|
|
|
forced_num_rows = self.apps_per_row,
|
|
|
|
}
|
|
|
|
widget = wibox.widget
|
2023-02-19 17:01:01 +01:00
|
|
|
{
|
|
|
|
layout = wibox.layout.fixed.vertical,
|
|
|
|
{
|
|
|
|
widget = wibox.container.background,
|
2023-02-20 02:39:42 +01:00
|
|
|
forced_height = dpi(120),
|
2023-02-19 17:01:01 +01:00
|
|
|
bg = self.prompt_bg_color,
|
|
|
|
{
|
|
|
|
widget = wibox.container.margin,
|
|
|
|
margins = dpi(30),
|
|
|
|
{
|
|
|
|
widget = wibox.container.place,
|
|
|
|
halign = "left",
|
|
|
|
valign = "center",
|
2023-02-20 01:28:47 +01:00
|
|
|
self._private.prompt
|
2023-02-19 17:01:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
widget = wibox.container.margin,
|
|
|
|
margins = dpi(30),
|
|
|
|
self._private.grid
|
|
|
|
}
|
|
|
|
}
|
2023-02-20 02:39:42 +01:00
|
|
|
else
|
|
|
|
self._private.prompt = widget:get_children_by_id("prompt_role")[1]
|
|
|
|
self._private.grid = widget:get_children_by_id("grid_role")[1]
|
2023-02-20 01:28:47 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
self._private.widget = awful.popup
|
|
|
|
{
|
2023-02-20 03:51:00 +01:00
|
|
|
screen = self.screen,
|
2023-02-20 01:28:47 +01:00
|
|
|
type = self.type,
|
|
|
|
visible = false,
|
|
|
|
ontop = true,
|
|
|
|
placement = self.placement,
|
|
|
|
border_width = self.border_width,
|
|
|
|
border_color = self.border_color,
|
|
|
|
shape = self.shape,
|
2023-02-20 04:09:34 +01:00
|
|
|
bg = self.bg,
|
2023-02-20 01:28:47 +01:00
|
|
|
widget = widget
|
2023-02-19 17:01:01 +01:00
|
|
|
}
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_grid():connect_signal("button::press", function(_, lx, ly, button, mods, find_widgets_result)
|
2023-02-20 03:08:51 +01:00
|
|
|
if button == 4 then
|
|
|
|
self:scroll_up()
|
|
|
|
elseif button == 5 then
|
|
|
|
self:scroll_down()
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_prompt():connect_signal("text::changed", function(_, text)
|
2023-02-19 17:01:01 +01:00
|
|
|
if text == self._private.text then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
self._private.text = text
|
|
|
|
self._private.search_timer:again()
|
|
|
|
end)
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_prompt():connect_signal("key::release", function(_, mod, key, cmd)
|
2023-02-19 17:01:01 +01:00
|
|
|
if key == "Escape" then
|
|
|
|
self:hide()
|
|
|
|
end
|
|
|
|
if key == "Return" then
|
|
|
|
if self._private.active_widget ~= nil then
|
|
|
|
self._private.active_widget:spawn()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if key == "Up" then
|
|
|
|
self:scroll_up()
|
|
|
|
end
|
|
|
|
if key == "Down" then
|
|
|
|
self:scroll_down()
|
|
|
|
end
|
|
|
|
if key == "Left" then
|
|
|
|
self:scroll_left()
|
|
|
|
end
|
|
|
|
if key == "Right" then
|
|
|
|
self:scroll_right()
|
|
|
|
end
|
|
|
|
end)
|
2023-02-20 03:08:51 +01:00
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
self._private.max_apps_per_page = self:get_grid().forced_num_cols * self:get_grid().forced_num_rows
|
2023-02-20 03:08:51 +01:00
|
|
|
self._private.apps_per_page = self._private.max_apps_per_page
|
2023-02-19 17:01:01 +01:00
|
|
|
end
|
|
|
|
|
2023-02-12 17:16:43 +01:00
|
|
|
function app_launcher:set_favorites(favorites)
|
|
|
|
self.favorites = favorites
|
2023-02-24 19:41:44 +01:00
|
|
|
sort_apps(self)
|
|
|
|
-- Refresh the app list
|
2023-02-24 19:42:51 +01:00
|
|
|
search(self)
|
2023-02-12 17:16:43 +01:00
|
|
|
end
|
|
|
|
|
2023-02-05 14:41:51 +01:00
|
|
|
function app_launcher:scroll_up()
|
|
|
|
scroll(self, "up")
|
|
|
|
end
|
|
|
|
|
|
|
|
function app_launcher:scroll_down()
|
|
|
|
scroll(self, "down")
|
|
|
|
end
|
|
|
|
|
|
|
|
function app_launcher:scroll_left()
|
|
|
|
scroll(self, "left")
|
|
|
|
end
|
|
|
|
|
|
|
|
function app_launcher:scroll_right()
|
|
|
|
scroll(self, "right")
|
|
|
|
end
|
|
|
|
|
2021-11-05 04:38:54 +01:00
|
|
|
function app_launcher:show()
|
|
|
|
if self.show_on_focused_screen then
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_widget().screen = awful.screen.focused()
|
2021-11-03 22:38:50 +01:00
|
|
|
end
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_widget().visible = true
|
|
|
|
self:get_prompt():start()
|
2023-02-20 03:16:31 +01:00
|
|
|
self:emit_signal("visibility", true)
|
2021-11-03 22:38:50 +01:00
|
|
|
end
|
|
|
|
|
2021-11-05 04:38:54 +01:00
|
|
|
function app_launcher:hide()
|
2023-02-24 22:03:56 +01:00
|
|
|
if self:get_widget().visible == false then
|
2023-02-22 04:17:18 +01:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2023-02-24 22:01:34 +01:00
|
|
|
if self.reset_on_hide == true then
|
|
|
|
self:reset()
|
|
|
|
end
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_widget().visible = false
|
|
|
|
self:get_prompt():stop()
|
2023-02-20 03:16:31 +01:00
|
|
|
self:emit_signal("visibility", false)
|
2021-11-03 22:38:50 +01:00
|
|
|
end
|
|
|
|
|
2021-11-05 04:38:54 +01:00
|
|
|
function app_launcher:toggle()
|
2023-02-24 22:03:56 +01:00
|
|
|
if self:get_widget().visible then
|
2021-11-05 04:38:54 +01:00
|
|
|
self:hide()
|
2021-11-03 22:38:50 +01:00
|
|
|
else
|
2021-11-05 04:38:54 +01:00
|
|
|
self:show()
|
2021-11-03 22:38:50 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-24 22:01:34 +01:00
|
|
|
function app_launcher:reset()
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_grid():reset()
|
2023-02-24 22:01:34 +01:00
|
|
|
self._private.matched_apps = self._private.all_apps
|
|
|
|
self._private.apps_per_page = self._private.max_apps_per_page
|
|
|
|
self._private.pages_count = math.ceil(#self._private.all_apps / self._private.apps_per_page)
|
|
|
|
self._private.current_page = 1
|
|
|
|
|
|
|
|
for index, app in ipairs(self._private.all_apps) do
|
|
|
|
-- Only add the apps that are part of the first page
|
|
|
|
if index <= self._private.apps_per_page then
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_grid():add(app_widget(self, app))
|
2023-02-24 22:01:34 +01:00
|
|
|
else
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
local app = self:get_grid():get_widgets_at(1, 1)[1]
|
2023-02-24 22:01:34 +01:00
|
|
|
app:select()
|
|
|
|
|
2023-02-24 22:03:56 +01:00
|
|
|
self:get_prompt():set_text("")
|
|
|
|
end
|
|
|
|
|
|
|
|
function app_launcher:get_widget()
|
|
|
|
return self._private.widget
|
|
|
|
end
|
|
|
|
|
|
|
|
function app_launcher:get_prompt()
|
|
|
|
return self._private.prompt
|
|
|
|
end
|
|
|
|
|
|
|
|
function app_launcher:get_grid()
|
|
|
|
return self._private.grid
|
2023-02-24 22:01:34 +01:00
|
|
|
end
|
|
|
|
|
2021-11-03 22:38:50 +01:00
|
|
|
local function new(args)
|
|
|
|
args = args or {}
|
|
|
|
|
2023-02-19 16:45:24 +01:00
|
|
|
args.favorites = default_value(args.favorites, {})
|
|
|
|
args.search_commands = default_value(args.search_commands, true)
|
|
|
|
args.skip_names = default_value(args.skip_names, {})
|
|
|
|
args.skip_commands = default_value(args.skip_commands, {})
|
|
|
|
args.skip_empty_icons = default_value(args.skip_empty_icons, false)
|
|
|
|
args.sort_alphabetically = default_value(args.sort_alphabetically, true)
|
|
|
|
args.reverse_sort_alphabetically = default_value(args.reverse_sort_alphabetically, false)
|
|
|
|
args.select_before_spawn = default_value(args.select_before_spawn, true)
|
|
|
|
args.hide_on_left_clicked_outside = default_value(args.hide_on_left_clicked_outside, true)
|
|
|
|
args.hide_on_right_clicked_outside = default_value(args.hide_on_right_clicked_outside, true)
|
|
|
|
args.hide_on_launch = default_value(args.hide_on_launch, true)
|
|
|
|
args.try_to_keep_index_after_searching = default_value(args.try_to_keep_index_after_searching, false)
|
|
|
|
args.reset_on_hide = default_value(args.reset_on_hide, true)
|
|
|
|
args.wrap_page_scrolling = default_value(args.wrap_page_scrolling, true)
|
|
|
|
args.wrap_app_scrolling = default_value(args.wrap_app_scrolling, true)
|
|
|
|
|
|
|
|
args.type = default_value(args.type, "dock")
|
|
|
|
args.show_on_focused_screen = default_value(args.show_on_focused_screen, true)
|
|
|
|
args.screen = default_value(args.screen, capi.screen.primary)
|
|
|
|
args.placement = default_value(args.placement, awful.placement.centered)
|
2023-02-20 04:09:34 +01:00
|
|
|
args.bg = default_value(args.bg, "#000000")
|
2023-02-19 16:45:24 +01:00
|
|
|
args.border_width = default_value(args.border_width, beautiful.border_width or dpi(0))
|
|
|
|
args.border_color = default_value(args.border_color, beautiful.border_color or "#FFFFFF")
|
|
|
|
args.shape = default_value(args.shape, nil)
|
|
|
|
|
|
|
|
args.default_app_icon_name = default_value(args.default_app_icon_name, nil)
|
|
|
|
args.default_app_icon_path = default_value(args.default_app_icon_path, nil)
|
|
|
|
args.icon_theme = default_value(args.icon_theme, nil)
|
|
|
|
args.icon_size = default_value(args.icon_size, nil)
|
|
|
|
|
|
|
|
args.apps_per_row = default_value(args.apps_per_row, 5)
|
|
|
|
args.apps_per_column = default_value(args.apps_per_column, 3)
|
|
|
|
|
2023-02-20 02:39:42 +01:00
|
|
|
args.prompt_bg_color = default_value(args.prompt_bg_color, "#000000")
|
|
|
|
args.prompt_icon_font = default_value(args.prompt_icon_font, beautiful.font)
|
|
|
|
args.prompt_icon_size = default_value(args.prompt_icon_size, 12)
|
|
|
|
args.prompt_icon_color = default_value(args.prompt_icon_color, "#FFFFFF")
|
2023-02-19 16:45:24 +01:00
|
|
|
args.prompt_icon = default_value(args.prompt_icon, "")
|
2023-02-20 02:39:42 +01:00
|
|
|
args.prompt_label_font = default_value(args.prompt_label_font, beautiful.font)
|
|
|
|
args.prompt_label_size = default_value(args.prompt_label_size, 12)
|
|
|
|
args.prompt_label_color = default_value(args.prompt_label_color, "#FFFFFF")
|
2023-02-19 16:45:24 +01:00
|
|
|
args.prompt_label = default_value(args.prompt_label, "<b>Search</b>: ")
|
2023-02-20 02:39:42 +01:00
|
|
|
args.prompt_text_font = default_value(args.prompt_text_font, beautiful.font)
|
|
|
|
args.prompt_text_size = default_value(args.prompt_text_size, 12)
|
|
|
|
args.prompt_text_color = default_value(args.prompt_text_color, "#FFFFFF")
|
|
|
|
|
|
|
|
args.app_normal_color = default_value(args.app_normal_color, "#000000")
|
|
|
|
args.app_selected_color = default_value(args.app_selected_color, "#FFFFFF")
|
|
|
|
args.app_name_normal_color = default_value( args.app_name_normal_color, "#FFFFFF")
|
|
|
|
args.app_name_selected_color = default_value(args.app_name_selected_color, "#000000")
|
2021-11-03 22:38:50 +01:00
|
|
|
|
2023-02-05 14:48:13 +01:00
|
|
|
local ret = gobject {}
|
2023-02-19 16:36:09 +01:00
|
|
|
gtable.crush(ret, app_launcher, true)
|
|
|
|
gtable.crush(ret, args, true)
|
2021-11-03 22:38:50 +01:00
|
|
|
|
2023-02-19 16:47:54 +01:00
|
|
|
ret._private = {}
|
|
|
|
ret._private.text = ""
|
|
|
|
ret._private.pages_count = 0
|
|
|
|
ret._private.current_page = 1
|
2023-02-19 16:36:14 +01:00
|
|
|
ret._private.search_timer = gtimer {
|
|
|
|
timeout = 0.05,
|
|
|
|
autostart = true,
|
|
|
|
single_shot = true,
|
|
|
|
callback = function()
|
2023-02-24 19:42:51 +01:00
|
|
|
search(ret)
|
2023-02-19 16:36:14 +01:00
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2021-11-08 21:42:38 +01:00
|
|
|
if ret.hide_on_left_clicked_outside then
|
2021-11-05 04:38:54 +01:00
|
|
|
awful.mouse.append_client_mousebinding(
|
|
|
|
awful.button({ }, 1, function (c)
|
|
|
|
ret:hide()
|
|
|
|
end)
|
|
|
|
)
|
|
|
|
|
|
|
|
awful.mouse.append_global_mousebinding(
|
|
|
|
awful.button({ }, 1, function (c)
|
|
|
|
ret:hide()
|
|
|
|
end)
|
|
|
|
)
|
2021-11-08 21:42:38 +01:00
|
|
|
end
|
|
|
|
if ret.hide_on_right_clicked_outside then
|
2021-11-05 04:38:54 +01:00
|
|
|
awful.mouse.append_client_mousebinding(
|
|
|
|
awful.button({ }, 3, function (c)
|
|
|
|
ret:hide()
|
|
|
|
end)
|
|
|
|
)
|
|
|
|
|
|
|
|
awful.mouse.append_global_mousebinding(
|
|
|
|
awful.button({ }, 3, function (c)
|
|
|
|
ret:hide()
|
|
|
|
end)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-02-04 23:53:50 +01:00
|
|
|
awful.spawn.easy_async_with_shell(KILL_OLD_INOTIFY_SCRIPT, function()
|
2023-02-05 13:57:39 +01:00
|
|
|
awful.spawn.with_line_callback(INOTIFY_SCRIPT, {stdout = function()
|
2021-11-08 21:42:38 +01:00
|
|
|
generate_apps(ret)
|
|
|
|
end})
|
|
|
|
end)
|
|
|
|
|
2023-02-19 17:01:01 +01:00
|
|
|
build_widget(ret)
|
2023-02-05 01:20:23 +01:00
|
|
|
generate_apps(ret)
|
2023-02-24 22:01:34 +01:00
|
|
|
ret:reset()
|
2023-02-05 01:20:23 +01:00
|
|
|
|
2021-11-03 22:38:50 +01:00
|
|
|
return ret
|
|
|
|
end
|
|
|
|
|
|
|
|
function app_launcher.mt:__call(...)
|
|
|
|
return new(...)
|
|
|
|
end
|
|
|
|
|
|
|
|
return setmetatable(app_launcher, app_launcher.mt)
|