feat: mvp
No files matched your search
@@ -0,0 +1,2 @@
|
||||
/.idea/
|
||||
xcuserdata/
|
||||
@@ -0,0 +1,396 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
D04BA0219AC4C1B8444B469F /* 4 windows - 1 line.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA996DB03B329C6B36A39 /* 4 windows - 1 line.jpg */; };
|
||||
D04BA10F27D14F5B90AA1910 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA7F8D6FBE1C6FDA7BC72 /* Application.swift */; };
|
||||
D04BA1BEB40295C4DC25F637 /* 1 window - 1 line.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA58701B5A47B6A2953E1 /* 1 window - 1 line.jpg */; };
|
||||
D04BA235E34B5120E2F75F34 /* 3 windows - 1 line.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA04504BB9802F6E0B889 /* 3 windows - 1 line.jpg */; };
|
||||
D04BA2FB9D4E2CE6E0B49F10 /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAECA9B67E89686393364 /* WindowManager.swift */; };
|
||||
D04BA3999A3D6A308EA41039 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = D04BAD40CE2D3A8AAC3819D0 /* .gitignore */; };
|
||||
D04BA486DF30D7169D4B69BA /* 3 windows - 1 line - wide window.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA8298AF4D8ED7827ACDC /* 3 windows - 1 line - wide window.jpg */; };
|
||||
D04BA4D8D74841AEC2601C80 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D04BA262E9A663B3A85A2A95 /* Assets.xcassets */; };
|
||||
D04BA5CB7B1B84BDC99E5C3C /* 7 windows - 2 lines - tall window.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA041E420F065EF8DC9F4 /* 7 windows - 2 lines - tall window.jpg */; };
|
||||
D04BA5F23B9618C2C526DA1E /* 5 windows - 1 line.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA4AF59F978749D697493 /* 5 windows - 1 line.jpg */; };
|
||||
D04BA7A29152DDF0BDCAC4AF /* 7 windows - 2 lines - wide window.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BADF2A78C91437E1E4C42 /* 7 windows - 2 lines - wide window.jpg */; };
|
||||
D04BA8877A8149B1439B1F04 /* 5 windows - 2 lines.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA5349996324809EA7208 /* 5 windows - 2 lines.jpg */; };
|
||||
D04BA958877CFEC80F5E883A /* 2 windows - 1 line.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BADB4ABF73F979B9FC0BA /* 2 windows - 1 line.jpg */; };
|
||||
D04BA960DDD1D32A3019C835 /* UiSubclasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA3202A2C22C347E849B3 /* UiSubclasses.swift */; };
|
||||
D04BAAACE304CA2BCB1DC1BE /* too many windows - 4 lines - paginated.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BAC503A122CC0068BB84E /* too many windows - 4 lines - paginated.jpg */; };
|
||||
D04BAAB78E71A923B63A9DFF /* 14 windows - 3 lines.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA75BFFC94D1E33047688 /* 14 windows - 3 lines.jpg */; };
|
||||
D04BAB37A8033F8D4309889F /* 3 windows - 1 line - tall window.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA685BCDB60B9FEA57438 /* 3 windows - 1 line - tall window.jpg */; };
|
||||
D04BAB6A446F35FA221E1831 /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA0E21840B63A2BC23253 /* Keyboard.swift */; };
|
||||
D04BAB8555DC582A5BA5F8EE /* 6 windows - 1 line.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA9EBCA307524DBE48DCF /* 6 windows - 1 line.jpg */; };
|
||||
D04BAE3835B9FBA218664C74 /* 7 windows - 2 lines - extra wide window.jpg in Resources */ = {isa = PBXBuildFile; fileRef = D04BA9B41D7EA9F1B6290C91 /* 7 windows - 2 lines - extra wide window.jpg */; };
|
||||
D04BAE5CC40654B1F96E8DE9 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D04BA7B6AAB0812631BBC7A2 /* Info.plist */; };
|
||||
D04BAEF78503D7A2CEFB9E9E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAA44C837F3A67403B9DB /* main.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
D04BA041E420F065EF8DC9F4 /* 7 windows - 2 lines - tall window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - tall window.jpg"; sourceTree = "<group>"; };
|
||||
D04BA04504BB9802F6E0B889 /* 3 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line.jpg"; sourceTree = "<group>"; };
|
||||
D04BA0E21840B63A2BC23253 /* Keyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = "<group>"; };
|
||||
D04BA262E9A663B3A85A2A95 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
D04BA3202A2C22C347E849B3 /* UiSubclasses.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiSubclasses.swift; sourceTree = "<group>"; };
|
||||
D04BA4AF59F978749D697493 /* 5 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "5 windows - 1 line.jpg"; sourceTree = "<group>"; };
|
||||
D04BA5349996324809EA7208 /* 5 windows - 2 lines.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "5 windows - 2 lines.jpg"; sourceTree = "<group>"; };
|
||||
D04BA58701B5A47B6A2953E1 /* 1 window - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "1 window - 1 line.jpg"; sourceTree = "<group>"; };
|
||||
D04BA685BCDB60B9FEA57438 /* 3 windows - 1 line - tall window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line - tall window.jpg"; sourceTree = "<group>"; };
|
||||
D04BA75BFFC94D1E33047688 /* 14 windows - 3 lines.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "14 windows - 3 lines.jpg"; sourceTree = "<group>"; };
|
||||
D04BA7B6AAB0812631BBC7A2 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = "<group>"; };
|
||||
D04BA7F8D6FBE1C6FDA7BC72 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||
D04BA8298AF4D8ED7827ACDC /* 3 windows - 1 line - wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line - wide window.jpg"; sourceTree = "<group>"; };
|
||||
D04BA82F792DF53958D92572 /* alt-tab-macos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "alt-tab-macos.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D04BA996DB03B329C6B36A39 /* 4 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "4 windows - 1 line.jpg"; sourceTree = "<group>"; };
|
||||
D04BA9B41D7EA9F1B6290C91 /* 7 windows - 2 lines - extra wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - extra wide window.jpg"; sourceTree = "<group>"; };
|
||||
D04BA9EBCA307524DBE48DCF /* 6 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "6 windows - 1 line.jpg"; sourceTree = "<group>"; };
|
||||
D04BAA44C837F3A67403B9DB /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
D04BAC503A122CC0068BB84E /* too many windows - 4 lines - paginated.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "too many windows - 4 lines - paginated.jpg"; sourceTree = "<group>"; };
|
||||
D04BAD1C9F215BCCD3B620AC /* alt_tab_macos.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = alt_tab_macos.entitlements; sourceTree = "<group>"; };
|
||||
D04BAD40CE2D3A8AAC3819D0 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = file.gitignore; path = .gitignore; sourceTree = "<group>"; };
|
||||
D04BADB4ABF73F979B9FC0BA /* 2 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "2 windows - 1 line.jpg"; sourceTree = "<group>"; };
|
||||
D04BADF2A78C91437E1E4C42 /* 7 windows - 2 lines - wide window.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "7 windows - 2 lines - wide window.jpg"; sourceTree = "<group>"; };
|
||||
D04BAECA9B67E89686393364 /* WindowManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
D04BA82F32FB183F65DC3E42 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
D04BA036CA8BDCF4608909C4 /* docs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BA9280DD11C32934568FC /* img */,
|
||||
);
|
||||
path = docs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BA1463D2A17038222BB84 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BA1A461BBB5A9BE15E015 /* Products */,
|
||||
D04BAD48B702C4B1DC543557 /* alt-tab-macos */,
|
||||
D04BAD40CE2D3A8AAC3819D0 /* .gitignore */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BA1A461BBB5A9BE15E015 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BA82F792DF53958D92572 /* alt-tab-macos.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BA9280DD11C32934568FC /* img */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BA58701B5A47B6A2953E1 /* 1 window - 1 line.jpg */,
|
||||
D04BADB4ABF73F979B9FC0BA /* 2 windows - 1 line.jpg */,
|
||||
D04BA04504BB9802F6E0B889 /* 3 windows - 1 line.jpg */,
|
||||
D04BA996DB03B329C6B36A39 /* 4 windows - 1 line.jpg */,
|
||||
D04BA4AF59F978749D697493 /* 5 windows - 1 line.jpg */,
|
||||
D04BA9EBCA307524DBE48DCF /* 6 windows - 1 line.jpg */,
|
||||
D04BA5349996324809EA7208 /* 5 windows - 2 lines.jpg */,
|
||||
D04BA75BFFC94D1E33047688 /* 14 windows - 3 lines.jpg */,
|
||||
D04BA685BCDB60B9FEA57438 /* 3 windows - 1 line - tall window.jpg */,
|
||||
D04BA8298AF4D8ED7827ACDC /* 3 windows - 1 line - wide window.jpg */,
|
||||
D04BA041E420F065EF8DC9F4 /* 7 windows - 2 lines - tall window.jpg */,
|
||||
D04BADF2A78C91437E1E4C42 /* 7 windows - 2 lines - wide window.jpg */,
|
||||
D04BAC503A122CC0068BB84E /* too many windows - 4 lines - paginated.jpg */,
|
||||
D04BA9B41D7EA9F1B6290C91 /* 7 windows - 2 lines - extra wide window.jpg */,
|
||||
);
|
||||
path = img;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BAAA28315301A906DD201 /* ui */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BA3202A2C22C347E849B3 /* UiSubclasses.swift */,
|
||||
);
|
||||
path = ui;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BAD48B702C4B1DC543557 /* alt-tab-macos */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BA262E9A663B3A85A2A95 /* Assets.xcassets */,
|
||||
D04BAD1C9F215BCCD3B620AC /* alt_tab_macos.entitlements */,
|
||||
D04BAAA28315301A906DD201 /* ui */,
|
||||
D04BA7F8D6FBE1C6FDA7BC72 /* Application.swift */,
|
||||
D04BA0E21840B63A2BC23253 /* Keyboard.swift */,
|
||||
D04BAECA9B67E89686393364 /* WindowManager.swift */,
|
||||
D04BA7B6AAB0812631BBC7A2 /* Info.plist */,
|
||||
D04BAA44C837F3A67403B9DB /* main.swift */,
|
||||
D04BA036CA8BDCF4608909C4 /* docs */,
|
||||
);
|
||||
path = "alt-tab-macos";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
D04BA5E73DAF232ADE11C97A /* alt-tab-macos */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D04BA4D71CBB2FA4B9947B10 /* Build configuration list for PBXNativeTarget "alt-tab-macos" */;
|
||||
buildPhases = (
|
||||
D04BAD01F4BCEDF8B539AFD2 /* Sources */,
|
||||
D04BA82F32FB183F65DC3E42 /* Frameworks */,
|
||||
D04BA96F3DC99263120BCD21 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "alt-tab-macos";
|
||||
productName = "alt-tab-macos";
|
||||
productReference = D04BA82F792DF53958D92572 /* alt-tab-macos.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
D04BA59EB5ED6F005E8F4407 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
ORGANIZATIONNAME = lwouis;
|
||||
};
|
||||
buildConfigurationList = D04BA83C9495F252D87CDF0F /* Build configuration list for PBXProject "alt-tab-macos" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = D04BA1463D2A17038222BB84;
|
||||
productRefGroup = D04BA1A461BBB5A9BE15E015 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
D04BA5E73DAF232ADE11C97A /* alt-tab-macos */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
D04BA96F3DC99263120BCD21 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D04BA4D8D74841AEC2601C80 /* Assets.xcassets in Resources */,
|
||||
D04BAE5CC40654B1F96E8DE9 /* Info.plist in Resources */,
|
||||
D04BA1BEB40295C4DC25F637 /* 1 window - 1 line.jpg in Resources */,
|
||||
D04BA958877CFEC80F5E883A /* 2 windows - 1 line.jpg in Resources */,
|
||||
D04BA235E34B5120E2F75F34 /* 3 windows - 1 line.jpg in Resources */,
|
||||
D04BA0219AC4C1B8444B469F /* 4 windows - 1 line.jpg in Resources */,
|
||||
D04BA5F23B9618C2C526DA1E /* 5 windows - 1 line.jpg in Resources */,
|
||||
D04BAB8555DC582A5BA5F8EE /* 6 windows - 1 line.jpg in Resources */,
|
||||
D04BA8877A8149B1439B1F04 /* 5 windows - 2 lines.jpg in Resources */,
|
||||
D04BAAB78E71A923B63A9DFF /* 14 windows - 3 lines.jpg in Resources */,
|
||||
D04BAB37A8033F8D4309889F /* 3 windows - 1 line - tall window.jpg in Resources */,
|
||||
D04BA486DF30D7169D4B69BA /* 3 windows - 1 line - wide window.jpg in Resources */,
|
||||
D04BA5CB7B1B84BDC99E5C3C /* 7 windows - 2 lines - tall window.jpg in Resources */,
|
||||
D04BA7A29152DDF0BDCAC4AF /* 7 windows - 2 lines - wide window.jpg in Resources */,
|
||||
D04BAAACE304CA2BCB1DC1BE /* too many windows - 4 lines - paginated.jpg in Resources */,
|
||||
D04BAE3835B9FBA218664C74 /* 7 windows - 2 lines - extra wide window.jpg in Resources */,
|
||||
D04BA3999A3D6A308EA41039 /* .gitignore in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
D04BAD01F4BCEDF8B539AFD2 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D04BA960DDD1D32A3019C835 /* UiSubclasses.swift in Sources */,
|
||||
D04BA10F27D14F5B90AA1910 /* Application.swift in Sources */,
|
||||
D04BAB6A446F35FA221E1831 /* Keyboard.swift in Sources */,
|
||||
D04BA2FB9D4E2CE6E0B49F10 /* WindowManager.swift in Sources */,
|
||||
D04BAEF78503D7A2CEFB9E9E /* main.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
D04BA49BCED00029C5289244 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "alt-tab-macos/alt_tab_macos.entitlements";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = "alt-tab-macos/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.lwouis.alt-tab-macos";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
D04BA6FB4EC72C6A126E86D7 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "alt-tab-macos/alt_tab_macos.entitlements";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
INFOPLIST_FILE = "alt-tab-macos/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.lwouis.alt-tab-macos";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D04BA801581D700C583B53FB /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
D04BA9150F87B08AB0ACE4AE /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
D04BA4D71CBB2FA4B9947B10 /* Build configuration list for PBXNativeTarget "alt-tab-macos" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D04BA6FB4EC72C6A126E86D7 /* Debug */,
|
||||
D04BA49BCED00029C5289244 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
};
|
||||
D04BA83C9495F252D87CDF0F /* Build configuration list for PBXProject "alt-tab-macos" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D04BA801581D700C583B53FB /* Debug */,
|
||||
D04BA9150F87B08AB0ACE4AE /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = D04BA59EB5ED6F005E8F4407 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,227 @@
|
||||
import Cocoa
|
||||
import Foundation
|
||||
|
||||
let maxScreenUsage: CGFloat = 0.8
|
||||
let windowPadding: CGFloat = 20
|
||||
let cellPadding: CGFloat = 6
|
||||
let cellBorderWidth: CGFloat = 2
|
||||
let maxThumbnailsPerRow: CGFloat = 4
|
||||
var thumbnailMaxWidth: CGFloat = 0
|
||||
var thumbnailMaxHeight: CGFloat = 0
|
||||
let iconSize: CGFloat = 32
|
||||
let fontHeight: CGFloat = 15
|
||||
let font: NSFont = .systemFont(ofSize: fontHeight)
|
||||
let interItemPadding: CGFloat = 4
|
||||
let tabKey = 48
|
||||
let metaKey = 0x3B
|
||||
let metaModifierFlag: NSEvent.ModifierFlags = .control
|
||||
let cellId = NSUserInterfaceItemIdentifier("Cell")
|
||||
let highlightColor: NSColor = .white
|
||||
|
||||
func updateThumbnailMaxSize() -> Void {
|
||||
let main = NSScreen.main!.frame
|
||||
thumbnailMaxWidth = (NSScreen.main!.frame.size.width * maxScreenUsage - windowPadding * 2) / maxThumbnailsPerRow - interItemPadding
|
||||
thumbnailMaxHeight = thumbnailMaxWidth * (main.height / main.width)
|
||||
}
|
||||
|
||||
class Application: NSApplication, NSApplicationDelegate, NSWindowDelegate, NSCollectionViewDataSource, NSCollectionViewDelegate, NSCollectionViewDelegateFlowLayout {
|
||||
var item: NSStatusItem?
|
||||
var backgroundView: NSVisualEffectView?
|
||||
var collectionView_: NSCollectionView?
|
||||
var window: NSWindow?
|
||||
var selectedOpenWindow: Int = 0
|
||||
var numberOfColumns: Int = 0
|
||||
var openWindows: [OpenWindow] = []
|
||||
var workItems: [DispatchWorkItem] = []
|
||||
var isFirstSummon: Bool = true
|
||||
var appIsBeingUsed: Bool = false
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
ensureAccessibilityCheckboxIsChecked()
|
||||
updateThumbnailMaxSize()
|
||||
makeStatusBarItem()
|
||||
makeWindow()
|
||||
makeCollectionView()
|
||||
backgroundView = NSVisualEffectView()
|
||||
backgroundView!.blendingMode = .behindWindow
|
||||
backgroundView!.material = .dark
|
||||
backgroundView!.state = .active
|
||||
backgroundView!.addSubview(collectionView_!)
|
||||
window!.contentView = backgroundView
|
||||
listenToGlobalKeyboardEvents(self)
|
||||
listenToScreenChanges()
|
||||
}
|
||||
|
||||
func listenToScreenChanges() {
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: NSApplication.didChangeScreenParametersNotification,
|
||||
object: NSApplication.shared,
|
||||
queue: OperationQueue.main
|
||||
) { notification -> Void in
|
||||
updateThumbnailMaxSize()
|
||||
}
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ notification: Notification) {
|
||||
if !appIsBeingUsed {
|
||||
focusSelectedWindow(currentlySelectedWindow())
|
||||
}
|
||||
}
|
||||
|
||||
func makeCollectionView() {
|
||||
let layout = CollectionViewCenterFlowLayout()
|
||||
layout.estimatedItemSize = NSSize(width: thumbnailMaxWidth, height: thumbnailMaxHeight)
|
||||
layout.minimumInteritemSpacing = 5
|
||||
layout.minimumLineSpacing = 5
|
||||
collectionView_ = NSCollectionView()
|
||||
collectionView_!.dataSource = self
|
||||
collectionView_!.delegate = self
|
||||
collectionView_!.collectionViewLayout = layout
|
||||
collectionView_!.backgroundColors = [.clear]
|
||||
collectionView_!.isSelectable = true
|
||||
collectionView_!.allowsMultipleSelection = false
|
||||
collectionView_!.register(Cell.self, forItemWithIdentifier: cellId)
|
||||
}
|
||||
|
||||
func makeWindow() {
|
||||
window = NSWindow()
|
||||
window!.styleMask = [.borderless]
|
||||
window!.level = .floating
|
||||
window!.animationBehavior = NSWindow.AnimationBehavior.none
|
||||
window!.backgroundColor = .clear
|
||||
window!.hidesOnDeactivate = true
|
||||
window!.delegate = self
|
||||
}
|
||||
|
||||
func makeStatusBarItem() {
|
||||
item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
item!.button!.title = "AltTab"
|
||||
item!.menu = NSMenu()
|
||||
item!.menu!.addItem(
|
||||
withTitle: "Quit \(ProcessInfo.processInfo.processName)",
|
||||
action: #selector(NSApplication.terminate(_:)),
|
||||
keyEquivalent: "q")
|
||||
}
|
||||
|
||||
func ensureAccessibilityCheckboxIsChecked() {
|
||||
if !AXIsProcessTrustedWithOptions(["AXTrustedCheckOptionPrompt": true] as CFDictionary) {
|
||||
debugPrint("Not trusted as an AX process; please authorize and re-launch")
|
||||
NSApp.terminate(self)
|
||||
}
|
||||
}
|
||||
|
||||
func computeOpenWindows() {
|
||||
openWindows.removeAll()
|
||||
for cgWindow in cgWindows() {
|
||||
let (cgMakeshiftId_, cgOwnerPid, cgId, cgTitle) = cgMakeshiftId(cgWindow)
|
||||
for axWindow in axWindows(cgOwnerPid) {
|
||||
if axMakeshiftId(axWindow).starts(with: cgMakeshiftId_) {
|
||||
openWindows.append(OpenWindow(target: axWindow, ownerPid: cgOwnerPid, cgId: cgId, cgTitle: cgTitle))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func highlightThumbnail(_ step: Int) {
|
||||
collectionView_!.selectItems(at: [IndexPath(item: selectedOpenWindow, section: 0)], scrollPosition: .top)
|
||||
collectionView_!.deselectItems(at: [IndexPath(item: cellWithStep(-step), section: 0)])
|
||||
}
|
||||
|
||||
func computeThumbnails() {
|
||||
let maxSize = NSSize(width: NSScreen.main!.frame.width * maxScreenUsage, height: NSScreen.main!.frame.height * maxScreenUsage)
|
||||
collectionView_!.setFrameSize(maxSize)
|
||||
collectionView_!.collectionViewLayout!.invalidateLayout()
|
||||
collectionView_!.reloadData()
|
||||
collectionView_!.layoutSubtreeIfNeeded()
|
||||
window!.setContentSize(NSSize(width: collectionView_!.frame.size.width + windowPadding * 2, height: collectionView_!.frame.size.height + windowPadding * 2))
|
||||
backgroundView!.setFrameSize(window!.frame.size)
|
||||
collectionView_!.setFrameOrigin(NSPoint(x: windowPadding, y: windowPadding))
|
||||
window!.center()
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
debugPrint("collectionView: count items", openWindows.count)
|
||||
return openWindows.count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||
debugPrint("collectionView: make item", indexPath.item)
|
||||
let item = collectionView.makeItem(withIdentifier: cellId, for: indexPath) as! Cell
|
||||
item.updateWithNewContent(openWindows[indexPath.item], self.focusSelectedWindow)
|
||||
return item
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
|
||||
debugPrint("collectionView: item size")
|
||||
if indexPath.item < openWindows.count {
|
||||
let (width, height) = computeDownscaledSize(openWindows[indexPath.item].thumbnail)
|
||||
return NSSize(width: CGFloat(width) + cellPadding * 2, height: CGFloat(height) + max(fontHeight, iconSize) + interItemPadding + cellPadding * 2)
|
||||
}
|
||||
return .zero
|
||||
}
|
||||
|
||||
func cellWithStep(_ step: Int) -> Int {
|
||||
return selectedOpenWindow + step < 0 ? openWindows.count - 1 : (selectedOpenWindow + step) % openWindows.count
|
||||
}
|
||||
|
||||
func selectOtherCell(_ step: Int) {
|
||||
if openWindows.count > 0 {
|
||||
selectedOpenWindow = cellWithStep(step)
|
||||
if isFirstSummon {
|
||||
isFirstSummon = false
|
||||
let workItem = DispatchWorkItem {
|
||||
self.computeOpenWindows()
|
||||
self.computeThumbnails()
|
||||
self.highlightThumbnail(step)
|
||||
self.showWindow()
|
||||
}
|
||||
workItems.append(workItem)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: workItem)
|
||||
} else {
|
||||
highlightThumbnail(step)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func currentlySelectedWindow() -> OpenWindow? {
|
||||
return openWindows.count > selectedOpenWindow ? openWindows[selectedOpenWindow] : nil
|
||||
}
|
||||
|
||||
func showWindow() {
|
||||
window!.makeKeyAndOrderFront(nil)
|
||||
NSRunningApplication.current.activate(options: [.activateIgnoringOtherApps])
|
||||
}
|
||||
|
||||
func focusSelectedWindow(_ window: OpenWindow?) {
|
||||
workItems.forEach({ $0.cancel() })
|
||||
workItems.removeAll()
|
||||
window?.focus()
|
||||
appIsBeingUsed = false
|
||||
isFirstSummon = true
|
||||
}
|
||||
|
||||
func keyDownMeta() {
|
||||
debugPrint("meta down")
|
||||
computeOpenWindows()
|
||||
selectedOpenWindow = 0
|
||||
}
|
||||
|
||||
func keyDownMetaTab() {
|
||||
debugPrint("meta+tab down")
|
||||
appIsBeingUsed = true
|
||||
selectOtherCell(1)
|
||||
}
|
||||
|
||||
func keyDownMetaShiftTab() {
|
||||
debugPrint("meta+shift+tab down")
|
||||
selectOtherCell(-1)
|
||||
}
|
||||
|
||||
func keyUpMeta() {
|
||||
debugPrint("meta up")
|
||||
if appIsBeingUsed {
|
||||
focusSelectedWindow(currentlySelectedWindow())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "16x16",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "16x16",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "32x32",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "32x32",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "512x512",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "512x512",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2019 Pontoise, Louis. All rights reserved.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,47 @@
|
||||
import Cocoa
|
||||
|
||||
func listenToGlobalKeyboardEvents(_ delegate: Application) {
|
||||
let eventMask = [CGEventType.keyDown, CGEventType.keyUp, CGEventType.flagsChanged].reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) })
|
||||
let eventTap = CGEvent.tapCreate(
|
||||
tap: .cgSessionEventTap,
|
||||
place: .headInsertEventTap,
|
||||
options: .defaultTap,
|
||||
eventsOfInterest: CGEventMask(eventMask),
|
||||
callback: { (_, _, event, delegate_) -> Unmanaged<CGEvent>? in
|
||||
let d = Unmanaged<Application>.fromOpaque(delegate_!).takeUnretainedValue()
|
||||
return keyboardHandler(event, d)
|
||||
},
|
||||
userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(delegate).toOpaque()))
|
||||
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
|
||||
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
|
||||
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
||||
CFRunLoopRun()
|
||||
}
|
||||
|
||||
func keyboardHandler(_ cgEvent: CGEvent, _ delegate: Application) -> Unmanaged<CGEvent>? {
|
||||
let event = NSEvent(cgEvent: cgEvent)!
|
||||
let keyDown = event.type == .keyDown
|
||||
let optionKeyEvent = event.keyCode == metaKey
|
||||
let tabKeyEvent = event.keyCode == tabKey
|
||||
if optionKeyEvent && event.modifiersDown([metaModifierFlag]) {
|
||||
delegate.keyDownMeta()
|
||||
} else if tabKeyEvent && event.modifiersDown([metaModifierFlag]) && keyDown {
|
||||
delegate.keyDownMetaTab()
|
||||
// focused app will not receive the event (will not press tab key in that app)
|
||||
return nil
|
||||
} else if tabKeyEvent && event.modifiersDown([metaModifierFlag, .shift]) && keyDown {
|
||||
delegate.keyDownMetaShiftTab()
|
||||
// focused app will not receive the event (will not press tab key in that app)
|
||||
return nil
|
||||
} else if optionKeyEvent && !keyDown {
|
||||
delegate.keyUpMeta()
|
||||
}
|
||||
// focused app will receive the event
|
||||
return Unmanaged.passRetained(cgEvent)
|
||||
}
|
||||
|
||||
extension NSEvent {
|
||||
func modifiersDown(_ modifiers: NSEvent.ModifierFlags) -> Bool {
|
||||
return self.modifierFlags.intersection(.deviceIndependentFlagsMask) == modifiers
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import Cocoa
|
||||
import Foundation
|
||||
|
||||
class OpenWindow {
|
||||
var target: AXUIElement?
|
||||
var ownerPid: pid_t?
|
||||
var cgId: CGWindowID
|
||||
var cgTitle: String
|
||||
lazy var thumbnail: NSImage = computeThumbnail()
|
||||
lazy var icon: NSImage = computeIcon()
|
||||
|
||||
init(target: AXUIElement?, ownerPid: pid_t?, cgId: CGWindowID, cgTitle: String) {
|
||||
self.target = target
|
||||
self.ownerPid = ownerPid
|
||||
self.cgId = cgId
|
||||
self.cgTitle = cgTitle
|
||||
}
|
||||
|
||||
func computeIcon() -> NSImage {
|
||||
return NSRunningApplication(processIdentifier: ownerPid!)!.icon!
|
||||
}
|
||||
|
||||
func computeThumbnail() -> NSImage {
|
||||
let windowImage = CGWindowListCreateImage(.null, .optionIncludingWindow, cgId, [.boundsIgnoreFraming, .bestResolution])
|
||||
return NSImage(cgImage: windowImage!, size: NSSize(width: windowImage!.width, height: windowImage!.height))
|
||||
}
|
||||
|
||||
func focus() {
|
||||
NSRunningApplication(processIdentifier: ownerPid!)!.activate(options: [.activateIgnoringOtherApps])
|
||||
AXUIElementPerformAction(target!, kAXRaiseAction as CFString)
|
||||
}
|
||||
}
|
||||
|
||||
func computeDownscaledSize(_ image: NSImage) -> (Int, Int) {
|
||||
let imageRatio = image.size.width / image.size.height
|
||||
let thumbnailWidth = Int(floor(thumbnailMaxHeight * imageRatio))
|
||||
if thumbnailWidth <= Int(thumbnailMaxWidth) {
|
||||
return (thumbnailWidth, Int(thumbnailMaxHeight))
|
||||
} else {
|
||||
return (Int(thumbnailMaxWidth), Int(floor(thumbnailMaxWidth / imageRatio)))
|
||||
}
|
||||
}
|
||||
|
||||
func cgMakeshiftId(_ window: NSDictionary) -> (String, pid_t, CGWindowID, String) {
|
||||
let cgId = window[kCGWindowNumber] as! CGWindowID
|
||||
let cgOwnerPid = window[kCGWindowOwnerPID] as! pid_t
|
||||
let cgBounds = window[kCGWindowBounds] as! [String: Int]
|
||||
let cgTitle = String(window[kCGWindowName] as! NSString)
|
||||
let cgMakeshiftId = String(cgBounds["X"]!) + String(cgBounds["Y"]!)
|
||||
+ String(cgBounds["Width"]!) + String(cgBounds["Height"]!)
|
||||
+ cgTitle
|
||||
return (cgMakeshiftId, cgOwnerPid, cgId, cgTitle)
|
||||
}
|
||||
|
||||
func axMakeshiftId(_ element: AXUIElement) -> String {
|
||||
let titleAttribute = AXUIElementCopyAttributeValue(element, kAXTitleAttribute, String.self)!
|
||||
let pos = AXValueGetValue(element, kAXPositionAttribute, NSPoint(), AXValueType.cgPoint)!
|
||||
let size = AXValueGetValue(element, kAXSizeAttribute, NSSize(), AXValueType.cgSize)!
|
||||
return String(Int(pos.x)) + String(Int(pos.y))
|
||||
+ String(Int(size.width)) + String(Int(size.height))
|
||||
+ titleAttribute
|
||||
}
|
||||
|
||||
func axWindows(_ cgOwnerPid: pid_t) -> [AXUIElement] {
|
||||
if let windows = AXUIElementCopyAttributeValue(AXUIElementCreateApplication(cgOwnerPid), kAXWindowsAttribute, [AXUIElement].self) {
|
||||
return windows.filter {
|
||||
let hasTitle = !(AXUIElementCopyAttributeValue($0, kAXTitleAttribute, String.self) ?? "").isEmpty
|
||||
return hasTitle
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
func cgWindows() -> [NSDictionary] {
|
||||
let windows = CGWindowListCopyWindowInfo([.excludeDesktopElements, .optionOnScreenOnly], kCGNullWindowID) as! [NSDictionary]
|
||||
return windows.filter {
|
||||
let isWindowNotMenubarOrOthers = $0[kCGWindowLayer] as? Int == 0
|
||||
let hasTitle = !(($0[kCGWindowName] as? String ?? "").isEmpty)
|
||||
return isWindowNotMenubarOrOthers && hasTitle
|
||||
}
|
||||
}
|
||||
|
||||
func AXUIElementCopyAttributeValue<T>(_ element: AXUIElement, _ attribute: String, _ type: T.Type) -> T? {
|
||||
var value: AnyObject?
|
||||
let result = AXUIElementCopyAttributeValue(element, attribute as CFString, &value)
|
||||
if result == .success, let typedValue = value as? T {
|
||||
return typedValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AXValueGetValue<T>(_ element: AXUIElement, _ attribute: String, _ target: T, _ type: AXValueType) -> T? {
|
||||
if let a = AXUIElementCopyAttributeValue(element, attribute, AXValue.self) {
|
||||
var value = target
|
||||
AXValueGetValue(a, type, &value)
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<false/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
After Width: | Height: | Size: 573 KiB |
|
After Width: | Height: | Size: 528 KiB |
|
After Width: | Height: | Size: 816 KiB |
|
After Width: | Height: | Size: 457 KiB |
|
After Width: | Height: | Size: 442 KiB |
|
After Width: | Height: | Size: 515 KiB |
|
After Width: | Height: | Size: 772 KiB |
|
After Width: | Height: | Size: 951 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 522 KiB |
|
After Width: | Height: | Size: 885 KiB |
|
After Width: | Height: | Size: 937 KiB |
|
After Width: | Height: | Size: 881 KiB |
|
After Width: | Height: | Size: 957 KiB |
@@ -0,0 +1,8 @@
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
autoreleasepool {
|
||||
let app = Application.shared
|
||||
app.delegate = app as? NSApplicationDelegate
|
||||
app.run()
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import Cocoa
|
||||
|
||||
class HighInterpolationImageView: NSImageView {
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
NSGraphicsContext.current!.imageInterpolation = .high
|
||||
super.draw(dirtyRect)
|
||||
}
|
||||
}
|
||||
|
||||
class Cell: NSCollectionViewItem {
|
||||
var thumbnail = NSImageView()
|
||||
var icon = NSImageView()
|
||||
var text = NSTextView()
|
||||
var openWindow: OpenWindow?
|
||||
var mouseDownCallback: ((OpenWindow) -> Void)?
|
||||
|
||||
override func mouseDown(with theEvent: NSEvent) {
|
||||
mouseDownCallback!(openWindow!)
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
let vStackView = NSStackView()
|
||||
vStackView.wantsLayer = true
|
||||
vStackView.layer!.borderWidth = cellBorderWidth
|
||||
vStackView.layer!.borderColor = .clear
|
||||
vStackView.edgeInsets = NSEdgeInsets(top: cellPadding, left: cellPadding, bottom: cellPadding, right: cellPadding)
|
||||
vStackView.orientation = .vertical
|
||||
vStackView.spacing = interItemPadding
|
||||
let hStackView = NSStackView()
|
||||
hStackView.spacing = interItemPadding
|
||||
let textStorage = NSTextStorage()
|
||||
let layoutManager = NSLayoutManager()
|
||||
textStorage.addLayoutManager(layoutManager)
|
||||
let textContainer = NSTextContainer()
|
||||
textContainer.maximumNumberOfLines = 1
|
||||
textContainer.lineFragmentPadding = 0
|
||||
layoutManager.addTextContainer(textContainer)
|
||||
text = NSTextView.init(frame: NSRect.zero, textContainer: textContainer)
|
||||
text.drawsBackground = true
|
||||
text.backgroundColor = .clear
|
||||
text.isSelectable = false
|
||||
text.isEditable = false
|
||||
text.font = font
|
||||
text.textColor = highlightColor
|
||||
let shadow = NSShadow()
|
||||
shadow.shadowColor = .darkGray
|
||||
shadow.shadowOffset = NSMakeSize(0, 0)
|
||||
shadow.shadowBlurRadius = 1
|
||||
text.shadow = shadow
|
||||
text.enabledTextCheckingTypes = 0
|
||||
let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
|
||||
paragraphStyle.lineBreakMode = .byTruncatingTail
|
||||
paragraphStyle.maximumLineHeight = fontHeight
|
||||
paragraphStyle.minimumLineHeight = fontHeight
|
||||
paragraphStyle.allowsDefaultTighteningForTruncation = false
|
||||
text.defaultParagraphStyle = paragraphStyle
|
||||
text.heightAnchor.constraint(equalToConstant: fontHeight).isActive = true
|
||||
let shadow2 = NSShadow()
|
||||
shadow2.shadowColor = .gray
|
||||
shadow2.shadowOffset = NSMakeSize(0, 0)
|
||||
shadow2.shadowBlurRadius = 1
|
||||
thumbnail.shadow = shadow2
|
||||
icon.shadow = shadow2
|
||||
hStackView.addView(icon, in: .leading)
|
||||
hStackView.addView(text, in: .leading)
|
||||
vStackView.addView(hStackView, in: .leading)
|
||||
vStackView.addView(thumbnail, in: .leading)
|
||||
view = vStackView
|
||||
}
|
||||
|
||||
override var isSelected: Bool {
|
||||
didSet {
|
||||
view.layer!.borderColor = isSelected ? highlightColor.cgColor : .clear
|
||||
}
|
||||
}
|
||||
|
||||
func updateWithNewContent(_ element: OpenWindow, _ fn: @escaping (OpenWindow) -> Void) {
|
||||
openWindow = element
|
||||
thumbnail.image = element.thumbnail
|
||||
let (width, height) = computeDownscaledSize(element.thumbnail)
|
||||
thumbnail.image!.size = NSSize(width: width, height: height)
|
||||
thumbnail.frame.size = NSSize(width: width, height: height)
|
||||
icon.image = element.icon
|
||||
icon.image!.size = NSSize(width: iconSize, height: iconSize)
|
||||
icon.frame.size = NSSize(width: iconSize, height: iconSize)
|
||||
text.string = element.cgTitle
|
||||
// workaround: setting string on NSTextView change the font (most likely a Cocoa bug)
|
||||
text.font = font
|
||||
text.textContainer!.size.width = thumbnail.frame.size.width - iconSize - interItemPadding
|
||||
mouseDownCallback = fn
|
||||
}
|
||||
}
|
||||
|
||||
class CollectionViewCenterFlowLayout: NSCollectionViewFlowLayout {
|
||||
override func layoutAttributesForElements(in rect: CGRect) -> [NSCollectionViewLayoutAttributes] {
|
||||
let attributes = super.layoutAttributesForElements(in: rect)
|
||||
if attributes.isEmpty {
|
||||
return attributes
|
||||
}
|
||||
var currentRow: [NSCollectionViewLayoutAttributes] = []
|
||||
var currentRowY = CGFloat(0)
|
||||
var currentRowWidth = CGFloat(0)
|
||||
var previousRowMaxY = CGFloat(0)
|
||||
var currentRowMaxY = CGFloat(0)
|
||||
var widestRow = CGFloat(0)
|
||||
var totalHeight = CGFloat(0)
|
||||
attributes.enumerated().forEach {
|
||||
let isNewRow = abs($1.frame.origin.y - currentRowY) > thumbnailMaxHeight
|
||||
if isNewRow {
|
||||
computeOriginXForAllItems(currentRowWidth - minimumInteritemSpacing, previousRowMaxY, currentRow)
|
||||
currentRow.removeAll()
|
||||
currentRowY = $1.frame.origin.y
|
||||
currentRowWidth = 0
|
||||
previousRowMaxY += currentRowMaxY + minimumLineSpacing
|
||||
currentRowMaxY = 0
|
||||
}
|
||||
currentRow.append($1)
|
||||
currentRowWidth += $1.frame.size.width + minimumInteritemSpacing
|
||||
widestRow = max(widestRow, currentRowWidth)
|
||||
currentRowMaxY = max(currentRowMaxY, $1.frame.size.height)
|
||||
if $0 == attributes.count - 1 {
|
||||
computeOriginXForAllItems(currentRowWidth - minimumInteritemSpacing, previousRowMaxY, currentRow)
|
||||
totalHeight = previousRowMaxY + currentRowMaxY
|
||||
}
|
||||
}
|
||||
collectionView!.setFrameSize(NSSize(width: widestRow - minimumInteritemSpacing, height: totalHeight))
|
||||
return attributes
|
||||
}
|
||||
|
||||
func computeOriginXForAllItems(_ currentRowWidth: CGFloat, _ previousRowMaxHeight: CGFloat, _ currentRow: [NSCollectionViewLayoutAttributes]) {
|
||||
var marginLeft = floor((collectionView!.frame.size.width - currentRowWidth) / 2)
|
||||
currentRow.forEach {
|
||||
$0.frame.origin.x = marginLeft
|
||||
$0.frame.origin.y = previousRowMaxHeight
|
||||
marginLeft += $0.frame.size.width + minimumInteritemSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||