Repository: Mortennn/Dozer Branch: master Commit: b0540378c016 Files: 42 Total size: 96.8 KB Directory structure: gitextract_h3o59koz/ ├── .gitignore ├── .swiftlint.yml ├── Brewfile ├── Brewfile.lock.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cartfile ├── Cartfile.resolved ├── Configs/ │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Dozer/ │ ├── AppDelegate.swift │ ├── Constants.swift │ ├── DozerIcons.swift │ ├── Other/ │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ └── HelperStatusItemIcon.imageset/ │ │ │ └── Contents.json │ │ ├── Dozer.entitlements │ │ ├── Info.plist │ │ ├── bridging-header.h │ │ └── main.swift │ ├── StatusIconClasses/ │ │ ├── HelperStatusIcon.swift │ │ ├── NormalStatusIcon.swift │ │ └── RemoveStatusIcon.swift │ ├── Util.swift │ ├── ViewControllers/ │ │ ├── DozerVC.swift │ │ └── GeneralVC.swift │ └── XIB/ │ ├── Dozer.xib │ └── General.xib ├── LICENSE ├── Makefile ├── README.md ├── Scripts/ │ ├── LaunchAtLogin.sh │ ├── SignFrameworks.sh │ └── Swiftlint.sh ├── Stuff/ │ └── OldFiles/ │ ├── AnimationWindow.swift │ ├── ClickedWindowInfo.swift │ ├── CreateAnimation.swift │ ├── GetLeftStatusIcon.swift │ └── Screen.swift ├── appcast.xml ├── project.yml └── swiftgen.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ build/ .build/ Releases/ Dozer.xcodeproj/ Credentials/ Carthage/ # Fastlane fastlane/ fastlaneLegacy/ Gemfile Gemfile.lock # Swiftgen Dozer/Other/Generated/ ================================================ FILE: .swiftlint.yml ================================================ whitelist_rules: - anyobject_protocol - array_init - attributes - block_based_kvo - class_delegate_protocol - closing_brace - closure_end_indentation - closure_parameter_position - closure_spacing - collection_alignment - colon - comma - compiler_protocol_init - conditional_returns_on_newline - contains_over_first_not_nil - control_statement - deployment_target - discarded_notification_center_observer - discouraged_direct_init - discouraged_object_literal - discouraged_optional_boolean - discouraged_optional_collection - duplicate_imports - dynamic_inline - empty_count - empty_enum_arguments - empty_parameters - empty_parentheses_with_trailing_closure - empty_string - empty_xctest_method - explicit_init - fallthrough - fatal_error_message - first_where - for_where - generic_type_name - identical_operands - identifier_name - implicit_getter - implicit_return - inert_defer - is_disjoint - joined_default_parameter - last_where - leading_whitespace - legacy_cggeometry_functions - legacy_constant - legacy_constructor - legacy_hashing - legacy_nsgeometry_functions - legacy_random - literal_expression_end_indentation - lower_acl_than_parent - mark - modifier_order - multiline_arguments - multiline_function_chains - multiline_literal_brackets - multiline_parameters - multiline_parameters_brackets - multiple_closures_with_trailing_closure - nimble_operator - no_extension_access_modifier - no_fallthrough_only - notification_center_detachment - number_separator - object_literal - opening_brace - operator_usage_whitespace - operator_whitespace - overridden_super_call - pattern_matching_keywords - private_action - private_outlet - private_unit_test - prohibited_super_call - protocol_property_accessors_order - redundant_discardable_let - redundant_nil_coalescing - redundant_objc_attribute - redundant_optional_initialization - redundant_set_access_control - redundant_string_enum_value - redundant_type_annotation - redundant_void_return - required_enum_case - return_arrow_whitespace - shorthand_operator - sorted_first_last - statement_position - static_operator - strong_iboutlet - superfluous_disable_command - switch_case_alignment - switch_case_on_newline - syntactic_sugar - todo - toggle_bool - trailing_closure - trailing_comma - trailing_newline - trailing_semicolon - trailing_whitespace - type_name - unavailable_function - unneeded_break_in_switch - unneeded_parentheses_in_closure_argument - untyped_error_in_catch - unused_closure_parameter - unused_control_flow_label - unused_enumerated - unused_optional_binding - unused_setter_value - valid_ibinspectable - vertical_parameter_alignment - vertical_parameter_alignment_on_call - vertical_whitespace_closing_braces - vertical_whitespace_opening_braces - void_return - weak_computed_property - weak_delegate - xct_specific_matcher - xctfail_message - yoda_condition analyzer_rules: - unused_import - unused_private_declaration force_cast: warning force_unwrapping: warning number_separator: minimum_length: 5 object_literal: image_literal: false discouraged_object_literal: color_literal: false identifier_name: max_length: warning: 100 error: 100 min_length: warning: 2 error: 2 validates_start_with_lowercase: false allowed_symbols: - '_' excluded: - 'x' - 'y' - 'a' - 'b' - 'x1' - 'x2' - 'y1' - 'y2' included: - Dozer ================================================ FILE: Brewfile ================================================ brew "swiftformat" brew "swiftgen" brew "swiftlint" brew "xcodegen" brew "carthage" ================================================ FILE: Brewfile.lock.json ================================================ { "entries": { "brew": { "swiftformat": { "version": "0.44.16", "bottle": { "cellar": ":any_skip_relocation", "prefix": "/usr/local", "files": { "catalina": { "url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.16.catalina.bottle.tar.gz", "sha256": "e03e40dbe228ee7136cc2dd57079b589a6c531f4f2a52fce8a0ad7aaa2e2a935" }, "mojave": { "url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.16.mojave.bottle.tar.gz", "sha256": "9b866de2516e8f68b1ad764f996f38cba31b2d2fc345ba683bdd1867e6a44091" }, "high_sierra": { "url": "https://homebrew.bintray.com/bottles/swiftformat-0.44.16.high_sierra.bottle.tar.gz", "sha256": "eb111096afada9821c4cf1489706b5daf599670d8651719955998f763c6b9cdb" } } } }, "swiftgen": { "version": "6.2.1", "bottle": { "cellar": ":any", "prefix": "/usr/local", "files": { "catalina": { "url": "https://homebrew.bintray.com/bottles/swiftgen-6.2.1.catalina.bottle.tar.gz", "sha256": "0fde7ff65be044c1685ab5aa84514e45c594cbc8bad028b5ac8f2f6218cfd626" } } } }, "swiftlint": { "version": "0.39.2", "bottle": { "cellar": ":any_skip_relocation", "prefix": "/usr/local", "files": { "catalina": { "url": "https://homebrew.bintray.com/bottles/swiftlint-0.39.2.catalina.bottle.tar.gz", "sha256": "32f9e5e37190e81856eff49c2118c04d1995110b509f0a4997e211c9f939919e" }, "mojave": { "url": "https://homebrew.bintray.com/bottles/swiftlint-0.39.2.mojave.bottle.tar.gz", "sha256": "c4fc10277d46c406277491003e5d5bbdfe526890c39b30eb924749696ed56d40" } } } }, "xcodegen": { "version": "2.16.0", "bottle": { "cellar": ":any_skip_relocation", "prefix": "/usr/local", "files": { "catalina": { "url": "https://homebrew.bintray.com/bottles/xcodegen-2.16.0.catalina.bottle.tar.gz", "sha256": "fc8f03f0f3968c6f913725629bdfa34a24a539a6868afdb1112b8f76cdbf5a55" }, "mojave": { "url": "https://homebrew.bintray.com/bottles/xcodegen-2.16.0.mojave.bottle.tar.gz", "sha256": "a8eea29cb064ef817a1aae173b102eb689a263fc4039987a4592e9f15c4b1d85" } } } }, "carthage": { "version": "0.35.0", "bottle": { "cellar": ":any_skip_relocation", "prefix": "/usr/local", "files": { "catalina": { "url": "https://homebrew.bintray.com/bottles/carthage-0.35.0.catalina.bottle.tar.gz", "sha256": "fdaacafff1566fa7b1e42e68e108da69aa7578dc82f4736272dca10283c9cba2" }, "mojave": { "url": "https://homebrew.bintray.com/bottles/carthage-0.35.0.mojave.bottle.tar.gz", "sha256": "1da29ca0b1d8e79bec5e548f5ab2433890ebc3fc9007f5fdb70d9e3be281dbb7" }, "high_sierra": { "url": "https://homebrew.bintray.com/bottles/carthage-0.35.0.high_sierra.bottle.tar.gz", "sha256": "130fcb9bc06ef8e7f1c5ac9af0d155bd347db039b2cf80fa0fef764b3627ffbf" } } } } } }, "system": { "macos": { "catalina": { "HOMEBREW_VERSION": "2.4.4-57-g6d2c395", "HOMEBREW_PREFIX": "/usr/local", "Homebrew/homebrew-core": "a4ba652e5d0b5380df4f2edecc2f4d15cd15fc48", "CLT": "1103.0.32.29", "Xcode": "11.5", "macOS": "10.15.5" } } } } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## Version 4.2.0 New features: * Configure amount of seconds to hide the icons after #104. @blakedgordon * Resize icons and padding capability added #101. @blakedgordon Fixed: * Fix both dozer icons from being hidden #105. @blakedgordon Thank you @blakedgordon for the contributions🙌 ## Version 4.1.0 New features: * Hide status bar icons at launch #78. @aonez Fixed: * Reduce CPU usage when "Hide status bar icons after 10 seconds" is checked #78. @aonez Thank you @aonez for the contributions🙌 ## Version 4 New features: * ”Remove”-icon. Additional icon to hide/show icons with `option+click` * Auto-hide status bar icons #22 * ”No Icon”-mode. Hide/show only using keyboard shortcut Other: * Improved UI in preferences ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Dozer There are various ways to contribute to Dozer, and all are welcome and appreciated! - [Bug reports](#bug-reports) - [Feature requests](#feature-requests) - [Design](#design) - [Code](#code) - [Getting started](#getting-started) - [Submitting your pull request](#submitting-your-pull-request) ## Bug reports Open issues about problems you may be encountering. When doing so please mention the version you're using. ## Feature requests If you have a good idea for a feature or enhancement open an issue. ## Design Refresh the Dozer icons or logo. [Design resources for logo and status bar icon](https://www.figma.com/file/g5MhiwxR1YFg5vti0tPANa/Dozer). ## Code You can submit your own code. This can be bug fixes or new features. Please ask me (@Mortennn) before implementing a new feature. Check out [Issues](https://github.com/Mortennn/Dozer/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc). ### Getting started Make sure you have at least Xcode 10 installed. Run the following command: ```shell git clone https://github.com/mortennn/dozer && cd dozer && make build ``` Done! The project should open automatically in Xcode. ### Submitting your pull request Please give a small summary of what has changed. Also add any github issues links (`Fixes #100`). Once your pull request is created, please add a changelog entry to the CHANGELOG.md along with the PR number. ================================================ FILE: Cartfile ================================================ github "sindresorhus/LaunchAtLogin" github "sindresorhus/Defaults" github "sindresorhus/Preferences" github "shpakovski/MASShortcut" github "sparkle-project/Sparkle" ================================================ FILE: Cartfile.resolved ================================================ github "shpakovski/MASShortcut" "2.4.0" github "sindresorhus/Defaults" "v4.2.2" github "sindresorhus/LaunchAtLogin" "v4.1.0" github "sindresorhus/Preferences" "v2.2.1" github "sparkle-project/Sparkle" "1.26.0" ================================================ FILE: Configs/Debug.xcconfig ================================================ SWIFT_COMPILATION_MODE=Incremental SWIFT_OPTIMIZATION_LEVEL=-Onone DEBUG_INFORMATION_FORMAT=dwarf ENABLE_TESTABILITY=YES ================================================ FILE: Configs/Release.xcconfig ================================================ SWIFT_COMPILATION_MODE=wholemodule SWIFT_OPTIMIZATION_LEVEL=-O ================================================ FILE: Dozer/AppDelegate.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import MASShortcut import Sparkle import Defaults import Preferences final class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_: Notification) { MASShortcutBinder.shared()?.bindShortcut(withDefaultsKey: UserDefaultKeys.Shortcuts.ToggleMenuItems) { () in DozerIcons.shared.toggle() } // Initalize Dozer Icons _ = DozerIcons.shared // If enabled hide menu bar icons at launch DozerIcons.shared.hideAtLaunch() _ = DozerIcons.toggleDockIcon(showIcon: false) } // Show all Dozer icons when opening Dozer from Finder etc. func applicationOpenUntitledFile(_ sender: NSApplication) -> Bool { DozerIcons.shared.showAll() return true } lazy var preferences: [PreferencePane] = [ Dozer(), General() ] lazy var preferencesWindowController = PreferencesWindowController( preferencePanes: preferences, style: .toolbarItems, animated: true, hidesToolbarForSingleItem: true ) } ================================================ FILE: Dozer/Constants.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import Preferences import Defaults extension Defaults.Keys { static let hideOnLogin: Defaults.Key = Key("hideOnLogin", default: false) static let hideAtLaunchEnabled: Defaults.Key = Key("hideAtLaunchEnabled", default: false) static let hideAfterDelayEnabled: Defaults.Key = Key("hideAfterDelayEnabled", default: false) static let hideAfterDelay: Defaults.Key = Key("hideAfterDelay", default: 10) static let noIconMode: Defaults.Key = Key("noIconMode", default: false) static let removeDozerIconEnabled: Defaults.Key = Key("removeStatusIconEnabled", default: false) static let showIconAndMenuEnabled: Defaults.Key = Key("showIconAndMenuEnabled", default: false) static let iconSize: Defaults.Key = Key("fontSize", default: 10) static let buttonPadding: Defaults.Key = Key("buttonPadding", default: 25) static let animationEnabled: Defaults.Key = Key("animationEnabeld", default: false) static let isShortcutSet: Defaults.Key = Key("isShortcutSet", default: false) } struct UserDefaultKeys { struct Shortcuts { static let ToggleMenuItems: String = "toggleMenuItems" } } extension NSStoryboard.Name { static let preferences: NSStoryboard.Name = NSStoryboard.Name("Preferences") } extension Preferences.PaneIdentifier { static let dozer = Self("dozer") static let general = Self("general") } struct AppInfo { static let bundleIdentifier: String = Bundle.main.bundleIdentifier! static var releaseVersionNumber: String? { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String } static var buildVersionNumber: String? { Bundle.main.infoDictionary?["CFBundleVersion"] as? String } } enum StatusIconAction { case show case hide case toggle } enum StatusIconType { case normal case remove } enum DozerIcon { case remove case normalLeft case normalRight } ================================================ FILE: Dozer/DozerIcons.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import Defaults public final class DozerIcons { static var shared = DozerIcons() private var dozerIcons: [HelperstatusIcon] = [] private var timerToCheckUserInteraction = Timer() private var timerToHideDozerIcons = Timer() private var previousApp = NSRunningApplication() private init() { dozerIcons.append(NormalStatusIcon()) if !hideBothDozerIcons || !Defaults[.isShortcutSet] { dozerIcons.append(NormalStatusIcon()) } if enableRemoveDozerIcon { dozerIcons.append(RemoveStatusIcon()) } if hideStatusBarIconsAfterDelay { startTimer() } Defaults.observe(.isShortcutSet) { change in self.triggerHideBothDozerIcons() } .tieToLifetime(of: self) } private func startUserInteractionTimer() { guard Defaults[.hideAfterDelayEnabled] else { stopUserInteractionTimer() return } timerToCheckUserInteraction = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in if self.isUserInteractingWithStatusBar() { self.resetTimer() } } } private func stopUserInteractionTimer() { timerToCheckUserInteraction.invalidate() } // MARK: Observe changes to settings public var hideStatusBarIconsAtLaunch: Bool = Defaults[.hideAtLaunchEnabled] { didSet { Defaults[.hideAtLaunchEnabled] = self.hideStatusBarIconsAtLaunch } } public var hideStatusBarIconsAfterDelay: Bool = Defaults[.hideAfterDelayEnabled] { didSet { Defaults[.hideAfterDelayEnabled] = self.hideStatusBarIconsAfterDelay if hideStatusBarIconsAfterDelay { startTimer() } else { stopTimer() } } } public var hideBothDozerIcons: Bool = Defaults[.noIconMode] { didSet { Defaults[.noIconMode] = self.hideBothDozerIcons triggerHideBothDozerIcons() } } public func triggerHideBothDozerIcons() { let normalStatusIconsCount = dozerIcons.filter { $0.type == .normal}.count if hideBothDozerIcons && Defaults[.isShortcutSet] { if normalStatusIconsCount == 2 { let rightDozerIconXPos = get(dozerIcon: .normalRight).xPositionOnScreen dozerIcons.removeAll { $0.xPositionOnScreen == rightDozerIconXPos } } } else if !hideBothDozerIcons && Defaults[.isShortcutSet] || !Defaults[.isShortcutSet] { if normalStatusIconsCount == 1 { show() dozerIcons.append(NormalStatusIcon()) } } show() } public var enableRemoveDozerIcon: Bool = Defaults[.removeDozerIconEnabled] { didSet { Defaults[.removeDozerIconEnabled] = self.enableRemoveDozerIcon if enableRemoveDozerIcon { dozerIcons.append(RemoveStatusIcon()) } else { dozerIcons.removeAll { $0.type == .remove } } showAll() } } public var enableIconAndMenu: Bool = Defaults[.showIconAndMenuEnabled] { didSet { Defaults[.showIconAndMenuEnabled] = self.enableIconAndMenu if self.enableIconAndMenu == false { _ = DozerIcons.toggleDockIcon(showIcon: false) appDelegate.preferencesWindowController.show(preferencePane: .general) } } } public var iconFontSize: Int = Defaults[.iconSize] { didSet { Defaults[.iconSize] = self.iconFontSize for icon in dozerIcons { icon.setSize() } } } public var buttonPadding: CGFloat = Defaults[.buttonPadding] { didSet { Defaults[.buttonPadding] = self.buttonPadding for icon in dozerIcons { icon.setSize() } } } // MARK: Public methods public func hide() { perform(action: .hide, statusIcon: .remove) perform(action: .hide, statusIcon: .normalLeft) if Defaults[.noIconMode] && Defaults[.isShortcutSet] { perform(action: .hide, statusIcon: .normalRight) } didHideStatusBarIcons() hideIconAndMenu() } public func hideAtLaunch() { if hideStatusBarIconsAtLaunch { if #available(macOS 11.0, *) { Timer.scheduledTimer(withTimeInterval: 0.15, repeats: false) { _ in self.hide() } } else { self.hide() } } } public func show() { resetTimer() perform(action: .hide, statusIcon: .remove) perform(action: .show, statusIcon: .normalLeft) if Defaults[.noIconMode] { perform(action: .show, statusIcon: .normalRight) } didShowStatusBarIcons() showIconAndMenu() } public func toggle() { if get(dozerIcon: .normalLeft).isShown { hide() } else { show() } } public func toggleRemove() { if get(dozerIcon: .remove).isShown { perform(action: .hide, statusIcon: .remove) } else { perform(action: .show, statusIcon: .remove) } } public func showIconAndMenu() { if NSWorkspace.shared.frontmostApplication?.bundleIdentifier != AppInfo.bundleIdentifier { previousApp = NSWorkspace.shared.frontmostApplication! } if Defaults[.showIconAndMenuEnabled] { _ = DozerIcons.toggleDockIcon(showIcon: true) NSApp.activate(ignoringOtherApps: true) } } public func hideIconAndMenu() { if Defaults[.showIconAndMenuEnabled] { _ = DozerIcons.toggleDockIcon(showIcon: false) if NSWorkspace.shared.frontmostApplication?.bundleIdentifier == AppInfo.bundleIdentifier { previousApp.activate() } } } /// Force show all Dozer icons public func showAll() { perform(action: .show, statusIcon: .remove) perform(action: .show, statusIcon: .normalLeft) perform(action: .show, statusIcon: .normalRight) didShowStatusBarIcons() } public func handleOptionClick() { showIconAndMenu() if get(dozerIcon: .normalLeft).isShown { DozerIcons.shared.perform( action: .toggle, statusIcon: .remove ) } else { DozerIcons.shared.perform( action: .show, statusIcon: .normalLeft ) DozerIcons.shared.perform( action: .show, statusIcon: .remove ) } stopUserInteractionTimer() startUserInteractionTimer() resetTimer() } // MARK: Show/hide lifecycle private func didShowStatusBarIcons() { //startTimer() startUserInteractionTimer() } private func didHideStatusBarIcons() { stopTimer() stopUserInteractionTimer() } private func willHideStatusBarIcons() { guard Defaults[.hideAfterDelayEnabled] else { return } // don't hide on user interaction with menu bar guard !isUserInteractingWithStatusBar() else { resetTimer() return } DozerIcons.shared.hide() } // MARK: timerToHideDozerIcons methods private func startTimer() { guard Defaults[.hideAfterDelayEnabled] else { stopTimer() return } timerToHideDozerIcons = Timer.scheduledTimer(withTimeInterval: Defaults[.hideAfterDelay], repeats: false) { (_: Timer) -> Void in self.willHideStatusBarIcons() } } private func stopTimer() { timerToHideDozerIcons.invalidate() } func resetTimer() { self.stopTimer() self.startTimer() } // MARK: Private methods /// Will fail silently if statusIcon does not exist private func perform(action: StatusIconAction, statusIcon: DozerIcon) { if statusIcon == .remove { guard Defaults[.removeDozerIconEnabled] else { return } } let theStatusIcon: HelperstatusIcon = get(dozerIcon: statusIcon) switch action { case .show: theStatusIcon.show() case .hide: theStatusIcon.hide() case .toggle: theStatusIcon.toggle() } } /// Will crash if trying to get ´DozerIcon´ which does not exist in the menu bar private func get(dozerIcon: DozerIcon) -> HelperstatusIcon { var normalStatusIconsXPosition: [CGFloat] = [] for statusIcon in dozerIcons where statusIcon.type == .normal { normalStatusIconsXPosition.append(statusIcon.xPositionOnScreen) } switch dozerIcon { case .remove: guard let removeStatusIcon = dozerIcons.first(where: { $0.type == .remove }) else { fatalError("Failed getting remove status icon") } return removeStatusIcon case .normalLeft: guard let leftStatusIcon = dozerIcons.first(where: { $0.xPositionOnScreen == normalStatusIconsXPosition.min() }) else { fatalError("Failed getting status icon on the left") } return leftStatusIcon case .normalRight: guard let rightStatusIcon = dozerIcons.first(where: { $0.xPositionOnScreen == normalStatusIconsXPosition.max() }) else { fatalError("Failed getting status icon on the right") } return rightStatusIcon } } /// hide and show dock icon and thus its menu bar: to free up space to show more menu bar icons public class func toggleDockIcon(showIcon state: Bool) -> Bool { if state { return NSApp.setActivationPolicy(NSApplication.ActivationPolicy.regular) } else { return NSApp.setActivationPolicy(NSApplication.ActivationPolicy.accessory) } } /// Determines if the user is interacting with the menu bar based on level, owner and y-coordinate /// /// - Returns: Returns whether the user is interacting with the menu bar or not private func isUserInteractingWithStatusBar() -> Bool { let windowListType = CGWindowListOption.optionOnScreenOnly guard let windowInfoList = CGWindowListCopyWindowInfo(windowListType, kCGNullWindowID) as NSArray? as? [[String: AnyObject]] else { return false } var statusBarAppsWindowInfo: [Window] = [] for windowInfo in windowInfoList { guard let window = Window(windowInfo), // If the preferences window are close to the menu bar it won't auto hide window.owner != "Dozer" else { continue } if window.isStatusIcon { statusBarAppsWindowInfo.append(window) } } for windowInfo in windowInfoList { guard let window = Window(windowInfo) else { continue } guard window.isStatusIcon == false else { continue } for statusBarApp in statusBarAppsWindowInfo { guard statusBarApp.owner == window.owner else { continue } guard (statusBarApp.y + 22...statusBarApp.y + 30).contains(window.y) else { continue } return true } } return false } /// Wrapper class for CGWindowList private class Window { var x: Int = 0 var y: Int = 0 var width: Int = 0 var height: Int = 0 var level: Int var owner: String init?(_ windowInfo: [String: AnyObject]) { guard let level = windowInfo[kCGWindowLayer as String] as? Int else { return nil } guard let owner = windowInfo[kCGWindowOwnerName as String] as? String else { return nil } self.level = level self.owner = owner let bounds: [String: Int] = windowInfo[kCGWindowBounds as String] as! [String: Int] for item in bounds { switch item.key { case "X": x = item.value case "Y": y = item.value case "Width": width = item.value case "Height": height = item.value default: continue } } } var isStatusIcon: Bool { guard level == 25 && height == 22 else { return false } return true } } } ================================================ FILE: Dozer/Other/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "Icon_16x16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "Icon_16x16@2x.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "Icon_32x32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "Icon_32x32@2x.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "Icon_128x128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "Icon_128x128@2x.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "Icon_256x256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "Icon_256x256@2x.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "Icon_512x512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "Icon_512x512@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Dozer/Other/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Dozer/Other/Assets.xcassets/HelperStatusItemIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "mac", "filename" : "icons8-filled-circle-24.png", "scale" : "1x" }, { "idiom" : "mac", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "original" } } ================================================ FILE: Dozer/Other/Dozer.entitlements ================================================ ================================================ FILE: Dozer/Other/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile AppIcon.icns CFBundleIdentifier com.mortennn.Dozer CFBundleInfoDictionaryVersion 6.0 CFBundleName Dozer CFBundlePackageType APPL CFBundleShortVersionString 4.2.0 CFBundleVersion 30 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 10.13 LSUIElement NSHumanReadableCopyright Copyright © 2020 Mortennn@GitHub. All rights reserved. NSPrincipalClass NSApplication SUFeedURL https://raw.githubusercontent.com/Mortennn/Dozer/master/appcast.xml SUPublicEDKey 97gvwr+B10rf6Exnj8yPuAXrYSu9yKikViJkYkvPMHY= ================================================ FILE: Dozer/Other/bridging-header.h ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #ifndef bridging_header_h #define bridging_header_h #import #import #endif ================================================ FILE: Dozer/Other/main.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import AppKit let app = NSApplication.shared let appDelegate = AppDelegate() app.delegate = appDelegate _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) ================================================ FILE: Dozer/StatusIconClasses/HelperStatusIcon.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import Defaults private struct StatusIconLength { static var show: CGFloat { Defaults[.buttonPadding] } static let hide: CGFloat = 10_000 } class HelperstatusIcon { var type: StatusIconType let statusIcon: NSStatusItem = NSStatusBar.system.statusItem(withLength: StatusIconLength.show) init() { type = .normal statusIcon.length = StatusIconLength.show statusIcon.highlightMode = false guard let statusIconButton = statusIcon.button else { fatalError("helper status item button failed") } statusIconButton.target = self statusIconButton.action = #selector(statusIconClicked(_:)) setIcon() statusIconButton.sendAction(on: [.leftMouseDown, .rightMouseDown]) } deinit { print("status item has been deallocated") } func show() { statusIcon.length = StatusIconLength.show } func hide() { statusIcon.length = StatusIconLength.hide } func toggle() { if isShown { hide() } else { show() } } func setIcon() { guard let statusIconButton = statusIcon.button else { fatalError("helper status item button failed") } statusIconButton.image = Icons().helperstatusIcon statusIconButton.image!.isTemplate = true } func setSize() { if statusIcon.length != StatusIconLength.hide { statusIcon.length = StatusIconLength.show } guard let statusIconButton = statusIcon.button else { fatalError("helper status item button failed") } let image = statusIconButton.image var size = DozerIcons.shared.iconFontSize if self.type == .remove { size /= 2 } image?.size = NSSize(width: size, height: size) statusIconButton.image = image } func showRemoveIcons() {} @objc func statusIconClicked(_ sender: AnyObject?) {} var isShown: Bool { statusIcon.length == StatusIconLength.show } var isHidden: Bool { statusIcon.length == StatusIconLength.hide } var xPositionOnScreen: CGFloat { guard let dozerIconFrame = statusIcon.button?.window?.frame else { return 0 } let dozerIconXPosition = dozerIconFrame.origin.x return dozerIconXPosition } } ================================================ FILE: Dozer/StatusIconClasses/NormalStatusIcon.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import Defaults class NormalStatusIcon: HelperstatusIcon { override init() { super.init() type = .normal } override func statusIconClicked(_: AnyObject?) { guard let currentEvent = NSApp.currentEvent else { return } if currentEvent.modifierFlags.contains(.option) && !currentEvent.modifierFlags.contains(.control) && !currentEvent.modifierFlags.contains(.command) { DozerIcons.shared.handleOptionClick() return } switch currentEvent.type { case .leftMouseDown: DozerIcons.shared.toggle() case .rightMouseDown: appDelegate.preferencesWindowController.show(preferencePane: .general) default: break } } } ================================================ FILE: Dozer/StatusIconClasses/RemoveStatusIcon.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import Defaults class RemoveStatusIcon: HelperstatusIcon { override init() { super.init() type = .remove } override func statusIconClicked(_: AnyObject?) { guard let currentEvent = NSApp.currentEvent else { return } switch currentEvent.type { case .leftMouseDown: DozerIcons.shared.toggleRemove() case .rightMouseDown: appDelegate.preferencesWindowController.show(preferencePane: .general) default: break } } override func setIcon() { guard let statusIconButton = statusIcon.button else { fatalError("helper status item button failed") } statusIconButton.image = Icons().removeStatusIcon statusIconButton.image!.isTemplate = true } } ================================================ FILE: Dozer/Util.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import Defaults extension NSButton { var isChecked: Bool { get { state == .on } set { state = newValue ? .on : .off } } } struct Icons { var helperstatusIcon: NSImage { let size = Defaults[.iconSize] return create(image: Assets.helperStatusItemIcon.name, size, size) } var removeStatusIcon: NSImage { let size = Defaults[.iconSize] / 2 return create(image: Assets.helperStatusItemIcon.name, size, size) } private func create(image name: String, _ width: Int, _ height: Int) -> NSImage { guard let image = Bundle.main.image(forResource: NSImage.Name(name)) else { fatalError("get image failed") } image.size = NSSize(width: width, height: height) return image } } ================================================ FILE: Dozer/ViewControllers/DozerVC.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import Sparkle import Preferences final class Dozer: NSViewController, PreferencePane { let preferencePaneIdentifier = Preferences.PaneIdentifier.dozer let preferencePaneTitle: String = "Dozer" let toolbarItemIcon = NSImage(named: "AppIcon")! override var nibName: NSNib.Name? { "Dozer" } @IBOutlet private var versionLabel: NSTextField! @IBOutlet private var checkForUpdates: NSButton! @IBOutlet private var quit: NSButton! override func viewDidLoad() { super.viewDidLoad() if let releaseVersionNumber = AppInfo.releaseVersionNumber, let buildVersionNumber = AppInfo.buildVersionNumber { versionLabel.stringValue = "\(releaseVersionNumber) (\(buildVersionNumber))" } checkForUpdates.target = SUUpdater.shared()! checkForUpdates.action = #selector(SUUpdater.shared()!.checkForUpdates(_:)) quit.action = #selector(NSApp.terminate(_:)) } } ================================================ FILE: Dozer/ViewControllers/GeneralVC.swift ================================================ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import Cocoa import Preferences import MASShortcut import LaunchAtLogin import Sparkle import Defaults final class General: NSViewController, PreferencePane { let preferencePaneIdentifier = Preferences.PaneIdentifier.general let preferencePaneTitle: String = "General" let toolbarItemIcon = NSImage(named: NSImage.preferencesGeneralName)! override var nibName: NSNib.Name? { "General" } fileprivate var userShortCut: MASShortcut! @IBOutlet private var LaunchAtLoginCheckbox: NSButton! @IBOutlet private var CheckForUpdatesCheckbox: NSButton! @IBOutlet private var HideStatusBarIconsAtLaunchCheckbox: NSButton! @IBOutlet private var HideStatusBarIconsAfterDelayCheckbox: NSButton! @IBOutlet private var HideStatusBarIconsSecondsPopUpButton: NSPopUpButton! @IBOutlet private var HideBothDozerIconsCheckbox: NSButton! @IBOutlet private var EnableRemoveDozerIconCheckbox: NSButton! @IBOutlet private var ShowIconAndMenuCheckbox: NSButton! @IBOutlet private var FontSizePopUpButton: NSPopUpButton! @IBOutlet private var ButtonPaddingPopUpButton: NSPopUpButton! @IBOutlet private var ToggleMenuItemsView: MASShortcutView! override func viewDidLoad() { super.viewDidLoad() LaunchAtLoginCheckbox.focusRingType = .none LaunchAtLoginCheckbox.isChecked = LaunchAtLogin.isEnabled if SUUpdater.shared() != nil { CheckForUpdatesCheckbox.isChecked = SUUpdater.shared()!.automaticallyChecksForUpdates } else { CheckForUpdatesCheckbox.isChecked = false } HideStatusBarIconsAtLaunchCheckbox.isChecked = Defaults[.hideAtLaunchEnabled] HideStatusBarIconsAfterDelayCheckbox.isChecked = Defaults[.hideAfterDelayEnabled] HideBothDozerIconsCheckbox.isChecked = Defaults[.noIconMode] EnableRemoveDozerIconCheckbox.isChecked = Defaults[.removeDozerIconEnabled] ShowIconAndMenuCheckbox.isChecked = Defaults[.showIconAndMenuEnabled] HideStatusBarIconsSecondsPopUpButton.selectItem(withTitle: "\(Int(Defaults[.hideAfterDelay])) seconds") FontSizePopUpButton.selectItem(withTitle: "\(Int(Defaults[.iconSize])) px") ButtonPaddingPopUpButton.selectItem(withTitle: "\(Int(Defaults[.buttonPadding])) px") ToggleMenuItemsView.associatedUserDefaultsKey = UserDefaultKeys.Shortcuts.ToggleMenuItems view.addSubview(ToggleMenuItemsView) configureEnabledNoIconCheckbox() ToggleMenuItemsView.shortcutValueChange = { _ -> Void in self.userShortCut = self.ToggleMenuItemsView.shortcutValue self.configureEnabledNoIconCheckbox() } } @IBAction private func launchAtLoginClicked(_ sender: NSButton) { LaunchAtLogin.isEnabled = (sender.state == .on) } @IBAction private func automaticallyCheckForUpdatesClicked(_ sender: NSButton) { guard SUUpdater.shared() != nil else { CheckForUpdatesCheckbox.isChecked = false return } SUUpdater.shared()!.automaticallyChecksForUpdates = CheckForUpdatesCheckbox.isChecked } @IBAction private func hideStatusBarIconsAtLaunchClicked(_ sender: NSButton) { DozerIcons.shared.hideStatusBarIconsAtLaunch = HideStatusBarIconsAtLaunchCheckbox.isChecked } @IBAction private func hideStatusBarIconsAfterDelayClicked(_ sender: NSButton) { DozerIcons.shared.hideStatusBarIconsAfterDelay = HideStatusBarIconsAfterDelayCheckbox.isChecked } @IBAction private func hideStatusBarIconsSecondsUpdated(_ sender: NSPopUpButton) { Defaults[.hideAfterDelay] = TimeInterval(HideStatusBarIconsSecondsPopUpButton.selectedTag()) DozerIcons.shared.resetTimer() } @IBAction private func hideBothDozerIconsClicked(_ sender: NSButton) { DozerIcons.shared.hideBothDozerIcons = HideBothDozerIconsCheckbox.isChecked } @IBAction private func showIconAndMenuClicked(_ sender: NSButton) { DozerIcons.shared.enableIconAndMenu = ShowIconAndMenuCheckbox.isChecked } @IBAction private func fontSizeChanged(_ sender: NSPopUpButton) { DozerIcons.shared.iconFontSize = FontSizePopUpButton.selectedTag() } @IBAction private func buttonPaddingChanged(_ sender: NSPopUpButton) { DozerIcons.shared.buttonPadding = CGFloat(ButtonPaddingPopUpButton.selectedTag()) } @IBAction private func enableRemoveDozerIconClicked(_ sender: NSButton) { DozerIcons.shared.enableRemoveDozerIcon = EnableRemoveDozerIconCheckbox.isChecked } /// disables the noIcon-checkbox if no shortcut is set and keeps track whether shortcut is set private func configureEnabledNoIconCheckbox() { if ToggleMenuItemsView.shortcutValue == nil { HideBothDozerIconsCheckbox.isEnabled = false Defaults[.isShortcutSet] = false } else { HideBothDozerIconsCheckbox.isEnabled = true Defaults[.isShortcutSet] = true } } } ================================================ FILE: Dozer/XIB/Dozer.xib ================================================ ================================================ FILE: Dozer/XIB/General.xib ================================================ ================================================ FILE: LICENSE ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: Makefile ================================================ build: @brew bundle --no-upgrade @carthage bootstrap --cache-builds --platform osx @mkdir -p Dozer/Other/Generated @swiftgen @xcodegen @xed "." release: @echo "Running Fastlane deploy" @bundle exec fastlane release .PHONY: build release ================================================ FILE: README.md ================================================

