-- This file is part of Reno desktop. -- -- Reno desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -- -- Reno desktop is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License along with Reno desktop. If not, see . -- Dismal - not your average run prompt -- Reference implementation of a generic widget local awmtk2 = require("awmtk2") local wibox = require("wibox") local gears = require("gears") local awful = require("awful") local ask = require("asckey") local xdg_search = function(name,rlimit,sorting_method) local ranked_results = {} if sorting_method == "usage" then local filter = {} local keys = {} for k,v in pairs(xdg.apps) do if not v.count then v.count = 0 end if v.name:lower():find(name,nil,true) then if not filter[v.count] then table.insert(keys, v.count) filter[v.count] = {} end table.insert(filter[v.count],{k,v}) end end table.sort(keys,function(a,b) return a > b end) local count = 0 local exit = false for k = 1,rlimit do local i = keys[k] if not filter[i] then break end for _,v in pairs(filter[i]) do table.insert(ranked_results, v) count = count + 1 if count >= rlimit then exit = true break end end if exit == true then break end end elseif sorting_method == "recent" then local most_recent = 0 for k,v in pairs(xdg.apps) do if v.name:lower():find(name,nil,true) and v.atime and v.atime >= most_recent then most_recent = v.atime table.insert(ranked_results,1,{k,v}) end if #ranked_results > rlimit then table.remove(ranked_results,rlimit+1) end end end return ranked_results end return function(args) local style = awmtk2.create_style("dismal", awmtk2.generic.popup,args.style,args.vertical) local templates = awmtk2.create_template_lib("dismal",awmtk2.templates,args.templates) local t = awmtk2.build_templates(templates,style,args.vertical) local dismal = awful.popup(t.popup({ id = "dismal_root", layout = wibox.layout.fixed.horizontal, spacing = style.container.spacing })) local available_positions = { top_left = awful.placement.top_left, top_right = awful.placement.top_right, bottom_left = awful.placement.bottom_left, bottom_right = awful.placement.bottom_right, left = awful.placement.left, right = awful.placement.right, top = awful.placement.top, bottom = awful.placement.bottom, centered = awful.placement.centered } local honor_workarea = true if (args.ignore_wibars) then honor_workarea = false end local popup_position = (args.pos and available_positions[args.pos]) or available_positions["top_left"] local button_cache = gears.cache(function(name,exec,description,icon,key) -- beacause apparently cache doesn't allow nil values if icon == "" then icon = nil end -- contents of our button local widget = wibox.widget(t.button(t.article({ icon = icon, resize = true, title = name, description = description, icon_size = (style.button.height and (style.button.height-style.button.margins)) or (34-style.button.margins) }),{ forced_height = style.button.height or 30 })) local exec = exec:gsub("%%%w","") -- theme-declared highlight function, because why not -- maybe you'd like to add duck noises to button presses idk. widget:connect_signal("button::press",style.button.onpress) widget:connect_signal("button::release",style.button.onrelease) widget:connect_signal("mouses::leave",style.button.onrelease) local bump = function() if not xdg.apps[key].count then xdg.apps[key].count = 0 end xdg.apps[key].count = xdg.apps[key].count + 1 xdg.apps[key].atime = os.time() end widget:buttons( gears.table.join( awful.button({},1,function() awful.spawn(exec) dismal.visible = false -- i know it's deprecated, you literally give me no option awful.keygrabber.stop() bump() end), awful.button({"Control"},1,function() awful.spawn({global.terminal,"-e",exec}) dismal.visible = false awful.keygrabber.stop() bump() end), awful.button({},3,function() awful.spawn(exec) bump() end), awful.button({"Control"},3,function() awful.spawn({global.terminal,"-e",exec}) bump() end) ) ) return widget end) local launchpad do -- Primary area launchpad = wibox.widget({ t.container { t.textbox({ text = "", id = "inputbox" }), layout = wibox.layout.fixed.vertical, }, t.container({ id = "resultbox", layout = wibox.layout.fixed.vertical, spacing = style.container.spacing },{ bg = style.container.bg_highlight, bgimage = style.container.bgimage_highlight, forced_width = style.button.width or 180 }), t.container({ t.button( t.textbox({ markup = "Most used" }),{ id = "usage" }), t.button( t.textbox({ markup = "Recent" }),{ id = "recent" }), id = "buttonbox", spacing = style.container.spacing, layout = wibox.layout.flex.horizontal }), t.textbox({ markup = "Enter - run\nCtrl+Enter - run in terminal" }), spacing = style.container.spacing, layout = wibox.layout.fixed.vertical }) local results_list = launchpad:get_children_by_id("resultbox")[1] local input_field = launchpad:get_children_by_id("inputbox")[1] local usage_sort = launchpad:get_children_by_id("usage")[1] local recent_sort = launchpad:get_children_by_id("recent")[1] local sorting_method = "usage" usage_sort:set_bg(style.bg_focus) if style.button.onpress then style.button.onpress(usage_sort) end usage_sort:connect_signal("button::press",function() recent_sort:set_bg(style.bg_normal) if style.button.onrelease then style.button.onrelease(recent_sort) end usage_sort:set_bg(style.bg_focus) if style.button.onpress then style.button.onpress(usage_sort) end sorting_method = "usage" end) recent_sort:connect_signal("button::press",function() usage_sort:set_bg(style.bg_normal) if style.button.onrelease then style.button.onrelease(usage_sort) end recent_sort:set_bg(style.bg_focus) if style.button.onpress then style.button.onpress(recent_sort) end sorting_method = "recent" end) root.keys(gears.table.join( root.keys(), ask.k(":dismal.run", function() results_list:reset() dismal.visible = true if dismal.visible then popup_position(dismal,{honor_workarea=honor_workarea}) awful.prompt.run { prompt = "Run: ", textbox = input_field, hooks = { { { "Control" }, "Return", function(cmd) awful.spawn({global.terminal, "-e", cmd}) end}, { { }, "Return", function(cmd) awful.spawn.with_shell(cmd) end}, { {}, 'Escape', function() input_field.markup = "" end}, }, done_callback = function() dismal.visible = false end, changed_callback = function(command) local results = xdg_search(command,args.result_limit or 5,sorting_method) results_list:reset() for _,v in pairs(results) do results_list:add(button_cache:get( v[2].name, v[2].exec, v[2].description or "", v[2].icon or "", v[1] )) end end, completion_callback = function(before,after,ncomp) return awful.completion.shell(before,after,ncomp,global.shell) end, history_path = (gears.filesystem.get_xdg_cache_home() or "/tmp/")..".dismal_history", history_max = 50 } end end,{description = "open run menu", group = "widgets"}) )) end local dismal_root = dismal.widget:get_children_by_id("dismal_root")[1] dismal_root:add(launchpad) return dismal end