half-baked re-implementation of the major parts of sdorfehs in Hammerspoon

frames: Add interactive resizing like sdorfehs

Make frame object in spaces table a table, with rect inside of it so
we can store other stuff in there.

+325 -153
+2 -5
events.lua
··· 14 14 spoonfish.watch_hswindow(element) 15 15 elseif event == spoonfish.events.focusedWindowChanged then 16 16 local win = spoonfish.window_find_by_id(element:id()) 17 - if win ~= nil then 17 + if win then 18 18 -- TODO: don't do this when it's in response to a window destroying 19 19 spoonfish.frame_focus(win["space"], win["frame"], false) 20 20 end 21 21 elseif event == spoonfish.events.windowResized then 22 22 local win = spoonfish.window_find_by_id(element:id()) 23 - if win ~= nil then 23 + if win then 24 24 spoonfish.window_reframe(win) 25 25 end 26 26 end ··· 72 72 end 73 73 end 74 74 if not matched then 75 - -- spoonfish.log.i("not watching app[" .. hsapp:pid() .. "] " .. hsapp:title()) 76 75 return 77 76 end 78 77 ··· 172 171 if new_space == -1 then 173 172 new_space = hs.spaces.activeSpaceOnScreen() 174 173 end 175 - 176 - spoonfish.log.d("switched to space " .. new_space) 177 174 178 175 if spoonfish.spaces[new_space] then 179 176 spoonfish.frame_focus(new_space, spoonfish.spaces[new_space].frame_current,
+198 -84
frames.lua
··· 1 - spoonfish.frame_s = function(frame) 2 - return "{x:" .. frame.x .. " y:" .. frame.y .. " w:" .. frame.w .. " h:" .. 3 - frame.h .. "}" 4 - end 5 - 6 1 -- take control of a new hs.window and resize it to a particular frame 7 2 -- return a window table object 8 3 spoonfish.frame_capture = function(space_id, frame_id, hswin) ··· 54 49 spoonfish._frame_cycle(space_id, frame_id, true, complain) 55 50 end 56 51 57 - -- find a frame relative to frame_id in the direction dir 58 - spoonfish.frame_find = function(space_id, frame_id, dir) 59 - local cur = spoonfish.spaces[space_id].frames[frame_id] 52 + -- return a table of frame ids touching frame_id on side dir 53 + spoonfish.frame_find_touching = function(space_id, frame_id, dir) 54 + local cur = spoonfish.spaces[space_id].frames[frame_id].rect 55 + local found = {} 60 56 61 57 if dir == spoonfish.direction.LEFT then 62 58 -- x+w touching the x of the current frame, with same y 63 59 for i, f in pairs(spoonfish.spaces[space_id].frames) do 64 - if f.x + f.w == cur.x and f.y == cur.y then 65 - return i 60 + if f.rect.x + f.rect.w == cur.x and f.rect.y == cur.y then 61 + found[i] = true 66 62 end 67 63 end 68 64 -- or just x+w touching the x of the current frame 69 65 for i, f in pairs(spoonfish.spaces[space_id].frames) do 70 - if f.x + f.w == cur.x then 71 - return i 66 + if f.rect.x + f.rect.w == cur.x then 67 + found[i] = true 72 68 end 73 69 end 74 70 elseif dir == spoonfish.direction.RIGHT then 75 71 -- x touching the x+w of the current frame, with same y 76 72 for i, f in pairs(spoonfish.spaces[space_id].frames) do 77 - if f.x == cur.x + cur.w and f.y == cur.y then 78 - return i 73 + if f.rect.x == cur.x + cur.w and f.rect.y == cur.y then 74 + found[i] = true 79 75 end 80 76 end 81 77 -- or just x touching the x+w of the current frame 82 78 for i, f in pairs(spoonfish.spaces[space_id].frames) do 83 - if f.x == cur.x + cur.w then 84 - return i 79 + if f.rect.x == cur.x + cur.w then 80 + found[i] = true 85 81 end 86 82 end 87 83 elseif dir == spoonfish.direction.DOWN then 88 84 -- y touching the y+h of the current frame, with same x 89 85 for i, f in pairs(spoonfish.spaces[space_id].frames) do 90 - if f.y == cur.y + cur.h and f.x == cur.x then 91 - return i 86 + if f.rect.y == cur.y + cur.h and f.rect.x == cur.x then 87 + found[i] = true 92 88 end 93 89 end 94 90 -- or just y touching the y+h of the current frame 95 91 for i, f in pairs(spoonfish.spaces[space_id].frames) do 96 - if f.y == cur.y + cur.h then 97 - return i 92 + if f.rect.y == cur.y + cur.h then 93 + found[i] = true 98 94 end 99 95 end 100 96 elseif dir == spoonfish.direction.UP then 101 97 -- y+h touching the y of the current frame, with same x 102 98 for i, f in pairs(spoonfish.spaces[space_id].frames) do 103 - if f.y + f.h == cur.y and f.x == cur.x then 104 - return i 99 + if f.rect.y + f.rect.h == cur.y and f.rect.x == cur.x then 100 + found[i] = true 105 101 end 106 102 end 107 103 -- or just y+h touching the y of the current frame 108 104 for i, f in pairs(spoonfish.spaces[space_id].frames) do 109 - if f.y + f.h == cur.y then 110 - return i 105 + if f.rect.y + f.rect.h == cur.y then 106 + found[i] = true 111 107 end 112 108 end 113 109 else 114 - error("frame_find: bogus direction") 110 + error("frame_find_touching: bogus direction") 115 111 end 116 112 117 - -- nothing applicable, keep the current frame 118 - return frame_id 113 + return table.keys(found) 119 114 end 120 115 121 - spoonfish.frame_with_gap = function(space_id, frame_id) 122 - local iframe = spoonfish.inset(spoonfish.spaces[space_id].frames[frame_id], 123 - spoonfish.gap) 116 + spoonfish.frame_rect_with_gap = function(space_id, frame_id) 117 + local rect = spoonfish.spaces[space_id].frames[frame_id].rect 118 + local hgap = spoonfish.gap / 2 119 + local grect = spoonfish.inset(rect, hgap) 120 + local srect = spoonfish.spaces[space_id].rect 124 121 125 - if spoonfish.frame_find(space_id, frame_id, 126 - spoonfish.direction.LEFT) ~= frame_id then 127 - iframe.x = iframe.x - (spoonfish.gap / 2) 128 - iframe.w = iframe.w + (spoonfish.gap / 2) 122 + if rect.x == srect.x then 123 + -- touching left side 124 + grect.x = grect.x + hgap 125 + grect.w = grect.w - hgap 129 126 end 130 127 131 - if spoonfish.frame_find(space_id, frame_id, 132 - spoonfish.direction.UP) ~= frame_id then 133 - iframe.y = iframe.y - (spoonfish.gap / 2) 134 - iframe.h = iframe.h + (spoonfish.gap / 2) 128 + if rect.y == srect.y then 129 + -- touching top 130 + grect.y = grect.y + hgap 131 + grect.h = grect.h - hgap 135 132 end 136 133 137 - if spoonfish.frame_find(space_id, frame_id, 138 - spoonfish.direction.RIGHT) ~= frame_id then 139 - iframe.w = iframe.w + (spoonfish.gap / 2) 134 + if (rect.x + rect.w) == (srect.x + srect.w) then 135 + -- touching right side 136 + grect.w = grect.w - hgap 140 137 end 141 138 142 - if spoonfish.frame_find(space_id, frame_id, 143 - spoonfish.direction.DOWN) ~= frame_id then 144 - iframe.h = iframe.h + (spoonfish.gap / 2) 139 + if (rect.y + rect.h) == (srect.y + srect.h) then 140 + -- touching bottom 141 + grect.h = grect.h - hgap 145 142 end 146 143 147 - return iframe 144 + return grect 148 145 end 149 146 150 147 -- give focus to frame and raise its active window ··· 156 153 157 154 local fc = spoonfish.spaces[space_id].frame_current 158 155 local wof = spoonfish.frame_top_window(space_id, frame_id) 159 - if wof ~= nil then 156 + if wof then 160 157 spoonfish.window_reframe(wof) 161 158 if raise then 162 159 wof["win"]:focus() ··· 186 183 spoonfish.window_reborder(win) 187 184 end 188 185 186 + spoonfish.frame_resize_interactively = function(space_id, frame_id) 187 + if table.count(spoonfish.spaces[space_id].frames) == 1 then 188 + spoonfish.frame_message(space_id, 189 + table.keys(spoonfish.spaces[space_id].frames)[1], 190 + "Cannot resize only frame", false) 191 + return 192 + end 193 + 194 + spoonfish.resizing = true 195 + spoonfish.frame_message(space_id, frame_id, "Resize frame", true) 196 + end 197 + 198 + -- shrink or grow a given frame 199 + spoonfish.frame_resize = function(space_id, frame_id, dir) 200 + local origrect = spoonfish.inset( 201 + spoonfish.spaces[space_id].frames[frame_id].rect, 0) 202 + local srect = spoonfish.spaces[space_id].rect 203 + local resized = {} 204 + 205 + if dir == spoonfish.direction.LEFT or dir == spoonfish.direction.RIGHT or 206 + dir == spoonfish.direction.UP or dir == spoonfish.direction.DOWN then 207 + local amt = spoonfish.resize_unit 208 + local xy = "x" 209 + local wh = "w" 210 + 211 + if dir == spoonfish.direction.LEFT or dir == spoonfish.direction.UP then 212 + -- shrink 213 + amt = -amt 214 + end 215 + 216 + if dir == spoonfish.direction.UP or dir == spoonfish.direction.DOWN then 217 + xy = "y" 218 + wh = "h" 219 + end 220 + 221 + if (origrect[xy] == srect[xy]) and 222 + (origrect[xy] + origrect[wh] == srect[xy] + srect[wh]) then 223 + -- the original frame can't be resized in this direction, don't bother 224 + -- resizing any others 225 + return 226 + end 227 + 228 + for i, f in pairs(spoonfish.spaces[space_id].frames) do 229 + if (f.rect[xy] == srect[xy]) and 230 + (f.rect[xy] + f.rect[wh] == srect[xy] + srect[wh]) then 231 + -- this frame can't be resized in this direction 232 + else 233 + -- shrink/grow frames with this frame's coord 234 + if f.rect[xy] == origrect[xy] then 235 + if (f.rect[xy] + f.rect[wh]) == (srect[xy] + srect[wh]) then 236 + -- frame is on the right/bottom screen edge, keep its edge there by 237 + -- moving it left or up 238 + f.rect[xy] = f.rect[xy] - amt 239 + end 240 + f.rect[wh] = f.rect[wh] + amt 241 + resized[i] = true 242 + end 243 + 244 + -- grow/shrink frames with edge of this frame's shrunken/grown edge 245 + if (f.rect[xy] + f.rect[wh] == origrect[xy]) or 246 + (f.rect[xy] == origrect[xy] + origrect[wh]) then 247 + if (f.rect[xy] + f.rect[wh]) == (srect[xy] + srect[wh]) then 248 + -- frame is on the right/bottom screen edge, keep its edge there 249 + f.rect[xy] = f.rect[xy] + amt 250 + end 251 + f.rect[wh] = f.rect[wh] - amt 252 + resized[i] = true 253 + end 254 + end 255 + end 256 + else 257 + error("frame_resize: bogus direction") 258 + return 259 + end 260 + 261 + spoonfish.draw_frames(space_id) 262 + 263 + for _, w in ipairs(spoonfish.windows) do 264 + if w["space"] == space_id and resized[w["frame"]] then 265 + spoonfish.window_reframe(w) 266 + end 267 + end 268 + end 269 + 189 270 -- split a frame vertically or horizontally 190 271 spoonfish.frame_split = function(space_id, frame_id, vertical) 191 - local old_frame = spoonfish.spaces[space_id].frames[frame_id] 272 + local old_rect = spoonfish.spaces[space_id].frames[frame_id].rect 273 + local new_rect = {} 192 274 193 275 -- halve current frame 194 276 if vertical then 195 - spoonfish.spaces[space_id].frames[frame_id] = hs.geometry.rect( 196 - old_frame.x, 197 - old_frame.y, 198 - math.floor(old_frame.w / 2), 199 - old_frame.h 277 + new_rect = hs.geometry.rect( 278 + old_rect.x, 279 + old_rect.y, 280 + math.floor(old_rect.w / 2), 281 + old_rect.h 200 282 ) 201 283 else 202 - spoonfish.spaces[space_id].frames[frame_id] = hs.geometry.rect( 203 - old_frame.x, 204 - old_frame.y, 205 - old_frame.w, 206 - math.floor(old_frame.h / 2) 284 + new_rect = hs.geometry.rect( 285 + old_rect.x, 286 + old_rect.y, 287 + old_rect.w, 288 + math.floor(old_rect.h / 2) 207 289 ) 208 290 end 291 + spoonfish.spaces[space_id].frames[frame_id].rect = new_rect 209 292 210 293 -- reframe all windows in that old frame 211 294 for _, w in ipairs(spoonfish.windows) do ··· 214 297 end 215 298 end 216 299 217 - local new_frame = table.count(spoonfish.spaces[space_id].frames) + 1 300 + local new_frame_id = table.count(spoonfish.spaces[space_id].frames) + 1 301 + local new_frame_rect = {} 218 302 219 303 if vertical then 220 - spoonfish.spaces[space_id].frames[new_frame] = hs.geometry.rect( 221 - spoonfish.spaces[space_id].frames[frame_id].x + 222 - spoonfish.spaces[space_id].frames[frame_id].w, 223 - spoonfish.spaces[space_id].frames[frame_id].y, 224 - old_frame.w - spoonfish.spaces[space_id].frames[frame_id].w, 225 - spoonfish.spaces[space_id].frames[frame_id].h 304 + new_frame_rect = hs.geometry.rect( 305 + new_rect.x + new_rect.w, 306 + new_rect.y, 307 + old_rect.w - new_rect.w, 308 + new_rect.h 226 309 ) 227 310 else 228 - spoonfish.spaces[space_id].frames[new_frame] = hs.geometry.rect( 229 - spoonfish.spaces[space_id].frames[frame_id].x, 230 - spoonfish.spaces[space_id].frames[frame_id].y + 231 - spoonfish.spaces[space_id].frames[frame_id].h, 232 - spoonfish.spaces[space_id].frames[frame_id].w, 233 - old_frame.h - spoonfish.spaces[space_id].frames[frame_id].h 311 + new_frame_rect = hs.geometry.rect( 312 + new_rect.x, 313 + new_rect.y + new_rect.h, 314 + new_rect.w, 315 + old_rect.h - new_rect.h 234 316 ) 235 317 end 318 + spoonfish.spaces[space_id].frames[new_frame_id] = { rect = new_frame_rect } 236 319 237 - spoonfish.frame_focus(space_id, frame_id, true) 320 + spoonfish.draw_frames(space_id) 321 + spoonfish.frame_focus(space_id, new_frame_id, true) 238 322 239 323 -- we'll probably want to go to this frame on tab 240 - spoonfish.spaces[space_id].frame_previous = new_frame 324 + spoonfish.spaces[space_id].frame_previous = new_frame_id 241 325 end 242 326 243 327 -- swap the front-most windows of two frames ··· 259 343 -- remove current frame 260 344 spoonfish.frame_remove = function(space_id) 261 345 if table.count(spoonfish.spaces[space_id].frames) == 1 then 346 + spoonfish.frame_message(space_id, 347 + table.keys(spoonfish.spaces[space_id].frames)[1], 348 + "Cannot remove only frame", false) 262 349 return 263 350 end 264 351 ··· 286 373 287 374 -- TODO: actually resize other frames 288 375 289 - spoonfish.frame_focus(space_id, spoonfish.spaces[space_id].frame_previous, true) 376 + spoonfish.frame_focus(space_id, spoonfish.spaces[space_id].frame_previous, 377 + true) 290 378 end 291 379 292 380 -- split a frame vertically ··· 305 393 306 394 spoonfish.frame_message_timer = nil 307 395 spoonfish._frame_message = nil 308 - spoonfish.frame_message = function(space_id, frame_id, message) 396 + spoonfish.frame_message = function(space_id, frame_id, message, sticky) 309 397 if spoonfish.frame_message_timer ~= nil then 310 398 spoonfish.frame_message_timer:stop() 311 399 end ··· 315 403 spoonfish._frame_message = nil 316 404 end 317 405 318 - local frame = spoonfish.spaces[space_id].frames[frame_id] 319 - if frame == nil then 406 + if message == nil then 407 + return 408 + end 409 + 410 + if spoonfish.spaces[space_id].frames[frame_id] == nil then 320 411 return 321 412 end 413 + local frame = spoonfish.spaces[space_id].frames[frame_id].rect 322 414 323 415 local textFrame = hs.drawing.getTextDrawingSize(message, 324 416 { size = spoonfish.frame_message_font_size }) ··· 360 452 361 453 spoonfish._frame_message:show() 362 454 363 - spoonfish.frame_message_timer = hs.timer.doAfter(spoonfish.frame_message_secs, 364 - function() 365 - if spoonfish._frame_message ~= nil then 366 - spoonfish._frame_message:delete() 367 - spoonfish._frame_message = nil 368 - end 369 - spoonfish.frame_message_timer = nil 370 - end) 455 + if not sticky then 456 + spoonfish.frame_message_timer = hs.timer.doAfter( 457 + spoonfish.frame_message_secs, function() 458 + if spoonfish._frame_message ~= nil then 459 + spoonfish._frame_message:delete() 460 + spoonfish._frame_message = nil 461 + end 462 + spoonfish.frame_message_timer = nil 463 + end) 464 + end 465 + end 466 + 467 + -- for debugging 468 + spoonfish.draw_frames = function(space_id) 469 + if not spoonfish.debug_frames then 470 + return 471 + end 472 + 473 + for i, f in pairs(spoonfish.spaces[space_id].frames) do 474 + if f.outline == nil then 475 + f.outline = hs.drawing.rectangle(f.rect) 476 + else 477 + f.outline:setFrame(f.rect) 478 + end 479 + f.outline:setLevel(hs.drawing.windowLevels.normal) 480 + f.outline:setStrokeColor({ ["hex"] = "#ff0000" }) 481 + f.outline:setStrokeWidth(4) 482 + f.outline:setFill(false) 483 + f.outline:show() 484 + end 371 485 end
+79 -56
init.lua
··· 8 8 9 9 -- default configuration, can be overridden in loading init.lua before calling 10 10 -- spoonfish.start() 11 - spoonfish.gap = 22 12 - spoonfish.terminal = "iTerm2" 13 - spoonfish.frame_message_secs = 1 14 - spoonfish.frame_message_font_size = 18 11 + 12 + -- prefix key (with control) 13 + spoonfish.prefix_key = "a" 14 + 15 + -- set sizes to 0 to disable 15 16 spoonfish.border_color = "#000000" 16 17 spoonfish.border_size = 4 17 18 spoonfish.shadow_color = "#000000" 18 19 spoonfish.shadow_size = 8 19 20 21 + -- space to put between windows in adjoining frames 22 + spoonfish.gap = 22 23 + 24 + -- program to send 'new window' to for 'c' command 25 + spoonfish.terminal = "iTerm2" 26 + 27 + -- for per-frame messages 28 + spoonfish.frame_message_secs = 1 29 + spoonfish.frame_message_font_size = 18 30 + 31 + -- increment to resize interactively 32 + spoonfish.resize_unit = 10 33 + 20 34 -- for these lists, anything not starting with ^ will be run through 21 35 -- escape_pattern to escape dashes and other special characters, so be sure 22 36 -- to escape such characters manually in ^-prefixed patterns ··· 36 50 spoonfish.start = function() 37 51 local s = spoonfish 38 52 39 - -- spaces and frame rects, keyed by frame number 40 - s.spaces = {} 41 - for _, space_id in 42 - pairs(hs.spaces.spacesForScreen(hs.screen.mainScreen():getUUID())) do 43 - s.spaces[space_id] = {} 44 - s.spaces[space_id].frames = {} 45 - s.spaces[space_id].frames[1] = hs.screen.mainScreen():frame() 46 - s.spaces[space_id].frame_previous = 1 47 - s.spaces[space_id].frame_current = 1 48 - end 49 - 50 53 s.direction = { 51 54 LEFT = 1, 52 55 RIGHT = 2, ··· 69 72 -- apps, keyed by pid 70 73 s.apps = {} 71 74 75 + -- debugging flags 76 + s.debug_frames = false 77 + 72 78 s.log = hs.logger.new("spoonfish", "debug") 73 79 80 + -- spaces and frame rects, keyed by frame number 81 + s.spaces = {} 82 + for _, space_id in 83 + pairs(hs.spaces.spacesForScreen(hs.screen.mainScreen():getUUID())) do 84 + s.spaces[space_id] = { rect = hs.screen.mainScreen():frame() } 85 + s.spaces[space_id].frames = {} 86 + s.spaces[space_id].frames[1] = { 87 + rect = hs.screen.mainScreen():frame(), 88 + } 89 + s.spaces[space_id].frame_previous = 1 90 + s.spaces[space_id].frame_current = 1 91 + 92 + if space_id == hs.spaces.activeSpaceOnScreen() then 93 + spoonfish.draw_frames(space_id) 94 + end 95 + end 96 + 74 97 -- watch for new apps launched 75 98 s.app_watcher = hs.application.watcher.new(s.app_meta_event) 76 99 s.app_watcher:start() ··· 85 108 s.spaces_watcher = hs.spaces.watcher.new(spoonfish.spaces_event) 86 109 s.spaces_watcher:start() 87 110 88 - spoonfish.initialized = true 89 - 111 + s.initialized = true 90 112 s.in_modal = false 91 113 s.send_modal = false 114 + s.resizing = false 92 115 93 116 s.eventtap = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, 94 117 function(event) ··· 113 136 return false 114 137 end 115 138 139 + if s.resizing then 140 + if key == "down" or key == "left" or key == "right" or key == "up" then 141 + if nomod then 142 + s.frame_resize(cs, space.frame_current, s.dir_from_string(key)) 143 + end 144 + else 145 + -- any other key will exit 146 + s.resizing = false 147 + s.frame_message(cs, space.frame_current, nil) 148 + return true 149 + end 150 + 151 + -- redisplay the frame message as it probably just changed size 152 + s.frame_message(cs, space.frame_current, "Resize frame", true) 153 + 154 + return true 155 + end 156 + 116 157 if not s.in_modal then 117 - if ctrl and key == "a" then 158 + if ctrl and key == spoonfish.prefix_key then 118 159 if s.send_modal then 119 160 s.send_modal = false 120 161 return false ··· 128 169 return false 129 170 end 130 171 131 - -- in-modal key bindings 172 + -- we're in modal, so anything after this point will reset it 132 173 s.in_modal = false 133 174 175 + -- in-modal key bindings 134 176 if flags:containExactly({ "shift" }) then 135 177 key = string.upper(key) 136 178 end 137 179 138 - spoonfish.ignore_events = true 180 + s.ignore_events = true 181 + 182 + -- TODO: put these in a table for dynamic reassignment 139 183 140 184 if key == "tab" then 141 185 if nomod or ctrl then 142 186 s.frame_focus(cs, space.frame_previous, true) 143 187 end 144 - elseif key == "left" then 145 - if nomod then 146 - s.frame_focus(cs, 147 - s.frame_find(cs, space.frame_current, s.direction.LEFT), true) 148 - elseif ctrl then 149 - s.frame_swap(cs, space.frame_current, 150 - s.frame_find(cs, space.frame_current, s.direction.LEFT)) 151 - end 152 - elseif key == "right" then 153 - if nomod then 154 - s.frame_focus(cs, 155 - s.frame_find(cs, space.frame_current, s.direction.RIGHT), 156 - true) 157 - elseif ctrl then 158 - s.frame_swap(cs, space.frame_current, 159 - s.frame_find(cs, space.frame_current, s.direction.RIGHT)) 160 - end 161 - elseif key == "up" then 162 - if nomod then 163 - s.frame_focus(cs, 164 - s.frame_find(cs, space.frame_current, s.direction.UP), 165 - true) 166 - elseif ctrl then 167 - s.frame_swap(cs, space.frame_current, 168 - s.frame_find(cs, space.frame_current, s.direction.UP)) 169 - end 170 - elseif key == "down" then 171 - if nomod then 172 - s.frame_focus(cs, 173 - s.frame_find(cs, space.frame_current, s.direction.DOWN), 174 - true) 175 - elseif ctrl then 176 - s.frame_swap(cs, space.frame_current, 177 - s.frame_find(cs, space.frame_current, s.direction.DOWN)) 188 + elseif key == "down" or key == "left" or key == "right" or key == "up" then 189 + local touching = s.frame_find_touching(cs, space.frame_current, 190 + s.dir_from_string(key)) 191 + if touching[1] then 192 + if nomod then 193 + s.frame_focus(cs, touching[1], true) 194 + elseif ctrl then 195 + s.frame_swap(cs, space.frame_current, touching[1]) 196 + end 178 197 end 179 198 elseif key == "space" then 180 199 if nomod or ctrl then 181 200 s.frame_cycle(cs, space.frame_current, true) 182 201 end 183 - elseif key == "a" then 202 + elseif key == spoonfish.prefix_key then 184 203 if nomod then 185 204 s.send_modal = true 186 - hs.eventtap.keyStroke({ "ctrl" }, "a") 205 + hs.eventtap.keyStroke({ "ctrl" }, spoonfish.prefix_key) 187 206 else 188 207 s.frame_reverse_cycle(cs, space.frame_current, true) 189 208 end ··· 207 226 elseif key == "n" then 208 227 if nomod or ctrl then 209 228 s.frame_cycle(cs, space.frame_current, true) 229 + end 230 + elseif key == "r" then 231 + if nomod then 232 + s.frame_resize_interactively(cs, space.frame_current) 210 233 end 211 234 elseif key == "R" then 212 235 if nomod then
+38
utils.lua
··· 1 + table.copy = function(tab) 2 + local ret = {} 3 + for k, v in pairs(tab) do 4 + ret[k] = v 5 + end 6 + return ret 7 + end 8 + 1 9 -- come on, lua 2 10 table.count = function(tab) 3 11 local c = 0 ··· 7 15 return c 8 16 end 9 17 18 + table.keys = function(tab) 19 + local ret = {} 20 + for k, _ in pairs(tab) do 21 + ret[#ret + 1] = k 22 + end 23 + return ret 24 + end 25 + 26 + spoonfish.dir_from_string = function(str) 27 + lstr = str:lower() 28 + 29 + if lstr == "left" then 30 + return spoonfish.direction.LEFT 31 + elseif lstr == "right" then 32 + return spoonfish.direction.RIGHT 33 + elseif lstr == "up" then 34 + return spoonfish.direction.UP 35 + elseif lstr == "down" then 36 + return spoonfish.direction.DOWN 37 + else 38 + error("dir_from_string: invalid direction") 39 + return nil 40 + end 41 + end 42 + 10 43 spoonfish.last_alert = nil 11 44 spoonfish.alert = function(str) 12 45 if spoonfish.last_alert then ··· 40 73 local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])' 41 74 return str:gsub(quotepattern, "%%%1") 42 75 end 76 + 77 + spoonfish.rect_s = function(rect) 78 + return "{x:" .. rect.x .. " y:" .. rect.y .. " w:" .. rect.w .. " h:" .. 79 + rect.h .. "}" 80 + end
+8 -8
windows.lua
··· 32 32 return 33 33 end 34 34 35 - local iframe = spoonfish.frame_with_gap(win["space"], win["frame"]) 35 + local iframe = spoonfish.frame_rect_with_gap(win["space"], win["frame"]) 36 36 win["win"]:move(iframe, nil, true, 0) 37 37 38 38 if win["space"] == hs.spaces.activeSpaceOnScreen() then ··· 52 52 end 53 53 54 54 for _, w in pairs({ "shadow", "border" }) do 55 - local iframe = spoonfish.frame_with_gap(win["space"], win["frame"]) 56 - iframe = spoonfish.inset(iframe, -(spoonfish.border_size)) 55 + local irect = spoonfish.frame_rect_with_gap(win["space"], win["frame"]) 56 + irect = spoonfish.inset(irect, -(spoonfish.border_size)) 57 57 58 58 if w == "shadow" then 59 - iframe.x = iframe.x + spoonfish.shadow_size 60 - iframe.y = iframe.y + spoonfish.shadow_size 59 + irect.x = irect.x + spoonfish.shadow_size 60 + irect.y = irect.y + spoonfish.shadow_size 61 61 end 62 62 63 63 if win[w] == nil then 64 - win[w] = hs.drawing.rectangle(iframe) 64 + win[w] = hs.drawing.rectangle(irect) 65 65 else 66 - win[w]:setFrame(iframe) 66 + win[w]:setFrame(irect) 67 67 end 68 - win[w]:setLevel(hs.drawing.windowLevels.desktopIcon) --floating) 68 + win[w]:setLevel(hs.drawing.windowLevels.desktopIcon) 69 69 70 70 local color 71 71 if w == "border" then