Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions DeskPad.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
6DC044522801877F00281728 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC044512801877F00281728 /* AppDelegate.swift */; };
6DC044562801878100281728 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6DC044552801878100281728 /* Assets.xcassets */; };
6DC04461280191EB00281728 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC04460280191EB00281728 /* main.swift */; };
70EA23072E38CF4A00B8ED62 /* SerialNumberManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70EA23062E38CF4A00B8ED62 /* SerialNumberManager.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -42,6 +43,7 @@
6DC044512801877F00281728 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
6DC044552801878100281728 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
6DC0445A2801878100281728 /* DeskPad.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DeskPad.entitlements; sourceTree = "<group>"; };
70EA23062E38CF4A00B8ED62 /* SerialNumberManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialNumberManager.swift; sourceTree = "<group>"; };
6DC04460280191EB00281728 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -133,6 +135,7 @@
children = (
6DC04460280191EB00281728 /* main.swift */,
6D2F1485280C20C800A3A2E5 /* SubscriberViewController.swift */,
70EA23062E38CF4A00B8ED62 /* SerialNumberManager.swift */,
6DC044512801877F00281728 /* AppDelegate.swift */,
6D2F1483280C201B00A3A2E5 /* Backend */,
6D2F1484280C202700A3A2E5 /* Frontend */,
Expand Down Expand Up @@ -247,6 +250,7 @@
6D2F148C280C20D000A3A2E5 /* Store.swift in Sources */,
6D2F1486280C20C800A3A2E5 /* SubscriberViewController.swift in Sources */,
6D68E1AF287ABB9900CD574A /* ScreenConfigurationSideEffect.swift in Sources */,
70EA23072E38CF4A00B8ED62 /* SerialNumberManager.swift in Sources */,
6D41B0A12879FABE007CEB2F /* MouseLocationState.swift in Sources */,
6D2F148A280C20D000A3A2E5 /* AppState.swift in Sources */,
6D2F148B280C20D000A3A2E5 /* SideEffectsMiddleware.swift in Sources */,
Expand Down
18 changes: 16 additions & 2 deletions DeskPad/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_: Notification) {
let viewController = ScreenViewController()
window = NSWindow(contentViewController: viewController)
window.bind(NSBindingName.title, to: viewController, withKeyPath: "title")
window.delegate = viewController
window.title = "DeskPad"
// window.title = "DeskPad"
window.makeKeyAndOrderFront(nil)
window.titlebarAppearsTransparent = true
window.isMovableByWindowBackground = true
window.titleVisibility = .hidden
// window.titleVisibility = .hidden
window.backgroundColor = .white
window.contentMinSize = CGSize(width: 400, height: 300)
window.contentMaxSize = CGSize(width: 3840, height: 2160)
Expand All @@ -26,6 +27,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let mainMenu = NSMenu()
let mainMenuItem = NSMenuItem()
let subMenu = NSMenu(title: "MainMenu")
let newMenuItem = NSMenuItem(
title: "Create New Screen",
action: #selector(spawnNewInstance),
keyEquivalent: "n"
)
subMenu.addItem(newMenuItem)
let quitMenuItem = NSMenuItem(
title: "Quit",
action: #selector(NSApp.terminate),
Expand All @@ -42,4 +49,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool {
return true
}

@objc func spawnNewInstance() {
let task = Process()
task.launchPath = "/usr/bin/open"
task.arguments = ["-n", Bundle.main.bundlePath]
try? task.run()
}
}
11 changes: 9 additions & 2 deletions DeskPad/Frontend/Screen/ScreenViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ class ScreenViewController: SubscriberViewController<ScreenViewData>, NSWindowDe

override func viewDidLoad() {
super.viewDidLoad()
SerialNumberManager.shared.claimSerial()
let serial = SerialNumberManager.shared.claimedSerial ?? 0x0001
title = "Screen \(serial)"

let descriptor = CGVirtualDisplayDescriptor()
descriptor.setDispatchQueue(DispatchQueue.main)
descriptor.name = "DeskPad Display"
descriptor.name = "DeskPad Display \(serial)"
descriptor.maxPixelsWide = 3840
descriptor.maxPixelsHigh = 2160
descriptor.sizeInMillimeters = CGSize(width: 1600, height: 1000)
descriptor.productID = 0x1234
descriptor.vendorID = 0x3456
descriptor.serialNum = 0x0001
descriptor.serialNum = serial

let display = CGVirtualDisplay(descriptor: descriptor)
store.dispatch(ScreenViewAction.setDisplayID(display.displayID))
Expand Down Expand Up @@ -120,4 +123,8 @@ class ScreenViewController: SubscriberViewController<ScreenViewData>, NSWindowDe
)
store.dispatch(MouseLocationAction.requestMove(toPoint: onScreenPoint))
}

func applicationWillTerminate(_: Notification) {
SerialNumberManager.shared.releaseSerial()
}
}
84 changes: 84 additions & 0 deletions DeskPad/SerialNumberManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Cocoa

class SerialNumberManager {
static let shared = SerialNumberManager()

private(set) var claimedSerial: UInt32?

private let fileCoordinator = NSFileCoordinator()
private let registryURL: URL

private init() {
let fileManager = FileManager.default
guard let appSupportURL = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
fatalError("Could not find Application Support directory.")
}

let bundleID = Bundle.main.bundleIdentifier ?? "com.example.YourAppBundleID"
let appDirectoryURL = appSupportURL.appendingPathComponent(bundleID, isDirectory: true)

try? fileManager.createDirectory(at: appDirectoryURL, withIntermediateDirectories: true, attributes: nil)

registryURL = appDirectoryURL.appendingPathComponent("serials.json", isDirectory: false)
}

func claimSerial() {
var coordinationError: NSError?

fileCoordinator.coordinate(writingItemAt: registryURL, options: .forMerging, error: &coordinationError) { url in
var activeSerials = [pid_t: UInt32]()
if let data = try? Data(contentsOf: url),
let decoded = try? JSONDecoder().decode([pid_t: UInt32].self, from: data)
{
activeSerials = decoded
}

for (pid, _) in activeSerials {
if NSRunningApplication(processIdentifier: pid) == nil {
activeSerials.removeValue(forKey: pid)
}
}

let usedSerials = Set<UInt32>(activeSerials.values)
var newSerial: UInt32 = 1
while usedSerials.contains(newSerial) {
newSerial += 1
}

let myPID = ProcessInfo.processInfo.processIdentifier
activeSerials[myPID] = newSerial
self.claimedSerial = newSerial

if let data = try? JSONEncoder().encode(activeSerials) {
try? data.write(to: url, options: .atomic)
}
}

if let error = coordinationError {
print("🚨 Coordination Error during claim: \(error)")
}
}

func releaseSerial() {
var coordinationError: NSError?

fileCoordinator.coordinate(writingItemAt: registryURL, options: .forMerging, error: &coordinationError) { url in
guard let data = try? Data(contentsOf: url),
var activeSerials = try? JSONDecoder().decode([pid_t: Int].self, from: data)
else {
return
}

let myPID = ProcessInfo.processInfo.processIdentifier
if activeSerials.removeValue(forKey: myPID) != nil {
if let data = try? JSONEncoder().encode(activeSerials) {
try? data.write(to: url, options: .atomic)
}
}
}

if let error = coordinationError {
print("🚨 Coordination Error during release: \(error)")
}
}
}