Skip to content

Commit

Permalink
fix: show minimized windows (closes #11)
Browse files Browse the repository at this point in the history
  • Loading branch information
louis.pontoise committed Dec 16, 2019
1 parent 87647b9 commit 0466e36
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 77 deletions.
42 changes: 29 additions & 13 deletions alt-tab-macos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@

/* Begin PBXBuildFile section */
D04BA02DD4152997C32CF50B /* StatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA0AF7C5DCF367FBB663C /* StatusItem.swift */; };
D04BA0F3D46BC79544E2B930 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA86768C6503A11ED81FC /* Extensions.swift */; };
D04BA0496ACF1427B6E9D369 /* CoreGraphicsApis.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA78E3B4E73B40DB77174 /* CoreGraphicsApis.swift */; };
D04BA20D4A240843293B3B52 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA56355579F78776E6D51 /* Cell.swift */; };
D04BA278D9EFA568C8D18A4C /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD1BED44EAEB77FED8A4 /* WindowManager.swift */; };
D04BA308162F8043F8561D03 /* AccessibilityApis.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA40A4291E4F310527DBF /* AccessibilityApis.swift */; };
D04BA3261C7DA5F48310E654 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA90C6C36DB1D65BC2B66 /* Application.swift */; };
D04BA57A871B7269BEBAFF84 /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA35456DA0DDA74F9687E /* Keyboard.swift */; };
D04BA6368E681BE3A408AC99 /* PreferencesPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA02F476DE30C4647886C /* PreferencesPanel.swift */; };
D04BA70FF7262BF5F9E6E13B /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BADCB1C0F50340A6CAFC2 /* Preferences.swift */; };
D04BA89A77CFDC4A3DF30487 /* CoreGraphicsApis.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA7AC3D9153353CE0A76D /* CoreGraphicsApis.swift */; };
D04BA8A3682D4FF07178C247 /* PrivateApis.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE463FA794A9B3F8F116 /* PrivateApis.swift */; };
D04BA8EBC0365A019A27C7EA /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA3F15EAE8D8C39B6F2CF /* Screen.swift */; };
D04BA9119E2329DB5A35B3C7 /* ThumbnailsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE5BBE182DD5DDFE2E3E /* ThumbnailsPanel.swift */; };
D04BA960DDD1D32A3019C835 /* CollectionViewCenterFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA3202A2C22C347E849B3 /* CollectionViewCenterFlowLayout.swift */; };
D04BA9CCE02D30C8164A552A /* SystemPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA2D2AD6B1CCA3F3A4DD7 /* SystemPermissions.swift */; };
D04BACD2FFB12589F9286B47 /* AccessibilityApis.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAE965ED779513FDEA0CD /* AccessibilityApis.swift */; };
D04BAD4DE538FDF7E7532EE2 /* Labels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD32E130E4A061DC8332 /* Labels.swift */; };
D04BAE369A14C3126A1606FE /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8F1AA48A323EE5638DC /* Extensions.swift */; };
D04BAEF78503D7A2CEFB9E9E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAA44C837F3A67403B9DB /* main.swift */; };
F029861A378EC1417106FEC3 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0298E42A818112B290FF6C7 /* TextField.swift */; };
F0298AB28A3CE5DBEC385730 /* HyperlinkLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0298708E2B13DBD4738AE76 /* HyperlinkLabel.swift */; };
Expand All @@ -43,18 +44,19 @@
D04BA32F25860B686DFE818A /* 3 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line.jpg"; sourceTree = "<group>"; };
D04BA35456DA0DDA74F9687E /* Keyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = "<group>"; };
D04BA3F15EAE8D8C39B6F2CF /* Screen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
D04BA40A4291E4F310527DBF /* AccessibilityApis.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityApis.swift; sourceTree = "<group>"; };
D04BA4336B6004A0A99849AD /* package.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = package.json; sourceTree = "<group>"; };
D04BA459034C1885CA43A807 /* LICENCE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENCE.md; sourceTree = "<group>"; };
D04BA4B5292629AA6B560216 /* package_release.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = package_release.sh; sourceTree = "<group>"; };
D04BA4F23325560BC0BCDDB7 /* 7 windows - 2 lines - tall window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - tall window.jpg"; sourceTree = "<group>"; };
D04BA51D43775E57CE91154A /* 3 windows - 1 line - wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line - wide window.jpg"; sourceTree = "<group>"; };
D04BA56355579F78776E6D51 /* Cell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = "<group>"; };
D04BA5ABFA5457A86536E2E4 /* 5 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "5 windows - 1 line.jpg"; sourceTree = "<group>"; };
D04BA7AC3D9153353CE0A76D /* CoreGraphicsApis.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreGraphicsApis.swift; sourceTree = "<group>"; };
D04BA78E3B4E73B40DB77174 /* CoreGraphicsApis.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreGraphicsApis.swift; sourceTree = "<group>"; };
D04BA7B6AAB0812631BBC7A2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = "<group>"; };
D04BA7ECCE728582D9ECA613 /* determine_version.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = determine_version.sh; sourceTree = "<group>"; };
D04BA82F792DF53958D92572 /* alt-tab-macos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "alt-tab-macos.app"; sourceTree = BUILT_PRODUCTS_DIR; };
D04BA86768C6503A11ED81FC /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
D04BA8F1AA48A323EE5638DC /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
D04BA90C6C36DB1D65BC2B66 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
D04BA92541D46EA4F6943A72 /* package-lock.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "package-lock.json"; sourceTree = "<group>"; };
D04BA9EF65B2E7AF9E3ADCA3 /* 2 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "2 windows - 1 line.jpg"; sourceTree = "<group>"; };
Expand All @@ -73,11 +75,12 @@
D04BADB20AB31BF83593E0BE /* greetings.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = greetings.yml; sourceTree = "<group>"; };
D04BADCB1C0F50340A6CAFC2 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
D04BAE1243C9B4BE3ED1B524 /* 7 windows - 2 lines - extra wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - extra wide window.jpg"; sourceTree = "<group>"; };
D04BAE463FA794A9B3F8F116 /* PrivateApis.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateApis.swift; sourceTree = "<group>"; };
D04BAE5BBE182DD5DDFE2E3E /* ThumbnailsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailsPanel.swift; sourceTree = "<group>"; };
D04BAE965ED779513FDEA0CD /* AccessibilityApis.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityApis.swift; sourceTree = "<group>"; };
D04BAF076A30A1BAFEDBEA66 /* 5 windows - 2 lines.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "5 windows - 2 lines.jpg"; sourceTree = "<group>"; };
D04BAF249324297C07E31164 /* frontpage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = frontpage.jpg; sourceTree = "<group>"; };
D04BAFA277EAE3BDDDB61110 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
D04BAFFC95D3A5BE76E3E653 /* PrivateApisBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrivateApisBridge.h; sourceTree = "<group>"; };
F0298708E2B13DBD4738AE76 /* HyperlinkLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HyperlinkLabel.swift; sourceTree = "<group>"; };
F0298E42A818112B290FF6C7 /* TextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -121,6 +124,17 @@
name = Products;
sourceTree = "<group>";
};
D04BA22D2CA2755FA5902C34 /* api-wrappers */ = {
isa = PBXGroup;
children = (
D04BA40A4291E4F310527DBF /* AccessibilityApis.swift */,
D04BA78E3B4E73B40DB77174 /* CoreGraphicsApis.swift */,
D04BAE463FA794A9B3F8F116 /* PrivateApis.swift */,
D04BA8F1AA48A323EE5638DC /* Extensions.swift */,
);
path = "api-wrappers";
sourceTree = "<group>";
};
D04BA2C9EF33A646D0977195 /* .github */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -179,9 +193,6 @@
D04BADCB1C0F50340A6CAFC2 /* Preferences.swift */,
D04BA3F15EAE8D8C39B6F2CF /* Screen.swift */,
D04BA2D2AD6B1CCA3F3A4DD7 /* SystemPermissions.swift */,
D04BA86768C6503A11ED81FC /* Extensions.swift */,
D04BAE965ED779513FDEA0CD /* AccessibilityApis.swift */,
D04BA7AC3D9153353CE0A76D /* CoreGraphicsApis.swift */,
);
path = logic;
sourceTree = "<group>";
Expand Down Expand Up @@ -227,6 +238,8 @@
D04BA7B6AAB0812631BBC7A2 /* Info.plist */,
D04BAA44C837F3A67403B9DB /* main.swift */,
D04BAA1C553891551B903DA7 /* logic */,
D04BAFFC95D3A5BE76E3E653 /* PrivateApisBridge.h */,
D04BA22D2CA2755FA5902C34 /* api-wrappers */,
);
path = "alt-tab-macos";
sourceTree = "<group>";
Expand Down Expand Up @@ -312,12 +325,13 @@
D04BA8EBC0365A019A27C7EA /* Screen.swift in Sources */,
D04BA9CCE02D30C8164A552A /* SystemPermissions.swift in Sources */,
D04BA02DD4152997C32CF50B /* StatusItem.swift in Sources */,
D04BA0F3D46BC79544E2B930 /* Extensions.swift in Sources */,
D04BAD4DE538FDF7E7532EE2 /* Labels.swift in Sources */,
F029861A378EC1417106FEC3 /* TextField.swift in Sources */,
F0298AB28A3CE5DBEC385730 /* HyperlinkLabel.swift in Sources */,
D04BACD2FFB12589F9286B47 /* AccessibilityApis.swift in Sources */,
D04BA89A77CFDC4A3DF30487 /* CoreGraphicsApis.swift in Sources */,
F0298AB28A3CE5DBEC385730 /* HyperlinkLabel.swift in Sources */,
D04BA308162F8043F8561D03 /* AccessibilityApis.swift in Sources */,
D04BA0496ACF1427B6E9D369 /* CoreGraphicsApis.swift in Sources */,
D04BA8A3682D4FF07178C247 /* PrivateApis.swift in Sources */,
D04BAE369A14C3126A1606FE /* Extensions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -335,6 +349,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = "com.lwouis.alt-tab-macos";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "alt-tab-macos/PrivateApisBridge.h";
SWIFT_VERSION = 4.2;
};
name = Release;
Expand All @@ -350,6 +365,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.12;
PRODUCT_BUNDLE_IDENTIFIER = "com.lwouis.alt-tab-macos";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "alt-tab-macos/PrivateApisBridge.h";
SWIFT_VERSION = 4.2;
};
name = Debug;
Expand Down
17 changes: 17 additions & 0 deletions alt-tab-macos/PrivateApisBridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Private APIs from Webkit repo
// see https://github.com/WebKit/webkit/blob/master/Source/WebCore/PAL/pal/spi/cg/CoreGraphicsSPI.h

#import <CoreFoundation/CoreFoundation.h>

typedef uint32_t CGSConnectionID;
typedef uint32_t CGSWindowID;
typedef uint32_t CGSWindowCount;
typedef uint32_t CGSWindowCaptureOptions;
enum {
kCGSWindowCaptureNominalResolution = 0x0200,
kCGSCaptureIgnoreGlobalClipShape = 0x0800,
};

extern CGSConnectionID CGSMainConnectionID(void);

extern CFArrayRef CGSHWCaptureWindowList(CGSConnectionID cid, CGSWindowID *windowList, CGSWindowCount windowCount, CGSWindowCaptureOptions options);
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import Foundation

class AccessibilityApis {
static func windows(_ cgOwnerPid: pid_t) -> [AXUIElement] {
if let windows = attribute(AXUIElementCreateApplication(cgOwnerPid), kAXWindowsAttribute, [AXUIElement].self) {
return windows.filter {
// workaround: some apps like chrome use a window to implement the search popover
let windowBounds = value($0, kAXSizeAttribute, NSSize(), .cgSize)!
let isReasonablyBig = windowBounds.width > Preferences.minimumWindowSize && windowBounds.height > Preferences.minimumWindowSize
return isReasonablyBig
}
}
return []
return attribute(AXUIElementCreateApplication(cgOwnerPid), kAXWindowsAttribute, [AXUIElement].self) ?? []
}

static func window(_ cgOwnerPid: pid_t, _ cgWindow: NSDictionary, _ openWindows: [OpenWindow]) -> AXUIElement? {
let cgBounds = CGRect(dictionaryRepresentation: cgWindow[kCGWindowBounds] as! NSDictionary)!
return windows(cgOwnerPid).first(where: { axWindow in
let axBounds = AccessibilityApis.value(axWindow, kAXSizeAttribute, NSSize(), .cgSize)!
let sameSize = cgBounds.width == axBounds.width && cgBounds.height == axBounds.height
// not already there from the visible-windows first pass
let notAlreadyInList = openWindows.first(where: { $0.target == axWindow }) == nil
return sameSize && notAlreadyInList
})
}

static func rect(_ element: AXUIElement) -> CGRect {
Expand All @@ -29,7 +32,7 @@ class AccessibilityApis {
AXUIElementSetAttributeValue(element, attribute as CFString, AXValueCreate(type, &v)!)
}

private static func value<T>(_ element: AXUIElement, _ key: String, _ target: T, _ type: AXValueType) -> T? {
static func value<T>(_ element: AXUIElement, _ key: String, _ target: T, _ type: AXValueType) -> T? {
if let a = attribute(element, key, AXValue.self) {
var value = target
AXValueGetValue(a, type, &value)
Expand All @@ -38,7 +41,7 @@ class AccessibilityApis {
return nil
}

private static func attribute<T>(_ element: AXUIElement, _ key: String, _ type: T.Type) -> T? {
static func attribute<T>(_ element: AXUIElement, _ key: String, _ type: T.Type) -> T? {
var value: AnyObject?
let result = AXUIElementCopyAttributeValue(element, key as CFString, &value)
if result == .success, let typedValue = value as? T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Cocoa
import Foundation

class CoreGraphicsApis {
static func windows() -> [NSDictionary] {
return (CGWindowListCopyWindowInfo([.excludeDesktopElements, .optionOnScreenOnly], kCGNullWindowID) as! [NSDictionary])
static func windows(_ option: CGWindowListOption) -> [NSDictionary] {
return (CGWindowListCopyWindowInfo([.excludeDesktopElements, option], kCGNullWindowID) as! [NSDictionary])
.filter {
// workaround: filtering this criteria seems to remove non-windows UI elements
let isWindowNotMenubarOrOthers = value($0, kCGWindowLayer, Int(0)) == 0
Expand Down
File renamed without changes.
20 changes: 20 additions & 0 deletions alt-tab-macos/api-wrappers/PrivateApis.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Cocoa
import Foundation

class PrivateApis {
// CGSHWCaptureWindowList can capture screenshots of minimized windows, which CGWindowListCreateImage can't
static func windowScreenshots(_ windowId: [CGSWindowID]) -> Array<CGImage> {
return CGSHWCaptureWindowList(
CGSMainConnectionID(),
UnsafeMutablePointer(mutating: windowId),
CGSWindowCount(windowId.count),
CGSWindowCaptureOptions(kCGSCaptureIgnoreGlobalClipShape | kCGSWindowCaptureNominalResolution)
)!.takeRetainedValue() as! Array<CGImage>
// // Fall back to the non-hardware capture path if we didn't get a snapshot
// // (which usually happens if the window is fully off-screen).
// CGWindowImageOption imageOptions = kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque;
// if (captureAtNominalResolution)
// imageOptions |= kCGWindowImageNominalResolution;
// return adoptCF(CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, windowID, imageOptions));
}
}
Loading

0 comments on commit 0466e36

Please sign in to comment.