Hi, need help at tranlation again... So I have thi...
# kotlin-native
g
Hi, need help at tranlation again... So I have this Swift code how it will be in Kotlin?
Copy code
class 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
    }
}
I cant figureout how to call
NSWorkspace.shared.observe
. The function
observe
does not show up in Kotlin, I guess its replacement is
observeValueForKeyPath
but this does not have a callback argument
r
I think the cpointer arg is the callback. Try calling
staticCFunction
with your lambda as the argument and then pass the returned pointer as the
context
parameter.
j
Kotlin calls the Objective-C API, which is a little different than the Swift API for key-value observation. First call
addObserver: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.
g
Forgive my lack of experience with Objective C... Could you provide an example of it? maybe on Github? You mention
addObserver: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...
j
Sorry, the colon notation is the Objective-C method signature. You'd be calling the equivalent in Kotlin. The colon comes before a method parameter.
g
okay, I got the first part. implementation of
observeValueForKeyPath
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?
j
That example you linked looks like it should be doing something similar.
KVideoPlayer
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.
g
This is my progress so far
Copy code
import 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 panda
r
I don't think I've ever seen anyone successfully implement key-value observing from Kotlin, so it might be that it's not possible.
Looking back, whatever I said last time was definitely wrong though, so what do I know?
You could try defining your observer in Swift and passing it in as a function parameter and see if it works that way. But maybe that complicates your use-case too much
j
I haven't personally done key-value observing in Kotlin/Native myself. The one difference I see is your code doesn't
override 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.
g
I just tried with the cinterop they had in the project and it worked on first try. Ofcourse it does not have
change: NSKeyValueObservedChange<A>
argument but it fits my usecase for the small sideproject I have...
🚀
j
Nice. So that must be pulling in an Objective-C object to inherit from then. Are you using
IOSPlayerObserverProtocol
as well then?
g
yes, but I renamed it
I dont 100% know, but it seems the .def file contains plain Objective-C code
Copy code
language = 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;
@protocol
means an interface in Kotlin
Anyways, the main part is that it works
❤️
j
Oh, yeah. That's not pulling in any code, just declaring the Objective-C key-value observer protocol. Interesting, good to know key-value observing is possible in Kotlin this way.
g
I have a plan to make a similar app to https://github.com/lwouis/alt-tab-macos, but in Kotlin and Multiplatform
👍🏼 1
currently researching the APIs I can use from Kotlin on MacOS... hope it goes well