Hide menu bar icons to give your Mac a cleaner look.

download platform systemrequirements swiftlint license

demo

Buy Me A Coffee ## ⚙️ Install Using [Homebrew Cask](https://formulae.brew.sh/cask/dozer): ```shell brew install --cask dozer ``` Manual: [Download](https://github.com/Mortennn/Dozer/releases/latest), open and drag the app to the Applications folder. ## ⚫️ Dozer Icons There are 2 or 3, numbered from right to left: 1. this can be positioned anywhere you prefer, it is only a point of interaction 2. this and everything to its left will be hidden/shown by clicking any Dozer icon 3. (Optional) the "remove" icon and everything to its left will be hidden/shown by option-clicking any Dozer icon ## 👨‍💻 Usage * Move the icons you want to hide until clicked to the left of the second Dozer icon * Move the icons you want to hide until option-clicked to the left of the third Dozer icon **N.B. hold command (`⌘`) then drag to move the menu bar icons.** ## 👇 Interactions * Left-click one of the Dozer icons to hide/show the first group of menu bar icons * Option-Left-click one of the Dozer icons to show the second group of menu bar icons (optional) * Right-click one of the Dozer icons to open the settings ## 📄 Requirements macOS 10.13+ ================================================ FILE: Scripts/LaunchAtLogin.sh ================================================ ./Carthage/Build/Mac/LaunchAtLogin.framework/Resources/copy-helper.sh ================================================ FILE: Scripts/SignFrameworks.sh ================================================ LOCATION="${BUILT_PRODUCTS_DIR}"/"${FRAMEWORKS_FOLDER_PATH}" # By default, use the configured code signing identity for the project/target IDENTITY="${CODE_SIGN_IDENTITY}" if [ "$IDENTITY" == "" ] then # If a code signing identity is not specified, use ad hoc signing IDENTITY="-" fi codesign --verbose --force --deep -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A/Resources/AutoUpdate.app" codesign --verbose --force -o runtime --sign "$IDENTITY" "$LOCATION/Sparkle.framework/Versions/A" ================================================ FILE: Scripts/Swiftlint.sh ================================================ if which swiftlint >/dev/null; then swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fi ================================================ FILE: Stuff/OldFiles/AnimationWindow.swift ================================================ import Cocoa final class AnimationWindow: NSPanel { convenience init(frame: NSRect) { self.init(contentRect: frame, styleMask: .nonactivatingPanel, backing: .buffered, defer: false) self.isOpaque = false self.hasShadow = true self.titlebarAppearsTransparent = true self.titleVisibility = .hidden self.level = .init(26) self.collectionBehavior = [.canJoinAllSpaces, .fullScreenNone] self.backgroundColor = NSColor(calibratedRed: 0.13, green: 0.13, blue: 0.12, alpha: 1) self.ignoresMouseEvents = false } func animate() { self.alphaValue = 1 self.makeKeyAndOrderFront(nil) NSAnimationContext.runAnimationGroup({ context in context.duration = TimeInterval(0.25) self.animator().alphaValue = 0 }, completionHandler: nil) } } ================================================ FILE: Stuff/OldFiles/ClickedWindowInfo.swift ================================================ // This can be used when detecting clicks on status bar extension NSEvent { var clickedWindowInfo: [String: AnyObject]? { let type = CGWindowListOption.optionOnScreenOnly let windowList = CGWindowListCopyWindowInfo(type, kCGNullWindowID) as NSArray? as? [[String: AnyObject]] for entry in windowList! { guard let windowNumber = entry[kCGWindowNumber as String] as? Int else { continue } if windowNumber == self.windowNumber { return entry } } return nil } } ================================================ FILE: Stuff/OldFiles/CreateAnimation.swift ================================================ private func createAnimation() { let normalStatusIconRightX = Int(DozerStatusIconController.shared.get(dozerIcon: .normalRight).xPositionOnScreen) let leftStatusIconPosition = getLeftStatusIcon() let frame = NSRect( x: leftStatusIconPosition, y: Int(NSScreen.main!.frame.height - 22), width: normalStatusIconRightX - leftStatusIconPosition, height: 22) let animationWindow = AnimationWindow(frame: frame) animationWindow.orderFront(nil) animationWindow.animate() } ================================================ FILE: Stuff/OldFiles/GetLeftStatusIcon.swift ================================================ import Cocoa #warning("possible bug: this should only return the visible icons positions") func getLeftStatusIcon() -> Int { guard let windowInfoList = CGWindowListCopyWindowInfo(CGWindowListOption.optionOnScreenOnly, kCGNullWindowID) as NSArray? as? [[String: AnyObject]] else { print("Failed getting the left status icon") return 0 } var statusBarIconX: [Int] = [] // collect all status bar icons x positions for windowInfo in windowInfoList { guard let window = Window(windowInfo) else { continue } guard window.isStatusIcon else { continue } statusBarIconX.append(window.x) } // find the smallest x position guard let minX = statusBarIconX.min() else { print("Failed getting the smallest x position") return 0 } return minX } ================================================ FILE: Stuff/OldFiles/Screen.swift ================================================ import Cocoa extension NSScreen { /// Takes a window point an outputs the screen the window is located on /// /// - Parameter point: Window point /// - Returns: The screen the window is located on class func currentScreenForPointLocation(point: NSPoint) -> NSScreen? { for screen in NSScreen.screens { if NSMouseInRect(point, screen.frame, false) { return screen } } return nil } /// Converts global point to point relative to screen /// /// - Parameter aPoint: window point /// - Returns: window point relative to screen func convertPoint(toScreenCoordinates aPoint: NSPoint) -> NSPoint { let normalizedX: CGFloat = abs(abs(frame.origin.x) - abs(aPoint.x)) let normalizedY: CGFloat = aPoint.y - frame.origin.y return NSPoint(x: normalizedX, y: normalizedY) } func flip(_ aPoint: NSPoint) -> NSPoint { return NSPoint(x: aPoint.x, y: frame.size.height - aPoint.y) } } // ORIGINAL // extension NSScreen { // class func currentScreenForMouseLocation() -> NSScreen? { // let mouseLocation: NSPoint = NSEvent.mouseLocation // // let screenEnumerator: NSEnumerator = (NSScreen.screens as NSArray).objectEnumerator() // var screen: NSScreen? // while (screen = screenEnumerator.nextObject() as? NSScreen) != nil, !NSMouseInRect(mouseLocation, screen!.frame, false) {} // // return screen // } // // func convertPoint(toScreenCoordinates aPoint: NSPoint) -> NSPoint { // let normalizedX: CGFloat = abs(abs(frame.origin.x) - abs(aPoint.x)) // let normalizedY: CGFloat = aPoint.y - frame.origin.y // // return NSPoint(x: normalizedX, y: normalizedY) // } // // func flip(_ aPoint: NSPoint) -> NSPoint { // return NSPoint(x: aPoint.x, y: frame.size.height - aPoint.y) // } // } ================================================ FILE: appcast.xml ================================================ Dozer 4.0.0 New features in version 4:
  • ”Remove”-icon. Additional icon to hide/show icons with `option+click`
  • Auto-hide status bar icons #22
  • ”No Icon”-mode. Hide/show only using keyboard shortcut

