To add to this: unfortunately, CF/NS RunLoop was never exposing the "wake up port" to be observed like well established APIs do (libinput, wayland, gtk, qt, etc etc). Most of them are using only public API of periodically run runloop like you've explained earlier and I've never seen anyone using the _dispatch_get_main_queue_port_4CF
private API as an alternative.
Because of this for my project I'm using Foundation's runloop as main loop and integrate others into it, since all of those expose the file descriptor to watch whenever events arrive on those loops. This way I don't have to use private API. Unfortunately, on linux (and probably on android too) this would not work properly either because of SR-14920. I've made a PR addressing the issue and porting CFFileDescriptor to make the usage more convenient.
With that said, I don't think what you are looking for can and will be addressed at any point in future. Android's Looper does not expose it's wake file descriptor either, just like CFRunLoop does not. Here's the thread I was asking pretty much the same thing that you need (arguably in opposite way) and reply there is basically same as here: runloop is dead