Skip to content

Commit f931827

Browse files
j05u3Copilot
andauthored
feat: improve text injection in general by using Unicode typing simulation (#76)
Co-authored-by: Copilot <[email protected]>
1 parent 994d520 commit f931827

File tree

6 files changed

+129
-109
lines changed

6 files changed

+129
-109
lines changed

VTSApp/VTS/Controllers/StatusBarController.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,13 +207,13 @@ public class StatusBarController: ObservableObject {
207207
// Priority: Recording > Processing > Idle
208208
if isRecording {
209209
button.title = "🔴"
210-
button.toolTip = "VTS is recording audio - Click to stop (\(hotkey))"
210+
button.toolTip = "VTS is recording audio - Press \(hotkey) to stop"
211211
} else if isProcessing {
212212
button.title = "🔵"
213-
button.toolTip = "VTS is processing audio - Click to view progress (\(hotkey))"
213+
button.toolTip = "VTS is processing audio"
214214
} else {
215215
button.title = "⚪️"
216-
button.toolTip = "VTS is ready - Click to start recording (\(hotkey))"
216+
button.toolTip = "VTS is ready - Press \(hotkey) to start recording"
217217
}
218218
}
219219

VTSApp/VTS/Services/TextInjector.swift

Lines changed: 68 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -108,35 +108,18 @@ public class TextInjector: ObservableObject {
108108
}
109109
}
110110

111-
public func testCursorInjection() {
112-
log("🧪 Starting code editor compatibility test...")
111+
public func testEmojiCharacters() {
112+
log("🧪 Starting emoji injection test...")
113113
checkPermissionStatus()
114114

115115
if hasAccessibilityPermission {
116-
log("🧪 Code editor test will begin in 3 seconds...")
117-
log("🧪 Please focus on a text field in your code editor!")
118-
119-
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
120-
// Test with mixed case and international characters
121-
self.injectText("Hello World! Testing VTS in code editor. Mixed CaSe TeXt: 123 ABC def. Español: ñáéíóú")
122-
}
123-
} else {
124-
log("🧪 Cannot test - accessibility permission required")
125-
}
126-
}
127-
128-
public func testSpanishCharacters() {
129-
log("🧪 Starting international character test...")
130-
checkPermissionStatus()
131-
132-
if hasAccessibilityPermission {
133-
log("🧪 International character test will begin in 3 seconds...")
116+
log("🧪 Emoji injection test will begin in 3 seconds...")
134117
log("🧪 Please focus on any text input field!")
135118

136119
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
137-
let spanishText = "Hola, ¿cómo estás? Me gusta el español: ñáéíóúüÑÁÉÍÓÚÜ"
138-
self.log("🧪 Testing international text: '\(spanishText)'")
139-
self.injectText(spanishText)
120+
let emojiText = "Hello! 😀😃😄😁😆😅😂🤣😊😇🙂👹👺🤡💩👽👾🤖🎃🙀😿😾"
121+
self.log("🧪 Testing emoji text: '\(emojiText)'")
122+
self.injectText(emojiText)
140123
}
141124
} else {
142125
log("🧪 Cannot test - accessibility permission required")
@@ -167,27 +150,53 @@ public class TextInjector: ObservableObject {
167150
}
168151
}
169152

