🚀 Version 5.2
Merge branch 'dev/5.x'
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Nico Verbruggen
|
||||
Copyright (c) 2019-2022 Nico Verbruggen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -11,6 +11,16 @@
|
||||
5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; };
|
||||
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; };
|
||||
54B48B60275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; };
|
||||
54D9E0B227E4F51E003B9AD9 /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */; };
|
||||
54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */; };
|
||||
54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AD27E4F51E003B9AD9 /* Key.swift */; };
|
||||
54D9E0B527E4F51E003B9AD9 /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AD27E4F51E003B9AD9 /* Key.swift */; };
|
||||
54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AE27E4F51E003B9AD9 /* HotKey.swift */; };
|
||||
54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AE27E4F51E003B9AD9 /* HotKey.swift */; };
|
||||
54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AF27E4F51E003B9AD9 /* KeyCombo.swift */; };
|
||||
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0AF27E4F51E003B9AD9 /* KeyCombo.swift */; };
|
||||
54D9E0BA27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0B027E4F51E003B9AD9 /* ModifierFlagsExtension.swift */; };
|
||||
54D9E0BB27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54D9E0B027E4F51E003B9AD9 /* ModifierFlagsExtension.swift */; };
|
||||
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; };
|
||||
54FCFD26276C883F004CE748 /* SelectPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */; };
|
||||
54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */; };
|
||||
@ -52,6 +62,10 @@
|
||||
C417DC75277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; };
|
||||
C4188989275FE8CB001EF227 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; };
|
||||
C418898A275FE8CB001EF227 /* Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4188988275FE8CB001EF227 /* Filesystem.swift */; };
|
||||
C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; };
|
||||
C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; };
|
||||
C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A527E60D7A009F26CB /* SiteScanner.swift */; };
|
||||
C41C02AB27E61CB3009F26CB /* ValetSite+Fake.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */; };
|
||||
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; };
|
||||
C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3A22B0098000E7CF16 /* Assets.xcassets */; };
|
||||
C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C41C1B3C22B0098000E7CF16 /* Main.storyboard */; };
|
||||
@ -66,11 +80,23 @@
|
||||
C42759672627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
|
||||
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42759662627662800093CAE /* NSMenuExtension.swift */; };
|
||||
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; };
|
||||
C42CFB1627DFDE7900862737 /* nicoverbruggen.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1527DFDE7900862737 /* nicoverbruggen.test */; };
|
||||
C42CFB1827DFDFDC00862737 /* nicoverbruggen_isolated.test in Resources */ = {isa = PBXBuildFile; fileRef = C42CFB1727DFDFDC00862737 /* nicoverbruggen_isolated.test */; };
|
||||
C42CFB1A27DFE8BD00862737 /* NginxConfigParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42CFB1927DFE8BD00862737 /* NginxConfigParserTest.swift */; };
|
||||
C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; };
|
||||
C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */; };
|
||||
C43A8A1A25D9CD1000591B77 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A1925D9CD1000591B77 /* Utility.swift */; };
|
||||
C43A8A2025D9D1D700591B77 /* brew.json in Resources */ = {isa = PBXBuildFile; fileRef = C43A8A1F25D9D1D700591B77 /* brew.json */; };
|
||||
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */; };
|
||||
C44067F527E2582B0045BD4E /* SiteListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* SiteListNameCell.swift */; };
|
||||
C44067F727E258410045BD4E /* SiteListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* SiteListPhpCell.swift */; };
|
||||
C44067F927E2585E0045BD4E /* SiteListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* SiteListTypeCell.swift */; };
|
||||
C44067FB27E25FD70045BD4E /* SiteListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* SiteListTLSCell.swift */; };
|
||||
C449B4F027EE7FB800C47E8A /* SiteListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* SiteListTLSCell.swift */; };
|
||||
C449B4F127EE7FC200C47E8A /* SiteListNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F427E2582B0045BD4E /* SiteListNameCell.swift */; };
|
||||
C449B4F227EE7FC400C47E8A /* SiteListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* SiteListPhpCell.swift */; };
|
||||
C449B4F327EE7FC600C47E8A /* SiteListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* SiteListTypeCell.swift */; };
|
||||
C449B4F427EE7FC800C47E8A /* SiteListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* SiteListKindCell.swift */; };
|
||||
C44C198D276E3A1C0072762D /* ProgressWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* ProgressWindow.swift */; };
|
||||
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* ProgressWindow.swift */; };
|
||||
C44C1991276E44CB0072762D /* ProgressWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C44C1990276E44CB0072762D /* ProgressWindow.storyboard */; };
|
||||
@ -83,8 +109,7 @@
|
||||
C464ADAD275A7A3F003FCD53 /* SiteListWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */; };
|
||||
C464ADAF275A7A69003FCD53 /* SiteListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* SiteListVC.swift */; };
|
||||
C464ADB0275A7A6A003FCD53 /* SiteListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADAE275A7A69003FCD53 /* SiteListVC.swift */; };
|
||||
C464ADB2275A87CA003FCD53 /* SiteListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* SiteListCell.swift */; };
|
||||
C464ADB3275A87CA003FCD53 /* SiteListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* SiteListCell.swift */; };
|
||||
C464ADB2275A87CA003FCD53 /* SiteListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* SiteListCellProtocol.swift */; };
|
||||
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; };
|
||||
C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; };
|
||||
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; };
|
||||
@ -106,10 +131,10 @@
|
||||
C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4927F0A27B2DFC200C55AFD /* Errors.swift */; };
|
||||
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; };
|
||||
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4930849279F331F009C240B /* AddSiteVC.swift */; };
|
||||
C4998F0626175E7200B2526E /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = C4998F0526175E7200B2526E /* HotKey */; };
|
||||
C4998F0A2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
||||
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4998F092617633900B2526E /* PrefsWC.swift */; };
|
||||
C49E171F27A5736E00787921 /* PMServicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49E171E27A5736E00787921 /* PMServicesView.swift */; };
|
||||
C4AC51FC27E27F47008528CA /* SiteListKindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AC51FB27E27F47008528CA /* SiteListKindCell.swift */; };
|
||||
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4ACA38E25C754C100060C66 /* PhpExtension.swift */; };
|
||||
C4AF9F71275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
|
||||
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */ = {isa = PBXBuildFile; fileRef = C4AF9F70275445FF00D44ED0 /* valet-config.json */; };
|
||||
@ -133,7 +158,6 @@
|
||||
C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; };
|
||||
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B97B7A275CF20A003F3378 /* App+GlobalHotkey.swift */; };
|
||||
C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */; };
|
||||
C4C1019627C659B7001FACC2 /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = C4C1019527C659B7001FACC2 /* HotKey */; };
|
||||
C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; };
|
||||
C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C1019A27C65C6F001FACC2 /* Process.swift */; };
|
||||
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C3ED402783497000AB15D8 /* MainMenu+Startup.swift */; };
|
||||
@ -148,8 +172,13 @@
|
||||
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; };
|
||||
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */; };
|
||||
C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */; };
|
||||
C4D5CFCA27E0F9CD00035329 /* NginxConfigParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigParser.swift */; };
|
||||
C4D5CFCB27E0F9CD00035329 /* NginxConfigParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigParser.swift */; };
|
||||
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
|
||||
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D89BC52783C99400A02B68 /* ComposerJson.swift */; };
|
||||
C4D936C927E3EB6100BD69FE /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; };
|
||||
C4D936CA27E3EB6100BD69FE /* PhpHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D936C827E3EB6100BD69FE /* PhpHelper.swift */; };
|
||||
C4D936CB27E3EE4A00BD69FE /* SiteListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C464ADB1275A87CA003FCD53 /* SiteListCellProtocol.swift */; };
|
||||
C4D9ADBF277610E1007277F4 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; };
|
||||
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */; };
|
||||
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */; };
|
||||
@ -219,6 +248,13 @@
|
||||
5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = "<group>"; };
|
||||
5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
54B48B5E275F66AE006D90C5 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||
54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotKeysController.swift; sourceTree = "<group>"; };
|
||||
54D9E0AD27E4F51E003B9AD9 /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = "<group>"; };
|
||||
54D9E0AE27E4F51E003B9AD9 /* HotKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotKey.swift; sourceTree = "<group>"; };
|
||||
54D9E0AF27E4F51E003B9AD9 /* KeyCombo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyCombo.swift; sourceTree = "<group>"; };
|
||||
54D9E0B027E4F51E003B9AD9 /* ModifierFlagsExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModifierFlagsExtension.swift; sourceTree = "<group>"; };
|
||||
54D9E0BF27E4F5D9003B9AD9 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
54D9E0C027E4F5E9003B9AD9 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
54FCFD25276C883F004CE748 /* SelectPreferenceView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectPreferenceView.xib; sourceTree = "<group>"; };
|
||||
54FCFD29276C8AA4004CE748 /* CheckboxPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxPreferenceView.swift; sourceTree = "<group>"; };
|
||||
54FCFD2C276C8D67004CE748 /* HotkeyPreferenceView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HotkeyPreferenceView.xib; sourceTree = "<group>"; };
|
||||
@ -240,6 +276,8 @@
|
||||
C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = "<group>"; };
|
||||
C417DC73277614690015E6EE /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
||||
C4188988275FE8CB001EF227 /* Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filesystem.swift; sourceTree = "<group>"; };
|
||||
C41C02A527E60D7A009F26CB /* SiteScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteScanner.swift; sourceTree = "<group>"; };
|
||||
C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ValetSite+Fake.swift"; sourceTree = "<group>"; };
|
||||
C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
C41C1B3A22B0098000E7CF16 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
@ -254,17 +292,24 @@
|
||||
C4232EE42612526500158FC6 /* Credits.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = Credits.html; sourceTree = "<group>"; };
|
||||
C42759662627662800093CAE /* NSMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenuExtension.swift; sourceTree = "<group>"; };
|
||||
C42C49DA27C2806F0074ABAC /* MainMenu+FixMyValet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+FixMyValet.swift"; sourceTree = "<group>"; };
|
||||
C42CFB1527DFDE7900862737 /* nicoverbruggen.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = nicoverbruggen.test; sourceTree = "<group>"; };
|
||||
C42CFB1727DFDFDC00862737 /* nicoverbruggen_isolated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = nicoverbruggen_isolated.test; sourceTree = "<group>"; };
|
||||
C42CFB1927DFE8BD00862737 /* NginxConfigParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigParserTest.swift; sourceTree = "<group>"; };
|
||||
C436039F275E67610028EFC6 /* AppDelegate+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Notifications.swift"; sourceTree = "<group>"; };
|
||||
C43A8A1925D9CD1000591B77 /* Utility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
|
||||
C43A8A1F25D9D1D700591B77 /* brew.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = brew.json; sourceTree = "<group>"; };
|
||||
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrewJsonParserTest.swift; sourceTree = "<group>"; };
|
||||
C44067F427E2582B0045BD4E /* SiteListNameCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListNameCell.swift; sourceTree = "<group>"; };
|
||||
C44067F627E258410045BD4E /* SiteListPhpCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListPhpCell.swift; sourceTree = "<group>"; };
|
||||
C44067F827E2585E0045BD4E /* SiteListTypeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteListTypeCell.swift; sourceTree = "<group>"; };
|
||||
C44067FA27E25FD70045BD4E /* SiteListTLSCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteListTLSCell.swift; sourceTree = "<group>"; };
|
||||
C44C198C276E3A1C0072762D /* ProgressWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressWindow.swift; sourceTree = "<group>"; };
|
||||
C44C1990276E44CB0072762D /* ProgressWindow.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ProgressWindow.storyboard; sourceTree = "<group>"; };
|
||||
C44CCD3F27AFE2FC00CE40E5 /* AlertableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertableError.swift; sourceTree = "<group>"; };
|
||||
C44CCD4827AFF3B700CE40E5 /* MainMenu+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Async.swift"; sourceTree = "<group>"; };
|
||||
C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListWC.swift; sourceTree = "<group>"; };
|
||||
C464ADAE275A7A69003FCD53 /* SiteListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListVC.swift; sourceTree = "<group>"; };
|
||||
C464ADB1275A87CA003FCD53 /* SiteListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListCell.swift; sourceTree = "<group>"; };
|
||||
C464ADB1275A87CA003FCD53 /* SiteListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteListCellProtocol.swift; sourceTree = "<group>"; };
|
||||
C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
|
||||
C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = "<group>"; };
|
||||
@ -283,6 +328,7 @@
|
||||
C4930849279F331F009C240B /* AddSiteVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddSiteVC.swift; sourceTree = "<group>"; };
|
||||
C4998F092617633900B2526E /* PrefsWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsWC.swift; sourceTree = "<group>"; };
|
||||
C49E171E27A5736E00787921 /* PMServicesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMServicesView.swift; sourceTree = "<group>"; };
|
||||
C4AC51FB27E27F47008528CA /* SiteListKindCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SiteListKindCell.swift; sourceTree = "<group>"; };
|
||||
C4ACA38E25C754C100060C66 /* PhpExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpExtension.swift; sourceTree = "<group>"; };
|
||||
C4AF9F70275445FF00D44ED0 /* valet-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "valet-config.json"; sourceTree = "<group>"; };
|
||||
C4AF9F76275447F100D44ED0 /* ValetConfigParserTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetConfigParserTest.swift; sourceTree = "<group>"; };
|
||||
@ -304,8 +350,10 @@
|
||||
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = "<group>"; };
|
||||
C4CE3BB727B31F2E0086CA49 /* MainMenu+Switcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainMenu+Switcher.swift"; sourceTree = "<group>"; };
|
||||
C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = "<group>"; };
|
||||
C4D5CFC927E0F9CD00035329 /* NginxConfigParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigParser.swift; sourceTree = "<group>"; };
|
||||
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
|
||||
C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = "<group>"; };
|
||||
C4D936C827E3EB6100BD69FE /* PhpHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpHelper.swift; sourceTree = "<group>"; };
|
||||
C4D9ADBE277610E1007277F4 /* PhpSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpSwitcher.swift; sourceTree = "<group>"; };
|
||||
C4D9ADC7277611A0007277F4 /* InternalSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalSwitcher.swift; sourceTree = "<group>"; };
|
||||
C4DEB7D327A5D60B00834718 /* Stats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stats.swift; sourceTree = "<group>"; };
|
||||
@ -340,7 +388,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C4998F0626175E7200B2526E /* HotKey in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -348,7 +395,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C4C1019627C659B7001FACC2 /* HotKey in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -383,6 +429,27 @@
|
||||
path = PHP;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54D9E0AB27E4F502003B9AD9 /* HotKey */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54D9E0BF27E4F5D9003B9AD9 /* LICENSE */,
|
||||
54D9E0AE27E4F51E003B9AD9 /* HotKey.swift */,
|
||||
54D9E0AC27E4F51E003B9AD9 /* HotKeysController.swift */,
|
||||
54D9E0AD27E4F51E003B9AD9 /* Key.swift */,
|
||||
54D9E0AF27E4F51E003B9AD9 /* KeyCombo.swift */,
|
||||
54D9E0B027E4F51E003B9AD9 /* ModifierFlagsExtension.swift */,
|
||||
);
|
||||
path = HotKey;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54D9E0BE27E4F5C0003B9AD9 /* Vendor */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54D9E0AB27E4F502003B9AD9 /* HotKey */,
|
||||
);
|
||||
path = Vendor;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
54FCFD28276C88C0004CE748 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -417,6 +484,8 @@
|
||||
C40C7F1C27720E1400DDDCDC /* Test Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C42CFB1527DFDE7900862737 /* nicoverbruggen.test */,
|
||||
C42CFB1727DFDFDC00862737 /* nicoverbruggen_isolated.test */,
|
||||
C4AF9F70275445FF00D44ED0 /* valet-config.json */,
|
||||
C43A8A1F25D9D1D700591B77 /* brew.json */,
|
||||
C4F30B06278E195800755FCE /* brew-services.json */,
|
||||
@ -447,6 +516,7 @@
|
||||
C4F8C0A522D4FA41002EFE61 /* README.md */,
|
||||
C4E713562570150F00007428 /* SECURITY.md */,
|
||||
C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */,
|
||||
54D9E0C027E4F5E9003B9AD9 /* LICENSE */,
|
||||
C4E713572570151400007428 /* docs */,
|
||||
C41C1B3522B0097F00E7CF16 /* phpmon */,
|
||||
C4F7807A25D7F84B000DBC97 /* phpmon-tests */,
|
||||
@ -469,6 +539,7 @@
|
||||
children = (
|
||||
C4B5853A2770FE2500DA4FBE /* Common */,
|
||||
C41E181722CB61EB0072CF09 /* Domain */,
|
||||
54D9E0BE27E4F5C0003B9AD9 /* Vendor */,
|
||||
C41C1B3F22B0098000E7CF16 /* Info.plist */,
|
||||
C4232EE42612526500158FC6 /* Credits.html */,
|
||||
C41C1B4022B0098000E7CF16 /* phpmon.entitlements */,
|
||||
@ -504,6 +575,19 @@
|
||||
path = Domain;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C44067F327E256560045BD4E /* Cells */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C464ADB1275A87CA003FCD53 /* SiteListCellProtocol.swift */,
|
||||
C44067FA27E25FD70045BD4E /* SiteListTLSCell.swift */,
|
||||
C44067F427E2582B0045BD4E /* SiteListNameCell.swift */,
|
||||
C44067F627E258410045BD4E /* SiteListPhpCell.swift */,
|
||||
C44067F827E2585E0045BD4E /* SiteListTypeCell.swift */,
|
||||
C4AC51FB27E27F47008528CA /* SiteListKindCell.swift */,
|
||||
);
|
||||
path = Cells;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C44C198F276E3A380072762D /* Progress */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -525,12 +609,12 @@
|
||||
C464ADAA275A7A25003FCD53 /* SiteList */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C44067F327E256560045BD4E /* Cells */,
|
||||
C464ADAB275A7A3F003FCD53 /* SiteListWC.swift */,
|
||||
C464ADAE275A7A69003FCD53 /* SiteListVC.swift */,
|
||||
C41E87192763D42300161EE0 /* SiteListVC+ContextMenu.swift */,
|
||||
C41CA5EC2774F8EE00A2C80E /* SiteListVC+Actions.swift */,
|
||||
C4930849279F331F009C240B /* AddSiteVC.swift */,
|
||||
C464ADB1275A87CA003FCD53 /* SiteListCell.swift */,
|
||||
);
|
||||
path = SiteList;
|
||||
sourceTree = "<group>";
|
||||
@ -573,6 +657,7 @@
|
||||
children = (
|
||||
C40C7F1D2772136000DDDCDC /* PhpEnv.swift */,
|
||||
C48D6C6F279CD2AC00F26D7E /* PhpVersionNumber.swift */,
|
||||
C4D936C827E3EB6100BD69FE /* PhpHelper.swift */,
|
||||
);
|
||||
path = "PHP Version";
|
||||
sourceTree = "<group>";
|
||||
@ -582,6 +667,9 @@
|
||||
children = (
|
||||
C4AF9F792754499000D44ED0 /* Valet.swift */,
|
||||
C4E4404527C56F4700D225E1 /* ValetSite.swift */,
|
||||
C41C02A827E61A65009F26CB /* ValetSite+Fake.swift */,
|
||||
C41C02A527E60D7A009F26CB /* SiteScanner.swift */,
|
||||
C4D5CFC927E0F9CD00035329 /* NginxConfigParser.swift */,
|
||||
);
|
||||
path = Valet;
|
||||
sourceTree = "<group>";
|
||||
@ -639,6 +727,7 @@
|
||||
C4AF9F76275447F100D44ED0 /* ValetConfigParserTest.swift */,
|
||||
C4F780AD25D80B37000DBC97 /* ExtensionParserTest.swift */,
|
||||
C43A8A2325D9D20D00591B77 /* BrewJsonParserTest.swift */,
|
||||
C42CFB1927DFE8BD00862737 /* NginxConfigParserTest.swift */,
|
||||
);
|
||||
path = Parsers;
|
||||
sourceTree = "<group>";
|
||||
@ -767,7 +856,6 @@
|
||||
);
|
||||
name = "PHP Monitor";
|
||||
packageProductDependencies = (
|
||||
C4998F0526175E7200B2526E /* HotKey */,
|
||||
);
|
||||
productName = phpmon;
|
||||
productReference = C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */;
|
||||
@ -788,7 +876,6 @@
|
||||
);
|
||||
name = "phpmon-tests";
|
||||
packageProductDependencies = (
|
||||
C4C1019527C659B7001FACC2 /* HotKey */,
|
||||
);
|
||||
productName = "phpmon-tests";
|
||||
productReference = C4F7807925D7F84B000DBC97 /* phpmon-tests.xctest */;
|
||||
@ -822,7 +909,6 @@
|
||||
);
|
||||
mainGroup = C41C1B2A22B0097F00E7CF16;
|
||||
packageReferences = (
|
||||
C4998F0426175E7200B2526E /* XCRemoteSwiftPackageReference "HotKey" */,
|
||||
);
|
||||
productRefGroup = C41C1B3422B0097F00E7CF16 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -863,12 +949,14 @@
|
||||
files = (
|
||||
54FCFD27276C883F004CE748 /* SelectPreferenceView.xib in Resources */,
|
||||
54FCFD2E276C8D67004CE748 /* HotkeyPreferenceView.xib in Resources */,
|
||||
C42CFB1827DFDFDC00862737 /* nicoverbruggen_isolated.test in Resources */,
|
||||
C4F780A825D80AE8000DBC97 /* php.ini in Resources */,
|
||||
C4068CA527B0780A00544CD5 /* CheckboxPreferenceView.xib in Resources */,
|
||||
C43A8A2025D9D1D700591B77 /* brew.json in Resources */,
|
||||
C4AF9F72275445FF00D44ED0 /* valet-config.json in Resources */,
|
||||
C44C1992276E44CB0072762D /* ProgressWindow.storyboard in Resources */,
|
||||
C4F30B08278E195800755FCE /* brew-services.json in Resources */,
|
||||
C42CFB1627DFDE7900862737 /* nicoverbruggen.test in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -903,13 +991,18 @@
|
||||
C48D0CA325CC992000CC7490 /* StatsView.swift in Sources */,
|
||||
C40C7F2827721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */,
|
||||
C4EE55A927708B9E001DF387 /* PMHeaderView.swift in Sources */,
|
||||
C41C02A927E61A65009F26CB /* ValetSite+Fake.swift in Sources */,
|
||||
C4F2E4372752F0870020E974 /* HomebrewDiagnostics.swift in Sources */,
|
||||
C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */,
|
||||
C4B585442770FE3900DA4FBE /* Command.swift in Sources */,
|
||||
C44067F527E2582B0045BD4E /* SiteListNameCell.swift in Sources */,
|
||||
C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */,
|
||||
C4EE55AB27708B9E001DF387 /* Preview.swift in Sources */,
|
||||
C44067F727E258410045BD4E /* SiteListPhpCell.swift in Sources */,
|
||||
C415D3B72770F294005EF286 /* Actions.swift in Sources */,
|
||||
C4AC51FC27E27F47008528CA /* SiteListKindCell.swift in Sources */,
|
||||
C44C198D276E3A1C0072762D /* ProgressWindow.swift in Sources */,
|
||||
54D9E0B827E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
|
||||
C4C3ED4327834C5200AB15D8 /* CustomPrefs.swift in Sources */,
|
||||
54B48B5F275F66AE006D90C5 /* Application.swift in Sources */,
|
||||
C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
|
||||
@ -918,9 +1011,11 @@
|
||||
C4811D2422D70A4700B5F6B3 /* App.swift in Sources */,
|
||||
C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */,
|
||||
C4F30B03278E16BA00755FCE /* HomebrewService.swift in Sources */,
|
||||
54D9E0B427E4F51E003B9AD9 /* Key.swift in Sources */,
|
||||
5420395F2613607600FB00FA /* Preferences.swift in Sources */,
|
||||
C48D0C9325CC804200CC7490 /* XibLoadable.swift in Sources */,
|
||||
54FCFD2A276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
|
||||
54D9E0B227E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
|
||||
C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */,
|
||||
C40C7F3027722E8D00DDDCDC /* Logger.swift in Sources */,
|
||||
C41CA5ED2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */,
|
||||
@ -936,6 +1031,7 @@
|
||||
C44CCD4927AFF3B700CE40E5 /* MainMenu+Async.swift in Sources */,
|
||||
C4C1019B27C65C6F001FACC2 /* Process.swift in Sources */,
|
||||
C4EC1E73279DFCF40010F296 /* Events.swift in Sources */,
|
||||
C44067FB27E25FD70045BD4E /* SiteListTLSCell.swift in Sources */,
|
||||
C4927F0B27B2DFC200C55AFD /* Errors.swift in Sources */,
|
||||
C4B5853E2770FE3900DA4FBE /* Paths.swift in Sources */,
|
||||
C41C1B4B22B019FF00E7CF16 /* ActivePhpInstallation.swift in Sources */,
|
||||
@ -947,17 +1043,23 @@
|
||||
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
|
||||
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
|
||||
C48D0C9625CC80B100CC7490 /* HeaderView.swift in Sources */,
|
||||
C4D5CFCA27E0F9CD00035329 /* NginxConfigParser.swift in Sources */,
|
||||
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
|
||||
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
||||
C4080FFA27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
|
||||
C4B5635E276AB09000F12CCB /* VersionExtractor.swift in Sources */,
|
||||
54D9E0B627E4F51E003B9AD9 /* HotKey.swift in Sources */,
|
||||
C4D936C927E3EB6100BD69FE /* PhpHelper.swift in Sources */,
|
||||
C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */,
|
||||
C44067F927E2585E0045BD4E /* SiteListTypeCell.swift in Sources */,
|
||||
54D9E0BA27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
|
||||
C4C3ED412783497000AB15D8 /* MainMenu+Startup.swift in Sources */,
|
||||
C4D89BC62783C99400A02B68 /* ComposerJson.swift in Sources */,
|
||||
C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */,
|
||||
C4B97B75275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
|
||||
C41C02A627E60D7A009F26CB /* SiteScanner.swift in Sources */,
|
||||
C464ADAC275A7A3F003FCD53 /* SiteListWC.swift in Sources */,
|
||||
C464ADB2275A87CA003FCD53 /* SiteListCell.swift in Sources */,
|
||||
C464ADB2275A87CA003FCD53 /* SiteListCellProtocol.swift in Sources */,
|
||||
C4EE188422D3386B00E126E5 /* Constants.swift in Sources */,
|
||||
C493084A279F331F009C240B /* AddSiteVC.swift in Sources */,
|
||||
C4DEB7D427A5D60B00834718 /* Stats.swift in Sources */,
|
||||
@ -968,9 +1070,11 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C449B4F427EE7FC800C47E8A /* SiteListKindCell.swift in Sources */,
|
||||
54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */,
|
||||
C4EE55AE27708B9E001DF387 /* PMStatsView.swift in Sources */,
|
||||
C41CA5EE2774F8EE00A2C80E /* SiteListVC+Actions.swift in Sources */,
|
||||
54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */,
|
||||
C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */,
|
||||
54FCFD2B276C8AA4004CE748 /* CheckboxPreferenceView.swift in Sources */,
|
||||
C415D3B82770F294005EF286 /* Actions.swift in Sources */,
|
||||
@ -978,18 +1082,23 @@
|
||||
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
|
||||
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */,
|
||||
C4D9ADC0277610E1007277F4 /* PhpSwitcher.swift in Sources */,
|
||||
C41C02AA27E61CA3009F26CB /* SiteScanner.swift in Sources */,
|
||||
C4080FFB27BD956700BF2C6B /* BetterAlertVC.swift in Sources */,
|
||||
C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */,
|
||||
54D9E0BB27E4F51E003B9AD9 /* ModifierFlagsExtension.swift in Sources */,
|
||||
C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */,
|
||||
C4D5CFCB27E0F9CD00035329 /* NginxConfigParser.swift in Sources */,
|
||||
C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */,
|
||||
C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */,
|
||||
C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */,
|
||||
C449B4F027EE7FB800C47E8A /* SiteListTLSCell.swift in Sources */,
|
||||
C4FBFC532616485F00CDB8E1 /* PhpVersionDetectionTest.swift in Sources */,
|
||||
C43A8A2425D9D20D00591B77 /* BrewJsonParserTest.swift in Sources */,
|
||||
C4F780CA25D80B75000DBC97 /* HomebrewPackage.swift in Sources */,
|
||||
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
|
||||
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
|
||||
C4F30B04278E16BA00755FCE /* HomebrewService.swift in Sources */,
|
||||
54D9E0B527E4F51E003B9AD9 /* Key.swift in Sources */,
|
||||
C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */,
|
||||
C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */,
|
||||
C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */,
|
||||
@ -999,10 +1108,12 @@
|
||||
C4F2E4382752F08D0020E974 /* HomebrewDiagnostics.swift in Sources */,
|
||||
C4F780AE25D80B37000DBC97 /* ExtensionParserTest.swift in Sources */,
|
||||
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */,
|
||||
54D9E0B927E4F51E003B9AD9 /* KeyCombo.swift in Sources */,
|
||||
C4EED88A27A48778006D7272 /* InterAppHandler.swift in Sources */,
|
||||
C48D6C75279CD3E400F26D7E /* PhpVersionNumberTest.swift in Sources */,
|
||||
C43603A1275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */,
|
||||
C42759682627662800093CAE /* NSMenuExtension.swift in Sources */,
|
||||
C4D936CB27E3EE4A00BD69FE /* SiteListCellProtocol.swift in Sources */,
|
||||
C4B97B76275CF08C003F3378 /* AppDelegate+MenuOutlets.swift in Sources */,
|
||||
C4F780CD25D80B75000DBC97 /* Alert.swift in Sources */,
|
||||
C481F79726164A78004FBCFF /* PrefsVC.swift in Sources */,
|
||||
@ -1010,7 +1121,6 @@
|
||||
C40C7F3127722E8D00DDDCDC /* Logger.swift in Sources */,
|
||||
C4068CAB27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
|
||||
C4F30B09278E1A0E00755FCE /* CustomPrefs.swift in Sources */,
|
||||
C464ADB3275A87CA003FCD53 /* SiteListCell.swift in Sources */,
|
||||
C415D3E92770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
|
||||
C4AF9F78275447F100D44ED0 /* ValetConfigParserTest.swift in Sources */,
|
||||
C4CE3BBC27B324250086CA49 /* ComposerWindow.swift in Sources */,
|
||||
@ -1018,10 +1128,13 @@
|
||||
C417DC75277614690015E6EE /* Helpers.swift in Sources */,
|
||||
C4080FF727BD8C6400BF2C6B /* BetterAlert.swift in Sources */,
|
||||
C4B97B7C275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */,
|
||||
54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
|
||||
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
|
||||
C4CE3BBB27B324230086CA49 /* MainMenu+Switcher.swift in Sources */,
|
||||
C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */,
|
||||
C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */,
|
||||
C4D936CA27E3EB6100BD69FE /* PhpHelper.swift in Sources */,
|
||||
C449B4F127EE7FC200C47E8A /* SiteListNameCell.swift in Sources */,
|
||||
C4F780BA25D80B62000DBC97 /* AppDelegate.swift in Sources */,
|
||||
54FCFD31276C8DA4004CE748 /* HotkeyPreferenceView.swift in Sources */,
|
||||
C4998F0B2617633900B2526E /* PrefsWC.swift in Sources */,
|
||||
@ -1031,6 +1144,8 @@
|
||||
C44C198E276E3A1C0072762D /* ProgressWindow.swift in Sources */,
|
||||
C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */,
|
||||
C4D9ADC9277611A0007277F4 /* InternalSwitcher.swift in Sources */,
|
||||
C449B4F227EE7FC400C47E8A /* SiteListPhpCell.swift in Sources */,
|
||||
C42CFB1A27DFE8BD00862737 /* NginxConfigParserTest.swift in Sources */,
|
||||
C4F30B0B278E203C00755FCE /* MainMenu+Startup.swift in Sources */,
|
||||
C40B24F227A310770018C7D2 /* Events.swift in Sources */,
|
||||
C4F30B0A278E1A1A00755FCE /* ComposerJson.swift in Sources */,
|
||||
@ -1043,7 +1158,9 @@
|
||||
C4927F0C27B2DFC200C55AFD /* Errors.swift in Sources */,
|
||||
C4E4404727C56F4700D225E1 /* ValetSite.swift in Sources */,
|
||||
C44CCD4A27AFF3BC00CE40E5 /* MainMenu+Async.swift in Sources */,
|
||||
C449B4F327EE7FC600C47E8A /* SiteListTypeCell.swift in Sources */,
|
||||
C48D6C71279CD2AC00F26D7E /* PhpVersionNumber.swift in Sources */,
|
||||
C41C02AB27E61CB3009F26CB /* ValetSite+Fake.swift in Sources */,
|
||||
C4F780C925D80B75000DBC97 /* StringExtension.swift in Sources */,
|
||||
C4B5853F2770FE3900DA4FBE /* Paths.swift in Sources */,
|
||||
C481F79A26164A7C004FBCFF /* Preferences.swift in Sources */,
|
||||
@ -1207,7 +1324,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 717;
|
||||
CURRENT_PROJECT_VERSION = 756;
|
||||
DEBUG = YES;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -1217,7 +1334,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MARKETING_VERSION = 5.1.1;
|
||||
MARKETING_VERSION = 5.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -1233,7 +1350,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 717;
|
||||
CURRENT_PROJECT_VERSION = 756;
|
||||
DEBUG = NO;
|
||||
DEVELOPMENT_TEAM = 8M54J5J787;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -1243,7 +1360,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MARKETING_VERSION = 5.1.1;
|
||||
MARKETING_VERSION = 5.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nicoverbruggen.phpmon;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -1320,30 +1437,6 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
C4998F0426175E7200B2526E /* XCRemoteSwiftPackageReference "HotKey" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/soffes/HotKey";
|
||||
requirement = {
|
||||
kind = upToNextMinorVersion;
|
||||
minimumVersion = 0.1.3;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
C4998F0526175E7200B2526E /* HotKey */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C4998F0426175E7200B2526E /* XCRemoteSwiftPackageReference "HotKey" */;
|
||||
productName = HotKey;
|
||||
};
|
||||
C4C1019527C659B7001FACC2 /* HotKey */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = C4998F0426175E7200B2526E /* XCRemoteSwiftPackageReference "HotKey" */;
|
||||
productName = HotKey;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = C41C1B2B22B0097F00E7CF16 /* Project object */;
|
||||
}
|
||||
|
@ -61,6 +61,13 @@
|
||||
ReferencedContainer = "container:PHP Monitor.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "PHPMON_MARKETING_MODE"
|
||||
value = "YES"
|
||||
isEnabled = "NO">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
66
README.md
@ -1,17 +1,12 @@
|
||||
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider leaving [a one-time donation](https://nicoverbruggen.be/sponsor) to support the project.
|
||||
> You can also send me [feedback](https://twitter.com/nicoverbruggen) if the app came in handy.<br>**Thank you!** ⭐️
|
||||
> If this software has been useful to you, I ask that you **please star the repository**, that way I know that the software is being used. Also, please consider leaving [a one-time donation](https://nicoverbruggen.be/sponsor) to support the project, as this is something I make in my free time. **Thank you!** ⭐️
|
||||
|
||||
<h1 align="center"><b>PHP Monitor</b> (phpmon)</h1>
|
||||
<p align="center"><img src="./docs/logo.png" alt="PHP Monitor Logo" width="500px" /></p>
|
||||
|
||||
<p align="center">
|
||||
<img src="./phpmon/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png" alt="phpmon icon" width="128px" />
|
||||
</p>
|
||||
**PHP Monitor** (or *phpmon*) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so <u>you need to have it set up before you can use this app</u> (consult the FAQ below with info about how to set up your environment).
|
||||
|
||||
**PHP Monitor** (or *phpmon*) is a lightweight macOS utility app that runs on your Mac and displays the active PHP version in your status bar. It's tightly integrated with [Laravel Valet](https://github.com/laravel/valet), so <u>you need to have it set up before you can use this</u>.
|
||||
<img src="./docs/screenshot.jpg" width="1085px" alt="phpmon screenshot (menu bar app)"/>
|
||||
|
||||
<img src="./docs/screenshot50.jpg" width="1085px" alt="phpmon screenshot (menu bar app)"/>
|
||||
|
||||
<small><i>Screenshot: Showing the key functionality of PHP Monitor. You can also add new domains as links, manage various services, and perform First Aid to fix all kinds of common PHP link issues.</i></small>
|
||||
<small><i>Screenshot: Showing the key functionality of PHP Monitor.</i></small>
|
||||
|
||||
It's super convenient to switch between different versions of PHP. You'll even get notifications (only if you choose to opt-in, of course)!
|
||||
|
||||
@ -19,16 +14,19 @@ It's super convenient to switch between different versions of PHP. You'll even g
|
||||
|
||||
PHP Monitor also gives you quick access to various useful functionality (like accessing configuration files, restarting services, and more).
|
||||
|
||||
You can also add new domains as links, isolate sites, manage various services, and perform First Aid to fix all kinds of common PHP link issues.
|
||||
|
||||
## 🖥 System requirements
|
||||
|
||||
PHP Monitor is a universal application that runs natively on Apple Silicon **and** Intel-based Macs.
|
||||
|
||||
* Your user account can administer your computer (required for some functionality, e.g. certificate generation)
|
||||
* macOS 11 Big Sur or higher (supports macOS 12 Monterey)
|
||||
* Homebrew is installed in `/usr/local/homebrew` or `/opt/homebrew`
|
||||
* The brew formula `php` has to be installed (which version is detected)
|
||||
* Laravel Valet 2.16.2 or higher (older versions might be compatible but are not supported)
|
||||
* Homebrew `php` formula is installed
|
||||
* Laravel Valet 2.16 or newer (supports Valet 3)
|
||||
|
||||
_You may need to update your Valet installation to keep everything working if a major version update of PHP has been released. You can do this by running `composer global update && valet install`._
|
||||
_You may need to update your Valet installation to keep everything working if a major version update of PHP has been released. You can do this by running `composer global update && valet install`. Some features are not supported when running Valet 2._
|
||||
|
||||
## 🚀 How to install
|
||||
|
||||
@ -79,7 +77,7 @@ If you're still having issues, here's a few common questions & answers, as well
|
||||
<summary><strong>Which versions of PHP are supported?</strong></summary>
|
||||
|
||||
<ul>
|
||||
<li>PHP 5.6</li>
|
||||
<li>PHP 5.6 (only if you are running Valet 2)</li>
|
||||
<li>PHP 7.0</li>
|
||||
<li>PHP 7.1</li>
|
||||
<li>PHP 7.2</li>
|
||||
@ -90,7 +88,7 @@ If you're still having issues, here's a few common questions & answers, as well
|
||||
<li>PHP 8.2 (experimental)</li>
|
||||
</ul>
|
||||
|
||||
For more details, consult the [constants file](https://github.com/nicoverbruggen/phpmon/blob/main/phpmon/Constants.swift#L16) file to see which versions are supported.
|
||||
For more details, consult the [constants file](https://github.com/nicoverbruggen/phpmon/blob/main/phpmon/Common/Core/Constants.swift#L16) file to see which versions are supported.
|
||||
|
||||
</details>
|
||||
|
||||
@ -151,6 +149,29 @@ This should install `dnsmasq` and set up Valet. Great, almost there!
|
||||
Finally, run PHP Monitor. Since the app is notarized and signed with a developer ID, it should work.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>I have PHP Monitor installed, and it works. I want to upgrade my PHP installations to the latest version, what's the best way to do this?</strong></summary>
|
||||
|
||||
It's easy to make a mistake here, and end up with an unlinked version of PHP or have versions missing from PHP Monitor.
|
||||
|
||||
Here's what I usually do:
|
||||
|
||||
* Open PHP Monitor and select **First Aid & Services** > **Restore Homebrew Permissions**.
|
||||
* Close PHP Monitor after the pop-up tells you the permissions were restored.
|
||||
* Run `brew update-reset`
|
||||
* Run `brew upgrade`
|
||||
|
||||
If after this, any PHP versions are missing in PHP Monitor, please run the following for the versions that are missing:
|
||||
|
||||
* Run `brew uninstall php@x.x` (where `x.x` is the version)
|
||||
* Run `brew cleanup` (if you get any permission issues you may need to manually clean up the folder)
|
||||
* Run `brew install php@x.x` (where `x.x` is the version)
|
||||
|
||||
You may still need to run `brew link php` after upgrading, too.
|
||||
|
||||
That's it. Now start up PHP Monitor again and you should be golden!
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>PHP Monitor tells me `php` is not installed...</strong></summary>
|
||||
|
||||
@ -203,6 +224,12 @@ You should see an error or a warning here in the output.
|
||||
Usually this is a duplicate extension declaration causing issues, or an extension that couldn't be loaded. You'll have to solve that issue yourself (usually by removing the offending extension or reinstalling).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>The option to isolate a site is disabled! What's going on?</strong></summary>
|
||||
|
||||
Make sure you have at least **Valet 3.0** installed, since support for isolation was added in this version of Valet. (Please note that this version of Valet drops support for PHP 5.6.)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>One of the limits (memory limit, max POST size, max upload size) shows an exclamation mark!</strong></summary>
|
||||
@ -366,13 +393,14 @@ Donations really help with the Apple Developer Program cost, and keep me motivat
|
||||
|
||||
## 😎 Acknowledgements
|
||||
|
||||
While I did make this application during my own free time, I have been lucky enough to do various experiments during work hours at [DIVE](https://dive.be). I'd also like to shout out the following folks:
|
||||
While I did make this application during my own free time, PHP Monitor started out from various learning experiments during work hours at my employer, DIVE. I'd also like to shout out the following folks:
|
||||
|
||||
* My colleagues at [DIVE](https://dive.be)
|
||||
* The [Homebrew](https://brew.sh/) team & [Valet maintainers](https://github.com/laravel/valet/graphs/contributors)
|
||||
* Various folks who [reached](https://twitter.com/stauffermatt) [out](https://twitter.com/marcelpociot) when PHP Monitor was still very much a small app with a handful of stars or so
|
||||
* My [GitHub Sponsors](https://github.com/sponsors/nicoverbruggen) and those who have donated
|
||||
* Everyone who has left feedback and reported bugs (appreciate it!)
|
||||
* Everyone in the Laravel community who shared the app (thanks!)
|
||||
* Everyone who left feedback via issues & who donated to keep the project up and running
|
||||
|
||||
Thank you very much for your contributions, kind words and support.
|
||||
|
||||
@ -388,7 +416,9 @@ In order to save power, this only happens once every 60 seconds.
|
||||
|
||||
This utility will detect which PHP versions you have installed via Homebrew, and then allows you to switch between them.
|
||||
|
||||
The switcher will disable all PHP-FPM services not belonging to the version you wish to use, and link the desired version of PHP. Then, it'll restart your desired PHP version's FPM process. This all happens in parallel, so this should be much faster than Valet’s switcher.
|
||||
The switcher will disable all PHP-FPM services not belonging to the version you wish to use, and link the desired version of PHP. Then, it'll restart your desired PHP version's FPM process. This all happens in parallel, so this should be a bit faster than Valet’s switcher.
|
||||
|
||||
If you're using Valet 3, versions of PHP-FPM required to keep isolated sites up and running will also be started or stopped as needed.
|
||||
|
||||
### Config change detection
|
||||
|
||||
|
@ -4,9 +4,11 @@
|
||||
|
||||
Generally speaking, only the latest version of **PHP Monitor** is supported, except during transition periods (for example, when particular system requirements go up):
|
||||
|
||||
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Minimum Required Valet Version |
|
||||
| Version | Apple Silicon | Supported | Supported macOS | Deployment Target | Detected PHP Versions | Recommended Valet Version |
|
||||
| ------- | ------------- | ------------------ | ----- | ----- | ----- | ----
|
||||
| 5 | ✅ Universal binary | ✅ Yes | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 | 2.16.2 |
|
||||
| 5.x | ✅ Universal binary | ✅ Yes | Big Sur (11.0) and Monterey (12.0) | macOS 11+ | PHP 5.6—PHP 8.2 (*) | 3.0 (2.16.2 minimum) |
|
||||
|
||||
_(*) Support for PHP 5.6 is only included if you are using Valet 2.x, since support for PHP 5.6 was dropped in Valet 3.0._
|
||||
|
||||
## Legacy versions
|
||||
|
||||
|
BIN
docs/logo.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
docs/screenshot.jpg
Normal file
After Width: | Height: | Size: 345 KiB |
Before Width: | Height: | Size: 370 KiB |
32
phpmon-tests/Parsers/NginxConfigParserTest.swift
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// NginxConfigParserTest.swift
|
||||
// phpmon-tests
|
||||
//
|
||||
// Created by Nico Verbruggen on 29/11/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
class NginxConfigParserTest: XCTestCase {
|
||||
|
||||
static var regularUrl: URL {
|
||||
return Bundle(for: Self.self).url(forResource: "nicoverbruggen", withExtension: "test")!
|
||||
}
|
||||
|
||||
static var isolatedUrl: URL {
|
||||
return Bundle(for: Self.self).url(forResource: "nicoverbruggen_isolated", withExtension: "test")!
|
||||
}
|
||||
|
||||
func testCanDetermineIsolation() throws {
|
||||
XCTAssertNil(
|
||||
NginxConfigParser(filePath: NginxConfigParserTest.regularUrl.path).isolatedVersion
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
"8.1",
|
||||
NginxConfigParser(filePath: NginxConfigParserTest.isolatedUrl.path).isolatedVersion
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -32,6 +32,7 @@ class ValetConfigParserTest: XCTestCase {
|
||||
"/Users/username/.config/valet/Sites",
|
||||
"/Users/username/Sites"
|
||||
])
|
||||
XCTAssertEqual(config.defaultSite, "/Users/username/default-site")
|
||||
XCTAssertEqual(config.loopback, "127.0.0.1")
|
||||
}
|
||||
|
||||
|
93
phpmon-tests/Test Files/nicoverbruggen.test
Normal file
@ -0,0 +1,93 @@
|
||||
server {
|
||||
listen 127.0.0.1:80;
|
||||
#listen 127.0.0.1:80; # valet loopback
|
||||
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:443 ssl http2;
|
||||
#listen 127.0.0.1:443 ssl http2; # valet loopback
|
||||
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||
root /;
|
||||
charset utf-8;
|
||||
client_max_body_size 512M;
|
||||
http2_push_preload on;
|
||||
|
||||
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||
internal;
|
||||
alias /;
|
||||
try_files $uri $uri/;
|
||||
}
|
||||
|
||||
ssl_certificate "/Users/nicoverbruggen/.config/valet/Certificates/nicoverbruggen.test.crt";
|
||||
ssl_certificate_key "/Users/nicoverbruggen/.config/valet/Certificates/nicoverbruggen.test.key";
|
||||
|
||||
location / {
|
||||
rewrite ^ "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php" last;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
access_log off;
|
||||
error_log "/Users/nicoverbruggen/.config/valet/Log/nginx-error.log";
|
||||
|
||||
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass "unix:/Users/nicoverbruggen/.config/valet/valet.sock";
|
||||
fastcgi_index "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:60;
|
||||
#listen 127.0.0.1:60; # valet loopback
|
||||
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||
root /;
|
||||
charset utf-8;
|
||||
client_max_body_size 128M;
|
||||
|
||||
add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive';
|
||||
|
||||
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||
internal;
|
||||
alias /;
|
||||
try_files $uri $uri/;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^ "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php" last;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
access_log off;
|
||||
error_log "/Users/nicoverbruggen/.config/valet/Log/nginx-error.log";
|
||||
|
||||
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass "unix:/Users/nicoverbruggen/.config/valet/valet.sock";
|
||||
fastcgi_index "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
94
phpmon-tests/Test Files/nicoverbruggen_isolated.test
Normal file
@ -0,0 +1,94 @@
|
||||
server {
|
||||
listen 127.0.0.1:80;
|
||||
#listen 127.0.0.1:80; # valet loopback
|
||||
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:443 ssl http2;
|
||||
#listen 127.0.0.1:443 ssl http2; # valet loopback
|
||||
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||
root /;
|
||||
charset utf-8;
|
||||
client_max_body_size 512M;
|
||||
http2_push_preload on;
|
||||
|
||||
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||
internal;
|
||||
alias /;
|
||||
try_files $uri $uri/;
|
||||
}
|
||||
|
||||
ssl_certificate "/Users/nicoverbruggen/.config/valet/Certificates/nicoverbruggen.test.crt";
|
||||
ssl_certificate_key "/Users/nicoverbruggen/.config/valet/Certificates/nicoverbruggen.test.key";
|
||||
|
||||
location / {
|
||||
rewrite ^ "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php" last;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
access_log off;
|
||||
error_log "/Users/nicoverbruggen/.config/valet/Log/nginx-error.log";
|
||||
|
||||
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
# ISOLATED_PHP_VERSION=php@8.1
|
||||
fastcgi_pass "unix:/Users/nicoverbruggen/.config/valet/valet81.sock";
|
||||
fastcgi_index "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:60;
|
||||
#listen 127.0.0.1:60; # valet loopback
|
||||
server_name nicoverbruggen.test www.nicoverbruggen.test *.nicoverbruggen.test;
|
||||
root /;
|
||||
charset utf-8;
|
||||
client_max_body_size 128M;
|
||||
|
||||
add_header X-Robots-Tag 'noindex, nofollow, nosnippet, noarchive';
|
||||
|
||||
location /41c270e4-5535-4daa-b23e-c269744c2f45/ {
|
||||
internal;
|
||||
alias /;
|
||||
try_files $uri $uri/;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^ "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php" last;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
access_log off;
|
||||
error_log "/Users/nicoverbruggen/.config/valet/Log/nginx-error.log";
|
||||
|
||||
error_page 404 "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_pass "unix:/Users/nicoverbruggen/.config/valet/valet.sock";
|
||||
fastcgi_index "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME "/Users/nicoverbruggen/.composer/vendor/laravel/valet/server.php";
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
@ -4,5 +4,6 @@
|
||||
"/Users/username/.config/valet/Sites",
|
||||
"/Users/username/Sites"
|
||||
],
|
||||
"loopback": "127.0.0.1"
|
||||
"loopback": "127.0.0.1",
|
||||
"default": "/Users/username/default-site"
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ class PhpVersionDetectionTest: XCTestCase {
|
||||
"unrelatedphp@1.0", // should be omitted, invalid
|
||||
"php@5.6",
|
||||
"php@5.4" // should be omitted, not supported
|
||||
], checkBinaries: false)
|
||||
], checkBinaries: false, generateHelpers: false)
|
||||
|
||||
XCTAssertEqual(outcome, ["8.0", "7.0", "5.6"])
|
||||
XCTAssertEqual(outcome, ["8.0", "7.0"])
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class ValetVersionExtractorTest: XCTestCase {
|
||||
|
||||
func testDetermineValetVersion() {
|
||||
let version = valet("--version", sudo: false)
|
||||
XCTAssert(version.contains("Laravel Valet 2."))
|
||||
XCTAssert(version.contains("Laravel Valet 2") || version.contains("Laravel Valet 3"))
|
||||
}
|
||||
|
||||
}
|
||||
|
25
phpmon/Assets.xcassets/IconDefault.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Default.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Default@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
phpmon/Assets.xcassets/IconDefault.imageset/Default.png
vendored
Normal file
After Width: | Height: | Size: 861 B |
BIN
phpmon/Assets.xcassets/IconDefault.imageset/Default@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
25
phpmon/Assets.xcassets/Isolated.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Isolated.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Isolated@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
phpmon/Assets.xcassets/Isolated.imageset/Isolated.png
vendored
Normal file
After Width: | Height: | Size: 690 B |
BIN
phpmon/Assets.xcassets/Isolated.imageset/Isolated@2x.png
vendored
Normal file
After Width: | Height: | Size: 827 B |
@ -41,8 +41,8 @@ class Actions {
|
||||
"\(Paths.brew) services stop dnsmasq",
|
||||
]
|
||||
var cellarCommands = [
|
||||
"chown -R \(Paths.whoami):staff \(Paths.cellarPath)/nginx",
|
||||
"chown -R \(Paths.whoami):staff \(Paths.cellarPath)/dnsmasq"
|
||||
"chown -R \(Paths.whoami):admin \(Paths.cellarPath)/nginx",
|
||||
"chown -R \(Paths.whoami):admin \(Paths.cellarPath)/dnsmasq"
|
||||
]
|
||||
|
||||
PhpEnv.shared.availablePhpVersions.forEach { version in
|
||||
@ -50,7 +50,7 @@ class Actions {
|
||||
? "php"
|
||||
: "php@\(version)"
|
||||
servicesCommands.append("\(Paths.brew) services stop \(formula)")
|
||||
cellarCommands.append("chown -R \(Paths.whoami):staff \(Paths.cellarPath)/\(formula)")
|
||||
cellarCommands.append("chown -R \(Paths.whoami):admin \(Paths.cellarPath)/\(formula)")
|
||||
}
|
||||
|
||||
let script =
|
||||
@ -124,25 +124,13 @@ class Actions {
|
||||
If this does not solve the issue, the user may need to install additional
|
||||
extensions and/or run `composer global update`.
|
||||
*/
|
||||
public static func fixMyValet()
|
||||
public static func fixMyValet(completed: @escaping () -> Void)
|
||||
{
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
|
||||
PhpEnv.shared.detectPhpVersions().forEach { (version) in
|
||||
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
|
||||
brew("unlink php@\(version)")
|
||||
brew("services stop \(formula)")
|
||||
brew("services stop \(formula)", sudo: true)
|
||||
}
|
||||
|
||||
brew("services stop dnsmasq")
|
||||
brew("services stop php")
|
||||
brew("services stop nginx")
|
||||
|
||||
brew("link php --overwrite --force")
|
||||
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
brew("services restart php", sudo: true)
|
||||
brew("services restart nginx", sudo: true)
|
||||
InternalSwitcher().performSwitch(to: PhpEnv.brewPhpVersion, completion: {
|
||||
brew("services restart dnsmasq", sudo: true)
|
||||
brew("services restart php", sudo: true)
|
||||
brew("services restart nginx", sudo: true)
|
||||
completed()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ struct Constants {
|
||||
// STABLE RELEASES
|
||||
// ====================
|
||||
// Versions of PHP that are stable and are supported.
|
||||
"5.6",
|
||||
"5.6", // only supported when Valet 2.x is active
|
||||
"7.0",
|
||||
"7.1",
|
||||
"7.2",
|
||||
|
@ -20,16 +20,19 @@ struct HomebrewService: Decodable, Equatable {
|
||||
let error_log_path: String?
|
||||
|
||||
public static func loadAll(
|
||||
filter: [String] = [PhpEnv.phpInstall.formula, "nginx", "dnsmasq"]
|
||||
) async -> [HomebrewService] {
|
||||
return try! JSONDecoder().decode(
|
||||
[HomebrewService].self,
|
||||
from: Shell.pipe(
|
||||
"sudo \(Paths.brew) services info --all --json",
|
||||
requiresPath: true
|
||||
).data(using: .utf8)!
|
||||
).filter({ service in
|
||||
return filter.contains(service.name)
|
||||
})
|
||||
filter: [String] = [PhpEnv.phpInstall.formula, "nginx", "dnsmasq"],
|
||||
completion: @escaping ([HomebrewService]) -> Void
|
||||
) {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let data = Shell
|
||||
.pipe("sudo \(Paths.brew) services info --all --json", requiresPath: true)
|
||||
.data(using: .utf8)!
|
||||
|
||||
let services = try! JSONDecoder()
|
||||
.decode([HomebrewService].self, from: data)
|
||||
.filter({ return filter.contains($0.name) })
|
||||
|
||||
completion(services)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,13 +117,23 @@ class PhpEnv {
|
||||
/**
|
||||
Extracts valid PHP versions from an array of strings.
|
||||
This array of strings is usually retrieved from `grep`.
|
||||
|
||||
If `generateHelpers` is set to true, after detecting
|
||||
all versions, helper scripts are generated as well.
|
||||
*/
|
||||
public func extractPhpVersions(
|
||||
from versions: [String],
|
||||
checkBinaries: Bool = true
|
||||
checkBinaries: Bool = true,
|
||||
generateHelpers: Bool = true
|
||||
) -> [String] {
|
||||
var output : [String] = []
|
||||
|
||||
var supported = Constants.SupportedPhpVersions
|
||||
|
||||
if !Valet.enabled(feature: .supportForPhp56) {
|
||||
supported.removeAll { $0 == "5.6" }
|
||||
}
|
||||
|
||||
versions.filter { (version) -> Bool in
|
||||
// Omit everything that doesn't start with php@
|
||||
// (e.g. something-php@8.0 won't be detected)
|
||||
@ -133,13 +143,17 @@ class PhpEnv {
|
||||
// Only append the version if it doesn't already exist (avoid dupes),
|
||||
// is supported and where the binary exists (avoids broken installs)
|
||||
if !output.contains(version)
|
||||
&& Constants.SupportedPhpVersions.contains(version)
|
||||
&& supported.contains(version)
|
||||
&& (checkBinaries ? Filesystem.fileExists("\(Paths.optPath)/php@\(version)/bin/php") : true)
|
||||
{
|
||||
output.append(version)
|
||||
}
|
||||
}
|
||||
|
||||
if generateHelpers {
|
||||
output.forEach { PhpHelper.generate(for: $0) }
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
|
60
phpmon/Common/PHP/PHP Version/PhpHelper.swift
Normal file
@ -0,0 +1,60 @@
|
||||
//
|
||||
// PhpHelper.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 17/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class PhpHelper {
|
||||
|
||||
static let keyPhrase = "This file was automatically generated by PHP Monitor."
|
||||
|
||||
public static func generate(for version: String) {
|
||||
// Take the PHP version (e.g. "7.2") and generate a dotless version
|
||||
let dotless = version.replacingOccurrences(of: ".", with: "")
|
||||
|
||||
do {
|
||||
let destination = "/usr/local/bin/pm\(dotless)"
|
||||
if FileManager.default.fileExists(atPath: destination) {
|
||||
let contents = try String(contentsOfFile: destination)
|
||||
if !contents.contains(keyPhrase) {
|
||||
Log.info("The file at '\(destination)' already exists and was not generated by PHP Monitor (or is unreadable). Not updating this file.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Let's follow the symlink to the PHP binary folder
|
||||
let path = URL(fileURLWithPath: "\(Paths.optPath)/php@\(version)/bin")
|
||||
.resolvingSymlinksInPath().path
|
||||
|
||||
// The contents of the script!
|
||||
let script = """
|
||||
#!/bin/zsh
|
||||
# \(keyPhrase)
|
||||
# It reflects the location of PHP \(version)'s binaries on your system.
|
||||
# Usage: . pm\(dotless)
|
||||
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] \\
|
||||
&& echo "PHP Monitor has enabled this terminal to use PHP \(version)." \\
|
||||
|| echo "You must run '. pm\(dotless)' (or 'source pm\(dotless)') instead!";
|
||||
export PATH=\(path):$PATH
|
||||
"""
|
||||
|
||||
// Write to the destination
|
||||
try script.write(
|
||||
to: URL(fileURLWithPath: destination),
|
||||
atomically: true,
|
||||
encoding: String.Encoding.utf8
|
||||
)
|
||||
|
||||
// Make sure the file is executable
|
||||
Shell.run("chmod +x \(destination)")
|
||||
} catch {
|
||||
print(error)
|
||||
Log.err("Could not write PHP Monitor helper for PHP \(version) to /usr/local/bin/pm\(dotless)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
class PhpInstallation {
|
||||
|
||||
var longVersion: PhpVersionNumber
|
||||
var versionNumber: PhpVersionNumber
|
||||
|
||||
/**
|
||||
In order to determine details about a PHP installation, we’ll simply run `php-config --version`
|
||||
@ -19,7 +19,7 @@ class PhpInstallation {
|
||||
init(_ version: String) {
|
||||
|
||||
let phpConfigExecutablePath = "\(Paths.optPath)/php@\(version)/bin/php-config"
|
||||
self.longVersion = PhpVersionNumber.make(from: version)!
|
||||
self.versionNumber = PhpVersionNumber.make(from: version)!
|
||||
|
||||
if Filesystem.fileExists(phpConfigExecutablePath) {
|
||||
let longVersionString = Command.execute(
|
||||
@ -29,7 +29,7 @@ class PhpInstallation {
|
||||
|
||||
// The parser should always work, or the string has to be very unusual.
|
||||
// If so, the app SHOULD crash, so that the users report what's up.
|
||||
self.longVersion = try! PhpVersionNumber.parse(longVersionString)
|
||||
self.versionNumber = try! PhpVersionNumber.parse(longVersionString)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,20 +24,26 @@ class InternalSwitcher: PhpSwitcher {
|
||||
{
|
||||
Log.info("Switching to \(version), unlinking all versions...")
|
||||
|
||||
let isolated = Valet.shared.sites.filter { site in
|
||||
site.isolatedPhpVersion != nil
|
||||
}.map { site in
|
||||
return site.isolatedPhpVersion!.versionNumber.homebrewVersion
|
||||
}
|
||||
|
||||
var versions: Set<String> = [version]
|
||||
|
||||
if (Valet.enabled(feature: .isolatedSites)) {
|
||||
versions = versions.union(isolated)
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
PhpEnv.shared.availablePhpVersions.forEach { (available) in
|
||||
group.enter()
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let formula = (available == PhpEnv.brewPhpVersion)
|
||||
? "php" : "php@\(available)"
|
||||
|
||||
brew("unlink \(formula)")
|
||||
brew("services stop \(formula)", sudo: true)
|
||||
|
||||
Log.perf("Unlinked and stopped services for \(formula)")
|
||||
|
||||
self.disableDefaultPhpFpmPool(available)
|
||||
self.stopPhpVersion(available)
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
@ -46,16 +52,58 @@ class InternalSwitcher: PhpSwitcher {
|
||||
Log.info("All versions have been unlinked!")
|
||||
Log.info("Linking the new version!")
|
||||
|
||||
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
|
||||
brew("link \(formula) --overwrite --force")
|
||||
brew("services start \(formula)", sudo: true)
|
||||
|
||||
for formula in versions {
|
||||
self.startPhpVersion(formula, primary: (version == formula))
|
||||
}
|
||||
|
||||
Log.info("Restarting nginx, just to be sure!")
|
||||
brew("services restart nginx", sudo: true)
|
||||
|
||||
Log.info("The new version has been linked!")
|
||||
Log.info("The new version(s) have been linked!")
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
private func disableDefaultPhpFpmPool(_ version: String) {
|
||||
let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf"
|
||||
if FileManager.default.fileExists(atPath: pool) {
|
||||
Log.info("A default `www.conf` file was found in the php-fpm.d directory for PHP \(version).")
|
||||
let existing = URL(string: "file://\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf")!
|
||||
let new = URL(string: "file://\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf.disabled-by-phpmon")!
|
||||
do {
|
||||
try FileManager.default.moveItem(at: existing, to: new)
|
||||
Log.info("Success: A default `www.conf` file was disabled for PHP \(version).")
|
||||
} catch {
|
||||
Log.err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stopPhpVersion(_ version: String) {
|
||||
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
|
||||
brew("unlink \(formula)")
|
||||
brew("services stop \(formula)", sudo: true)
|
||||
Log.info("Unlinked and stopped services for \(formula)")
|
||||
}
|
||||
|
||||
private func startPhpVersion(_ version: String, primary: Bool) {
|
||||
let formula = (version == PhpEnv.brewPhpVersion) ? "php" : "php@\(version)"
|
||||
|
||||
if (primary) {
|
||||
Log.info("\(formula) is the primary formula, linking and starting services...")
|
||||
brew("link \(formula) --overwrite --force")
|
||||
} else {
|
||||
Log.info("\(formula) is an isolated PHP version, starting services only...")
|
||||
}
|
||||
|
||||
brew("services start \(formula)", sudo: true)
|
||||
|
||||
if Valet.enabled(feature: .isolatedSites) && primary {
|
||||
let socketVersion = version.replacingOccurrences(of: ".", with: "")
|
||||
Shell.run("ln -sF ~/.config/valet/valet\(socketVersion).sock ~/.config/valet/valet.sock")
|
||||
Log.info("Symlinked new socket version (valet\(socketVersion).sock → valet.sock).")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import HotKey
|
||||
import Cocoa
|
||||
|
||||
extension App {
|
||||
|
@ -6,7 +6,6 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import HotKey
|
||||
|
||||
class App {
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<capability name="Image references" minToolsVersion="12.0"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Search Toolbar Item" minToolsVersion="12.0" minSystemVersion="11.0"/>
|
||||
@ -387,10 +387,10 @@
|
||||
<window key="window" title="Domains" subtitle="Linked & Parked" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="raw-02-3Q1">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="425" y="461" width="550" height="263"/>
|
||||
<rect key="contentRect" x="425" y="461" width="600" height="263"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<view key="contentView" id="uVx-Da-x4I">
|
||||
<rect key="frame" x="0.0" y="0.0" width="550" height="263"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="263"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
<toolbar key="toolbar" implicitIdentifier="594015E3-8428-4926-9341-4B8CE4C7E373" autosavesConfiguration="NO" allowsUserCustomization="NO" showsBaselineSeparator="NO" displayMode="iconOnly" sizeMode="regular" id="OOz-oZ-vlN">
|
||||
@ -407,12 +407,12 @@
|
||||
<action selector="pressedReload:" target="8Ec-9q-82s" id="fLc-bD-oYQ"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<searchToolbarItem implicitItemIdentifier="629F0782-3C5F-4CD0-9396-3A054A422180" label="Search" paletteLabel="Search" visibilityPriority="1001" id="Q7Z-fw-lB9">
|
||||
<searchToolbarItem implicitItemIdentifier="7C834FBE-7118-4082-A09F-7CBECEC1356A" label="Search" paletteLabel="Search" visibilityPriority="1001" id="G2g-jS-RVc">
|
||||
<nil key="toolTip"/>
|
||||
<searchField key="view" verticalHuggingPriority="750" textCompletion="NO" id="oWA-TH-Pm7">
|
||||
<searchField key="view" verticalHuggingPriority="750" textCompletion="NO" id="0gE-Yr-MLy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" sendsSearchStringImmediately="YES" id="3NO-6x-aLc">
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" sendsSearchStringImmediately="YES" id="vp9-vH-goQ">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -423,7 +423,7 @@
|
||||
<defaultToolbarItems>
|
||||
<toolbarItem reference="GsC-ra-40U"/>
|
||||
<toolbarItem reference="YtK-vM-5y7"/>
|
||||
<searchToolbarItem reference="Q7Z-fw-lB9"/>
|
||||
<searchToolbarItem reference="G2g-jS-RVc"/>
|
||||
</defaultToolbarItems>
|
||||
</toolbar>
|
||||
<connections>
|
||||
@ -431,13 +431,13 @@
|
||||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<outlet property="searchToolbarItem" destination="Q7Z-fw-lB9" id="J5o-oh-VhO"/>
|
||||
<outlet property="searchToolbarItem" destination="G2g-jS-RVc" id="xlc-qe-k7e"/>
|
||||
<segue destination="JZI-Vd-9oq" kind="relationship" relationship="window.shadowedContentViewController" id="9Gy-Gw-hPH"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="VCP-dF-cqM" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-374" y="758"/>
|
||||
<point key="canvasLocation" x="-374" y="746"/>
|
||||
</scene>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="HTI-x5-rOp">
|
||||
@ -532,7 +532,7 @@ Gw
|
||||
</connections>
|
||||
</button>
|
||||
<button wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="n5T-nn-k3j">
|
||||
<rect key="frame" x="13" y="13" width="81" height="32"/>
|
||||
<rect key="frame" x="13" y="13" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Tertiary" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="mzA-Uu-gyf">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -651,7 +651,7 @@ Gw
|
||||
<color key="fillColor" name="windowBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</box>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PVw-cM-qAB">
|
||||
<rect key="frame" x="364" y="13" width="103" height="32"/>
|
||||
<rect key="frame" x="363" y="13" width="104" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Create Link" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="WwW-Wv-I8s">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -720,7 +720,7 @@ Gw
|
||||
<rect key="frame" x="20" y="185" width="440" height="22"/>
|
||||
<pathCell key="cell" selectable="YES" refusesFirstResponder="YES" alignment="left" id="m8d-XF-kh9">
|
||||
<font key="font" metaFont="system"/>
|
||||
<url key="url" string="file:///Users/nicoverbruggen/Code/nicoverbruggen.be/"/>
|
||||
<url key="url" string="file:///Users/"/>
|
||||
</pathCell>
|
||||
</pathControl>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="P0B-Ht-R8n">
|
||||
@ -732,7 +732,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="900-Z2-tID">
|
||||
<rect key="frame" x="230" y="23" width="128" height="14"/>
|
||||
<rect key="frame" x="229" y="23" width="128" height="14"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="That link already exists." id="jOt-n6-TQf">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -788,35 +788,68 @@ Gw
|
||||
</viewController>
|
||||
<customObject id="6XV-bG-0N1" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="191" y="1099"/>
|
||||
<point key="canvasLocation" x="191" y="1098.5"/>
|
||||
</scene>
|
||||
<!--Site ListVC-->
|
||||
<scene sceneID="aZt-6w-TFl">
|
||||
<objects>
|
||||
<viewController identifier="siteList" storyboardIdentifier="siteList" id="JZI-Vd-9oq" customClass="SiteListVC" customModule="PHP_Monitor" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="rIZ-4U-bhj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="309"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView id="j65-Lf-0lG">
|
||||
<rect key="frame" x="9" y="0.0" width="581" height="203"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</customView>
|
||||
<scrollView autohidesScrollers="YES" horizontalLineScroll="54" horizontalPageScroll="10" verticalLineScroll="54" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p0j-eB-I2i">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="309"/>
|
||||
<clipView key="contentView" id="6IL-DW-37w">
|
||||
<rect key="frame" x="1" y="1" width="598" height="307"/>
|
||||
<scrollView borderType="none" horizontalLineScroll="54" horizontalPageScroll="10" verticalLineScroll="54" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p0j-eB-I2i">
|
||||
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
|
||||
<clipView key="contentView" ambiguous="YES" drawsBackground="NO" id="6IL-DW-37w">
|
||||
<rect key="frame" x="0.0" y="0.0" width="626" height="309"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" rowHeight="54" rowSizeStyle="automatic" viewBased="YES" id="cp3-34-pQj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="598" height="307"/>
|
||||
<tableView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" multipleSelection="NO" autosaveName="phpmon-sitelist-columns" rowHeight="54" headerView="xUg-Mq-OSh" viewBased="YES" id="cp3-34-pQj">
|
||||
<rect key="frame" x="0.0" y="0.0" width="662" height="281"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="17" height="0.0"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableViewGridLines key="gridStyleMask" horizontal="YES"/>
|
||||
<color key="gridColor" name="quaternaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn width="586" minWidth="40" maxWidth="10000" id="oeH-B2-0rA">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<tableColumn identifier="TLS" width="36" minWidth="36" maxWidth="36" id="z6X-Ni-Eev">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="TLS">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="OsS-YW-O4s">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="compare:" sortKey="Secure"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="siteListTLSCell" id="hft-M4-nWb" customClass="SiteListTLSCell" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<rect key="frame" x="18" y="0.0" width="34" height="55"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Wel-Ho-Kpp">
|
||||
<rect key="frame" x="7" y="18" width="20" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="7mC-me-Nse"/>
|
||||
<constraint firstAttribute="height" constant="20" id="AjD-xX-suI"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="Lock" id="gK0-Mh-K9Y"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Wel-Ho-Kpp" firstAttribute="centerY" secondItem="hft-M4-nWb" secondAttribute="centerY" id="L6B-iA-BCQ"/>
|
||||
<constraint firstItem="Wel-Ho-Kpp" firstAttribute="centerX" secondItem="hft-M4-nWb" secondAttribute="centerX" id="jAF-AV-EeX"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="imageViewLock" destination="Wel-Ho-Kpp" id="iji-uw-8we"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="DOMAIN" width="290" minWidth="250" maxWidth="10000" id="oeH-B2-0rA">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Domain">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
@ -825,14 +858,15 @@ Gw
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="compare:" sortKey="Domain"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="siteItem" wantsLayer="YES" id="5GY-nN-BWd" customClass="SiteListCell" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<rect key="frame" x="8" y="0.0" width="581" height="54"/>
|
||||
<tableCellView identifier="siteListNameCell" wantsLayer="YES" id="5GY-nN-BWd" customClass="SiteListNameCell" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<rect key="frame" x="69" y="0.0" width="290" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="XJL-Uw-frD">
|
||||
<rect key="frame" x="38" y="26" width="145" height="16"/>
|
||||
<rect key="frame" x="3" y="26" width="145" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="my-domain-name.test" id="SGC-Gm-Mxd">
|
||||
<font key="font" metaFont="systemSemibold" size="13"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -840,106 +874,166 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="CXK-Q9-CpO">
|
||||
<rect key="frame" x="38" y="12" width="75" height="14"/>
|
||||
<rect key="frame" x="3" y="12" width="75" height="14"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="~/path/to/site" id="fe7-Ha-mR9">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QPX-eu-eV8">
|
||||
<rect key="frame" x="10" y="22" width="20" height="20"/>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="XJL-Uw-frD" secondAttribute="trailing" constant="20" symbolic="YES" id="62d-gz-2lX"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="CXK-Q9-CpO" secondAttribute="trailing" constant="20" symbolic="YES" id="Agn-Ag-QYd"/>
|
||||
<constraint firstItem="CXK-Q9-CpO" firstAttribute="leading" secondItem="XJL-Uw-frD" secondAttribute="leading" id="Ojw-VZ-3EG"/>
|
||||
<constraint firstItem="XJL-Uw-frD" firstAttribute="top" secondItem="5GY-nN-BWd" secondAttribute="top" constant="12" id="QeE-c7-I9U"/>
|
||||
<constraint firstItem="CXK-Q9-CpO" firstAttribute="top" secondItem="XJL-Uw-frD" secondAttribute="bottom" id="VKg-Vq-sYa"/>
|
||||
<constraint firstItem="XJL-Uw-frD" firstAttribute="leading" secondItem="5GY-nN-BWd" secondAttribute="leading" constant="5" id="u1q-b0-iKq"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="labelPathName" destination="CXK-Q9-CpO" id="iVZ-cL-azB"/>
|
||||
<outlet property="labelSiteName" destination="XJL-Uw-frD" id="f0t-vd-W68"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="ENVIRONMENT" width="100" minWidth="100" maxWidth="150" id="hzb-XI-Out">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Active">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="ryK-6j-qWW">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="compare:" sortKey="PHP"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="siteListPhpCell" wantsLayer="YES" id="T49-0U-d58" customClass="SiteListPhpCell" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<rect key="frame" x="376" y="0.0" width="100" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZXQ-bg-Xba">
|
||||
<rect key="frame" x="27" y="18" width="70" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="Bmk-CN-Yyn"/>
|
||||
<constraint firstAttribute="height" constant="20" id="d4z-lb-Ww0"/>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="70" id="MBa-bB-DTB"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="Lock" id="aJ0-ia-YrZ"/>
|
||||
<buttonCell key="cell" type="inline" title="PHP X.X" bezelStyle="inline" alignment="center" borderStyle="border" inset="2" id="Tfk-YR-L4B">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="smallSystemBold"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="pressedPhpVersion:" target="T49-0U-d58" id="jVO-TS-F6d"/>
|
||||
</connections>
|
||||
</button>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Yq0-qk-bFt">
|
||||
<rect key="frame" x="1" y="18" width="18" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="18" id="5fd-EQ-BgV"/>
|
||||
<constraint firstAttribute="height" constant="18" id="nP7-13-SSn"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="S66-ow-Jo1">
|
||||
<imageReference key="image" image="Checkmark" symbolScale="default"/>
|
||||
</imageCell>
|
||||
<color key="contentTintColor" name="IconColorGreen"/>
|
||||
</imageView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jKi-Ls-7FZ">
|
||||
<rect key="frame" x="474" y="28" width="64" height="11"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="DRIVER TYPE" id="fjd-eb-itv">
|
||||
<font key="font" metaFont="miniSystem"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TbX-e2-3QL">
|
||||
<rect key="frame" x="474" y="15" width="36" height="14"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Driver" id="GMt-SG-vFl">
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Yq0-qk-bFt" firstAttribute="centerY" secondItem="T49-0U-d58" secondAttribute="centerY" id="4Kz-lX-kOP"/>
|
||||
<constraint firstItem="ZXQ-bg-Xba" firstAttribute="leading" secondItem="Yq0-qk-bFt" secondAttribute="trailing" constant="8" symbolic="YES" id="6jM-Qf-SG3"/>
|
||||
<constraint firstItem="ZXQ-bg-Xba" firstAttribute="centerY" secondItem="T49-0U-d58" secondAttribute="centerY" id="FFK-4B-qIb"/>
|
||||
<constraint firstItem="ZXQ-bg-Xba" firstAttribute="centerX" secondItem="T49-0U-d58" secondAttribute="centerX" constant="12" id="zjr-UQ-Dd7"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="buttonPhpVersion" destination="ZXQ-bg-Xba" id="vxL-if-CCC"/>
|
||||
<outlet property="imageViewPhpVersionOK" destination="Yq0-qk-bFt" id="0hT-cZ-9NI"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="KIND" width="36" minWidth="36" maxWidth="36" id="7EV-ZL-92u">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Kind">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="kAe-u7-lN6">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="compare:" sortKey="Kind"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="siteListKindCell" wantsLayer="YES" id="AhT-xR-16a" customClass="SiteListKindCell" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<rect key="frame" x="493" y="0.0" width="36" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="sYR-vb-OW1">
|
||||
<rect key="frame" x="9" y="18" width="18" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="18" id="XcB-uw-szU"/>
|
||||
<constraint firstAttribute="height" constant="18" id="bGN-Vh-Sh0"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="IconLinked" id="T6e-IU-aZy"/>
|
||||
<color key="contentTintColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="sYR-vb-OW1" firstAttribute="centerY" secondItem="AhT-xR-16a" secondAttribute="centerY" id="4mB-oS-T6I"/>
|
||||
<constraint firstItem="sYR-vb-OW1" firstAttribute="centerX" secondItem="AhT-xR-16a" secondAttribute="centerX" id="LyQ-XZ-J3u"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="imageViewType" destination="sYR-vb-OW1" id="txH-es-roq"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="TYPE" width="100" minWidth="100" maxWidth="100" id="ncU-Ge-cyW">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="center" title="Project Type">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="qHO-1M-TT2">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<sortDescriptor key="sortDescriptorPrototype" selector="compare:" sortKey="Type"/>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="siteListTypeCell" wantsLayer="YES" id="ntU-Rl-ciP" customClass="SiteListTypeCell" customModule="PHP_Monitor" customModuleProvider="target">
|
||||
<rect key="frame" x="546" y="0.0" width="97" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ljl-8B-key">
|
||||
<rect key="frame" x="6" y="26" width="93" height="14"/>
|
||||
<textFieldCell key="cell" alignment="left" title="Laravel" id="0lu-L6-oKr">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="syz-LF-l6P">
|
||||
<rect key="frame" x="0.0" y="-2" width="581" height="5"/>
|
||||
</box>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="0NQ-ZD-CqD">
|
||||
<rect key="frame" x="450" y="18" width="18" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="18" id="Suw-gm-AEi"/>
|
||||
<constraint firstAttribute="height" constant="18" id="qO6-vg-5nC"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="IconLinked" id="2ng-pK-kvv"/>
|
||||
<color key="contentTintColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
</imageView>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="3xt-wC-hUJ">
|
||||
<rect key="frame" x="363" y="18" width="75" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="75" id="VI8-MP-7Hv"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="inline" title=" PHP X.X" bezelStyle="inline" alignment="center" borderStyle="border" inset="2" id="anZ-hP-G0R">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="smallSystemBold"/>
|
||||
</buttonCell>
|
||||
<color key="contentTintColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<connections>
|
||||
<action selector="pressedPhpVersion:" target="5GY-nN-BWd" id="mB5-WD-aZy"/>
|
||||
</connections>
|
||||
</button>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="5aN-ZI-D7U">
|
||||
<rect key="frame" x="341" y="20" width="14" height="14"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="14" id="NKD-Pc-okU"/>
|
||||
<constraint firstAttribute="width" constant="14" id="wrl-lJ-3eN"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="Checkmark" id="R5o-Cd-a91"/>
|
||||
<color key="contentTintColor" name="IconColorGreen"/>
|
||||
</imageView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aPK-Xc-J4B">
|
||||
<rect key="frame" x="6" y="15" width="93" height="11"/>
|
||||
<textFieldCell key="cell" alignment="left" title="PHP 8.0" id="puf-Jh-ham">
|
||||
<font key="font" metaFont="miniSystem"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="0NQ-ZD-CqD" firstAttribute="leading" secondItem="3xt-wC-hUJ" secondAttribute="trailing" constant="12" id="2G8-Ow-FTu"/>
|
||||
<constraint firstItem="3xt-wC-hUJ" firstAttribute="leading" secondItem="5aN-ZI-D7U" secondAttribute="trailing" constant="8" symbolic="YES" id="39Z-nB-kXx"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="TbX-e2-3QL" secondAttribute="trailing" constant="20" symbolic="YES" id="3vE-LR-S7N"/>
|
||||
<constraint firstItem="TbX-e2-3QL" firstAttribute="leading" secondItem="0NQ-ZD-CqD" secondAttribute="trailing" constant="8" symbolic="YES" id="4cb-D9-8d1"/>
|
||||
<constraint firstItem="XJL-Uw-frD" firstAttribute="leading" secondItem="QPX-eu-eV8" secondAttribute="trailing" constant="10" id="55y-3V-RYt"/>
|
||||
<constraint firstItem="syz-LF-l6P" firstAttribute="leading" secondItem="5GY-nN-BWd" secondAttribute="leading" id="8QK-nf-Fiw"/>
|
||||
<constraint firstItem="QPX-eu-eV8" firstAttribute="top" secondItem="XJL-Uw-frD" secondAttribute="top" id="9QB-jZ-k1V"/>
|
||||
<constraint firstItem="QPX-eu-eV8" firstAttribute="leading" secondItem="5GY-nN-BWd" secondAttribute="leading" constant="10" id="GOj-sw-ZlZ"/>
|
||||
<constraint firstItem="TbX-e2-3QL" firstAttribute="top" secondItem="jKi-Ls-7FZ" secondAttribute="bottom" constant="-1" id="J29-wT-Uex"/>
|
||||
<constraint firstItem="CXK-Q9-CpO" firstAttribute="leading" secondItem="XJL-Uw-frD" secondAttribute="leading" id="Ojw-VZ-3EG"/>
|
||||
<constraint firstAttribute="trailing" secondItem="syz-LF-l6P" secondAttribute="trailing" id="PWd-5k-AlD"/>
|
||||
<constraint firstItem="XJL-Uw-frD" firstAttribute="top" secondItem="5GY-nN-BWd" secondAttribute="top" constant="12" id="QeE-c7-I9U"/>
|
||||
<constraint firstItem="0NQ-ZD-CqD" firstAttribute="centerY" secondItem="5GY-nN-BWd" secondAttribute="centerY" id="Utr-aa-tqX"/>
|
||||
<constraint firstItem="CXK-Q9-CpO" firstAttribute="top" secondItem="XJL-Uw-frD" secondAttribute="bottom" id="VKg-Vq-sYa"/>
|
||||
<constraint firstItem="5aN-ZI-D7U" firstAttribute="centerY" secondItem="3xt-wC-hUJ" secondAttribute="centerY" id="a6n-E2-i2x"/>
|
||||
<constraint firstItem="TbX-e2-3QL" firstAttribute="centerY" secondItem="5GY-nN-BWd" secondAttribute="centerY" constant="5" id="cN8-zO-fnc"/>
|
||||
<constraint firstAttribute="bottom" secondItem="syz-LF-l6P" secondAttribute="bottom" id="gj7-cJ-Lle"/>
|
||||
<constraint firstItem="0NQ-ZD-CqD" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="CXK-Q9-CpO" secondAttribute="trailing" constant="8" symbolic="YES" id="iEd-Y3-zhp"/>
|
||||
<constraint firstItem="0NQ-ZD-CqD" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="XJL-Uw-frD" secondAttribute="trailing" constant="8" symbolic="YES" id="lLA-Jx-Q4W"/>
|
||||
<constraint firstItem="3xt-wC-hUJ" firstAttribute="centerY" secondItem="5GY-nN-BWd" secondAttribute="centerY" id="vhb-WC-3NC"/>
|
||||
<constraint firstAttribute="trailing" secondItem="jKi-Ls-7FZ" secondAttribute="trailing" constant="45" id="vwD-Sg-Lzc"/>
|
||||
<constraint firstItem="jKi-Ls-7FZ" firstAttribute="leading" secondItem="TbX-e2-3QL" secondAttribute="leading" id="zjN-s3-2Ww"/>
|
||||
<constraint firstItem="aPK-Xc-J4B" firstAttribute="top" secondItem="Ljl-8B-key" secondAttribute="bottom" id="0Ta-4x-l8E"/>
|
||||
<constraint firstItem="Ljl-8B-key" firstAttribute="centerY" secondItem="ntU-Rl-ciP" secondAttribute="centerY" constant="-6" id="9Lh-QK-qnR"/>
|
||||
<constraint firstItem="aPK-Xc-J4B" firstAttribute="leading" secondItem="Ljl-8B-key" secondAttribute="leading" id="PNb-yh-XOn"/>
|
||||
<constraint firstItem="aPK-Xc-J4B" firstAttribute="trailing" secondItem="Ljl-8B-key" secondAttribute="trailing" id="T28-JT-Zkq"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Ljl-8B-key" secondAttribute="trailing" id="Y7O-lc-fqb"/>
|
||||
<constraint firstItem="Ljl-8B-key" firstAttribute="leading" secondItem="ntU-Rl-ciP" secondAttribute="leading" constant="8" id="idV-Vu-YeP"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="buttonPhpVersion" destination="3xt-wC-hUJ" id="LpB-7n-qUr"/>
|
||||
<outlet property="imageViewLock" destination="QPX-eu-eV8" id="Nnh-kB-adG"/>
|
||||
<outlet property="imageViewPhpVersionOK" destination="5aN-ZI-D7U" id="ePz-Cb-dWk"/>
|
||||
<outlet property="imageViewType" destination="0NQ-ZD-CqD" id="Cph-FN-LaY"/>
|
||||
<outlet property="labelDriver" destination="TbX-e2-3QL" id="qJh-Ak-Dge"/>
|
||||
<outlet property="labelDriverType" destination="jKi-Ls-7FZ" id="ZTq-pP-qUC"/>
|
||||
<outlet property="labelPathName" destination="CXK-Q9-CpO" id="iVZ-cL-azB"/>
|
||||
<outlet property="labelSiteName" destination="XJL-Uw-frD" id="f0t-vd-W68"/>
|
||||
<outlet property="labelDriver" destination="Ljl-8B-key" id="82M-LT-pHT"/>
|
||||
<outlet property="labelPhpVersion" destination="aPK-Xc-J4B" id="TdR-xE-xhX"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
@ -951,22 +1045,27 @@ Gw
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<nil key="backgroundColor"/>
|
||||
</clipView>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="300" id="R3Z-g3-tYQ"/>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="600" id="iRQ-sz-oyv"/>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="620" id="iRQ-sz-oyv"/>
|
||||
</constraints>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="TDE-ff-DQT">
|
||||
<rect key="frame" x="1" y="292" width="598" height="16"/>
|
||||
<scroller key="horizontalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="TDE-ff-DQT">
|
||||
<rect key="frame" x="0.0" y="293" width="626" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="wFn-93-f10">
|
||||
<rect key="frame" x="558" y="29" width="15" height="225"/>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="wFn-93-f10">
|
||||
<rect key="frame" x="610" y="28" width="16" height="281"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<tableHeaderView key="headerView" wantsLayer="YES" id="xUg-Mq-OSh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="662" height="28"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableHeaderView>
|
||||
</scrollView>
|
||||
<progressIndicator maxValue="100" displayedWhenStopped="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="ZiS-Gq-TLQ">
|
||||
<rect key="frame" x="285" y="150" width="30" height="30"/>
|
||||
<rect key="frame" x="298" y="150" width="30" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="30" id="XK3-AR-Oc0"/>
|
||||
<constraint firstAttribute="height" constant="30" id="lfW-dB-Eu3"/>
|
||||
@ -989,7 +1088,7 @@ Gw
|
||||
</viewController>
|
||||
<customObject id="HgD-aB-bQb" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="251" y="741.5"/>
|
||||
<point key="canvasLocation" x="388" y="715.5"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
@ -166,6 +166,23 @@ class Startup {
|
||||
descriptionText: "startup.errors.services_json_error.desc".localized
|
||||
),
|
||||
// =================================================================================
|
||||
// Determine that the Valet configuration JSON file is valid.
|
||||
// =================================================================================
|
||||
EnvironmentCheck(
|
||||
command: {
|
||||
// Detect additional binaries (e.g. Composer)
|
||||
Paths.shared.detectBinaryPaths()
|
||||
// Load the configuration file (config.json)
|
||||
Valet.shared.loadConfiguration()
|
||||
// This check fails when the config is nil
|
||||
return Valet.shared.config == nil
|
||||
},
|
||||
name: "`config.json` was valid",
|
||||
titleText: "startup.errors.valet_json_invalid.title".localized,
|
||||
subtitleText: "startup.errors.valet_json_invalid.subtitle".localized,
|
||||
descriptionText: "startup.errors.valet_json_invalid.desc".localized
|
||||
),
|
||||
// =================================================================================
|
||||
// Determine the Valet version and ensure it isn't unknown.
|
||||
// =================================================================================
|
||||
EnvironmentCheck(
|
||||
|
@ -36,7 +36,8 @@ struct PhpFrameworks {
|
||||
"statamic/cms": "Statamic",
|
||||
"johnpbloch/wordpress-core": "WordPress",
|
||||
"zendframework/zendframework": "Zend",
|
||||
"zendframework/zend-mvc": "Zend"
|
||||
"zendframework/zend-mvc": "Zend",
|
||||
"typo3/cms-core": "Typo3",
|
||||
|
||||
// TODO (5.1): Handle these in v5.1
|
||||
// "magento/*": "Magento",
|
||||
@ -58,6 +59,10 @@ struct PhpFrameworks {
|
||||
"/wp-config.php",
|
||||
"/wp-config-sample.php"
|
||||
],
|
||||
"Typo3": [
|
||||
"/typo3",
|
||||
"/public/typo3",
|
||||
]
|
||||
]
|
||||
|
||||
/**
|
||||
|
39
phpmon/Domain/Integrations/Valet/NginxConfigParser.swift
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// NginxConfigParser.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 15/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class NginxConfigParser {
|
||||
|
||||
var contents: String!
|
||||
|
||||
init(filePath: String) {
|
||||
self.contents = try! String(contentsOfFile: filePath
|
||||
.replacingOccurrences(of: "~", with: "/Users/\(Paths.whoami)")
|
||||
)
|
||||
}
|
||||
|
||||
lazy var isolatedVersion: String? = {
|
||||
let regex = try! NSRegularExpression(
|
||||
// PHP versions have (so far) never needed multiple digits for version numbers
|
||||
pattern: #"(ISOLATED_PHP_VERSION=(php)?(@)?)((?<major>\d)(.)?(?<minor>\d))"#,
|
||||
options: []
|
||||
)
|
||||
|
||||
let match = regex.firstMatch(in: contents, range: NSMakeRange(0, contents.count))
|
||||
|
||||
if match == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
let major: String = contents[Range(match!.range(withName: "major"), in: contents)!]
|
||||
let minor: String = contents[Range(match!.range(withName: "minor"), in: contents)!]
|
||||
|
||||
return "\(major).\(minor)"
|
||||
}()
|
||||
}
|
134
phpmon/Domain/Integrations/Valet/SiteScanner.swift
Normal file
@ -0,0 +1,134 @@
|
||||
//
|
||||
// ValetSiteScanner.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 19/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol SiteScanner
|
||||
{
|
||||
func resolveSiteCount(paths: [String]) -> Int
|
||||
|
||||
func resolveSitesFrom(paths: [String]) -> [ValetSite]
|
||||
|
||||
func resolveSite(path: String) -> ValetSite?
|
||||
}
|
||||
|
||||
class FakeSiteScanner: SiteScanner
|
||||
{
|
||||
let fakes = [
|
||||
ValetSite(fakeWithName: "laravel", tld: "test", secure: true, path: "~/Code/laravel/framework", linked: true),
|
||||
|
||||
ValetSite(fakeWithName: "tailwind", tld: "test", secure: true, path: "~/Code/tailwind/site", linked: true, constraint: "8.0"),
|
||||
|
||||
ValetSite(fakeWithName: "forge", tld: "test", secure: true, path: "~/Code/laravel/forge", linked: true),
|
||||
|
||||
ValetSite(fakeWithName: "concord", tld: "test", secure: false,
|
||||
path: "~/Code/concord", linked: true, driver: "Laravel (^8.0)", constraint: "^7.4", isolated: "7.4"),
|
||||
|
||||
ValetSite(fakeWithName: "drupal", tld: "test", secure: false,
|
||||
path: "~/Sites/drupal", linked: false, driver: "Drupal", constraint: "^7.4", isolated: "7.4"),
|
||||
|
||||
ValetSite(fakeWithName: "wordpress", tld: "test", secure: false,
|
||||
path: "~/Sites/wordpress", linked: false, driver: "WordPress", constraint: "^7.4", isolated: "7.4")
|
||||
]
|
||||
|
||||
func resolveSiteCount(paths: [String]) -> Int {
|
||||
return fakes.count
|
||||
}
|
||||
|
||||
func resolveSitesFrom(paths: [String]) -> [ValetSite] {
|
||||
return fakes
|
||||
}
|
||||
|
||||
func resolveSite(path: String) -> ValetSite? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
class ValetSiteScanner: SiteScanner
|
||||
{
|
||||
func resolveSiteCount(paths: [String]) -> Int {
|
||||
return paths.map { path in
|
||||
|
||||
let entries = try! FileManager.default
|
||||
.contentsOfDirectory(atPath: path)
|
||||
|
||||
return entries
|
||||
.map { self.isSite($0, forPath: path) }
|
||||
.filter{ $0 == true}
|
||||
.count
|
||||
|
||||
}.reduce(0, +)
|
||||
}
|
||||
|
||||
func resolveSitesFrom(paths: [String]) -> [ValetSite] {
|
||||
var sites: [ValetSite] = []
|
||||
|
||||
paths.forEach { path in
|
||||
let entries = try! FileManager.default
|
||||
.contentsOfDirectory(atPath: path)
|
||||
|
||||
return entries.forEach {
|
||||
if let site = self.resolveSite(path: "\(path)/\($0)") {
|
||||
sites.append(site)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sites
|
||||
}
|
||||
|
||||
/**
|
||||
Determines whether the site can be resolved as a symbolic link or as a directory.
|
||||
Regular files are ignored, and the site is added to Valet's list of sites.
|
||||
*/
|
||||
func resolveSite(path: String) -> ValetSite? {
|
||||
// Get the TLD from the global Valet object
|
||||
let tld = Valet.shared.config.tld
|
||||
|
||||
// See if the file is a symlink, if so, resolve it
|
||||
guard let attrs = try? FileManager.default.attributesOfItem(atPath: path) else {
|
||||
Log.warn("Could not parse the site: \(path), skipping!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// We can also determine whether the thing at the path is a directory, too
|
||||
let type = attrs[FileAttributeKey.type] as! FileAttributeType
|
||||
|
||||
// We should also check that we can interpret the path correctly
|
||||
if URL(fileURLWithPath: path).lastPathComponent == "" {
|
||||
Log.warn("Could not parse the site: \(path), skipping!")
|
||||
return nil
|
||||
}
|
||||
|
||||
if type == FileAttributeType.typeSymbolicLink {
|
||||
return ValetSite(aliasPath: path, tld: tld)
|
||||
} else if type == FileAttributeType.typeDirectory {
|
||||
return ValetSite(absolutePath: path, tld: tld)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
Determines whether the site can be resolved as a symbolic link or as a directory.
|
||||
Regular files are ignored. Returns true if the path can be parsed.
|
||||
*/
|
||||
private func isSite(_ entry: String, forPath path: String) -> Bool {
|
||||
let siteDir = path + "/" + entry
|
||||
|
||||
let attrs = try! FileManager.default.attributesOfItem(atPath: siteDir)
|
||||
|
||||
let type = attrs[FileAttributeKey.type] as! FileAttributeType
|
||||
|
||||
if type == FileAttributeType.typeSymbolicLink || type == FileAttributeType.typeDirectory {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
@ -10,6 +10,11 @@ import Foundation
|
||||
|
||||
class Valet {
|
||||
|
||||
enum FeatureFlag {
|
||||
case isolatedSites,
|
||||
supportForPhp56
|
||||
}
|
||||
|
||||
static let shared = Valet()
|
||||
|
||||
/// The version of Valet that was detected.
|
||||
@ -24,28 +29,56 @@ class Valet {
|
||||
/// Whether we're busy with some blocking operation.
|
||||
var isBusy: Bool = false
|
||||
|
||||
/// Various feature flags. Enabled based on the installed Valet version.
|
||||
var features: [FeatureFlag] = []
|
||||
|
||||
/// When initialising the Valet singleton assume no sites loaded. We will load the version later.
|
||||
init() {
|
||||
self.version = nil
|
||||
self.sites = []
|
||||
}
|
||||
|
||||
/**
|
||||
If marketing mode is enabled, show a list of sites that are used for promotional screenshots.
|
||||
This can be done by swapping out the real Valet scanner with one that always returns a fixed
|
||||
list of fake sites. You should not interact with these sites!
|
||||
*/
|
||||
static var siteScanner: SiteScanner {
|
||||
if ProcessInfo.processInfo.environment["PHPMON_MARKETING_MODE"] != nil {
|
||||
return FakeSiteScanner()
|
||||
}
|
||||
|
||||
return ValetSiteScanner()
|
||||
}
|
||||
|
||||
/**
|
||||
Check if a particular feature is enabled.
|
||||
*/
|
||||
public static func enabled(feature: FeatureFlag) -> Bool {
|
||||
return self.shared.features.contains(feature)
|
||||
}
|
||||
|
||||
/**
|
||||
We don't want to load the initial config.json file as soon as the class is initialised.
|
||||
|
||||
Instead, we'll defer the loading of the configuration file once the initial app checks
|
||||
have passed: if the user does not have Valet installed, we'll crash the app because we
|
||||
force unwrap the file. Currently, this does also mean that if the JSON is invalid or
|
||||
incompatible with the `Decodable` `Valet.Configuration` class, that the app will crash.
|
||||
have passed: otherwise the file might not exist, leading to a crash.
|
||||
|
||||
Since version 5.2, it is no longer possible for an invalid file to crash the app.
|
||||
If the JSON is invalid when the app launches, an alert will be presented, however.
|
||||
*/
|
||||
public func loadConfiguration() {
|
||||
let file = FileManager.default.homeDirectoryForCurrentUser
|
||||
.appendingPathComponent(".config/valet/config.json")
|
||||
|
||||
// TODO: (5.1) Fix loading of invalid JSON: do not crash the app
|
||||
config = try! JSONDecoder().decode(
|
||||
Valet.Configuration.self,
|
||||
from: try! String(contentsOf: file, encoding: .utf8).data(using: .utf8)!
|
||||
)
|
||||
do {
|
||||
config = try JSONDecoder().decode(
|
||||
Valet.Configuration.self,
|
||||
from: try String(contentsOf: file, encoding: .utf8).data(using: .utf8)!
|
||||
)
|
||||
} catch {
|
||||
Log.err(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,7 +87,7 @@ class Valet {
|
||||
(This is done to keep the startup speed as fast as possible.)
|
||||
*/
|
||||
public func startPreloadingSites() {
|
||||
let maximumPreload = 30
|
||||
let maximumPreload = 50
|
||||
let foundSites = self.countPaths()
|
||||
if foundSites <= maximumPreload {
|
||||
// Preload the sites and their drivers
|
||||
@ -67,14 +100,34 @@ class Valet {
|
||||
|
||||
/**
|
||||
Reloads the list of sites, assuming that the list isn't being reloaded at the time.
|
||||
We don't want to do duplicate or parallel work!
|
||||
(We don't want to do duplicate or parallel work!)
|
||||
*/
|
||||
public func reloadSites() {
|
||||
loadConfiguration()
|
||||
|
||||
if (isBusy) {
|
||||
return
|
||||
}
|
||||
|
||||
resolvePaths(tld: config.tld)
|
||||
resolvePaths()
|
||||
}
|
||||
|
||||
/**
|
||||
Depending on the version of Valet that is active, the feature set of PHP Monitor will change.
|
||||
|
||||
In version 6.0, support for Valet 2.x will be dropped, but until then features are evaluated by using the helper
|
||||
`enabled(feature)`, which contains information about the feature set of the version of Valet that is currently
|
||||
in use. This allows PHP Monitor to do different things when Valet 3.0 is enabled.
|
||||
*/
|
||||
public func evaluateFeatureSupport() -> Void {
|
||||
let isOlderThanVersionThree = version.versionCompare("3.0") == .orderedAscending
|
||||
|
||||
if isOlderThanVersionThree {
|
||||
self.features.append(.supportForPhp56)
|
||||
} else {
|
||||
Log.info("This version of Valet supports isolation.")
|
||||
self.features.append(.isolatedSites)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,6 +136,10 @@ class Valet {
|
||||
installed is not recent enough.
|
||||
*/
|
||||
public func validateVersion() -> Void {
|
||||
// 1. Evaluate feature support
|
||||
Valet.shared.evaluateFeatureSupport()
|
||||
|
||||
// 2. Notify user if the version is too old
|
||||
if version.versionCompare(Constants.MinimumRecommendedValetVersion) == .orderedAscending {
|
||||
let version = version
|
||||
Log.warn("Valet version \(version!) is too old! (recommended: \(Constants.MinimumRecommendedValetVersion))")
|
||||
@ -104,82 +161,28 @@ class Valet {
|
||||
Returns a count of how many sites are linked and parked.
|
||||
*/
|
||||
private func countPaths() -> Int {
|
||||
var count = 0
|
||||
for path in config.paths {
|
||||
let entries = try! FileManager.default.contentsOfDirectory(atPath: path)
|
||||
for entry in entries {
|
||||
if resolveSite(entry, forPath: path) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
return Self.siteScanner
|
||||
.resolveSiteCount(paths: config.paths)
|
||||
}
|
||||
|
||||
/**
|
||||
Resolves all paths and creates linked or parked site instances that can be referenced later.
|
||||
*/
|
||||
private func resolvePaths(tld: String) {
|
||||
private func resolvePaths() {
|
||||
isBusy = true
|
||||
|
||||
sites = []
|
||||
sites = Self.siteScanner
|
||||
.resolveSitesFrom(paths: config.paths)
|
||||
.sorted { $0.absolutePath < $1.absolutePath }
|
||||
|
||||
for path in config.paths {
|
||||
let entries = try! FileManager.default.contentsOfDirectory(atPath: path)
|
||||
for entry in entries {
|
||||
resolvePath(entry, forPath: path, tld: tld)
|
||||
}
|
||||
if let defaultPath = Valet.shared.config.defaultSite,
|
||||
let site = ValetSiteScanner().resolveSite(path: defaultPath) {
|
||||
sites.insert(site, at: 0)
|
||||
}
|
||||
|
||||
sites = sites.sorted { $0.absolutePath < $1.absolutePath }
|
||||
|
||||
isBusy = false
|
||||
}
|
||||
|
||||
/**
|
||||
Determines whether the site can be resolved as a symbolic link or as a directory.
|
||||
Regular files are ignored. Returns true if the path can be parsed.
|
||||
*/
|
||||
private func resolveSite(_ entry: String, forPath path: String) -> Bool {
|
||||
let siteDir = path + "/" + entry
|
||||
|
||||
let attrs = try! FileManager.default.attributesOfItem(atPath: siteDir)
|
||||
|
||||
let type = attrs[FileAttributeKey.type] as! FileAttributeType
|
||||
|
||||
if type == FileAttributeType.typeSymbolicLink || type == FileAttributeType.typeDirectory {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
Determines whether the site can be resolved as a symbolic link or as a directory.
|
||||
Regular files are ignored, and the site is added to Valet's list of sites.
|
||||
*/
|
||||
private func resolvePath(_ entry: String, forPath path: String, tld: String) {
|
||||
let siteDir = path + "/" + entry
|
||||
|
||||
// See if the file is a symlink, if so, resolve it
|
||||
let attrs = try! FileManager.default.attributesOfItem(atPath: siteDir)
|
||||
|
||||
// We can also determine whether the thing at the path is a directory, too
|
||||
let type = attrs[FileAttributeKey.type] as! FileAttributeType
|
||||
|
||||
// We should also check that we can interpret the path correctly
|
||||
if URL(fileURLWithPath: siteDir).lastPathComponent == "" {
|
||||
Log.warn("Could not parse the site: \(siteDir), skipping!")
|
||||
return
|
||||
}
|
||||
|
||||
if type == FileAttributeType.typeSymbolicLink {
|
||||
sites.append(ValetSite(aliasPath: siteDir, tld: tld))
|
||||
} else if type == FileAttributeType.typeDirectory {
|
||||
sites.append(ValetSite(absolutePath: siteDir, tld: tld))
|
||||
}
|
||||
}
|
||||
|
||||
struct Configuration: Decodable {
|
||||
/// Top level domain suffix. Usually "test" but can be set to something else.
|
||||
/// - Important: Does not include the actual dot. ("test", not ".test"!)
|
||||
|
44
phpmon/Domain/Integrations/Valet/ValetSite+Fake.swift
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// ValetSite+Fake.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 19/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension ValetSite {
|
||||
|
||||
convenience init(
|
||||
fakeWithName name: String,
|
||||
tld: String,
|
||||
secure: Bool,
|
||||
path: String,
|
||||
linked: Bool,
|
||||
driver: String = "Laravel (^9.0)",
|
||||
constraint: String = "^8.1",
|
||||
isolated: String? = nil
|
||||
) {
|
||||
self.init(name: name, tld: tld, absolutePath: path, aliasPath: nil, makeDeterminations: false)
|
||||
self.secured = secure
|
||||
self.composerPhp = constraint
|
||||
|
||||
self.composerPhpCompatibleWithLinked = self.composerPhp.split(separator: "|")
|
||||
.map { string in
|
||||
return PhpVersionNumberCollection.make(from: [PhpEnv.phpInstall.version.long])
|
||||
.matching(constraint: string.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
.count > 0
|
||||
}.contains(true)
|
||||
|
||||
self.driver = driver
|
||||
self.driverDeterminedByComposer = true
|
||||
if linked {
|
||||
self.aliasPath = self.absolutePath
|
||||
}
|
||||
if let isolated = isolated {
|
||||
self.isolatedPhpVersion = PhpInstallation(isolated)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -11,10 +11,10 @@ import Foundation
|
||||
class ValetSite {
|
||||
|
||||
/// Name of the site. Does not include the TLD.
|
||||
var name: String!
|
||||
var name: String
|
||||
|
||||
/// The absolute path to the directory that is served.
|
||||
var absolutePath: String!
|
||||
var absolutePath: String
|
||||
|
||||
/// The absolute path to the directory that is served,
|
||||
/// replacing the user's home folder with ~.
|
||||
@ -23,6 +23,12 @@ class ValetSite {
|
||||
.replacingOccurrences(of: "/Users/\(Paths.whoami)", with: "~")
|
||||
}()
|
||||
|
||||
/// The TLD used to locate this site.
|
||||
var tld: String = "test"
|
||||
|
||||
/// The PHP version that is being used to serve this site specifically (if not global).
|
||||
var isolatedPhpVersion: PhpInstallation?
|
||||
|
||||
/// Location of the alias. If set, this is a linked domain.
|
||||
var aliasPath: String?
|
||||
|
||||
@ -47,6 +53,12 @@ class ValetSite {
|
||||
/// How the PHP version was determined.
|
||||
var composerPhpSource: VersionSource = .unknown
|
||||
|
||||
/// Which version of PHP is actually used to serve this site.
|
||||
var servingPhpVersion: String {
|
||||
return self.isolatedPhpVersion?.versionNumber.homebrewVersion
|
||||
?? PhpEnv.phpInstall.version.short
|
||||
}
|
||||
|
||||
enum VersionSource: String {
|
||||
case unknown = "unknown"
|
||||
case require = "require"
|
||||
@ -54,34 +66,59 @@ class ValetSite {
|
||||
case valetphprc = "valetphprc"
|
||||
}
|
||||
|
||||
init() {}
|
||||
init(
|
||||
name: String,
|
||||
tld: String,
|
||||
absolutePath: String,
|
||||
aliasPath: String? = nil,
|
||||
makeDeterminations: Bool = true
|
||||
) {
|
||||
self.name = name
|
||||
self.tld = tld
|
||||
self.absolutePath = absolutePath
|
||||
self.aliasPath = aliasPath
|
||||
self.secured = false
|
||||
|
||||
if makeDeterminations {
|
||||
determineSecured()
|
||||
determineComposerPhpVersion()
|
||||
determineDriver()
|
||||
determineIsolated()
|
||||
}
|
||||
}
|
||||
|
||||
convenience init(absolutePath: String, tld: String) {
|
||||
self.init()
|
||||
self.absolutePath = absolutePath
|
||||
self.name = URL(fileURLWithPath: absolutePath).lastPathComponent
|
||||
self.aliasPath = nil
|
||||
determineSecured(tld)
|
||||
determineComposerPhpVersion()
|
||||
determineDriver()
|
||||
let name = URL(fileURLWithPath: absolutePath).lastPathComponent
|
||||
self.init(name: name, tld: tld, absolutePath: absolutePath)
|
||||
}
|
||||
|
||||
convenience init(aliasPath: String, tld: String) {
|
||||
self.init()
|
||||
self.absolutePath = try! FileManager.default.destinationOfSymbolicLink(atPath: aliasPath)
|
||||
self.name = URL(fileURLWithPath: aliasPath).lastPathComponent
|
||||
self.aliasPath = aliasPath
|
||||
determineSecured(tld)
|
||||
determineComposerPhpVersion()
|
||||
determineDriver()
|
||||
let name = URL(fileURLWithPath: aliasPath).lastPathComponent
|
||||
let absolutePath = try! FileManager.default.destinationOfSymbolicLink(atPath: aliasPath)
|
||||
self.init(name: name, tld: tld, absolutePath: absolutePath, aliasPath: aliasPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Determine whether a site is isolated.
|
||||
*/
|
||||
public func determineIsolated() {
|
||||
if let version = ValetSite.isolatedVersion("~/.config/valet/Nginx/\(self.name).\(self.tld)") {
|
||||
if (!PhpEnv.shared.cachedPhpInstallations.keys.contains(version)) {
|
||||
Log.err("The PHP version \(version) is isolated for the site \(self.name) but that PHP version is unavailable.")
|
||||
return
|
||||
}
|
||||
self.isolatedPhpVersion = PhpEnv.shared.cachedPhpInstallations[version]
|
||||
} else {
|
||||
self.isolatedPhpVersion = nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if a certificate file can be found in the `valet/Certificates` directory.
|
||||
- Note: The file is not validated, only its presence is checked.
|
||||
*/
|
||||
public func determineSecured(_ tld: String) {
|
||||
secured = Filesystem.fileExists("~/.config/valet/Certificates/\(self.name!).\(tld).key")
|
||||
public func determineSecured() {
|
||||
secured = Filesystem.fileExists("~/.config/valet/Certificates/\(self.name).\(self.tld).key")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,7 +184,7 @@ class ValetSite {
|
||||
as well as the requested PHP version. If no composer.json file is found, nothing happens.
|
||||
*/
|
||||
private func determineComposerInformation() {
|
||||
let path = "\(absolutePath!)/composer.json"
|
||||
let path = "\(absolutePath)/composer.json"
|
||||
|
||||
do {
|
||||
if Filesystem.fileExists(path) {
|
||||
@ -168,7 +205,7 @@ class ValetSite {
|
||||
Checks the contents of the .valetphprc file and determine the version, if possible.
|
||||
*/
|
||||
private func determineValetPhpFileInfo() {
|
||||
let path = "\(absolutePath!)/.valetphprc"
|
||||
let path = "\(absolutePath)/.valetphprc"
|
||||
|
||||
do {
|
||||
if Filesystem.fileExists(path) {
|
||||
@ -182,4 +219,16 @@ class ValetSite {
|
||||
Log.err("Something went wrong parsing the .valetphprc file")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: File Parsing
|
||||
|
||||
public static func isolatedVersion(_ filePath: String) -> String? {
|
||||
if Filesystem.fileExists(filePath) {
|
||||
return NginxConfigParser
|
||||
.init(filePath: filePath)
|
||||
.isolatedVersion
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -32,13 +32,13 @@ extension MainMenu {
|
||||
return
|
||||
}
|
||||
|
||||
asyncExecution {
|
||||
Actions.fixMyValet()
|
||||
} success: {
|
||||
if previousVersion == PhpEnv.brewPhpVersion {
|
||||
self.presentAlertForSameVersion()
|
||||
} else {
|
||||
self.presentAlertForDifferentVersion(version: previousVersion)
|
||||
Actions.fixMyValet {
|
||||
DispatchQueue.main.async {
|
||||
if previousVersion == PhpEnv.brewPhpVersion {
|
||||
self.presentAlertForSameVersion()
|
||||
} else {
|
||||
self.presentAlertForDifferentVersion(version: previousVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,9 +76,6 @@ extension MainMenu {
|
||||
Log.info("PHP Monitor has extracted the version number of Valet: \(Valet.shared.version!)")
|
||||
}
|
||||
|
||||
Paths.shared.detectBinaryPaths()
|
||||
|
||||
Valet.shared.loadConfiguration()
|
||||
Valet.shared.validateVersion()
|
||||
Valet.shared.startPreloadingSites()
|
||||
|
||||
|
@ -340,6 +340,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
|
||||
func menuWillOpen(_ menu: NSMenu) {
|
||||
// Make sure the shortcut key does not trigger this when the menu is open
|
||||
App.shared.shortcutHotkey?.isPaused = true
|
||||
NotificationCenter.default.post(name: Events.ServicesUpdated, object: nil)
|
||||
}
|
||||
|
||||
func menuDidClose(_ menu: NSMenu) {
|
||||
|
@ -44,21 +44,17 @@ class ServicesView: NSView, XibLoadable {
|
||||
)
|
||||
return item
|
||||
}
|
||||
|
||||
override func viewWillDraw() {
|
||||
super.viewWillDraw()
|
||||
Task { await self.loadData() }
|
||||
}
|
||||
|
||||
@objc func updateInformation() {
|
||||
Task { await self.loadData() }
|
||||
self.loadData()
|
||||
}
|
||||
|
||||
func loadData() async {
|
||||
self.applyAllInfoFieldsFromCachedValue()
|
||||
let services = await HomebrewService.loadAll()
|
||||
ServicesView.services = Dictionary(uniqueKeysWithValues: services.map{ ($0.name, $0) })
|
||||
func loadData() {
|
||||
self.applyAllInfoFieldsFromCachedValue()
|
||||
HomebrewService.loadAll { services in
|
||||
ServicesView.services = Dictionary(uniqueKeysWithValues: services.map{ ($0.name, $0) })
|
||||
self.applyAllInfoFieldsFromCachedValue()
|
||||
}
|
||||
}
|
||||
|
||||
func applyAllInfoFieldsFromCachedValue() {
|
||||
|
@ -97,15 +97,20 @@ class StatusMenu : NSMenu {
|
||||
func addFirstAidAndServicesMenuItems() {
|
||||
let services = NSMenuItem(title: "mi_other".localized, action: nil, keyEquivalent: "")
|
||||
let servicesMenu = NSMenu()
|
||||
servicesMenu.addItem(NSMenuItem(
|
||||
title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
|
||||
action: #selector(MainMenu.fixMyValet), keyEquivalent: "")
|
||||
)
|
||||
|
||||
servicesMenu.addItem(NSMenuItem(
|
||||
title: "mi_fix_brew_permissions".localized(),
|
||||
action: #selector(MainMenu.fixHomebrewPermissions), keyEquivalent: "")
|
||||
let fixMyValetMenuItem = NSMenuItem(
|
||||
title: "mi_fix_my_valet".localized(PhpEnv.brewPhpVersion),
|
||||
action: #selector(MainMenu.fixMyValet), keyEquivalent: ""
|
||||
)
|
||||
fixMyValetMenuItem.toolTip = "mi_fix_my_valet_tooltip".localized
|
||||
servicesMenu.addItem(fixMyValetMenuItem)
|
||||
|
||||
let fixHomebrewMenuItem = NSMenuItem(
|
||||
title: "mi_fix_brew_permissions".localized(),
|
||||
action: #selector(MainMenu.fixHomebrewPermissions), keyEquivalent: ""
|
||||
)
|
||||
fixHomebrewMenuItem.toolTip = "mi_fix_brew_permissions_tooltip".localized
|
||||
servicesMenu.addItem(fixHomebrewMenuItem)
|
||||
|
||||
servicesMenu.addItem(NSMenuItem.separator())
|
||||
servicesMenu.addItem(HeaderView.asMenuItem(text: "mi_services".localized))
|
||||
@ -139,7 +144,7 @@ class StatusMenu : NSMenu {
|
||||
|
||||
// Get the short and long version
|
||||
let shortVersion = PhpEnv.shared.availablePhpVersions[index]
|
||||
let longVersion = PhpEnv.shared.cachedPhpInstallations[shortVersion]!.longVersion
|
||||
let longVersion = PhpEnv.shared.cachedPhpInstallations[shortVersion]!.versionNumber
|
||||
|
||||
let long = Preferences.preferences[.fullPhpVersionDynamicIcon] as! Bool
|
||||
let versionString = long ? longVersion.toString() : shortVersion
|
||||
|
@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import HotKey
|
||||
import Carbon
|
||||
|
||||
class PrefsVC: NSViewController {
|
||||
|
@ -9,7 +9,6 @@
|
||||
import Foundation
|
||||
|
||||
import Foundation
|
||||
import HotKey
|
||||
import Cocoa
|
||||
|
||||
class HotkeyPreferenceView: NSView, XibLoadable {
|
||||
|
@ -63,7 +63,9 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
Shell.run("cd '\(path)' && \(Paths.valet) link '\(name)'", requiresPath: true)
|
||||
// Adding `valet links` is a workaround for Valet malforming the config.json file
|
||||
// TODO: I will have to investigate and report this behaviour if possible
|
||||
Shell.run("cd '\(path)' && \(Paths.valet) link '\(name)' && valet links", requiresPath: true)
|
||||
|
||||
self.dismissView(outcome: .OK)
|
||||
|
||||
|
14
phpmon/Domain/SiteList/Cells/SiteListCellProtocol.swift
Normal file
@ -0,0 +1,14 @@
|
||||
//
|
||||
// SiteListCellProtocol.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 03/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
protocol SiteListCellProtocol {
|
||||
func populateCell(with site: ValetSite)
|
||||
}
|
33
phpmon/Domain/SiteList/Cells/SiteListKindCell.swift
Normal file
@ -0,0 +1,33 @@
|
||||
//
|
||||
// SiteListTypeCell.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class SiteListKindCell: NSTableCellView, SiteListCellProtocol
|
||||
{
|
||||
static let reusableName = "siteListKindCell"
|
||||
|
||||
@IBOutlet weak var imageViewType: NSImageView!
|
||||
|
||||
func populateCell(with site: ValetSite) {
|
||||
// If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
|
||||
imageViewType.image = NSImage(
|
||||
named: site.aliasPath == nil
|
||||
? "IconParked"
|
||||
: "IconLinked"
|
||||
)
|
||||
|
||||
// Unless, of course, this is a default site
|
||||
if site.absolutePath == Valet.shared.config.defaultSite {
|
||||
imageViewType.image = NSImage(named: "IconDefault")
|
||||
}
|
||||
|
||||
imageViewType.contentTintColor = NSColor.tertiaryLabelColor
|
||||
}
|
||||
}
|
26
phpmon/Domain/SiteList/Cells/SiteListNameCell.swift
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// SiteListNameCell.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class SiteListNameCell: NSTableCellView, SiteListCellProtocol
|
||||
{
|
||||
static let reusableName = "siteListNameCell"
|
||||
|
||||
@IBOutlet weak var labelSiteName: NSTextField!
|
||||
@IBOutlet weak var labelPathName: NSTextField!
|
||||
|
||||
func populateCell(with site: ValetSite) {
|
||||
// Show the name of the site (including tld)
|
||||
labelSiteName.stringValue = "\(site.name).\(Valet.shared.config.tld)"
|
||||
|
||||
// Show the absolute path, except make sure to replace the /Users/username segment with ~ for readability
|
||||
labelPathName.stringValue = site.absolutePathRelative
|
||||
}
|
||||
}
|
90
phpmon/Domain/SiteList/Cells/SiteListPhpCell.swift
Normal file
@ -0,0 +1,90 @@
|
||||
//
|
||||
// SiteListPhpCell.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class SiteListPhpCell: NSTableCellView, SiteListCellProtocol
|
||||
{
|
||||
static let reusableName = "siteListPhpCell"
|
||||
|
||||
var site: ValetSite? = nil
|
||||
|
||||
@IBOutlet weak var buttonPhpVersion: NSButton!
|
||||
@IBOutlet weak var imageViewPhpVersionOK: NSImageView!
|
||||
|
||||
func populateCell(with site: ValetSite) {
|
||||
self.site = site
|
||||
|
||||
buttonPhpVersion.title = " PHP \(site.servingPhpVersion)"
|
||||
|
||||
if site.isolatedPhpVersion != nil {
|
||||
imageViewPhpVersionOK.isHidden = false
|
||||
imageViewPhpVersionOK.image = NSImage(named: "Isolated")
|
||||
} else {
|
||||
imageViewPhpVersionOK.isHidden = (site.composerPhp == "???" || !site.composerPhpCompatibleWithLinked)
|
||||
imageViewPhpVersionOK.image = NSImage(named: "Checkmark")
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func pressedPhpVersion(_ sender: Any) {
|
||||
guard let site = self.site else { return }
|
||||
|
||||
let alert = NSAlert.init()
|
||||
alert.alertStyle = .informational
|
||||
|
||||
var information = ""
|
||||
|
||||
if (self.site?.isolatedPhpVersion != nil) {
|
||||
information += "alert.composer_php_isolated.desc".localized(
|
||||
self.site!.isolatedPhpVersion!.versionNumber.homebrewVersion,
|
||||
PhpEnv.phpInstall.version.short
|
||||
)
|
||||
information += "\n\n"
|
||||
}
|
||||
|
||||
information += "alert.composer_php_requirement.type.\(site.composerPhpSource.rawValue)"
|
||||
.localized
|
||||
|
||||
alert.messageText = "alert.composer_php_requirement.title"
|
||||
.localized("\(site.name).\(Valet.shared.config.tld)", site.composerPhp)
|
||||
alert.informativeText = information
|
||||
|
||||
alert.addButton(withTitle: "site_link.close".localized)
|
||||
|
||||
var mapIndex: Int = NSApplication.ModalResponse.alertSecondButtonReturn.rawValue
|
||||
var map: [Int: String] = [:]
|
||||
|
||||
if site.isolatedPhpVersion == nil {
|
||||
// Determine which installed versions would be ideal to switch to,
|
||||
// but make sure to exclude the currently linked version
|
||||
PhpEnv.shared.validVersions(for: site.composerPhp).filter({ version in
|
||||
version.homebrewVersion != PhpEnv.phpInstall.version.short
|
||||
}).forEach { version in
|
||||
alert.addButton(withTitle: "site_link.switch_to_php".localized(version.homebrewVersion))
|
||||
map[mapIndex] = version.homebrewVersion
|
||||
mapIndex += 1
|
||||
}
|
||||
|
||||
// Site is not isolated, show options to switch global PHP version
|
||||
alert.beginSheetModal(for: App.shared.siteListWindowController!.window!) { response in
|
||||
if response.rawValue > NSApplication.ModalResponse.alertFirstButtonReturn.rawValue {
|
||||
if map.keys.contains(response.rawValue) {
|
||||
let version = map[response.rawValue]!
|
||||
Log.info("Pressed button to switch to \(version)")
|
||||
MainMenu.shared.switchToPhpVersion(version)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Site is isolated, do not show any options to switch
|
||||
alert.beginSheetModal(for: App.shared.siteListWindowController!.window!)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
24
phpmon/Domain/SiteList/Cells/SiteListTLSCell.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// SiteListNameCell.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class SiteListTLSCell: NSTableCellView, SiteListCellProtocol
|
||||
{
|
||||
static let reusableName = "siteListTLSCell"
|
||||
|
||||
@IBOutlet weak var imageViewLock: NSImageView!
|
||||
|
||||
func populateCell(with site: ValetSite) {
|
||||
// Show the green or red lock based on whether the site was secured
|
||||
imageViewLock.contentTintColor = site.secured
|
||||
? NSColor(named: "IconColorGreen") // green
|
||||
: NSColor(named: "IconColorRed")
|
||||
}
|
||||
}
|
31
phpmon/Domain/SiteList/Cells/SiteListTypeCell.swift
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// SiteListTypeCell.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 16/03/2022.
|
||||
// Copyright © 2022 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class SiteListTypeCell: NSTableCellView, SiteListCellProtocol
|
||||
{
|
||||
static let reusableName = "siteListTypeCell"
|
||||
|
||||
@IBOutlet weak var labelDriver: NSTextField!
|
||||
@IBOutlet weak var labelPhpVersion: NSTextField!
|
||||
|
||||
func populateCell(with site: ValetSite) {
|
||||
labelDriver.stringValue = site.driver ?? "driver.not_detected".localized
|
||||
|
||||
// Determine the Laravel version
|
||||
if site.driver == "Laravel" && site.notableComposerDependencies.keys.contains("laravel/framework") {
|
||||
let constraint = site.notableComposerDependencies["laravel/framework"]!
|
||||
labelDriver.stringValue = "Laravel (\(constraint))"
|
||||
}
|
||||
|
||||
// PHP version
|
||||
labelPhpVersion.stringValue = site.composerPhp == "???" ? "Any PHP" : "PHP \(site.composerPhp)"
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
//
|
||||
// SiteListCell.swift
|
||||
// PHP Monitor
|
||||
//
|
||||
// Created by Nico Verbruggen on 03/12/2021.
|
||||
// Copyright © 2021 Nico Verbruggen. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import AppKit
|
||||
|
||||
class SiteListCell: NSTableCellView
|
||||
{
|
||||
var site: ValetSite? = nil
|
||||
|
||||
@IBOutlet weak var labelSiteName: NSTextField!
|
||||
@IBOutlet weak var labelPathName: NSTextField!
|
||||
@IBOutlet weak var labelDriverType: NSTextField!
|
||||
|
||||
@IBOutlet weak var imageViewLock: NSImageView!
|
||||
@IBOutlet weak var imageViewType: NSImageView!
|
||||
|
||||
@IBOutlet weak var labelDriver: NSTextField!
|
||||
|
||||
@IBOutlet weak var buttonPhpVersion: NSButton!
|
||||
@IBOutlet weak var imageViewPhpVersionOK: NSImageView!
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
}
|
||||
|
||||
func populateCell(with site: ValetSite) {
|
||||
self.site = site
|
||||
|
||||
// Make sure to show the TLD
|
||||
labelSiteName.stringValue = "\(site.name!).\(Valet.shared.config.tld)"
|
||||
|
||||
// Show the absolute path, except make sure to replace the /Users/username segment with ~ for readability
|
||||
labelPathName.stringValue = site.absolutePathRelative
|
||||
|
||||
// If the `aliasPath` is nil, we're dealing with a parked site (otherwise: linked).
|
||||
imageViewType.image = NSImage(
|
||||
named: site.aliasPath == nil
|
||||
? "IconParked"
|
||||
: "IconLinked"
|
||||
)
|
||||
imageViewType.contentTintColor = NSColor.tertiaryLabelColor
|
||||
|
||||
// Show the green or red lock based on whether the site was secured
|
||||
imageViewLock.contentTintColor = site.secured ?
|
||||
NSColor(named: "IconColorGreen") // green
|
||||
: NSColor(named: "IconColorRed")
|
||||
|
||||
// Show the current driver
|
||||
labelDriverType.stringValue = site.driverDeterminedByComposer
|
||||
? "Project Type".uppercased()
|
||||
: "Driver Type".uppercased()
|
||||
|
||||
labelDriver.stringValue = site.driver ?? "driver.not_detected".localized
|
||||
|
||||
// Determine the Laravel version
|
||||
if site.driver == "Laravel" && site.notableComposerDependencies.keys.contains("laravel/framework") {
|
||||
let constraint = site.notableComposerDependencies["laravel/framework"]!
|
||||
labelDriver.stringValue = "Laravel (\(constraint))"
|
||||
}
|
||||
|
||||
// Show the PHP version
|
||||
buttonPhpVersion.title = " PHP \(site.composerPhp) "
|
||||
buttonPhpVersion.isHidden = (site.composerPhp == "???")
|
||||
|
||||
|
||||
imageViewPhpVersionOK.isHidden = (site.composerPhp == "???" || !site.composerPhpCompatibleWithLinked)
|
||||
}
|
||||
|
||||
@IBAction func pressedPhpVersion(_ sender: Any) {
|
||||
guard let site = self.site else { return }
|
||||
|
||||
let alert = NSAlert.init()
|
||||
alert.alertStyle = .informational
|
||||
|
||||
alert.messageText = "alert.composer_php_requirement.title"
|
||||
.localized("\(site.name!).\(Valet.shared.config.tld)", site.composerPhp)
|
||||
alert.informativeText = "alert.composer_php_requirement.type.\(site.composerPhpSource.rawValue)"
|
||||
.localized
|
||||
|
||||
alert.addButton(withTitle: "site_link.close".localized)
|
||||
|
||||
var mapIndex: Int = NSApplication.ModalResponse.alertSecondButtonReturn.rawValue
|
||||
var map: [Int: String] = [:]
|
||||
|
||||
// Determine which installed versions would be ideal to switch to,
|
||||
// but make sure to exclude the currently linked version
|
||||
PhpEnv.shared.validVersions(for: site.composerPhp).filter({ version in
|
||||
version.homebrewVersion != PhpEnv.phpInstall.version.short
|
||||
}).forEach { version in
|
||||
alert.addButton(withTitle: "site_link.switch_to_php".localized(version.homebrewVersion))
|
||||
map[mapIndex] = version.homebrewVersion
|
||||
mapIndex += 1
|
||||
}
|
||||
|
||||
alert.beginSheetModal(for: App.shared.siteListWindowController!.window!) { response in
|
||||
if response.rawValue > NSApplication.ModalResponse.alertFirstButtonReturn.rawValue {
|
||||
if map.keys.contains(response.rawValue) {
|
||||
let version = map[response.rawValue]!
|
||||
Log.info("Pressed button to switch to \(version)")
|
||||
MainMenu.shared.switchToPhpVersion(version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,12 +16,12 @@ extension SiteListVC {
|
||||
let originalSecureStatus = selectedSite!.secured
|
||||
let action = selectedSite!.secured ? "unsecure" : "secure"
|
||||
let selectedSite = selectedSite!
|
||||
let command = "cd '\(selectedSite.absolutePath!)' && sudo \(Paths.valet) \(action) && exit;"
|
||||
let command = "cd '\(selectedSite.absolutePath)' && sudo \(Paths.valet) \(action) && exit;"
|
||||
|
||||
waitAndExecute {
|
||||
Shell.run(command, requiresPath: true)
|
||||
} completion: { [self] in
|
||||
selectedSite.determineSecured(Valet.shared.config.tld)
|
||||
selectedSite.determineSecured()
|
||||
if selectedSite.secured == originalSecureStatus {
|
||||
BetterAlert()
|
||||
.withInformation(
|
||||
@ -36,13 +36,13 @@ extension SiteListVC {
|
||||
title: "site_list.alerts_status_changed.title".localized,
|
||||
subtitle: "site_list.alerts_status_changed.desc"
|
||||
.localized(
|
||||
"\(selectedSite.name!).\(Valet.shared.config.tld)",
|
||||
"\(selectedSite.name).\(Valet.shared.config.tld)",
|
||||
newState
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0])
|
||||
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4])
|
||||
tableView.deselectRow(rowToReload)
|
||||
tableView.selectRowIndexes([rowToReload], byExtendingSelection: true)
|
||||
}
|
||||
@ -50,7 +50,7 @@ extension SiteListVC {
|
||||
|
||||
@objc func openInBrowser() {
|
||||
let prefix = selectedSite!.secured ? "https://" : "http://"
|
||||
let url = URL(string: "\(prefix)\(selectedSite!.name!).\(Valet.shared.config.tld)")
|
||||
let url = URL(string: "\(prefix)\(selectedSite!.name).\(Valet.shared.config.tld)")
|
||||
if url != nil {
|
||||
NSWorkspace.shared.open(url!)
|
||||
} else {
|
||||
@ -65,16 +65,41 @@ extension SiteListVC {
|
||||
}
|
||||
|
||||
@objc func openInFinder() {
|
||||
Shell.run("open '\(selectedSite!.absolutePath!)'")
|
||||
Shell.run("open '\(selectedSite!.absolutePath)'")
|
||||
}
|
||||
|
||||
@objc func openInTerminal() {
|
||||
Shell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath!)'")
|
||||
Shell.run("open -b com.apple.terminal '\(selectedSite!.absolutePath)'")
|
||||
}
|
||||
|
||||
@objc func openWithEditor(sender: EditorMenuItem) {
|
||||
guard let editor = sender.editor else { return }
|
||||
editor.openDirectory(file: selectedSite!.absolutePath!)
|
||||
editor.openDirectory(file: selectedSite!.absolutePath)
|
||||
}
|
||||
|
||||
@objc func isolateSite(sender: PhpMenuItem) {
|
||||
let command = "cd '\(selectedSite!.absolutePath)' && sudo \(Paths.valet) isolate php@\(sender.version) && exit;"
|
||||
|
||||
self.performAction(command: command) {
|
||||
self.selectedSite!.determineIsolated()
|
||||
|
||||
if self.selectedSite!.isolatedPhpVersion == nil {
|
||||
BetterAlert()
|
||||
.withInformation(
|
||||
title: "site_list.alerts_isolation_failed.title".localized,
|
||||
subtitle: "site_list.alerts_isolation_failed.subtitle".localized,
|
||||
description: "site_list.alerts_isolation_failed.desc".localized(command)
|
||||
)
|
||||
.withPrimary(text: "OK")
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func removeIsolatedSite() {
|
||||
self.performAction(command: "cd '\(selectedSite!.absolutePath)' && sudo \(Paths.valet) unisolate && exit;") {
|
||||
self.selectedSite!.isolatedPhpVersion = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc func unlinkSite() {
|
||||
@ -94,10 +119,23 @@ extension SiteListVC {
|
||||
secondButtonTitle: "Cancel",
|
||||
style: .critical,
|
||||
onFirstButtonPressed: {
|
||||
Shell.run("valet unlink '\(site.name!)'", requiresPath: true)
|
||||
Shell.run("valet unlink '\(site.name)'", requiresPath: true)
|
||||
self.reloadSites()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func performAction(command: String, beforeCellReload: @escaping () -> Void) {
|
||||
let rowToReload = tableView.selectedRow
|
||||
|
||||
waitAndExecute {
|
||||
Shell.run(command, requiresPath: true)
|
||||
} completion: { [self] in
|
||||
beforeCellReload()
|
||||
tableView.reloadData(forRowIndexes: [rowToReload], columnIndexes: [0, 1, 2, 3, 4])
|
||||
tableView.deselectRow(rowToReload)
|
||||
tableView.selectRowIndexes([rowToReload], byExtendingSelection: true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,12 @@ extension SiteListVC {
|
||||
addDetectedApps(to: menu)
|
||||
addSeparator(to: menu)
|
||||
|
||||
if Valet.enabled(feature: .isolatedSites) {
|
||||
addIsolate(to: menu, with: site)
|
||||
} else {
|
||||
addDisabledIsolation(to: menu)
|
||||
}
|
||||
|
||||
addUnlink(to: menu, with: site)
|
||||
addToggleSecure(to: menu, with: site)
|
||||
|
||||
@ -76,6 +82,37 @@ extension SiteListVC {
|
||||
}
|
||||
}
|
||||
|
||||
private func addDisabledIsolation(to menu: NSMenu) {
|
||||
menu.addItem(withTitle: "site_list.isolation_unavailable".localized, action: nil, keyEquivalent: "")
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
||||
private func addIsolate(to menu: NSMenu, with site: ValetSite) {
|
||||
if site.isolatedPhpVersion == nil {
|
||||
// ISOLATION POSSIBLE
|
||||
let isolationMenuItem = NSMenuItem(title:"site_list.isolate".localized, action: nil, keyEquivalent: "")
|
||||
let submenu = NSMenu()
|
||||
submenu.addItem(withTitle: "Choose a PHP version", action: nil, keyEquivalent: "")
|
||||
for version in PhpEnv.shared.availablePhpVersions.reversed() {
|
||||
let item = PhpMenuItem(title: "Always use PHP \(version)", action: #selector(self.isolateSite), keyEquivalent: "")
|
||||
item.version = version
|
||||
submenu.addItem(item)
|
||||
}
|
||||
menu.setSubmenu(submenu, for: isolationMenuItem)
|
||||
|
||||
menu.addItem(isolationMenuItem)
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
} else {
|
||||
// REMOVE ISOLATION POSSIBLE
|
||||
menu.addItem(
|
||||
withTitle: "site_list.remove_isolation".localized,
|
||||
action: #selector(self.removeIsolatedSite),
|
||||
keyEquivalent: ""
|
||||
)
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
}
|
||||
|
||||
private func addToggleSecure(to menu: NSMenu, with site: ValetSite) {
|
||||
menu.addItem(
|
||||
withTitle: site.secured
|
||||
|
@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import HotKey
|
||||
import Carbon
|
||||
|
||||
class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
|
||||
@ -27,6 +26,9 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
|
||||
return App.shared.detectedApplications
|
||||
}
|
||||
|
||||
/// The last sort descriptor used.
|
||||
var sortDescriptor: NSSortDescriptor? = nil
|
||||
|
||||
/// String that was last searched for. Empty by default.
|
||||
var lastSearchedFor = ""
|
||||
|
||||
@ -145,6 +147,28 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func applySortDescriptor(_ descriptor: NSSortDescriptor) {
|
||||
sortDescriptor = descriptor
|
||||
|
||||
var sorted = self.sites
|
||||
|
||||
switch descriptor.key {
|
||||
case "Secure":
|
||||
sorted = self.sites.sorted { $0.secured && !$1.secured }; break
|
||||
case "Domain":
|
||||
sorted = self.sites.sorted { $0.absolutePath < $1.absolutePath }; break
|
||||
case "PHP":
|
||||
sorted = self.sites.sorted { $0.servingPhpVersion < $1.servingPhpVersion }; break
|
||||
case "Kind":
|
||||
sorted = self.sites.sorted { ($0.aliasPath == nil) && !($1.aliasPath == nil) }; break
|
||||
case "Type":
|
||||
sorted = self.sites.sorted { $0.driver ?? "ZZZ" < $1.driver ?? "ZZZ" }; break
|
||||
default: break;
|
||||
}
|
||||
|
||||
self.sites = descriptor.ascending ? sorted.reversed() : sorted
|
||||
}
|
||||
|
||||
func addedNewSite(name: String, secure: Bool) {
|
||||
waitAndExecute {
|
||||
Valet.shared.reloadSites()
|
||||
@ -173,14 +197,32 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
|
||||
return sites.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
|
||||
guard let sortDescriptor = tableView.sortDescriptors.first else { return }
|
||||
// Kinda scuffed way of applying sort descriptors here, but it works.
|
||||
Log.info("Applying sort descriptor for column: \(sortDescriptor.key ?? "Unknown")")
|
||||
applySortDescriptor(sortDescriptor)
|
||||
searchedFor(text: lastSearchedFor)
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||
guard let userCell = tableView.makeView(
|
||||
withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "siteItem"), owner: self
|
||||
) as? SiteListCell else { return nil }
|
||||
let mapping: [String: String] = [
|
||||
"TLS": SiteListTLSCell.reusableName,
|
||||
"DOMAIN": SiteListNameCell.reusableName,
|
||||
"ENVIRONMENT": SiteListPhpCell.reusableName,
|
||||
"KIND": SiteListKindCell.reusableName,
|
||||
"TYPE": SiteListTypeCell.reusableName,
|
||||
]
|
||||
|
||||
let columnName = tableColumn!.identifier.rawValue
|
||||
let identifier = NSUserInterfaceItemIdentifier(rawValue: mapping[columnName]!)
|
||||
|
||||
guard let userCell = tableView.makeView(withIdentifier: identifier, owner: self)
|
||||
as? SiteListCellProtocol else { return nil }
|
||||
|
||||
userCell.populateCell(with: sites[row])
|
||||
|
||||
return userCell
|
||||
return userCell as? NSView
|
||||
}
|
||||
|
||||
func tableViewSelectionDidChange(_ notification: Notification) {
|
||||
@ -205,9 +247,14 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
|
||||
if searchString.isEmpty {
|
||||
sites = Valet.shared.sites
|
||||
|
||||
if let sortDescriptor = sortDescriptor {
|
||||
self.applySortDescriptor(sortDescriptor)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -221,6 +268,10 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
|
||||
}.contains(false)
|
||||
})
|
||||
|
||||
if let sortDescriptor = sortDescriptor {
|
||||
self.applySortDescriptor(sortDescriptor)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
|
@ -25,8 +25,12 @@
|
||||
"mi_manage_services" = "Manage Services";
|
||||
"mi_restart_all_services" = "Restart All Services";
|
||||
"mi_stop_all_services" = "Stop All Services";
|
||||
|
||||
"mi_fix_my_valet" = "Fix My Valet...";
|
||||
"mi_fix_my_valet_tooltip" = "Something wrong with your Valet installation? Try PHP Monitor’s automatic fixes that’ll get you back up and running in no time!";
|
||||
"mi_fix_brew_permissions" = "Restore Homebrew Permissions...";
|
||||
"mi_fix_brew_permissions_tooltip" = "Having permission issues when running `brew upgrade`? PHP Monitor to the rescue!";
|
||||
|
||||
"mi_php_refresh" = "Refresh Information";
|
||||
|
||||
"mi_configuration" = "PHP Configuration";
|
||||
@ -67,7 +71,11 @@
|
||||
"site_list.title" = "Domains";
|
||||
"site_list.subtitle" = "Linked & Parked";
|
||||
|
||||
"site_list.alerts_status_not_changed.title" = "SSL Status Not Changed";
|
||||
"site_list.alerts_isolation_failed.title" = "Oops! Site Not Isolated";
|
||||
"site_list.alerts_isolation_failed.subtitle" = "Something went wrong trying to isolate this site. If this is your default site but it is not linked, I recommend manually linking the site prior to setting up isolation.";
|
||||
"site_list.alerts_isolation_failed.desc" = "To find out what goes wrong, you can try running the command in your terminal manually: %@";
|
||||
|
||||
"site_list.alerts_status_not_changed.title" = "Oops! SSL Status Not Changed";
|
||||
"site_list.alerts_status_not_changed.desc" = "Something went wrong. Try running the command in your terminal manually: %@";
|
||||
|
||||
"site_list.alerts_status_changed.title" = "SSL Status Changed";
|
||||
@ -103,6 +111,10 @@
|
||||
|
||||
// SITE LIST ACTIONS
|
||||
|
||||
"site_list.isolate" = "Isolate Domain";
|
||||
"site_list.remove_isolation" = "Remove Isolation";
|
||||
"site_list.isolation_unavailable" = "Isolation Not Supported (in Valet 2)";
|
||||
|
||||
"site_list.unlink" = "Unlink Directory";
|
||||
"site_list.secure" = "Secure Domain";
|
||||
"site_list.unsecure" = "Unsecure Domain";
|
||||
@ -117,6 +129,12 @@
|
||||
"site_list.alert.invalid_folder_name" = "Invalid folder name";
|
||||
"site_list.alert.invalid_folder_name_desc" = "This folder could not be resolved to a valid URL. This is usually because there’s a space in the folder name. Please rename the folder, reload the list of sites, and try again.";
|
||||
|
||||
"site_list.columns.tls" = "TLS";
|
||||
"site_list.columns.domain" = "Domain";
|
||||
"site_list.columns.php" = "PHP";
|
||||
"site_list.columns.type" = "Type";
|
||||
"site_list.columns.kind" = "Kind";
|
||||
|
||||
// DRIVERS
|
||||
|
||||
"driver.not_detected" = "Other";
|
||||
@ -202,8 +220,9 @@ problem manually, using your own Terminal app (this just shows you the output)."
|
||||
"alert.composer_success.info" = "Your global Composer dependencies have been successfully updated.";
|
||||
|
||||
// Composer Version
|
||||
"alert.composer_php_isolated.desc" = "This site has been isolated, which means that Valet serves PHP %@ for this site specifically (the global version is %@).";
|
||||
"alert.composer_php_requirement.title" = "`%@` has the following PHP requirement: %@.";
|
||||
"alert.composer_php_requirement.type.unknown" = "The required PHP version was determined by an unknown factor.";
|
||||
"alert.composer_php_requirement.type.unknown" = "The required PHP version is a mystery.";
|
||||
"alert.composer_php_requirement.type.require" = "This required PHP version was determined by checking the `require` field in the `composer.json` file when the site list was last refreshed.";
|
||||
"alert.composer_php_requirement.type.platform" = "This required PHP version was determined by checking the `platform` field in the `composer.json` file when the site list was last refreshed.";
|
||||
"alert.composer_php_requirement.type.valetphprc" = "This required PHP version was determined by checking the .valetphprc file in your project's directory.";
|
||||
@ -288,9 +307,10 @@ You can do this by running `composer global update` in your terminal. After that
|
||||
"startup.errors.valet_executable.subtitle" = "You must install Valet with Composer. The app will not work correctly until you resolve this issue.";
|
||||
"startup.errors.valet_executable.desc" = "If you haven't installed Laravel Valet yet, please do so first. If you have it installed but are seeing this message anyway, then try running `which valet` in Terminal, it should return: `%@`.";
|
||||
|
||||
/// Valet configuration file missing [currently not enabled]
|
||||
"startup.errors.valet_config.title" = "Laravel Valet configuration file missing";
|
||||
"startup.errors.valet_config.desc" = "PHP Monitor needs to be able to read the configuration file in `~/.config/valet/config.json`.";
|
||||
/// Valet configuration file missing or broken
|
||||
"startup.errors.valet_json_invalid.title" = "Laravel Valet configuration file invalid or missing";
|
||||
"startup.errors.valet_json_invalid.subtitle" = "PHP Monitor needs to be able to read the configuration file. It appears the file is malformed or missing. Please check that it exists and is formatted correctly.";
|
||||
"startup.errors.valet_json_invalid.desc" = "You can find the file at `~/.config/valet/config.json`. If Laravel Valet cannot parse the configuration file, running any `valet` command will usually automatically fix the JSON file. Try running `valet --version` to automatically fix the file.";
|
||||
|
||||
/// Valet version not readable
|
||||
"startup.errors.valet_version_unknown.title" = "Your Valet version could not be read";
|
||||
@ -310,10 +330,6 @@ You can do this by running `composer global update` in your terminal. After that
|
||||
"startup.errors.services_json_error.subtitle" = "PHP Monitor usually queries `brew` using the following command to test if the services can be retrieved: `sudo brew services info nginx --json`.\n\nPHP Monitor could not interpret this response.";
|
||||
"startup.errors.services_json_error.desc" = "This can happen if your Homebrew installation is out of date, in which case Homebrew won't return JSON yet. You can usually fix this by running `brew update`. You can also try running `sudo brew services info nginx --json` in your terminal of choice.";
|
||||
|
||||
/// Multiple services active
|
||||
"startup.errors.services.title" = "Multiple PHP services are active";
|
||||
"startup.errors.services.desc" = "This can cause php-fpm to serve a more recent version of PHP than the one you'd like to see active. Please terminate all extra PHP processes.\n\nThe easiest solution is to choose the option 'First Aid & Services > Fix My Valet' in the menu bar.\n\nAlternatively, you can fix this manually. You can do this by running `brew services list` and running `sudo brew services stop php@7.3` (and use the version that applies).\n\nPHP Monitor usually handles the starting and stopping of these services, so once the correct version is the only PHP version running you should not have any issues. It is recommended to restart PHP Monitor once you have resolved this issue.\n\nFor more information about this issue, please see the README.md file in the repository on GitHub.";
|
||||
|
||||
// SPONSOR ENCOURAGEMENT
|
||||
|
||||
"startup.sponsor_encouragement.title" = "If PHP Monitor has been useful to you or your company, please consider leaving a tip.";
|
||||
|
50
phpmon/Vendor/HotKey/HotKey.swift
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
import AppKit
|
||||
import Carbon
|
||||
|
||||
public final class HotKey {
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
public typealias Handler = () -> Void
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
let identifier = UUID()
|
||||
|
||||
public let keyCombo: KeyCombo
|
||||
public var keyDownHandler: Handler?
|
||||
public var keyUpHandler: Handler?
|
||||
public var isPaused = false {
|
||||
didSet {
|
||||
if isPaused {
|
||||
HotKeysController.unregister(self)
|
||||
} else {
|
||||
HotKeysController.register(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
public init(keyCombo: KeyCombo, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
|
||||
self.keyCombo = keyCombo
|
||||
self.keyDownHandler = keyDownHandler
|
||||
self.keyUpHandler = keyUpHandler
|
||||
|
||||
HotKeysController.register(self)
|
||||
}
|
||||
|
||||
public convenience init(carbonKeyCode: UInt32, carbonModifiers: UInt32, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
|
||||
let keyCombo = KeyCombo(carbonKeyCode: carbonKeyCode, carbonModifiers: carbonModifiers)
|
||||
self.init(keyCombo: keyCombo, keyDownHandler: keyDownHandler, keyUpHandler: keyUpHandler)
|
||||
}
|
||||
|
||||
public convenience init(key: Key, modifiers: NSEvent.ModifierFlags, keyDownHandler: Handler? = nil, keyUpHandler: Handler? = nil) {
|
||||
let keyCombo = KeyCombo(key: key, modifiers: modifiers)
|
||||
self.init(keyCombo: keyCombo, keyDownHandler: keyDownHandler, keyUpHandler: keyUpHandler)
|
||||
}
|
||||
|
||||
deinit {
|
||||
HotKeysController.unregister(self)
|
||||
}
|
||||
}
|
185
phpmon/Vendor/HotKey/HotKeysController.swift
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
import Carbon
|
||||
|
||||
final class HotKeysController {
|
||||
|
||||
// MARK: - Types
|
||||
|
||||
final class HotKeyBox {
|
||||
let identifier: UUID
|
||||
weak var hotKey: HotKey?
|
||||
let carbonHotKeyID: UInt32
|
||||
var carbonEventHotKey: EventHotKeyRef?
|
||||
|
||||
init(hotKey: HotKey, carbonHotKeyID: UInt32) {
|
||||
self.identifier = hotKey.identifier
|
||||
self.hotKey = hotKey
|
||||
self.carbonHotKeyID = carbonHotKeyID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
static var hotKeys = [UInt32: HotKeyBox]()
|
||||
static private var hotKeysCount: UInt32 = 0
|
||||
|
||||
static let eventHotKeySignature: UInt32 = {
|
||||
let string = "SSHk"
|
||||
var result: FourCharCode = 0
|
||||
for char in string.utf16 {
|
||||
result = (result << 8) + FourCharCode(char)
|
||||
}
|
||||
return result
|
||||
}()
|
||||
|
||||
private static let eventSpec = [
|
||||
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
|
||||
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
|
||||
]
|
||||
|
||||
private static var eventHandler: EventHandlerRef?
|
||||
|
||||
// MARK: - Registration
|
||||
|
||||
static func register(_ hotKey: HotKey) {
|
||||
// Don't register an already registered HotKey
|
||||
if hotKeys.values.first(where: { $0.identifier == hotKey.identifier }) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Increment the count which will become out next ID
|
||||
hotKeysCount += 1
|
||||
|
||||
// Create a box for our metadata and weak HotKey
|
||||
let box = HotKeyBox(hotKey: hotKey, carbonHotKeyID: hotKeysCount)
|
||||
hotKeys[box.carbonHotKeyID] = box
|
||||
|
||||
// Register with the system
|
||||
var eventHotKey: EventHotKeyRef?
|
||||
let hotKeyID = EventHotKeyID(signature: eventHotKeySignature, id: box.carbonHotKeyID)
|
||||
let registerError = RegisterEventHotKey(
|
||||
hotKey.keyCombo.carbonKeyCode,
|
||||
hotKey.keyCombo.carbonModifiers,
|
||||
hotKeyID,
|
||||
GetEventDispatcherTarget(),
|
||||
0,
|
||||
&eventHotKey
|
||||
)
|
||||
|
||||
// Ensure registration worked
|
||||
guard registerError == noErr, eventHotKey != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
// Store the event so we can unregister it later
|
||||
box.carbonEventHotKey = eventHotKey
|
||||
|
||||
// Setup the event handler if needed
|
||||
updateEventHandler()
|
||||
}
|
||||
|
||||
static func unregister(_ hotKey: HotKey) {
|
||||
// Find the box
|
||||
guard let box = self.box(for: hotKey) else {
|
||||
return
|
||||
}
|
||||
|
||||
// Unregister the hot key
|
||||
UnregisterEventHotKey(box.carbonEventHotKey)
|
||||
|
||||
// Destroy the box
|
||||
box.hotKey = nil
|
||||
hotKeys.removeValue(forKey: box.carbonHotKeyID)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Events
|
||||
|
||||
static func handleCarbonEvent(_ event: EventRef?) -> OSStatus {
|
||||
// Ensure we have an event
|
||||
guard let event = event else {
|
||||
return OSStatus(eventNotHandledErr)
|
||||
}
|
||||
|
||||
// Get the hot key ID from the event
|
||||
var hotKeyID = EventHotKeyID()
|
||||
let error = GetEventParameter(
|
||||
event,
|
||||
UInt32(kEventParamDirectObject),
|
||||
UInt32(typeEventHotKeyID),
|
||||
nil,
|
||||
MemoryLayout<EventHotKeyID>.size,
|
||||
nil,
|
||||
&hotKeyID
|
||||
)
|
||||
|
||||
if error != noErr {
|
||||
return error
|
||||
}
|
||||
|
||||
// Ensure we have a HotKey registered for this ID
|
||||
guard hotKeyID.signature == eventHotKeySignature,
|
||||
let hotKey = self.hotKey(for: hotKeyID.id)
|
||||
else {
|
||||
return OSStatus(eventNotHandledErr)
|
||||
}
|
||||
|
||||
// Call the handler
|
||||
switch GetEventKind(event) {
|
||||
case UInt32(kEventHotKeyPressed):
|
||||
if !hotKey.isPaused, let handler = hotKey.keyDownHandler {
|
||||
handler()
|
||||
return noErr
|
||||
}
|
||||
case UInt32(kEventHotKeyReleased):
|
||||
if !hotKey.isPaused, let handler = hotKey.keyUpHandler {
|
||||
handler()
|
||||
return noErr
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return OSStatus(eventNotHandledErr)
|
||||
}
|
||||
|
||||
private static func updateEventHandler() {
|
||||
if hotKeysCount == 0 || eventHandler != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Register for key down and key up
|
||||
let eventSpec = [
|
||||
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
|
||||
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
|
||||
]
|
||||
|
||||
// Install the handler
|
||||
InstallEventHandler(GetEventDispatcherTarget(), hotKeyEventHandler, 2, eventSpec, nil, &eventHandler)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Querying
|
||||
|
||||
private static func hotKey(for carbonHotKeyID: UInt32) -> HotKey? {
|
||||
if let hotKey = hotKeys[carbonHotKeyID]?.hotKey {
|
||||
return hotKey
|
||||
}
|
||||
|
||||
hotKeys.removeValue(forKey: carbonHotKeyID)
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func box(for hotKey: HotKey) -> HotKeyBox? {
|
||||
for box in hotKeys.values {
|
||||
if box.identifier == hotKey.identifier {
|
||||
return box
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func hotKeyEventHandler(eventHandlerCall: EventHandlerCallRef?, event: EventRef?, userData: UnsafeMutableRawPointer?) -> OSStatus {
|
||||
return HotKeysController.handleCarbonEvent(event)
|
||||
}
|
497
phpmon/Vendor/HotKey/Key.swift
vendored
Normal file
@ -0,0 +1,497 @@
|
||||
import Carbon
|
||||
|
||||
public enum Key {
|
||||
|
||||
// MARK: - Letters
|
||||
case a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z
|
||||
|
||||
// MARK: - Numbers
|
||||
case zero, one, two, three, four, five, six, seven, eight, nine
|
||||
|
||||
// MARK: - Symbols
|
||||
case period, quote, rightBracket, semicolon, slash, backslash, comma, equal, grave, leftBracket, minus
|
||||
|
||||
// MARK: - Whitespace
|
||||
case space, tab, `return`
|
||||
|
||||
// MARK: - Modifiers
|
||||
case command, rightCommand, option, rightOption, control, rightControl, shift, rightShift, function, capsLock
|
||||
|
||||
// MARK: - Navigation
|
||||
case pageUp, pageDown, home, end, upArrow, rightArrow, downArrow, leftArrow
|
||||
|
||||
// MARK: - Functions
|
||||
case f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20
|
||||
|
||||
// MARK: - Keypad
|
||||
case keypad0, keypad1, keypad2, keypad3, keypad4, keypad5, keypad6, keypad7, keypad8, keypad9,
|
||||
keypadClear, keypadDecimal, keypadDivide, keypadEnter, keypadEquals, keypadMinus, keypadMultiply, keypadPlus
|
||||
|
||||
// MARK: - Misc
|
||||
case escape, delete, forwardDelete, help, volumeUp, volumeDown, mute
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
public init?(string: String) {
|
||||
switch string.lowercased() {
|
||||
case "a": self = .a
|
||||
case "s": self = .s
|
||||
case "d": self = .d
|
||||
case "f": self = .f
|
||||
case "h": self = .h
|
||||
case "g": self = .g
|
||||
case "z": self = .z
|
||||
case "x": self = .x
|
||||
case "c": self = .c
|
||||
case "v": self = .v
|
||||
case "b": self = .b
|
||||
case "q": self = .q
|
||||
case "w": self = .w
|
||||
case "e": self = .e
|
||||
case "r": self = .r
|
||||
case "y": self = .y
|
||||
case "t": self = .t
|
||||
case "one", "1": self = .one
|
||||
case "two", "2": self = .two
|
||||
case "three", "3": self = .three
|
||||
case "four", "4": self = .four
|
||||
case "six", "6": self = .six
|
||||
case "five", "5": self = .five
|
||||
case "equal", "=": self = .equal
|
||||
case "nine", "9": self = .nine
|
||||
case "seven", "7": self = .seven
|
||||
case "minus", "-": self = .minus
|
||||
case "eight", "8": self = .eight
|
||||
case "zero", "0": self = .zero
|
||||
case "rightBracket", "]": self = .rightBracket
|
||||
case "o": self = .o
|
||||
case "u": self = .u
|
||||
case "leftBracket", "[": self = .leftBracket
|
||||
case "i": self = .i
|
||||
case "p": self = .p
|
||||
case "l": self = .l
|
||||
case "j": self = .j
|
||||
case "quote", "\"": self = .quote
|
||||
case "k": self = .k
|
||||
case "semicolon", ";": self = .semicolon
|
||||
case "backslash", "\\": self = .backslash
|
||||
case "comma", ",": self = .comma
|
||||
case "slash", "/": self = .slash
|
||||
case "n": self = .n
|
||||
case "m": self = .m
|
||||
case "period", ".": self = .period
|
||||
case "grave", "`", "ˋ", "`": self = .grave
|
||||
case "keypaddecimal": self = .keypadDecimal
|
||||
case "keypadmultiply": self = .keypadMultiply
|
||||
case "keypadplus": self = .keypadPlus
|
||||
case "keypadclear", "⌧": self = .keypadClear
|
||||
case "keypaddivide": self = .keypadDivide
|
||||
case "keypadenter": self = .keypadEnter
|
||||
case "keypadminus": self = .keypadMinus
|
||||
case "keypadequals": self = .keypadEquals
|
||||
case "keypad0": self = .keypad0
|
||||
case "keypad1": self = .keypad1
|
||||
case "keypad2": self = .keypad2
|
||||
case "keypad3": self = .keypad3
|
||||
case "keypad4": self = .keypad4
|
||||
case "keypad5": self = .keypad5
|
||||
case "keypad6": self = .keypad6
|
||||
case "keypad7": self = .keypad7
|
||||
case "keypad8": self = .keypad8
|
||||
case "keypad9": self = .keypad9
|
||||
case "return", "\r", "↩︎", "⏎", "⮐": self = .return
|
||||
case "tab", "\t", "⇥": self = .tab
|
||||
case "space", " ", "␣": self = .space
|
||||
case "delete", "⌫": self = .delete
|
||||
case "escape", "⎋": self = .escape
|
||||
case "command", "⌘", "": self = .command
|
||||
case "shift", "⇧": self = .shift
|
||||
case "capslock", "⇪": self = .capsLock
|
||||
case "option", "⌥": self = .option
|
||||
case "control", "⌃": self = .control
|
||||
case "rightcommand": self = .rightCommand
|
||||
case "rightshift": self = .rightShift
|
||||
case "rightoption": self = .rightOption
|
||||
case "rightcontrol": self = .rightControl
|
||||
case "function", "fn": self = .function
|
||||
case "f17", "F17": self = .f17
|
||||
case "volumeup", "🔊": self = .volumeUp
|
||||
case "volumedown", "🔉": self = .volumeDown
|
||||
case "mute", "🔇": self = .mute
|
||||
case "f18", "F18": self = .f18
|
||||
case "f19", "F19": self = .f19
|
||||
case "f20", "F20": self = .f20
|
||||
case "f5", "F5": self = .f5
|
||||
case "f6", "F6": self = .f6
|
||||
case "f7", "F7": self = .f7
|
||||
case "f3", "F3": self = .f3
|
||||
case "f8", "F8": self = .f8
|
||||
case "f9", "F9": self = .f9
|
||||
case "f11", "F11": self = .f11
|
||||
case "f13", "F13": self = .f13
|
||||
case "f16", "F16": self = .f16
|
||||
case "f14", "F14": self = .f14
|
||||
case "f10", "F10": self = .f10
|
||||
case "f12", "F12": self = .f12
|
||||
case "f15", "F15": self = .f15
|
||||
case "help", "?⃝": self = .help
|
||||
case "home", "↖": self = .home
|
||||
case "pageup", "⇞": self = .pageUp
|
||||
case "forwarddelete", "⌦": self = .forwardDelete
|
||||
case "f4", "F4": self = .f4
|
||||
case "end", "↘": self = .end
|
||||
case "f2", "F2": self = .f2
|
||||
case "pagedown", "⇟": self = .pageDown
|
||||
case "f1", "F1": self = .f1
|
||||
case "leftarrow", "←": self = .leftArrow
|
||||
case "rightarrow", "→": self = .rightArrow
|
||||
case "downarrow", "↓": self = .downArrow
|
||||
case "uparrow", "↑": self = .upArrow
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
public init?(carbonKeyCode: UInt32) {
|
||||
switch carbonKeyCode {
|
||||
case UInt32(kVK_ANSI_A): self = .a
|
||||
case UInt32(kVK_ANSI_S): self = .s
|
||||
case UInt32(kVK_ANSI_D): self = .d
|
||||
case UInt32(kVK_ANSI_F): self = .f
|
||||
case UInt32(kVK_ANSI_H): self = .h
|
||||
case UInt32(kVK_ANSI_G): self = .g
|
||||
case UInt32(kVK_ANSI_Z): self = .z
|
||||
case UInt32(kVK_ANSI_X): self = .x
|
||||
case UInt32(kVK_ANSI_C): self = .c
|
||||
case UInt32(kVK_ANSI_V): self = .v
|
||||
case UInt32(kVK_ANSI_B): self = .b
|
||||
case UInt32(kVK_ANSI_Q): self = .q
|
||||
case UInt32(kVK_ANSI_W): self = .w
|
||||
case UInt32(kVK_ANSI_E): self = .e
|
||||
case UInt32(kVK_ANSI_R): self = .r
|
||||
case UInt32(kVK_ANSI_Y): self = .y
|
||||
case UInt32(kVK_ANSI_T): self = .t
|
||||
case UInt32(kVK_ANSI_1): self = .one
|
||||
case UInt32(kVK_ANSI_2): self = .two
|
||||
case UInt32(kVK_ANSI_3): self = .three
|
||||
case UInt32(kVK_ANSI_4): self = .four
|
||||
case UInt32(kVK_ANSI_6): self = .six
|
||||
case UInt32(kVK_ANSI_5): self = .five
|
||||
case UInt32(kVK_ANSI_Equal): self = .equal
|
||||
case UInt32(kVK_ANSI_9): self = .nine
|
||||
case UInt32(kVK_ANSI_7): self = .seven
|
||||
case UInt32(kVK_ANSI_Minus): self = .minus
|
||||
case UInt32(kVK_ANSI_8): self = .eight
|
||||
case UInt32(kVK_ANSI_0): self = .zero
|
||||
case UInt32(kVK_ANSI_RightBracket): self = .rightBracket
|
||||
case UInt32(kVK_ANSI_O): self = .o
|
||||
case UInt32(kVK_ANSI_U): self = .u
|
||||
case UInt32(kVK_ANSI_LeftBracket): self = .leftBracket
|
||||
case UInt32(kVK_ANSI_I): self = .i
|
||||
case UInt32(kVK_ANSI_P): self = .p
|
||||
case UInt32(kVK_ANSI_L): self = .l
|
||||
case UInt32(kVK_ANSI_J): self = .j
|
||||
case UInt32(kVK_ANSI_Quote): self = .quote
|
||||
case UInt32(kVK_ANSI_K): self = .k
|
||||
case UInt32(kVK_ANSI_Semicolon): self = .semicolon
|
||||
case UInt32(kVK_ANSI_Backslash): self = .backslash
|
||||
case UInt32(kVK_ANSI_Comma): self = .comma
|
||||
case UInt32(kVK_ANSI_Slash): self = .slash
|
||||
case UInt32(kVK_ANSI_N): self = .n
|
||||
case UInt32(kVK_ANSI_M): self = .m
|
||||
case UInt32(kVK_ANSI_Period): self = .period
|
||||
case UInt32(kVK_ANSI_Grave): self = .grave
|
||||
case UInt32(kVK_ANSI_KeypadDecimal): self = .keypadDecimal
|
||||
case UInt32(kVK_ANSI_KeypadMultiply): self = .keypadMultiply
|
||||
case UInt32(kVK_ANSI_KeypadPlus): self = .keypadPlus
|
||||
case UInt32(kVK_ANSI_KeypadClear): self = .keypadClear
|
||||
case UInt32(kVK_ANSI_KeypadDivide): self = .keypadDivide
|
||||
case UInt32(kVK_ANSI_KeypadEnter): self = .keypadEnter
|
||||
case UInt32(kVK_ANSI_KeypadMinus): self = .keypadMinus
|
||||
case UInt32(kVK_ANSI_KeypadEquals): self = .keypadEquals
|
||||
case UInt32(kVK_ANSI_Keypad0): self = .keypad0
|
||||
case UInt32(kVK_ANSI_Keypad1): self = .keypad1
|
||||
case UInt32(kVK_ANSI_Keypad2): self = .keypad2
|
||||
case UInt32(kVK_ANSI_Keypad3): self = .keypad3
|
||||
case UInt32(kVK_ANSI_Keypad4): self = .keypad4
|
||||
case UInt32(kVK_ANSI_Keypad5): self = .keypad5
|
||||
case UInt32(kVK_ANSI_Keypad6): self = .keypad6
|
||||
case UInt32(kVK_ANSI_Keypad7): self = .keypad7
|
||||
case UInt32(kVK_ANSI_Keypad8): self = .keypad8
|
||||
case UInt32(kVK_ANSI_Keypad9): self = .keypad9
|
||||
case UInt32(kVK_Return): self = .`return`
|
||||
case UInt32(kVK_Tab): self = .tab
|
||||
case UInt32(kVK_Space): self = .space
|
||||
case UInt32(kVK_Delete): self = .delete
|
||||
case UInt32(kVK_Escape): self = .escape
|
||||
case UInt32(kVK_Command): self = .command
|
||||
case UInt32(kVK_Shift): self = .shift
|
||||
case UInt32(kVK_CapsLock): self = .capsLock
|
||||
case UInt32(kVK_Option): self = .option
|
||||
case UInt32(kVK_Control): self = .control
|
||||
case UInt32(kVK_RightCommand): self = .rightCommand
|
||||
case UInt32(kVK_RightShift): self = .rightShift
|
||||
case UInt32(kVK_RightOption): self = .rightOption
|
||||
case UInt32(kVK_RightControl): self = .rightControl
|
||||
case UInt32(kVK_Function): self = .function
|
||||
case UInt32(kVK_F17): self = .f17
|
||||
case UInt32(kVK_VolumeUp): self = .volumeUp
|
||||
case UInt32(kVK_VolumeDown): self = .volumeDown
|
||||
case UInt32(kVK_Mute): self = .mute
|
||||
case UInt32(kVK_F18): self = .f18
|
||||
case UInt32(kVK_F19): self = .f19
|
||||
case UInt32(kVK_F20): self = .f20
|
||||
case UInt32(kVK_F5): self = .f5
|
||||
case UInt32(kVK_F6): self = .f6
|
||||
case UInt32(kVK_F7): self = .f7
|
||||
case UInt32(kVK_F3): self = .f3
|
||||
case UInt32(kVK_F8): self = .f8
|
||||
case UInt32(kVK_F9): self = .f9
|
||||
case UInt32(kVK_F11): self = .f11
|
||||
case UInt32(kVK_F13): self = .f13
|
||||
case UInt32(kVK_F16): self = .f16
|
||||
case UInt32(kVK_F14): self = .f14
|
||||
case UInt32(kVK_F10): self = .f10
|
||||
case UInt32(kVK_F12): self = .f12
|
||||
case UInt32(kVK_F15): self = .f15
|
||||
case UInt32(kVK_Help): self = .help
|
||||
case UInt32(kVK_Home): self = .home
|
||||
case UInt32(kVK_PageUp): self = .pageUp
|
||||
case UInt32(kVK_ForwardDelete): self = .forwardDelete
|
||||
case UInt32(kVK_F4): self = .f4
|
||||
case UInt32(kVK_End): self = .end
|
||||
case UInt32(kVK_F2): self = .f2
|
||||
case UInt32(kVK_PageDown): self = .pageDown
|
||||
case UInt32(kVK_F1): self = .f1
|
||||
case UInt32(kVK_LeftArrow): self = .leftArrow
|
||||
case UInt32(kVK_RightArrow): self = .rightArrow
|
||||
case UInt32(kVK_DownArrow): self = .downArrow
|
||||
case UInt32(kVK_UpArrow): self = .upArrow
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var carbonKeyCode: UInt32 {
|
||||
switch self {
|
||||
case .a: return UInt32(kVK_ANSI_A)
|
||||
case .s: return UInt32(kVK_ANSI_S)
|
||||
case .d: return UInt32(kVK_ANSI_D)
|
||||
case .f: return UInt32(kVK_ANSI_F)
|
||||
case .h: return UInt32(kVK_ANSI_H)
|
||||
case .g: return UInt32(kVK_ANSI_G)
|
||||
case .z: return UInt32(kVK_ANSI_Z)
|
||||
case .x: return UInt32(kVK_ANSI_X)
|
||||
case .c: return UInt32(kVK_ANSI_C)
|
||||
case .v: return UInt32(kVK_ANSI_V)
|
||||
case .b: return UInt32(kVK_ANSI_B)
|
||||
case .q: return UInt32(kVK_ANSI_Q)
|
||||
case .w: return UInt32(kVK_ANSI_W)
|
||||
case .e: return UInt32(kVK_ANSI_E)
|
||||
case .r: return UInt32(kVK_ANSI_R)
|
||||
case .y: return UInt32(kVK_ANSI_Y)
|
||||
case .t: return UInt32(kVK_ANSI_T)
|
||||
case .one: return UInt32(kVK_ANSI_1)
|
||||
case .two: return UInt32(kVK_ANSI_2)
|
||||
case .three: return UInt32(kVK_ANSI_3)
|
||||
case .four: return UInt32(kVK_ANSI_4)
|
||||
case .six: return UInt32(kVK_ANSI_6)
|
||||
case .five: return UInt32(kVK_ANSI_5)
|
||||
case .equal: return UInt32(kVK_ANSI_Equal)
|
||||
case .nine: return UInt32(kVK_ANSI_9)
|
||||
case .seven: return UInt32(kVK_ANSI_7)
|
||||
case .minus: return UInt32(kVK_ANSI_Minus)
|
||||
case .eight: return UInt32(kVK_ANSI_8)
|
||||
case .zero: return UInt32(kVK_ANSI_0)
|
||||
case .rightBracket: return UInt32(kVK_ANSI_RightBracket)
|
||||
case .o: return UInt32(kVK_ANSI_O)
|
||||
case .u: return UInt32(kVK_ANSI_U)
|
||||
case .leftBracket: return UInt32(kVK_ANSI_LeftBracket)
|
||||
case .i: return UInt32(kVK_ANSI_I)
|
||||
case .p: return UInt32(kVK_ANSI_P)
|
||||
case .l: return UInt32(kVK_ANSI_L)
|
||||
case .j: return UInt32(kVK_ANSI_J)
|
||||
case .quote: return UInt32(kVK_ANSI_Quote)
|
||||
case .k: return UInt32(kVK_ANSI_K)
|
||||
case .semicolon: return UInt32(kVK_ANSI_Semicolon)
|
||||
case .backslash: return UInt32(kVK_ANSI_Backslash)
|
||||
case .comma: return UInt32(kVK_ANSI_Comma)
|
||||
case .slash: return UInt32(kVK_ANSI_Slash)
|
||||
case .n: return UInt32(kVK_ANSI_N)
|
||||
case .m: return UInt32(kVK_ANSI_M)
|
||||
case .period: return UInt32(kVK_ANSI_Period)
|
||||
case .grave: return UInt32(kVK_ANSI_Grave)
|
||||
case .keypadDecimal: return UInt32(kVK_ANSI_KeypadDecimal)
|
||||
case .keypadMultiply: return UInt32(kVK_ANSI_KeypadMultiply)
|
||||
case .keypadPlus: return UInt32(kVK_ANSI_KeypadPlus)
|
||||
case .keypadClear: return UInt32(kVK_ANSI_KeypadClear)
|
||||
case .keypadDivide: return UInt32(kVK_ANSI_KeypadDivide)
|
||||
case .keypadEnter: return UInt32(kVK_ANSI_KeypadEnter)
|
||||
case .keypadMinus: return UInt32(kVK_ANSI_KeypadMinus)
|
||||
case .keypadEquals: return UInt32(kVK_ANSI_KeypadEquals)
|
||||
case .keypad0: return UInt32(kVK_ANSI_Keypad0)
|
||||
case .keypad1: return UInt32(kVK_ANSI_Keypad1)
|
||||
case .keypad2: return UInt32(kVK_ANSI_Keypad2)
|
||||
case .keypad3: return UInt32(kVK_ANSI_Keypad3)
|
||||
case .keypad4: return UInt32(kVK_ANSI_Keypad4)
|
||||
case .keypad5: return UInt32(kVK_ANSI_Keypad5)
|
||||
case .keypad6: return UInt32(kVK_ANSI_Keypad6)
|
||||
case .keypad7: return UInt32(kVK_ANSI_Keypad7)
|
||||
case .keypad8: return UInt32(kVK_ANSI_Keypad8)
|
||||
case .keypad9: return UInt32(kVK_ANSI_Keypad9)
|
||||
case .`return`: return UInt32(kVK_Return)
|
||||
case .tab: return UInt32(kVK_Tab)
|
||||
case .space: return UInt32(kVK_Space)
|
||||
case .delete: return UInt32(kVK_Delete)
|
||||
case .escape: return UInt32(kVK_Escape)
|
||||
case .command: return UInt32(kVK_Command)
|
||||
case .shift: return UInt32(kVK_Shift)
|
||||
case .capsLock: return UInt32(kVK_CapsLock)
|
||||
case .option: return UInt32(kVK_Option)
|
||||
case .control: return UInt32(kVK_Control)
|
||||
case .rightCommand: return UInt32(kVK_RightCommand)
|
||||
case .rightShift: return UInt32(kVK_RightShift)
|
||||
case .rightOption: return UInt32(kVK_RightOption)
|
||||
case .rightControl: return UInt32(kVK_RightControl)
|
||||
case .function: return UInt32(kVK_Function)
|
||||
case .f17: return UInt32(kVK_F17)
|
||||
case .volumeUp: return UInt32(kVK_VolumeUp)
|
||||
case .volumeDown: return UInt32(kVK_VolumeDown)
|
||||
case .mute: return UInt32(kVK_Mute)
|
||||
case .f18: return UInt32(kVK_F18)
|
||||
case .f19: return UInt32(kVK_F19)
|
||||
case .f20: return UInt32(kVK_F20)
|
||||
case .f5: return UInt32(kVK_F5)
|
||||
case .f6: return UInt32(kVK_F6)
|
||||
case .f7: return UInt32(kVK_F7)
|
||||
case .f3: return UInt32(kVK_F3)
|
||||
case .f8: return UInt32(kVK_F8)
|
||||
case .f9: return UInt32(kVK_F9)
|
||||
case .f11: return UInt32(kVK_F11)
|
||||
case .f13: return UInt32(kVK_F13)
|
||||
case .f16: return UInt32(kVK_F16)
|
||||
case .f14: return UInt32(kVK_F14)
|
||||
case .f10: return UInt32(kVK_F10)
|
||||
case .f12: return UInt32(kVK_F12)
|
||||
case .f15: return UInt32(kVK_F15)
|
||||
case .help: return UInt32(kVK_Help)
|
||||
case .home: return UInt32(kVK_Home)
|
||||
case .pageUp: return UInt32(kVK_PageUp)
|
||||
case .forwardDelete: return UInt32(kVK_ForwardDelete)
|
||||
case .f4: return UInt32(kVK_F4)
|
||||
case .end: return UInt32(kVK_End)
|
||||
case .f2: return UInt32(kVK_F2)
|
||||
case .pageDown: return UInt32(kVK_PageDown)
|
||||
case .f1: return UInt32(kVK_F1)
|
||||
case .leftArrow: return UInt32(kVK_LeftArrow)
|
||||
case .rightArrow: return UInt32(kVK_RightArrow)
|
||||
case .downArrow: return UInt32(kVK_DownArrow)
|
||||
case .upArrow: return UInt32(kVK_UpArrow)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Key: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .a: return "A"
|
||||
case .s: return "S"
|
||||
case .d: return "D"
|
||||
case .f: return "F"
|
||||
case .h: return "H"
|
||||
case .g: return "G"
|
||||
case .z: return "Z"
|
||||
case .x: return "X"
|
||||
case .c: return "C"
|
||||
case .v: return "V"
|
||||
case .b: return "B"
|
||||
case .q: return "Q"
|
||||
case .w: return "W"
|
||||
case .e: return "E"
|
||||
case .r: return "R"
|
||||
case .y: return "Y"
|
||||
case .t: return "T"
|
||||
case .one, .keypad1: return "1"
|
||||
case .two, .keypad2: return "2"
|
||||
case .three, .keypad3: return "3"
|
||||
case .four, .keypad4: return "4"
|
||||
case .six, .keypad6: return "6"
|
||||
case .five, .keypad5: return "5"
|
||||
case .equal: return "="
|
||||
case .nine, .keypad9: return "9"
|
||||
case .seven, .keypad7: return "7"
|
||||
case .minus: return "-"
|
||||
case .eight, .keypad8: return "8"
|
||||
case .zero, .keypad0: return "0"
|
||||
case .rightBracket: return "]"
|
||||
case .o: return "O"
|
||||
case .u: return "U"
|
||||
case .leftBracket: return "["
|
||||
case .i: return "I"
|
||||
case .p: return "P"
|
||||
case .l: return "L"
|
||||
case .j: return "J"
|
||||
case .quote: return "\""
|
||||
case .k: return "K"
|
||||
case .semicolon: return ";"
|
||||
case .backslash: return "\\"
|
||||
case .comma: return ","
|
||||
case .slash: return "/"
|
||||
case .n: return "N"
|
||||
case .m: return "M"
|
||||
case .period: return "."
|
||||
case .grave: return "`"
|
||||
case .keypadDecimal: return "."
|
||||
case .keypadMultiply: return "𝗑"
|
||||
case .keypadPlus: return "+"
|
||||
case .keypadClear: return "⌧"
|
||||
case .keypadDivide: return "/"
|
||||
case .keypadEnter: return "↩︎"
|
||||
case .keypadMinus: return "-"
|
||||
case .keypadEquals: return "="
|
||||
case .`return`: return "↩︎"
|
||||
case .tab: return "⇥"
|
||||
case .space: return "␣"
|
||||
case .delete: return "⌫"
|
||||
case .escape: return "⎋"
|
||||
case .command, .rightCommand: return "⌘"
|
||||
case .shift, .rightShift: return "⇧"
|
||||
case .capsLock: return "⇪"
|
||||
case .option, .rightOption: return "⌥"
|
||||
case .control, .rightControl: return "⌃"
|
||||
case .function: return "fn"
|
||||
case .f17: return "F17"
|
||||
case .volumeUp: return "🔊"
|
||||
case .volumeDown: return "🔉"
|
||||
case .mute: return "🔇"
|
||||
case .f18: return "F18"
|
||||
case .f19: return "F19"
|
||||
case .f20: return "F20"
|
||||
case .f5: return "F5"
|
||||
case .f6: return "F6"
|
||||
case .f7: return "F7"
|
||||
case .f3: return "F3"
|
||||
case .f8: return "F8"
|
||||
case .f9: return "F9"
|
||||
case .f11: return "F11"
|
||||
case .f13: return "F13"
|
||||
case .f16: return "F16"
|
||||
case .f14: return "F14"
|
||||
case .f10: return "F10"
|
||||
case .f12: return "F12"
|
||||
case .f15: return "F15"
|
||||
case .help: return "?⃝"
|
||||
case .home: return "↖"
|
||||
case .pageUp: return "⇞"
|
||||
case .forwardDelete: return "⌦"
|
||||
case .f4: return "F4"
|
||||
case .end: return "↘"
|
||||
case .f2: return "F2"
|
||||
case .pageDown: return "⇟"
|
||||
case .f1: return "F1"
|
||||
case .leftArrow: return "←"
|
||||
case .rightArrow: return "→"
|
||||
case .downArrow: return "↓"
|
||||
case .upArrow: return "↑"
|
||||
}
|
||||
}
|
||||
}
|
82
phpmon/Vendor/HotKey/KeyCombo.swift
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
import AppKit
|
||||
|
||||
public struct KeyCombo: Equatable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
public var carbonKeyCode: UInt32
|
||||
public var carbonModifiers: UInt32
|
||||
|
||||
public var key: Key? {
|
||||
get {
|
||||
return Key(carbonKeyCode: carbonKeyCode)
|
||||
}
|
||||
|
||||
set {
|
||||
carbonKeyCode = newValue?.carbonKeyCode ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
public var modifiers: NSEvent.ModifierFlags {
|
||||
get {
|
||||
return NSEvent.ModifierFlags(carbonFlags: carbonModifiers)
|
||||
}
|
||||
|
||||
set {
|
||||
carbonModifiers = newValue.carbonFlags
|
||||
}
|
||||
}
|
||||
|
||||
public var isValid: Bool {
|
||||
return carbonKeyCode >= 0
|
||||
}
|
||||
|
||||
// MARK: - Initializers
|
||||
|
||||
public init(carbonKeyCode: UInt32, carbonModifiers: UInt32 = 0) {
|
||||
self.carbonKeyCode = carbonKeyCode
|
||||
self.carbonModifiers = carbonModifiers
|
||||
}
|
||||
|
||||
public init(key: Key, modifiers: NSEvent.ModifierFlags = []) {
|
||||
self.carbonKeyCode = key.carbonKeyCode
|
||||
self.carbonModifiers = modifiers.carbonFlags
|
||||
}
|
||||
|
||||
// MARK: - Converting Keys
|
||||
|
||||
public static func carbonKeyCodeToString(_ carbonKeyCode: UInt32) -> String? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyCombo {
|
||||
public var dictionary: [String: Any] {
|
||||
return [
|
||||
"keyCode": Int(carbonKeyCode),
|
||||
"modifiers": Int(carbonModifiers)
|
||||
]
|
||||
}
|
||||
|
||||
public init?(dictionary: [String: Any]) {
|
||||
guard let keyCode = dictionary["keyCode"] as? Int,
|
||||
let modifiers = dictionary["modifiers"] as? Int
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(carbonKeyCode: UInt32(keyCode), carbonModifiers: UInt32(modifiers))
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyCombo: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var output = modifiers.description
|
||||
|
||||
if let keyDescription = key?.description {
|
||||
output += keyDescription
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
20
phpmon/Vendor/HotKey/LICENSE
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2017–2019 Sam Soffes, http://soff.es
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in 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:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
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 AUTHORS 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 IN THE SOFTWARE.
|
70
phpmon/Vendor/HotKey/ModifierFlagsExtension.swift
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
import AppKit
|
||||
import Carbon
|
||||
|
||||
extension NSEvent.ModifierFlags {
|
||||
public var carbonFlags: UInt32 {
|
||||
var carbonFlags: UInt32 = 0
|
||||
|
||||
if contains(.command) {
|
||||
carbonFlags |= UInt32(cmdKey)
|
||||
}
|
||||
|
||||
if contains(.option) {
|
||||
carbonFlags |= UInt32(optionKey)
|
||||
}
|
||||
|
||||
if contains(.control) {
|
||||
carbonFlags |= UInt32(controlKey)
|
||||
}
|
||||
|
||||
if contains(.shift) {
|
||||
carbonFlags |= UInt32(shiftKey)
|
||||
}
|
||||
|
||||
return carbonFlags
|
||||
}
|
||||
|
||||
public init(carbonFlags: UInt32) {
|
||||
self.init()
|
||||
|
||||
if carbonFlags & UInt32(cmdKey) == UInt32(cmdKey) {
|
||||
insert(.command)
|
||||
}
|
||||
|
||||
if carbonFlags & UInt32(optionKey) == UInt32(optionKey) {
|
||||
insert(.option)
|
||||
}
|
||||
|
||||
if carbonFlags & UInt32(controlKey) == UInt32(controlKey) {
|
||||
insert(.control)
|
||||
}
|
||||
|
||||
if carbonFlags & UInt32(shiftKey) == UInt32(shiftKey) {
|
||||
insert(.shift)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSEvent.ModifierFlags: CustomStringConvertible {
|
||||
public var description: String {
|
||||
var output = ""
|
||||
|
||||
if contains(.control) {
|
||||
output += Key.control.description
|
||||
}
|
||||
|
||||
if contains(.option) {
|
||||
output += Key.option.description
|
||||
}
|
||||
|
||||
if contains(.shift) {
|
||||
output += Key.shift.description
|
||||
}
|
||||
|
||||
if contains(.command) {
|
||||
output += Key.command.description
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
}
|