Skip to content

Commit

Permalink
feat: show indicator for fullscreen windows
Browse files Browse the repository at this point in the history
Also:

* preference to show or not fullscreen windows
* new indicator for hidden windows
  • Loading branch information
lwouis committed May 21, 2020
1 parent 2674c8f commit 0138cd1
Show file tree
Hide file tree
Showing 14 changed files with 42 additions and 25 deletions.
Binary file modified resources/SF-Pro-Text-Regular.otf
Binary file not shown.
3 changes: 3 additions & 0 deletions resources/l10n/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
/* No comment provided by engineer. */
"Fade out animation:" = "Fade out animation:";

/* No comment provided by engineer. */
"Fullscreen" = "Fullscreen";

/* No comment provided by engineer. */
"General" = "General";

Expand Down
7 changes: 4 additions & 3 deletions scripts/ensure_generated_files_are_up_to_date.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
set -exu

pod install
scripts/extract_l10n_strings.sh
scripts/import_l10n_strings_from_poeditor.sh
brew install jq
scripts/update_contributors.sh
scripts/import_l10n_strings_from_poeditor.sh
scripts/extract_l10n_strings.sh
pod install

git status
git --no-pager diff
Expand Down
2 changes: 1 addition & 1 deletion scripts/subset_fonticon.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ set -exu

"$(pipenv --venv)"/bin/pyftsubset resources/SF-Pro-Text-Regular-Full.otf \
--output-file=resources/SF-Pro-Text-Regular.otf \
--text="􀁎􀍷􀕬􀀸􀀺􀀼􀀾􀁀􀁂􀁄􀑱􀁆􀁈􀁊􀑳􀓵􀓶􀓷􀓸􀓹􀓺􀓻􀓼􀓽􀓾􀓿􀔀􀔁􀔂􀔃􀔄􀔅􀔆􀔇􀔈􀔉􀘠􀚗􀚙􀚛􀚝􀚟􀚡􀚣􀚥􀚧􀚩􀚫􀚭􀚯􀚱􀚳􀚵􀚷􀚹􀚻"
--text="􀁎􀁌􀕧􀕬􀀸􀀺􀀼􀀾􀁀􀁂􀁄􀑱􀁆􀁈􀁊􀑳􀓵􀓶􀓷􀓸􀓹􀓺􀓻􀓼􀓽􀓾􀓿􀔀􀔁􀔂􀔃􀔄􀔅􀔆􀔇􀔈􀔉􀘠􀚗􀚙􀚛􀚝􀚟􀚡􀚣􀚥􀚧􀚩􀚫􀚭􀚯􀚱􀚳􀚵􀚷􀚹􀚻"
2 changes: 1 addition & 1 deletion scripts/travis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
set -exu

env | sort

npm ci
brew install jq

npx commitlint-travis
scripts/ensure_generated_files_are_up_to_date.sh
Expand Down
7 changes: 0 additions & 7 deletions src/api-wrappers/AXUIElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,6 @@ extension AXUIElement {
return attribute(kAXWindowsAttribute, [AXUIElement].self)
}

func isTabbed(_ app: AXUIElement, _ spaceId: CGSSpaceID) -> Bool {
// we can only detect tabs for windows on the current space, as AXUIElement.windows() only reports current space windows
// also, windows that start in fullscreen will have the wrong spaceID at that point in time, so we check if they are fullscreen too
return spaceId == Spaces.currentSpaceId && !isFullScreen() &&
app.windows()?.first { $0 == self } == nil
}

