Skip to content

Commit

Permalink
feat: add audio devices event source
Browse files Browse the repository at this point in the history
  • Loading branch information
mishamyrt committed Nov 22, 2023
1 parent ce4cfb4 commit 2518492
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 32 deletions.
18 changes: 18 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
"version" : "2.3.0"
}
},
{
"identity" : "simplycoreaudio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/rnine/SimplyCoreAudio.git",
"state" : {
"revision" : "35cc0e6eac5c2ee5049431f4238b0e333cf79869",
"version" : "4.1.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
Expand All @@ -18,6 +27,15 @@
"version" : "1.2.3"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
"version" : "1.2.0"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
Expand Down
6 changes: 4 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
.package(url: "https://github.com/JohnSundell/ShellOut.git", from: "2.0.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.6")
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.6"),
.package(url: "https://github.com/rnine/SimplyCoreAudio.git", from: "4.1.1"),
],
targets: [
.executableTarget(
name: "runif",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "ShellOut", package: "ShellOut"),
.product(name: "Yams", package: "Yams")
.product(name: "Yams", package: "Yams"),
.product(name: "SimplyCoreAudio", package: "SimplyCoreAudio")
]
)
]
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,5 @@ If you want to run a command on an event, regardless of the input (`with`), then

The following event sources can be subscribed to:

* `screen` — change connected displays. declares `connected` and `disconnected` events. In `with` takes the display name.


* `screen` — connected displays list change. declares `connected` and `disconnected` events. In `with` takes the display name.
* `audio` — connected audio device list change. declares `connected` and `disconnected` events. In `with` takes the audio device name. Handles both input and output devices.
40 changes: 40 additions & 0 deletions Sources/Events/AudioSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Cocoa
import SimplyCoreAudio

class AudioSource: EventSource {
let coreAudio = SimplyCoreAudio()
var name = "audio"
var listener: EventListener?
var lastScreens: [String]?
var updating = false

func emitAll(kind: String, devices: [AudioDevice]) {
var names: [String] = []
for device in devices where !names.contains(device.name) {
emit(kind: kind, target: device.name)
names.append(device.name)
}
}

@objc
func handleDeviceListChanged(notification: Notification) {
let addedDevices = notification.userInfo?["addedDevices"] as? [AudioDevice]
if addedDevices != nil && !addedDevices!.isEmpty {
emitAll(kind: "connected", devices: addedDevices!)
}

let removedDevices = notification.userInfo?["removedDevices"] as? [AudioDevice]
if removedDevices != nil && !removedDevices!.isEmpty {
emitAll(kind: "disconnected", devices: removedDevices!)
}
}

func subscribe() {
NotificationCenter.default.addObserver(
self,
selector: #selector(self.handleDeviceListChanged),
name: Notification.Name.deviceListChanged,
object: nil
)
}
}
6 changes: 1 addition & 5 deletions Sources/Events/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,10 @@ protocol EventProvider {
protocol EventSource: EventProvider {
var name: String { get }

func handle()
func subscribe()
}

extension EventSource {
func handle() {
// Do nothing by default
}

func emit(kind: String, target: String) {
if listener == nil {
return
Expand Down
21 changes: 10 additions & 11 deletions Sources/Events/EventObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class EventObserver: EventListener, EventProvider {
var sources: [EventSource] = []

init(sources: [EventSource]) {
for var provider in sources {
provider.listener = self
for var source in sources {
source.listener = self
}
self.sources = sources
}
Expand All @@ -19,20 +19,19 @@ class EventObserver: EventListener, EventProvider {
}
}

func subscribeSources() {
for source in sources {
source.subscribe()
}
}

func runLoop() {
subscribeSources()
_ = NSApplication.shared
let ticket = Timer.publish(every: 5, on: .main, in: .common)
.autoconnect()
.sink { _ in
self.run()
}
.sink { _ in }
RunLoop.main.run()
ticket.cancel()
}

func run() {
for provider in sources {
provider.handle()
}
}
}
8 changes: 2 additions & 6 deletions Sources/Events/ScreenSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,19 @@ class ScreenSource: EventSource {
var lastScreens: [String]?
var updating = false

init() {
subscribeChange()
refreshScreens()
}

@objc
func handleDisplayConnection(notification _: Notification) {
refreshScreens()
}

func subscribeChange() {
func subscribe() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleDisplayConnection),
name: NSApplication.didChangeScreenParametersNotification,
object: nil
)
refreshScreens()
}

func getScreenNames() -> [String] {
Expand Down
14 changes: 9 additions & 5 deletions Sources/RunIf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@ struct RunIf: ParsableCommand {
var configPath: String?

mutating func run() throws {
let sources: [EventSource] = [
ScreenSource(),
AudioSource()
]
let config = ConfigLoader.read(handlersOf: configPath)
if config == nil {
throw ValidationError("Can't open config file.")
}
let activeSources = sources.filter { source in
config!.keys.contains(source.name)
}
let runner = CommandRunner(with: config!)
let sources: [EventSource] = [
ScreenSource()
]
let observer = EventObserver(sources: sources)
let observer = EventObserver(sources: activeSources)
observer.listener = runner
print("starting with \(sources.count) event sources")
print("starting with \(activeSources.count) event sources")
observer.runLoop()
}
}

0 comments on commit 2518492

Please sign in to comment.