Other:

  • Improved UI in preferences
    ]]> Tue, 20 Aug 2019 13:01:53 +0200 10.13 ================================================ FILE: project.yml ================================================ name: Dozer options: usesTabs: false tabWidth: 4 createIntermediateGroups: true xcodeVersion: 10.2.1 deploymentTarget: 10.13 targets: Dozer: type: application platform: macOS deploymentTarget: macOS: 10.13 configFiles: Debug: Configs/Debug.xcconfig Release: Configs/Release.xcconfig sources: - Dozer postBuildScripts: - path: Scripts/LaunchAtLogin.sh name: Launch At Login runOnlyWhenInstalling: true - path: Scripts/SignFrameworks.sh name: Sign Frameworks runOnlyWhenInstalling: true runOnlyWhenInstalling: true - script: /usr/local/bin/swiftgen name: Generate files using SwiftGen - path: Scripts/Swiftlint.sh name: Swiftlint settings: base: CODE_SIGN_ENTITLEMENTS: Dozer/Other/Dozer.entitlements PRODUCT_BUNDLE_IDENTIFIER: "com.mortennn.Dozer" SWIFT_VERSION: 5 DEBUG_INFORMATION_FORMAT: "dwarf-with-dsym" ENABLE_HARDENED_RUNTIME: true MACOSX_DEPLOYMENT_TARGET: 10.13 VERSIONING_SYSTEM: apple-generic CURRENT_PROJECT_VERSION: 30 dependencies: - carthage: LaunchAtLogin - carthage: Defaults - carthage: Preferences - carthage: MASShortcut - carthage: Sparkle schemes: Dozer: build: targets: Dozer: all ================================================ FILE: swiftgen.yml ================================================ input_dir: Dozer/Other/ output_dir: Dozer/Other/Generated/ xcassets: inputs: Assets.xcassets outputs: - templateName: swift4 output: Assets.swift params: enumName: Assets