diff --git a/.gitignore b/.gitignore
index 53eea1f..0e1b3d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.DS_Store
/*.app
/*.tar.gz
+*.xcodeproj
diff --git a/README.md b/README.md
index 474b4cb..8dad794 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
[](#install)
[](https://github.com/relikd/Memmon/releases)
+[](https://github.com/relikd/Memmon/releases)
@@ -7,7 +8,7 @@
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
@@ -53,7 +54,7 @@ Yes, for example [Mjolnir](https://github.com/mjolnirapp/mjolnir) or [Hammerspoo
### 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.
diff --git a/src/Info.plist b/src/Info.plist
index 8916d53..c298e8b 100644
--- a/src/Info.plist
+++ b/src/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.3
+ 1.4
CFBundleVersion
42
LSMinimumSystemVersion
diff --git a/src/main.swift b/src/main.swift
index c9262af..af2acb7 100755
--- a/src/main.swift
+++ b/src/main.swift
@@ -13,8 +13,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var numScreens: Int = NSScreen.screens.count
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 spacesVisited: Set = [] // fill-up on space-switch
private var spacesNeedRestore: Set = [] // dropped after restore
func applicationDidFinishLaunching(_ aNotification: Notification) {
@@ -23,6 +23,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
// track space changes
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.activeSpaceChanged), name: NSWorkspace.activeSpaceDidChangeNotification, object: nil)
_ = self.currentSpace() // create space-id win for current space
+ self.spacesVisited = Set(self.getWinIds())
// create status menu icon
UserDefaults.standard.register(defaults: ["icon": 2])
let icon = UserDefaults.standard.integer(forKey: "icon")
@@ -36,7 +37,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
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: "Quit", action: #selector(NSApp.terminate), keyEquivalent: "q")
}
@@ -46,9 +47,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
func applicationDidChangeScreenParameters(_ notification: Notification) {
- if numScreens != NSScreen.screens.count {
+ if self.numScreens != NSScreen.screens.count {
self.saveState()
- numScreens = NSScreen.screens.count
+ self.numScreens = NSScreen.screens.count
+ self.spacesVisited.removeAll(keepingCapacity: true)
self.restoreState()
}
}
@@ -61,31 +63,36 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private func saveState() {
self.spacesNeedRestore = Set(self.spacesAll)
- guard self.time.timeIntervalSinceNow < -5 else {
- // Last save is less than 5 sec ago.
- // Do not override a (probably) still correct state.
- // Otherwise a monitor flicker will forget win-positions in other spaces.
- return
+ if self.state[self.numScreens] == nil {
+ self.state[self.numScreens] = [:] // otherwise state.keys wont run
}
let newState = self.getState()
- self.state[numScreens] = newState
- // update existing
let dummy: WinPos = (0, CGRect.zero)
for kNum in self.state.keys {
- if kNum == numScreens { continue } // current state, already set above
+ let isCurrent = kNum == self.numScreens
var tmp_state: WinConf = [:]
- for (n_app, new_val) in newState {
- if let old_val = self.state[kNum]![n_app] {
- tmp_state[n_app] = []
- for (n_win, _) in new_val {
- let old_pos = old_val.first { $0.0 == n_win }
- tmp_state[n_app]!.append(old_pos ?? dummy)
+ for (n_app, n_windows) in newState {
+ if let old_windows = self.state[kNum]![n_app] {
+ var win_arr: [WinPos] = []
+ for n_win in n_windows {
+ // In theory, every space that was visited, was also restored.
+ // 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.time = Date(timeIntervalSinceNow: 0)
}
private func getState() -> WinConf {
@@ -125,7 +132,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if let space = currentSpace(), self.spacesNeedRestore.contains(space) {
self.spacesNeedRestore.remove(space)
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) })
}
}