func isMinimized() -> Bool {
return attribute(kAXMinimizedAttribute, Bool.self) == true
}
Expand Down
2 changes: 2 additions & 0 deletions src/logic/DebugProfile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ class DebugProfile {

private static func appWindow(_ window: Window) -> String {
return listLevel3([
("isFullscreen", String(window.isFullscreen)),
("isMinimized", String(window.isMinimized)),
("isHidden", String(window.isHidden)),
("isTabbed", String(window.isTabbed)),
("isOnAllSpaces", String(window.isOnAllSpaces)),
("spaceId", String(window.spaceId)),
("spaceIndex", String(window.spaceIndex)),
Expand Down
2 changes: 2 additions & 0 deletions src/logic/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Preferences {
"hideShowAppShortcut": "H",
"arrowKeysEnabled": "true",
"mouseHoverEnabled": "true",
"showFullscreenWindows": "true",
"showMinimizedWindows": "true",
"showHiddenWindows": "true",
"showTabsAsWindows": "false",
Expand Down Expand Up @@ -63,6 +64,7 @@ class Preferences {
static var hideShowAppShortcut: String { defaults.string("hideShowAppShortcut") }
static var arrowKeysEnabled: Bool { defaults.bool("arrowKeysEnabled") }
static var mouseHoverEnabled: Bool { defaults.bool("mouseHoverEnabled") }
static var showFullscreenWindows: Bool { defaults.bool("showFullscreenWindows") }
static var showMinimizedWindows: Bool { defaults.bool("showMinimizedWindows") }
static var showHiddenWindows: Bool { defaults.bool("showHiddenWindows") }
static var showTabsAsWindows: Bool { defaults.bool("showTabsAsWindows") }
Expand Down
19 changes: 14 additions & 5 deletions src/logic/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import Cocoa

class Window {
var cgWindowId: CGWindowID
var title: String
var title: String = ""
var thumbnail: NSImage?
var icon: NSImage?
var shouldShowTheUser = true
var isTabbed: Bool
var isTabbed: Bool = false
var isHidden: Bool
var isFullscreen: Bool
var isMinimized: Bool
var isOnAllSpaces: Bool
var spaceId: CGSSpaceID
Expand All @@ -33,11 +34,12 @@ class Window {
self.spaceId = Spaces.currentSpaceId
self.spaceIndex = Spaces.currentSpaceIndex
self.icon = application.runningApplication.icon
self.isTabbed = axUiElement.isTabbed(application.axUiElement!, spaceId)
self.isHidden = application.runningApplication.isHidden
self.isFullscreen = axUiElement.isFullScreen()
self.isMinimized = axUiElement.isMinimized()
self.isOnAllSpaces = false
self.title = Window.bestEffortTitle(axUiElement, cgWindowId, application)
self.title = bestEffortTitle()
self.isTabbed = getIsTabbed()
debugPrint("Adding window", cgWindowId, title, application.runningApplication.bundleIdentifier ?? "nil", Spaces.currentSpaceId, Spaces.currentSpaceIndex)
observeEvents()
}
Expand All @@ -56,6 +58,13 @@ class Window {
thumbnail = NSImage(cgImage: cgImage, size: NSSize(width: cgImage.width, height: cgImage.height))
}

func getIsTabbed() -> Bool {
// we can only detect tabs for windows on the current space, as AXUIElement.windows() only reports current space windows
// also, windows that start in fullscreen will have the wrong spaceID at that point in time, so we check if they are fullscreen too
return spaceId == Spaces.currentSpaceId && !isFullscreen &&
application.axUiElement!.windows()?.first { $0 == axUiElement } == nil
}

func close() {
DispatchQueues.accessibilityCommands.async { [weak self] in
self?.axUiElement.closeWindow()
Expand Down Expand Up @@ -121,7 +130,7 @@ class Window {
}

// for some windows (e.g. Slack), the AX API doesn't return a title; we try CG API; finally we resort to the app name
static func bestEffortTitle(_ axUiElement: AXUIElement, _ cgWindowId: CGWindowID, _ application: Application) -> String {
func bestEffortTitle() -> String {
if let axTitle = axUiElement.title(), !axTitle.isEmpty {
return axTitle
}
Expand Down
4 changes: 3 additions & 1 deletion src/logic/Windows.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ class Windows {
}

static func refreshIfWindowShouldBeShownToTheUser(_ window: Window, _ screen: NSScreen) {
window.shouldShowTheUser = !(!Preferences.showMinimizedWindows && window.isMinimized) &&
window.shouldShowTheUser =
!(!Preferences.showFullscreenWindows && window.isFullscreen) &&
!(!Preferences.showMinimizedWindows && window.isMinimized) &&
!(!Preferences.showHiddenWindows && window.isHidden) &&
!(Preferences.appsToShow == .active && window.application.runningApplication != NSWorkspace.shared.frontmostApplication) &&
!(Preferences.spacesToShow == .active && window.spaceId != Spaces.currentSpaceId) &&
Expand Down
5 changes: 3 additions & 2 deletions src/logic/events/AccessibilityEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ func axObserverCallback(observer: AXObserver, element: AXUIElement, notification
}

private func focusedUiElementChanged(_ element: AXUIElement, _ applicationPointer: UnsafeMutableRawPointer?) {
// this event is the only opportunity we have to check if a window became a tab, or a tab became a window
let application = Unmanaged<Application>.fromOpaque(applicationPointer!).takeUnretainedValue()
Windows.list.forEach {
if $0.application == application {
$0.isTabbed = $0.axUiElement.isTabbed(application.axUiElement!, $0.spaceId)
// this event is the only opportunity we have to check if a window became a tab, or a tab became a window
$0.isTabbed = $0.getIsTabbed()
}
}
}
Expand Down Expand Up @@ -96,5 +96,6 @@ private func windowTitleChanged(_ element: AXUIElement) {
private func windowResized(_ element: AXUIElement) {
guard let index = Windows.list.firstIndexThatMatches(element) else { return }
let window = Windows.list[index]
window.isFullscreen = window.axUiElement.isFullScreen()
App.app.refreshOpenUi([window])
}
3 changes: 2 additions & 1 deletion src/ui/main-window/ThumbnailFontIconView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import Cocoa
// Font icon using SF Symbols from the SF Pro font from Apple
// see https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/
class ThumbnailFontIconView: ThumbnailTitleView {
static let sfSymbolCircledPlusSign = "􀁌"
static let sfSymbolCircledMinusSign = "􀁎"
static let sfSymbolCircledDotSign = "􀍷"
static let sfSymbolCircledSlashSign = "􀕧"
static let sfSymbolCircledNumber0 = "􀀸"
static let sfSymbolCircledNumber10 = "􀓵"
static let sfSymbolCircledStart = "􀕬"
Expand Down
8 changes: 5 additions & 3 deletions src/ui/main-window/ThumbnailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ class ThumbnailView: NSStackView {
var thumbnail = NSImageView()
var appIcon = NSImageView()
var label = ThumbnailTitleView(Preferences.fontHeight)
var fullscreenIcon = ThumbnailFontIconView(ThumbnailFontIconView.sfSymbolCircledPlusSign, Preferences.fontIconSize, .white)
var minimizedIcon = ThumbnailFontIconView(ThumbnailFontIconView.sfSymbolCircledMinusSign, Preferences.fontIconSize, .white)
var hiddenIcon = ThumbnailFontIconView(ThumbnailFontIconView.sfSymbolCircledDotSign, Preferences.fontIconSize, .white)
var hiddenIcon = ThumbnailFontIconView(ThumbnailFontIconView.sfSymbolCircledSlashSign, Preferences.fontIconSize, .white)
var spaceIcon = ThumbnailFontIconView(ThumbnailFontIconView.sfSymbolCircledNumber0, Preferences.fontIconSize, .white)
var hStackView: NSStackView!
var mouseDownCallback: (() -> Void)!
Expand All @@ -32,7 +33,7 @@ class ThumbnailView: NSStackView {
let shadow = ThumbnailView.makeShadow(.gray)
thumbnail.shadow = shadow
appIcon.shadow = shadow
hStackView = NSStackView(views: [appIcon, label, hiddenIcon, minimizedIcon, spaceIcon])
hStackView = NSStackView(views: [appIcon, label, hiddenIcon, fullscreenIcon, minimizedIcon, spaceIcon])
hStackView.spacing = Preferences.intraCellPadding
setViews([hStackView, thumbnail], in: .leading)
}
Expand Down Expand Up @@ -78,6 +79,7 @@ class ThumbnailView: NSStackView {
label.string = element.title
}
assignIfDifferent(&hiddenIcon.isHidden, !element.isHidden)
assignIfDifferent(&fullscreenIcon.isHidden, !element.isFullscreen)
assignIfDifferent(&minimizedIcon.isHidden, !element.isMinimized)
assignIfDifferent(&spaceIcon.isHidden, Spaces.isSingleSpace || Preferences.hideSpaceNumberLabels)
if !spaceIcon.isHidden {
Expand All @@ -89,7 +91,7 @@ class ThumbnailView: NSStackView {
}
assignIfDifferent(&frame.size.width, max(thumbnail.frame.size.width + Preferences.intraCellPadding * 2, ThumbnailView.widthMin(screen)))
assignIfDifferent(&frame.size.height, newHeight)
let fontIconWidth = CGFloat([minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontIconSize + Preferences.intraCellPadding)
let fontIconWidth = CGFloat([fullscreenIcon, minimizedIcon, hiddenIcon, spaceIcon].filter { !$0.isHidden }.count) * (Preferences.fontIconSize + Preferences.intraCellPadding)
assignIfDifferent(&label.textContainer!.size.width, frame.width - Preferences.iconSize - Preferences.intraCellPadding * 3 - fontIconWidth)
assignIfDifferent(&subviews.first!.frame.size, frame.size)
self.mouseDownCallback = { () -> Void in App.app.focusSelectedWindow(element) }
Expand Down
3 changes: 2 additions & 1 deletion src/ui/preferences-window/tabs/GeneralTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ class GeneralTab: NSViewController, PreferencePane {
let appsToShow = dropdown("appsToShow", AppsToShowPreference.allCases)
let spacesToShow = dropdown("spacesToShow", SpacesToShowPreference.allCases)
let screensToShow = dropdown("screensToShow", ScreensToShowPreference.allCases)
let showFullscreenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Fullscreen", comment: ""), "showFullscreenWindows", labelPosition: .right))
let showMinimizedWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Minimized", comment: ""), "showMinimizedWindows", labelPosition: .right))
let showHiddenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hidden", comment: ""), "showHiddenWindows", labelPosition: .right))
let shortcuts = StackView([nextWindowShortcut, previousWindowShortcut, cancelShortcut, closeWindowShortcut, minDeminWindowShortcut, quitAppShortcut, hideShowAppShortcut].map { (view: [NSView]) in StackView(view) }, .vertical)
let toShowDropdowns = StackView([appsToShow, spacesToShow, screensToShow], .vertical)
let toShowCheckboxes = StackView([showMinimizedWindows, showHiddenWindows], .vertical)
let toShowCheckboxes = StackView([showMinimizedWindows, showHiddenWindows, showFullscreenWindows], .vertical)
let toShowExplanations = LabelAndControl.makeLabel(NSLocalizedString("Show the following windows:", comment: ""))
let toShow = StackView([toShowDropdowns, toShowCheckboxes], .vertical)

Expand Down

0 comments on commit 0138cd1

Please sign in to comment.