From 24bce4c810a55f60678dbfd6b32d5f0124f8cce0 Mon Sep 17 00:00:00 2001 From: Rene Kievits Date: Mon, 24 Apr 2023 22:12:18 +0200 Subject: [PATCH] rewritten app launcher --- awesome/src/modules/app_launcher/init.lua | 552 ++++++++++++++++++ .../application_launcher/application.lua | 475 --------------- .../src/modules/application_launcher/init.lua | 213 ------- 3 files changed, 552 insertions(+), 688 deletions(-) create mode 100644 awesome/src/modules/app_launcher/init.lua delete mode 100644 awesome/src/modules/application_launcher/application.lua delete mode 100644 awesome/src/modules/application_launcher/init.lua diff --git a/awesome/src/modules/app_launcher/init.lua b/awesome/src/modules/app_launcher/init.lua new file mode 100644 index 0000000..af5030c --- /dev/null +++ b/awesome/src/modules/app_launcher/init.lua @@ -0,0 +1,552 @@ +local abutton = require('awful.button') +local akey = require('awful.key') +local akeygrabber = require('awful.keygrabber') +local aplacement = require('awful.placement') +local apopup = require('awful.popup') +local beautiful = require('beautiful') +local dpi = beautiful.xresources.apply_dpi +local gtable = require('gears.table') +local gtimer = require('gears.timer') +local wibox = require('wibox') +local gobject = require('gears.object') +local Gio = require('lgi').Gio +local gfilesystem = require('gears.filesystem') +local gcolor = require('gears.color') + +local fzy = require('fzy') + +local hover = require('src.tools.hover') +local inputbox = require('src.modules.inputbox') +local context_menu = require('src.modules.context_menu') +local config = require('src.tools.config') +local icon_lookup = require('src.tools.gio_icon_lookup') +local dock = require('src.modules.crylia_bar.dock') + +local icondir = gfilesystem.get_configuration_dir() .. 'src/assets/icons/context_menu/' + +local capi = { + awesome = awesome, + mouse = mouse, +} + +local launcher = gobject {} + +function launcher:fetch_apps() + for _, app in ipairs(Gio.AppInfo.get_all()) do + local app_id = app:get_id() + if app:should_show() and not self.app_table[app_id] then + local GDesktopAppInfo = Gio.DesktopAppInfo.new(app_id) + local app_icon = app:get_icon() + local app_name = app:get_name() + + local w = wibox.widget { + { + { + { + { + { -- Icon + valign = 'center', + halign = 'center', + image = icon_lookup:get_gicon_path(app_icon) + or icon_lookup:get_gicon_path(app_icon, GDesktopAppInfo:get_string('X-AppImage-Old-Icon')) + or '', + resize = true, + widget = wibox.widget.imagebox, + }, + height = dpi(64), + width = dpi(64), + strategy = 'exact', + widget = wibox.container.constraint, + }, + { + { -- Name + text = app_name, + halign = 'center', + valign = 'center', + widget = wibox.widget.textbox, + }, + strategy = 'exact', + width = dpi(170), + -- Prevents widget from overflowing + height = dpi(40), + widget = wibox.container.constraint, + }, + layout = wibox.layout.fixed.vertical, + }, + widget = wibox.container.place, + }, + margins = dpi(10), + widget = wibox.container.margin, + }, + widget = wibox.container.background, + shape = beautiful.shape[4], + fg = beautiful.colorscheme.fg, + bg = beautiful.colorscheme.bg1, + border_color = beautiful.colorscheme.border_color, + border_width = dpi(2), + + name = app_name, + keywords = GDesktopAppInfo:get_string('Keywords'), + categories = GDesktopAppInfo:get_categories(), + execute = function() + app:launch_uris_async() + end, + } + + hover.bg_hover { widget = w } + + local cm = context_menu { + widget_template = wibox.widget { + { + { + { + { + widget = wibox.widget.imagebox, + resize = true, + valign = 'center', + halign = 'center', + id = 'icon_role', + }, + widget = wibox.container.constraint, + stragety = 'exact', + width = dpi(24), + height = dpi(24), + id = 'const', + }, + { + widget = wibox.widget.textbox, + valign = 'center', + halign = 'left', + id = 'text_role', + }, + layout = wibox.layout.fixed.horizontal, + }, + widget = wibox.container.margin, + }, + fg = beautiful.colorscheme.fg, + widget = wibox.container.background, + }, + spacing = dpi(10), + entries = { + { + name = 'Launch', + icon = gcolor.recolor_image(icondir .. 'launch.svg', beautiful.colorscheme.bg_purple), + callback = function() + self:toggle(capi.mouse.screen) + app:launch_uris_async() + end, + }, + { + name = 'Add to Desktop', + icon = gcolor.recolor_image(icondir .. 'desktop.svg', beautiful.colorscheme.bg_purple), + callback = function() + --TODO: Replace desktop:add_to_desktop() once rewritten + capi.awesome.emit_signal('desktop::add_to_desktop', { + label = app_name, + icon = icon_lookup:get_gicon_path(app:get_icon()) or '', + exec = GDesktopAppInfo:get_string('Exec'), + desktop_file = GDesktopAppInfo:get_filename() or '', + }) + end, + }, + { + name = 'Pin to Dock', + icon = gcolor.recolor_image(icondir .. 'pin.svg', beautiful.colorscheme.bg_purple), + callback = function() + dock:get_dock_for_screen(capi.mouse.screen):pin_element { + desktop_file = GDesktopAppInfo:get_filename(), + } + end, + }, + }, + } + + cm:connect_signal('mouse::leave', function() + cm.visible = false + end) + + w:buttons(gtable.join { + abutton { + modifiers = {}, + button = 1, + on_release = function() + app.launch_uris_async(app) + akeygrabber.stop() + self.searchbar:set_text('') + self:filter_apps('') + self:toggle(capi.mouse.screen) + end, + }, + abutton { + modifiers = {}, + button = 3, + on_release = function() + cm:toggle() + end, + }, + }) + self.app_table[app_id] = w + end + end +end + +local function levenshtein_distance(str1, str2) + local len1 = string.len(str1) + local len2 = string.len(str2) + local matrix = {} + local cost = 0 + + if (len1 == 0) then + return len2 + elseif (len2 == 0) then + return len1 + elseif (str1 == str2) then + return 0 + end + + for i = 0, len1, 1 do + matrix[i] = {} + matrix[i][0] = i + end + for j = 0, len2, 1 do + matrix[0][j] = j + end + + 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 matrix[len1][len2] +end + +function launcher:filter_apps(filter) + filter = filter or '' + self.grid:reset() + local app_list = {} + for _, app in pairs(self.app_table) do + if filter == '' + or fzy.has_match(filter, app.name) + or fzy.has_match(filter, app.category or '') + or fzy.has_match(filter, app.keywords or '') + then + table.insert(app_list, app) + end + end + table.sort(app_list, function(a, b) + return a.name < b.name + end) + --sort by lowest levenshtein distance + table.sort(app_list, function(a, b) + return levenshtein_distance(filter, a.name) < levenshtein_distance(filter, b.name) + end) + for _, app in ipairs(app_list) do + self.grid:add(app) + end +end + +function launcher:toggle(screen) + if not self.popup.visible then + self.popup.screen = screen + self.grid.forced_num_cols = math.floor((screen.geometry.width / 100 * 60) / 200) + self:focus_searchbar() + self.popup.widget:get_children_by_id('overflow')[1]:set_scroll_factor(0) + self.popup.visible = true + else + self.cursor = { + x = 1, y = 1, + } + self.popup.visible = false + end +end + +function launcher:selection_remove(x, y) + self.grid:get_widgets_at(y, x)[1].border_color = beautiful.colorscheme.border_color +end + +function launcher:selection_update(x, y) + local w_old = self.grid:get_widgets_at(y, x)[1] + local w_new = self.grid:get_widgets_at(self.cursor.y, self.cursor.x)[1] + w_old.border_color = beautiful.colorscheme.border_color + w_new.border_color = beautiful.colorscheme.bg_teal +end + +local up_offset = 0 +function launcher:move_down() + local row, _ = self.grid:get_dimension() + if self.cursor.y < row then + if not self.grid:get_widgets_at(self.cursor.y + 1, self.cursor.x) then return end + self.cursor.y = self.cursor.y + 1 + self:selection_update(self.cursor.x, self.cursor.y - 1) + up_offset = up_offset - 1 + if up_offset < 0 then + up_offset = 0 + end + end + + if up_offset == 0 then + if math.floor((capi.mouse.screen.geometry.width / 100 * 60) / 200) < self.cursor.y then + local overflow = self.popup.widget:get_children_by_id('overflow')[1] + overflow:set_scroll_factor(overflow:get_scroll_factor() + (1 / 24 * 127 / 100)) + end + end +end + +function launcher:move_up() + local row, _ = self.grid:get_dimension() + if self.cursor.y > 1 then + self.cursor.y = self.cursor.y - 1 + self:selection_update(self.cursor.x, self.cursor.y + 1) + up_offset = up_offset + 1 + if up_offset > 5 then + up_offset = 5 + end + end + if up_offset == 5 then + local overflow = self.popup.widget:get_children_by_id('overflow')[1] + + overflow:set_scroll_factor(overflow:get_scroll_factor() - (1 / 24 * 127 / 100)) + end +end + +function launcher:move_left() + if self.cursor.x > 1 then + self.cursor.x = self.cursor.x - 1 + self:selection_update(self.cursor.x + 1, self.cursor.y) + end +end + +function launcher:move_right() + local _, col = self.grid:get_dimension() + if self.cursor.x < col then + if not self.grid:get_widgets_at(self.cursor.y, self.cursor.x + 1) then return end + self.cursor.x = self.cursor.x + 1 + self:selection_update(self.cursor.x - 1, self.cursor.y) + end +end + +function launcher:focus_searchbar() + self.searchbar:focus() + self.popup.widget:get_children_by_id('searchbar_bg')[1].border_color = beautiful.colorscheme.bg_teal +end + +function launcher:unfocus_searchbar() + self.searchbar:unfocus() + self.popup.widget:get_children_by_id('searchbar_bg')[1].border_color = beautiful.colorscheme.border_color +end + +local instance = nil +if not instance then + instance = setmetatable(launcher, { + __call = function(self, screen) + self.app_table = {} + self.cursor = { + x = 1, + y = 1, + } + + self:fetch_apps() + self.grid = wibox.widget { + homogenous = true, + expand = false, + spacing = dpi(10), + -- 190 is the application element width + 10 spacing + forced_num_cols = math.floor((capi.mouse.screen.geometry.width / 100 * 50) / 190), + orientation = 'vertical', + layout = wibox.layout.grid, + } + + self.keygrabber = akeygrabber { + autostart = false, + stop_key = { 'Escape', 'Return' }, + mask_event_callback = false, + stop_callback = function(_, k) + if (k == 'Return') or (k == 'Escape') then + if k == 'Return' then + self.grid:get_widgets_at(self.cursor.y, self.cursor.x)[1].execute() + end + self:selection_remove(self.cursor.x, self.cursor.y) + self:toggle(capi.mouse.screen) + self:filter_apps('') + self.searchbar:set_text('') + self.keygrabber:stop() + end + self.cursor = { + x = 1, + y = 1, + } + end, + keybindings = { + akey { + modifiers = {}, + key = 'Down', + on_press = function() + self:move_down() + end, + }, + akey { + modifiers = {}, + key = 'Up', + on_press = function() + local y = self.cursor.y + self:move_up() + + -- If it didn't move we want to reenter the searchbar + if y - self.cursor.y == 0 then + self:selection_remove(self.cursor.x, self.cursor.y) + + self.keygrabber:stop() + self:focus_searchbar() + end + end, + }, + akey { + modifiers = {}, + key = 'Left', + on_press = function() + self:move_left() + end, + }, + akey { + modifiers = {}, + key = 'Right', + on_press = function() + self:move_right() + end, + }, + }, + } + + self.searchbar = inputbox { + text_hint = 'Search...', + mouse_focus = false, + font = beautiful.font .. ' regular 12', + fg = beautiful.colorscheme.fg, + } + self.searchbar:connect_signal('button::press', function() + self:selection_remove(self.cursor.x, self.cursor.y) + akeygrabber.stop() + self:focus_searchbar() + end) + self.searchbar:connect_signal('inputbox::keypressed', function(_, _, key) + if key == 'Escape' then + self:unfocus_searchbar() + self:toggle(capi.mouse.screen) + self.searchbar:set_text('') + self:filter_apps('') + elseif key == 'Return' then + self:unfocus_searchbar() + self:toggle(capi.mouse.screen) + self.grid:get_widgets_at(self.cursor.x, self.cursor.y)[1].execute() + self:filter_apps('') + elseif key == 'Down' then + if not (self.keygrabber.running == akeygrabber.current_instance) then + self:selection_update(1, 1) + self:unfocus_searchbar() + self.keygrabber:start() + end + else + self:filter_apps(self.searchbar:get_text()) + end + end) + --#region Hover signals to change the cursor to a text cursor + local old_cursor, old_wibox + self.searchbar:connect_signal('mouse::enter', function() + local wid = capi.mouse.current_wibox + if wid then + old_cursor, old_wibox = wid.cursor, wid + wid.cursor = 'xterm' + end + end) + self.searchbar:connect_signal('mouse::leave', function() + old_wibox.cursor = old_cursor + old_wibox = nil + end) + --#endregion + + self:filter_apps('') + + self.popup = apopup { + widget = { + { + { + { + { + { + self.searchbar.widget, + halign = 'left', + valign = 'center', + id = 'searchbar', + buttons = { gtable.join( + abutton { + modifiers = {}, + button = 1, + on_press = function() + self:focus_searchbar() + end, + } + ), }, + widget = wibox.container.place, + }, + widget = wibox.container.margin, + margins = 5, + }, + widget = wibox.container.constraint, + strategy = 'exact', + height = dpi(50), + }, + widget = wibox.container.background, + bg = beautiful.colorscheme.bg, + fg = beautiful.colorscheme.fg, + border_color = beautiful.colorscheme.border_color, + border_width = dpi(2), + shape = beautiful.shape[4], + id = 'searchbar_bg', + }, + { + { + self.grid, + layout = require('src.lib.overflow_widget.overflow').vertical, + scrollbar_width = 0, + id = 'overflow', + step = dpi(100), + }, + height = dpi((122 * 5) + 10 * 4), + strategy = 'exact', + widget = wibox.container.constraint, + }, + spacing = dpi(10), + layout = wibox.layout.fixed.vertical, + }, + margins = dpi(20), + widget = wibox.container.margin, + }, + ontop = true, + visible = true, + stretch = false, + screen = screen, + placement = aplacement.centered, + bg = beautiful.colorscheme.bg, + border_color = beautiful.colorscheme.border_color, + border_width = dpi(2), + } + + -- Let the popup render once + gtimer.delayed_call(function() + self.popup.visible = false + end) + end, + }) +end +return instance diff --git a/awesome/src/modules/application_launcher/application.lua b/awesome/src/modules/application_launcher/application.lua deleted file mode 100644 index 88a7774..0000000 --- a/awesome/src/modules/application_launcher/application.lua +++ /dev/null @@ -1,475 +0,0 @@ --------------------------------------- --- This is the application launcher -- --------------------------------------- - --- Awesome libs -local abutton = require('awful.button') -local aspawn = require('awful.spawn') -local base = require('wibox.widget.base') -local beautiful = require('beautiful') -local dpi = require('beautiful').xresources.apply_dpi -local gcolor = require('gears.color') -local gfilesystem = require('gears').filesystem -local Gio = require('lgi').Gio -local gtable = require('gears.table') -local wibox = require('wibox') - --- Local libs -local config = require('src.tools.config') -local hover = require('src.tools.hover') -local cm = require('src.modules.context_menu.init') -local dock = require('src.modules.crylia_bar.dock') -local icon_lookup = require('src.tools.gio_icon_lookup')() - -local capi = { - awesome = awesome, - mouse = mouse, -} - -local icondir = gfilesystem.get_configuration_dir() .. 'src/assets/icons/context_menu/' - -local application_grid = { mt = {} } - ---[[ - Make sure that the config folder exists and the applications.json - This is done here once because it would be unnecessary to do it for every instance -]] -do - local dir = gfilesystem.get_configuration_dir() .. 'src/config' - gfilesystem.make_directories(dir) - dir = dir .. '/applications.json' - if not gfilesystem.file_readable(dir) then - aspawn('touch ' .. dir) - end -end - ---#region wibox.widget.base boilerplate -function application_grid:layout(_, width, height) - if self._private.widget then - return { base.place_widget_at(self._private.widget, 0, 0, width, height) } - end -end - -function application_grid:fit(context, width, height) - local w, h = 0, 0 - if self._private.widget then - w, h = base.fit_widget(self, context, self._private.widget, width, height) - end - return w, h -end - -application_grid.set_widget = base.set_widget_common - -function application_grid:get_widget() - return self._private.widget -end - ---#endregion - ---[[ - Calculate the levenshtein distance between two strings to determine how similar they are - I stole this from a random github gist -]] -local function levenshtein_distance(str1, str2) - local len1 = string.len(str1) - local len2 = string.len(str2) - local matrix = {} - local cost = 0 - - if (len1 == 0) then - return len2 - elseif (len2 == 0) then - return len1 - elseif (str1 == str2) then - return 0 - end - - for i = 0, len1, 1 do - matrix[i] = {} - matrix[i][0] = i - end - for j = 0, len2, 1 do - matrix[0][j] = j - end - - 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 matrix[len1][len2] -end - ----Gets all .desktop files found and filters them based on their visibility ----It used Gio.AppInfo and Gio.DesktopAppInfo to get the information ----@return table -local function get_applications_from_file() - local list = {} - local app_info = Gio.AppInfo - --Get all .desktop files - local apps = app_info.get_all() - for _, app in ipairs(apps) do - if app.should_show(app) then -- check no display - --Create a new .desktop object - local desktop_app_info = Gio.DesktopAppInfo.new(app_info.get_id(app)) - local app_widget = wibox.widget { - { - { - { - { - { -- Icon - valign = 'center', - halign = 'center', - image = icon_lookup:get_gicon_path(app_info.get_icon(app)) or - icon_lookup:get_gicon_path(app_info.get_icon(app), - Gio.DesktopAppInfo.get_string(desktop_app_info, 'X-AppImage-Old-Icon')) or '', - resize = true, - widget = wibox.widget.imagebox, - }, - height = dpi(64), - width = dpi(64), - strategy = 'exact', - widget = wibox.container.constraint, - }, - { - { -- Name - text = app_info.get_name(app), - align = 'center', - valign = 'center', - widget = wibox.widget.textbox, - }, - strategy = 'exact', - width = dpi(170), - -- Prevents widget from overflowing - height = dpi(40), - widget = wibox.container.constraint, - }, - layout = wibox.layout.fixed.vertical, - }, - halign = 'center', - valign = 'center', - widget = wibox.container.place, - }, - margins = dpi(10), - widget = wibox.container.margin, - }, - name = app_info.get_name(app), - comment = Gio.DesktopAppInfo.get_string(desktop_app_info, 'Comment') or '', - exec = Gio.DesktopAppInfo.get_string(desktop_app_info, 'Exec'), - keywords = Gio.DesktopAppInfo.get_string(desktop_app_info, 'Keywords') or '', - categories = Gio.DesktopAppInfo.get_categories(desktop_app_info) or '', - terminal = Gio.DesktopAppInfo.get_string(desktop_app_info, 'Terminal') == 'true', - actions = Gio.DesktopAppInfo.list_actions(desktop_app_info), - desktop_file = Gio.DesktopAppInfo.get_filename(desktop_app_info) or '', - border_color = beautiful.colorscheme.border_color, - border_width = dpi(2), - bg = beautiful.colorscheme.bg1, - fg = beautiful.colorscheme.fg, - shape = beautiful.shape[4], - widget = wibox.container.background, - } - local context_menu = cm { - widget_template = wibox.widget { - { - { - { - { - widget = wibox.widget.imagebox, - resize = true, - valign = 'center', - halign = 'center', - id = 'icon_role', - }, - widget = wibox.container.constraint, - stragety = 'exact', - width = dpi(24), - height = dpi(24), - id = 'const', - }, - { - widget = wibox.widget.textbox, - valign = 'center', - halign = 'left', - id = 'text_role', - }, - layout = wibox.layout.fixed.horizontal, - }, - widget = wibox.container.margin, - }, - widget = wibox.container.background, - }, - spacing = dpi(10), - entries = { - { - name = 'Execute as sudo', - icon = gcolor.recolor_image(icondir .. 'launch.svg', - beautiful.colorscheme.bg_purple), - callback = function() - capi.awesome.emit_signal('application_launcher::show') - aspawn('/home/crylia/.config/awesome/src/scripts/start_as_admin.sh ' .. app_widget.exec) - end, - }, - { - name = 'Pin to dock', - icon = gcolor.recolor_image(icondir .. 'pin.svg', - beautiful.colorscheme.bg_purple), - callback = function() - -- Open dock.js and read all its content into a table, add the new app into the table and write it back - --[[ async.read_json(gfilesystem.get_configuration_dir() .. 'src/config/dock.json', function(data) - table.insert(data, { - name = app_widget.name or '', - icon = icon_lookup.get_gicon_path(app_info.get_icon(app)) or - icon_lookup.get_gicon_path(app_info.get_icon(app), - Gio.DesktopAppInfo.get_string(desktop_app_info, 'X-AppImage-Old-Icon')) or '', - comment = app_widget.comment or '', - exec = app_widget.exec or '', - keywords = app_widget.keywords or '', - categories = app_widget.categories or '', - terminal = app_widget.terminal or '', - actions = app_widget.actions or '', - desktop_file = Gio.DesktopAppInfo.get_filename(desktop_app_info) or '' - }) - - async.write_json(gfilesystem.get_configuration_dir() .. 'src/config/dock.json', data, function() - capi.awesome.emit_signal('dock::changed') - end) - end) ]] - dock:get_dock_for_screen(capi.mouse.screen):pin_element { - desktop_file = Gio.DesktopAppInfo.get_filename(desktop_app_info) or '', - } - end, - }, - { - name = 'Add to desktop', - icon = gcolor.recolor_image(icondir .. 'desktop.svg', - beautiful.colorscheme.bg_purple), - callback = function() - capi.awesome.emit_signal('application_launcher::show') - capi.awesome.emit_signal('desktop::add_to_desktop', { - label = app_info.get_name(app), - icon = icon_lookup:get_gicon_path(app_info.get_icon(app)) or '', - exec = Gio.DesktopAppInfo.get_string(desktop_app_info, 'Exec'), - desktop_file = Gio.DesktopAppInfo.get_filename(desktop_app_info) or '', - }) - end, - }, - }, - } - - -- Hide context menu when the mouse leaves it - context_menu:connect_signal('mouse::leave', function() - context_menu.visible = false - end) - - -- Execute command on left click and hide launcher, right click to show context menu - app_widget:buttons( - gtable.join( - abutton { - modifiers = {}, - button = 1, - on_release = function() - Gio.AppInfo.launch_uris_async(app) - capi.awesome.emit_signal('application_launcher::show') - end, - }, - abutton { - modifiers = {}, - button = 3, - on_release = function() - context_menu:toggle() - end, - } - ) - ) - - hover.bg_hover { widget = app_widget } - table.insert(list, app_widget) - end - end - return list -end - ----Takes the search filter and returns a list of applications in the correct order ----@param search_filter any -function application_grid:set_applications(search_filter) - local filter = search_filter or self.filter or '' - -- Reset to first position - self._private.curser = { - x = 1, - y = 1, - } - - local grid = wibox.widget { - homogenous = true, - expand = false, - spacing = dpi(10), - id = 'grid', - -- 200 is the application element width + 10 spacing - forced_num_cols = math.floor((capi.mouse.screen.geometry.width / 100 * 60) / 200), - forced_num_rows = 7, - orientation = 'vertical', - layout = wibox.layout.grid, - } - - -- Read the dock.json file and get all apps, these are needed to read/write the launch count - local data = config.read_json(gfilesystem.get_configuration_dir() .. 'src/config/applications.json') - local mylist = {} - - for _, application in ipairs(self.app_list) do - -- Match the filter for the name, categories and keywords - if string.match(string.lower(application.name or ''), string.lower(filter)) or - string.match(string.lower(application.categories or ''), string.lower(filter)) or - string.match(string.lower(application.keywords or ''), string.lower(filter)) then - - -- If there are no elements in the table, set everything to 0 - if #data == 0 then - application.counter = 0 - end - -- Read the counter for the matching app - for _, app in ipairs(data) do - if app.desktop_file == application.desktop_file then - application.counter = app.counter or 0 - break; - else - application.counter = 0 - end - end - - table.insert(mylist, application) - end - end - - -- Sort the applications using the levenshtein algorithm - table.sort(mylist, function(a, b) - return levenshtein_distance(filter, a.name) < levenshtein_distance(filter, b.name) - end) - --Sort the applications using the counter - table.sort(mylist, function(a, b) - return a.counter > b.counter - end) - - -- Add the apps one by one into the grid and read its position - for _, app in ipairs(mylist) do - grid:add(app) - - -- Get the current position in the grid of the app as a table - local pos = grid:get_widget_position(app) - - -- Check if the curser is currently at the same position as the app - capi.awesome.connect_signal( - 'update::selected', - function() - if self._private.curser.y == pos.row and self._private.curser.x == pos.col then - app.border_color = beautiful.colorscheme.bg_purple - else - app.border_color = beautiful.colorscheme.bg1 - end - end - ) - end - - capi.awesome.emit_signal('update::selected') - self:set_widget(grid) -end - --- Move the curser up by one, making sure it doesn't go out of bounds -function application_grid:move_up() - self._private.curser.y = self._private.curser.y - 1 - if self._private.curser.y < 1 then - self._private.curser.y = 1 - end - capi.awesome.emit_signal('update::selected') -end - --- Move the curser down by one, making sure it doesn't go out of bounds -function application_grid:move_down() - self._private.curser.y = self._private.curser.y + 1 - local grid_rows, _ = self:get_widget():get_dimension() - if self._private.curser.y > grid_rows then - self._private.curser.y = grid_rows - end - capi.awesome.emit_signal('update::selected') -end - --- Move the curser left by one, making sure it doesn't go out of bounds -function application_grid:move_left() - self._private.curser.x = self._private.curser.x - 1 - if self._private.curser.x < 1 then - self._private.curser.x = 1 - end - capi.awesome.emit_signal('update::selected') -end - --- Move the curser right by one, making sure it doesn't go out of bounds -function application_grid:move_right() - self._private.curser.x = self._private.curser.x + 1 - local _, grid_cols = self:get_widget():get_dimension() - if self._private.curser.x > grid_cols then - self._private.curser.x = grid_cols - end - capi.awesome.emit_signal('update::selected') -end - ---- Execute the currently selected app and add to the launch count -function application_grid:execute() - -- Get the app at the current x,y - local selected_widget = self:get_widget():get_widgets_at(self._private.curser.y, - self._private.curser.x)[1] - -- Launch the application async - Gio.AppInfo.launch_uris_async(Gio.AppInfo.create_from_commandline(selected_widget.exec, nil, 0)) - - local data = config.read_json(gfilesystem.get_configuration_dir() .. 'src/config/applications.json') - -- Increase the counter by one then rewrite to the file, its a bit hacky but it works - for _, prog in ipairs(data) do - if prog.desktop_file:match(selected_widget.desktop_file) then - prog.counter = prog.counter + 1 - -- I don't like goto's, but its the easiest way here(PR is welcome). - goto continue - end - end - do - local prog = { - name = selected_widget.name, - desktop_file = selected_widget.desktop_file, - counter = 1, - } - table.insert(data, prog) - end - ::continue:: - config.write_json(gfilesystem.get_configuration_dir() .. 'src/config/applications.json', data) -end - --- Reset the grid cursor -function application_grid:reset() - self._private.curser = { - x = 1, - y = 1, - } - capi.awesome.emit_signal('update::selected') -end - -function application_grid.new(args) - args = args or {} - - local w = base.make_widget(nil, nil, { enable_properties = true }) - - gtable.crush(w, application_grid, true) - - w.app_list = get_applications_from_file() - - w:set_applications() - return w -end - -return setmetatable(application_grid, { __call = function(...) return application_grid.new(...) end }) diff --git a/awesome/src/modules/application_launcher/init.lua b/awesome/src/modules/application_launcher/init.lua deleted file mode 100644 index d5d28e4..0000000 --- a/awesome/src/modules/application_launcher/init.lua +++ /dev/null @@ -1,213 +0,0 @@ --------------------------------------- --- This is the application launcher -- --------------------------------------- - --- Awesome Libs -local abutton = require('awful.button') -local akeygrabber = require('awful.keygrabber') -local aplacement = require('awful.placement') -local apopup = require('awful.popup') -local awidget = require('awful.widget') -local beautiful = require('beautiful') -local dpi = require('beautiful').xresources.apply_dpi -local gtable = require('gears.table') -local wibox = require('wibox') -local gtimer = require('gears.timer') - --- Own libs -local app_grid = require('src.modules.application_launcher.application') -local input = require('src.modules.inputbox') - -local capi = { - awesome = awesome, - mouse = mouse, -} - --- This grid object is shared to avoid having multipe unnecessary instances -local application_grid = app_grid {} - -local application_launcher = {} - -function application_launcher.new(args) - args = args or {} - - -- Create a new inputbox - local searchbar = input { - text_hint = 'Search...', - mouse_focus = true, - fg = beautiful.colorscheme.fg, - password_mode = true, - } - -- Application launcher popup - local application_container = apopup { - widget = { - { - { - { - { - { - { - searchbar.widget, - halign = 'left', - valign = 'center', - widget = wibox.container.place, - }, - widget = wibox.container.margin, - margins = 5, - }, - widget = wibox.container.constraint, - strategy = 'exact', - height = dpi(50), - }, - widget = wibox.container.background, - bg = beautiful.colorscheme.bg, - fg = beautiful.colorscheme.fg, - border_color = beautiful.colorscheme.border_color, - border_width = dpi(2), - shape = beautiful.shape[4], - id = 'searchbar_bg', - }, - { - application_grid, - layout = require('src.lib.overflow_widget.overflow').vertical, - scrollbar_width = 0, - step = dpi(100), - }, - spacing = dpi(10), - layout = wibox.layout.fixed.vertical, - }, - margins = dpi(20), - widget = wibox.container.margin, - }, - height = args.screen.geometry.height / 100 * 60, - strategy = 'exact', - widget = wibox.container.constraint, - }, - ontop = true, - visible = false, - stretch = false, - screen = args.screen, - placement = aplacement.centered, - bg = beautiful.colorscheme.bg, - border_color = beautiful.colorscheme.border_color, - border_width = dpi(2), - } - - -- Delayed call to give the popup some time to evaluate its width - gtimer.delayed_call(function() - if application_container.width then - application_container.widget.width = application_container.width - end - end) - - gtable.crush(application_container, application_launcher, true) - - --#region Hover signals to change the cursor to a text cursor - local old_cursor, old_wibox - searchbar:connect_signal('mouse::enter', function() - local wid = capi.mouse.current_wibox - if wid then - old_cursor, old_wibox = wid.cursor, wid - wid.cursor = 'xterm' - end - end) - searchbar:connect_signal('mouse::leave', function() - old_wibox.cursor = old_cursor - old_wibox = nil - end) - --#endregion - - -- Get a reference to the searchbar background value - local searchbar_bg = application_container.widget:get_children_by_id('searchbar_bg')[1] - - -- Toggle visible for the application launcher and init the searchbar - capi.awesome.connect_signal('application_launcher::show', function() - if capi.mouse.screen == args.screen then - capi.awesome.emit_signal('update::selected') - if capi.mouse.screen == args.screen then - application_container.visible = not application_container.visible - end - if application_container.visible then - searchbar_bg.border_color = beautiful.colorscheme.bg_blue - searchbar:focus() - else - searchbar:set_text('') - akeygrabber.stop() - end - end - end) - - -- Hide the application launcher when the keygrabber stops and reset the searchbar - searchbar:connect_signal('inputbox::stop', function(_, stop_key) - if stop_key == 'Escape' then - capi.awesome.emit_signal('application_launcher::show') - end - searchbar:set_text('') - application_grid:set_applications(searchbar:get_text()) - searchbar_bg.border_color = beautiful.colorscheme.border_color - end) - - -- When started change the background for the searchbar - searchbar:connect_signal('inputbox::start', function() - searchbar_bg.border_color = beautiful.colorscheme.bg_blue - end) - - -- On every keypress in the searchbar check for certain inputs - searchbar:connect_signal('inputbox::keypressed', function(_, modkey, key) - if key == 'Escape' then -- Escape to stop the keygrabber, hide the launcher and reset the searchbar - searchbar:unfocus() - capi.awesome.emit_signal('application_launcher::show') - application_grid:reset() - searchbar:set_text('') - elseif key == 'Return' then - application_grid:execute() - capi.awesome.emit_signal('application_launcher::show') - searchbar:set_text('') - application_grid:set_applications(searchbar:get_text()) - searchbar_bg.border_color = beautiful.colorscheme.border_color - elseif key == 'Down' then --If down or right is pressed initiate the grid navigation - if key == 'Down' then - application_grid:move_down() - elseif key == 'Right' then - application_grid:move_right() - end - searchbar:unfocus() - --New keygrabber to allow for key navigation - akeygrabber.run(function(mod, key2, event) - if event == 'press' then - if key2 == 'Down' then - application_grid:move_down() - elseif key2 == 'Up' then - local old_y = application_grid._private.curser.y - application_grid:move_up() - if old_y - application_grid._private.curser.y == 0 then - searchbar:focus() - end - elseif key2 == 'Left' then - application_grid:move_left() - elseif key2 == 'Right' then - application_grid:move_right() - elseif key2 == 'Return' then - akeygrabber.stop() - application_grid:execute() - capi.awesome.emit_signal('application_launcher::show') - application_grid:reset() - searchbar:set_text('') - application_grid:set_applications(searchbar:get_text()) - elseif key2 == 'Escape' then - capi.awesome.emit_signal('application_launcher::show') - application_grid:reset() - searchbar:set_text('') - application_grid:set_applications(searchbar:get_text()) - akeygrabber.stop() - end - end - end) - searchbar_bg.border_color = beautiful.colorscheme.border_color - end - -- Update the applications in the grid - application_grid:set_applications(searchbar:get_text()) - end) -end - -return setmetatable(application_launcher, { __call = function(_, ...) return application_launcher.new(...) end })