local setmetatable = setmetatable local table = table local ipairs = ipairs local pairs = pairs -- Awesome Libs local abutton = require('awful.button') 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 gcolor = require('gears.color') local gfilesystem = require('gears.filesystem') local gtable = require('gears.table') local gtimer = require('gears.timer') local lgi = require('lgi') local Gio = lgi.Gio local wibox = require('wibox') -- Local libs local config = require('src.tools.config') local context_menu = require('src.modules.context_menu') local hover = require('src.tools.hover') local icon_lookup = require('src.tools.gio_icon_lookup')() local capi = { awesome = awesome, client = client, mouse = mouse, screen = screen, } local icondir = gfilesystem.get_configuration_dir() .. 'src/assets/icons/' local elements = { ['pinned'] = {}, ['applauncher_starter'] = {}, ['running'] = {}, } local instances = {} local dock = { mt = {} } --[[ Json format of dock.json, running apps won't be saved [ { "applauncher_starter": { { "name": "Application Launcher", "icon": "/usr/share/icons/Papirus/48x48/apps/xfce4-appfinder.svg", } }, "pinned": [ { "name": "firefox", "icon": "/usr/share/icons/Papirus/48x48/apps/firefox.svg", "exec": "firefox", "desktop_file": "/usr/share/applications/firefox.desktop" }, { "name": "discord", "icon": "/usr/share/icons/Papirus/48x48/apps/discord.svg", "exec": "discord", "desktop_file": "/usr/share/applications/discord.desktop" } ], } ] ]] function dock:toggle() self.popup.visible = not self.popup.visible end function dock:write_elements_to_file_async(callback) --create a local copy of the elements["pinned"] table and only set the desktop_file key from its children local elements_copy = { pinned = {} } for _, element in ipairs(elements['pinned']) do table.insert(elements_copy['pinned'], { desktop_file = element.desktop_file }) end config.write_json(gfilesystem.get_configuration_dir() .. 'src/config/dock_' .. self.screen.index .. '.json', elements_copy['pinned'], callback) end ---Read the content of dock.json and get the content as a table ---@param callback function Called after the elements have been set, no values are passed function dock:read_elements_from_file_async(callback) local data = config.read_json(gfilesystem.get_configuration_dir() .. 'src/config/dock_' .. self.screen.index .. '.json') -- Make sure to not set the running key to nil on accident for _, v in ipairs(data) do local w = self:get_element_widget(v.desktop_file) table.insert(elements['pinned'], w) end if callback then callback() end end ---Creates a pinned widget for the dock and adds it into the elements table ---@param desktop_file string .desktop file path ---@return nil function dock:get_element_widget(desktop_file) if not desktop_file then return end local GDesktopAppInfo = Gio.DesktopAppInfo.new_from_filename(desktop_file) local icon = icon_lookup:get_gicon_path(nil, GDesktopAppInfo.get_string(GDesktopAppInfo, 'Icon')) or icon_lookup:get_gicon_path(nil, Gio.DesktopAppInfo.get_string(GDesktopAppInfo, 'X-AppImage-Old-Icon')) or '' local widget = wibox.widget { { { { { widget = wibox.widget.imagebox, image = icon, valign = 'center', halign = 'center', resize = true, id = 'icon_role', }, widget = wibox.container.constraint, width = beautiful.user_config.dock_icon_size, height = beautiful.user_config.dock_icon_size, }, widget = wibox.container.margin, margins = dpi(5), }, widget = wibox.container.constraint, width = beautiful.user_config.dock_icon_size + dpi(10), -- + margins height = beautiful.user_config.dock_icon_size + dpi(10), strategy = 'exact', }, bg = beautiful.colorscheme.bg1, shape = beautiful.shape[8], widget = wibox.container.background, } local action_entries = {} for _, action in ipairs(Gio.DesktopAppInfo.list_actions(GDesktopAppInfo)) do table.insert(action_entries, { name = Gio.DesktopAppInfo.get_action_name(GDesktopAppInfo, action) or '', icon = icon_lookup:get_gicon_path(nil, GDesktopAppInfo.get_string(GDesktopAppInfo, 'Icon')) or icon_lookup:get_gicon_path(nil, Gio.DesktopAppInfo.get_string(GDesktopAppInfo, 'X-AppImage-Old-Icon')) or gcolor.recolor_image(icondir .. 'entry.svg', beautiful.colorscheme.bg_yellow), callback = function() Gio.DesktopAppInfo.launch_action(GDesktopAppInfo, action) end, }) end table.insert(action_entries, { name = 'Remove from Dock', icon = gcolor.recolor_image(icondir .. 'context_menu/entry.svg', beautiful.colorscheme.bg_yellow), callback = function() local data = config.read_json(gfilesystem.get_configuration_dir() .. 'src/config/dock_' .. self.screen.index .. '.json') for i, v in ipairs(data) do if v.desktop_file == desktop_file then if type(data) == 'table' then table.remove(data, i) end break end end config.write_json(gfilesystem.get_configuration_dir() .. 'src/config/dock_' .. self.screen.index .. '.json', data) self:remove_element_widget(widget) end, }) widget.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, }, widget = wibox.container.background, }, spacing = dpi(10), entries = action_entries, } widget.cm:connect_signal('mouse::leave', function() widget.cm.visible = false self.cm_open = widget.cm.visible end) hover.bg_hover { widget = widget, overlay = 12, press_overlay = 24, } local exec = Gio.DesktopAppInfo.get_string(GDesktopAppInfo, 'Exec') widget:buttons(gtable.join { abutton({}, 1, function() Gio.AppInfo.launch_uris_async(Gio.AppInfo.create_from_commandline(exec, nil, 0)) end), abutton({}, 3, function() widget.cm:toggle() self.cm_open = widget.cm.visible end), }) table.insert(elements['pinned'], widget) self:emit_signal('dock::element_added', widget) end ---Removes a given widget from the dock ---@param widget wibox.widget The widget to remove function dock:remove_element_widget(widget) if not widget then return end for k, v in pairs(elements['pinned']) do if v == widget then table.remove(elements['pinned'], k) self:emit_signal('dock::element_removed', widget) return end end end ---Pins an element to the dock by adding it to the pinned table, then writes the table to the file ---emits the signal `dock::pin_element` then successfully added to the table ---@param args {desktop_file: string} The path to the .desktop file function dock:pin_element(args) if not args then return end local e = args.desktop_file self:emit_signal('dock::pin_element', e) self:write_elements_to_file_async() end function dock:add_start_element() local widget = wibox.widget { { { { { widget = wibox.widget.imagebox, image = gfilesystem.get_configuration_dir() .. 'src/assets/CT.svg', valign = 'center', halign = 'center', resize = true, id = 'icon_role', }, widget = wibox.container.constraint, width = beautiful.user_config.dock_icon_size, height = beautiful.user_config.dock_icon_size, }, widget = wibox.container.margin, margins = dpi(5), }, widget = wibox.container.constraint, width = beautiful.user_config.dock_icon_size + dpi(10), -- + margins height = beautiful.user_config.dock_icon_size + dpi(10), strategy = 'exact', }, bg = beautiful.colorscheme.bg1, shape = beautiful.shape[8], widget = wibox.container.background, } hover.bg_hover { widget = widget, overlay = 12, press_overlay = 24, } widget:buttons(gtable.join { abutton({}, 1, function() capi.awesome.emit_signal('application_launcher::show') end), }) return widget end function dock:unpin_element(args) if not args then return end for index, value in ipairs(elements['pinned']) do if value == args.desktop_file then table.remove(elements['pinned'], index) break; end end self:emit_signal('dock::unpin_element', args.desktop_file) self:write_elements_to_file_async() end function dock:get_all_elements() return elements end function dock:get_applauncher_starter_element() return elements['applauncher_starter'] end function dock:get_pinned_elements() return elements['pinned'] end function dock:get_running_elements() return elements['running'] end function dock:get_dock_for_screen(screen) return instances[screen] end local function check_for_dock_hide(self, a_popup) if self.cm_open then return end local clients_on_tag = self.screen.selected_tag:clients() -- If there is no client on the current tag show the dock if #clients_on_tag < 1 then self.visible = true return end -- If there is a maximized client hide the dock and if fullscreened hide the activation area for _, client in ipairs(clients_on_tag) do if client.maximized or client.fullscreen then dock.visible = false if client.fullscreen then a_popup.visible = false capi.awesome.emit_signal('notification_center_activation::toggle', self.screen, false) end elseif not client.fullscreen then a_popup.visible = true capi.awesome.emit_signal('notification_center_activation::toggle', self.screen, true) end end if self.screen == capi.mouse.screen then local minimized = false for _, c in ipairs(clients_on_tag) do if c.minimized then minimized = true else minimized = false local y = c:geometry().y local h = c.height if (y + h) >= self.screen.geometry.height - beautiful.user_config.dock_icon_size - 35 then self.visible = false return else self.visible = true end end end if minimized then self.visible = true end else self.visible = false end end function dock:activation_area() local activation = apopup { widget = { width = self.screen.geometry.width / 4, height = 1, strategy = 'exact', widget = wibox.container.constraint, }, ontop = true, bg = gcolor.transparent, visible = false, screen = self.screen, type = 'dock', placement = function(c) aplacement.bottom(c) end, } -- Call the function every second to check if the dock needs to be hidden local dock_hide = gtimer { timeout = 1, autostart = true, call_now = true, callback = function() check_for_dock_hide(self, activation) end, } activation:connect_signal('mouse::enter', function() if #self.screen.clients < 1 then self.visible = true dock_hide:stop() return end for _, c in ipairs(self.screen.clients) do if not c.fullscreen then self.visible = true dock_hide:stop() end end end) self:connect_signal('mouse::enter', function() dock_hide:stop() end) self:connect_signal('mouse::leave', function() --[[ if cm_open then return end ]] check_for_dock_hide(self, activation) dock_hide:again() end) end function dock.new(args) args = args or {} local w = apopup { widget = { { { spacing = dpi(5), id = 'applauncher_starter', layout = wibox.layout.fixed.horizontal, }, wibox.widget.separator { forced_width = dpi(2), forced_height = dpi(20), thickness = dpi(2), color = beautiful.colorscheme.border_color, }, { spacing = dpi(5), id = 'pinned', layout = wibox.layout.fixed.horizontal, }, { id = 'running', spacing = dpi(5), layout = wibox.layout.fixed.horizontal, }, spacing = dpi(10), id = 'elements', layout = wibox.layout.fixed.horizontal, }, widget = wibox.container.margin, margins = dpi(5), }, ontop = true, visible = true, placement = function(c) aplacement.bottom(c, { margins = dpi(10) }) end, bg = beautiful.colorscheme.bg, screen = args.screen, } gtable.crush(w, dock) instances[args.screen] = w w:activation_area() w.task_list = awidget.tasklist { screen = args.screen, layout = wibox.layout.fixed.horizontal, filter = awidget.tasklist.filter.alltags, update_function = function(widget, _, _, _, clients) widget:reset() if #clients == 0 then return widget end widget:add { { widget = wibox.widget.separator, forced_height = dpi(20), forced_width = dpi(2), thickness = dpi(2), color = beautiful.colorscheme.border_color, }, widget = wibox.container.margin, right = dpi(5), } for _, client in ipairs(clients) do local element = wibox.widget { { { { { widget = wibox.widget.imagebox, image = client.icon, valign = 'center', halign = 'center', resize = true, id = 'icon_role', }, widget = wibox.container.constraint, width = beautiful.user_config.dock_icon_size, height = beautiful.user_config.dock_icon_size, }, widget = wibox.container.margin, margins = dpi(5), }, widget = wibox.container.constraint, width = beautiful.user_config.dock_icon_size + dpi(10), -- + margins height = beautiful.user_config.dock_icon_size + dpi(10), strategy = 'exact', }, bg = beautiful.colorscheme.bg1, shape = beautiful.shape[8], widget = wibox.container.background, } hover.bg_hover { widget = element, overlay = 12, press_overlay = 24, } element:buttons(gtable.join( abutton({}, 1, function() if client == client.focus then client.minimized = true else if client.first_tag then client.first_tag:view_only() end client:emit_signal('request::activate') client:raise() end end), abutton({}, 3, function() --TODO: Add context menu with options end) )) widget:add(element) widget:set_spacing(dpi(5)) end return widget end, } w.widget.elements.applauncher_starter:add(w:add_start_element()) w.widget.elements.running:add(w.task_list) w:connect_signal('dock::element_added', function(_, widget) w.widget.elements.pinned:add(widget) end) w:connect_signal('dock::element_removed', function(_, widget) w.widget.elements.pinned:remove_widgets(widget) end) w:connect_signal('dock::pin_element', function(_, element) w:get_element_widget(element) end) capi.awesome.connect_signal('dock::pin_element', function(args) w:pin_element(args) end) w:connect_signal('dock::unpin_element', function(_, widget) w:remove_element_widget(widget) end) w:read_elements_from_file_async() return w end return setmetatable(dock, { __call = function(_, ...) return dock.new(...) end })