wrap blocks around a layout concept
This commit is contained in:
@@ -24,6 +24,7 @@ extension PHBar {
|
|||||||
let config = try PHConfig.load()
|
let config = try PHConfig.load()
|
||||||
let environment = PHEnvironment.process.merging(config.env)
|
let environment = PHEnvironment.process.merging(config.env)
|
||||||
let theme = try PHTheme.load(config.theme, environment: environment)
|
let theme = try PHTheme.load(config.theme, environment: environment)
|
||||||
|
let layouts = try PHLayout.load(["default", "mini"])
|
||||||
|
|
||||||
// NOTE: Make sure NSApp.run() runs in the main thread
|
// NOTE: Make sure NSApp.run() runs in the main thread
|
||||||
dispatchPrecondition(condition: .onQueue(.main))
|
dispatchPrecondition(condition: .onQueue(.main))
|
||||||
@@ -31,7 +32,7 @@ extension PHBar {
|
|||||||
try MainActor.assumeIsolated {
|
try MainActor.assumeIsolated {
|
||||||
guard !NSScreen.screens.isEmpty else { throw CleanExit.message("No monitor found") }
|
guard !NSScreen.screens.isEmpty else { throw CleanExit.message("No monitor found") }
|
||||||
|
|
||||||
let delegate = PHBarDelegate(config: config, theme: theme, debug: debug)
|
let delegate = PHBarDelegate(config: config, theme: theme, layouts: layouts, debug: debug)
|
||||||
let app = NSApplication.shared
|
let app = NSApplication.shared
|
||||||
app.setActivationPolicy(.accessory)
|
app.setActivationPolicy(.accessory)
|
||||||
app.delegate = delegate
|
app.delegate = delegate
|
||||||
|
|||||||
@@ -9,21 +9,18 @@ final class PHBarDelegate: NSObject, NSApplicationDelegate {
|
|||||||
/// Convenience accessor over `windows`.
|
/// Convenience accessor over `windows`.
|
||||||
var controllers: [BarController] { windows.map(\.barController) }
|
var controllers: [BarController] { windows.map(\.barController) }
|
||||||
|
|
||||||
/// Resolves a controller for any screen from the current config. Swapped by
|
/// Resolves a controller for any screen from the current config.
|
||||||
/// `reloadAll()` so a live reload re-reads theme/blocks from disk.
|
|
||||||
var factory: PHBarFactory
|
var factory: PHBarFactory
|
||||||
|
|
||||||
private var observers: [IPCObserver] = []
|
private var observers: [IPCObserver] = []
|
||||||
private var screenObserver: NSObjectProtocol?
|
private var screenObserver: NSObjectProtocol?
|
||||||
|
|
||||||
/// `config` is captured here (not re-read per screen) so all bars share one
|
init(config: PHConfig, theme: PHTheme, layouts: [String : PHLayout], debug: Bool) {
|
||||||
/// resolved config + environment for their lifetime. Live reload swaps the
|
|
||||||
/// whole factory via `reloadAll()`.
|
|
||||||
init(config: PHConfig, theme: PHTheme, debug: Bool) {
|
|
||||||
let environment = PHEnvironment.process.merging(config.env)
|
let environment = PHEnvironment.process.merging(config.env)
|
||||||
self.factory = PHBarFactory(
|
self.factory = PHBarFactory(
|
||||||
config: config,
|
config: config,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
|
layouts: layouts,
|
||||||
environment: environment,
|
environment: environment,
|
||||||
debug: debug
|
debug: debug
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
extension PHBarDelegate {
|
|
||||||
// MARK: - Reload (entry points for a future `phbar reload`)
|
|
||||||
|
|
||||||
/// Re-read config and theme from disk and rebuild every bar against them.
|
|
||||||
/// Screens are kept; only theme/blocks/window are re-resolved.
|
|
||||||
/// This is the global path for picking up config edits at runtime.
|
|
||||||
func reloadAll() {
|
|
||||||
let config: PHConfig
|
|
||||||
let theme: PHTheme
|
|
||||||
do {
|
|
||||||
config = try PHConfig.load()
|
|
||||||
let environment = PHEnvironment.process.merging(config.env)
|
|
||||||
theme = try PHTheme.load(config.theme, environment: environment)
|
|
||||||
} catch {
|
|
||||||
stderr("reload failed, could not read config: \(error)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
factory = PHBarFactory(
|
|
||||||
config: config,
|
|
||||||
theme: theme,
|
|
||||||
environment: PHEnvironment.process.merging(config.env),
|
|
||||||
debug: factory.debug
|
|
||||||
)
|
|
||||||
rebuildAllBars()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rebuild a single monitor's bar against the current factory. Per-monitor
|
|
||||||
/// path for picking up config edits at runtime. Returns `false` if no bar
|
|
||||||
/// currently runs on `name` (or its reload failed to load).
|
|
||||||
@discardableResult
|
|
||||||
func reload(screenNamed name: String) -> Bool {
|
|
||||||
guard let screen = window(named: name)?.barController.screen else { return false }
|
|
||||||
removeBar(for: screen)
|
|
||||||
return addBar(for: screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tear down every bar and rebuild against the current factory, preserving
|
|
||||||
/// the set of screens.
|
|
||||||
private func rebuildAllBars() {
|
|
||||||
let screens = controllers.map(\.screen)
|
|
||||||
for screen in screens {
|
|
||||||
removeBar(for: screen)
|
|
||||||
}
|
|
||||||
for screen in screens {
|
|
||||||
addBar(for: screen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
/// Builds a `BarController` for a screen by resolving its blocks and
|
/// Builds a `BarController` for a screen by resolving its layout
|
||||||
/// window from the config.
|
/// and window from the config.
|
||||||
@MainActor
|
@MainActor
|
||||||
struct PHBarFactory {
|
struct PHBarFactory {
|
||||||
let config: PHConfig
|
let config: PHConfig
|
||||||
let theme: PHTheme
|
let theme: PHTheme
|
||||||
|
let layouts: [String: PHLayout]
|
||||||
let environment: PHEnvironment
|
let environment: PHEnvironment
|
||||||
let debug: Bool
|
let debug: Bool
|
||||||
|
|
||||||
/// Resolve blocks for `screen` and build its controller.
|
/// Resolve layout for `screen` and build its controller.
|
||||||
///
|
///
|
||||||
/// - Parameter screen: The screen to build a bar for. Its localized name
|
/// - Parameter screen: The screen to build a bar for. Its localized name
|
||||||
/// and index in `NSScreen.screens` select any per-monitor config override.
|
/// and index in `NSScreen.screens` select any per-monitor config override.
|
||||||
@@ -17,12 +18,14 @@ struct PHBarFactory {
|
|||||||
func make(for screen: NSScreen) throws -> BarController {
|
func make(for screen: NSScreen) throws -> BarController {
|
||||||
let name = screen.localizedName
|
let name = screen.localizedName
|
||||||
let index = NSScreen.screens.firstIndex(of: screen)
|
let index = NSScreen.screens.firstIndex(of: screen)
|
||||||
let blocks = try PHBlock.load(config.blocks(screenName: name, screenIndex: index))
|
guard let layout = layouts[config.layout(screenName: name, screenIndex: index)] else {
|
||||||
|
throw PHBar.Error("Layout `\(name)` not found.")
|
||||||
|
}
|
||||||
return BarController(
|
return BarController(
|
||||||
config: config,
|
config: config,
|
||||||
screen: screen,
|
screen: screen,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
blocks: blocks,
|
layout: layout,
|
||||||
environment: environment,
|
environment: environment,
|
||||||
debug: debug
|
debug: debug
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import TOML
|
|
||||||
|
|
||||||
extension PHBlock {
|
|
||||||
private struct Wrapper: Decodable {
|
|
||||||
let blocks: [PHBlock]
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
|
||||||
case blocks = "block"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a named block set from `<configDirectory>/blocks/<name>.toml`.
|
|
||||||
///
|
|
||||||
/// - Parameter configDirectory: Override the lookup root (used by tests);
|
|
||||||
/// defaults to the resolved config directory (see `PHPaths`).
|
|
||||||
static func load(_ blocks: String, in configDirectory: URL? = nil) throws -> [PHBlock] {
|
|
||||||
let base = configDirectory ?? PHPaths.configDirectory
|
|
||||||
|
|
||||||
let setFile = base.appending(path: "blocks").appending(path: "\(blocks).toml")
|
|
||||||
guard FileManager.default.fileExists(atPath: setFile.relativePath) else {
|
|
||||||
throw PHBar.Error("Failed to load blocks '\(blocks)'")
|
|
||||||
}
|
|
||||||
return try load(from: setFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load theme from a file at URL.
|
|
||||||
/// If the file doesn't exist or fails to parse, the execution is interrupted.
|
|
||||||
static func load(from url: URL) throws -> [PHBlock] {
|
|
||||||
do {
|
|
||||||
let data = try Data(contentsOf: url)
|
|
||||||
guard let contents = String(data: data, encoding: .utf8), !contents.isEmpty else {
|
|
||||||
throw PHBar.Error("the file is empty")
|
|
||||||
}
|
|
||||||
return try load(from: contents)
|
|
||||||
} catch {
|
|
||||||
throw PHBar.Error("Failed to load blocks file", underlyingError: error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load theme from a TOML string.
|
|
||||||
/// If the content fails to parse, the execution is interrupted.
|
|
||||||
static func load(from contents: String) throws -> [PHBlock] {
|
|
||||||
do {
|
|
||||||
let decoder = TOMLDecoder()
|
|
||||||
let wrapper = try decoder.decode(PHBlock.Wrapper.self, from: contents)
|
|
||||||
return wrapper.blocks
|
|
||||||
} catch {
|
|
||||||
throw PHBar.Error("Failed to parse blocks file", underlyingError: error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
struct PHConfig: Decodable {
|
struct PHConfig: Decodable {
|
||||||
let theme: String
|
let theme: String
|
||||||
let window: String
|
let window: String
|
||||||
let blocks: String?
|
let layout: String
|
||||||
let env: [String: String]?
|
let env: [String: String]?
|
||||||
let monitors: [String: PHConfigMonitorOverride]?
|
let monitors: [String: PHConfigMonitorOverride]?
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case theme, window, blocks, env
|
case theme, window, layout, env
|
||||||
case monitors = "monitor"
|
case monitors = "monitor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ extension PHConfig {
|
|||||||
return PHConfig(
|
return PHConfig(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
window: window,
|
window: window,
|
||||||
blocks: blocks,
|
layout: layout,
|
||||||
env: env.mapValues(environment.expand),
|
env: env.mapValues(environment.expand),
|
||||||
monitors: monitors
|
monitors: monitors
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import Foundation
|
|||||||
import TOML
|
import TOML
|
||||||
|
|
||||||
extension PHConfig {
|
extension PHConfig {
|
||||||
/// Load configuration from the resolved config directory
|
/// Load configuration from the resolved config directory (see `PHPaths`).
|
||||||
/// (see `PHPaths`). If the file doesn't exist or fails to parse,
|
/// If the file doesn't exist or fails to parse, the execution is interrupted.
|
||||||
/// the execution is interrupted.
|
|
||||||
static func load() throws -> PHConfig {
|
static func load() throws -> PHConfig {
|
||||||
let url = PHPaths.configDirectory.appending(path: "config.toml")
|
let url = PHPaths.configDirectory.appending(path: "config.toml")
|
||||||
|
|
||||||
|
|||||||
@@ -3,23 +3,20 @@ import Foundation
|
|||||||
/// A per-monitor entry from the config's `[monitor]` table.
|
/// A per-monitor entry from the config's `[monitor]` table.
|
||||||
///
|
///
|
||||||
/// Either field is optional; an unset field inherits the matching global
|
/// Either field is optional; an unset field inherits the matching global
|
||||||
/// (`theme`, `window`, or `blocks`) so a monitor can override just one of them.
|
/// (`window` or `layout`) so a monitor can override just one of them.
|
||||||
struct PHConfigMonitorOverride: Decodable {
|
struct PHConfigMonitorOverride: Decodable {
|
||||||
let window: String?
|
let window: String?
|
||||||
let blocks: String?
|
let layout: String?
|
||||||
|
|
||||||
init(window: String? = nil, blocks: String? = nil) {
|
init(window: String? = nil, layout: String? = nil) {
|
||||||
self.window = window
|
self.window = window
|
||||||
self.blocks = blocks
|
self.layout = layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PHConfig {
|
extension PHConfig {
|
||||||
/// The per-monitor override matching the given screen identity, if any.
|
/// The per-monitor override matching the given screen identity, if any.
|
||||||
///
|
///
|
||||||
/// This is the single entry point for per-monitor resolution: the
|
|
||||||
/// `theme`/`window`/`blocks` helpers below all delegate to it. Kept free of
|
|
||||||
/// AppKit so the resolution logic is testable without an `NSScreen`.
|
|
||||||
/// Precedence: monitor name → monitor index.
|
/// Precedence: monitor name → monitor index.
|
||||||
func monitorOverride(screenName: String, screenIndex: Int?) -> PHConfigMonitorOverride? {
|
func monitorOverride(screenName: String, screenIndex: Int?) -> PHConfigMonitorOverride? {
|
||||||
guard let monitors else { return nil }
|
guard let monitors else { return nil }
|
||||||
@@ -28,15 +25,15 @@ extension PHConfig {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Window definition name for a screen: the override's `window` if set,
|
/// Window name for a screen: the override's `window` if set,
|
||||||
/// otherwise the global `window`.
|
/// otherwise the global `window`.
|
||||||
func window(screenName: String, screenIndex: Int?) -> String {
|
func window(screenName: String, screenIndex: Int?) -> String {
|
||||||
monitorOverride(screenName: screenName, screenIndex: screenIndex)?.window ?? window
|
monitorOverride(screenName: screenName, screenIndex: screenIndex)?.window ?? window
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block-set name for a screen: the override's `blocks` if set, otherwise
|
/// Layout name for a screen: the override's `layout` if set,
|
||||||
/// the global `blocks` (defaulting to `"default"`).
|
/// otherwise the global `layout`.
|
||||||
func blocks(screenName: String, screenIndex: Int?) -> String {
|
func layout(screenName: String, screenIndex: Int?) -> String {
|
||||||
monitorOverride(screenName: screenName, screenIndex: screenIndex)?.blocks ?? (blocks ?? "default")
|
monitorOverride(screenName: screenName, screenIndex: screenIndex)?.layout ?? layout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
struct PHLayout: Decodable {
|
||||||
|
let blocks: [PHBlock]
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case blocks = "block"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import Foundation
|
||||||
|
import TOML
|
||||||
|
|
||||||
|
extension PHLayout {
|
||||||
|
/// Load the specified layouts from the resolved config directory (see `PHPaths`).
|
||||||
|
/// If any of theme doesn't exist or fails to parse, the execution is interrupted.
|
||||||
|
static func load(_ layouts: [String]) throws -> [String: PHLayout] {
|
||||||
|
var dictionary = [String: PHLayout]()
|
||||||
|
|
||||||
|
for layout in layouts {
|
||||||
|
dictionary.updateValue(try load(layout), forKey: layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a layout from the resolved config directory (see `PHPaths`).
|
||||||
|
/// If the file doesn't exist or fails to parse, the execution is interrupted.
|
||||||
|
static func load(_ layout: String) throws -> PHLayout {
|
||||||
|
let url = PHPaths.layoutsDirectory.appending(path: "\(layout).toml")
|
||||||
|
|
||||||
|
if FileManager.default.fileExists(atPath: url.relativePath) {
|
||||||
|
return try load(from: url)
|
||||||
|
} else {
|
||||||
|
let layout = url.lastPathComponent
|
||||||
|
throw PHBar.Error(
|
||||||
|
"""
|
||||||
|
Layout file not found.
|
||||||
|
Make sure to have a file named `\(layout)` inside the
|
||||||
|
directory: `\(PHPaths.layoutsDirectory.relativePath)`
|
||||||
|
|
||||||
|
Tip: If you've never used phbar before, run the `phbar install` command
|
||||||
|
to automatically generate all the required configuration files.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a layout from the file at URL.
|
||||||
|
/// If the file doesn't exist or fails to parse, the execution is interrupted.
|
||||||
|
static private func load(from url: URL) throws -> PHLayout {
|
||||||
|
do {
|
||||||
|
let data = try Data(contentsOf: url)
|
||||||
|
guard let contents = String(data: data, encoding: .utf8) else {
|
||||||
|
throw PHBar.Error("content is not UTF8 encoded.")
|
||||||
|
}
|
||||||
|
let decoder = TOMLDecoder()
|
||||||
|
return try decoder.decode(PHLayout.self, from: contents)
|
||||||
|
} catch {
|
||||||
|
let layout = url.lastPathComponent
|
||||||
|
throw PHBar.Error("Layout file `\(layout)` not readable", underlyingError: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,11 @@ enum PHPaths {
|
|||||||
/// The resolved configuration directory (computed once, cached).
|
/// The resolved configuration directory (computed once, cached).
|
||||||
static let configDirectory: URL = resolveConfigDirectory()
|
static let configDirectory: URL = resolveConfigDirectory()
|
||||||
|
|
||||||
|
/// The resolved layouts directory (computed once, cached).
|
||||||
|
static let layoutsDirectory: URL = {
|
||||||
|
resolveConfigDirectory().appending(path: "layouts")
|
||||||
|
}()
|
||||||
|
|
||||||
/// Ordered candidate directories, priority high → low.
|
/// Ordered candidate directories, priority high → low.
|
||||||
///
|
///
|
||||||
/// - Parameter environment: Override the environment lookup (used by tests);
|
/// - Parameter environment: Override the environment lookup (used by tests);
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ final class BarController: ObservableObject {
|
|||||||
blocks.filter { $0.centered == true }
|
blocks.filter { $0.centered == true }
|
||||||
}
|
}
|
||||||
|
|
||||||
init(config: PHConfig, screen: NSScreen, theme: PHTheme, blocks: [PHBlock], environment: PHEnvironment = .process, debug: Bool) {
|
init(config: PHConfig, screen: NSScreen, theme: PHTheme, layout: PHLayout, environment: PHEnvironment = .process, debug: Bool) {
|
||||||
self.config = config
|
self.config = config
|
||||||
self.screen = screen
|
self.screen = screen
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.window = Self.window(for: config, screen: screen, from: theme)
|
self.window = Self.window(for: config, screen: screen, from: theme)
|
||||||
self.blocks = blocks
|
self.blocks = layout.blocks
|
||||||
|
|
||||||
for block in blocks {
|
for block in blocks {
|
||||||
block.text = text(for: block)
|
block.text = text(for: block)
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Default window and block set for every monitor.
|
# Default window and block set for every monitor.
|
||||||
# - theme: a named theme at ~/.config/phbar/themes/<name>.toml
|
# - theme: a named theme at ~/.config/phbar/themes/<name>.toml
|
||||||
# - window: a [[window]] definition from the theme file
|
# - window: a [[window]] definition from the theme file
|
||||||
# - blocks: a named set at ~/.config/phbar/blocks/<name>.toml
|
# - layout: a named layout at ~/.config/phbar/layouts/<name>.toml
|
||||||
theme = "default"
|
theme = "default"
|
||||||
window = "default"
|
window = "default"
|
||||||
blocks = "default"
|
layout = "default"
|
||||||
|
|
||||||
# Optional: override the window and/or block set per monitor.
|
# Optional: override the window and/or layout per monitor.
|
||||||
# - quoted numeric key → NSScreen index
|
# - quoted numeric key → NSScreen index
|
||||||
# - string key → NSScreen.localizedName (stable across replugs)
|
# - string key → NSScreen.localizedName (stable across replugs)
|
||||||
# Precedence: monitor name → monitor index → the globals above.
|
# Precedence: monitor name → monitor index → the globals above.
|
||||||
@@ -14,11 +14,11 @@ blocks = "default"
|
|||||||
#
|
#
|
||||||
# [monitor."1"]
|
# [monitor."1"]
|
||||||
# window = "bottom"
|
# window = "bottom"
|
||||||
# blocks = "laptop"
|
# layout = "laptop"
|
||||||
#
|
#
|
||||||
# [monitor."DELL U2723QE"]
|
# [monitor."DELL U2723QE"]
|
||||||
# window = "clock"
|
# window = "clock"
|
||||||
# blocks = "external"
|
# layout = "external"
|
||||||
|
|
||||||
# Optional: environment variables passed to block scripts when they run.
|
# Optional: environment variables passed to block scripts when they run.
|
||||||
# - scripts always inherit phbar's own process environment (e.g. $PATH)
|
# - scripts always inherit phbar's own process environment (e.g. $PATH)
|
||||||
|
|||||||
@@ -28,19 +28,19 @@ private func makeConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func windowMatchesByIndex() {
|
@Test func windowMatchesByIndex() {
|
||||||
let config = makeConfig(monitors: ["1": .init(window: "bottom", blocks: nil)])
|
let config = makeConfig(monitors: ["1": .init(window: "bottom", layout: nil)])
|
||||||
#expect(config.window(screenName: "Whatever", screenIndex: 1) == "bottom")
|
#expect(config.window(screenName: "Whatever", screenIndex: 1) == "bottom")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func windowMatchesByName() {
|
@Test func windowMatchesByName() {
|
||||||
let config = makeConfig(monitors: ["DELL U2723QE": .init(window: "clock", blocks: nil)])
|
let config = makeConfig(monitors: ["DELL U2723QE": .init(window: "clock", layout: nil)])
|
||||||
#expect(config.window(screenName: "DELL U2723QE", screenIndex: 0) == "clock")
|
#expect(config.window(screenName: "DELL U2723QE", screenIndex: 0) == "clock")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func windowPrefersNameOverIndex() {
|
@Test func windowPrefersNameOverIndex() {
|
||||||
let config = makeConfig(monitors: [
|
let config = makeConfig(monitors: [
|
||||||
"0": .init(window: "byIndex", blocks: nil),
|
"0": .init(window: "byIndex", layout: nil),
|
||||||
"DELL": .init(window: "byName", blocks: nil),
|
"DELL": .init(window: "byName", layout: nil),
|
||||||
])
|
])
|
||||||
#expect(config.window(screenName: "DELL", screenIndex: 0) == "byName")
|
#expect(config.window(screenName: "DELL", screenIndex: 0) == "byName")
|
||||||
}
|
}
|
||||||
@@ -48,17 +48,17 @@ private func makeConfig(
|
|||||||
// MARK: blocks
|
// MARK: blocks
|
||||||
|
|
||||||
@Test func blocksDefaultsToDefault() {
|
@Test func blocksDefaultsToDefault() {
|
||||||
#expect(makeConfig().blocks(screenName: "S", screenIndex: 0) == "default")
|
#expect(makeConfig().layout(screenName: "S", screenIndex: 0) == "default")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func blocksUsesGlobalWhenNoOverride() {
|
@Test func blocksUsesGlobalWhenNoOverride() {
|
||||||
let config = makeConfig(blocks: "main")
|
let config = makeConfig(blocks: "main")
|
||||||
#expect(config.blocks(screenName: "S", screenIndex: 0) == "main")
|
#expect(config.layout(screenName: "S", screenIndex: 0) == "main")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func blocksOverrideBeatsGlobal() {
|
@Test func blocksOverrideBeatsGlobal() {
|
||||||
let config = makeConfig(blocks: "main", monitors: ["1": .init(window: nil, blocks: "alt")])
|
let config = makeConfig(blocks: "main", monitors: ["1": .init(window: nil, layout: "alt")])
|
||||||
#expect(config.blocks(screenName: "S", screenIndex: 1) == "alt")
|
#expect(config.layout(screenName: "S", screenIndex: 1) == "alt")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: theme
|
// MARK: theme
|
||||||
@@ -77,10 +77,10 @@ private func makeConfig(
|
|||||||
|
|
||||||
@Test func overrideInheritsUnsetFieldsFromGlobal() {
|
@Test func overrideInheritsUnsetFieldsFromGlobal() {
|
||||||
// An override that sets only `blocks` still inherits the global window/theme.
|
// An override that sets only `blocks` still inherits the global window/theme.
|
||||||
let config = makeConfig(theme: "voltage", window: "top", monitors: ["1": .init(window: nil, blocks: "laptop")])
|
let config = makeConfig(theme: "voltage", window: "top", monitors: ["1": .init(window: nil, layout: "laptop")])
|
||||||
#expect(config.theme(screenName: "S", screenIndex: 1) == "voltage")
|
#expect(config.theme(screenName: "S", screenIndex: 1) == "voltage")
|
||||||
#expect(config.window(screenName: "S", screenIndex: 1) == "top")
|
#expect(config.window(screenName: "S", screenIndex: 1) == "top")
|
||||||
#expect(config.blocks(screenName: "S", screenIndex: 1) == "laptop")
|
#expect(config.layout(screenName: "S", screenIndex: 1) == "laptop")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func themeWindowAndBlocksResolveIndependently() {
|
@Test func themeWindowAndBlocksResolveIndependently() {
|
||||||
@@ -107,6 +107,6 @@ private func makeConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func monitorOverrideReturnsNilForUnmatchedScreen() {
|
@Test func monitorOverrideReturnsNilForUnmatchedScreen() {
|
||||||
let config = makeConfig(monitors: ["DELL": .init(window: "clock", blocks: nil)])
|
let config = makeConfig(monitors: ["DELL": .init(window: "clock", layout: nil)])
|
||||||
#expect(config.monitorOverride(screenName: "Unknown", screenIndex: 99) == nil)
|
#expect(config.monitorOverride(screenName: "Unknown", screenIndex: 99) == nil)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user