5 GUIs - initial drop
Ooh LA LA ...
2
.gitignore
vendored
@@ -15,3 +15,5 @@ build
|
||||
Package.resolved
|
||||
.build
|
||||
|
||||
LLVM/llvm-objdump
|
||||
|
||||
|
||||
546
5GUIs.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,546 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
E83E32BF25220277009BE980 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E83E32BE25220277009BE980 /* Assets.xcassets */; };
|
||||
E83E32C225220277009BE980 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E83E32C125220277009BE980 /* Preview Assets.xcassets */; };
|
||||
E83E32C525220277009BE980 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E83E32C325220277009BE980 /* Main.storyboard */; };
|
||||
E8F1C3D92524BA3F000B517B /* llvm-objdump in CopyFiles */ = {isa = PBXBuildFile; fileRef = E8F1C3D82524BA3F000B517B /* llvm-objdump */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
E8F1C3F72524EAFB000B517B /* LLVM-LICENSE.TXT in Resources */ = {isa = PBXBuildFile; fileRef = E8F1C3F62524EAFB000B517B /* LLVM-LICENSE.TXT */; };
|
||||
E8F1C41C25260051000B517B /* OTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40225260051000B517B /* OTool.swift */; };
|
||||
E8F1C41D25260051000B517B /* WindowEnvironmentKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40325260051000B517B /* WindowEnvironmentKey.swift */; };
|
||||
E8F1C41E25260051000B517B /* URLItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40425260051000B517B /* URLItems.swift */; };
|
||||
E8F1C41F25260051000B517B /* ProcessHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40525260051000B517B /* ProcessHelper.swift */; };
|
||||
E8F1C42025260051000B517B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40625260051000B517B /* AppDelegate.swift */; };
|
||||
E8F1C42125260051000B517B /* SummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40825260051000B517B /* SummaryView.swift */; };
|
||||
E8F1C42225260051000B517B /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40925260051000B517B /* SpinnerView.swift */; };
|
||||
E8F1C42325260051000B517B /* PleaseDropAFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40A25260051000B517B /* PleaseDropAFileView.swift */; };
|
||||
E8F1C42425260051000B517B /* DetectionStepsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40B25260051000B517B /* DetectionStepsView.swift */; };
|
||||
E8F1C42525260051000B517B /* SorryNotAnExecutableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40C25260051000B517B /* SorryNotAnExecutableView.swift */; };
|
||||
E8F1C42625260051000B517B /* ThirdPartyLicensesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40D25260051000B517B /* ThirdPartyLicensesView.swift */; };
|
||||
E8F1C42725260051000B517B /* InfoPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40E25260051000B517B /* InfoPanel.swift */; };
|
||||
E8F1C42825260051000B517B /* DetailsPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40F25260051000B517B /* DetailsPopover.swift */; };
|
||||
E8F1C42925260051000B517B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41025260051000B517B /* ContentView.swift */; };
|
||||
E8F1C42A25260051000B517B /* MainFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41125260051000B517B /* MainFileView.swift */; };
|
||||
E8F1C42B25260051000B517B /* FileDetectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41325260051000B517B /* FileDetectionState.swift */; };
|
||||
E8F1C42C25260051000B517B /* InfoDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41425260051000B517B /* InfoDict.swift */; };
|
||||
E8F1C42D25260051000B517B /* FakeDetectionStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41525260051000B517B /* FakeDetectionStepper.swift */; };
|
||||
E8F1C42E25260051000B517B /* FakeStepConfigs.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41625260051000B517B /* FakeStepConfigs.swift */; };
|
||||
E8F1C42F25260051000B517B /* LoadBundleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41725260051000B517B /* LoadBundleImage.swift */; };
|
||||
E8F1C43025260051000B517B /* ExecutableFileTechnologyInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41825260051000B517B /* ExecutableFileTechnologyInfo.swift */; };
|
||||
E8F1C43125260051000B517B /* Windows.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41925260051000B517B /* Windows.swift */; };
|
||||
E8F1C43225260051000B517B /* WindowState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41A25260051000B517B /* WindowState.swift */; };
|
||||
E8F1C43325260051000B517B /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41B25260051000B517B /* Style.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
E8F1C3DB2524BA5C000B517B /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 6;
|
||||
files = (
|
||||
E8F1C3D92524BA3F000B517B /* llvm-objdump in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
E83E32B725220275009BE980 /* 5GUIs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = 5GUIs.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
E83E32BE25220277009BE980 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
E83E32C125220277009BE980 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
E83E32C425220277009BE980 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
E83E32C625220277009BE980 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
E83E32C725220277009BE980 /* _GUIs.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = _GUIs.entitlements; sourceTree = "<group>"; };
|
||||
E8F1C3D82524BA3F000B517B /* llvm-objdump */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = "llvm-objdump"; sourceTree = "<group>"; };
|
||||
E8F1C3F62524EAFB000B517B /* LLVM-LICENSE.TXT */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "LLVM-LICENSE.TXT"; sourceTree = "<group>"; };
|
||||
E8F1C40225260051000B517B /* OTool.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTool.swift; sourceTree = "<group>"; };
|
||||
E8F1C40325260051000B517B /* WindowEnvironmentKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowEnvironmentKey.swift; sourceTree = "<group>"; };
|
||||
E8F1C40425260051000B517B /* URLItems.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLItems.swift; sourceTree = "<group>"; };
|
||||
E8F1C40525260051000B517B /* ProcessHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessHelper.swift; sourceTree = "<group>"; };
|
||||
E8F1C40625260051000B517B /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Sources/5GUIs/AppDelegate.swift; sourceTree = SOURCE_ROOT; };
|
||||
E8F1C40825260051000B517B /* SummaryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SummaryView.swift; sourceTree = "<group>"; };
|
||||
E8F1C40925260051000B517B /* SpinnerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = "<group>"; };
|
||||
E8F1C40A25260051000B517B /* PleaseDropAFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PleaseDropAFileView.swift; sourceTree = "<group>"; };
|
||||
E8F1C40B25260051000B517B /* DetectionStepsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetectionStepsView.swift; sourceTree = "<group>"; };
|
||||
E8F1C40C25260051000B517B /* SorryNotAnExecutableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SorryNotAnExecutableView.swift; sourceTree = "<group>"; };
|
||||
E8F1C40D25260051000B517B /* ThirdPartyLicensesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThirdPartyLicensesView.swift; sourceTree = "<group>"; };
|
||||
E8F1C40E25260051000B517B /* InfoPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoPanel.swift; sourceTree = "<group>"; };
|
||||
E8F1C40F25260051000B517B /* DetailsPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsPopover.swift; sourceTree = "<group>"; };
|
||||
E8F1C41025260051000B517B /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
E8F1C41125260051000B517B /* MainFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainFileView.swift; sourceTree = "<group>"; };
|
||||
E8F1C41325260051000B517B /* FileDetectionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileDetectionState.swift; sourceTree = "<group>"; };
|
||||
E8F1C41425260051000B517B /* InfoDict.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoDict.swift; sourceTree = "<group>"; };
|
||||
E8F1C41525260051000B517B /* FakeDetectionStepper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeDetectionStepper.swift; sourceTree = "<group>"; };
|
||||
E8F1C41625260051000B517B /* FakeStepConfigs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeStepConfigs.swift; sourceTree = "<group>"; };
|
||||
E8F1C41725260051000B517B /* LoadBundleImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadBundleImage.swift; sourceTree = "<group>"; };
|
||||
E8F1C41825260051000B517B /* ExecutableFileTechnologyInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExecutableFileTechnologyInfo.swift; sourceTree = "<group>"; };
|
||||
E8F1C41925260051000B517B /* Windows.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Windows.swift; path = Sources/5GUIs/Windows.swift; sourceTree = SOURCE_ROOT; };
|
||||
E8F1C41A25260051000B517B /* WindowState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WindowState.swift; path = Sources/5GUIs/WindowState.swift; sourceTree = SOURCE_ROOT; };
|
||||
E8F1C41B25260051000B517B /* Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Style.swift; path = Sources/5GUIs/Style.swift; sourceTree = SOURCE_ROOT; };
|
||||
E8F1C437252600A9000B517B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
E8F1C438252603B5000B517B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = Sources/5GUIs/README.md; sourceTree = SOURCE_ROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
E83E32B425220275009BE980 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
E83E32AE25220275009BE980 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C437252600A9000B517B /* README.md */,
|
||||
E83E32B925220275009BE980 /* 5GUIs */,
|
||||
E83E32B825220275009BE980 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E83E32B825220275009BE980 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E83E32B725220275009BE980 /* 5GUIs.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E83E32B925220275009BE980 /* 5GUIs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C438252603B5000B517B /* README.md */,
|
||||
E8F1C40625260051000B517B /* AppDelegate.swift */,
|
||||
E8F1C41B25260051000B517B /* Style.swift */,
|
||||
E8F1C41A25260051000B517B /* WindowState.swift */,
|
||||
E8F1C41925260051000B517B /* Windows.swift */,
|
||||
E8F1C41225260051000B517B /* Model */,
|
||||
E8F1C40725260051000B517B /* Views */,
|
||||
E8F1C40125260051000B517B /* Utilities */,
|
||||
E8F1C3D72524BA3F000B517B /* LLVM */,
|
||||
E83E32BE25220277009BE980 /* Assets.xcassets */,
|
||||
E83E32C325220277009BE980 /* Main.storyboard */,
|
||||
E83E32C625220277009BE980 /* Info.plist */,
|
||||
E83E32C725220277009BE980 /* _GUIs.entitlements */,
|
||||
E83E32C025220277009BE980 /* Preview Content */,
|
||||
);
|
||||
path = 5GUIs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E83E32C025220277009BE980 /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E83E32C125220277009BE980 /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E8F1C3D72524BA3F000B517B /* LLVM */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C3F62524EAFB000B517B /* LLVM-LICENSE.TXT */,
|
||||
E8F1C3D82524BA3F000B517B /* llvm-objdump */,
|
||||
);
|
||||
name = LLVM;
|
||||
path = ../LLVM;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E8F1C40125260051000B517B /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C40225260051000B517B /* OTool.swift */,
|
||||
E8F1C40325260051000B517B /* WindowEnvironmentKey.swift */,
|
||||
E8F1C40425260051000B517B /* URLItems.swift */,
|
||||
E8F1C40525260051000B517B /* ProcessHelper.swift */,
|
||||
);
|
||||
name = Utilities;
|
||||
path = Sources/5GUIs/Utilities;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
E8F1C40725260051000B517B /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C41025260051000B517B /* ContentView.swift */,
|
||||
E8F1C43A252615B2000B517B /* Reusable */,
|
||||
E8F1C43B252615C5000B517B /* Windows */,
|
||||
);
|
||||
name = Views;
|
||||
path = Sources/5GUIs/Views;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
E8F1C41225260051000B517B /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C41325260051000B517B /* FileDetectionState.swift */,
|
||||
E8F1C41425260051000B517B /* InfoDict.swift */,
|
||||
E8F1C41525260051000B517B /* FakeDetectionStepper.swift */,
|
||||
E8F1C41625260051000B517B /* FakeStepConfigs.swift */,
|
||||
E8F1C41725260051000B517B /* LoadBundleImage.swift */,
|
||||
E8F1C41825260051000B517B /* ExecutableFileTechnologyInfo.swift */,
|
||||
);
|
||||
name = Model;
|
||||
path = Sources/5GUIs/Model;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
E8F1C43A252615B2000B517B /* Reusable */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C40925260051000B517B /* SpinnerView.swift */,
|
||||
);
|
||||
path = Reusable;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E8F1C43B252615C5000B517B /* Windows */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C43D252615DB000B517B /* MainWindow */,
|
||||
E8F1C43C252615D2000B517B /* InfoPanel */,
|
||||
E8F1C43E252615E5000B517B /* LicenseWindow */,
|
||||
);
|
||||
path = Windows;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E8F1C43C252615D2000B517B /* InfoPanel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C40E25260051000B517B /* InfoPanel.swift */,
|
||||
);
|
||||
path = InfoPanel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E8F1C43D252615DB000B517B /* MainWindow */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C40A25260051000B517B /* PleaseDropAFileView.swift */,
|
||||
E8F1C40C25260051000B517B /* SorryNotAnExecutableView.swift */,
|
||||
E8F1C41125260051000B517B /* MainFileView.swift */,
|
||||
E8F1C40825260051000B517B /* SummaryView.swift */,
|
||||
E8F1C40B25260051000B517B /* DetectionStepsView.swift */,
|
||||
E8F1C40F25260051000B517B /* DetailsPopover.swift */,
|
||||
);
|
||||
path = MainWindow;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E8F1C43E252615E5000B517B /* LicenseWindow */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E8F1C40D25260051000B517B /* ThirdPartyLicensesView.swift */,
|
||||
);
|
||||
path = LicenseWindow;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
E83E32B625220275009BE980 /* 5GUIs */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = E83E32CA25220277009BE980 /* Build configuration list for PBXNativeTarget "5GUIs" */;
|
||||
buildPhases = (
|
||||
E83E32B325220275009BE980 /* Sources */,
|
||||
E83E32B425220275009BE980 /* Frameworks */,
|
||||
E83E32B525220275009BE980 /* Resources */,
|
||||
E8F1C3DB2524BA5C000B517B /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = 5GUIs;
|
||||
productName = 5GUIs;
|
||||
productReference = E83E32B725220275009BE980 /* 5GUIs.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
E83E32AF25220275009BE980 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1200;
|
||||
LastUpgradeCheck = 1200;
|
||||
TargetAttributes = {
|
||||
E83E32B625220275009BE980 = {
|
||||
CreatedOnToolsVersion = 12.0;
|
||||
LastSwiftMigration = 1200;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = E83E32B225220275009BE980 /* Build configuration list for PBXProject "5GUIs" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = E83E32AE25220275009BE980;
|
||||
productRefGroup = E83E32B825220275009BE980 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
E83E32B625220275009BE980 /* 5GUIs */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
E83E32B525220275009BE980 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E83E32C525220277009BE980 /* Main.storyboard in Resources */,
|
||||
E8F1C3F72524EAFB000B517B /* LLVM-LICENSE.TXT in Resources */,
|
||||
E83E32C225220277009BE980 /* Preview Assets.xcassets in Resources */,
|
||||
E83E32BF25220277009BE980 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
E83E32B325220275009BE980 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E8F1C41C25260051000B517B /* OTool.swift in Sources */,
|
||||
E8F1C42E25260051000B517B /* FakeStepConfigs.swift in Sources */,
|
||||
E8F1C43225260051000B517B /* WindowState.swift in Sources */,
|
||||
E8F1C41F25260051000B517B /* ProcessHelper.swift in Sources */,
|
||||
E8F1C42925260051000B517B /* ContentView.swift in Sources */,
|
||||
E8F1C43025260051000B517B /* ExecutableFileTechnologyInfo.swift in Sources */,
|
||||
E8F1C42C25260051000B517B /* InfoDict.swift in Sources */,
|
||||
E8F1C43325260051000B517B /* Style.swift in Sources */,
|
||||
E8F1C42F25260051000B517B /* LoadBundleImage.swift in Sources */,
|
||||
E8F1C42B25260051000B517B /* FileDetectionState.swift in Sources */,
|
||||
E8F1C42825260051000B517B /* DetailsPopover.swift in Sources */,
|
||||
E8F1C42725260051000B517B /* InfoPanel.swift in Sources */,
|
||||
E8F1C41E25260051000B517B /* URLItems.swift in Sources */,
|
||||
E8F1C43125260051000B517B /* Windows.swift in Sources */,
|
||||
E8F1C42025260051000B517B /* AppDelegate.swift in Sources */,
|
||||
E8F1C41D25260051000B517B /* WindowEnvironmentKey.swift in Sources */,
|
||||
E8F1C42125260051000B517B /* SummaryView.swift in Sources */,
|
||||
E8F1C42625260051000B517B /* ThirdPartyLicensesView.swift in Sources */,
|
||||
E8F1C42D25260051000B517B /* FakeDetectionStepper.swift in Sources */,
|
||||
E8F1C42425260051000B517B /* DetectionStepsView.swift in Sources */,
|
||||
E8F1C42A25260051000B517B /* MainFileView.swift in Sources */,
|
||||
E8F1C42225260051000B517B /* SpinnerView.swift in Sources */,
|
||||
E8F1C42325260051000B517B /* PleaseDropAFileView.swift in Sources */,
|
||||
E8F1C42525260051000B517B /* SorryNotAnExecutableView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
E83E32C325220277009BE980 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
E83E32C425220277009BE980 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
E83E32C825220277009BE980 /* 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
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;
|
||||
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.15;
|
||||
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;
|
||||
};
|
||||
E83E32C925220277009BE980 /* 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
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;
|
||||
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.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
E83E32CB25220277009BE980 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = 5GUIs/_GUIs.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"5GUIs/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 4GXF3JAMM4;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = 5GUIs/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.zeezide.swift.five-guis";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
E83E32CC25220277009BE980 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = 5GUIs/_GUIs.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"5GUIs/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 4GXF3JAMM4;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = 5GUIs/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.zeezide.swift.five-guis";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
E83E32B225220275009BE980 /* Build configuration list for PBXProject "5GUIs" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
E83E32C825220277009BE980 /* Debug */,
|
||||
E83E32C925220277009BE980 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
E83E32CA25220277009BE980 /* Build configuration list for PBXNativeTarget "5GUIs" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
E83E32CB25220277009BE980 /* Debug */,
|
||||
E83E32CC25220277009BE980 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = E83E32AF25220275009BE980 /* Project object */;
|
||||
}
|
||||
7
5GUIs.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
11
5GUIs/Assets.xcassets/AccentColor.colorset/Contents.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs-1024.png
Normal file
|
After Width: | Height: | Size: 227 KiB |
BIN
5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs-128.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs-128@2x.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs-256.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs-256@2x.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs-512.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs-64.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
65
5GUIs/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "5GUIs-64.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "5GUIs-128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "5GUIs-128@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "5GUIs-256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "5GUIs-256@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "5GUIs-512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "5GUIs-1024.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
5GUIs/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
148
5GUIs/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17156"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
<scene sceneID="JPo-4y-FX3">
|
||||
<objects>
|
||||
<application id="hnw-xV-0zn" sceneMemberID="viewController">
|
||||
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
|
||||
<items>
|
||||
<menuItem title="5 GUIs" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="5 GUIs" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About 5 GUIs" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="showInfoPanel:" target="Voe-Tx-rLC" id="DYp-bc-Xqj"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||
<menuItem title="Hide 5 GUIs" keyEquivalent="h" id="Olw-nP-bQN">
|
||||
<connections>
|
||||
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show All" id="Kd2-mp-pUS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
|
||||
<menuItem title="Quit 5 GUIs" keyEquivalent="q" id="4sb-4s-VLi">
|
||||
<connections>
|
||||
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="File" id="dMs-cI-mzQ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="File" id="bib-Uj-vzu">
|
||||
<items>
|
||||
<menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
|
||||
<connections>
|
||||
<action selector="newDocument:" target="Ady-hI-5gd" id="4Si-XN-c54"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
|
||||
<connections>
|
||||
<action selector="openDocument:" target="Ady-hI-5gd" id="bVn-NM-KNZ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
|
||||
<menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
|
||||
<connections>
|
||||
<action selector="performClose:" target="Ady-hI-5gd" id="HmO-Ls-i7Q"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="5QF-Oa-p0T">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
|
||||
<items>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
|
||||
<connections>
|
||||
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
|
||||
<connections>
|
||||
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="aUF-d1-5bR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||
<items>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="Ady-hI-5gd" id="VwT-WD-YPe"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Zoom" id="R4o-n2-Eq4">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="Ady-hI-5gd" id="DRN-fu-gQh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Help" id="wpr-3q-Mcd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
|
||||
<items>
|
||||
<menuItem title="5 GUIs Help" keyEquivalent="?" id="FKE-Sm-Kum">
|
||||
<connections>
|
||||
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||
</connections>
|
||||
</application>
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="_GUIs" customModuleProvider="target"/>
|
||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="75" y="0.0"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
30
5GUIs/Info.plist
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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>5 GUIs</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
10
5GUIs/_GUIs.entitlements
Normal file
@@ -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>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
279
LLVM/LLVM-LICENSE.TXT
Normal file
@@ -0,0 +1,279 @@
|
||||
==============================================================================
|
||||
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
|
||||
==============================================================================
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
---- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
==============================================================================
|
||||
Software from third parties included in the LLVM Project:
|
||||
==============================================================================
|
||||
The LLVM Project contains third party software which is under different license
|
||||
terms. All such code will be identified clearly using at least one of two
|
||||
mechanisms:
|
||||
1) It will be in a separate directory tree with its own `LICENSE.txt` or
|
||||
`LICENSE` file at the top containing the specific license and restrictions
|
||||
which apply to that software, or
|
||||
2) It will contain specific license and restriction terms at the top of every
|
||||
file.
|
||||
|
||||
==============================================================================
|
||||
Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
|
||||
==============================================================================
|
||||
University of Illinois/NCSA
|
||||
Open Source License
|
||||
|
||||
Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign.
|
||||
All rights reserved.
|
||||
|
||||
Developed by:
|
||||
|
||||
LLVM Team
|
||||
|
||||
University of Illinois at Urbana-Champaign
|
||||
|
||||
http://llvm.org
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal with
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimers.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimers in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names of the LLVM Team, University of Illinois at
|
||||
Urbana-Champaign, nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this Software without specific
|
||||
prior written permission.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||
SOFTWARE.
|
||||
|
||||
52
README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
<h2>5 GUIs
|
||||
<img src="5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs- 128.png"
|
||||
align="right" width="128" height="128" />
|
||||
</h2>
|
||||
|
||||
... the app for the [tweet](https://twitter.com/jckarter/status/1310412969289773056):
|
||||
|
||||
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">With its eclectic mix of AppKit, Catalyst, iOS, SwiftUI, and web apps, macOS should consider rebranding to “Five GUIs”</p>— Joe Groff (@jckarter) <a href="https://twitter.com/jckarter/status/1310412969289773056?ref_src=twsrc%5Etfw">September 28, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||
|
||||
GUI is an abbreviation for [Graphical User Interface](https://en.wikipedia.org/wiki/Graphical_user_interface).
|
||||
|
||||
|
||||
### How it works:
|
||||
|
||||
5 GUIs grabs some information from the app bundle.
|
||||
It then uses LLVM's objdump to check what libraries the app links,
|
||||
e.g. Electron or UIKit, to figure out what technology is being used.
|
||||
|
||||
5 GUIs itself is a SwiftUI application.
|
||||
|
||||
|
||||
### Idea and Implementation
|
||||
|
||||
I had the idea for this kind of app for quite some time, but when @jckarter
|
||||
tweeted the proper name for this ★ "5 GUIs" ★, it finally had to be done.
|
||||
|
||||
This is a quick hack, scrambled together in about 2 days.
|
||||
The source is not "nice" at all, don't use it as a proper example 🙈
|
||||
PRs with cleanups are warmly welcome.
|
||||
|
||||
|
||||
### Help wanted!
|
||||
|
||||
All improvements are very welcome, but most of all this app could use better
|
||||
design.
|
||||
SwiftUI gives you something OKayish looking out of the box, but if someone
|
||||
has the time to add some fancy animations,
|
||||
better colors, iconography and styling,
|
||||
that would be *very* welcome!
|
||||
|
||||
|
||||
### 3rd Party Software Used
|
||||
|
||||
- LLVM objdump: [license](LLVM/LLVM-LICENSE.TXT)
|
||||
|
||||
|
||||
### Who
|
||||
|
||||
**5 GUIs** is brought to you by
|
||||
[ZeeZide](http://zeezide.de).
|
||||
We like feedback, GitHub stars, cool contract work,
|
||||
presumably any form of praise you can think of.
|
||||
43
Sources/5GUIs/AppDelegate.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import SwiftUI
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
let window = makeAppWindow(ContentView())
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
@IBAction func newDocument(_ sender: Any?) {
|
||||
let window = makeAppWindow(ContentView())
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
@IBAction func openDocument(_ sender: Any?) {
|
||||
let panel = makeOpenPanel()
|
||||
panel.begin { response in
|
||||
guard response == .OK else { return }
|
||||
|
||||
for url in panel.urls {
|
||||
let view = ContentView()
|
||||
let window = makeAppWindow(view)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
view.loadURL(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var infoPanel = makeInfoPanel(InfoPanel())
|
||||
|
||||
@IBAction func showInfoPanel(_ sender: Any?) {
|
||||
infoPanel.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
}
|
||||
77
Sources/5GUIs/Model/ExecutableFileTechnologyInfo.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// ExecutableFileTechnologyInfo.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import struct Foundation.URL
|
||||
import struct SwiftUI.Image
|
||||
|
||||
struct ExecutableFileTechnologyInfo: Equatable {
|
||||
|
||||
let fileURL : URL
|
||||
|
||||
var infoDictionary : InfoDict?
|
||||
var executableURL : URL?
|
||||
var receiptURL : URL?
|
||||
var appImage : Image?
|
||||
var dependencies = [ String ]()
|
||||
|
||||
// TODO: Also scan embedded apps and plugins and keep them as a nested
|
||||
// array in here.
|
||||
var embeddedExecutables = [ ExecutableFileTechnologyInfo ]()
|
||||
|
||||
struct DetectedTechnologies: OptionSet {
|
||||
let rawValue : UInt64
|
||||
|
||||
static let electron = DetectedTechnologies(rawValue: 1 << 1)
|
||||
static let catalyst = DetectedTechnologies(rawValue: 1 << 2)
|
||||
static let swiftui = DetectedTechnologies(rawValue: 1 << 3)
|
||||
static let uikit = DetectedTechnologies(rawValue: 1 << 4)
|
||||
static let appkit = DetectedTechnologies(rawValue: 1 << 5)
|
||||
|
||||
static let objc = DetectedTechnologies(rawValue: 1 << 10)
|
||||
static let swift = DetectedTechnologies(rawValue: 1 << 11)
|
||||
static let cplusplus = DetectedTechnologies(rawValue: 1 << 12)
|
||||
static let java = DetectedTechnologies(rawValue: 1 << 14)
|
||||
}
|
||||
var detectedTechnologies : DetectedTechnologies = []
|
||||
}
|
||||
|
||||
extension ExecutableFileTechnologyInfo.DetectedTechnologies {
|
||||
|
||||
mutating func scanDependencies(_ dependencies: [ String ]) {
|
||||
for dep in dependencies {
|
||||
func check(_ option: ExecutableFileTechnologyInfo.DetectedTechnologies,
|
||||
_ needle: String) -> Bool
|
||||
{
|
||||
guard !contains(option) else { return false } // scanned already
|
||||
guard dep.contains(needle) else { return false }
|
||||
self.insert(option)
|
||||
return true
|
||||
}
|
||||
|
||||
if check(.electron, "Electron") { continue }
|
||||
if check(.catalyst, "UIKitMacHelper") { continue }
|
||||
if check(.appkit, "AppKit.framework") { continue }
|
||||
if check(.swiftui, "SwiftUI.framework") { continue }
|
||||
if check(.uikit, "UIKit.framework") { continue }
|
||||
|
||||
if check(.cplusplus, "libc++") { continue }
|
||||
if check(.objc, "libobjc") { continue }
|
||||
if check(.swift, "libswiftCore") { continue }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ExecutableFileTechnologyInfo {
|
||||
// View layer stuff, doesn't really belong here
|
||||
|
||||
var appName : String {
|
||||
infoDictionary?.displayName
|
||||
?? infoDictionary?.name
|
||||
?? executableURL?.lastPathComponent
|
||||
?? "???"
|
||||
}
|
||||
}
|
||||
118
Sources/5GUIs/Model/FakeDetectionStepper.swift
Normal file
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// FakeDetectionStepper.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import AppKit
|
||||
|
||||
/**
|
||||
* This is the result of a technology analysis.
|
||||
*/
|
||||
struct FakeStep: Identifiable {
|
||||
var id : Int { config.id }
|
||||
let config : FakeStepConfig
|
||||
var state : Bool
|
||||
}
|
||||
|
||||
/**
|
||||
* A "ViewModel" object which steps through a set of FakeStep's,
|
||||
* aka features we detect (well, detected already, this is just
|
||||
* a fake progress emulator).
|
||||
*/
|
||||
final class FakeDetectionStepper: ObservableObject {
|
||||
|
||||
/**
|
||||
* Wrap a fake step one more time - this is necessary to fake the
|
||||
* "processing" phase.
|
||||
*/
|
||||
struct ActiveStep: Identifiable {
|
||||
// This one is faking the 'pending' state
|
||||
|
||||
var id : Int { step.id }
|
||||
var config : FakeStepConfig { step.config }
|
||||
|
||||
let step : FakeStep
|
||||
var pending : Bool
|
||||
|
||||
var state : Bool? { pending ? nil : step.state }
|
||||
|
||||
// Doesn't really belong here, but well:
|
||||
|
||||
var title : String {
|
||||
switch state {
|
||||
case .none: return config.runTitle
|
||||
case .some(true) : return config.positiveTitle
|
||||
case .some(false) : return config.negativeTitle
|
||||
}
|
||||
}
|
||||
var checkmark: String {
|
||||
switch state {
|
||||
case .none: return ""
|
||||
case .some(true) : return config.positiveCheckmark
|
||||
case .some(false) : return config.negativeCheckmark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Seconds, we take a random value in this range to determine the fake
|
||||
/// progress delay.
|
||||
let stepRange = 0.3...1.2
|
||||
|
||||
private var nextDelay: DispatchTimeInterval {
|
||||
return .milliseconds(Int(TimeInterval.random(in: stepRange) * 1000.0))
|
||||
}
|
||||
|
||||
private var allSteps = [ FakeStep ]()
|
||||
|
||||
@Published var activeSteps = [ ActiveStep ]()
|
||||
|
||||
var isDone : Bool {
|
||||
if activeSteps.count < allSteps.count { return false }
|
||||
if activeSteps.count > allSteps.count { return true }
|
||||
return !(activeSteps.last?.pending ?? false)
|
||||
}
|
||||
|
||||
deinit {
|
||||
stop()
|
||||
}
|
||||
func stop() {
|
||||
}
|
||||
func suspend() {
|
||||
stop()
|
||||
activeSteps = []
|
||||
allSteps = []
|
||||
}
|
||||
|
||||
func resume(with steps: [ FakeStep ]) {
|
||||
allSteps = steps
|
||||
activeSteps = []
|
||||
|
||||
self.NeXTstep()
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + nextDelay) {
|
||||
self.NeXTstep()
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleOpenStep() {
|
||||
guard !isDone else { stop(); return }
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + nextDelay) {
|
||||
self.NeXTstep()
|
||||
}
|
||||
}
|
||||
|
||||
private func NeXTstep() {
|
||||
defer { scheduleOpenStep() }
|
||||
|
||||
if activeSteps.last?.pending ?? false {
|
||||
activeSteps[activeSteps.count - 1].pending = false
|
||||
return
|
||||
}
|
||||
|
||||
guard activeSteps.count < allSteps.count else { return stop() }
|
||||
activeSteps.append(.init(step: allSteps[activeSteps.count], pending: true))
|
||||
}
|
||||
}
|
||||
68
Sources/5GUIs/Model/FakeStepConfigs.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// FakeStepConfigs.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
/**
|
||||
* The data for the configurations which show up as badges in the UI,
|
||||
* i.e. the 5 features (GUI frameworks) we test.
|
||||
*
|
||||
* Note that there are also `FakeStep`s, which also contain the info
|
||||
* whether or not the feature is available or not.
|
||||
*/
|
||||
struct FakeStepConfig : Equatable, Identifiable {
|
||||
|
||||
let id : Int
|
||||
let runTitle : String
|
||||
let positiveTitle : String
|
||||
let positiveCheckmark : String
|
||||
let negativeTitle : String
|
||||
let negativeCheckmark : String
|
||||
|
||||
static let electron = FakeStepConfig(
|
||||
id : 1,
|
||||
runTitle : "Checking for Electron …",
|
||||
positiveTitle : "App is ⚛️ Electronized! The Web is going to take over!",
|
||||
positiveCheckmark : "🙈",
|
||||
negativeTitle : "No ⚛️ Electrons detected, GPU & RAM are secure.",
|
||||
negativeCheckmark : "✅"
|
||||
)
|
||||
static let catalyst = FakeStepConfig(
|
||||
id : 2,
|
||||
runTitle : "Catalyzed? …",
|
||||
positiveTitle : "Uses macOS 🧪 Catalyst, don't resize windows!",
|
||||
positiveCheckmark : "🙉",
|
||||
negativeTitle : "No 🧪 Catalysts detected. Fast windows resizing.",
|
||||
negativeCheckmark : "✅"
|
||||
)
|
||||
static let swiftUI = FakeStepConfig(
|
||||
id : 3,
|
||||
runTitle : "Maybe declarative? …",
|
||||
positiveTitle : "App is using SwiftUI, that can be ❡ declared.",
|
||||
positiveCheckmark : "🙊",
|
||||
negativeTitle : "Nothing seems to be “declared” ❡ No SwiftUI in use.",
|
||||
negativeCheckmark : "❌"
|
||||
)
|
||||
static let phone = FakeStepConfig(
|
||||
id : 4,
|
||||
runTitle : "Possibly an iPhone application? …",
|
||||
positiveTitle : "This looks like an 📱 iPhone or iPad app!",
|
||||
positiveCheckmark : "📱",
|
||||
negativeTitle : "Not an 📱 iPhone app. Those don't belong here.",
|
||||
negativeCheckmark : "✅"
|
||||
)
|
||||
static let appKit = FakeStepConfig(
|
||||
id : 5,
|
||||
runTitle : "Checking for old-school AppKit …",
|
||||
positiveTitle : "What to expect, indeed this app uses 🖥 AppKit!",
|
||||
positiveCheckmark : "👨🏽🦳",
|
||||
negativeTitle : "No 👨🏽🦳 AppKit usage to be found? 🤔",
|
||||
negativeCheckmark : "❌"
|
||||
)
|
||||
|
||||
static let all : [ FakeStepConfig ] = [
|
||||
.electron, .catalyst, .swiftUI, .phone, .appKit
|
||||
]
|
||||
}
|
||||
219
Sources/5GUIs/Model/FileDetectionState.swift
Normal file
@@ -0,0 +1,219 @@
|
||||
//
|
||||
// FileDetectionState.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
protocol FileDetectionStateDelegate: AnyObject {
|
||||
func detectionStateDidChange(_ state: FileDetectionState)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main "operation" object which runs from a background queue and
|
||||
* collects all the info we want.
|
||||
*/
|
||||
final class FileDetectionState: ObservableObject {
|
||||
// FIXME: this is more like a 'load operation'
|
||||
// TBD: if this goes away, we could keep a global URL => State mapping
|
||||
|
||||
weak var delegate : FileDetectionStateDelegate?
|
||||
|
||||
enum State: Equatable {
|
||||
case processing
|
||||
case failedToOpen(Swift.Error?)
|
||||
case notAnApplication
|
||||
case finished
|
||||
static func == (lhs: State, rhs: State) -> Bool {
|
||||
switch ( lhs, rhs ) {
|
||||
case ( .processing , .processing ): return true
|
||||
case ( .failedToOpen , .failedToOpen ): return true
|
||||
case ( .notAnApplication , .notAnApplication ): return true
|
||||
case ( .finished , .finished ): return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fm = FileManager.default
|
||||
|
||||
@Published var state = State.processing {
|
||||
didSet {
|
||||
delegate?.detectionStateDidChange(self)
|
||||
}
|
||||
}
|
||||
@Published var info : ExecutableFileTechnologyInfo {
|
||||
didSet {
|
||||
delegate?.detectionStateDidChange(self)
|
||||
}
|
||||
}
|
||||
@Published var otoolAvailable = true
|
||||
|
||||
var url : URL { info.fileURL }
|
||||
|
||||
init(_ url: URL) {
|
||||
self.info = ExecutableFileTechnologyInfo(fileURL: url)
|
||||
}
|
||||
func resume() {
|
||||
DispatchQueue.global().async {
|
||||
self.startWork()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Just some threadsafe helpers ...
|
||||
|
||||
private func apply(_ block: @escaping () -> Void) {
|
||||
RunLoop.main.perform(block)
|
||||
}
|
||||
private func apply<V>(_ keyPath:
|
||||
ReferenceWritableKeyPath<FileDetectionState, V>,
|
||||
_ value: V)
|
||||
{
|
||||
apply {
|
||||
self[keyPath: keyPath] = value
|
||||
}
|
||||
}
|
||||
private func applyState(_ state: State) { // Q: Any
|
||||
apply(\.state, state)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Main Entry
|
||||
|
||||
private func startWork() { // Q: background
|
||||
|
||||
var isDir : ObjCBool = false
|
||||
guard fm.fileExists(atPath: url.path, isDirectory: &isDir) else {
|
||||
return applyState(.failedToOpen(nil))
|
||||
}
|
||||
|
||||
if isDir.boolValue {
|
||||
processWrapper(url)
|
||||
}
|
||||
else {
|
||||
processFile(url)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Workers
|
||||
|
||||
private func processFile(_ url: URL) {
|
||||
guard fm.isExecutableFile(atPath: url.path) else {
|
||||
return applyState(.notAnApplication)
|
||||
}
|
||||
|
||||
// TODO: grab Info.plist embedded in Mach-O
|
||||
|
||||
apply(\.info.executableURL, url)
|
||||
|
||||
processExecutable(url)
|
||||
|
||||
applyState(.finished)
|
||||
}
|
||||
|
||||
private func processWrapper(_ url: URL) { // Q: Any
|
||||
guard let bundle = Bundle(url: url) else {
|
||||
print("could not open bundle:", url)
|
||||
return applyState(.failedToOpen(nil))
|
||||
}
|
||||
|
||||
let info = InfoDict(bundle.infoDictionary ?? [:])
|
||||
|
||||
guard let executableURL = bundle.executableURL else {
|
||||
print("no executable in bundle:", bundle)
|
||||
return applyState(.notAnApplication)
|
||||
}
|
||||
let receiptURL = bundle.appStoreReceiptURL
|
||||
|
||||
apply {
|
||||
self.info.executableURL = executableURL
|
||||
self.info.receiptURL = receiptURL
|
||||
self.info.infoDictionary = info
|
||||
}
|
||||
|
||||
let image = loadImage(in: info, bundle: bundle)
|
||||
apply(\.info.appImage, image)
|
||||
|
||||
processExecutable(executableURL)
|
||||
|
||||
processDirectoryContents(url)
|
||||
|
||||
self.applyState(.finished)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Individual Workers
|
||||
|
||||
private func processExecutable(_ executableURL: URL) { // Q: Any
|
||||
do {
|
||||
let dependencies = try otool(executableURL)
|
||||
|
||||
// scan in this bg thread
|
||||
var detectedFeatures = ExecutableFileTechnologyInfo.DetectedTechnologies()
|
||||
detectedFeatures.scanDependencies(dependencies)
|
||||
|
||||
apply {
|
||||
self.otoolAvailable = true
|
||||
self.info.dependencies = dependencies
|
||||
self.info.detectedTechnologies.formUnion(detectedFeatures)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
print("Could not invoke OTool:", error)
|
||||
apply(\.otoolAvailable, false)
|
||||
}
|
||||
}
|
||||
|
||||
private func processDirectoryContents(_ url: URL) { // Q: Any
|
||||
var detectedFeatures = ExecutableFileTechnologyInfo.DetectedTechnologies()
|
||||
let contents = url.appendingPathComponent("Contents")
|
||||
|
||||
// Charles & Eclipse
|
||||
for pc in [ "Java", "Eclipse" ] {
|
||||
let suburl = contents.appendingPathComponent(pc)
|
||||
if fm.fileExists(atPath: suburl.path) {
|
||||
detectedFeatures.insert(.java)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
do { // Electron apps seem to have this ...
|
||||
let suburl = contents.appendingPathComponent("Resources/app.asar")
|
||||
if fm.fileExists(atPath: suburl.path) {
|
||||
detectedFeatures.insert(.electron)
|
||||
}
|
||||
}
|
||||
|
||||
if !detectedFeatures.isEmpty {
|
||||
apply {
|
||||
self.info.detectedTechnologies.formUnion(detectedFeatures)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Results
|
||||
|
||||
// Our "5 GUIs"
|
||||
var analysisResults : [ FakeStep ] {
|
||||
func make(_ feature : ExecutableFileTechnologyInfo.DetectedTechnologies,
|
||||
_ config : FakeStepConfig) -> FakeStep
|
||||
{
|
||||
.init(config: config, state: info.detectedTechnologies.contains(feature))
|
||||
}
|
||||
|
||||
let isPhone = info.detectedTechnologies.contains(.uikit)
|
||||
&& !(info.detectedTechnologies.contains(.catalyst))
|
||||
|
||||
return [
|
||||
make(.electron, .electron),
|
||||
make(.catalyst, .catalyst),
|
||||
make(.swiftui, .swiftUI),
|
||||
.init(config: .phone, state: isPhone),
|
||||
make(.appkit, .appKit) // TBD: only report if others don't match?
|
||||
]
|
||||
}
|
||||
}
|
||||
65
Sources/5GUIs/Model/InfoDict.swift
Normal file
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// InfoDict.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
struct InfoDict: Equatable {
|
||||
|
||||
let id : String? // com.apple.Safari
|
||||
let name : String? // Safari
|
||||
let displayName : String? // Safari
|
||||
let info : String?
|
||||
let version : String?
|
||||
let shortVersion : String? // 14.0
|
||||
let applicationCategory : String?
|
||||
let supportedPlatforms : [ String ] // MacOSX
|
||||
let minimumSystemVersion : String?
|
||||
let appleScriptEnabled : Bool
|
||||
|
||||
let iconName : String? // AppIcon
|
||||
let iconFile : String? // AppIcon
|
||||
|
||||
let executable : String? // Safari
|
||||
|
||||
// TODO: services?
|
||||
// CFBundleURLTypes
|
||||
// CFBundleDocumentTypes
|
||||
|
||||
init(_ dictionary: [ String : Any ]) {
|
||||
func S(_ key: String) -> String? {
|
||||
guard let s = dictionary[key] as? String else { return nil }
|
||||
return s.isEmpty ? nil : s
|
||||
}
|
||||
func B(_ key: String) -> Bool {
|
||||
guard let v = dictionary[key] else { return false }
|
||||
if let b = v as? Bool { return b }
|
||||
if let i = v as? Int { return i != 0 }
|
||||
if let s = (v as? String)?.lowercased() {
|
||||
if s == "no" || s == "false" { return false }
|
||||
return !s.isEmpty
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
id = S("CFBundleIdentifier")
|
||||
name = S("CFBundleName")
|
||||
info = S("CFBundleGetInfoString")
|
||||
displayName = S("CFBundleDisplayName")
|
||||
version = S("CFBundleVersion")
|
||||
shortVersion = S("CFBundleShortVersionString")
|
||||
minimumSystemVersion = S("LSMinimumSystemVersion")
|
||||
applicationCategory = S("LSApplicationCategoryType")
|
||||
|
||||
iconName = S("CFBundleIconName")
|
||||
iconFile = S("CFBundleIconFile")
|
||||
|
||||
executable = S("CFBundleExecutable")
|
||||
|
||||
appleScriptEnabled = B("NSAppleScriptEnabled")
|
||||
|
||||
supportedPlatforms = dictionary["CFBundleSupportedPlatforms"] as? [ String ]
|
||||
?? []
|
||||
}
|
||||
}
|
||||
51
Sources/5GUIs/Model/LoadBundleImage.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// LoadBundleImage.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import class Foundation.Bundle
|
||||
import class AppKit.NSImage
|
||||
import class AppKit.NSWorkspace
|
||||
import struct SwiftUI.Image
|
||||
|
||||
/**
|
||||
* Try to load the image contained in an app bundle.
|
||||
* The info dict contains the name and/or location.
|
||||
*
|
||||
* If no image could be found, we fall back to what NSWorkspace provides
|
||||
* for the URL. (which seems to be a little small?)
|
||||
*/
|
||||
func loadImage(in info: InfoDict, bundle: Bundle) -> Image {
|
||||
let bundleImage : Image? = {
|
||||
// Note: `Image(name, bundle:)` is lazy.
|
||||
if let name = info.iconName,
|
||||
let nsImage = bundle.image(forResource: name)
|
||||
{
|
||||
return Image(nsImage: nsImage)
|
||||
}
|
||||
guard let iconFile = info.iconFile else { // e.g. helper apps
|
||||
print("WARN: No image set?!"); return nil
|
||||
}
|
||||
if let nsImage = bundle.image(forResource: iconFile) { // TimeMachine
|
||||
return Image(nsImage: nsImage)
|
||||
}
|
||||
|
||||
guard let path = bundle.path(forResource: iconFile, ofType: nil) else {
|
||||
print("ERROR: did not find:", iconFile); return nil
|
||||
}
|
||||
guard let nsImage = NSImage(contentsOfFile: path) else {
|
||||
print("ERROR: could not load image:", path); return nil
|
||||
}
|
||||
return Image(nsImage: nsImage)
|
||||
}()
|
||||
|
||||
if let image = bundleImage {
|
||||
return image
|
||||
}
|
||||
|
||||
// those are pretty small?
|
||||
return Image(nsImage: NSWorkspace.shared.icon(forFile: bundle.bundlePath))
|
||||
}
|
||||
|
||||
44
Sources/5GUIs/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
<h2>5 GUIs Sources
|
||||
<img src="5GUIs/Assets.xcassets/AppIcon.appiconset/5GUIs- 128.png"
|
||||
align="right" width="128" height="128" />
|
||||
</h2>
|
||||
|
||||
Reminder: This is a quick 2-day hack, not a beautiful and cleaned up thing ...
|
||||
Cleanup PRs are welcome!
|
||||
|
||||
The app is a macOS SwiftUI v1 app, built on macOS 10.15.
|
||||
Means: It uses an AppDelegate and creates the windows using AppKit.
|
||||
The default storyboard is used for menus.
|
||||
|
||||
The state of a main window is represented in that `WindowState`
|
||||
ObservableObject. It is the main driver.
|
||||
This hooks into `FileDetectionState`, which is actually more like an `Operation`
|
||||
object. TBF. Some states are dupe.
|
||||
|
||||
The `Model` directory contains a wild mixture of actual models, and what one might
|
||||
call a ViewModel.
|
||||
|
||||
The `Views` directory contains all the SwiftUI Views.
|
||||
Some more generic, but mostly just hacked together to get the app out of the door.
|
||||
|
||||
`Utilities` contains, you can guess it.
|
||||
|
||||
|
||||
### UI
|
||||
|
||||
- there is a spinner, but it is not really necessary, a detection runs very fast ...
|
||||
- the badges which come up in a progress style manner are just fake, with their own
|
||||
fake progress model and observable driver object :-)
|
||||
|
||||
|
||||
### Contact
|
||||
|
||||
Any questions? Just ask! :-)
|
||||
|
||||
|
||||
### Who
|
||||
|
||||
**5 GUIs** is brought to you by
|
||||
[ZeeZide](http://zeezide.de).
|
||||
We like feedback, GitHub stars, cool contract work,
|
||||
presumably any form of praise you can think of.
|
||||
37
Sources/5GUIs/Style.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Style.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct Style {
|
||||
|
||||
let textColor = Color.white
|
||||
|
||||
// 0x0068DA (Shrugs Website)
|
||||
let lightShrugsBlue = Color(red: 0, green: 104/255, blue: 218/255)
|
||||
let darkShrugsBlue = Color(red: 0, green: 37/255, blue: 77/255)
|
||||
// 0x00254D vs .black
|
||||
|
||||
var baseGradient : Gradient {
|
||||
.init(colors: [
|
||||
lightShrugsBlue,
|
||||
darkShrugsBlue,
|
||||
])
|
||||
}
|
||||
|
||||
var baseFillGradient : LinearGradient {
|
||||
LinearGradient(gradient: baseGradient,
|
||||
startPoint: .top, endPoint: .bottom)
|
||||
}
|
||||
var hoverFillGradient : LinearGradient {
|
||||
LinearGradient(gradient: baseGradient,
|
||||
startPoint : UnitPoint(x: 0, y: 0.5),
|
||||
endPoint : UnitPoint(x: 1, y: 1))
|
||||
}
|
||||
|
||||
}
|
||||
let style = Style()
|
||||
140
Sources/5GUIs/Utilities/OTool.swift
Normal file
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// OTool.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum OToolError: Swift.Error {
|
||||
case xCodeMissing
|
||||
case objdumpMissing
|
||||
case invocationFailed(status: Int)
|
||||
}
|
||||
|
||||
/**
|
||||
* A compiled LLVM objdump can be bundled in the app. Use a separate Copy build
|
||||
* phase with "Executables" as the target and make sure the binary is signed.
|
||||
*/
|
||||
fileprivate let embeddedObjdump : URL = {
|
||||
return Bundle.main.bundleURL
|
||||
.appendingPathComponent("Contents")
|
||||
.appendingPathComponent("MacOS")
|
||||
.appendingPathComponent("llvm-objdump")
|
||||
}()
|
||||
|
||||
func otool(_ url: URL) throws -> [ String ] {
|
||||
// xcrun doesn't work in the Sandbox but calling Xcode's objdump DOES work,
|
||||
// on 10.15. On macOS Catalyst it doesn't.
|
||||
let fm = FileManager.default
|
||||
|
||||
let objdump : String
|
||||
if fm.isExecutableFile(atPath: embeddedObjdump.path) {
|
||||
objdump = embeddedObjdump.path
|
||||
}
|
||||
else {
|
||||
let xCodePath = "/Applications/Xcode.app"
|
||||
objdump = "\(xCodePath)/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/objdump"
|
||||
// We don't require objdump, we can also derive info from the general
|
||||
// contents.
|
||||
}
|
||||
|
||||
var dependencies = Set<String>()
|
||||
dependencies.reserveCapacity(32)
|
||||
try run(objdump: objdump, against: url, maxNesting: 3, into: &dependencies)
|
||||
return dependencies.sorted()
|
||||
}
|
||||
|
||||
private func run(objdump: String, against url: URL,
|
||||
nesting: Int = 1, maxNesting: Int = 4,
|
||||
into result: inout Set<String>) throws
|
||||
{
|
||||
guard nesting <= maxNesting else { return }
|
||||
|
||||
let scannedDeps = result
|
||||
|
||||
let directDeps = try run(objdump: objdump, against: url)
|
||||
result.formUnion(directDeps)
|
||||
guard nesting + 1 <= maxNesting else { return }
|
||||
|
||||
let baseURL = url
|
||||
.deletingLastPathComponent() // Slack
|
||||
.deletingLastPathComponent() // MacOS
|
||||
|
||||
for dep in directDeps {
|
||||
guard !scannedDeps.contains(dep) else { continue } // processed already
|
||||
|
||||
let dependencyURL : URL
|
||||
|
||||
func checkRelname<S: StringProtocol>(_ relname: S) -> URL? {
|
||||
let fw = baseURL.appendingPathComponent("Frameworks")
|
||||
let fwDep = fw.appendingPathComponent(String(relname))
|
||||
guard FileManager.default.fileExists(atPath: fwDep.path) else {
|
||||
print("did not find @ dep:",
|
||||
"\n dep: ", dep,
|
||||
"\n in: ", url.path,
|
||||
"\n base:", baseURL.path)
|
||||
return nil
|
||||
}
|
||||
return fwDep
|
||||
}
|
||||
|
||||
// Hm, quite hacky :-)
|
||||
if dep.hasPrefix("@rpath/") {
|
||||
guard let url = checkRelname(dep.dropFirst(7)) else { continue }
|
||||
dependencyURL = url
|
||||
}
|
||||
else if dep.hasPrefix("@executable_path/../Frameworks/") {
|
||||
guard let url = checkRelname(dep.dropFirst(31)) else { continue }
|
||||
dependencyURL = url
|
||||
}
|
||||
else if dep.hasPrefix("@loader_path/../Frameworks/") {
|
||||
guard let url = checkRelname(dep.dropFirst(27)) else { continue }
|
||||
dependencyURL = url
|
||||
}
|
||||
else if dep.hasPrefix("@") {
|
||||
// e.g. @rpath/libswiftos.dylib
|
||||
print("unprocessed dependency @:", dep)
|
||||
continue
|
||||
}
|
||||
else {
|
||||
dependencyURL = URL(fileURLWithPath: dep, relativeTo: url)
|
||||
}
|
||||
|
||||
do {
|
||||
try run(objdump: objdump, against: dependencyURL,
|
||||
nesting: nesting + 1, maxNesting: maxNesting,
|
||||
into: &result)
|
||||
}
|
||||
catch {
|
||||
print("ERROR: ignoring nested error:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func run(objdump: String, against url: URL) throws -> [ String ] {
|
||||
// bash escaping
|
||||
let result = Process.launch(at: objdump,
|
||||
with: [ "-macho", "--dylibs-used", "'\(url.path)'" ])
|
||||
guard result.status == 0 else {
|
||||
print("ERROR: objdump result:", result)
|
||||
throw OToolError.invocationFailed(status: result.status)
|
||||
}
|
||||
|
||||
// Example:
|
||||
// /System/Library/PrivateFrameworks/Safari.framework/Versions/A/Safari (compatibility version 528.0.0, current version 610.1.28
|
||||
// We parse:
|
||||
// - deps must start with "\t" (we also accept " ")
|
||||
// - extra version info in () is cut off
|
||||
return result.stdout
|
||||
.split(separator: "\n", maxSplits: 1000, omittingEmptySubsequences: true)
|
||||
.lazy
|
||||
.filter { $0.hasPrefix(" ") || $0.hasPrefix("\t") }
|
||||
.map { ( s : Substring ) -> Substring in
|
||||
guard let idx = s.lastIndex(of: "(") else { return s }
|
||||
return s[..<idx]
|
||||
}
|
||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { !$0.isEmpty }
|
||||
}
|
||||
116
Sources/5GUIs/Utilities/ProcessHelper.swift
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// ProcessHelper.swift
|
||||
// GetUpdateState
|
||||
//
|
||||
// Created by Helge Heß on 14.11.19.
|
||||
// Copyright © 2019-2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
// Copied from SwiftPM Catalog
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Process {
|
||||
|
||||
public struct FancyResult {
|
||||
// Note that fancy actually :-) Convenience before everything!!!
|
||||
|
||||
public let status : Int
|
||||
public let outputData : Data
|
||||
public let errorData : Data
|
||||
|
||||
public var isSuccess : Bool { return status == 0 }
|
||||
|
||||
public var stdout : String {
|
||||
return String(data: outputData, encoding: .utf8) ?? "<binary data>"
|
||||
}
|
||||
public var stderr : String {
|
||||
return String(data: errorData, encoding: .utf8) ?? "<binary data>"
|
||||
}
|
||||
|
||||
public func split(separator: Character) -> [ Substring ] {
|
||||
return stdout.split(separator: separator)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static func launch(at launchPath: String, with arguments: [ String ],
|
||||
currentDirectory: String? = nil,
|
||||
using shell: String? = "/bin/bash")
|
||||
-> FancyResult
|
||||
{
|
||||
let process = Process()
|
||||
process.launchPath = shell ?? launchPath
|
||||
process.arguments = shell != nil
|
||||
? [ "-c", launchPath + " " + arguments.joined(separator: " ") ]
|
||||
: arguments
|
||||
|
||||
if let cwd = currentDirectory, !cwd.isEmpty {
|
||||
process.currentDirectoryPath = cwd
|
||||
}
|
||||
|
||||
let stdout = Pipe()
|
||||
let stderr = Pipe()
|
||||
process.standardOutput = stdout
|
||||
process.standardError = stderr
|
||||
|
||||
var outputData = Data()
|
||||
var errorData = Data()
|
||||
|
||||
let Q = DispatchQueue(label: "shell")
|
||||
|
||||
stdout.fileHandleForReading.readabilityHandler = { handle in
|
||||
let data = handle.availableData
|
||||
Q.async { outputData.append(data) }
|
||||
}
|
||||
stderr.fileHandleForReading.readabilityHandler = { handle in
|
||||
let data = handle.availableData
|
||||
Q.async { errorData.append(data) }
|
||||
}
|
||||
|
||||
process.launch()
|
||||
process.waitUntilExit()
|
||||
|
||||
stdout.fileHandleForReading.readabilityHandler = nil
|
||||
stderr.fileHandleForReading.readabilityHandler = nil
|
||||
Q.async {
|
||||
stdout.fileHandleForReading.closeFile()
|
||||
stderr.fileHandleForReading.closeFile()
|
||||
}
|
||||
|
||||
return Q.sync {
|
||||
return FancyResult(status : Int(process.terminationStatus),
|
||||
outputData : outputData,
|
||||
errorData : errorData )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Process.FancyResult : CustomStringConvertible {
|
||||
|
||||
public var description : String {
|
||||
|
||||
func string(for data: Data) -> String {
|
||||
guard let s = String(data: data, encoding: .utf8) else {
|
||||
return data.description
|
||||
}
|
||||
if s.count > 72 {
|
||||
return String(s[..<s.index(s.startIndex, offsetBy: 72)]) + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
if isSuccess, errorData.isEmpty { return string(for: outputData) }
|
||||
|
||||
var ms = "<ProcessResult:"
|
||||
if status != 0 { ms += " \(status)" }
|
||||
|
||||
ms += " \"\(string(for: outputData))\""
|
||||
if !errorData.isEmpty {
|
||||
ms += " stderr=\"\(string(for: errorData))\""
|
||||
}
|
||||
|
||||
ms += ">"
|
||||
return ms
|
||||
}
|
||||
}
|
||||
42
Sources/5GUIs/Utilities/URLItems.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// URLItems.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
import struct Foundation.Data
|
||||
import struct Foundation.URL
|
||||
import class SwiftUI.NSItemProvider
|
||||
import class AppKit.RunLoop
|
||||
|
||||
enum UTI: String {
|
||||
case fileURL = "public.file-url"
|
||||
}
|
||||
|
||||
extension NSItemProvider {
|
||||
|
||||
func loadURL(forTypeIdentifier id: String = UTI.fileURL.rawValue,
|
||||
yield: @escaping ( URL? ) -> Void)
|
||||
{
|
||||
// this returns a `Progress`:
|
||||
loadItem(forTypeIdentifier: UTI.fileURL.rawValue, options: nil) {
|
||||
urlData, error in
|
||||
|
||||
guard let urlData = urlData as? Data else {
|
||||
print("failed to load URL data:", error as Any) // TODO: how to log?
|
||||
return yield(nil)
|
||||
}
|
||||
|
||||
guard let url = URL(dataRepresentation: urlData, relativeTo: nil) else {
|
||||
print("failed to decode URL data:", urlData)
|
||||
return yield(nil)
|
||||
}
|
||||
|
||||
RunLoop.main.perform {
|
||||
yield(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
Sources/5GUIs/Utilities/WindowEnvironmentKey.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// WindowEnvironmentKey.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension EnvironmentValues {
|
||||
|
||||
var window : NSWindow? {
|
||||
set {
|
||||
self[WindowEnvironmentKey.self] =
|
||||
WindowEnvironmentKey.WeakWindow(window: newValue)
|
||||
}
|
||||
get {
|
||||
self[WindowEnvironmentKey.self].window
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WindowEnvironmentKey: EnvironmentKey {
|
||||
struct WeakWindow {
|
||||
weak var window : NSWindow?
|
||||
}
|
||||
public static let defaultValue = WeakWindow(window: nil)
|
||||
}
|
||||
113
Sources/5GUIs/Views/ContentView.swift
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@Environment(\.window) private var window
|
||||
|
||||
@State private var isTargeted = false
|
||||
@ObservedObject private var stateObserver : WindowState
|
||||
private var state = WindowState() // to keep it around? cycle or not?
|
||||
|
||||
init() {
|
||||
stateObserver = state
|
||||
}
|
||||
|
||||
private var url : URL? { state.url }
|
||||
|
||||
func loadURL(_ url: URL) {
|
||||
window?.title = url.lastPathComponent
|
||||
state.loadURL(url)
|
||||
}
|
||||
|
||||
private func openInNewWindow(_ url: URL) {
|
||||
let view = ContentView()
|
||||
let window = makeAppWindow(view)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
view.loadURL(url)
|
||||
}
|
||||
|
||||
private func loadURLs(_ urls: [ URL ]) {
|
||||
urls.first.flatMap(loadURL)
|
||||
urls.dropFirst().forEach { openInNewWindow($0) }
|
||||
}
|
||||
|
||||
private func handleDrop(items: [ NSItemProvider ]) -> Bool {
|
||||
guard !items.isEmpty else { return false }
|
||||
|
||||
items.first.flatMap { $0.loadURL { $0.flatMap(self.loadURL) } }
|
||||
|
||||
items.dropFirst().forEach { // item load is async!
|
||||
$0.loadURL { $0.flatMap { openInNewWindow($0) } }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func onOpen() {
|
||||
let panel = makeOpenPanel()
|
||||
if let window = window {
|
||||
panel.beginSheetModal(for: window) { response in
|
||||
if response == .OK { self.loadURLs(panel.urls) }
|
||||
}
|
||||
}
|
||||
else {
|
||||
panel.begin { response in
|
||||
if response == .OK { self.loadURLs(panel.urls) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var activeGradient : LinearGradient {
|
||||
isTargeted ? style.hoverFillGradient : style.baseFillGradient
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
// TBD: can we transition between the views nicely?
|
||||
if isTargeted {
|
||||
PleaseDropAFileView()
|
||||
}
|
||||
else if case .notAnApp(let url) = state.state {
|
||||
SorryNotAnExecutableView(url: url)
|
||||
}
|
||||
else if let detectionState = state.detectionState {
|
||||
MainFileView(stepper: state.fakeDetectionStepper, state: detectionState)
|
||||
}
|
||||
else {
|
||||
PleaseDropAFileView()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
|
||||
.foregroundColor(style.textColor)
|
||||
.background(activeGradient)
|
||||
.animation(.default)
|
||||
|
||||
.onDrop(of: [ UTI.fileURL.rawValue ], isTargeted: $isTargeted,
|
||||
perform: handleDrop)
|
||||
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
|
||||
.focusable() // doesn't help w/ onCommand either?
|
||||
|
||||
// we never get this?
|
||||
.onCommand(#selector(ResponderActions.openDocument(_:)), perform: onOpen)
|
||||
}
|
||||
}
|
||||
|
||||
@objc protocol ResponderActions {
|
||||
func openDocument(_ sender: Any?)
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
55
Sources/5GUIs/Views/Reusable/SpinnerView.swift
Normal file
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// SpinnerView.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SpinnerView: View {
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
var body: some View {
|
||||
Circle()
|
||||
.trim(from: 0, to: 0.8) // cut out a segment of the circle
|
||||
.stroke(
|
||||
AngularGradient(
|
||||
gradient: style.baseGradient,
|
||||
center: .center
|
||||
),
|
||||
style: StrokeStyle(lineWidth: 8, lineCap: .round)
|
||||
)
|
||||
.frame(width: 45, height: 45)
|
||||
}
|
||||
}
|
||||
|
||||
@State var animates = false
|
||||
// This is necessary! Why? To trigger the animation we need to
|
||||
// transition the View from one state to another.
|
||||
// We don't need to constantly toggle, this is what
|
||||
// `repeatForever` does for us automagically.
|
||||
// This needs to be an '@State', because the view (`self`) is
|
||||
// immutable within `onAppear`.
|
||||
|
||||
var body: some View {
|
||||
ContentView()
|
||||
.rotationEffect(.init(degrees: animates ? 360 : 0),
|
||||
anchor: .center)
|
||||
.animation(
|
||||
Animation.linear(duration: 0.7)
|
||||
.repeatForever(autoreverses: false)
|
||||
)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
// required?! otherwise the circle "flies in"
|
||||
.padding(24)
|
||||
.onAppear { self.animates = true }
|
||||
}
|
||||
}
|
||||
|
||||
struct SpinnerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SpinnerView()
|
||||
}
|
||||
}
|
||||
95
Sources/5GUIs/Views/Windows/InfoPanel/InfoPanel.swift
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// InfoPanel.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
fileprivate let licenseWindow =
|
||||
makeLicenseWindow(ThirdPartyLicensesView())
|
||||
|
||||
struct InfoPanel: View {
|
||||
|
||||
private struct Content: View {
|
||||
|
||||
private var image: some View {
|
||||
Image(nsImage: appIcon)
|
||||
.frame(width: 64, height: 64) // this is the size?
|
||||
.padding(.top)
|
||||
.shadow(color: Color(NSColor.init(deviceWhite: 1, alpha: 0.8)),
|
||||
radius: 10, x: 0, y: 0)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
Text("Used 3rd Party Software…")
|
||||
.onTapGesture { licenseWindow.makeKeyAndOrderFront(nil) }
|
||||
.font(.subheadline)
|
||||
|
||||
(Text("©2020 ") + Text("ZeeZide").bold() + Text(" GmbH"))
|
||||
.openLink("https://zeezide.com")
|
||||
.font(.title)
|
||||
|
||||
Text("… the app for the tweet!")
|
||||
.font(.callout)
|
||||
.openLink(
|
||||
"https://twitter.com/jckarter/status/1310412969289773056")
|
||||
|
||||
Spacer()
|
||||
|
||||
image
|
||||
.openLink("https://en.wikipedia.org/wiki/Graphical_user_interface")
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(
|
||||
"""
|
||||
How it works:
|
||||
5 GUIs grabs some information from the app bundle. It then
|
||||
uses LLVM's objdump to check what libraries the app links,
|
||||
e.g. Electron or UIKit, to figure out what technology
|
||||
is being used.
|
||||
5 GUIs itself is a SwiftUI application available as OpenSource at the
|
||||
ZeeZide GitHub repository.
|
||||
"""
|
||||
.replacingOccurrences(of: "\n", with: " ")
|
||||
).font(.body).multilineTextAlignment(.leading)
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(8)
|
||||
.font(.title)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Content()
|
||||
.padding()
|
||||
.foregroundColor(style.textColor)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(style.baseFillGradient)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension View {
|
||||
|
||||
func openLink(_ s: String) -> some View {
|
||||
return onTapGesture {
|
||||
guard let url = URL(string: s) else {
|
||||
assertionFailure("Invalid URL")
|
||||
return print("ERROR: can't parse URL:", s)
|
||||
}
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InfoPanel_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InfoPanel()
|
||||
.frame(width: 320, height: 320)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// ThirdPartyLicensesView.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
fileprivate let licenses : String = {
|
||||
guard let url = Bundle.main
|
||||
.url(forResource: "LLVM-LICENSE", withExtension: "TXT") else {
|
||||
return "No licenses found?!"
|
||||
}
|
||||
do {
|
||||
return try String(contentsOf: url)
|
||||
}
|
||||
catch {
|
||||
print("ERROR: failed to load:", url.path, error)
|
||||
return "Failed to load licenses file!"
|
||||
}
|
||||
}()
|
||||
|
||||
struct ThirdPartyLicensesView: View {
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack {
|
||||
Text("3rd Party Licenses")
|
||||
.font(.title)
|
||||
|
||||
Divider()
|
||||
|
||||
Text(verbatim: licenses)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.frame(minWidth : 700, maxWidth : 1024,
|
||||
minHeight : 320, maxHeight : 800)
|
||||
}
|
||||
}
|
||||
|
||||
struct ThirdPartyLicensesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ThirdPartyLicensesView()
|
||||
}
|
||||
}
|
||||
100
Sources/5GUIs/Views/Windows/MainWindow/DetailsPopover.swift
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// DetailsPopover.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DetailsPopover: View {
|
||||
|
||||
let info : ExecutableFileTechnologyInfo
|
||||
|
||||
private struct BundleInfoView: View {
|
||||
|
||||
let info : InfoDict
|
||||
|
||||
private var title : String {
|
||||
info.displayName ?? info.name ?? info.id ?? "??"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(verbatim: "Bundle: \(title)")
|
||||
.font(.callout)
|
||||
.padding()
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
info.id .flatMap { Text("ID: \($0)") }
|
||||
info.name .flatMap { Text("Name: \($0)") }
|
||||
info.info .flatMap { Text("Info: \($0)") }
|
||||
info.version .flatMap { Text("Version: \($0)") }
|
||||
info.shortVersion .flatMap { Text("Short Version: \($0)") }
|
||||
info.applicationCategory.flatMap { Text("App Category: \($0)") }
|
||||
if info.appleScriptEnabled {
|
||||
Text("Fancy, AppleScript is enabled!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct DependenciesView: View {
|
||||
|
||||
let dependencies : [ String ]
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if dependencies.isEmpty {
|
||||
Text("No dependencies detected, yet?")
|
||||
}
|
||||
else {
|
||||
VStack {
|
||||
Text("#\(dependencies.count) Dependencies:")
|
||||
.font(.callout)
|
||||
.padding()
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
ForEach(dependencies, id: \.self) { dependency in
|
||||
Text(verbatim: dependency)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var hasReceipt : Bool {
|
||||
guard let url = info.receiptURL else { return false }
|
||||
return FileManager.default.fileExists(atPath: url.path)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack(spacing: 8) {
|
||||
if let info = info.infoDictionary {
|
||||
BundleInfoView(info: info)
|
||||
}
|
||||
else {
|
||||
Text("No Bundle Info?")
|
||||
}
|
||||
|
||||
if let url = info.executableURL {
|
||||
Text("Executable: \(url.path)")
|
||||
}
|
||||
|
||||
if hasReceipt {
|
||||
Text("App has a receipt, probably downloaded from the AppStore!")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
Divider()
|
||||
|
||||
DependenciesView(dependencies: info.dependencies)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// DetectionStepsView.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/**
|
||||
* Showing a list of fake detection steps. Driven by the
|
||||
* `FakeDetectionStepper` (which is just a timed walk
|
||||
* through the steps).
|
||||
*/
|
||||
struct DetectionStepsView: View {
|
||||
|
||||
@ObservedObject var stepper : FakeDetectionStepper
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
ForEach(stepper.activeSteps) { step in
|
||||
StepView(step: step)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Just a single step "badge".
|
||||
*/
|
||||
struct StepView: View {
|
||||
|
||||
let step : FakeDetectionStepper.ActiveStep
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if step.state != nil {
|
||||
Text(step.title)
|
||||
Spacer()
|
||||
Text(step.checkmark)
|
||||
}
|
||||
else {
|
||||
Spacer()
|
||||
Text(step.config.runTitle)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.font(.callout)
|
||||
.padding(16)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(style.lightShrugsBlue)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Sources/5GUIs/Views/Windows/MainWindow/MainFileView.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// MainFileView.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MainFileView: View {
|
||||
|
||||
@ObservedObject var stepper : FakeDetectionStepper
|
||||
@ObservedObject var state : FileDetectionState
|
||||
|
||||
@State private var detailsVisible = false
|
||||
|
||||
private var appName : String { state.info.appName }
|
||||
|
||||
private var topImage : some View {
|
||||
state.info.appImage?
|
||||
.resizable(capInsets: .init(), resizingMode: .stretch)
|
||||
.frame(width: 128, height: 128, alignment: .center)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if state.state == .processing {
|
||||
SpinnerView()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
if state.state == .processing {
|
||||
Text("Analyzing …")
|
||||
.padding(.top, 110)
|
||||
.font(.callout)
|
||||
}
|
||||
|
||||
VStack {
|
||||
topImage
|
||||
.popover(isPresented: $detailsVisible, arrowEdge: .bottom) {
|
||||
ScrollView {
|
||||
DetailsPopover(info: state.info)
|
||||
}
|
||||
.frame(minWidth: 480, maxWidth: .infinity, minHeight: 320)
|
||||
}
|
||||
.padding(.top)
|
||||
.onTapGesture {
|
||||
detailsVisible = true
|
||||
}
|
||||
|
||||
Text("\(appName)")
|
||||
.font(.largeTitle)
|
||||
|
||||
Spacer()
|
||||
|
||||
if state.state == .finished && stepper.isDone {
|
||||
SummaryView(info: state.info)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
DetectionStepsView(stepper: stepper)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// PleaseDropAFileView.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Created by Helge Heß on 28.09.20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
let appIcon = Bundle.main.image(forResource: "AppIcon")!
|
||||
|
||||
struct PleaseDropAFileView: View {
|
||||
|
||||
var headlineText: some View {
|
||||
(Text("AppKit, Catalyst, iOS, SwiftUI or Web?\n")
|
||||
+ Text("On which of the Five GUIs is a macOS app based on? Maybe all?"))
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(8)
|
||||
.font(.system(size: 24, weight: .light, design: .default))
|
||||
}
|
||||
var centerText: some View {
|
||||
Text("Drop the application on this window to figure it out!")
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(8.0)
|
||||
.font(.largeTitle)
|
||||
}
|
||||
var image: some View {
|
||||
Image(nsImage: appIcon)
|
||||
.frame(width: 128, height: 128) // this is the size?
|
||||
.padding(42)
|
||||
.shadow(color: Color(NSColor.init(deviceWhite: 1, alpha: 0.8)),
|
||||
radius: 10, x: 0, y: 0)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
//background
|
||||
|
||||
VStack {
|
||||
headlineText
|
||||
.padding(48)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
centerText
|
||||
}
|
||||
.padding(48)
|
||||
|
||||
image
|
||||
.padding(48)
|
||||
}
|
||||
}
|
||||
}
|
||||
struct PleaseDropAFileView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PleaseDropAFileView()
|
||||
.frame(width: 480, height: 480, alignment: .center)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// SorryNotAnExecutableView.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SorryNotAnExecutableView: View {
|
||||
|
||||
let url : URL
|
||||
|
||||
var headlineText: some View {
|
||||
(Text("The file your dropped doesn't seem to be an application?") +
|
||||
Text(" You can find some apps in the /Applications folder.")
|
||||
)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(8)
|
||||
.font(.system(size: 24, weight: .light, design: .default))
|
||||
}
|
||||
var centerText: some View {
|
||||
Text("Give it another try!")
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(8.0)
|
||||
.font(.largeTitle)
|
||||
}
|
||||
|
||||
var image: some View {
|
||||
VStack {
|
||||
Image(nsImage: NSWorkspace.shared.icon(forFile: url.path))
|
||||
Text(verbatim: "“\(url.lastPathComponent)”")
|
||||
.font(.title)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
//background
|
||||
|
||||
VStack {
|
||||
headlineText
|
||||
.padding(48)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
centerText
|
||||
}
|
||||
.padding(48)
|
||||
|
||||
image
|
||||
.padding(48)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /Users/helge/Desktop/Kaffee
|
||||
|
||||
struct SorryNotAnExecutableView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
SorryNotAnExecutableView(
|
||||
url:
|
||||
URL(fileURLWithPath: "/Users/helge/Desktop/Excellent-frog.jpg")
|
||||
)
|
||||
.frame(width: 480, height: 480, alignment: .center)
|
||||
|
||||
SorryNotAnExecutableView(
|
||||
url:
|
||||
URL(fileURLWithPath: "/Users/helge/Desktop/Kaffee")
|
||||
)
|
||||
.frame(width: 480, height: 480, alignment: .center)
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Sources/5GUIs/Views/Windows/MainWindow/SummaryView.swift
Normal file
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// SummaryView.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/**
|
||||
* After all detection badges are shown, we present a summary.
|
||||
*/
|
||||
struct SummaryView: View {
|
||||
|
||||
let info : ExecutableFileTechnologyInfo
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text(verbatim: info.summaryText)
|
||||
.padding(16)
|
||||
}
|
||||
.font(.callout)
|
||||
.foregroundColor(Color(NSColor.textColor))
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color(NSColor.textBackgroundColor))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct Texts {
|
||||
|
||||
static let none = "Crazy, we couldn't detect any technology?!"
|
||||
static let fallback = "We don't have any words for this combination!"
|
||||
|
||||
static let electronAndCatalyst =
|
||||
"Uh boy, this app uses Electron AND Catalyst! What a strange combo."
|
||||
static let electronAndSwiftUI =
|
||||
"Uses Electron and SwiftUI. This app might be a proper native app soon!"
|
||||
static let electron =
|
||||
"An Electron app. Sure, why not!"
|
||||
|
||||
static let catalyst =
|
||||
"A macOS Catalyst app, i.e. a mobile app " +
|
||||
"longing for larger screens w/o touch (yet?)."
|
||||
|
||||
static let phone =
|
||||
"This seems to be an iPhone or iPad app. Welcome Apple silicon!"
|
||||
|
||||
static let swiftui =
|
||||
"SwiftUI. Respect! " +
|
||||
"The developer of this app likes to live on the bleeding edge."
|
||||
|
||||
static let appKitSwift =
|
||||
"An AppKit app. But a modern one! This app is using Swift."
|
||||
static let appKitObjC =
|
||||
"A gem! This app looks a trustworthy AppKit Objective-C app. " +
|
||||
"No experiments, please!"
|
||||
|
||||
static let java =
|
||||
"Java. An actual app built using Java. Charles, is this you?"
|
||||
}
|
||||
|
||||
fileprivate extension ExecutableFileTechnologyInfo {
|
||||
|
||||
func features(_ feature: ExecutableFileTechnologyInfo.DetectedTechnologies)
|
||||
-> Bool
|
||||
{
|
||||
detectedTechnologies.contains(feature)
|
||||
}
|
||||
|
||||
var summaryText : String {
|
||||
if detectedTechnologies.isEmpty { return Texts.none }
|
||||
|
||||
if features(.electron) {
|
||||
if features(.catalyst) { return Texts.electronAndCatalyst }
|
||||
if features(.swift) { return Texts.electronAndSwiftUI }
|
||||
return Texts.electron
|
||||
}
|
||||
|
||||
if features(.catalyst) {
|
||||
return Texts.catalyst
|
||||
}
|
||||
|
||||
if !features(.catalyst) && features(.uikit) && !features(.appkit) {
|
||||
return Texts.phone
|
||||
}
|
||||
|
||||
if features(.java) {
|
||||
return Texts.java
|
||||
}
|
||||
|
||||
if features(.swiftui) {
|
||||
return Texts.swiftui
|
||||
}
|
||||
|
||||
if features(.appkit) {
|
||||
if features(.swift) { return Texts.appKitSwift }
|
||||
return Texts.appKitObjC
|
||||
}
|
||||
|
||||
return Texts.fallback
|
||||
}
|
||||
}
|
||||
75
Sources/5GUIs/WindowState.swift
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// WindowState.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
final class WindowState: ObservableObject {
|
||||
|
||||
enum State : Equatable {
|
||||
case empty
|
||||
case loading(URL)
|
||||
case notAnApp(URL)
|
||||
case app(ExecutableFileTechnologyInfo)
|
||||
}
|
||||
|
||||
@Published var state = State.empty
|
||||
@Published var detectionState : FileDetectionState?
|
||||
var fakeDetectionStepper = FakeDetectionStepper()
|
||||
|
||||
var url : URL? {
|
||||
switch state {
|
||||
case .empty: return nil
|
||||
case .notAnApp(let url), .loading(let url): return url
|
||||
case .app(let info): return info.fileURL
|
||||
}
|
||||
}
|
||||
|
||||
func loadURL(_ url: URL) {
|
||||
// Hm, we need a 'didChange' event, hence the delegate. (the observed
|
||||
// object emits a 'willChange')
|
||||
self.detectionState?.delegate = nil
|
||||
self.detectionState = nil
|
||||
fakeDetectionStepper.suspend()
|
||||
|
||||
self.state = .loading(url)
|
||||
let detectionState = FileDetectionState(url)
|
||||
self.detectionState = detectionState
|
||||
|
||||
detectionState.delegate = self
|
||||
detectionState.resume()
|
||||
}
|
||||
}
|
||||
|
||||
extension WindowState: FileDetectionStateDelegate {
|
||||
|
||||
func detectionStateDidChange(_ state: FileDetectionState) {
|
||||
guard detectionState === state else { return }
|
||||
|
||||
switch state.state {
|
||||
case .failedToOpen:
|
||||
self.state = .empty
|
||||
self.detectionState = nil
|
||||
|
||||
case .processing:
|
||||
self.state = .loading(state.url)
|
||||
|
||||
case .finished:
|
||||
self.state = .app(state.info)
|
||||
fakeDetectionStepper.resume(with: state.analysisResults)
|
||||
|
||||
#if false // until done
|
||||
self.detectionState = nil
|
||||
#endif
|
||||
// TODO: analysis
|
||||
|
||||
case .notAnApplication:
|
||||
self.state = .notAnApp(state.url)
|
||||
self.detectionState = nil
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Sources/5GUIs/Windows.swift
Normal file
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// Window.swift
|
||||
// 5 GUIs
|
||||
//
|
||||
// Copyright © 2020 ZeeZide GmbH. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
func makeAppWindow<V: View>(_ contentView: V) -> NSWindow {
|
||||
let window = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 500, height: 640),
|
||||
styleMask: [
|
||||
.closable, .fullSizeContentView, .titled //, .borderless
|
||||
],
|
||||
backing: .buffered, defer: false
|
||||
)
|
||||
|
||||
window.titlebarAppearsTransparent = true
|
||||
window.titleVisibility = .hidden
|
||||
|
||||
window.isMovableByWindowBackground = true
|
||||
window.isReleasedWhenClosed = true
|
||||
window.center()
|
||||
window.setFrameAutosaveName("5GUIs")
|
||||
|
||||
window.contentView = NSHostingView(
|
||||
rootView: contentView
|
||||
.environment(\.window, window)
|
||||
)
|
||||
window.makeFirstResponder(window.contentView) // doesn't help w/ onCommand
|
||||
return window
|
||||
}
|
||||
|
||||
func makeOpenPanel() -> NSOpenPanel {
|
||||
let panel = NSOpenPanel()
|
||||
panel.canChooseFiles = true
|
||||
panel.canChooseDirectories = true
|
||||
panel.canCreateDirectories = false
|
||||
panel.showsHiddenFiles = true
|
||||
panel.allowsMultipleSelection = true
|
||||
panel.title = "Chose an application!"
|
||||
return panel
|
||||
}
|
||||
|
||||
func makeInfoPanel<V: View>(_ contentView: V) -> NSWindow {
|
||||
let window = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 580, height: 480),
|
||||
styleMask: [
|
||||
.closable, .fullSizeContentView, .titled
|
||||
],
|
||||
backing: .buffered, defer: false
|
||||
)
|
||||
|
||||
window.titlebarAppearsTransparent = true
|
||||
window.titleVisibility = .hidden
|
||||
|
||||
window.isMovableByWindowBackground = true
|
||||
window.center()
|
||||
window.setFrameAutosaveName("5GUIs Info")
|
||||
|
||||
window.contentView = NSHostingView(
|
||||
rootView: contentView
|
||||
.environment(\.window, window)
|
||||
)
|
||||
return window
|
||||
}
|
||||
|
||||
func makeLicenseWindow<V: View>(_ contentView: V) -> NSWindow {
|
||||
let window = NSPanel(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 700, height: 480),
|
||||
styleMask: [
|
||||
.closable, .fullSizeContentView, .titled, .resizable
|
||||
],
|
||||
backing: .buffered, defer: false
|
||||
)
|
||||
window.minSize = NSMakeSize(700, 300)
|
||||
|
||||
window.titlebarAppearsTransparent = true
|
||||
window.titleVisibility = .hidden
|
||||
|
||||
window.isMovableByWindowBackground = true
|
||||
window.isReleasedWhenClosed = false
|
||||
window.center()
|
||||
window.setFrameAutosaveName("5GUIs Licenses")
|
||||
|
||||
window.contentView = NSHostingView(
|
||||
rootView: contentView
|
||||
.environment(\.window, window)
|
||||
)
|
||||
return window
|
||||
}
|
||||