diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 1d10b7e1..22f233ec 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,7 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AeroSpace.xcodeproj/project.pbxproj b/AeroSpace.xcodeproj/project.pbxproj
index 2277a31d..2e641346 100644
--- a/AeroSpace.xcodeproj/project.pbxproj
+++ b/AeroSpace.xcodeproj/project.pbxproj
@@ -9,7 +9,9 @@
/* Begin PBXBuildFile section */
07FF7938628995F68DFEB524 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F99E8C4FD17A1D939C41F1 /* ViewModel.swift */; };
080DAA83BADA766D94A2BD3C /* Workspace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A4C333EA5B4D4527DD97D4 /* Workspace.swift */; };
+ 082EECCB2607F31DCBBF3870 /* defaultConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA7CD89E786588324DFB5575 /* defaultConfig.swift */; };
115F5CA4BEB80B645E66D198 /* NSScreenEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF3BB3DD434C75536217CB88 /* NSScreenEx.swift */; };
+ 1C46EBB55D401C0D1AFD50F0 /* CollectionEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CE37C1B8D858C81A396F40 /* CollectionEx.swift */; };
1CB4082BE5C95CA8CD52BED9 /* Maybe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345148B22F8A8F85109229AE /* Maybe.swift */; };
2F8DC074DAB97DC87E07A559 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9CAC977020A08D0227FAFB2 /* Bundle.swift */; };
4005ECE237BD9230F74CA917 /* TreeNodeEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C39B0C4E4888832129C4C7 /* TreeNodeEx.swift */; };
@@ -18,7 +20,6 @@
64A058E536F1EEF7F01043AF /* TOMLKit in Frameworks */ = {isa = PBXBuildFile; productRef = EC8E4F2CA4FF8884F9F59975 /* TOMLKit */; };
66E6CDA75DDD5E4B9647EDE2 /* AeroSpaceApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E81623E8954701269A22322 /* AeroSpaceApp.swift */; };
6820E6846AE51B6988B6F673 /* utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD02433B4415EEB163074CE5 /* utils.swift */; };
- 7035674CE8D4D0D5D43FAD95 /* Toml.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB0A5C2842534F121A0C430 /* Toml.swift */; };
783B0B965BA45D7A2943F7BF /* TilingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E05FB0C7158C8B6DECBD603 /* TilingContainer.swift */; };
78EE0CEF814ABDBA67941B84 /* Rect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B788A95DD3C267878E05B5 /* Rect.swift */; };
7FE92DDAC2F094C83A177914 /* MacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6296D5F9AFE5F266EE4B1D0 /* MacApp.swift */; };
@@ -32,7 +33,9 @@
B1E2002BB8F70F2555AAA82D /* TreeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D295CA45172ADBDB1E4DF708 /* TreeNode.swift */; };
B3702BB393A9B03CCAE4C60E /* refresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526B113159987FA43EA41120 /* refresh.swift */; };
C0A88261ECF505FC5648FC0A /* OptionalEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9EDFD4A9F45182CA6E0BD7B /* OptionalEx.swift */; };
+ C39C2054893A6506C35732D7 /* config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651C5EE18862C252795811B3 /* config.swift */; };
E2FD8E2B2D2BE6B88BF8E8AD /* accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE605CF46DE6377C69B9D49D /* accessibility.swift */; };
+ F2AFA702961A1D653EB7D269 /* command.swift in Sources */ = {isa = PBXBuildFile; fileRef = 776E3F4EE298A9C69C97EF7F /* command.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -45,13 +48,17 @@
38A4C333EA5B4D4527DD97D4 /* Workspace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Workspace.swift; sourceTree = ""; };
3C2E5977331398421A4FC168 /* GlobalObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalObserver.swift; sourceTree = ""; };
3E05FB0C7158C8B6DECBD603 /* TilingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilingContainer.swift; sourceTree = ""; };
+ 51CE37C1B8D858C81A396F40 /* CollectionEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionEx.swift; sourceTree = ""; };
526B113159987FA43EA41120 /* refresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = refresh.swift; sourceTree = ""; };
+ 651C5EE18862C252795811B3 /* config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = config.swift; sourceTree = ""; };
6935AF0A2DB3D186D1C6218F /* NSWorkspaceEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSWorkspaceEx.swift; sourceTree = ""; };
+ 776E3F4EE298A9C69C97EF7F /* command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = command.swift; sourceTree = ""; };
7E6F3930E3BF5D8196A20E9B /* axObservers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = axObservers.swift; sourceTree = ""; };
883D7F7F87FBE7D0BDE4E87F /* ArrayEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayEx.swift; sourceTree = ""; };
A9EDFD4A9F45182CA6E0BD7B /* OptionalEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalEx.swift; sourceTree = ""; };
AAE5DCAEC5EE619CE33859E7 /* SequenceEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceEx.swift; sourceTree = ""; };
AF3BB3DD434C75536217CB88 /* NSScreenEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSScreenEx.swift; sourceTree = ""; };
+ BA7CD89E786588324DFB5575 /* defaultConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = defaultConfig.swift; sourceTree = ""; };
BD02433B4415EEB163074CE5 /* utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = utils.swift; sourceTree = ""; };
BEF353340822CD20E9DAB3EC /* AeroSpace.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AeroSpace.entitlements; sourceTree = ""; };
D295CA45172ADBDB1E4DF708 /* TreeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeNode.swift; sourceTree = ""; };
@@ -62,7 +69,6 @@
F6507EBAA795220FD0C05384 /* Monitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Monitor.swift; sourceTree = ""; };
F8C39B0C4E4888832129C4C7 /* TreeNodeEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeNodeEx.swift; sourceTree = ""; };
F9CAC977020A08D0227FAFB2 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; };
- FDB0A5C2842534F121A0C430 /* Toml.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toml.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -97,20 +103,22 @@
);
sourceTree = "";
};
- 62BEA6F49E6648E2EE3C208F /* Products */ = {
+ 50A41C4CCFC95C514CA1EAD5 /* config */ = {
isa = PBXGroup;
children = (
- 09685297933511208058F7CF /* AeroSpace.app */,
+ 776E3F4EE298A9C69C97EF7F /* command.swift */,
+ 651C5EE18862C252795811B3 /* config.swift */,
+ BA7CD89E786588324DFB5575 /* defaultConfig.swift */,
);
- name = Products;
+ path = config;
sourceTree = "";
};
- 80F16ED76205CA1AC4E0BF3E /* settings */ = {
+ 62BEA6F49E6648E2EE3C208F /* Products */ = {
isa = PBXGroup;
children = (
- FDB0A5C2842534F121A0C430 /* Toml.swift */,
+ 09685297933511208058F7CF /* AeroSpace.app */,
);
- path = settings;
+ name = Products;
sourceTree = "";
};
8338180CE208CBDCD6D8E911 /* src */ = {
@@ -126,8 +134,8 @@
526B113159987FA43EA41120 /* refresh.swift */,
24F99E8C4FD17A1D939C41F1 /* ViewModel.swift */,
89B36C927EF9F6CB9CE830C7 /* axWrappers */,
+ 50A41C4CCFC95C514CA1EAD5 /* config */,
38E5F06C862662755065FF1D /* model */,
- 80F16ED76205CA1AC4E0BF3E /* settings */,
C136937AA077E63558E9707C /* util */,
);
path = src;
@@ -147,6 +155,7 @@
isa = PBXGroup;
children = (
883D7F7F87FBE7D0BDE4E87F /* ArrayEx.swift */,
+ 51CE37C1B8D858C81A396F40 /* CollectionEx.swift */,
345148B22F8A8F85109229AE /* Maybe.swift */,
F6507EBAA795220FD0C05384 /* Monitor.swift */,
AF3BB3DD434C75536217CB88 /* NSScreenEx.swift */,
@@ -237,6 +246,7 @@
66E6CDA75DDD5E4B9647EDE2 /* AeroSpaceApp.swift in Sources */,
A2CBF9674964F9083BB198D2 /* ArrayEx.swift in Sources */,
2F8DC074DAB97DC87E07A559 /* Bundle.swift in Sources */,
+ 1C46EBB55D401C0D1AFD50F0 /* CollectionEx.swift in Sources */,
B0D0C37BAE7E7F0D0FF1E9FC /* GlobalObserver.swift in Sources */,
7FE92DDAC2F094C83A177914 /* MacApp.swift in Sources */,
518B9E5AC031C24C7C84CD70 /* MacWindow.swift in Sources */,
@@ -248,13 +258,15 @@
78EE0CEF814ABDBA67941B84 /* Rect.swift in Sources */,
AE76A183D0454E4C8ADCE380 /* SequenceEx.swift in Sources */,
783B0B965BA45D7A2943F7BF /* TilingContainer.swift in Sources */,
- 7035674CE8D4D0D5D43FAD95 /* Toml.swift in Sources */,
B1E2002BB8F70F2555AAA82D /* TreeNode.swift in Sources */,
4005ECE237BD9230F74CA917 /* TreeNodeEx.swift in Sources */,
07FF7938628995F68DFEB524 /* ViewModel.swift in Sources */,
080DAA83BADA766D94A2BD3C /* Workspace.swift in Sources */,
E2FD8E2B2D2BE6B88BF8E8AD /* accessibility.swift in Sources */,
96593DF93A69CA2E05189A3F /* axObservers.swift in Sources */,
+ F2AFA702961A1D653EB7D269 /* command.swift in Sources */,
+ C39C2054893A6506C35732D7 /* config.swift in Sources */,
+ 082EECCB2607F31DCBBF3870 /* defaultConfig.swift in Sources */,
B3702BB393A9B03CCAE4C60E /* refresh.swift in Sources */,
6820E6846AE51B6988B6F673 /* utils.swift in Sources */,
);
diff --git a/src/AeroSpaceApp.swift b/src/AeroSpaceApp.swift
index f8f0ae3b..bf2c014f 100644
--- a/src/AeroSpaceApp.swift
+++ b/src/AeroSpaceApp.swift
@@ -17,21 +17,29 @@ struct Setting {
let modifiers: NSEvent.ModifierFlags
}
+//osascript -e 'tell app "Terminal"
+//activate
+//do script "tail -f ~/log/0.txt"
+//end tell'
+
@main
struct AeroSpaceApp: App {
var hotKeys: [HotKey] = [] // Keep hotkeys in memory
@StateObject var viewModel = ViewModel.shared
init() {
- checkAccessibilityPermissions()
- GlobalObserver.initObserver()
- for setting in settings {
- hotKeys.append(HotKey(key: setting.hotkey, modifiers: setting.modifiers, keyUpHandler: {
- switchToWorkspace(Workspace.get(byName: setting.name))
- }))
- }
- refresh()
- test()
+ reloadConfig()
+
+ //checkAccessibilityPermissions()
+ //GlobalObserver.initObserver()
+ //for setting in settings {
+ // hotKeys.append(HotKey(key: setting.hotkey, modifiers: setting.modifiers, keyUpHandler: {
+ // switchToWorkspace(Workspace.get(byName: setting.name))
+ // }))
+ //}
+ //refresh()
+ //test()
+
}
var body: some Scene {
@@ -44,16 +52,20 @@ struct AeroSpaceApp: App {
switchToWorkspace(workspace)
} label: {
Toggle(isOn: workspace.name == viewModel.focusedWorkspaceTrayText
- ? Binding(get: { true }, set: { _, _ in })
- : Binding(get: { false }, set: { _, _ in })) {
+ ? Binding(get: { true }, set: { _, _ in })
+ : Binding(get: { false }, set: { _, _ in })) {
let monitor = (workspace.assignedMonitor?.name).flatMap { " - \($0)" } ?? ""
Text(workspace.name + monitor).font(.system(.body, design: .monospaced))
}
}
}
Divider()
- Button("Quit \(Bundle.appName)") { NSApplication.shared.terminate(nil) }
- .keyboardShortcut("Q", modifiers: .command)
+ Button("Reload config") {
+ } // todo
+ Button("Quit \(Bundle.appName)") {
+ NSApplication.shared.terminate(nil)
+ }
+ .keyboardShortcut("Q", modifiers: .command)
} label: {
// .font(.system(.body, design: .monospaced)) doesn't work unfortunately :(
Text(viewModel.focusedWorkspaceTrayText)
diff --git a/src/ViewModel.swift b/src/ViewModel.swift
index cf49a4da..11d45f64 100644
--- a/src/ViewModel.swift
+++ b/src/ViewModel.swift
@@ -6,6 +6,6 @@ class ViewModel: ObservableObject {
private init() {
}
- @Published var focusedWorkspaceTrayText: String = currentEmptyWorkspace.name // settings.first?.name ?? "W: 1"
+ @Published var focusedWorkspaceTrayText: String = currentEmptyWorkspace.name // config.first?.name ?? "W: 1"
}
diff --git a/src/config/command.swift b/src/config/command.swift
new file mode 100644
index 00000000..b27f1fb4
--- /dev/null
+++ b/src/config/command.swift
@@ -0,0 +1,77 @@
+import Foundation
+
+protocol Command {
+ func run()
+}
+
+struct ChainedCommand: Command {
+ let subCommands: [Command]
+
+ func run() {
+ for command in subCommands {
+ command.run()
+ }
+ }
+}
+
+enum NoOpCommand: Command {
+ case instance
+
+ func run() {} // It does nothing
+}
+
+struct WorkspaceCommand : Command {
+ let workspaceName: String
+
+ func run() {
+ switchToWorkspace(Workspace.get(byName: workspaceName))
+ }
+}
+
+struct ModeCommand: Command {
+ let idToActivate: String
+
+ func run() {
+ for mode in config.modes {
+ for binding in mode.bindings {
+ if mode.id == idToActivate {
+ binding.activate()
+ } else {
+ binding.deactivate()
+ }
+ }
+ }
+ }
+}
+
+struct BashCommand: Command {
+ let bashCommand: String
+
+ func run() {
+ do {
+ try Process.run(URL(filePath: "/bin/bash"), arguments: ["-c", bashCommand])
+ } catch {
+ }
+ }
+}
+
+struct FocusCommand: Command {
+ let direction: Direction
+
+ enum Direction {
+ case up
+ case down
+ case left
+ case right
+
+ case parent
+ case child
+ case floating
+ case tiling
+ case toggle_tiling_floating
+ }
+
+ func run() {
+ // todo
+ }
+}
diff --git a/src/config/config.swift b/src/config/config.swift
new file mode 100644
index 00000000..07b0755c
--- /dev/null
+++ b/src/config/config.swift
@@ -0,0 +1,203 @@
+import Foundation
+import TOMLKit
+import HotKey
+
+// todo convert all `error` during config parsing to returning defaults and reporting errors to where? Some kind of log?
+
+struct Config {
+ let afterStartupCommand: Command
+ let usePaddingForNestedContainersWithTheSameOrientation: Bool
+ let autoFlattenContainers: Bool
+ let floatingWindowsOnTop: Bool
+}
+
+struct ConfigRoot {
+ let config: Config
+ let modes: [Mode]
+}
+
+struct Mode {
+ let id: String
+ /// User visible name. Optional. todo drop it?
+ let name: String?
+ let bindings: [HotkeyBinding]
+}
+
+var config: ConfigRoot = defaultConfig
+
+func reloadConfig() {
+ let rawConfig = String.fromUrl(FileManager.default.homeDirectoryForCurrentUser.appending(path: ".aerospace.toml"))
+ config = parseConfigRoot(rawConfig ?? "")
+}
+
+private func parseConfigRoot(_ rawToml: String) -> ConfigRoot {
+ let rawTable: TOMLTable
+ do {
+ rawTable = try TOMLTable(string: rawToml)
+ } catch let e as TOMLParseError {
+ error(e.debugDescription)
+ } catch let e {
+ error(e.localizedDescription)
+ }
+ let backtrace: TomlBacktrace = .root
+ var config: Config? = nil
+ var modes: [Mode] = []
+ for (key, value) in rawTable {
+ switch key {
+ case "config":
+ config = parseConfig(value, backtrace + .key("config"))
+ case "mode":
+ modes = parseModes(value, backtrace + .key("mode"))
+ default:
+ unknownKeyError(key, backtrace + .key(key))
+ }
+ }
+ return ConfigRoot(
+ config: config ?? defaultConfig.config,
+ modes: modes
+ )
+}
+
+private func parseModes(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> [Mode] {
+ // todo
+ []
+}
+
+private func unknownKeyError(_ key: String, _ backtrace: TomlBacktrace) -> Never {
+ error("\(backtrace): Unknown key '\(key)'")
+}
+
+private func expectedActualTypeError(expected: TOMLType, actual: TOMLType, _ backtrace: TomlBacktrace) -> T {
+ expectedActualTypeError(expected: [expected], actual: actual, backtrace)
+}
+
+private func expectedActualTypeError(expected: [TOMLType], actual: TOMLType, _ backtrace: TomlBacktrace) -> T {
+ error("\(backtrace): Expected type is \(expected.map { $0.description }.joined(separator: " or ")). But actual type is \(actual)")
+}
+
+private func parseConfig(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> Config {
+ let rawTable = raw.table ?? expectedActualTypeError(expected: .table, actual: raw.type, backtrace)
+
+ let key1 = "after-startup-command"
+ var value1: Command = defaultConfig.config.afterStartupCommand
+
+ let key2 = "use-padding-for-nested-containers-with-the-same-orientation"
+ var value2: Bool = defaultConfig.config.usePaddingForNestedContainersWithTheSameOrientation
+
+ let key3 = "auto-flatten-containers"
+ var value3: Bool = defaultConfig.config.autoFlattenContainers
+
+ let key4 = "floating-windows-on-top"
+ var value4: Bool = defaultConfig.config.floatingWindowsOnTop
+
+ for (key, value) in rawTable {
+ let keyBacktrace = backtrace + .key(key)
+ switch key {
+ case key1:
+ value1 = parseCommand(value, keyBacktrace)
+ case key2:
+ value2 = parseBool(value, keyBacktrace)
+ case key3:
+ value3 = parseBool(value, keyBacktrace)
+ case key4:
+ value4 = parseBool(value, keyBacktrace)
+ default:
+ unknownKeyError(key, backtrace + .key(key))
+ }
+ }
+
+ return Config(
+ afterStartupCommand: value1,
+ usePaddingForNestedContainersWithTheSameOrientation: value2,
+ autoFlattenContainers: value3,
+ floatingWindowsOnTop: value4
+ )
+}
+
+private func parseBool(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> Bool {
+ raw.bool ?? expectedActualTypeError(expected: .bool, actual: raw.type, backtrace)
+}
+
+class HotkeyBinding {
+ let modifiers: NSEvent.ModifierFlags
+ let key: Key
+ let command: Command
+ private var hotKey: HotKey? = nil
+
+ init(_ modifiers: NSEvent.ModifierFlags, _ key: Key, _ command: Command) {
+ self.modifiers = modifiers
+ self.key = key
+ self.command = command
+ }
+
+ func activate() {
+ hotKey = HotKey(key: key, modifiers: modifiers, keyUpHandler: command.run)
+ }
+
+ func deactivate() {
+ hotKey = nil
+ }
+}
+
+private indirect enum TomlBacktrace: CustomStringConvertible {
+ case root
+ case key(String)
+ case index(Int)
+ case pair((TomlBacktrace, TomlBacktrace))
+
+ var description: String {
+ switch self {
+ case .root:
+ return "" // todo rework
+ case .key(let value):
+ return "." + value
+ case .index(let index):
+ return "[\(index)]"
+ case .pair((let first, let second)):
+ return first.description + second.description
+ }
+ }
+
+ static func +(lhs: TomlBacktrace, rhs: TomlBacktrace) -> TomlBacktrace {
+ pair((lhs, rhs))
+ }
+}
+
+private func parseCommand(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> Command {
+ if let rawString = raw.string {
+ return parseSingleCommand(rawString, backtrace)
+ } else if let rawArray = raw.array {
+ let commands: [Command] = (0.. Command {
+ let words = raw.split(separator: " ")
+ let args = words[1...]
+ switch words.first {
+ case "workspace":
+ let name = args.singleOrNil() ?? errorT(
+ "\(backtrace): Can't parse 'workspace' command arguments: '\(args.joined())'. Expected: a single arg"
+ )
+ return WorkspaceCommand(workspaceName: String(name))
+ case "mode":
+ let id = args.singleOrNil() ?? errorT(
+ "\(backtrace): Can't parse 'mode' command arguments: '\(args.joined())'. Expected: a single arg"
+ )
+ return ModeCommand(idToActivate: String(id))
+ case "bash":
+ return BashCommand(bashCommand: raw.removePrefix("bash"))
+ case "":
+ error("\(backtrace): Can't parse empty string command")
+ default:
+ error("\(backtrace): Can't parse '\(raw)' command")
+ }
+}
diff --git a/src/config/defaultConfig.swift b/src/config/defaultConfig.swift
new file mode 100644
index 00000000..44100f15
--- /dev/null
+++ b/src/config/defaultConfig.swift
@@ -0,0 +1,20 @@
+let defaultConfig = ConfigRoot(
+ config: Config(
+ afterStartupCommand: NoOpCommand.instance,
+ usePaddingForNestedContainersWithTheSameOrientation: false,
+ autoFlattenContainers: true,
+ floatingWindowsOnTop: true
+ ),
+ modes: [
+ Mode(
+ id: "main",
+ name: nil,
+ bindings: [
+ HotkeyBinding(.option, .h, FocusCommand(direction: .left)),
+ HotkeyBinding(.option, .j, FocusCommand(direction: .down)),
+ HotkeyBinding(.option, .k, FocusCommand(direction: .up)),
+ HotkeyBinding(.option, .l, FocusCommand(direction: .right)),
+ ]
+ )
+ ]
+)
diff --git a/src/model/TilingContainer.swift b/src/model/TilingContainer.swift
index c2040429..19d15fd4 100644
--- a/src/model/TilingContainer.swift
+++ b/src/model/TilingContainer.swift
@@ -20,7 +20,7 @@ class TilingContainer: TreeNode {
extension TilingContainer {
func normalizeWeightsRecursive() {
guard let delta = (getWeight(orientation) - children.sumOf { $0.getWeight(orientation) })
- .div(children.count) else { return }
+ .div(children.count) else { return }
for child in children {
child.setWeight(orientation, child.getWeight(orientation) + delta)
if let tilingChild = child as? TilingContainer {
diff --git a/src/model/Workspace.swift b/src/model/Workspace.swift
index 4e57b910..1ab14a0a 100644
--- a/src/model/Workspace.swift
+++ b/src/model/Workspace.swift
@@ -153,15 +153,15 @@ private func rearrangeWorkspacesOnMonitors() {
monitorToNotEmptyWorkspace = [:]
let monitors = NSScreen.screens.map { $0.monitor }.toSet()
let preservedWorkspaces: [Workspace] = oldMonitorToWorkspaces
- .filter { oldMonitor, oldWorkspace in monitors.contains(oldMonitor) }
- .map { $0.value.valueOrNil }
- .filterNotNil()
+ .filter { oldMonitor, oldWorkspace in monitors.contains(oldMonitor) }
+ .map { $0.value.valueOrNil }
+ .filterNotNil()
let lostWorkspaces: [Workspace] = oldMonitorToWorkspaces
- .filter { oldMonitor, oldWorkspace in !monitors.contains(oldMonitor) }
- .map { $0.value.valueOrNil }
- .filterNotNil()
+ .filter { oldMonitor, oldWorkspace in !monitors.contains(oldMonitor) }
+ .map { $0.value.valueOrNil }
+ .filterNotNil()
var poolOfWorkspaces: [Workspace] =
- Workspace.all.reversed() - (preservedWorkspaces + lostWorkspaces) + lostWorkspaces
+ Workspace.all.reversed() - (preservedWorkspaces + lostWorkspaces) + lostWorkspaces
for monitor in monitors {
// If monitors change, most likely we will preserve only the main monitor (It always has (0, 0) origin)
if let existing = oldMonitorToWorkspaces[monitor] {
diff --git a/src/settings/Toml.swift b/src/settings/Toml.swift
deleted file mode 100644
index 4250cffe..00000000
--- a/src/settings/Toml.swift
+++ /dev/null
@@ -1,8 +0,0 @@
-import Foundation
-import TOMLKit
-
-func foo(table: TOMLTable) {
- table["foo"]?.type
- //TOMLTable()
- //TOMLDecoder().dec
-}
diff --git a/src/util/ArrayEx.swift b/src/util/ArrayEx.swift
index aa0803d0..9967ff81 100644
--- a/src/util/ArrayEx.swift
+++ b/src/util/ArrayEx.swift
@@ -1,8 +1,4 @@
extension Array {
- func singleOrNil() -> Element? {
- count == 1 ? first : nil
- }
-
func singleOrNil(where predicate: (Self.Element) throws -> Bool) rethrows -> Self.Element? {
var found: Self.Element? = nil
for elem in self {
diff --git a/src/util/CollectionEx.swift b/src/util/CollectionEx.swift
new file mode 100644
index 00000000..956e7e51
--- /dev/null
+++ b/src/util/CollectionEx.swift
@@ -0,0 +1,5 @@
+extension Collection {
+ func singleOrNil() -> Element? {
+ count == 1 ? first : nil
+ }
+}
\ No newline at end of file
diff --git a/src/util/utils.swift b/src/util/utils.swift
index 179b94d8..2a5f9c1d 100644
--- a/src/util/utils.swift
+++ b/src/util/utils.swift
@@ -37,6 +37,20 @@ extension String? {
var isNilOrEmpty: Bool { self == nil || self == "" }
}
+extension String {
+ static func fromUrl(_ url: URL) -> String? {
+ do {
+ return try String(contentsOf: url)
+ } catch {
+ return nil
+ }
+ }
+
+ func removePrefix(_ prefix: String) -> String {
+ hasPrefix(prefix) ? String(dropFirst(prefix.count)) : self
+ }
+}
+
extension Double {
var squared: Double { self * self }
}
@@ -57,11 +71,11 @@ extension CGPoint {
/// Distance to ``Rect`` outline frame
func distanceToRectFrame(to rect: Rect) -> CGFloat {
let list: [CGFloat] = ((rect.minY..