sindresorhus / Settings

⚙ Add a settings window to your macOS app in minutes
MIT License
1.43k stars 100 forks source link

Make it work automatically when used in a menu bar app #40

Open tesths opened 5 years ago

tesths commented 5 years ago

I make an app, just with statusbar and menu item. And I add preferences in the menu item. But when the Preferences open, I can't use cmd+w close it. Only has to click red close button in the left up corner.

Mortennn commented 5 years ago

You can't because your app misses an app menu.

As a workaround, you could use a library to bind cmd+w to closing the preferences window.

tesths commented 5 years ago

You can't because your app misses an app menu.

As a workaround, you could use a library to bind cmd+w to closing the preferences window.

@Mortennn Thanks for your advise. But I already try your solution. It has some problem. MASShortcut is global binding. If I bind cmd+w to my app, other app can not use cmd+w to close windows. I have to stop my app, so that other apps will be normal to use cmd+w to close it's window.

tesths commented 5 years ago

I think it's my fault. As apple document says

All of an application’s menus in the menu bar are owned by one NSMenu instance that’s created by the application when it starts up. You can retrieve this main menu with the NSApplication method mainMenu.

So I add custom menu programmatically. And now I can close it by cmd+w. I just reference Working without a nib, Part 10: Mac Main Menu. All things done.

sindresorhus commented 4 years ago

I think we could add something to make this just work.

  1. If the app is a menu bar app, call NSApp.activate(ignoringOtherApps: true) in PreferencesWindowController.show().
  2. If the app is a menu bar app, we could add the Command+C and Command+M shortcuts:
final class WindowForMenuBarApp: NSWindow {
    override var canBecomeMain: Bool { true }
    override var canBecomeKey: Bool { true }
    override var acceptsFirstResponder: Bool { true }

    override func cancelOperation(_ sender: Any?) {
        performClose(self)
    }

    override func keyDown(with event: NSEvent) {
        if event.modifiers == .command {
            if event.charactersIgnoringModifiers == "w" {
                performClose(self)
                return
            }

            if event.charactersIgnoringModifiers == "m" {
                performMiniaturize(self)
                return
            }
        }

        super.keyDown(with: event)
    }
}

@DivineDominion @SamusAranX Thoughts?

DivineDominion commented 4 years ago

I don't understand the use case, yet. Even menu bar apps can have an (invisible) main menu that handles the shortcuts. (I maintain 2 that work nicely.)

I think this is workaround the app developers need to implement, not the Preferences library. Since preferences are so central to apps, there's no harm in showing a workaround as a code snippet or as another example app target. (I would prefer the latter.)

sindresorhus commented 4 years ago

Even menu bar apps can have an (invisible) main menu that handles the shortcuts. (I maintain 2 that work nicely.)

Without also showing the Dock icon when the window is shown? That's news to me.

I think this is workaround the app developers need to implement, not the Preferences library.

I'm arguing this for egoistic reasons, as I plan to use Preferences in one of my menu bar apps, and it would be nice if it just worked. After all, the point of this package is to make it easier to add a Preferences window, and many apps are menu bar apps, so it's not like this is a niche problem. I think most menu bar apps either don't care about fixing this or don't even know it's an issue. So the ultimate gain of us adding this here is generally better UX in many apps for the user, which is a big win.

DivineDominion commented 4 years ago

I don't have a computer with Xcode for 2 weeks, but can have a look at this later!

If you want to try, create a new Cocoa app from scratch, add the LSUIElement Info.plist key, and add a text view to the main window. Copy and paste should just work. Now disable or remove the "Edit" main menu (that is not even visible during the app runtime) and try again.

sindresorhus commented 4 years ago

If you want to try, create a new Cocoa app from scratch, add the LSUIElement Info.plist key, and add a text view to the main window. Copy and paste should just work. Now disable or remove the "Edit" main menu (that is not even visible during the app runtime) and try again.

Wow. Interesting. I could have sworn that did not use to work, but I have not had a MainMenu.xib in my menu bar app for years.

sindresorhus commented 4 years ago

Tidbit: When using SwiftUI, that (Command+C, etc) works even with a main menu.

iolate commented 2 years ago

I just reference Working without a nib, Part 10: Mac Main Menu. All things done.

Here is code for my menu bar app with SwiftUI. Since main menu are invisible on menu bar app, I removed File/Edit submenu rules and removed localized string.

import Cocoa

@objc protocol EditMenuActions {
    func redo(_ sender: AnyObject)
    func undo(_ sender: AnyObject)
}

class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationWillFinishLaunching(_ notification: Notification) {
        let mainMenu = NSMenu(title: "MainMenu")
        let menuItem = mainMenu.addItem(withTitle: "", action: nil, keyEquivalent: "")

        let submenu = NSMenu()
        submenu.addItem(withTitle: "Close Window", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w")
        submenu.addItem(withTitle: "Undo", action: #selector(EditMenuActions.undo(_:)), keyEquivalent: "z")
        submenu.addItem(withTitle: "Redo", action: #selector(EditMenuActions.redo(_:)), keyEquivalent: "Z")
        submenu.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x")
        submenu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c")
        submenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v")
        submenu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a")

        mainMenu.setSubmenu(submenu, for: menuItem)
        NSApp.mainMenu = mainMenu
    }
}
DivineDominion commented 2 years ago

@iolate 👍 Keep in mind that if users set custom keyboard shortcuts, though, and using a non-English language for macOS, their rebound keys might be broken if you do not localize the titles. https://support.apple.com/en-sa/guide/mac-help/mchlp2271/mac