170-
public func testCursorPositionInsertion() {
171-
log("🧪 TextInjector: Starting cursor position insertion test...")
153+
public func testAccessibilityOnlyInjection() {
154+
log("🧪 TextInjector: Starting ACCESSIBILITY API ONLY test...")
155+
log("🔬 TextInjector: This test will ONLY use the Accessibility API, no fallback to typing simulation")
156+
checkPermissionStatus()
157+
158+
if hasAccessibilityPermission {
159+
log("🧪 TextInjector: Accessibility-only test will begin in 3 seconds...")
160+
log("🧪 TextInjector: Please focus on a text field now!")
161+
log("🔬 TextInjector: This test helps diagnose if Accessibility API works in specific apps")
162+
163+
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
164+
let testText = "ACCESSIBILITY-ONLY: Hello from VTS!"
165+
self.log("🔬 TextInjector: Testing ONLY Accessibility API with: '\(testText)'")
166+
167+
if self.tryModernAccessibilityInsertion(testText) {
168+
self.log("✅ TextInjector: Accessibility API test SUCCEEDED")
169+
} else {
170+
self.log("❌ TextInjector: Accessibility API test FAILED - this app may have broken accessibility support")
171+
}
172+
}
173+
} else {
174+
log("🧪 TextInjector: Cannot test - accessibility permission required")
175+
}
176+
}
177+
178+
public func testUnicodeTypingOnlyInjection() {
179+
log("🧪 TextInjector: Starting UNICODE TYPING ONLY test...")
180+
log("🔬 TextInjector: This test will ONLY use Unicode typing simulation, no Accessibility API")
172181
checkPermissionStatus()
173182

174183
if hasAccessibilityPermission {
175-
log("🧪 TextInjector: This test will help verify cursor position insertion works correctly.")
176-
log("🧪 TextInjector: Instructions:")
177-
log(" 1. Focus on a text field")
178-
log(" 2. Type some text: 'Hello World'")
179-
log(" 3. Position cursor between 'Hello' and 'World' (middle of the text)")
180-
log(" 4. Wait for injection in 5 seconds...")
181-
log("🧪 TextInjector: Expected result: Text should be inserted AT the cursor, not at the end!")
184+
log("🧪 TextInjector: Unicode typing-only test will begin in 3 seconds...")
185+
log("🧪 TextInjector: Please focus on a text field now!")
186+
log("🔬 TextInjector: This test helps verify if typing simulation works when Accessibility API fails")
182187

183-
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
184-
let insertText = " INSERTED "
185-
self.log("🧪 TextInjector: Inserting '\(insertText)' at cursor position...")
186-
self.injectText(insertText)
187-
self.log("🧪 TextInjector: If working correctly, text should become: 'Hello INSERTED World'")
188+
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
189+
let testText = "TYPING-ONLY: Hello from VTS!"
190+
self.log("🔬 TextInjector: Testing ONLY Unicode typing simulation with: '\(testText)'")
191+
192+
if self.simulateModernUnicodeTyping(testText) {
193+
self.log("✅ TextInjector: Unicode typing test SUCCEEDED")
194+
} else {
195+
self.log("❌ TextInjector: Unicode typing test FAILED")
196+
}
188197
}
189198
} else {
190-
log("🧪 TextInjector: Cannot test - no accessibility permission")
199+
log("🧪 TextInjector: Cannot test - accessibility permission required")
191200
}
192201
}
193202

