Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Apps/Keyty/Sources/Keyty/App/Lifecycle/AppController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ final class AppController: NSObject {
)
super.init()
self.menuController.setAppController(self)
NSApp.mainMenu = self.menuController.makeMainMenu()
self.dependencies.captureController.onCapturingChanged = { [weak self] isCapturing in
self?.statusItemController.isCapturing = isCapturing
}
Expand Down
38 changes: 35 additions & 3 deletions Apps/Keyty/Sources/Keyty/App/Shell/MainMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,41 @@ final class MenuController {
let menu = NSMenu(title: AppConstants.appName)
menu.addItem(shortcutItem)
menu.addItem(self.makeSettingsMenuItem())
let quit = NSMenuItem(title: L10n.MainMenu.quit(AppConstants.appName), action: #selector(AppController.quitApplication(_:)), keyEquivalent: "q")
quit.keyEquivalentModifierMask = [.command]
menu.addItem(self.register(quit))
menu.addItem(self.makeQuitMenuItem())
return menu
}

func makeMainMenu() -> NSMenu {
let mainMenu = NSMenu(title: AppConstants.appName)

let appMenuItem = NSMenuItem()
appMenuItem.submenu = self.makeApplicationMenu()
mainMenu.addItem(appMenuItem)

let fileMenuItem = NSMenuItem()
fileMenuItem.submenu = self.makeFileMenu()
mainMenu.addItem(fileMenuItem)

return mainMenu
}
}

// MARK: - Private API
private extension MenuController {
private func makeApplicationMenu() -> NSMenu {
let menu = NSMenu(title: AppConstants.appName)
menu.addItem(self.makeQuitMenuItem())
return menu
}

private func makeFileMenu() -> NSMenu {
let menu = NSMenu(title: "File")
let close = NSMenuItem(title: "Close Window", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w")
close.keyEquivalentModifierMask = [.command]
menu.addItem(close)
return menu
}

private func makeSettingsMenuItem() -> NSMenuItem {
let item = NSMenuItem(
title: L10n.MainMenu.settings,
Expand All @@ -54,6 +80,12 @@ private extension MenuController {
return self.register(item)
}

private func makeQuitMenuItem() -> NSMenuItem {
let item = NSMenuItem(title: L10n.MainMenu.quit(AppConstants.appName), action: #selector(AppController.quitApplication(_:)), keyEquivalent: "q")
item.keyEquivalentModifierMask = [.command]
return self.register(item)
}

private func register(_ item: NSMenuItem) -> NSMenuItem {
item.target = self.appController
self.controlledMenuItems.append(item)
Expand Down
35 changes: 35 additions & 0 deletions Apps/Keyty/Tests/KeytyTests/App/Shell/MenuControllerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// MenuControllerTests.swift
// KeytyTests
//
// SPDX-FileCopyrightText: 2026 Serhii Bykov
// SPDX-License-Identifier: BSD-3-Clause
//

import AppKit
import XCTest
@testable import Keyty

final class MenuControllerTests: XCTestCase {
func testMainMenuIncludesOnlyRequiredCommandQAndCommandWShortcuts() throws {
let controller = MenuController()

let menu = controller.makeMainMenu()
XCTAssertEqual(menu.items.count, 2)

let appMenu = try XCTUnwrap(menu.items.first?.submenu)
XCTAssertEqual(appMenu.items.count, 1)
let quitItem = try XCTUnwrap(appMenu.items.first)
XCTAssertEqual(quitItem.keyEquivalent, "q")
XCTAssertEqual(quitItem.keyEquivalentModifierMask, [.command])
XCTAssertEqual(quitItem.action, #selector(AppController.quitApplication(_:)))

let fileMenu = try XCTUnwrap(menu.items.dropFirst().first?.submenu)
XCTAssertEqual(fileMenu.items.count, 1)
let closeItem = try XCTUnwrap(fileMenu.items.first)
XCTAssertEqual(closeItem.keyEquivalent, "w")
XCTAssertEqual(closeItem.keyEquivalentModifierMask, [.command])
XCTAssertEqual(closeItem.action, #selector(NSWindow.performClose(_:)))
XCTAssertNil(closeItem.target)
}
}
Loading