I'm creating a Mac menu bar app that I'd like to be notified when the user switches the visible desktop space (including external monitors). This is a menu bar only app (i.e no actual window).
I've seen a few similar questions, but none of the answers seemed to work for me. Most answers I've seen involve observing NSWorkspaceActiveSpaceDidChangeNotification on the NSWorkspace's notification center.
I've tried observing this in my AppDelegate in applicationDidFinishLaunching I have the following code:
NSWorkspace.sharedWorkspace().notificationCenter.addObserver(self,
selector: Selector(spaceChanged()),
name: NSWorkspaceActiveSpaceDidChangeNotification,
object: nil)
In my spaceChanged() function I'm just printing something to console for debugging purposes. This function only ever gets called on app launch. Whenever I change the desktop space though I never get notified.
Is there something I'm missing? Any help is appreciated.
Rather than Selector(spaceChanged()) you should use #selector(spaceChanged). With Selector(spaceChanged()), you're actually calling this function immediately, and using the result (which is probably just an empty tuple ()) to create a null selector. The latter syntax actually creates the proper selector referencing your spaceChanged function.
An update for Swift:
NSWorkspace.shared.notificationCenter.addObserver(
self,
selector: #selector(spaceChanged),
name: NSWorkspace.activeSpaceDidChangeNotification,
object: nil
)
Related
Recently I have been working on an old swift project built in back in 2015. Its really difficult to find which ViewController is executing currently because of the naming convention, usage of really massive storyboard(I feel terrified to even go to the main.storyboard) and various reason like usage of different language. Of course I can find it but it takes long. I was thinking if there is any way like when I run the project on a device and navigate to different page is there any way to see in the console which ViewController is executing ?
There is a quick way to find what view controller you are on when you are running your app.
1) Launch your app on the device or simulator.
2) Go into Xcode and tap this button:
3) This will open the Debug View Hierarchy. Click on the phone and you will see all the elements in the top bar. You can also see a hierarchy on the left-hand side. In here, you can click down and you will see the name of the view controller on screen.
Using the debugger after putting a breakpoint where you prefer you can write this and press return.
Swift 4.x
po UIApplication.shared.keyWindow?.rootViewController?.value(forKey: "_printHierarchy")
This method is a private API, thus you cannot use on production code, just use it in debug or from the debugger console.
I think it is better to see in debug area for your current viewcontroller named self, you get all information from there open the dropdown and and see you need to put break point in your viewController init method or where you want to debug, However you want to know programatically which is you current viewController you can get it by
appDelegate.window.currentViewController()
If you need which ViewController is Pushed or presented you can get from the above code but the case will be different if you are using the Slidemenu controller, I mean it is totally depends on how you have started navigation and which navigation controller is currently is use.
Can you briefly describe why you need current ViewController so i can help you further.
Is there a way to force the NSPopover to start in the detached state? I only see isDetached which is a read-only property for the state of the popover and an NSPopoverDelegate method detachableWindow(forPopover:) which lets me override the window that gets created. I'd like to essentially click a button and have the NSPopover start in the state in this photo.
The style of this window is exactly what a product requirement is and I can't seem to find any NSWindow style settings that would make a window do something like this (nor an NSPanel)
This detached popover functionality seems special in that it:
non-modal, but stays above main app. Able to still interact with the main app just like in Messages how you can still click around and type a new message.
Clicking another app, AppFoo, puts both the main app and the helper window behind AppFoo.
The helper window can be moved around and isn't hidden on app deactivation (another app gets selected).
Has the little, native, grey X in the top left.
If you don't mind calling private API, it's actually pretty simple:
let detach = NSSelectorFromString("detach")
if popover.responds(to: detach) {
popover.perform(detach)
}
No need to even add a delegate. I don't know when this private method was added but it's available at least since macOS 10.13. I suspect it's available since the introduction of NSPopover, though.
Here is the trick.
Use the required delegate method detachableWindowForPopover: to do the work for you, like:
- (void) showPopoverDetached
{
NSWindow* detachedWindow = [self detachableWindowForPopover:nil];
[detachedWindow.windowController showWindow:nil];
}
Seems that the Apple engineers implemented detachableWindowForPopover: on a pretty smart way, I guess it uses the content view controller class, and will always create a singleton like instance of the detached window.
Once detachableWindowForPopover: has called the presented window instance will be re-used no matter when and why it is called, called it directly (from a func like my sample above) or indirectly (e.g. when you drag out, detach, the popover from its original position)
This way they can prevent a popover from being detached 'twice' and we can also implement the detached way programmatically, nice job from them!
Here is a tiny demo of how it works in a real life (tested on macOS 10.13 - 13.0)
https://imgur.com/a/sfc7e6d
I would like to be notified when the main window of the frontmost app changes i.e changing a tab in a browser, changing a document in SublimeText etc. and get the title of that current main window. I'm quite new to Swift so I'd appreciate any kind of info/help.
I've written a piece of code to observe the frontmostapp by using addobserver on the didActivateAppNotification object. It successfully prints out the frontmost app everytime it changes to a new one.
So I also wanna do the same with main window of the frontmost app. However, all I can do is to get the window lists of the frontmost app via CGWindowListOption as explained here, then scroll through it to get a list of the windows of frontmost app and filter it to get the mainwindow. But I don't want a polling solution, which is to get this list every second. Instead I'd like to get use of a notification observer so that I can actually ask when the mainwindow changes. -I've also tried some Applescript solutions, they all need to be polling solutions.
I couldn't find any tutorials, examples of NSAccessibilityNotificationName classes as it says they require special handling in Apple Dev Forums. How could I observe those notifications for external applications? (specifically mainWindowChanged in my example)
Cheers, happy coding!
class activeApp: NSObject {
override init() {
super.init()
NSWorkspace.shared.notificationCenter.addObserver(self,
selector: #selector(printMe(notification:)),
name: NSWorkspace.didActivateApplicationNotification,
object:nil)
}
#objc func printMe(notification: NSNotification) {
let app = notification.userInfo!["NSWorkspaceApplicationKey"] as! NSRunningApplication
print(app.localizedName!)
}
}
let runme = activeApp()
RunLoop.main.run()
If you secondary-click on the Dock you can click the Turn Hiding On option to automatically hide the Dock. Alternatively, you can go to System Preferences > Dock and click the Automatically hide and show the Dock.
I want to mimic that functionality from within an app I am making (which is basically a status bar icon app) and preferably in Swift.
The code I have written so far to turn on the Dock Automatic Hiding functionality is the following:
// Update the value for key "autohide" in com.apple.dock.plist, located in ~/Library/Preferences/.
var dict = NSUserDefaults.standardUserDefaults().persistentDomainForName("com.apple.dock")
dict.updateValue(true, forKey: "autohide")
NSUserDefaults.standardUserDefaults().setPersistentDomain(dict, forName: "com.apple.dock")
// Send notification to the OS.
dispatch_async(dispatch_get_main_queue()) {
CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(), "com.apple.dock.prefchanged", nil, nil, true)
}
The first part of the code updates a value in a plist file and I have confirmed that that is working. The second part sends a notification to the OS to tell it that a value has been changed in that plist, which I have also confirmed to be working.
However, these two things are not making the Dock hide, making me believe I need to do something else. Or made my approach to the problem is wrong? How do I make the Dock start hiding?
PS: I have read something about a private, undocumented API called CoreDock, but I would like to avoid going that way, as it may cause many problems...
Almost certainly better to use AppleScript or the Scripting Bridge to do this. The following script turns Dock autohiding on:
tell application "System Events"
set autohide of dock preferences to true
end tell
You can run that using NSAppleScript.
In our project, we're using gtkmm and we have several classes that extend Gtk::Window in order to display our graphical interface.
I now found out what call produces the behaviour (described in the previous revision. The question now slightly changed.)
We're displaying one window, works like a charm.
Then, we have a window which displays various status messages. Let's call it MessageWindow. It has a method setMessage(Glib::ustring msg) which simply calls a label's set_text().
After some processing, we hide this window again and we now show a toolbar. Just yet another simple window, nothing crazy.
For all windows applies: The main thread calls show() on the window and creates a new thread which calls Gtk::Main::run() (without argument).
That's how it should be, until now.
The problem starts here: The main thread now wants to call MessageWindow::setMessage("any string"). a) if I call this method, the message window reacts completely correctly. But afterwards, the toolbar-window is displayed empty. b) if I don't call it, the message window doesn't change the label (which is absolutely clear), and the toolbar window is displayed as it should.
Seems like the windows are messing up each other.
Now the question:
If my gui-thread is blocking in Gtk::Main::run(), how can I now change the text of a label?
We're using gtkmm-2.4 (and no, we cannot upgrade)
Any help is appreciated.
Wow! That's complicated...
First: you should not manipulate windows from several threads. That is you should have just one GUI thread that does all the GUI work, and let the other threads communicate with it.
It is theoretically possible to make it work (in Linux; in Windows it is impossible) but it is more trouble than it is worth.
Second: the line Gtk::Main main(argc, argv) is not a call, it is an object declaration. The object main should live for the duration of the program, so if you use it in a object constructor, as soon as you return from it, the object will be destroyed! Just put it at the top of the main function and forget about it.
UPDATE: My usual approach here is to create a pipe, a g_io_channel to read, and write bytes on the other end.
Other option, although I didn't test it is to call get the GMainContext of the main thread and then g_idle_source_new() and attach that source to the main context with g_source_attach(). If you try this one and it works, please post your result here!