initial commit

This commit is contained in:
2026-04-15 18:03:52 -04:00
commit 451bf3a9fc
6 changed files with 284 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.build/
.direnv/
.swiftpm/

27
Makefile Normal file
View File

@@ -0,0 +1,27 @@
DEVELOPER_DIR := /Library/Developer/CommandLineTools
SWIFT := /usr/bin/swift
INSTALL_DIR := $(HOME)/.local/bin
BINARY_NAME := nunc
export DEVELOPER_DIR
unexport SDKROOT
.PHONY: build release install clean run
build:
$(SWIFT) build
release:
$(SWIFT) build -c release
install: release
mkdir -p $(INSTALL_DIR)
cp .build/release/Nunc $(INSTALL_DIR)/$(BINARY_NAME)
@echo "Installed to $(INSTALL_DIR)/$(BINARY_NAME)"
clean:
$(SWIFT) package clean
rm -rf .build
run: build
.build/debug/Nunc

13
Package.swift Normal file
View File

@@ -0,0 +1,13 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "Nunc",
platforms: [.macOS(.v13)],
targets: [
.executableTarget(
name: "Nunc",
path: "Sources/Nunc"
)
]
)

152
Sources/Nunc/main.swift Normal file
View File

@@ -0,0 +1,152 @@
import AppKit
enum Position: String, Codable {
case top_left, top_center, top_right
case bottom_left, bottom_center, bottom_right
}
struct Config: Codable {
var fontSize: Double = 14.0
var verticalPadding: Double = 8.0
var horizontalPadding: Double = 12.0
var position: Position = .top_right
var offsetX: Double = 0.0
var offsetY: Double = 0.0
}
func loadConfig() -> Config {
let configDir = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent(".config/nunc")
let configFile = configDir.appendingPathComponent("config.json")
guard FileManager.default.fileExists(atPath: configFile.path) else {
// Write default config if none exists
try? FileManager.default.createDirectory(at: configDir, withIntermediateDirectories: true)
let defaultConfig = Config()
if let data = try? JSONEncoder.pretty.encode(defaultConfig) {
try? data.write(to: configFile)
}
return Config()
}
guard let data = try? Data(contentsOf: configFile),
let config = try? JSONDecoder().decode(Config.self, from: data) else {
fputs("nunc: failed to parse \(configFile.path), using defaults\n", stderr)
return Config()
}
return config
}
extension JSONEncoder {
static var pretty: JSONEncoder {
let e = JSONEncoder()
e.outputFormatting = [.prettyPrinted, .sortedKeys]
return e
}
}
let config = loadConfig()
class UnconstrainedPanel: NSPanel {
override func constrainFrameRect(_ frameRect: NSRect, to screen: NSScreen?) -> NSRect {
return frameRect
}
}
let app = NSApplication.shared
app.setActivationPolicy(.prohibited)
let font = NSFont.monospacedDigitSystemFont(ofSize: config.fontSize, weight: .regular)
let label = NSTextField(labelWithString: "")
label.font = font
label.textColor = .white
label.alignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
// Sample Dimensions
let sampleText = "2026-04-15T23:59:59+00:00"
let textSize = (sampleText as NSString).size(withAttributes: [.font: font])
let windowSize = NSSize(
width: ceil(textSize.width + config.horizontalPadding * 2),
height: ceil(textSize.height + config.verticalPadding * 2)
)
let screenFrame = NSScreen.main?.frame ?? NSRect(x: 0, y: 0, width: 1440, height: 900)
func computeOrigin() -> NSPoint {
let x: Double
let y: Double
switch config.position {
case .top_left:
x = screenFrame.minX
y = screenFrame.maxY - windowSize.height
case .top_center:
x = screenFrame.midX - windowSize.width / 2
y = screenFrame.maxY - windowSize.height
case .top_right:
x = screenFrame.maxX - windowSize.width
y = screenFrame.maxY - windowSize.height
case .bottom_left:
x = screenFrame.minX
y = screenFrame.minY
case .bottom_center:
x = screenFrame.midX - windowSize.width / 2
y = screenFrame.minY
case .bottom_right:
x = screenFrame.maxX - windowSize.width
y = screenFrame.minY
}
return NSPoint(x: x + config.offsetX, y: y - config.offsetY)
}
let windowFrame = NSRect(origin: computeOrigin(), size: windowSize)
let window = UnconstrainedPanel(
contentRect: windowFrame,
styleMask: [.nonactivatingPanel, .fullSizeContentView, .borderless],
backing: .buffered,
defer: false
)
window.level = .floating
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.isMovableByWindowBackground = true
window.backgroundColor = .clear
window.hasShadow = true
window.isReleasedWhenClosed = false
let vfx = NSVisualEffectView(frame: NSRect(origin: .zero, size: windowSize))
vfx.material = .hudWindow
vfx.blendingMode = .behindWindow
vfx.state = .active
vfx.wantsLayer = true
vfx.layer?.cornerRadius = 10
vfx.layer?.masksToBounds = true
vfx.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: vfx.centerXAnchor),
label.centerYAnchor.constraint(equalTo: vfx.centerYAnchor),
])
window.contentView = vfx
window.orderFrontRegardless()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
formatter.locale = Locale(identifier: "en_US_POSIX")
func updateClock() {
label.stringValue = formatter.string(from: Date())
}
updateClock()
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
updateClock()
}
RunLoop.main.add(timer, forMode: .common)
app.run()

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1771208521,
"narHash": "sha256-X01Q3DgSpjeBpapoGA4rzKOn25qdKxbPnxHeMLNoHTU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fa56d7d6de78f5a7f997b0ea2bc6efd5868ad9e8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

28
flake.nix Normal file
View File

@@ -0,0 +1,28 @@
{
description = "Dev Shell";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{ self
, nixpkgs
, flake-utils
,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
packages = with pkgs; [
gnumake
];
};
}
);
}