···1414 spoonfish.watch_hswindow(element)
1515 elseif event == spoonfish.events.focusedWindowChanged then
1616 local win = spoonfish.window_find_by_id(element:id())
1717- if win ~= nil then
1717+ if win then
1818 -- TODO: don't do this when it's in response to a window destroying
1919 spoonfish.frame_focus(win["space"], win["frame"], false)
2020 end
2121 elseif event == spoonfish.events.windowResized then
2222 local win = spoonfish.window_find_by_id(element:id())
2323- if win ~= nil then
2323+ if win then
2424 spoonfish.window_reframe(win)
2525 end
2626 end
···7272 end
7373 end
7474 if not matched then
7575- -- spoonfish.log.i("not watching app[" .. hsapp:pid() .. "] " .. hsapp:title())
7675 return
7776 end
7877···172171 if new_space == -1 then
173172 new_space = hs.spaces.activeSpaceOnScreen()
174173 end
175175-176176- spoonfish.log.d("switched to space " .. new_space)
177174178175 if spoonfish.spaces[new_space] then
179176 spoonfish.frame_focus(new_space, spoonfish.spaces[new_space].frame_current,
+198-84
frames.lua
···11-spoonfish.frame_s = function(frame)
22- return "{x:" .. frame.x .. " y:" .. frame.y .. " w:" .. frame.w .. " h:" ..
33- frame.h .. "}"
44-end
55-61-- take control of a new hs.window and resize it to a particular frame
72-- return a window table object
83spoonfish.frame_capture = function(space_id, frame_id, hswin)
···5449 spoonfish._frame_cycle(space_id, frame_id, true, complain)
5550end
56515757--- find a frame relative to frame_id in the direction dir
5858-spoonfish.frame_find = function(space_id, frame_id, dir)
5959- local cur = spoonfish.spaces[space_id].frames[frame_id]
5252+-- return a table of frame ids touching frame_id on side dir
5353+spoonfish.frame_find_touching = function(space_id, frame_id, dir)
5454+ local cur = spoonfish.spaces[space_id].frames[frame_id].rect
5555+ local found = {}
60566157 if dir == spoonfish.direction.LEFT then
6258 -- x+w touching the x of the current frame, with same y
6359 for i, f in pairs(spoonfish.spaces[space_id].frames) do
6464- if f.x + f.w == cur.x and f.y == cur.y then
6565- return i
6060+ if f.rect.x + f.rect.w == cur.x and f.rect.y == cur.y then
6161+ found[i] = true
6662 end
6763 end
6864 -- or just x+w touching the x of the current frame
6965 for i, f in pairs(spoonfish.spaces[space_id].frames) do
7070- if f.x + f.w == cur.x then
7171- return i
6666+ if f.rect.x + f.rect.w == cur.x then
6767+ found[i] = true
7268 end
7369 end
7470 elseif dir == spoonfish.direction.RIGHT then
7571 -- x touching the x+w of the current frame, with same y
7672 for i, f in pairs(spoonfish.spaces[space_id].frames) do
7777- if f.x == cur.x + cur.w and f.y == cur.y then
7878- return i
7373+ if f.rect.x == cur.x + cur.w and f.rect.y == cur.y then
7474+ found[i] = true
7975 end
8076 end
8177 -- or just x touching the x+w of the current frame
8278 for i, f in pairs(spoonfish.spaces[space_id].frames) do
8383- if f.x == cur.x + cur.w then
8484- return i
7979+ if f.rect.x == cur.x + cur.w then
8080+ found[i] = true
8581 end
8682 end
8783 elseif dir == spoonfish.direction.DOWN then
8884 -- y touching the y+h of the current frame, with same x
8985 for i, f in pairs(spoonfish.spaces[space_id].frames) do
9090- if f.y == cur.y + cur.h and f.x == cur.x then
9191- return i
8686+ if f.rect.y == cur.y + cur.h and f.rect.x == cur.x then
8787+ found[i] = true
9288 end
9389 end
9490 -- or just y touching the y+h of the current frame
9591 for i, f in pairs(spoonfish.spaces[space_id].frames) do
9696- if f.y == cur.y + cur.h then
9797- return i
9292+ if f.rect.y == cur.y + cur.h then
9393+ found[i] = true
9894 end
9995 end
10096 elseif dir == spoonfish.direction.UP then
10197 -- y+h touching the y of the current frame, with same x
10298 for i, f in pairs(spoonfish.spaces[space_id].frames) do
103103- if f.y + f.h == cur.y and f.x == cur.x then
104104- return i
9999+ if f.rect.y + f.rect.h == cur.y and f.rect.x == cur.x then
100100+ found[i] = true
105101 end
106102 end
107103 -- or just y+h touching the y of the current frame
108104 for i, f in pairs(spoonfish.spaces[space_id].frames) do
109109- if f.y + f.h == cur.y then
110110- return i
105105+ if f.rect.y + f.rect.h == cur.y then
106106+ found[i] = true
111107 end
112108 end
113109 else
114114- error("frame_find: bogus direction")
110110+ error("frame_find_touching: bogus direction")
115111 end
116112117117- -- nothing applicable, keep the current frame
118118- return frame_id
113113+ return table.keys(found)
119114end
120115121121-spoonfish.frame_with_gap = function(space_id, frame_id)
122122- local iframe = spoonfish.inset(spoonfish.spaces[space_id].frames[frame_id],
123123- spoonfish.gap)
116116+spoonfish.frame_rect_with_gap = function(space_id, frame_id)
117117+ local rect = spoonfish.spaces[space_id].frames[frame_id].rect
118118+ local hgap = spoonfish.gap / 2
119119+ local grect = spoonfish.inset(rect, hgap)
120120+ local srect = spoonfish.spaces[space_id].rect
124121125125- if spoonfish.frame_find(space_id, frame_id,
126126- spoonfish.direction.LEFT) ~= frame_id then
127127- iframe.x = iframe.x - (spoonfish.gap / 2)
128128- iframe.w = iframe.w + (spoonfish.gap / 2)
122122+ if rect.x == srect.x then
123123+ -- touching left side
124124+ grect.x = grect.x + hgap
125125+ grect.w = grect.w - hgap
129126 end
130127131131- if spoonfish.frame_find(space_id, frame_id,
132132- spoonfish.direction.UP) ~= frame_id then
133133- iframe.y = iframe.y - (spoonfish.gap / 2)
134134- iframe.h = iframe.h + (spoonfish.gap / 2)
128128+ if rect.y == srect.y then
129129+ -- touching top
130130+ grect.y = grect.y + hgap
131131+ grect.h = grect.h - hgap
135132 end
136133137137- if spoonfish.frame_find(space_id, frame_id,
138138- spoonfish.direction.RIGHT) ~= frame_id then
139139- iframe.w = iframe.w + (spoonfish.gap / 2)
134134+ if (rect.x + rect.w) == (srect.x + srect.w) then
135135+ -- touching right side
136136+ grect.w = grect.w - hgap
140137 end
141138142142- if spoonfish.frame_find(space_id, frame_id,
143143- spoonfish.direction.DOWN) ~= frame_id then
144144- iframe.h = iframe.h + (spoonfish.gap / 2)
139139+ if (rect.y + rect.h) == (srect.y + srect.h) then
140140+ -- touching bottom
141141+ grect.h = grect.h - hgap
145142 end
146143147147- return iframe
144144+ return grect
148145end
149146150147-- give focus to frame and raise its active window
···156153157154 local fc = spoonfish.spaces[space_id].frame_current
158155 local wof = spoonfish.frame_top_window(space_id, frame_id)
159159- if wof ~= nil then
156156+ if wof then
160157 spoonfish.window_reframe(wof)
161158 if raise then
162159 wof["win"]:focus()
···186183 spoonfish.window_reborder(win)
187184end
188185186186+spoonfish.frame_resize_interactively = function(space_id, frame_id)
187187+ if table.count(spoonfish.spaces[space_id].frames) == 1 then
188188+ spoonfish.frame_message(space_id,
189189+ table.keys(spoonfish.spaces[space_id].frames)[1],
190190+ "Cannot resize only frame", false)
191191+ return
192192+ end
193193+194194+ spoonfish.resizing = true
195195+ spoonfish.frame_message(space_id, frame_id, "Resize frame", true)
196196+end
197197+198198+-- shrink or grow a given frame
199199+spoonfish.frame_resize = function(space_id, frame_id, dir)
200200+ local origrect = spoonfish.inset(
201201+ spoonfish.spaces[space_id].frames[frame_id].rect, 0)
202202+ local srect = spoonfish.spaces[space_id].rect
203203+ local resized = {}
204204+205205+ if dir == spoonfish.direction.LEFT or dir == spoonfish.direction.RIGHT or
206206+ dir == spoonfish.direction.UP or dir == spoonfish.direction.DOWN then
207207+ local amt = spoonfish.resize_unit
208208+ local xy = "x"
209209+ local wh = "w"
210210+211211+ if dir == spoonfish.direction.LEFT or dir == spoonfish.direction.UP then
212212+ -- shrink
213213+ amt = -amt
214214+ end
215215+216216+ if dir == spoonfish.direction.UP or dir == spoonfish.direction.DOWN then
217217+ xy = "y"
218218+ wh = "h"
219219+ end
220220+221221+ if (origrect[xy] == srect[xy]) and
222222+ (origrect[xy] + origrect[wh] == srect[xy] + srect[wh]) then
223223+ -- the original frame can't be resized in this direction, don't bother
224224+ -- resizing any others
225225+ return
226226+ end
227227+228228+ for i, f in pairs(spoonfish.spaces[space_id].frames) do
229229+ if (f.rect[xy] == srect[xy]) and
230230+ (f.rect[xy] + f.rect[wh] == srect[xy] + srect[wh]) then
231231+ -- this frame can't be resized in this direction
232232+ else
233233+ -- shrink/grow frames with this frame's coord
234234+ if f.rect[xy] == origrect[xy] then
235235+ if (f.rect[xy] + f.rect[wh]) == (srect[xy] + srect[wh]) then
236236+ -- frame is on the right/bottom screen edge, keep its edge there by
237237+ -- moving it left or up
238238+ f.rect[xy] = f.rect[xy] - amt
239239+ end
240240+ f.rect[wh] = f.rect[wh] + amt
241241+ resized[i] = true
242242+ end
243243+244244+ -- grow/shrink frames with edge of this frame's shrunken/grown edge
245245+ if (f.rect[xy] + f.rect[wh] == origrect[xy]) or
246246+ (f.rect[xy] == origrect[xy] + origrect[wh]) then
247247+ if (f.rect[xy] + f.rect[wh]) == (srect[xy] + srect[wh]) then
248248+ -- frame is on the right/bottom screen edge, keep its edge there
249249+ f.rect[xy] = f.rect[xy] + amt
250250+ end
251251+ f.rect[wh] = f.rect[wh] - amt
252252+ resized[i] = true
253253+ end
254254+ end
255255+ end
256256+ else
257257+ error("frame_resize: bogus direction")
258258+ return
259259+ end
260260+261261+ spoonfish.draw_frames(space_id)
262262+263263+ for _, w in ipairs(spoonfish.windows) do
264264+ if w["space"] == space_id and resized[w["frame"]] then
265265+ spoonfish.window_reframe(w)
266266+ end
267267+ end
268268+end
269269+189270-- split a frame vertically or horizontally
190271spoonfish.frame_split = function(space_id, frame_id, vertical)
191191- local old_frame = spoonfish.spaces[space_id].frames[frame_id]
272272+ local old_rect = spoonfish.spaces[space_id].frames[frame_id].rect
273273+ local new_rect = {}
192274193275 -- halve current frame
194276 if vertical then
195195- spoonfish.spaces[space_id].frames[frame_id] = hs.geometry.rect(
196196- old_frame.x,
197197- old_frame.y,
198198- math.floor(old_frame.w / 2),
199199- old_frame.h
277277+ new_rect = hs.geometry.rect(
278278+ old_rect.x,
279279+ old_rect.y,
280280+ math.floor(old_rect.w / 2),
281281+ old_rect.h
200282 )
201283 else
202202- spoonfish.spaces[space_id].frames[frame_id] = hs.geometry.rect(
203203- old_frame.x,
204204- old_frame.y,
205205- old_frame.w,
206206- math.floor(old_frame.h / 2)
284284+ new_rect = hs.geometry.rect(
285285+ old_rect.x,
286286+ old_rect.y,
287287+ old_rect.w,
288288+ math.floor(old_rect.h / 2)
207289 )
208290 end
291291+ spoonfish.spaces[space_id].frames[frame_id].rect = new_rect
209292210293 -- reframe all windows in that old frame
211294 for _, w in ipairs(spoonfish.windows) do
···214297 end
215298 end
216299217217- local new_frame = table.count(spoonfish.spaces[space_id].frames) + 1
300300+ local new_frame_id = table.count(spoonfish.spaces[space_id].frames) + 1
301301+ local new_frame_rect = {}
218302219303 if vertical then
220220- spoonfish.spaces[space_id].frames[new_frame] = hs.geometry.rect(
221221- spoonfish.spaces[space_id].frames[frame_id].x +
222222- spoonfish.spaces[space_id].frames[frame_id].w,
223223- spoonfish.spaces[space_id].frames[frame_id].y,
224224- old_frame.w - spoonfish.spaces[space_id].frames[frame_id].w,
225225- spoonfish.spaces[space_id].frames[frame_id].h
304304+ new_frame_rect = hs.geometry.rect(
305305+ new_rect.x + new_rect.w,
306306+ new_rect.y,
307307+ old_rect.w - new_rect.w,
308308+ new_rect.h
226309 )
227310 else
228228- spoonfish.spaces[space_id].frames[new_frame] = hs.geometry.rect(
229229- spoonfish.spaces[space_id].frames[frame_id].x,
230230- spoonfish.spaces[space_id].frames[frame_id].y +
231231- spoonfish.spaces[space_id].frames[frame_id].h,
232232- spoonfish.spaces[space_id].frames[frame_id].w,
233233- old_frame.h - spoonfish.spaces[space_id].frames[frame_id].h
311311+ new_frame_rect = hs.geometry.rect(
312312+ new_rect.x,
313313+ new_rect.y + new_rect.h,
314314+ new_rect.w,
315315+ old_rect.h - new_rect.h
234316 )
235317 end
318318+ spoonfish.spaces[space_id].frames[new_frame_id] = { rect = new_frame_rect }
236319237237- spoonfish.frame_focus(space_id, frame_id, true)
320320+ spoonfish.draw_frames(space_id)
321321+ spoonfish.frame_focus(space_id, new_frame_id, true)
238322239323 -- we'll probably want to go to this frame on tab
240240- spoonfish.spaces[space_id].frame_previous = new_frame
324324+ spoonfish.spaces[space_id].frame_previous = new_frame_id
241325end
242326243327-- swap the front-most windows of two frames
···259343-- remove current frame
260344spoonfish.frame_remove = function(space_id)
261345 if table.count(spoonfish.spaces[space_id].frames) == 1 then
346346+ spoonfish.frame_message(space_id,
347347+ table.keys(spoonfish.spaces[space_id].frames)[1],
348348+ "Cannot remove only frame", false)
262349 return
263350 end
264351···286373287374 -- TODO: actually resize other frames
288375289289- spoonfish.frame_focus(space_id, spoonfish.spaces[space_id].frame_previous, true)
376376+ spoonfish.frame_focus(space_id, spoonfish.spaces[space_id].frame_previous,
377377+ true)
290378end
291379292380-- split a frame vertically
···305393306394spoonfish.frame_message_timer = nil
307395spoonfish._frame_message = nil
308308-spoonfish.frame_message = function(space_id, frame_id, message)
396396+spoonfish.frame_message = function(space_id, frame_id, message, sticky)
309397 if spoonfish.frame_message_timer ~= nil then
310398 spoonfish.frame_message_timer:stop()
311399 end
···315403 spoonfish._frame_message = nil
316404 end
317405318318- local frame = spoonfish.spaces[space_id].frames[frame_id]
319319- if frame == nil then
406406+ if message == nil then
407407+ return
408408+ end
409409+410410+ if spoonfish.spaces[space_id].frames[frame_id] == nil then
320411 return
321412 end
413413+ local frame = spoonfish.spaces[space_id].frames[frame_id].rect
322414323415 local textFrame = hs.drawing.getTextDrawingSize(message,
324416 { size = spoonfish.frame_message_font_size })
···360452361453 spoonfish._frame_message:show()
362454363363- spoonfish.frame_message_timer = hs.timer.doAfter(spoonfish.frame_message_secs,
364364- function()
365365- if spoonfish._frame_message ~= nil then
366366- spoonfish._frame_message:delete()
367367- spoonfish._frame_message = nil
368368- end
369369- spoonfish.frame_message_timer = nil
370370- end)
455455+ if not sticky then
456456+ spoonfish.frame_message_timer = hs.timer.doAfter(
457457+ spoonfish.frame_message_secs, function()
458458+ if spoonfish._frame_message ~= nil then
459459+ spoonfish._frame_message:delete()
460460+ spoonfish._frame_message = nil
461461+ end
462462+ spoonfish.frame_message_timer = nil
463463+ end)
464464+ end
465465+end
466466+467467+-- for debugging
468468+spoonfish.draw_frames = function(space_id)
469469+ if not spoonfish.debug_frames then
470470+ return
471471+ end
472472+473473+ for i, f in pairs(spoonfish.spaces[space_id].frames) do
474474+ if f.outline == nil then
475475+ f.outline = hs.drawing.rectangle(f.rect)
476476+ else
477477+ f.outline:setFrame(f.rect)
478478+ end
479479+ f.outline:setLevel(hs.drawing.windowLevels.normal)
480480+ f.outline:setStrokeColor({ ["hex"] = "#ff0000" })
481481+ f.outline:setStrokeWidth(4)
482482+ f.outline:setFill(false)
483483+ f.outline:show()
484484+ end
371485end
+79-56
init.lua
···8899-- default configuration, can be overridden in loading init.lua before calling
1010-- spoonfish.start()
1111-spoonfish.gap = 22
1212-spoonfish.terminal = "iTerm2"
1313-spoonfish.frame_message_secs = 1
1414-spoonfish.frame_message_font_size = 18
1111+1212+-- prefix key (with control)
1313+spoonfish.prefix_key = "a"
1414+1515+-- set sizes to 0 to disable
1516spoonfish.border_color = "#000000"
1617spoonfish.border_size = 4
1718spoonfish.shadow_color = "#000000"
1819spoonfish.shadow_size = 8
19202121+-- space to put between windows in adjoining frames
2222+spoonfish.gap = 22
2323+2424+-- program to send 'new window' to for 'c' command
2525+spoonfish.terminal = "iTerm2"
2626+2727+-- for per-frame messages
2828+spoonfish.frame_message_secs = 1
2929+spoonfish.frame_message_font_size = 18
3030+3131+-- increment to resize interactively
3232+spoonfish.resize_unit = 10
3333+2034-- for these lists, anything not starting with ^ will be run through
2135-- escape_pattern to escape dashes and other special characters, so be sure
2236-- to escape such characters manually in ^-prefixed patterns
···3650spoonfish.start = function()
3751 local s = spoonfish
38523939- -- spaces and frame rects, keyed by frame number
4040- s.spaces = {}
4141- for _, space_id in
4242- pairs(hs.spaces.spacesForScreen(hs.screen.mainScreen():getUUID())) do
4343- s.spaces[space_id] = {}
4444- s.spaces[space_id].frames = {}
4545- s.spaces[space_id].frames[1] = hs.screen.mainScreen():frame()
4646- s.spaces[space_id].frame_previous = 1
4747- s.spaces[space_id].frame_current = 1
4848- end
4949-5053 s.direction = {
5154 LEFT = 1,
5255 RIGHT = 2,
···6972 -- apps, keyed by pid
7073 s.apps = {}
71747575+ -- debugging flags
7676+ s.debug_frames = false
7777+7278 s.log = hs.logger.new("spoonfish", "debug")
73798080+ -- spaces and frame rects, keyed by frame number
8181+ s.spaces = {}
8282+ for _, space_id in
8383+ pairs(hs.spaces.spacesForScreen(hs.screen.mainScreen():getUUID())) do
8484+ s.spaces[space_id] = { rect = hs.screen.mainScreen():frame() }
8585+ s.spaces[space_id].frames = {}
8686+ s.spaces[space_id].frames[1] = {
8787+ rect = hs.screen.mainScreen():frame(),
8888+ }
8989+ s.spaces[space_id].frame_previous = 1
9090+ s.spaces[space_id].frame_current = 1
9191+9292+ if space_id == hs.spaces.activeSpaceOnScreen() then
9393+ spoonfish.draw_frames(space_id)
9494+ end
9595+ end
9696+7497 -- watch for new apps launched
7598 s.app_watcher = hs.application.watcher.new(s.app_meta_event)
7699 s.app_watcher:start()
···85108 s.spaces_watcher = hs.spaces.watcher.new(spoonfish.spaces_event)
86109 s.spaces_watcher:start()
871108888- spoonfish.initialized = true
8989-111111+ s.initialized = true
90112 s.in_modal = false
91113 s.send_modal = false
114114+ s.resizing = false
9211593116 s.eventtap = hs.eventtap.new({ hs.eventtap.event.types.keyDown },
94117 function(event)
···113136 return false
114137 end
115138139139+ if s.resizing then
140140+ if key == "down" or key == "left" or key == "right" or key == "up" then
141141+ if nomod then
142142+ s.frame_resize(cs, space.frame_current, s.dir_from_string(key))
143143+ end
144144+ else
145145+ -- any other key will exit
146146+ s.resizing = false
147147+ s.frame_message(cs, space.frame_current, nil)
148148+ return true
149149+ end
150150+151151+ -- redisplay the frame message as it probably just changed size
152152+ s.frame_message(cs, space.frame_current, "Resize frame", true)
153153+154154+ return true
155155+ end
156156+116157 if not s.in_modal then
117117- if ctrl and key == "a" then
158158+ if ctrl and key == spoonfish.prefix_key then
118159 if s.send_modal then
119160 s.send_modal = false
120161 return false
···128169 return false
129170 end
130171131131- -- in-modal key bindings
172172+ -- we're in modal, so anything after this point will reset it
132173 s.in_modal = false
133174175175+ -- in-modal key bindings
134176 if flags:containExactly({ "shift" }) then
135177 key = string.upper(key)
136178 end
137179138138- spoonfish.ignore_events = true
180180+ s.ignore_events = true
181181+182182+ -- TODO: put these in a table for dynamic reassignment
139183140184 if key == "tab" then
141185 if nomod or ctrl then
142186 s.frame_focus(cs, space.frame_previous, true)
143187 end
144144- elseif key == "left" then
145145- if nomod then
146146- s.frame_focus(cs,
147147- s.frame_find(cs, space.frame_current, s.direction.LEFT), true)
148148- elseif ctrl then
149149- s.frame_swap(cs, space.frame_current,
150150- s.frame_find(cs, space.frame_current, s.direction.LEFT))
151151- end
152152- elseif key == "right" then
153153- if nomod then
154154- s.frame_focus(cs,
155155- s.frame_find(cs, space.frame_current, s.direction.RIGHT),
156156- true)
157157- elseif ctrl then
158158- s.frame_swap(cs, space.frame_current,
159159- s.frame_find(cs, space.frame_current, s.direction.RIGHT))
160160- end
161161- elseif key == "up" then
162162- if nomod then
163163- s.frame_focus(cs,
164164- s.frame_find(cs, space.frame_current, s.direction.UP),
165165- true)
166166- elseif ctrl then
167167- s.frame_swap(cs, space.frame_current,
168168- s.frame_find(cs, space.frame_current, s.direction.UP))
169169- end
170170- elseif key == "down" then
171171- if nomod then
172172- s.frame_focus(cs,
173173- s.frame_find(cs, space.frame_current, s.direction.DOWN),
174174- true)
175175- elseif ctrl then
176176- s.frame_swap(cs, space.frame_current,
177177- s.frame_find(cs, space.frame_current, s.direction.DOWN))
188188+ elseif key == "down" or key == "left" or key == "right" or key == "up" then
189189+ local touching = s.frame_find_touching(cs, space.frame_current,
190190+ s.dir_from_string(key))
191191+ if touching[1] then
192192+ if nomod then
193193+ s.frame_focus(cs, touching[1], true)
194194+ elseif ctrl then
195195+ s.frame_swap(cs, space.frame_current, touching[1])
196196+ end
178197 end
179198 elseif key == "space" then
180199 if nomod or ctrl then
181200 s.frame_cycle(cs, space.frame_current, true)
182201 end
183183- elseif key == "a" then
202202+ elseif key == spoonfish.prefix_key then
184203 if nomod then
185204 s.send_modal = true
186186- hs.eventtap.keyStroke({ "ctrl" }, "a")
205205+ hs.eventtap.keyStroke({ "ctrl" }, spoonfish.prefix_key)
187206 else
188207 s.frame_reverse_cycle(cs, space.frame_current, true)
189208 end
···207226 elseif key == "n" then
208227 if nomod or ctrl then
209228 s.frame_cycle(cs, space.frame_current, true)
229229+ end
230230+ elseif key == "r" then
231231+ if nomod then
232232+ s.frame_resize_interactively(cs, space.frame_current)
210233 end
211234 elseif key == "R" then
212235 if nomod then
+38
utils.lua
···11+table.copy = function(tab)
22+ local ret = {}
33+ for k, v in pairs(tab) do
44+ ret[k] = v
55+ end
66+ return ret
77+end
88+19-- come on, lua
210table.count = function(tab)
311 local c = 0
···715 return c
816end
9171818+table.keys = function(tab)
1919+ local ret = {}
2020+ for k, _ in pairs(tab) do
2121+ ret[#ret + 1] = k
2222+ end
2323+ return ret
2424+end
2525+2626+spoonfish.dir_from_string = function(str)
2727+ lstr = str:lower()
2828+2929+ if lstr == "left" then
3030+ return spoonfish.direction.LEFT
3131+ elseif lstr == "right" then
3232+ return spoonfish.direction.RIGHT
3333+ elseif lstr == "up" then
3434+ return spoonfish.direction.UP
3535+ elseif lstr == "down" then
3636+ return spoonfish.direction.DOWN
3737+ else
3838+ error("dir_from_string: invalid direction")
3939+ return nil
4040+ end
4141+end
4242+1043spoonfish.last_alert = nil
1144spoonfish.alert = function(str)
1245 if spoonfish.last_alert then
···4073 local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])'
4174 return str:gsub(quotepattern, "%%%1")
4275end
7676+7777+spoonfish.rect_s = function(rect)
7878+ return "{x:" .. rect.x .. " y:" .. rect.y .. " w:" .. rect.w .. " h:" ..
7979+ rect.h .. "}"
8080+end
+8-8
windows.lua
···3232 return
3333 end
34343535- local iframe = spoonfish.frame_with_gap(win["space"], win["frame"])
3535+ local iframe = spoonfish.frame_rect_with_gap(win["space"], win["frame"])
3636 win["win"]:move(iframe, nil, true, 0)
37373838 if win["space"] == hs.spaces.activeSpaceOnScreen() then
···5252 end
53535454 for _, w in pairs({ "shadow", "border" }) do
5555- local iframe = spoonfish.frame_with_gap(win["space"], win["frame"])
5656- iframe = spoonfish.inset(iframe, -(spoonfish.border_size))
5555+ local irect = spoonfish.frame_rect_with_gap(win["space"], win["frame"])
5656+ irect = spoonfish.inset(irect, -(spoonfish.border_size))
57575858 if w == "shadow" then
5959- iframe.x = iframe.x + spoonfish.shadow_size
6060- iframe.y = iframe.y + spoonfish.shadow_size
5959+ irect.x = irect.x + spoonfish.shadow_size
6060+ irect.y = irect.y + spoonfish.shadow_size
6161 end
62626363 if win[w] == nil then
6464- win[w] = hs.drawing.rectangle(iframe)
6464+ win[w] = hs.drawing.rectangle(irect)
6565 else
6666- win[w]:setFrame(iframe)
6666+ win[w]:setFrame(irect)
6767 end
6868- win[w]:setLevel(hs.drawing.windowLevels.desktopIcon) --floating)
6868+ win[w]:setLevel(hs.drawing.windowLevels.desktopIcon)
69697070 local color
7171 if w == "border" then