Giorgi
07/15/2023, 7:28 PMclass WorkspaceEvents {
private static var appsObserver: NSKeyValueObservation!
private static var previousValueOfRunningApps: Set<NSRunningApplication>!
static func observeRunningApplications() {
previousValueOfRunningApps = Set(NSWorkspace.shared.runningApplications)
appsObserver = NSWorkspace.shared.observe(\.runningApplications, options: [.old, .new], changeHandler: observerCallback)
}
static func observerCallback<A>(_ application: NSWorkspace, _ change: NSKeyValueObservedChange<A>) {
let workspaceApps = Set(NSWorkspace.shared.runningApplications)
// TODO: symmetricDifference has bad performance
let diff = Array(workspaceApps.symmetricDifference(previousValueOfRunningApps))
if change.kind == .insertion {
print("OS event", "apps launched", diff.map { ($0.processIdentifier, $0.bundleIdentifier) })
} else if change.kind == .removal {
print("OS event", "apps quit", diff.map { ($0.processIdentifier, $0.bundleIdentifier) })
}
previousValueOfRunningApps = workspaceApps
}
}
Giorgi
07/15/2023, 7:33 PMNSWorkspace.shared.observe
.
The function observe
does not show up in Kotlin, I guess its replacement is observeValueForKeyPath
but this does not have a callback argumentrusshwolf
07/15/2023, 10:07 PMstaticCFunction
with your lambda as the argument and then pass the returned pointer as the context
parameter.russhwolf
07/15/2023, 10:07 PMJeff Lockhart
07/15/2023, 10:38 PMaddObserver:forKeyPath:options:context:
on the target object you're observing, in this case NSWorkspace.shared
. The observer argument object will receive notifications by implementing observeValueForKeyPath:ofObject:change:context:
. So instead of the lambda callback the Swift API uses, you need to use an object to receive notifications. See the docs for more info.Giorgi
07/18/2023, 2:31 PMaddObserver:forKeyPath:options:context:
should be called on the objects, Im not sure how to call it or what colons mean...
Also not sure how do I implement observeValueForKeyPath:ofObject:change:context:
Looking on GIthub I found this https://github.com/JoeSteven/KMP-VideoPlayer/blob/4e19d34e1327ea3f5874a1ee39293ffc[…]winMain/kotlin/com.mimao.kmp.videoplayer/KVideoPlayer.darwin.kt is that what needs to be done to observe changes? it is using cinterop and has IOSPlayerObserverProtocol
interface which comes from native code...Jeff Lockhart
07/18/2023, 2:35 PMGiorgi
07/18/2023, 2:43 PMobserveValueForKeyPath
is something I dont know how should happen. In the project above, there is lots of Gradle stuff and cinterop. Do I also need it? or is there an easy way?Jeff Lockhart
07/18/2023, 2:56 PMKVideoPlayer
is an NSObject
that overrides observeValueForKeyPath:ofObject:change:context:
. It's adding itself as an observer with addObserver:forKeyPath:options:context:
here. Think of the NSObject
subclass's overridden observeValueForKeyPath:ofObject:change:context:
method as the lambda you're using to observe the object you call addObserver:forKeyPath:options:context:
on.
The fact that it also implements IOSPlayerObserverProtocol
shouldn't be a requirement for key-value observing. Probably just a part of the underlying API used on the darwin platform.Giorgi
07/19/2023, 3:44 PMimport kotlinx.cinterop.COpaquePointer
import platform.AppKit.NSApp
import platform.AppKit.NSApplication
import platform.AppKit.NSWorkspace
import platform.Foundation.NSKeyValueObservingOptionNew
import platform.Foundation.NSKeyValueObservingOptionOld
import platform.Foundation.addObserver
import platform.darwin.NSObject
fun main() {
NSApplication.sharedApplication()
NSWorkspace.sharedWorkspace.addObserver(
StatusObserver(),
"runningApplications",
NSKeyValueObservingOptionOld and NSKeyValueObservingOptionNew,
null
)
NSApp?.run()
}
class StatusObserver : NSObject() {
fun observeValueForKeyPath(
keyPath: String?,
ofObject: Any?,
change: Map<Any?, *>?,
context: COpaquePointer?
) {
println("boiiiii it works!")
}
}
But it does not work. Any ideas or any directions? at this point I feel like it is not possible, or at least I cant make it work... sad pandarusshwolf
07/19/2023, 3:51 PMrusshwolf
07/19/2023, 3:51 PMrusshwolf
07/19/2023, 3:53 PMJeff Lockhart
07/19/2023, 4:20 PMoverride fun observeValueForKeyPath
as the example you linked above does. Maybe that's where IOSPlayerObserverProtocol
is useful. I don't see it referenced anywhere else in the library's git repo. So maybe it's defined in Objective-C and defines the observeValueForKeyPath:ofObject:change:context:
method there, which is then overridden in Kotlin. I'd look into that some more to see where that protocol is coming from and how the method is being overridden in Kotlin. If it is in Objective-C, then maybe you can similarly create an Objective-C protocol to inherit from, in which case you might need that Gradle cinterop after all.Giorgi
07/22/2023, 3:34 PMchange: NSKeyValueObservedChange<A>
argument but it fits my usecase for the small sideproject I have...Giorgi
07/22/2023, 3:34 PMJeff Lockhart
07/22/2023, 3:37 PMIOSPlayerObserverProtocol
as well then?Giorgi
07/22/2023, 3:38 PMGiorgi
07/22/2023, 3:38 PMGiorgi
07/22/2023, 3:39 PMlanguage = Objective-C
---
#import <Foundation/Foundation.h>
@protocol KVObserver
@required
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context;
@end;
Giorgi
07/22/2023, 3:39 PM@protocol
means an interface in KotlinGiorgi
07/22/2023, 3:40 PMGiorgi
07/22/2023, 3:40 PMJeff Lockhart
07/22/2023, 3:42 PMGiorgi
07/22/2023, 3:43 PMGiorgi
07/22/2023, 3:44 PM