From 1a09eb588d27bd9417f8e36e85cd0e7410ce2c53 Mon Sep 17 00:00:00 2001 From: Harvey Zuccon Date: Tue, 28 Apr 2026 00:03:08 +1000 Subject: [PATCH] Fix opencode scrolling. Fix build script --- .../CommandNotch.xcodeproj/project.pbxproj | 8 +++ .../UserInterfaceState.xcuserstate | Bin 45534 -> 45534 bytes .../Components/MouseAwareTerminalView.swift | 49 +++++++++++++++++ .../CommandNotch/Models/TerminalSession.swift | 52 ++++++++++++++++++ .../TerminalScrollWheelRouterTests.swift | 49 +++++++++++++++++ scripts/build-release-dmg.sh | 1 - 6 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 CommandNotch/CommandNotch/Components/MouseAwareTerminalView.swift create mode 100644 CommandNotch/CommandNotchTests/TerminalScrollWheelRouterTests.swift diff --git a/CommandNotch/CommandNotch.xcodeproj/project.pbxproj b/CommandNotch/CommandNotch.xcodeproj/project.pbxproj index 9af2fa9..f50ecea 100644 --- a/CommandNotch/CommandNotch.xcodeproj/project.pbxproj +++ b/CommandNotch/CommandNotch.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 34AF69BF7AF8DC78ADE3774A /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E7EEEDFBD8D9CEB8FD86A5 /* GeneralSettingsView.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 */; }; 4D335F67B71F7DD977B6AEF9 /* AppSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6C98C406899F4B242075AF /* AppSettingsStore.swift */; }; 4E0AD14B7427532271E485AA /* NotchWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFAC70814C72BAF76D90B9DF /* NotchWindow.swift */; }; @@ -56,6 +57,7 @@ D088BC850F37E717844761C6 /* CommandNotchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5159CB9DBE2BAA0D2E201C39 /* CommandNotchApp.swift */; }; D2468B19D6F0A2C1DFDFE2B7 /* ScreenContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A770A63582CF9834F4E7F058 /* ScreenContextTests.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 */; }; DCFD5B03E64A46783F46726B /* TerminalCommandArrowBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22AA47452CF798A977A6F47 /* TerminalCommandArrowBehavior.swift */; }; DD116B0FCC341D66F1534EC4 /* TerminalScrollbackEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165DCCD7BB164A6470D49BBF /* TerminalScrollbackEstimator.swift */; }; @@ -110,6 +112,7 @@ 726B935606FD961FD7E8C2BE /* CommandNotchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandNotchUITests.swift; sourceTree = ""; }; 728B3125F7F7FDB7313D2DC6 /* SettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = ""; }; 72A1D3D12BAC593838B3125C /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; + 7315EA485A52A8DC42DF925E /* TerminalScrollWheelRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalScrollWheelRouterTests.swift; sourceTree = ""; }; 74463E4EAB78F56345360CD5 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; 7B2BCA543CE54DAB1DB80E43 /* WorkspaceSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceSummary.swift; sourceTree = ""; }; 8210D7783A614FD7190F5DDD /* LaunchAtLoginHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLoginHelper.swift; sourceTree = ""; }; @@ -118,6 +121,7 @@ 8BB1C403BC2157756F572ACF /* HotkeyBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeyBinding.swift; sourceTree = ""; }; 8CB8AF76C0F728897A26D7EF /* ScreenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenManager.swift; sourceTree = ""; }; 900F0476BE9E3600FBD371BB /* SettingsBindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsBindings.swift; sourceTree = ""; }; + 982BA8965FE92A59D66F378B /* MouseAwareTerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseAwareTerminalView.swift; sourceTree = ""; }; 9C55F29B779DA0E5C5FC8627 /* SwiftTermView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTermView.swift; sourceTree = ""; }; 9E6C98C406899F4B242075AF /* AppSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsStore.swift; sourceTree = ""; }; A64A11F27E65B342B991629A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -227,6 +231,7 @@ C0D19729317029008D81F361 /* TerminalCommandArrowBehaviorTests.swift */, D77DC2AED643512C786AD70A /* TerminalScrollbackEstimatorTests.swift */, 40D9E323185F6D722B360C3C /* TerminalScrollCoordinatorTests.swift */, + 7315EA485A52A8DC42DF925E /* TerminalScrollWheelRouterTests.swift */, D288132700770C4A625A15F6 /* WindowFrameCalculatorTests.swift */, 591FCE91AF83A8A8E44E1625 /* WorkspaceRegistryTests.swift */, 4FA62DFE9AD003F4C5B55F14 /* WorkspaceStoreTests.swift */, @@ -257,6 +262,7 @@ isa = PBXGroup; children = ( 2B81432CECBDB61D21EE4DC3 /* HotkeyRecorderView.swift */, + 982BA8965FE92A59D66F378B /* MouseAwareTerminalView.swift */, 3F57837A7115DEEE11E14B40 /* NotchShape.swift */, EFAC70814C72BAF76D90B9DF /* NotchWindow.swift */, 9C55F29B779DA0E5C5FC8627 /* SwiftTermView.swift */, @@ -433,6 +439,7 @@ 8A1B2A4CD61B0D9A4BAB075B /* ScreenRegistryTests.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 */, 154F363D434A26105C5999B5 /* WorkspaceRegistryTests.swift in Sources */, @@ -459,6 +466,7 @@ E792411FA82E79E810F4B4C3 /* HotkeyRecorderView.swift in Sources */, 6A18F6635B509FF58669F505 /* HotkeySettingsView.swift in Sources */, 12F68EDA880030DAB644FF5F /* LaunchAtLoginHelper.swift in Sources */, + 4BA9D6274CFD6A87DC397B8E /* MouseAwareTerminalView.swift in Sources */, A74E14B8AD19C53820853D8E /* NSScreen+Extensions.swift in Sources */, EE72479BA5A25FF31BACCC50 /* NotchOrchestrator.swift in Sources */, 3B69CB3CDEC2E5F2DCE600F9 /* NotchSettings.swift in Sources */, diff --git a/CommandNotch/CommandNotch.xcodeproj/project.xcworkspace/xcuserdata/harvmaster.xcuserdatad/UserInterfaceState.xcuserstate b/CommandNotch/CommandNotch.xcodeproj/project.xcworkspace/xcuserdata/harvmaster.xcuserdatad/UserInterfaceState.xcuserstate index 9f6b18ffc143cfbf6019e850e450771325195ad3..b7d76ce7af7f84e91fe85066e140c31666872254 100644 GIT binary patch delta 52 zcmccjnCaeQrVW9-tQTD;rC*;M#=DF4qO+9Yi_M~Zs`Wso$b!?G?R(i7LCh7GHhcF? GRt5lHmKFX0 delta 52 zcmccjnCaeQrVW9-tml`w?sz*njCU97`3gPvip`>Ys`Wso#-F0i_Py+kK&B>>`)2RH G$;tq6cNN0` diff --git a/CommandNotch/CommandNotch/Components/MouseAwareTerminalView.swift b/CommandNotch/CommandNotch/Components/MouseAwareTerminalView.swift new file mode 100644 index 0000000..4e78632 --- /dev/null +++ b/CommandNotch/CommandNotch/Components/MouseAwareTerminalView.swift @@ -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) + } +} diff --git a/CommandNotch/CommandNotch/Models/TerminalSession.swift b/CommandNotch/CommandNotch/Models/TerminalSession.swift index 5b91a7d..2d60563 100644 --- a/CommandNotch/CommandNotch/Models/TerminalSession.swift +++ b/CommandNotch/CommandNotch/Models/TerminalSession.swift @@ -62,6 +62,7 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon let terminalView: TerminalView private var process: LocalProcess? private var keyEventMonitor: Any? + private var scrollEventMonitor: Any? private let backgroundColor = NSColor.black private let configuredShellPath: String private var scrollbackLines: Int @@ -95,6 +96,7 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon applyTheme(theme) updateScrollbackLines(self.scrollbackLines) installCommandArrowMonitor() + installScrollWheelMonitor() if startImmediately { startShell() @@ -107,6 +109,9 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon if let keyEventMonitor { NSEvent.removeMonitor(keyEventMonitor) } + if let scrollEventMonitor { + NSEvent.removeMonitor(scrollEventMonitor) + } } // MARK: - Shell management @@ -191,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..