@@ -256,6 +265,7 @@ public class TextInjector: ObservableObject {
256265
public func injectText(_ text: String, replaceLastText: String? = nil) {
257266
guard hasAccessibilityPermission else {
258267
print("❌ TextInjector: No accessibility permission - cannot inject text")
268+
print("📋 TextInjector: VTS requires accessibility permission for text injection functionality")
259269
print("📋 TextInjector: Please grant accessibility permission in System Settings > Privacy & Security > Accessibility")
260270
return
261271
}
@@ -272,20 +282,11 @@ public class TextInjector: ObservableObject {
272282
deleteText(count: lastText.count)
273283
}
274284

275-
// Get app info for method selection
285+
// Get app info for logging
276286
let appInfo = getCurrentAppInfo()
277-
278287
print("📱 TextInjector: Target app: \(appInfo.name)")
279288

280-
// Try modern Accessibility API first (best for most apps)
281-
if tryModernAccessibilityInsertion(text) {
282-
print("✅ TextInjector: Successfully injected via modern Accessibility API")
283-
return
284-
}
285-
286-
print("⚠️ TextInjector: Accessibility method failed, trying Unicode typing simulation...")
287-
288-
// Fallback to improved Unicode typing simulation
289+
// Use Unicode typing simulation as the primary method
289290
if simulateModernUnicodeTyping(text) {
290291
print("✅ TextInjector: Successfully injected via Unicode typing")
291292
return
@@ -331,6 +332,23 @@ public class TextInjector: ObservableObject {
331332
return AppInfo(name: appName, bundleIdentifier: bundleId)
332333
}
333334

335+
private func deleteTextViaKeyboard(count: Int) {
336+
guard count > 0 else { return }
337+
338+
print("🗑️ TextInjector: Deleting \(count) characters via keyboard simulation...")
339+
340+
for _ in 0..<count {
341+
let deleteEvent = CGEvent(keyboardEventSource: nil, virtualKey: 51, keyDown: true)
342+
deleteEvent?.post(tap: .cghidEventTap)
343+
let deleteUpEvent = CGEvent(keyboardEventSource: nil, virtualKey: 51, keyDown: false)
344+
deleteUpEvent?.post(tap: .cghidEventTap)
345+
Thread.sleep(forTimeInterval: 0.01)
346+
}
347+
print("✅ TextInjector: Deletion completed via keyboard simulation")
348+
}
349+
350+
// MARK: - Legacy Accessibility Methods (kept for testing and future use)
351+
334352
private func deleteText(count: Int) {
335353
guard count > 0 else { return }
336354

@@ -345,14 +363,7 @@ public class TextInjector: ObservableObject {
345363

346364
// Fallback to keyboard simulation
347365
print("⚠️ TextInjector: Accessibility deletion failed, using keyboard simulation...")
348-
for _ in 0..<count {
349-
let deleteEvent = CGEvent(keyboardEventSource: nil, virtualKey: 51, keyDown: true)
350-
deleteEvent?.post(tap: .cghidEventTap)
351-
let deleteUpEvent = CGEvent(keyboardEventSource: nil, virtualKey: 51, keyDown: false)
352-
deleteUpEvent?.post(tap: .cghidEventTap)
353-
Thread.sleep(forTimeInterval: 0.01)
354-
}
355-
print("✅ TextInjector: Deletion completed via keyboard simulation")
366+
deleteTextViaKeyboard(count: count)
356367
}
357368

358369
private func tryAccessibilityDeletion(count: Int) -> Bool {

VTSApp/VTS/Views/OnboardingSteps/OnboardingAnalyticsStep.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import SwiftUI
33
struct OnboardingAnalyticsStep: View {
44
@ObservedObject var appState: AppState
55
@State private var animateContent = false
6-
@StateObject private var consentManager = AnalyticsConsentManager.shared
6+
7+
private var consentManager: AnalyticsConsentManager {
8+
appState.analyticsConsentManagerService
9+
}
710

811
var body: some View {
912
VStack(spacing: 40) {

VTSApp/VTS/Views/PreferencesView.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -777,19 +777,19 @@ struct PreferencesView: View {
777777
Spacer()
778778

779779
Toggle("", isOn: Binding(
780-
get: { AnalyticsConsentManager.shared.hasConsent },
780+
get: { appState.analyticsConsentManagerService.hasConsent },
781781
set: { newValue in
782782
if newValue {
783-
AnalyticsConsentManager.shared.grantConsent()
783+
appState.analyticsConsentManagerService.grantConsent()
784784
} else {
785-
AnalyticsConsentManager.shared.revokeConsent()
785+
appState.analyticsConsentManagerService.revokeConsent()
786786
}
787787
}
788788
))
789789
.toggleStyle(.switch)
790790
}
791791

792-
if AnalyticsConsentManager.shared.hasConsent {
792+
if appState.analyticsConsentManagerService.hasConsent {
793793
HStack {
794794
Image(systemName: "checkmark.circle.fill")
795795
.foregroundColor(.green)

0 commit comments

Comments
 (0)