-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwindow_mover.lua
186 lines (155 loc) Β· 4.98 KB
/
window_mover.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
--
-- Window Mover
--
-- Move windows on wake up or screen change
--
local logger = hs.logger.new('window_mover', 'debug')
Wm = {}
Wm.stacks = require('window_mover_stacks')
local WINDOW_MAP_UPDATE_INTERVAL = 600
local UPDATE_WAKE_UP_DELAY = 30
-- hs.window.get() returns nil if the delay is too short.
local SCREEN_WATCHER_DEBOUNCE_DELAY = 2
--
-- Save the position and size of the running windows
--
function Wm.insertWindowMap()
if Cafe.isLocked then
logger:d('Skipping inserting a new window map because the screen is locked')
return
end
logger:d('Inserting a new window map')
local windowMap = {}
local windows = hs.window.allWindows()
for _, win in ipairs(windows) do
logger:d('Update window map for the window: ' .. win:title())
local frame = win:frame()
if (frame.w > 0 and frame.h > 0) then
windowMap[win:id()] = frame
end
end
Wm.stacks.push(windowMap)
end
--
-- Restore the position and size of the running windows
--
local function restoreWindow(win)
local windowMap = Wm.stacks.peek()
if (not windowMap) then
logger:d('No window map to restore for the current number of screens')
return
end
local frame = windowMap[win:id()]
if (not frame) then
logger:d('No position and size for the window: ' .. win:title())
return
end
logger:d('Getting the application for the window: ' .. win:title())
local app = win:application()
-- Retry until the window is set to the correct position and size
timer.safeDoUntil(function()
return win:frame() == frame
end, function()
logger:i('Setting frame size for the window: ' .. app:name())
win:setFrame(frame)
end, function()
logger:i('Failed to set frame size correctly: ' .. app:name())
end)
end
--
-- Restore the position and size of the running windows
--
-- @return true if the windows are restored successfully
--
function Wm.restoreAll()
logger:d('Restoring position and size')
local windowMap = Wm.stacks.peek()
if (not windowMap) then
logger:d('No window map to restore for the current number of screens')
return
elseif tl.tableLength(windowMap) == 0 then
logger:d('Window map is empty')
return
end
local failCount = 0
for winId, frame in pairs(windowMap) do
local win = hs.window.get(winId)
if (win) then
logger:d('Restoring position and size of the window: ' .. win:title())
logger:d('Position and size: ' .. hs.inspect(frame))
restoreWindow(win)
else
failCount = failCount + 1
logger:d('Window not found: ' .. winId)
end
end
local isAllFailed = failCount == tl.tableLength(windowMap)
if isAllFailed then
logger:i('All windows are failed to restore')
end
-- If all the windows are failed to restore,
-- it's probably because the windows weren't accessible yet.
return not isAllFailed
end
local screenWatcherTimer
function Wm.screenWatcherHandler()
logger:d('Screen changed')
if Cafe.isSleeping then
logger:d('Skipping the screen change because the computer is sleeping')
return
end
-- Debounce the timer
if (screenWatcherTimer) then
logger:d('Debouncing the screen watcher timer. ' ..
'Screen count: '.. #hs.screen.allScreens())
screenWatcherTimer:stop()
end
-- For some reason, it doesn't recognize the number of screens correctly
-- if it runs immediately after the screen change.
local MAX_TRIAL = 3
local screenCount = #hs.screen.allScreens()
screenWatcherTimer = timer.safeDoUntil(function()
no.alert('Starting moving windows')
logger:i('Starting moving windows. Number of screens: ' .. screenCount)
return Wm.restoreAll()
end, function()
logger:w('Failed to restore windows. ' ..
'Number of screens: ' .. screenCount)
end, function()
logger:i('Successfully restored windows. ' ..
'Number of screens: ' .. screenCount)
end, MAX_TRIAL, SCREEN_WATCHER_DEBOUNCE_DELAY)
end
function Wm.restorePreviousMap()
logger:d('Manually restoring previous window map')
if Wm.stacks.isEmpty() then
logger:d('No previous window map')
no.alert('No previous window map')
return
elseif Wm.stacks.size() == 1 then
logger:d('This is the last window map')
no.alert('This is the last window map')
Wm.restoreAll()
else
logger:d('Using previous window map')
no.alert('Using previous window map')
Wm.stacks.pop()
Wm.restoreAll()
end
end
hs.hotkey.bind({"ctrl", "shift"}, "r", Wm.restorePreviousMap)
--
-- Screen watcher to move all windows to their screens
--
Wm.screenWatcher = hs.screen.watcher.new(Wm.screenWatcherHandler)
Wm.screenWatcher:start()
Wm.updateWindowTimer = hs.timer.doEvery(WINDOW_MAP_UPDATE_INTERVAL, function()
-- Don't update the window map if the computer is sleeping or if it just woke up.
local secondsSinceWakeUp = os.time() - Cafe.wakeUpTime
if Cafe.isSleeping or secondsSinceWakeUp < UPDATE_WAKE_UP_DELAY then
logger:d('Skipping the window update because the computer is sleeping')
return
end
Wm.insertWindowMap()
end)
Wm.insertWindowMap()