Compare commits
7 Commits
v0.2.0
...
8aa57ee3d7
| Author | SHA1 | Date | |
|---|---|---|---|
|
8aa57ee3d7
|
|||
|
6576cc5e3c
|
|||
|
1a09eb588d
|
|||
|
c8cb209165
|
|||
|
645af1f660
|
|||
|
bb87d7d84c
|
|||
|
507d77a0de
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -81,3 +81,6 @@ build/
|
|||||||
|
|
||||||
# Mac... files
|
# Mac... files
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
|
|
||||||
|
# Releases
|
||||||
|
releases/
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
187F4B521BFC3BD29ADA79E3 /* HotkeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEB7FE2BDE5C25B7E599F340 /* HotkeyManager.swift */; };
|
187F4B521BFC3BD29ADA79E3 /* HotkeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEB7FE2BDE5C25B7E599F340 /* HotkeyManager.swift */; };
|
||||||
1AB4A0F1BE668D3130EFBA93 /* TerminalTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE9E5BADB0C427903A0D874 /* TerminalTheme.swift */; };
|
1AB4A0F1BE668D3130EFBA93 /* TerminalTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFE9E5BADB0C427903A0D874 /* TerminalTheme.swift */; };
|
||||||
2089566A2BBAA65EA82119B3 /* NotchOrchestratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6D136A0B3FC79DDE12A826 /* NotchOrchestratorTests.swift */; };
|
2089566A2BBAA65EA82119B3 /* NotchOrchestratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6D136A0B3FC79DDE12A826 /* NotchOrchestratorTests.swift */; };
|
||||||
|
21373D6E9C2F34FD63CEC6A5 /* TerminalScrollCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D9E323185F6D722B360C3C /* TerminalScrollCoordinatorTests.swift */; };
|
||||||
2375B9DA559A0777FE558A8B /* WorkspaceStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA62DFE9AD003F4C5B55F14 /* WorkspaceStoreTests.swift */; };
|
2375B9DA559A0777FE558A8B /* WorkspaceStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA62DFE9AD003F4C5B55F14 /* WorkspaceStoreTests.swift */; };
|
||||||
23E2DDCF36D0DAB2EA72C39C /* NotchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B352301BC9CAD7C9D8B7AA9 /* NotchState.swift */; };
|
23E2DDCF36D0DAB2EA72C39C /* NotchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B352301BC9CAD7C9D8B7AA9 /* NotchState.swift */; };
|
||||||
26AE379040149CBE05B314BB /* WorkspacesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70A31EFACF23DD9262A040E /* WorkspacesSettingsView.swift */; };
|
26AE379040149CBE05B314BB /* WorkspacesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70A31EFACF23DD9262A040E /* WorkspacesSettingsView.swift */; };
|
||||||
@@ -22,6 +23,8 @@
|
|||||||
2DF22798D3A7514E2A9183FC /* WorkspaceSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2BCA543CE54DAB1DB80E43 /* WorkspaceSummary.swift */; };
|
2DF22798D3A7514E2A9183FC /* WorkspaceSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2BCA543CE54DAB1DB80E43 /* WorkspaceSummary.swift */; };
|
||||||
34AF69BF7AF8DC78ADE3774A /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E7EEEDFBD8D9CEB8FD86A5 /* GeneralSettingsView.swift */; };
|
34AF69BF7AF8DC78ADE3774A /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E7EEEDFBD8D9CEB8FD86A5 /* GeneralSettingsView.swift */; };
|
||||||
3B69CB3CDEC2E5F2DCE600F9 /* NotchSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297BA3E5B837FBDDEED9DE66 /* NotchSettings.swift */; };
|
3B69CB3CDEC2E5F2DCE600F9 /* NotchSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297BA3E5B837FBDDEED9DE66 /* NotchSettings.swift */; };
|
||||||
|
40183737D8C237022D0882FD /* TerminalScrollbackEstimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77DC2AED643512C786AD70A /* TerminalScrollbackEstimatorTests.swift */; };
|
||||||
|
4BA9D6274CFD6A87DC397B8E /* MouseAwareTerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982BA8965FE92A59D66F378B /* MouseAwareTerminalView.swift */; };
|
||||||
4CFA0C79095ACE0A327A2469 /* WorkspaceRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E97758F68FACCFFACA895B7 /* WorkspaceRegistry.swift */; };
|
4CFA0C79095ACE0A327A2469 /* WorkspaceRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E97758F68FACCFFACA895B7 /* WorkspaceRegistry.swift */; };
|
||||||
4D335F67B71F7DD977B6AEF9 /* AppSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6C98C406899F4B242075AF /* AppSettingsStore.swift */; };
|
4D335F67B71F7DD977B6AEF9 /* AppSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6C98C406899F4B242075AF /* AppSettingsStore.swift */; };
|
||||||
4E0AD14B7427532271E485AA /* NotchWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFAC70814C72BAF76D90B9DF /* NotchWindow.swift */; };
|
4E0AD14B7427532271E485AA /* NotchWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFAC70814C72BAF76D90B9DF /* NotchWindow.swift */; };
|
||||||
@@ -54,8 +57,10 @@
|
|||||||
D088BC850F37E717844761C6 /* CommandNotchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5159CB9DBE2BAA0D2E201C39 /* CommandNotchApp.swift */; };
|
D088BC850F37E717844761C6 /* CommandNotchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5159CB9DBE2BAA0D2E201C39 /* CommandNotchApp.swift */; };
|
||||||
D2468B19D6F0A2C1DFDFE2B7 /* ScreenContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A770A63582CF9834F4E7F058 /* ScreenContextTests.swift */; };
|
D2468B19D6F0A2C1DFDFE2B7 /* ScreenContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A770A63582CF9834F4E7F058 /* ScreenContextTests.swift */; };
|
||||||
D4CEB4B895F75D91FA892A06 /* PopoutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB593A2546BF2C0BE8E40387 /* PopoutWindowController.swift */; };
|
D4CEB4B895F75D91FA892A06 /* PopoutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB593A2546BF2C0BE8E40387 /* PopoutWindowController.swift */; };
|
||||||
|
D6BBC4DE23DEA39D9713469A /* TerminalScrollWheelRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7315EA485A52A8DC42DF925E /* TerminalScrollWheelRouterTests.swift */; };
|
||||||
DAD3AB4A0DAADA32C02D959E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3125FD3DC55420122CF85D80 /* SettingsView.swift */; };
|
DAD3AB4A0DAADA32C02D959E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3125FD3DC55420122CF85D80 /* SettingsView.swift */; };
|
||||||
DCFD5B03E64A46783F46726B /* TerminalCommandArrowBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22AA47452CF798A977A6F47 /* TerminalCommandArrowBehavior.swift */; };
|
DCFD5B03E64A46783F46726B /* TerminalCommandArrowBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22AA47452CF798A977A6F47 /* TerminalCommandArrowBehavior.swift */; };
|
||||||
|
DD116B0FCC341D66F1534EC4 /* TerminalScrollbackEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165DCCD7BB164A6470D49BBF /* TerminalScrollbackEstimator.swift */; };
|
||||||
E63FD5862C0EC54E284F6A0F /* ScreenRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF23753B5A0CAF04D7566A3 /* ScreenRegistry.swift */; };
|
E63FD5862C0EC54E284F6A0F /* ScreenRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF23753B5A0CAF04D7566A3 /* ScreenRegistry.swift */; };
|
||||||
E792411FA82E79E810F4B4C3 /* HotkeyRecorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B81432CECBDB61D21EE4DC3 /* HotkeyRecorderView.swift */; };
|
E792411FA82E79E810F4B4C3 /* HotkeyRecorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B81432CECBDB61D21EE4DC3 /* HotkeyRecorderView.swift */; };
|
||||||
EE72479BA5A25FF31BACCC50 /* NotchOrchestrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7912AF01E1600B8619AF31 /* NotchOrchestrator.swift */; };
|
EE72479BA5A25FF31BACCC50 /* NotchOrchestrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC7912AF01E1600B8619AF31 /* NotchOrchestrator.swift */; };
|
||||||
@@ -82,6 +87,7 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
0E97758F68FACCFFACA895B7 /* WorkspaceRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceRegistry.swift; sourceTree = "<group>"; };
|
0E97758F68FACCFFACA895B7 /* WorkspaceRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceRegistry.swift; sourceTree = "<group>"; };
|
||||||
|
165DCCD7BB164A6470D49BBF /* TerminalScrollbackEstimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalScrollbackEstimator.swift; sourceTree = "<group>"; };
|
||||||
27E7EEEDFBD8D9CEB8FD86A5 /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = "<group>"; };
|
27E7EEEDFBD8D9CEB8FD86A5 /* GeneralSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsView.swift; sourceTree = "<group>"; };
|
||||||
297BA3E5B837FBDDEED9DE66 /* NotchSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchSettings.swift; sourceTree = "<group>"; };
|
297BA3E5B837FBDDEED9DE66 /* NotchSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchSettings.swift; sourceTree = "<group>"; };
|
||||||
2B81432CECBDB61D21EE4DC3 /* HotkeyRecorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeyRecorderView.swift; sourceTree = "<group>"; };
|
2B81432CECBDB61D21EE4DC3 /* HotkeyRecorderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeyRecorderView.swift; sourceTree = "<group>"; };
|
||||||
@@ -91,6 +97,7 @@
|
|||||||
3CB1DFD6FCDF64B4DF24230A /* WorkspaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceController.swift; sourceTree = "<group>"; };
|
3CB1DFD6FCDF64B4DF24230A /* WorkspaceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceController.swift; sourceTree = "<group>"; };
|
||||||
3F57837A7115DEEE11E14B40 /* NotchShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchShape.swift; sourceTree = "<group>"; };
|
3F57837A7115DEEE11E14B40 /* NotchShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchShape.swift; sourceTree = "<group>"; };
|
||||||
3F5FF5623898FA150C3B70D4 /* AppSettingsControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsControllerTests.swift; sourceTree = "<group>"; };
|
3F5FF5623898FA150C3B70D4 /* AppSettingsControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsControllerTests.swift; sourceTree = "<group>"; };
|
||||||
|
40D9E323185F6D722B360C3C /* TerminalScrollCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalScrollCoordinatorTests.swift; sourceTree = "<group>"; };
|
||||||
48198AFE5473B0F7AECAB3FB /* AboutSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSettingsView.swift; sourceTree = "<group>"; };
|
48198AFE5473B0F7AECAB3FB /* AboutSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSettingsView.swift; sourceTree = "<group>"; };
|
||||||
496267F03E261FEC9EBD5A9D /* CommandNotchUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CommandNotchUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
496267F03E261FEC9EBD5A9D /* CommandNotchUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CommandNotchUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
49E1791BB45E1505500ACC67 /* TerminalSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSession.swift; sourceTree = "<group>"; };
|
49E1791BB45E1505500ACC67 /* TerminalSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSession.swift; sourceTree = "<group>"; };
|
||||||
@@ -105,6 +112,7 @@
|
|||||||
726B935606FD961FD7E8C2BE /* CommandNotchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandNotchUITests.swift; sourceTree = "<group>"; };
|
726B935606FD961FD7E8C2BE /* CommandNotchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandNotchUITests.swift; sourceTree = "<group>"; };
|
||||||
728B3125F7F7FDB7313D2DC6 /* SettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = "<group>"; };
|
728B3125F7F7FDB7313D2DC6 /* SettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = "<group>"; };
|
||||||
72A1D3D12BAC593838B3125C /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; };
|
72A1D3D12BAC593838B3125C /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = "<group>"; };
|
||||||
|
7315EA485A52A8DC42DF925E /* TerminalScrollWheelRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalScrollWheelRouterTests.swift; sourceTree = "<group>"; };
|
||||||
74463E4EAB78F56345360CD5 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
74463E4EAB78F56345360CD5 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||||
7B2BCA543CE54DAB1DB80E43 /* WorkspaceSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSummary.swift; sourceTree = "<group>"; };
|
7B2BCA543CE54DAB1DB80E43 /* WorkspaceSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSummary.swift; sourceTree = "<group>"; };
|
||||||
8210D7783A614FD7190F5DDD /* LaunchAtLoginHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLoginHelper.swift; sourceTree = "<group>"; };
|
8210D7783A614FD7190F5DDD /* LaunchAtLoginHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLoginHelper.swift; sourceTree = "<group>"; };
|
||||||
@@ -113,6 +121,7 @@
|
|||||||
8BB1C403BC2157756F572ACF /* HotkeyBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeyBinding.swift; sourceTree = "<group>"; };
|
8BB1C403BC2157756F572ACF /* HotkeyBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeyBinding.swift; sourceTree = "<group>"; };
|
||||||
8CB8AF76C0F728897A26D7EF /* ScreenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenManager.swift; sourceTree = "<group>"; };
|
8CB8AF76C0F728897A26D7EF /* ScreenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenManager.swift; sourceTree = "<group>"; };
|
||||||
900F0476BE9E3600FBD371BB /* SettingsBindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBindings.swift; sourceTree = "<group>"; };
|
900F0476BE9E3600FBD371BB /* SettingsBindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBindings.swift; sourceTree = "<group>"; };
|
||||||
|
982BA8965FE92A59D66F378B /* MouseAwareTerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseAwareTerminalView.swift; sourceTree = "<group>"; };
|
||||||
9C55F29B779DA0E5C5FC8627 /* SwiftTermView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTermView.swift; sourceTree = "<group>"; };
|
9C55F29B779DA0E5C5FC8627 /* SwiftTermView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTermView.swift; sourceTree = "<group>"; };
|
||||||
9E6C98C406899F4B242075AF /* AppSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsStore.swift; sourceTree = "<group>"; };
|
9E6C98C406899F4B242075AF /* AppSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsStore.swift; sourceTree = "<group>"; };
|
||||||
A64A11F27E65B342B991629A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
A64A11F27E65B342B991629A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
@@ -124,6 +133,7 @@
|
|||||||
CFE9E5BADB0C427903A0D874 /* TerminalTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalTheme.swift; sourceTree = "<group>"; };
|
CFE9E5BADB0C427903A0D874 /* TerminalTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalTheme.swift; sourceTree = "<group>"; };
|
||||||
D03D042117E59DCA9D553844 /* HotkeySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeySettingsView.swift; sourceTree = "<group>"; };
|
D03D042117E59DCA9D553844 /* HotkeySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeySettingsView.swift; sourceTree = "<group>"; };
|
||||||
D288132700770C4A625A15F6 /* WindowFrameCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowFrameCalculatorTests.swift; sourceTree = "<group>"; };
|
D288132700770C4A625A15F6 /* WindowFrameCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowFrameCalculatorTests.swift; sourceTree = "<group>"; };
|
||||||
|
D77DC2AED643512C786AD70A /* TerminalScrollbackEstimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalScrollbackEstimatorTests.swift; sourceTree = "<group>"; };
|
||||||
DC7912AF01E1600B8619AF31 /* NotchOrchestrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchOrchestrator.swift; sourceTree = "<group>"; };
|
DC7912AF01E1600B8619AF31 /* NotchOrchestrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchOrchestrator.swift; sourceTree = "<group>"; };
|
||||||
DF0FFBC96F2446687D6474F4 /* WorkspaceSwitcherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSwitcherView.swift; sourceTree = "<group>"; };
|
DF0FFBC96F2446687D6474F4 /* WorkspaceSwitcherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSwitcherView.swift; sourceTree = "<group>"; };
|
||||||
E37A6DCD9C5DE1FE11C4C1CD /* TerminalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSettingsView.swift; sourceTree = "<group>"; };
|
E37A6DCD9C5DE1FE11C4C1CD /* TerminalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSettingsView.swift; sourceTree = "<group>"; };
|
||||||
@@ -165,6 +175,7 @@
|
|||||||
7181BB1F3926B457445105E5 /* ScreenContext.swift */,
|
7181BB1F3926B457445105E5 /* ScreenContext.swift */,
|
||||||
AAF23753B5A0CAF04D7566A3 /* ScreenRegistry.swift */,
|
AAF23753B5A0CAF04D7566A3 /* ScreenRegistry.swift */,
|
||||||
567E85A2ED628460CEC760DB /* TerminalManager.swift */,
|
567E85A2ED628460CEC760DB /* TerminalManager.swift */,
|
||||||
|
165DCCD7BB164A6470D49BBF /* TerminalScrollbackEstimator.swift */,
|
||||||
49E1791BB45E1505500ACC67 /* TerminalSession.swift */,
|
49E1791BB45E1505500ACC67 /* TerminalSession.swift */,
|
||||||
CFE9E5BADB0C427903A0D874 /* TerminalTheme.swift */,
|
CFE9E5BADB0C427903A0D874 /* TerminalTheme.swift */,
|
||||||
3CB1DFD6FCDF64B4DF24230A /* WorkspaceController.swift */,
|
3CB1DFD6FCDF64B4DF24230A /* WorkspaceController.swift */,
|
||||||
@@ -218,6 +229,9 @@
|
|||||||
A770A63582CF9834F4E7F058 /* ScreenContextTests.swift */,
|
A770A63582CF9834F4E7F058 /* ScreenContextTests.swift */,
|
||||||
EEC7F7D8D15A1BC4EE43DDDB /* ScreenRegistryTests.swift */,
|
EEC7F7D8D15A1BC4EE43DDDB /* ScreenRegistryTests.swift */,
|
||||||
C0D19729317029008D81F361 /* TerminalCommandArrowBehaviorTests.swift */,
|
C0D19729317029008D81F361 /* TerminalCommandArrowBehaviorTests.swift */,
|
||||||
|
D77DC2AED643512C786AD70A /* TerminalScrollbackEstimatorTests.swift */,
|
||||||
|
40D9E323185F6D722B360C3C /* TerminalScrollCoordinatorTests.swift */,
|
||||||
|
7315EA485A52A8DC42DF925E /* TerminalScrollWheelRouterTests.swift */,
|
||||||
D288132700770C4A625A15F6 /* WindowFrameCalculatorTests.swift */,
|
D288132700770C4A625A15F6 /* WindowFrameCalculatorTests.swift */,
|
||||||
591FCE91AF83A8A8E44E1625 /* WorkspaceRegistryTests.swift */,
|
591FCE91AF83A8A8E44E1625 /* WorkspaceRegistryTests.swift */,
|
||||||
4FA62DFE9AD003F4C5B55F14 /* WorkspaceStoreTests.swift */,
|
4FA62DFE9AD003F4C5B55F14 /* WorkspaceStoreTests.swift */,
|
||||||
@@ -248,6 +262,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
2B81432CECBDB61D21EE4DC3 /* HotkeyRecorderView.swift */,
|
2B81432CECBDB61D21EE4DC3 /* HotkeyRecorderView.swift */,
|
||||||
|
982BA8965FE92A59D66F378B /* MouseAwareTerminalView.swift */,
|
||||||
3F57837A7115DEEE11E14B40 /* NotchShape.swift */,
|
3F57837A7115DEEE11E14B40 /* NotchShape.swift */,
|
||||||
EFAC70814C72BAF76D90B9DF /* NotchWindow.swift */,
|
EFAC70814C72BAF76D90B9DF /* NotchWindow.swift */,
|
||||||
9C55F29B779DA0E5C5FC8627 /* SwiftTermView.swift */,
|
9C55F29B779DA0E5C5FC8627 /* SwiftTermView.swift */,
|
||||||
@@ -370,7 +385,6 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = C47A3896770C98F2A3E62B7A /* Build configuration list for PBXProject "CommandNotch" */;
|
buildConfigurationList = C47A3896770C98F2A3E62B7A /* Build configuration list for PBXProject "CommandNotch" */;
|
||||||
compatibilityVersion = "Xcode 14.0";
|
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
@@ -383,6 +397,7 @@
|
|||||||
28377BE3F9997892D4929B6E /* XCRemoteSwiftPackageReference "SwiftTerm" */,
|
28377BE3F9997892D4929B6E /* XCRemoteSwiftPackageReference "SwiftTerm" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
|
productRefGroup = B269158E04E8E603B61448F0 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
@@ -423,6 +438,9 @@
|
|||||||
D2468B19D6F0A2C1DFDFE2B7 /* ScreenContextTests.swift in Sources */,
|
D2468B19D6F0A2C1DFDFE2B7 /* ScreenContextTests.swift in Sources */,
|
||||||
8A1B2A4CD61B0D9A4BAB075B /* ScreenRegistryTests.swift in Sources */,
|
8A1B2A4CD61B0D9A4BAB075B /* ScreenRegistryTests.swift in Sources */,
|
||||||
CFBBE994BA6BAD4658AAB9CB /* TerminalCommandArrowBehaviorTests.swift in Sources */,
|
CFBBE994BA6BAD4658AAB9CB /* TerminalCommandArrowBehaviorTests.swift in Sources */,
|
||||||
|
21373D6E9C2F34FD63CEC6A5 /* TerminalScrollCoordinatorTests.swift in Sources */,
|
||||||
|
D6BBC4DE23DEA39D9713469A /* TerminalScrollWheelRouterTests.swift in Sources */,
|
||||||
|
40183737D8C237022D0882FD /* TerminalScrollbackEstimatorTests.swift in Sources */,
|
||||||
0F133E8A88D2E313D90C32AD /* WindowFrameCalculatorTests.swift in Sources */,
|
0F133E8A88D2E313D90C32AD /* WindowFrameCalculatorTests.swift in Sources */,
|
||||||
154F363D434A26105C5999B5 /* WorkspaceRegistryTests.swift in Sources */,
|
154F363D434A26105C5999B5 /* WorkspaceRegistryTests.swift in Sources */,
|
||||||
2375B9DA559A0777FE558A8B /* WorkspaceStoreTests.swift in Sources */,
|
2375B9DA559A0777FE558A8B /* WorkspaceStoreTests.swift in Sources */,
|
||||||
@@ -448,6 +466,7 @@
|
|||||||
E792411FA82E79E810F4B4C3 /* HotkeyRecorderView.swift in Sources */,
|
E792411FA82E79E810F4B4C3 /* HotkeyRecorderView.swift in Sources */,
|
||||||
6A18F6635B509FF58669F505 /* HotkeySettingsView.swift in Sources */,
|
6A18F6635B509FF58669F505 /* HotkeySettingsView.swift in Sources */,
|
||||||
12F68EDA880030DAB644FF5F /* LaunchAtLoginHelper.swift in Sources */,
|
12F68EDA880030DAB644FF5F /* LaunchAtLoginHelper.swift in Sources */,
|
||||||
|
4BA9D6274CFD6A87DC397B8E /* MouseAwareTerminalView.swift in Sources */,
|
||||||
A74E14B8AD19C53820853D8E /* NSScreen+Extensions.swift in Sources */,
|
A74E14B8AD19C53820853D8E /* NSScreen+Extensions.swift in Sources */,
|
||||||
EE72479BA5A25FF31BACCC50 /* NotchOrchestrator.swift in Sources */,
|
EE72479BA5A25FF31BACCC50 /* NotchOrchestrator.swift in Sources */,
|
||||||
3B69CB3CDEC2E5F2DCE600F9 /* NotchSettings.swift in Sources */,
|
3B69CB3CDEC2E5F2DCE600F9 /* NotchSettings.swift in Sources */,
|
||||||
@@ -465,6 +484,7 @@
|
|||||||
88113BA9B217DA579C36BEBE /* TabBar.swift in Sources */,
|
88113BA9B217DA579C36BEBE /* TabBar.swift in Sources */,
|
||||||
DCFD5B03E64A46783F46726B /* TerminalCommandArrowBehavior.swift in Sources */,
|
DCFD5B03E64A46783F46726B /* TerminalCommandArrowBehavior.swift in Sources */,
|
||||||
6F249EDFA2D654457DF385F1 /* TerminalManager.swift in Sources */,
|
6F249EDFA2D654457DF385F1 /* TerminalManager.swift in Sources */,
|
||||||
|
DD116B0FCC341D66F1534EC4 /* TerminalScrollbackEstimator.swift in Sources */,
|
||||||
7A69B5AAC686174BCA54D0F0 /* TerminalSession.swift in Sources */,
|
7A69B5AAC686174BCA54D0F0 /* TerminalSession.swift in Sources */,
|
||||||
65C7DB7296C6C6A77598A1F4 /* TerminalSettingsView.swift in Sources */,
|
65C7DB7296C6C6A77598A1F4 /* TerminalSettingsView.swift in Sources */,
|
||||||
1AB4A0F1BE668D3130EFBA93 /* TerminalTheme.swift in Sources */,
|
1AB4A0F1BE668D3130EFBA93 /* TerminalTheme.swift in Sources */,
|
||||||
|
|||||||
Binary file not shown.
@@ -5,33 +5,9 @@
|
|||||||
<key>SchemeUserState</key>
|
<key>SchemeUserState</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CommandNotch.xcscheme_^#shared#^_</key>
|
<key>CommandNotch.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict/>
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>0</integer>
|
|
||||||
</dict>
|
|
||||||
<key>CommandNotchTests.xcscheme_^#shared#^_</key>
|
|
||||||
<dict>
|
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>2</integer>
|
|
||||||
</dict>
|
|
||||||
<key>CommandNotchUITests.xcscheme_^#shared#^_</key>
|
|
||||||
<dict>
|
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>2</integer>
|
|
||||||
</dict>
|
|
||||||
<key>Release-CommandNotch.xcscheme_^#shared#^_</key>
|
<key>Release-CommandNotch.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict/>
|
||||||
<key>orderHint</key>
|
|
||||||
<integer>1</integer>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>SuppressBuildableAutocreation</key>
|
|
||||||
<dict>
|
|
||||||
<key>1485207FA11756EC2DF4F08B</key>
|
|
||||||
<dict>
|
|
||||||
<key>primary</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
observeSizePreferences()
|
observeSizePreferences()
|
||||||
observeFontSizeChanges()
|
observeFontSizeChanges()
|
||||||
observeTerminalThemeChanges()
|
observeTerminalThemeChanges()
|
||||||
|
observeTerminalScrollbackChanges()
|
||||||
applyUITestLaunchBehaviorIfNeeded()
|
applyUITestLaunchBehaviorIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +91,16 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func observeTerminalScrollbackChanges() {
|
||||||
|
settingsController.$settings
|
||||||
|
.map(\.terminal.scrollbackLines)
|
||||||
|
.removeDuplicates()
|
||||||
|
.sink { scrollbackLines in
|
||||||
|
WorkspaceRegistry.shared.updateAllWorkspacesScrollbackLines(scrollbackLines)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
private var launchArguments: [String] {
|
private var launchArguments: [String] {
|
||||||
ProcessInfo.processInfo.arguments
|
ProcessInfo.processInfo.arguments
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import AppKit
|
||||||
|
import SwiftTerm
|
||||||
|
|
||||||
|
struct TerminalScrollWheelRouter {
|
||||||
|
static func shouldSendMouseWheel(
|
||||||
|
allowMouseReporting: Bool,
|
||||||
|
mouseMode: Terminal.MouseMode,
|
||||||
|
deltaY: Double
|
||||||
|
) -> Bool {
|
||||||
|
allowMouseReporting && mouseMode != .off && deltaY != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static func velocity(for deltaY: Double) -> Int {
|
||||||
|
let magnitude = Int(abs(deltaY))
|
||||||
|
if magnitude > 9 {
|
||||||
|
return 20
|
||||||
|
}
|
||||||
|
if magnitude > 5 {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
if magnitude > 1 {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
static func gridPosition(
|
||||||
|
point: CGPoint,
|
||||||
|
bounds: CGRect,
|
||||||
|
cols: Int,
|
||||||
|
rows: Int
|
||||||
|
) -> (x: Int, y: Int, pixelX: Int, pixelY: Int) {
|
||||||
|
let safeCols = max(cols, 1)
|
||||||
|
let safeRows = max(rows, 1)
|
||||||
|
let width = max(bounds.width, 1)
|
||||||
|
let height = max(bounds.height, 1)
|
||||||
|
let clampedX = min(max(point.x, 0), width)
|
||||||
|
let clampedY = min(max(point.y, 0), height)
|
||||||
|
let cellWidth = width / CGFloat(safeCols)
|
||||||
|
let cellHeight = height / CGFloat(safeRows)
|
||||||
|
|
||||||
|
let column = min(max(Int(clampedX / cellWidth), 0), safeCols - 1)
|
||||||
|
let row = min(max(Int((height - clampedY) / cellHeight), 0), safeRows - 1)
|
||||||
|
let pixelX = min(max(Int(clampedX), 0), Int(width))
|
||||||
|
let pixelY = min(max(Int(height - clampedY), 0), Int(height))
|
||||||
|
|
||||||
|
return (column, row, pixelX, pixelY)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,7 @@ struct AppSettings: Equatable, Codable {
|
|||||||
fontSize: NotchSettings.Defaults.terminalFontSize,
|
fontSize: NotchSettings.Defaults.terminalFontSize,
|
||||||
shellPath: NotchSettings.Defaults.terminalShell,
|
shellPath: NotchSettings.Defaults.terminalShell,
|
||||||
themeRawValue: NotchSettings.Defaults.terminalTheme,
|
themeRawValue: NotchSettings.Defaults.terminalTheme,
|
||||||
|
scrollbackLines: NotchSettings.Defaults.terminalScrollbackLines,
|
||||||
sizePresetsJSON: NotchSettings.Defaults.terminalSizePresets
|
sizePresetsJSON: NotchSettings.Defaults.terminalSizePresets
|
||||||
),
|
),
|
||||||
hotkeys: HotkeySettings(
|
hotkeys: HotkeySettings(
|
||||||
@@ -106,6 +107,7 @@ extension AppSettings {
|
|||||||
var fontSize: Double
|
var fontSize: Double
|
||||||
var shellPath: String
|
var shellPath: String
|
||||||
var themeRawValue: String
|
var themeRawValue: String
|
||||||
|
var scrollbackLines: Int
|
||||||
var sizePresetsJSON: String
|
var sizePresetsJSON: String
|
||||||
|
|
||||||
var theme: TerminalTheme {
|
var theme: TerminalTheme {
|
||||||
@@ -155,6 +157,7 @@ struct TerminalSessionConfiguration: Equatable {
|
|||||||
var fontSize: CGFloat
|
var fontSize: CGFloat
|
||||||
var theme: TerminalTheme
|
var theme: TerminalTheme
|
||||||
var shellPath: String
|
var shellPath: String
|
||||||
|
var scrollbackLines: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ final class AppSettingsController: ObservableObject, TerminalSessionConfiguratio
|
|||||||
TerminalSessionConfiguration(
|
TerminalSessionConfiguration(
|
||||||
fontSize: CGFloat(settings.terminal.fontSize),
|
fontSize: CGFloat(settings.terminal.fontSize),
|
||||||
theme: settings.terminal.theme,
|
theme: settings.terminal.theme,
|
||||||
shellPath: settings.terminal.shellPath
|
shellPath: settings.terminal.shellPath,
|
||||||
|
scrollbackLines: settings.terminal.scrollbackLines
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ struct UserDefaultsAppSettingsStore: AppSettingsStoreType {
|
|||||||
fontSize: double(NotchSettings.Keys.terminalFontSize, default: NotchSettings.Defaults.terminalFontSize),
|
fontSize: double(NotchSettings.Keys.terminalFontSize, default: NotchSettings.Defaults.terminalFontSize),
|
||||||
shellPath: string(NotchSettings.Keys.terminalShell, default: NotchSettings.Defaults.terminalShell),
|
shellPath: string(NotchSettings.Keys.terminalShell, default: NotchSettings.Defaults.terminalShell),
|
||||||
themeRawValue: string(NotchSettings.Keys.terminalTheme, default: NotchSettings.Defaults.terminalTheme),
|
themeRawValue: string(NotchSettings.Keys.terminalTheme, default: NotchSettings.Defaults.terminalTheme),
|
||||||
|
scrollbackLines: integer(NotchSettings.Keys.terminalScrollbackLines, default: NotchSettings.Defaults.terminalScrollbackLines),
|
||||||
sizePresetsJSON: string(NotchSettings.Keys.terminalSizePresets, default: NotchSettings.Defaults.terminalSizePresets)
|
sizePresetsJSON: string(NotchSettings.Keys.terminalSizePresets, default: NotchSettings.Defaults.terminalSizePresets)
|
||||||
),
|
),
|
||||||
hotkeys: .init(
|
hotkeys: .init(
|
||||||
@@ -101,6 +102,7 @@ struct UserDefaultsAppSettingsStore: AppSettingsStoreType {
|
|||||||
defaults.set(settings.terminal.fontSize, forKey: NotchSettings.Keys.terminalFontSize)
|
defaults.set(settings.terminal.fontSize, forKey: NotchSettings.Keys.terminalFontSize)
|
||||||
defaults.set(settings.terminal.shellPath, forKey: NotchSettings.Keys.terminalShell)
|
defaults.set(settings.terminal.shellPath, forKey: NotchSettings.Keys.terminalShell)
|
||||||
defaults.set(settings.terminal.themeRawValue, forKey: NotchSettings.Keys.terminalTheme)
|
defaults.set(settings.terminal.themeRawValue, forKey: NotchSettings.Keys.terminalTheme)
|
||||||
|
defaults.set(settings.terminal.scrollbackLines, forKey: NotchSettings.Keys.terminalScrollbackLines)
|
||||||
defaults.set(settings.terminal.sizePresetsJSON, forKey: NotchSettings.Keys.terminalSizePresets)
|
defaults.set(settings.terminal.sizePresetsJSON, forKey: NotchSettings.Keys.terminalSizePresets)
|
||||||
|
|
||||||
defaults.set(settings.hotkeys.toggle.toJSON(), forKey: NotchSettings.Keys.hotkeyToggle)
|
defaults.set(settings.hotkeys.toggle.toJSON(), forKey: NotchSettings.Keys.hotkeyToggle)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ enum NotchSettings {
|
|||||||
static let terminalFontSize = "terminalFontSize"
|
static let terminalFontSize = "terminalFontSize"
|
||||||
static let terminalShell = "terminalShell"
|
static let terminalShell = "terminalShell"
|
||||||
static let terminalTheme = "terminalTheme"
|
static let terminalTheme = "terminalTheme"
|
||||||
|
static let terminalScrollbackLines = "terminalScrollbackLines"
|
||||||
static let terminalSizePresets = "terminalSizePresets"
|
static let terminalSizePresets = "terminalSizePresets"
|
||||||
static let workspaceSummaries = "workspaceSummaries"
|
static let workspaceSummaries = "workspaceSummaries"
|
||||||
static let screenAssignments = "screenAssignments"
|
static let screenAssignments = "screenAssignments"
|
||||||
@@ -98,6 +99,7 @@ enum NotchSettings {
|
|||||||
static let terminalFontSize: Double = 13
|
static let terminalFontSize: Double = 13
|
||||||
static let terminalShell: String = ""
|
static let terminalShell: String = ""
|
||||||
static let terminalTheme: String = TerminalTheme.terminalApp.rawValue
|
static let terminalTheme: String = TerminalTheme.terminalApp.rawValue
|
||||||
|
static let terminalScrollbackLines: Int = 500
|
||||||
static let terminalSizePresets: String = TerminalSizePresetStore.defaultPresetsJSON()
|
static let terminalSizePresets: String = TerminalSizePresetStore.defaultPresetsJSON()
|
||||||
|
|
||||||
// Default hotkey bindings as JSON
|
// Default hotkey bindings as JSON
|
||||||
@@ -148,6 +150,7 @@ enum NotchSettings {
|
|||||||
Keys.terminalFontSize: Defaults.terminalFontSize,
|
Keys.terminalFontSize: Defaults.terminalFontSize,
|
||||||
Keys.terminalShell: Defaults.terminalShell,
|
Keys.terminalShell: Defaults.terminalShell,
|
||||||
Keys.terminalTheme: Defaults.terminalTheme,
|
Keys.terminalTheme: Defaults.terminalTheme,
|
||||||
|
Keys.terminalScrollbackLines: Defaults.terminalScrollbackLines,
|
||||||
Keys.terminalSizePresets: Defaults.terminalSizePresets,
|
Keys.terminalSizePresets: Defaults.terminalSizePresets,
|
||||||
|
|
||||||
Keys.hotkeyToggle: Defaults.hotkeyToggle,
|
Keys.hotkeyToggle: Defaults.hotkeyToggle,
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import AppKit
|
||||||
|
import CoreText
|
||||||
|
import SwiftTerm
|
||||||
|
|
||||||
|
enum TerminalScrollbackEstimator {
|
||||||
|
private static let minimumColumns = 1
|
||||||
|
private static let minimumRows = 1
|
||||||
|
private static let defaultBytesPerLineOverhead = 256
|
||||||
|
|
||||||
|
struct Estimate: Equatable {
|
||||||
|
let bytes: Int
|
||||||
|
let columns: Int
|
||||||
|
let rows: Int
|
||||||
|
|
||||||
|
var formattedBytes: String {
|
||||||
|
ByteCountFormatter.string(fromByteCount: Int64(bytes), countStyle: .memory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func estimate(
|
||||||
|
scrollbackLines: Int,
|
||||||
|
fontSize: Double,
|
||||||
|
openWidth: Double,
|
||||||
|
openHeight: Double
|
||||||
|
) -> Estimate {
|
||||||
|
let safeScrollbackLines = max(0, scrollbackLines)
|
||||||
|
let dimensions = terminalGridDimensions(
|
||||||
|
fontSize: fontSize,
|
||||||
|
openWidth: openWidth,
|
||||||
|
openHeight: openHeight
|
||||||
|
)
|
||||||
|
let totalLines = safeScrollbackLines + dimensions.rows
|
||||||
|
let bytesPerCell = MemoryLayout<CharData>.stride
|
||||||
|
let bytesPerLine = (dimensions.columns * bytesPerCell) + defaultBytesPerLineOverhead
|
||||||
|
let totalBytes = max(0, totalLines * bytesPerLine)
|
||||||
|
|
||||||
|
return Estimate(bytes: totalBytes, columns: dimensions.columns, rows: dimensions.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func terminalGridDimensions(
|
||||||
|
fontSize: Double,
|
||||||
|
openWidth: Double,
|
||||||
|
openHeight: Double
|
||||||
|
) -> (columns: Int, rows: Int) {
|
||||||
|
let font = NSFont.monospacedSystemFont(ofSize: CGFloat(max(1, fontSize)), weight: .regular)
|
||||||
|
let cellWidth = max(1, font.advancement(forGlyph: font.glyph(withName: "W")).width)
|
||||||
|
let cellHeight = max(1, ceil(CTFontGetAscent(font) + CTFontGetDescent(font) + CTFontGetLeading(font)))
|
||||||
|
|
||||||
|
let columns = max(minimumColumns, Int(CGFloat(max(1, openWidth)) / cellWidth))
|
||||||
|
let rows = max(minimumRows, Int(CGFloat(max(1, openHeight)) / cellHeight))
|
||||||
|
|
||||||
|
return (columns, rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,58 @@ import AppKit
|
|||||||
import SwiftTerm
|
import SwiftTerm
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
/// Tracks whether the terminal viewport should follow live output or preserve
|
||||||
|
/// the user's current scrollback position.
|
||||||
|
final class TerminalScrollCoordinator {
|
||||||
|
private let bottomThreshold: Double
|
||||||
|
private var suppressScrollTracking = false
|
||||||
|
|
||||||
|
private(set) var followsOutput = true
|
||||||
|
private(set) var preservedScrollPosition: Double = 1
|
||||||
|
|
||||||
|
init(bottomThreshold: Double = 0.999) {
|
||||||
|
self.bottomThreshold = bottomThreshold
|
||||||
|
}
|
||||||
|
|
||||||
|
func terminalDidScroll(to position: Double, canScroll: Bool) {
|
||||||
|
guard !suppressScrollTracking else { return }
|
||||||
|
|
||||||
|
guard canScroll else {
|
||||||
|
followsOutput = true
|
||||||
|
preservedScrollPosition = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let clampedPosition = min(max(position, 0), 1)
|
||||||
|
if clampedPosition >= bottomThreshold {
|
||||||
|
followsOutput = true
|
||||||
|
preservedScrollPosition = 1
|
||||||
|
} else {
|
||||||
|
followsOutput = false
|
||||||
|
preservedScrollPosition = clampedPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputRestorePosition(canScroll: Bool) -> Double? {
|
||||||
|
guard canScroll, !followsOutput else { return nil }
|
||||||
|
return preservedScrollPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func userDidStartTyping() -> Bool {
|
||||||
|
let shouldJumpToBottom = !followsOutput
|
||||||
|
followsOutput = true
|
||||||
|
preservedScrollPosition = 1
|
||||||
|
return shouldJumpToBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
func suppressTracking<T>(_ body: () -> T) -> T {
|
||||||
|
suppressScrollTracking = true
|
||||||
|
defer { suppressScrollTracking = false }
|
||||||
|
return body()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Wraps a single SwiftTerm TerminalView + LocalProcess pair.
|
/// Wraps a single SwiftTerm TerminalView + LocalProcess pair.
|
||||||
@MainActor
|
@MainActor
|
||||||
class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @preconcurrency TerminalViewDelegate {
|
class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @preconcurrency TerminalViewDelegate {
|
||||||
@@ -10,9 +62,12 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
let terminalView: TerminalView
|
let terminalView: TerminalView
|
||||||
private var process: LocalProcess?
|
private var process: LocalProcess?
|
||||||
private var keyEventMonitor: Any?
|
private var keyEventMonitor: Any?
|
||||||
|
private var scrollEventMonitor: Any?
|
||||||
private let backgroundColor = NSColor.black
|
private let backgroundColor = NSColor.black
|
||||||
private let configuredShellPath: String
|
private let configuredShellPath: String
|
||||||
|
private var scrollbackLines: Int
|
||||||
private let launchDirectory: String
|
private let launchDirectory: String
|
||||||
|
private let scrollCoordinator = TerminalScrollCoordinator()
|
||||||
|
|
||||||
@Published var title: String = "shell"
|
@Published var title: String = "shell"
|
||||||
@Published var isRunning: Bool = true
|
@Published var isRunning: Bool = true
|
||||||
@@ -22,21 +77,26 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
fontSize: CGFloat,
|
fontSize: CGFloat,
|
||||||
theme: TerminalTheme,
|
theme: TerminalTheme,
|
||||||
shellPath: String,
|
shellPath: String,
|
||||||
|
scrollbackLines: Int,
|
||||||
initialDirectory: String? = nil,
|
initialDirectory: String? = nil,
|
||||||
startImmediately: Bool = true
|
startImmediately: Bool = true
|
||||||
) {
|
) {
|
||||||
terminalView = TerminalView(frame: NSRect(x: 0, y: 0, width: 600, height: 300))
|
terminalView = TerminalView(frame: NSRect(x: 0, y: 0, width: 600, height: 300))
|
||||||
configuredShellPath = shellPath
|
configuredShellPath = shellPath
|
||||||
|
self.scrollbackLines = max(0, scrollbackLines)
|
||||||
launchDirectory = Self.resolveInitialDirectory(initialDirectory)
|
launchDirectory = Self.resolveInitialDirectory(initialDirectory)
|
||||||
currentDirectory = launchDirectory
|
currentDirectory = launchDirectory
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
terminalView.terminalDelegate = self
|
terminalView.terminalDelegate = self
|
||||||
|
installOsc52ClipboardHandler()
|
||||||
|
|
||||||
let font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .regular)
|
let font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .regular)
|
||||||
terminalView.font = font
|
terminalView.font = font
|
||||||
applyTheme(theme)
|
applyTheme(theme)
|
||||||
|
updateScrollbackLines(self.scrollbackLines)
|
||||||
installCommandArrowMonitor()
|
installCommandArrowMonitor()
|
||||||
|
installScrollWheelMonitor()
|
||||||
|
|
||||||
if startImmediately {
|
if startImmediately {
|
||||||
startShell()
|
startShell()
|
||||||
@@ -49,6 +109,9 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
if let keyEventMonitor {
|
if let keyEventMonitor {
|
||||||
NSEvent.removeMonitor(keyEventMonitor)
|
NSEvent.removeMonitor(keyEventMonitor)
|
||||||
}
|
}
|
||||||
|
if let scrollEventMonitor {
|
||||||
|
NSEvent.removeMonitor(scrollEventMonitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Shell management
|
// MARK: - Shell management
|
||||||
@@ -96,6 +159,23 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
return ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh"
|
return ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func installOsc52ClipboardHandler() {
|
||||||
|
let maxPayloadSize = 1_048_576 // 1 MB
|
||||||
|
terminalView.getTerminal().registerOscHandler(code: 52) { [weak self] data in
|
||||||
|
guard data.count >= 2,
|
||||||
|
data[data.startIndex] == UInt8(ascii: "c"),
|
||||||
|
data[data.startIndex + 1] == UInt8(ascii: ";") else { return }
|
||||||
|
|
||||||
|
let base64 = Data(data[(data.startIndex + 2)...])
|
||||||
|
guard let content = Data(base64Encoded: base64),
|
||||||
|
content.count <= maxPayloadSize,
|
||||||
|
let string = String(data: content, encoding: .utf8) else { return }
|
||||||
|
|
||||||
|
NSPasteboard.general.clearContents()
|
||||||
|
NSPasteboard.general.setString(string, forType: .string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func installCommandArrowMonitor() {
|
private func installCommandArrowMonitor() {
|
||||||
keyEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
|
keyEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
|
||||||
guard let self else { return event }
|
guard let self else { return event }
|
||||||
@@ -116,6 +196,53 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func installScrollWheelMonitor() {
|
||||||
|
scrollEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .scrollWheel) { [weak self] event in
|
||||||
|
guard let self else { return event }
|
||||||
|
guard let window = self.terminalView.window else { return event }
|
||||||
|
guard event.window === window else { return event }
|
||||||
|
guard window.firstResponder === self.terminalView else { return event }
|
||||||
|
|
||||||
|
let terminal = self.terminalView.getTerminal()
|
||||||
|
guard TerminalScrollWheelRouter.shouldSendMouseWheel(
|
||||||
|
allowMouseReporting: self.terminalView.allowMouseReporting,
|
||||||
|
mouseMode: terminal.mouseMode,
|
||||||
|
deltaY: event.deltaY
|
||||||
|
) else {
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
let localPoint = self.terminalView.convert(event.locationInWindow, from: nil)
|
||||||
|
let dims = terminal.getDims()
|
||||||
|
let hit = TerminalScrollWheelRouter.gridPosition(
|
||||||
|
point: localPoint,
|
||||||
|
bounds: self.terminalView.bounds,
|
||||||
|
cols: dims.cols,
|
||||||
|
rows: dims.rows
|
||||||
|
)
|
||||||
|
let button = event.deltaY > 0 ? 4 : 5
|
||||||
|
let flags = terminal.encodeButton(
|
||||||
|
button: button,
|
||||||
|
release: false,
|
||||||
|
shift: event.modifierFlags.contains(.shift),
|
||||||
|
meta: event.modifierFlags.contains(.option),
|
||||||
|
control: event.modifierFlags.contains(.control)
|
||||||
|
)
|
||||||
|
|
||||||
|
for _ in 0..<TerminalScrollWheelRouter.velocity(for: event.deltaY) {
|
||||||
|
terminal.sendEvent(
|
||||||
|
buttonFlags: flags,
|
||||||
|
x: hit.x,
|
||||||
|
y: hit.y,
|
||||||
|
pixelX: hit.pixelX,
|
||||||
|
pixelY: hit.pixelY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateFontSize(_ size: CGFloat) {
|
func updateFontSize(_ size: CGFloat) {
|
||||||
terminalView.font = NSFont.monospacedSystemFont(ofSize: size, weight: .regular)
|
terminalView.font = NSFont.monospacedSystemFont(ofSize: size, weight: .regular)
|
||||||
}
|
}
|
||||||
@@ -128,6 +255,12 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
terminalView.installColors(theme.ansiColors)
|
terminalView.installColors(theme.ansiColors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateScrollbackLines(_ scrollbackLines: Int) {
|
||||||
|
let sanitizedScrollbackLines = max(0, scrollbackLines)
|
||||||
|
self.scrollbackLines = sanitizedScrollbackLines
|
||||||
|
terminalView.getTerminal().changeHistorySize(sanitizedScrollbackLines)
|
||||||
|
}
|
||||||
|
|
||||||
func terminate() {
|
func terminate() {
|
||||||
process?.terminate()
|
process?.terminate()
|
||||||
process = nil
|
process = nil
|
||||||
@@ -137,12 +270,43 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
// MARK: - LocalProcessDelegate
|
// MARK: - LocalProcessDelegate
|
||||||
|
|
||||||
nonisolated func processTerminated(_ source: LocalProcess, exitCode: Int32?) {
|
nonisolated func processTerminated(_ source: LocalProcess, exitCode: Int32?) {
|
||||||
Task { @MainActor in self.isRunning = false }
|
Task { @MainActor in
|
||||||
|
self.isRunning = false
|
||||||
|
self.resetTerminalModes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetTerminalModes() {
|
||||||
|
let resetSequences: [[UInt8]] = [
|
||||||
|
Array("\u{1b}[?9l".utf8),
|
||||||
|
Array("\u{1b}[?1000l".utf8),
|
||||||
|
Array("\u{1b}[?1002l".utf8),
|
||||||
|
Array("\u{1b}[?1003l".utf8),
|
||||||
|
Array("\u{1b}[?1006l".utf8),
|
||||||
|
Array("\u{1b}[?1015l".utf8),
|
||||||
|
Array("\u{1b}[?2004l".utf8),
|
||||||
|
Array("\u{1b}[?1l".utf8),
|
||||||
|
Array("\u{1b}[?1049l".utf8),
|
||||||
|
Array("\u{1b}[?25h".utf8),
|
||||||
|
]
|
||||||
|
|
||||||
|
for seq in resetSequences {
|
||||||
|
terminalView.feed(byteArray: seq[...])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nonisolated func dataReceived(slice: ArraySlice<UInt8>) {
|
nonisolated func dataReceived(slice: ArraySlice<UInt8>) {
|
||||||
let data = slice
|
let data = slice
|
||||||
Task { @MainActor in self.terminalView.feed(byteArray: data) }
|
Task { @MainActor in
|
||||||
|
if let restorePosition = self.scrollCoordinator.outputRestorePosition(canScroll: self.terminalView.canScroll) {
|
||||||
|
self.scrollCoordinator.suppressTracking {
|
||||||
|
self.terminalView.feed(byteArray: data)
|
||||||
|
self.terminalView.scroll(toPosition: restorePosition)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.terminalView.feed(byteArray: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nonisolated func getWindowSize() -> winsize {
|
nonisolated func getWindowSize() -> winsize {
|
||||||
@@ -155,6 +319,9 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
// MARK: - TerminalViewDelegate
|
// MARK: - TerminalViewDelegate
|
||||||
|
|
||||||
func send(source: TerminalView, data: ArraySlice<UInt8>) {
|
func send(source: TerminalView, data: ArraySlice<UInt8>) {
|
||||||
|
if scrollCoordinator.userDidStartTyping() {
|
||||||
|
terminalView.scroll(toPosition: 1)
|
||||||
|
}
|
||||||
process?.send(data: data)
|
process?.send(data: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +346,9 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
|||||||
currentDirectory = normalizedDirectory
|
currentDirectory = normalizedDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrolled(source: TerminalView, position: Double) {}
|
func scrolled(source: TerminalView, position: Double) {
|
||||||
|
scrollCoordinator.terminalDidScroll(to: position, canScroll: source.canScroll)
|
||||||
|
}
|
||||||
func rangeChanged(source: TerminalView, startY: Int, endY: Int) {}
|
func rangeChanged(source: TerminalView, startY: Int, endY: Int) {}
|
||||||
|
|
||||||
func clipboardCopy(source: TerminalView, content: Data) {
|
func clipboardCopy(source: TerminalView, content: Data) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ protocol TerminalSessionFactoryType {
|
|||||||
fontSize: CGFloat,
|
fontSize: CGFloat,
|
||||||
theme: TerminalTheme,
|
theme: TerminalTheme,
|
||||||
shellPath: String,
|
shellPath: String,
|
||||||
|
scrollbackLines: Int,
|
||||||
initialDirectory: String?
|
initialDirectory: String?
|
||||||
) -> TerminalSession
|
) -> TerminalSession
|
||||||
}
|
}
|
||||||
@@ -16,12 +17,14 @@ struct LiveTerminalSessionFactory: TerminalSessionFactoryType {
|
|||||||
fontSize: CGFloat,
|
fontSize: CGFloat,
|
||||||
theme: TerminalTheme,
|
theme: TerminalTheme,
|
||||||
shellPath: String,
|
shellPath: String,
|
||||||
|
scrollbackLines: Int,
|
||||||
initialDirectory: String?
|
initialDirectory: String?
|
||||||
) -> TerminalSession {
|
) -> TerminalSession {
|
||||||
TerminalSession(
|
TerminalSession(
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
shellPath: shellPath,
|
shellPath: shellPath,
|
||||||
|
scrollbackLines: scrollbackLines,
|
||||||
initialDirectory: initialDirectory
|
initialDirectory: initialDirectory
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -106,6 +109,7 @@ final class WorkspaceController: ObservableObject {
|
|||||||
fontSize: config.fontSize,
|
fontSize: config.fontSize,
|
||||||
theme: config.theme,
|
theme: config.theme,
|
||||||
shellPath: config.shellPath,
|
shellPath: config.shellPath,
|
||||||
|
scrollbackLines: config.scrollbackLines,
|
||||||
initialDirectory: activeTab?.currentDirectory
|
initialDirectory: activeTab?.currentDirectory
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -187,4 +191,10 @@ final class WorkspaceController: ObservableObject {
|
|||||||
tab.applyTheme(theme)
|
tab.applyTheme(theme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAllScrollbackLines(_ scrollbackLines: Int) {
|
||||||
|
for tab in tabs {
|
||||||
|
tab.updateScrollbackLines(scrollbackLines)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,6 +157,12 @@ final class WorkspaceRegistry: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAllWorkspacesScrollbackLines(_ scrollbackLines: Int) {
|
||||||
|
for controller in controllers.values {
|
||||||
|
controller.updateAllScrollbackLines(scrollbackLines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func resolvedWorkspaceName(from proposedName: String?) -> String {
|
private func resolvedWorkspaceName(from proposedName: String?) -> String {
|
||||||
let trimmed = proposedName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
let trimmed = proposedName?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||||
if !trimmed.isEmpty {
|
if !trimmed.isEmpty {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TerminalSettingsView: View {
|
struct TerminalSettingsView: View {
|
||||||
|
private static let scrollbackRange = 0...1_000_000
|
||||||
|
|
||||||
@ObservedObject private var settingsController = AppSettingsController.shared
|
@ObservedObject private var settingsController = AppSettingsController.shared
|
||||||
@State private var sizePresets: [TerminalSizePreset] = []
|
@State private var sizePresets: [TerminalSizePreset] = []
|
||||||
|
|
||||||
@@ -40,6 +42,34 @@ struct TerminalSettingsView: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section("Scrollback") {
|
||||||
|
HStack {
|
||||||
|
Text("Lines")
|
||||||
|
Spacer()
|
||||||
|
TextField(
|
||||||
|
"Scrollback lines",
|
||||||
|
value: scrollbackBinding,
|
||||||
|
format: .number.grouping(.automatic)
|
||||||
|
)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.frame(width: 140)
|
||||||
|
}
|
||||||
|
|
||||||
|
Stepper(
|
||||||
|
value: scrollbackBinding,
|
||||||
|
in: Self.scrollbackRange,
|
||||||
|
step: scrollbackStepSize
|
||||||
|
) {
|
||||||
|
Text("\(settingsController.settings.terminal.scrollbackLines.formatted()) lines")
|
||||||
|
.monospacedDigit()
|
||||||
|
}
|
||||||
|
|
||||||
|
let estimate = scrollbackEstimate
|
||||||
|
Text("Based on the current terminal size, this may use ~\(estimate.formattedBytes) of RAM (\(estimate.columns) columns x \(estimate.rows) rows visible).")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
Section("Size Presets") {
|
Section("Size Presets") {
|
||||||
ForEach($sizePresets) { $preset in
|
ForEach($sizePresets) { $preset in
|
||||||
TerminalSizePresetEditor(
|
TerminalSizePresetEditor(
|
||||||
@@ -94,6 +124,39 @@ struct TerminalSettingsView: View {
|
|||||||
sizePresets.removeAll { $0.id == id }
|
sizePresets.removeAll { $0.id == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var scrollbackBinding: Binding<Int> {
|
||||||
|
Binding(
|
||||||
|
get: { settingsController.settings.terminal.scrollbackLines },
|
||||||
|
set: { newValue in
|
||||||
|
let clampedValue = min(max(Self.scrollbackRange.lowerBound, newValue), Self.scrollbackRange.upperBound)
|
||||||
|
settingsController.update {
|
||||||
|
$0.terminal.scrollbackLines = clampedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var scrollbackEstimate: TerminalScrollbackEstimator.Estimate {
|
||||||
|
TerminalScrollbackEstimator.estimate(
|
||||||
|
scrollbackLines: settingsController.settings.terminal.scrollbackLines,
|
||||||
|
fontSize: settingsController.settings.terminal.fontSize,
|
||||||
|
openWidth: settingsController.settings.display.openWidth,
|
||||||
|
openHeight: settingsController.settings.display.openHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var scrollbackStepSize: Int {
|
||||||
|
let lines = settingsController.settings.terminal.scrollbackLines
|
||||||
|
switch lines {
|
||||||
|
case ..<10_000:
|
||||||
|
return 500
|
||||||
|
case ..<100_000:
|
||||||
|
return 5_000
|
||||||
|
default:
|
||||||
|
return 10_000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func applyPreset(_ preset: TerminalSizePreset) {
|
private func applyPreset(_ preset: TerminalSizePreset) {
|
||||||
settingsController.update {
|
settingsController.update {
|
||||||
$0.display.openWidth = preset.width
|
$0.display.openWidth = preset.width
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ final class AppSettingsControllerTests: XCTestCase {
|
|||||||
let store = InMemoryAppSettingsStore()
|
let store = InMemoryAppSettingsStore()
|
||||||
var settings = AppSettings.default
|
var settings = AppSettings.default
|
||||||
settings.terminal.shellPath = "/opt/homebrew/bin/fish"
|
settings.terminal.shellPath = "/opt/homebrew/bin/fish"
|
||||||
|
settings.terminal.scrollbackLines = 12_000
|
||||||
store.storedSettings = settings
|
store.storedSettings = settings
|
||||||
|
|
||||||
let controller = AppSettingsController(store: store)
|
let controller = AppSettingsController(store: store)
|
||||||
|
|
||||||
XCTAssertEqual(controller.terminalSessionConfiguration.shellPath, "/opt/homebrew/bin/fish")
|
XCTAssertEqual(controller.terminalSessionConfiguration.shellPath, "/opt/homebrew/bin/fish")
|
||||||
|
XCTAssertEqual(controller.terminalSessionConfiguration.scrollbackLines, 12_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTerminalSizePresetsDecodeFromTypedSettings() {
|
func testTerminalSizePresetsDecodeFromTypedSettings() {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ final class AppSettingsStoreTests: XCTestCase {
|
|||||||
settings.appearance.blurRadius = 4.5
|
settings.appearance.blurRadius = 4.5
|
||||||
settings.terminal.fontSize = 16
|
settings.terminal.fontSize = 16
|
||||||
settings.terminal.themeRawValue = TerminalTheme.dracula.rawValue
|
settings.terminal.themeRawValue = TerminalTheme.dracula.rawValue
|
||||||
|
settings.terminal.scrollbackLines = 25_000
|
||||||
settings.terminal.sizePresetsJSON = TerminalSizePresetStore.encodePresets([
|
settings.terminal.sizePresetsJSON = TerminalSizePresetStore.encodePresets([
|
||||||
TerminalSizePreset(name: "Wide", width: 960, height: 420, hotkey: .cmdShiftDigit(4))
|
TerminalSizePreset(name: "Wide", width: 960, height: 420, hotkey: .cmdShiftDigit(4))
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ private final class TestAppSettingsStore: AppSettingsStoreType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class ScreenRegistryTestSettingsProvider: TerminalSessionConfigurationProviding {
|
private final class ScreenRegistryTestSettingsProvider: TerminalSessionConfigurationProviding {
|
||||||
let terminalSessionConfiguration = TerminalSessionConfiguration(fontSize: 13, theme: .terminalApp, shellPath: "")
|
let terminalSessionConfiguration = TerminalSessionConfiguration(fontSize: 13, theme: .terminalApp, shellPath: "", scrollbackLines: 500)
|
||||||
let hotkeySettings = AppSettings.default.hotkeys
|
let hotkeySettings = AppSettings.default.hotkeys
|
||||||
let terminalSizePresets = TerminalSizePresetStore.loadDefaults()
|
let terminalSizePresets = TerminalSizePresetStore.loadDefaults()
|
||||||
}
|
}
|
||||||
@@ -317,6 +317,7 @@ private struct ScreenRegistryUnusedTerminalSessionFactory: TerminalSessionFactor
|
|||||||
fontSize: CGFloat,
|
fontSize: CGFloat,
|
||||||
theme: TerminalTheme,
|
theme: TerminalTheme,
|
||||||
shellPath: String,
|
shellPath: String,
|
||||||
|
scrollbackLines: Int,
|
||||||
initialDirectory: String?
|
initialDirectory: String?
|
||||||
) -> TerminalSession {
|
) -> TerminalSession {
|
||||||
fatalError("ScreenRegistryTests should not create live terminal sessions.")
|
fatalError("ScreenRegistryTests should not create live terminal sessions.")
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import CommandNotch
|
||||||
|
|
||||||
|
final class TerminalScrollCoordinatorTests: XCTestCase {
|
||||||
|
func testScrollAwayFromBottomDisablesOutputFollow() {
|
||||||
|
let coordinator = TerminalScrollCoordinator()
|
||||||
|
|
||||||
|
coordinator.terminalDidScroll(to: 0.42, canScroll: true)
|
||||||
|
|
||||||
|
XCTAssertFalse(coordinator.followsOutput)
|
||||||
|
XCTAssertEqual(coordinator.outputRestorePosition(canScroll: true) ?? .nan, 0.42, accuracy: 0.0001)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTypingReEnablesFollowAndRequestsJumpToBottom() {
|
||||||
|
let coordinator = TerminalScrollCoordinator()
|
||||||
|
coordinator.terminalDidScroll(to: 0.42, canScroll: true)
|
||||||
|
|
||||||
|
let shouldJump = coordinator.userDidStartTyping()
|
||||||
|
|
||||||
|
XCTAssertTrue(shouldJump)
|
||||||
|
XCTAssertTrue(coordinator.followsOutput)
|
||||||
|
XCTAssertNil(coordinator.outputRestorePosition(canScroll: true))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testScrollingBackToBottomReEnablesOutputFollow() {
|
||||||
|
let coordinator = TerminalScrollCoordinator()
|
||||||
|
coordinator.terminalDidScroll(to: 0.42, canScroll: true)
|
||||||
|
|
||||||
|
coordinator.terminalDidScroll(to: 1, canScroll: true)
|
||||||
|
|
||||||
|
XCTAssertTrue(coordinator.followsOutput)
|
||||||
|
XCTAssertNil(coordinator.outputRestorePosition(canScroll: true))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSuppressedTrackingIgnoresProgrammaticScrollUpdates() {
|
||||||
|
let coordinator = TerminalScrollCoordinator()
|
||||||
|
coordinator.terminalDidScroll(to: 0.42, canScroll: true)
|
||||||
|
|
||||||
|
coordinator.suppressTracking {
|
||||||
|
coordinator.terminalDidScroll(to: 1, canScroll: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertFalse(coordinator.followsOutput)
|
||||||
|
XCTAssertEqual(coordinator.outputRestorePosition(canScroll: true) ?? .nan, 0.42, accuracy: 0.0001)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import CommandNotch
|
||||||
|
import SwiftTerm
|
||||||
|
|
||||||
|
final class TerminalScrollWheelRouterTests: XCTestCase {
|
||||||
|
func testMouseWheelForwardingRequiresMouseReportingAndActiveMouseMode() {
|
||||||
|
XCTAssertFalse(TerminalScrollWheelRouter.shouldSendMouseWheel(
|
||||||
|
allowMouseReporting: false,
|
||||||
|
mouseMode: .vt200,
|
||||||
|
deltaY: 1
|
||||||
|
))
|
||||||
|
XCTAssertFalse(TerminalScrollWheelRouter.shouldSendMouseWheel(
|
||||||
|
allowMouseReporting: true,
|
||||||
|
mouseMode: .off,
|
||||||
|
deltaY: 1
|
||||||
|
))
|
||||||
|
XCTAssertFalse(TerminalScrollWheelRouter.shouldSendMouseWheel(
|
||||||
|
allowMouseReporting: true,
|
||||||
|
mouseMode: .vt200,
|
||||||
|
deltaY: 0
|
||||||
|
))
|
||||||
|
XCTAssertTrue(TerminalScrollWheelRouter.shouldSendMouseWheel(
|
||||||
|
allowMouseReporting: true,
|
||||||
|
mouseMode: .vt200,
|
||||||
|
deltaY: -1
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testVelocityMatchesExpectedThresholds() {
|
||||||
|
XCTAssertEqual(TerminalScrollWheelRouter.velocity(for: 1), 1)
|
||||||
|
XCTAssertEqual(TerminalScrollWheelRouter.velocity(for: 2), 3)
|
||||||
|
XCTAssertEqual(TerminalScrollWheelRouter.velocity(for: 6), 10)
|
||||||
|
XCTAssertEqual(TerminalScrollWheelRouter.velocity(for: 10), 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGridPositionClampsToTerminalBounds() {
|
||||||
|
let hit = TerminalScrollWheelRouter.gridPosition(
|
||||||
|
point: CGPoint(x: 210, y: -10),
|
||||||
|
bounds: CGRect(x: 0, y: 0, width: 200, height: 100),
|
||||||
|
cols: 10,
|
||||||
|
rows: 5
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertEqual(hit.x, 9)
|
||||||
|
XCTAssertEqual(hit.y, 4)
|
||||||
|
XCTAssertEqual(hit.pixelX, 200)
|
||||||
|
XCTAssertEqual(hit.pixelY, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import CommandNotch
|
||||||
|
|
||||||
|
final class TerminalScrollbackEstimatorTests: XCTestCase {
|
||||||
|
func testEstimateIncreasesAsScrollbackGrows() {
|
||||||
|
let small = TerminalScrollbackEstimator.estimate(
|
||||||
|
scrollbackLines: 5_000,
|
||||||
|
fontSize: 13,
|
||||||
|
openWidth: 640,
|
||||||
|
openHeight: 350
|
||||||
|
)
|
||||||
|
let large = TerminalScrollbackEstimator.estimate(
|
||||||
|
scrollbackLines: 100_000,
|
||||||
|
fontSize: 13,
|
||||||
|
openWidth: 640,
|
||||||
|
openHeight: 350
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(large.bytes, small.bytes)
|
||||||
|
XCTAssertGreaterThan(large.columns, 0)
|
||||||
|
XCTAssertGreaterThan(large.rows, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEstimateClampsNegativeScrollbackToZero() {
|
||||||
|
let estimate = TerminalScrollbackEstimator.estimate(
|
||||||
|
scrollbackLines: -1_000,
|
||||||
|
fontSize: 13,
|
||||||
|
openWidth: 640,
|
||||||
|
openHeight: 350
|
||||||
|
)
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(estimate.bytes, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -191,7 +191,7 @@ private final class InMemoryWorkspaceStore: WorkspaceStoreType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class TestSettingsProvider: TerminalSessionConfigurationProviding {
|
private final class TestSettingsProvider: TerminalSessionConfigurationProviding {
|
||||||
let terminalSessionConfiguration = TerminalSessionConfiguration(fontSize: 13, theme: .terminalApp, shellPath: "")
|
let terminalSessionConfiguration = TerminalSessionConfiguration(fontSize: 13, theme: .terminalApp, shellPath: "", scrollbackLines: 500)
|
||||||
let hotkeySettings = AppSettings.default.hotkeys
|
let hotkeySettings = AppSettings.default.hotkeys
|
||||||
let terminalSizePresets = TerminalSizePresetStore.loadDefaults()
|
let terminalSizePresets = TerminalSizePresetStore.loadDefaults()
|
||||||
}
|
}
|
||||||
@@ -204,6 +204,7 @@ private final class RecordingTerminalSessionFactory: TerminalSessionFactoryType
|
|||||||
fontSize: CGFloat,
|
fontSize: CGFloat,
|
||||||
theme: TerminalTheme,
|
theme: TerminalTheme,
|
||||||
shellPath: String,
|
shellPath: String,
|
||||||
|
scrollbackLines: Int,
|
||||||
initialDirectory: String?
|
initialDirectory: String?
|
||||||
) -> TerminalSession {
|
) -> TerminalSession {
|
||||||
requestedDirectories.append(initialDirectory)
|
requestedDirectories.append(initialDirectory)
|
||||||
@@ -211,6 +212,7 @@ private final class RecordingTerminalSessionFactory: TerminalSessionFactoryType
|
|||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
shellPath: shellPath,
|
shellPath: shellPath,
|
||||||
|
scrollbackLines: scrollbackLines,
|
||||||
initialDirectory: initialDirectory,
|
initialDirectory: initialDirectory,
|
||||||
startImmediately: false
|
startImmediately: false
|
||||||
)
|
)
|
||||||
@@ -223,6 +225,7 @@ private struct UnusedTerminalSessionFactory: TerminalSessionFactoryType {
|
|||||||
fontSize: CGFloat,
|
fontSize: CGFloat,
|
||||||
theme: TerminalTheme,
|
theme: TerminalTheme,
|
||||||
shellPath: String,
|
shellPath: String,
|
||||||
|
scrollbackLines: Int,
|
||||||
initialDirectory: String?
|
initialDirectory: String?
|
||||||
) -> TerminalSession {
|
) -> TerminalSession {
|
||||||
fatalError("WorkspaceRegistryTests should not create live terminal sessions.")
|
fatalError("WorkspaceRegistryTests should not create live terminal sessions.")
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -63,6 +63,7 @@ Click the preview above to watch the demo recording.
|
|||||||
- macOS 14 or later
|
- macOS 14 or later
|
||||||
- Xcode 16 or later
|
- Xcode 16 or later
|
||||||
- Homebrew `xcodegen`
|
- Homebrew `xcodegen`
|
||||||
|
- Homebrew `create-dmg` for release `.dmg` packaging
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
@@ -81,6 +82,33 @@ DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \
|
|||||||
xcodebuild build -project CommandNotch.xcodeproj -scheme CommandNotch -destination 'platform=macOS'
|
xcodebuild build -project CommandNotch.xcodeproj -scheme CommandNotch -destination 'platform=macOS'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Build a release `.dmg`
|
||||||
|
|
||||||
|
Use `create-dmg` to build the styled Finder installer window with the usual drag-to-`Applications` layout.
|
||||||
|
|
||||||
|
Install the packaging dependency once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install create-dmg
|
||||||
|
```
|
||||||
|
|
||||||
|
Then build from the `app/` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/build-release-dmg.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
That produces:
|
||||||
|
|
||||||
|
- `releases/CommandNotch YYYY-MM-DD HH-MM-SS/CommandNotch.app`
|
||||||
|
- `releases/CommandNotch YYYY-MM-DD HH-MM-SS/CommandNotch.dmg`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- The script regenerates the Xcode project, archives the Release build, then packages the archived app into a styled `.dmg`.
|
||||||
|
- The archive is written to `/tmp` and is only used as the source for the exported `.app`.
|
||||||
|
- If you want a distributable build signed with a specific identity, make sure your Xcode signing settings are configured before running the archive step.
|
||||||
|
|
||||||
## Project Layout
|
## Project Layout
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
129
scripts/build-dmg.sh
Executable file
129
scripts/build-dmg.sh
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PROJECT_DIR="$ROOT_DIR/CommandNotch"
|
||||||
|
PROJECT_SPEC="$PROJECT_DIR/project.yml"
|
||||||
|
XCODEPROJ="$PROJECT_DIR/CommandNotch.xcodeproj"
|
||||||
|
SCHEME="Release-CommandNotch"
|
||||||
|
CONFIGURATION="Release"
|
||||||
|
APP_NAME="CommandNotch"
|
||||||
|
DERIVED_DATA_DIR="$ROOT_DIR/build/release"
|
||||||
|
DIST_DIR="$ROOT_DIR/dist"
|
||||||
|
STAGING_DIR="$DIST_DIR/dmg"
|
||||||
|
APP_BUNDLE="$DERIVED_DATA_DIR/Build/Products/$CONFIGURATION/$APP_NAME.app"
|
||||||
|
DMG_PATH="$DIST_DIR/$APP_NAME.dmg"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") [--skip-generate]
|
||||||
|
|
||||||
|
Builds $APP_NAME.app and packages it into a drag-to-Applications DMG.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--skip-generate Reuse the existing Xcode project without running xcodegen.
|
||||||
|
-h, --help Show this help text.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||||||
|
echo "error: required command not found: $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_developer_dir() {
|
||||||
|
local current_dir
|
||||||
|
current_dir="$(xcode-select -p 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ "$current_dir" == "/Library/Developer/CommandLineTools" ]]; then
|
||||||
|
local xcode_dir="/Applications/Xcode.app/Contents/Developer"
|
||||||
|
if [[ -d "$xcode_dir" ]]; then
|
||||||
|
export DEVELOPER_DIR="$xcode_dir"
|
||||||
|
echo "Using Xcode developer directory: $DEVELOPER_DIR"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "error: xcode-select is pointing at Command Line Tools, and /Applications/Xcode.app was not found." >&2
|
||||||
|
echo "Install Xcode or run: sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_generate=0
|
||||||
|
|
||||||
|
while (($# > 0)); do
|
||||||
|
case "$1" in
|
||||||
|
--skip-generate)
|
||||||
|
skip_generate=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "error: unknown argument: $1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
require_command xcodebuild
|
||||||
|
require_command hdiutil
|
||||||
|
if [[ $skip_generate -eq 0 ]]; then
|
||||||
|
require_command xcodegen
|
||||||
|
fi
|
||||||
|
|
||||||
|
configure_developer_dir
|
||||||
|
|
||||||
|
if [[ ! -f "$PROJECT_SPEC" ]]; then
|
||||||
|
echo "error: project spec not found at $PROJECT_SPEC" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $skip_generate -eq 0 ]]; then
|
||||||
|
echo "Generating Xcode project..."
|
||||||
|
xcodegen generate --spec "$PROJECT_SPEC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "$XCODEPROJ" ]]; then
|
||||||
|
echo "error: Xcode project not found at $XCODEPROJ" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building $APP_NAME.app..."
|
||||||
|
rm -rf "$DERIVED_DATA_DIR"
|
||||||
|
xcodebuild \
|
||||||
|
-project "$XCODEPROJ" \
|
||||||
|
-scheme "$SCHEME" \
|
||||||
|
-configuration "$CONFIGURATION" \
|
||||||
|
-derivedDataPath "$DERIVED_DATA_DIR" \
|
||||||
|
build
|
||||||
|
|
||||||
|
if [[ ! -d "$APP_BUNDLE" ]]; then
|
||||||
|
echo "error: built app bundle not found at $APP_BUNDLE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Preparing DMG staging folder..."
|
||||||
|
rm -rf "$STAGING_DIR" "$DMG_PATH"
|
||||||
|
mkdir -p "$STAGING_DIR"
|
||||||
|
ditto "$APP_BUNDLE" "$STAGING_DIR/$APP_NAME.app"
|
||||||
|
ln -s /Applications "$STAGING_DIR/Applications"
|
||||||
|
|
||||||
|
echo "Creating DMG..."
|
||||||
|
hdiutil create \
|
||||||
|
-volname "$APP_NAME" \
|
||||||
|
-srcfolder "$STAGING_DIR" \
|
||||||
|
-ov \
|
||||||
|
-format UDZO \
|
||||||
|
"$DMG_PATH"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Done:"
|
||||||
|
echo " App: $APP_BUNDLE"
|
||||||
|
echo " DMG: $DMG_PATH"
|
||||||
62
scripts/build-release-dmg.sh
Executable file
62
scripts/build-release-dmg.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
APP_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
PROJECT_DIR="$APP_ROOT/CommandNotch"
|
||||||
|
|
||||||
|
if ! command -v xcodegen >/dev/null 2>&1; then
|
||||||
|
echo "error: xcodegen is required. Install it with: brew install xcodegen" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v create-dmg >/dev/null 2>&1; then
|
||||||
|
echo "error: create-dmg is required. Install it with: brew install create-dmg" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
timestamp="$(date '+%Y-%m-%d %H-%M-%S')"
|
||||||
|
release_dir="$APP_ROOT/releases/CommandNotch $timestamp"
|
||||||
|
archive_path="/tmp/CommandNotch-$timestamp.xcarchive"
|
||||||
|
staging_dir="$(mktemp -d)"
|
||||||
|
app_path="$release_dir/CommandNotch.app"
|
||||||
|
dmg_path="$release_dir/CommandNotch.dmg"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$staging_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
mkdir -p "$release_dir"
|
||||||
|
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
xcodegen generate --spec project.yml
|
||||||
|
|
||||||
|
DEVELOPER_DIR="${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}" \
|
||||||
|
xcodebuild archive \
|
||||||
|
-project CommandNotch.xcodeproj \
|
||||||
|
-scheme CommandNotch \
|
||||||
|
-configuration Release \
|
||||||
|
-destination 'generic/platform=macOS' \
|
||||||
|
-archivePath "$archive_path"
|
||||||
|
|
||||||
|
ditto "$archive_path/Products/Applications/CommandNotch.app" "$app_path"
|
||||||
|
ditto "$app_path" "$staging_dir/CommandNotch.app"
|
||||||
|
|
||||||
|
create-dmg \
|
||||||
|
--volname "CommandNotch" \
|
||||||
|
--window-pos 200 120 \
|
||||||
|
--window-size 720 420 \
|
||||||
|
--icon-size 128 \
|
||||||
|
--icon "CommandNotch.app" 180 210 \
|
||||||
|
--icon "Applications" 540 210 \
|
||||||
|
--hide-extension "CommandNotch.app" \
|
||||||
|
--app-drop-link 540 210 \
|
||||||
|
"$dmg_path" \
|
||||||
|
"$staging_dir"
|
||||||
|
|
||||||
|
echo "Created:"
|
||||||
|
echo " $app_path"
|
||||||
|
echo " $dmg_path"
|
||||||
Reference in New Issue
Block a user