restoring spaces v2

This commit is contained in:
relikd
2021-10-27 18:48:16 +02:00
parent 9920201fe2
commit d26169f8b9
4 changed files with 33 additions and 23 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.DS_Store .DS_Store
/*.app /*.app
/*.tar.gz /*.tar.gz
*.xcodeproj

View File

@@ -1,5 +1,6 @@
[![macOS 10.10+](https://img.shields.io/badge/macOS-10.10+-888)](#install) [![macOS 10.10+](https://img.shields.io/badge/macOS-10.10+-888)](#install)
[![Current release](https://img.shields.io/github/release/relikd/Memmon)](https://github.com/relikd/Memmon/releases) [![Current release](https://img.shields.io/github/release/relikd/Memmon)](https://github.com/relikd/Memmon/releases)
[![All downloads](https://img.shields.io/github/downloads/relikd/Memmon/total)](https://github.com/relikd/Memmon/releases)
<img src="img/icon.svg" width="180" height="180"> <img src="img/icon.svg" width="180" height="180">
@@ -7,7 +8,7 @@
Memmon remembers what your Mac forgets A simple deamon that restores your window positions on external monitors. Memmon remembers what your Mac forgets A simple deamon that restores your window positions on external monitors.
**Limitations:** Currently, Memmon can not restore windows in other spaces, only the currently active space. If you know a way to access the accessibility settings of a different space, let me know. **Limitations:** Currently, Memmon restores windows in other spaces only if the space is activated. If you know a way to access the accessibility settings of a different space, let me know.
## Install ## Install
@@ -53,7 +54,7 @@ Yes, for example [Mjolnir](https://github.com/mjolnirapp/mjolnir) or [Hammerspoo
### What is it good for? ### What is it good for?
First off, Memmon is just 140 lines of code no dependencies. You can audit it in 5 minutes and build it from scratch just run `make`. First off, Memmon is less than 300 lines of code no dependencies. You can audit it in 10 minutes... And build it from scratch just run `make`.
Secondly, it does one thing and one thing only: Save and restore window positions whenever your monitor setup changes. Secondly, it does one thing and one thing only: Save and restore window positions whenever your monitor setup changes.

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.3</string> <string>1.4</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>42</string> <string>42</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>

View File

@@ -13,8 +13,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var numScreens: Int = NSScreen.screens.count private var numScreens: Int = NSScreen.screens.count
private var state: [Int: WinConf] = [:] // [screencount: [pid: [windows]]] private var state: [Int: WinConf] = [:] // [screencount: [pid: [windows]]]
private var time: Date = Date.distantPast
private var spacesAll: [SpaceId] = [] // keep forever (and keep order) private var spacesAll: [SpaceId] = [] // keep forever (and keep order)
private var spacesVisited: Set<WinNum> = [] // fill-up on space-switch
private var spacesNeedRestore: Set<SpaceId> = [] // dropped after restore private var spacesNeedRestore: Set<SpaceId> = [] // dropped after restore
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
@@ -23,6 +23,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
// track space changes // track space changes
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.activeSpaceChanged), name: NSWorkspace.activeSpaceDidChangeNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.activeSpaceChanged), name: NSWorkspace.activeSpaceDidChangeNotification, object: nil)
_ = self.currentSpace() // create space-id win for current space _ = self.currentSpace() // create space-id win for current space
self.spacesVisited = Set(self.getWinIds())
// create status menu icon // create status menu icon
UserDefaults.standard.register(defaults: ["icon": 2]) UserDefaults.standard.register(defaults: ["icon": 2])
let icon = UserDefaults.standard.integer(forKey: "icon") let icon = UserDefaults.standard.integer(forKey: "icon")
@@ -36,7 +37,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
} }
self.statusItem.menu = NSMenu(title: "") self.statusItem.menu = NSMenu(title: "")
self.statusItem.menu!.addItem(withTitle: "Memmon (v1.3)", action: nil, keyEquivalent: "") self.statusItem.menu!.addItem(withTitle: "Memmon (v1.4)", action: nil, keyEquivalent: "")
self.statusItem.menu!.addItem(withTitle: "Hide Status Icon", action: #selector(self.enableInvisbleMode), keyEquivalent: "") self.statusItem.menu!.addItem(withTitle: "Hide Status Icon", action: #selector(self.enableInvisbleMode), keyEquivalent: "")
self.statusItem.menu!.addItem(withTitle: "Quit", action: #selector(NSApp.terminate), keyEquivalent: "q") self.statusItem.menu!.addItem(withTitle: "Quit", action: #selector(NSApp.terminate), keyEquivalent: "q")
} }
@@ -46,9 +47,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
func applicationDidChangeScreenParameters(_ notification: Notification) { func applicationDidChangeScreenParameters(_ notification: Notification) {
if numScreens != NSScreen.screens.count { if self.numScreens != NSScreen.screens.count {
self.saveState() self.saveState()
numScreens = NSScreen.screens.count self.numScreens = NSScreen.screens.count
self.spacesVisited.removeAll(keepingCapacity: true)
self.restoreState() self.restoreState()
} }
} }
@@ -61,31 +63,36 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private func saveState() { private func saveState() {
self.spacesNeedRestore = Set(self.spacesAll) self.spacesNeedRestore = Set(self.spacesAll)
guard self.time.timeIntervalSinceNow < -5 else { if self.state[self.numScreens] == nil {
// Last save is less than 5 sec ago. self.state[self.numScreens] = [:] // otherwise state.keys wont run
// Do not override a (probably) still correct state.
// Otherwise a monitor flicker will forget win-positions in other spaces.
return
} }
let newState = self.getState() let newState = self.getState()
self.state[numScreens] = newState
// update existing
let dummy: WinPos = (0, CGRect.zero) let dummy: WinPos = (0, CGRect.zero)
for kNum in self.state.keys { for kNum in self.state.keys {
if kNum == numScreens { continue } // current state, already set above let isCurrent = kNum == self.numScreens
var tmp_state: WinConf = [:] var tmp_state: WinConf = [:]
for (n_app, new_val) in newState { for (n_app, n_windows) in newState {
if let old_val = self.state[kNum]![n_app] { if let old_windows = self.state[kNum]![n_app] {
tmp_state[n_app] = [] var win_arr: [WinPos] = []
for (n_win, _) in new_val { for n_win in n_windows {
let old_pos = old_val.first { $0.0 == n_win } // In theory, every space that was visited, was also restored.
tmp_state[n_app]!.append(old_pos ?? dummy) // If not visited (and not restored) then windows may still appear minimized,
// so we rather copy the old value, assuming windows weren't moved while in an unvisited space.
if isCurrent && self.spacesVisited.contains(n_win.0) {
win_arr.append(n_win)
} else {
// caution! the positions of all other states are updated as well.
let old_win = old_windows.first { $0.0 == n_win.0 }
win_arr.append(old_win ?? dummy)
}
} }
tmp_state[n_app] = win_arr
} else if isCurrent { // and not saved yet
tmp_state[n_app] = n_windows // TODO: or only add if visited?
} }
} }
self.state[kNum] = tmp_state self.state[kNum] = tmp_state
} }
self.time = Date(timeIntervalSinceNow: 0)
} }
private func getState() -> WinConf { private func getState() -> WinConf {
@@ -125,7 +132,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if let space = currentSpace(), self.spacesNeedRestore.contains(space) { if let space = currentSpace(), self.spacesNeedRestore.contains(space) {
self.spacesNeedRestore.remove(space) self.spacesNeedRestore.remove(space)
let spaceWinNums = self.getWinIds() let spaceWinNums = self.getWinIds()
for (pid, bounds) in self.state[numScreens] ?? [:] { self.spacesVisited.formUnion(spaceWinNums)
for (pid, bounds) in self.state[self.numScreens] ?? [:] {
self.setWindowSizes(pid, bounds.filter{ spaceWinNums.contains($0.0) }) self.setWindowSizes(pid, bounds.filter{ spaceWinNums.contains($0.0) })
} }
} }