├── LICENSE ├── README.md ├── init.lua └── rockspec └── awesome-handy-0.3.1-1.rockspec /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alexander Gehrke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # awesome-handy - Popup programs for awesomewm 2 | 3 | handy is a module for [awesome](https://awesomewm.org/), that allows you to open 4 | and hide a floating program with a keybinding. Main features: 5 | 6 | - spawns an instance per screen or can be bound for a specific screen 7 | - instances are remembered across awesome restarts, so pressing your key won't 8 | start a second instance but reuse the previous one 9 | - placement via `awful.placement` API 10 | 11 | *handy requires awesome 4.0+* 12 | 13 | ## Installation 14 | 15 | Put this repository somewhere in the lua search path for awesome. 16 | If you're using [LuaRocks](https://luarocks.org/), you can install it via 17 | ``` 18 | luarocks install --local awesome-handy 19 | ``` 20 | 21 | Alternatively, if your awesome configuration is managed by git, you can add 22 | this repo as a git submodule: 23 | 24 | ``` 25 | git submodule add https://github.com/crater2150/awesome-handy.git handy 26 | ``` 27 | 28 | Otherwise just clone it into your configuration directory. 29 | 30 | 31 | Then, in your `rc.lua`: 32 | 33 | ```lua 34 | local handy = require("handy") 35 | ``` 36 | 37 | ## Usage 38 | 39 | The following example spawns an urxvt instance in the center of the screen, 90% 40 | wide and 70% high when pressing F12 the first time, after that it 41 | toggles its visibility: 42 | 43 | ```lua 44 | awful.key({ }, "F12", function () 45 | handy("urxvt", awful.placement.centered, 0.9, 0.7) 46 | end ), 47 | ``` 48 | 49 | The following parameters are accepted: 50 | `handy(prog, placement, width, height, options, screen, class)` 51 | 52 | - `prog`: the only mandatory parameter, the command to run 53 | - `placement`: controls the position of the window, see [`awful.placement`](https://awesomewm.org/apidoc/libraries/awful.placement.html) 54 | - `width`, `height`: the size of the program. Values ≤ 1 are interpreted as 55 | percentage of screen size, values above 1 are interpreted as pixel sizes 56 | - `options`: arguments passed to awful.placement 57 | - `screen`: the screen to use. if not given, defaults to the currently focused 58 | screen, so each screen will have its own instance 59 | 60 | You can also set this to the string `"single"` for a client, that should only 61 | have a single instance (instead of one per screen). The client will always be 62 | shown on the currently focused screen, even if it was opened on another 63 | screen before (note that if the client is currently shown on an unfocused 64 | screen, you'll have to toggle it twice to move it to the current screen). 65 | - `class`: If given, must be the class or instance name of the window. Will 66 | enable using the fallback method for programs not supporting startup 67 | notification ([see below](#programs-without-startup-notification)) 68 | 69 | 70 | ## Programs without startup notification 71 | 72 | The default method for `handy` to detect which window is supposed to toggle is 73 | to use `awful.spawn` with a callback. This callback mechanism works by passing 74 | a startup id to the program, which only works with programs supporting the 75 | [Startup Notification spec](https://www.freedesktop.org/wiki/Specifications/startup-notification-spec/). 76 | 77 | When specifying the window instance name, handy will use a fallback method for 78 | the callback, which has the drawback, that if you start another program with 79 | the same class/instance during the startup of the first, the one which displays 80 | its window earlier will become the pop-up. If your program supports it, use 81 | a custom window instance that is unlikely to be used by other windows (e.g. 82 | "handy" + program name) and don't double press the key for handy. 83 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- handy.lua - awesome module for popup clients 2 | -- 3 | -- Usage: 4 | -- handy = require("handy") 5 | -- handy("console", "urxvt", 6 | -- 7 | local awful = require("awful") 8 | local inspect = inspect 9 | 10 | local handy = {} 11 | 12 | local clients = { single = {} } 13 | awful.screen.connect_for_each_screen(function(s) 14 | clients[s] = {} 15 | end) 16 | 17 | awesome.register_xproperty("handy_id", "string") 18 | awesome.register_xproperty("handy_visible", "boolean") 19 | 20 | local function spawn_callback(handy_id, placement, options, screen) 21 | return function(c) 22 | if clients[screen] == nil then clients[screen] = {} end 23 | c:set_xproperty("handy_id", handy_id) 24 | clients[screen][handy_id] = c 25 | 26 | -- workaround for awesomeWM/awesome#1937 27 | c:connect_signal("focus", function (c) 28 | placement(c, options) 29 | end) 30 | 31 | -- remove clients that were closed 32 | c:connect_signal("unmanage", function (c) 33 | clients[screen][handy_id] = nil 34 | end) 35 | end 36 | end 37 | 38 | --- Fallback for clients that do not support the startup notification protocol 39 | -- Use a "manage" callback with window class to apply post-launch parameters 40 | -- Equivalent to calling awful.spawn(prog, properties, spawn_callback(prog, placement, opt, s)) 41 | -- for a program with startup notification support 42 | -- 43 | -- @param prog Program command line, used as key 44 | -- @param instance Window instance or class 45 | -- @param properties Client properties for the window 46 | -- @param placement Placement rule 47 | -- @param opt Options for the placement rule 48 | -- @param s Screen 49 | local function awful_spawn_no_startup_notification(prog, instance, properties, placement, opt, s) 50 | local callback 51 | callback = function(c) 52 | if c.instance == instance or c.class == instance then 53 | awful.rules.execute(c, properties) 54 | placement(c, opt) 55 | spawn_callback(prog, placement, opt, s)(c) 56 | client.disconnect_signal("manage", callback) 57 | end 58 | end 59 | client.connect_signal("manage", callback) 60 | awful.spawn(prog) 61 | end 62 | 63 | local function toggle_client(c, s) 64 | if c:isvisible() then 65 | c.hidden = true 66 | c:set_xproperty("handy_visible", false) 67 | else 68 | c:move_to_tag(s.selected_tag) 69 | client.focus = c 70 | c.hidden = false 71 | c:set_xproperty("handy_visible", true) 72 | end 73 | end 74 | 75 | -- look for an already running client on a single screen 76 | local function restore_client_single_screen(handy_id, s, key, properties, target_screen) 77 | if not target_screen then 78 | target_screen = s 79 | end 80 | 81 | for _,c in ipairs(s.all_clients) do 82 | if c:get_xproperty("handy_id") == handy_id then 83 | clients[key][handy_id] = c 84 | c:connect_signal("unmanage", function (c) 85 | clients[key][handy_id] = nil 86 | end) 87 | for prop, val in pairs(properties) do 88 | c[prop] = val 89 | end 90 | toggle_client(c, target_screen) 91 | return true 92 | end 93 | end 94 | return false 95 | end 96 | 97 | -- restore an already running client as a handy client 98 | -- this ensures handy state across awesome restarts 99 | local function restore_client(handy_id, key, properties, target_screen) 100 | if key == 'single' then 101 | -- try all screens for single instance clients 102 | for s in screen do 103 | if restore_client_single_screen(handy_id, s, key, properties, target_screen) then 104 | return true 105 | end 106 | end 107 | return false 108 | else 109 | return restore_client_single_screen(handy_id, target_screen, key, properties, target_screen) 110 | end 111 | end 112 | 113 | 114 | -- 'target_screen' may be either 115 | -- - a screen object 116 | -- - not given (or a false value), to use the currently focused screen and 117 | -- spawn separate instances for each screen when first used there 118 | -- - the string "single", to use the currently focused screen and switch to 119 | -- the current screen on each call 120 | local function toggle(prog, placement, width, height, target_screen, class) 121 | local place = placement or awful.placement.centered 122 | local w = width or 0.5 123 | local h = height or 0.5 124 | local opt = options or {} 125 | 126 | local s 127 | local key 128 | if not target_screen then 129 | s = awful.screen.focused() 130 | key = s 131 | elseif target_screen == 'single' then 132 | s = awful.screen.focused() 133 | key = 'single' 134 | else 135 | s = target_screen 136 | key = s 137 | end 138 | 139 | if w <= 1 then w = s.geometry.width * w end 140 | if h <= 1 then h = s.geometry.height * h end 141 | 142 | if clients[key][prog] ~= nil then 143 | local c = clients[key][prog] 144 | toggle_client(c, s) 145 | else 146 | local properties = { width = w, height = h, floating = true, ontop = true } 147 | if restore_client(prog, key, properties, s) then return end 148 | 149 | if class ~= nil then 150 | awful_spawn_no_startup_notification( 151 | prog, class, properties, placement, opt, s 152 | ) 153 | else 154 | awful.spawn(prog, properties, spawn_callback(prog, placement, opt, s)) 155 | end 156 | end 157 | end 158 | 159 | function handy.fun(prog, placement, width, height, target_screen, class) 160 | return function() toggle(prog, placement, width, height, target_screen, class) end 161 | end 162 | 163 | return setmetatable(handy, { __call = function(_, ...) return toggle(...) end }) 164 | -------------------------------------------------------------------------------- /rockspec/awesome-handy-0.3.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "awesome-handy" 2 | version = "0.3.1-1" 3 | source = { 4 | url = "git+https://github.com/crater2150/awesome-handy.git", 5 | tag = "v0.3.1" 6 | } 7 | description = { 8 | summary = "pop-up apps for awesomewm", 9 | detailed = [[ 10 | Handy is a module for [awesome](https://awesomewm.org/). It allows you to launch 11 | a floating application, which can be shown and hidden with a keypress, similar 12 | to drop-down terminals like guake or tilda, without the animation, but not 13 | limited to terminals. 14 | ]], 15 | homepage = "https://github.com/crater2150/awesome-handy", 16 | license = "Apache-2.0" 17 | } 18 | dependencies = { 19 | "lua >= 5.2, < 5.5", 20 | } 21 | build = { 22 | type = "builtin", 23 | modules = { 24 | handy = "init.lua", 25 | } 26 | } 27 | --------------------------------------------------------------------------------