Skip to content

Commit 9f97125

Browse files
committed
Fix per-tab input source drifting across windows; bump to 1.3.2
Three defensive fixes to the per-tab keyboard input source logic: - panelDidResignKey is now idempotent (guard isPanelKey). macOS fires didResignKey more than once per focus loss; the second pass used to run captureInputSource *after* the external source was already restored, silently recording the outside app's CJK source into the active tab — poisoning English shadow tabs. - applyInputSource only touches the global input source when a Notchy panel is genuinely the key window, so a stale isPanelKey can't flip another app's input method. - panelDidBecomeKey re-applies the active tab's source even when returning already-key, so focus lands on the right IME after a missed resign, while still not recapturing the external source.
1 parent 93801be commit 9f97125

2 files changed

Lines changed: 23 additions & 6 deletions

File tree

Notchy.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@
303303
"@executable_path/../Frameworks",
304304
);
305305
MACOSX_DEPLOYMENT_TARGET = 15.6;
306-
MARKETING_VERSION = 1.3.1;
306+
MARKETING_VERSION = 1.3.2;
307307
PRODUCT_BUNDLE_IDENTIFIER = li.luy.notchy;
308308
PRODUCT_NAME = "$(TARGET_NAME)";
309309
REGISTER_APP_GROUPS = YES;
@@ -343,7 +343,7 @@
343343
"@executable_path/../Frameworks",
344344
);
345345
MACOSX_DEPLOYMENT_TARGET = 15.6;
346-
MARKETING_VERSION = 1.3.1;
346+
MARKETING_VERSION = 1.3.2;
347347
PRODUCT_BUNDLE_IDENTIFIER = li.luy.notchy;
348348
PRODUCT_NAME = "$(TARGET_NAME)";
349349
REGISTER_APP_GROUPS = YES;

Notchy/SessionStore.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class SessionStore {
2828
}
2929
}
3030
}
31+
3132
/// Stack of session IDs in the order they became active, most-recent last.
3233
/// On close, the previously active tab (browser-style) is restored.
3334
private var activationHistory: [UUID] = []
@@ -170,17 +171,29 @@ class SessionStore {
170171
detectAllXcodeProjectsAsync()
171172
let wasKey = isPanelKey
172173
isPanelKey = true
173-
// Skip when returning from an in-Notchy sheet/dialog (already key) so we
174-
// don't clobber the saved external source with our own applied one.
175-
guard SettingsManager.shared.perTabInputSourceEnabled, !wasKey else { return }
176-
externalInputSource = InputSourceManager.currentSourceID()
174+
guard SettingsManager.shared.perTabInputSourceEnabled else { return }
175+
// Only record the external app's source on a real outside→panel
176+
// transition. Returning from an in-Notchy sheet/dialog (already key)
177+
// must not capture the sheet's source as "external". But always re-apply
178+
// the active tab's source so focus lands back on the right input method
179+
// even if an earlier resign was missed (e.g. focus left via a sheet and
180+
// then went to another app, so our own resignKey never fired).
181+
if !wasKey {
182+
externalInputSource = InputSourceManager.currentSourceID()
183+
}
177184
applyInputSource(for: activeSessionId)
178185
}
179186

180187
/// Called when the panel truly loses focus to another app (not an in-Notchy
181188
/// sheet). Remembers the active tab's input source and restores the external
182189
/// app's input source so we never leave another app stuck in English.
183190
func panelDidResignKey() {
191+
// Idempotent: didResignKey can fire more than once for a single focus
192+
// loss (app switch, Mission Control, orderOut). A second pass would run
193+
// captureInputSource *after* we already restored the external source
194+
// below — recording the outside app's (often CJK) source into the still-
195+
// active tab, silently poisoning e.g. an English shadow tab.
196+
guard isPanelKey else { return }
184197
isPanelKey = false
185198
guard SettingsManager.shared.perTabInputSourceEnabled else { return }
186199
captureInputSource(into: activeSessionId)
@@ -204,6 +217,10 @@ class SessionStore {
204217
/// "+" tab defaults to English; xcode/pinned tabs inherit the current source
205218
/// on first visit (and remember whatever they're left in thereafter).
206219
private func applyInputSource(for sessionID: UUID?) {
220+
// Never change the global input source unless a Notchy panel is truly
221+
// the key window at this instant. Guards against a stale `isPanelKey`
222+
// flipping another app's input method out from under it.
223+
guard NSApp.keyWindow is TerminalPanel else { return }
207224
guard let id = sessionID,
208225
let session = sessions.first(where: { $0.id == id }) else { return }
209226
if let saved = session.inputSource {

0 commit comments

Comments
 (0)