From a762070f3d29aed33e943785b441bfcc16e8f352 Mon Sep 17 00:00:00 2001 From: Beingpax Date: Tue, 18 Nov 2025 15:36:12 +0545 Subject: [PATCH] Remove redundant codefiles --- .../Services/PasteEligibilityService.swift | 31 --- .../TranscriptionFallbackManager.swift | 177 ------------------ VoiceInk/Services/VoiceActivityDetector.swift | 88 --------- .../Common/TranscriptionFallbackView.swift | 100 ---------- 4 files changed, 396 deletions(-) delete mode 100644 VoiceInk/Services/PasteEligibilityService.swift delete mode 100644 VoiceInk/Services/TranscriptionFallbackManager.swift delete mode 100644 VoiceInk/Services/VoiceActivityDetector.swift delete mode 100644 VoiceInk/Views/Common/TranscriptionFallbackView.swift diff --git a/VoiceInk/Services/PasteEligibilityService.swift b/VoiceInk/Services/PasteEligibilityService.swift deleted file mode 100644 index c183302..0000000 --- a/VoiceInk/Services/PasteEligibilityService.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Cocoa - -class PasteEligibilityService { - static func isPastePossible() -> Bool { - guard AXIsProcessTrustedWithOptions(nil) else { - return true - } - - guard let frontmostApp = NSWorkspace.shared.frontmostApplication else { - return false - } - - let appElement = AXUIElementCreateApplication(frontmostApp.processIdentifier) - - var focusedElement: AnyObject? - let result = AXUIElementCopyAttributeValue(appElement, kAXFocusedUIElementAttribute as CFString, &focusedElement) - - guard result == .success, let element = focusedElement else { - return false - } - - var isWritable: DarwinBoolean = false - let isSettableResult = AXUIElementIsAttributeSettable(element as! AXUIElement, kAXValueAttribute as CFString, &isWritable) - - if isSettableResult == .success && isWritable.boolValue { - return true - } - - return false - } -} \ No newline at end of file diff --git a/VoiceInk/Services/TranscriptionFallbackManager.swift b/VoiceInk/Services/TranscriptionFallbackManager.swift deleted file mode 100644 index f1bf85b..0000000 --- a/VoiceInk/Services/TranscriptionFallbackManager.swift +++ /dev/null @@ -1,177 +0,0 @@ -import SwiftUI -import AppKit - -/// Custom NSPanel that can become key window for text editing -class EditablePanel: NSPanel { - override var canBecomeKey: Bool { - return true - } - - override var canBecomeMain: Bool { - return false - } -} - -/// Manages the presentation and dismissal of the `TranscriptionFallbackView`. -class TranscriptionFallbackManager { - static let shared = TranscriptionFallbackManager() - - private var fallbackWindow: NSPanel? - - /// Observer that listens for the fallback window losing key status so it can be dismissed automatically. - private var windowObserver: Any? - - private init() {} - - /// Displays the fallback window with the provided transcription text. - @MainActor - func showFallback(for text: String) { - dismiss() - - let fallbackView = TranscriptionFallbackView( - transcriptionText: text, - onCopy: { [weak self] in - self?.dismiss() - }, - onClose: { [weak self] in - self?.dismiss() - }, - onTextChange: { [weak self] newText in - self?.resizeWindow(for: newText) - } - ) - - let hostingController = NSHostingController(rootView: fallbackView) - - let finalSize = calculateOptimalSize(for: text) - - let panel = createFallbackPanel(with: finalSize) - panel.contentView = hostingController.view - - self.fallbackWindow = panel - - panel.alphaValue = 0 - panel.makeKeyAndOrderFront(nil) - - NSAnimationContext.runAnimationGroup { context in - context.duration = 0.3 - panel.animator().alphaValue = 1 - } completionHandler: { - DispatchQueue.main.async { - panel.makeFirstResponder(hostingController.view) - } - } - - // Automatically close the window when the user clicks outside of it. - windowObserver = NotificationCenter.default.addObserver( - forName: NSWindow.didResignKeyNotification, - object: panel, - queue: .main - ) { [weak self] _ in - self?.dismiss() - } - } - - /// Dynamically resizes the window based on new text content - @MainActor - private func resizeWindow(for text: String) { - guard let window = fallbackWindow else { return } - - let newSize = calculateOptimalSize(for: text) - let currentFrame = window.frame - - // Preserve the bottom anchor and center horizontally while resizing - let newX = currentFrame.midX - (newSize.width / 2) - let newY = currentFrame.minY // keep the bottom position constant - - let newFrame = NSRect(x: newX, y: newY, width: newSize.width, height: newSize.height) - - // Animate the resize - NSAnimationContext.runAnimationGroup { context in - context.duration = 0.2 - context.allowsImplicitAnimation = true - window.animator().setFrame(newFrame, display: true) - } - } - - /// Dismisses the fallback window with an animation. - @MainActor - func dismiss() { - guard let window = fallbackWindow else { return } - - fallbackWindow = nil - - // Remove the key-window observer if it exists. - if let observer = windowObserver { - NotificationCenter.default.removeObserver(observer) - windowObserver = nil - } - - NSAnimationContext.runAnimationGroup({ context in - context.duration = 0.2 - window.animator().alphaValue = 0 - }, completionHandler: { - window.close() - }) - } - - private func calculateOptimalSize(for text: String) -> CGSize { - let minWidth: CGFloat = 280 - let maxWidth: CGFloat = 400 - let minHeight: CGFloat = 80 - let maxHeight: CGFloat = 300 - let horizontalPadding: CGFloat = 48 - let verticalPadding: CGFloat = 56 - - let font = NSFont.systemFont(ofSize: 14, weight: .regular) - let textStorage = NSTextStorage(string: text, attributes: [.font: font]) - let textContainer = NSTextContainer(size: CGSize(width: maxWidth - horizontalPadding, height: .greatestFiniteMagnitude)) - let layoutManager = NSLayoutManager() - - layoutManager.addTextContainer(textContainer) - textStorage.addLayoutManager(layoutManager) - - layoutManager.glyphRange(for: textContainer) - let usedRect = layoutManager.usedRect(for: textContainer) - - let idealWidth = usedRect.width + horizontalPadding - let idealHeight = usedRect.height + verticalPadding - - let finalWidth = min(maxWidth, max(minWidth, idealWidth)) - let finalHeight = min(maxHeight, max(minHeight, idealHeight)) - - return CGSize(width: finalWidth, height: finalHeight) - } - - private func createFallbackPanel(with finalSize: NSSize) -> NSPanel { - let panel = EditablePanel( - contentRect: .zero, - styleMask: [.borderless, .nonactivatingPanel], - backing: .buffered, - defer: false - ) - - panel.isFloatingPanel = true - panel.level = .floating - panel.backgroundColor = .clear - panel.isOpaque = false - panel.hasShadow = true - panel.isMovable = false - panel.hidesOnDeactivate = false - panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] - panel.acceptsMouseMovedEvents = true - panel.worksWhenModal = true - - if let activeScreen = NSScreen.main { - let screenRect = activeScreen.visibleFrame - let xPos = screenRect.midX - (finalSize.width / 2) - let padding: CGFloat = 40 // increased distance from bottom of visible frame (above Dock) - let yPos = screenRect.minY + padding - panel.setFrameOrigin(NSPoint(x: xPos, y: yPos)) - } - - panel.setContentSize(finalSize) - - return panel - } -} \ No newline at end of file diff --git a/VoiceInk/Services/VoiceActivityDetector.swift b/VoiceInk/Services/VoiceActivityDetector.swift deleted file mode 100644 index 3459dc6..0000000 --- a/VoiceInk/Services/VoiceActivityDetector.swift +++ /dev/null @@ -1,88 +0,0 @@ -import Foundation -import AVFoundation -import os.log -#if canImport(whisper) -import whisper -#else -#error("Unable to import whisper module. Please check your project configuration.") -#endif - -// MARK: - C API Bridge - -// Opaque pointers for the C contexts -fileprivate typealias WhisperVADContext = OpaquePointer -fileprivate typealias WhisperVADSegments = OpaquePointer - - -// MARK: - VoiceActivityDetector Class - -class VoiceActivityDetector { - private var vadContext: WhisperVADContext - private let logger = Logger(subsystem: "com.prakashjoshipax.voiceink", category: "VoiceActivityDetector") - - init?(modelPath: String) { - var contextParams = whisper_vad_default_context_params() - contextParams.n_threads = max(1, min(8, Int32(ProcessInfo.processInfo.processorCount) - 2)) - - let contextOpt: WhisperVADContext? = modelPath.withCString { cPath in - whisper_vad_init_from_file_with_params(cPath, contextParams) - } - - guard let context = contextOpt else { - logger.error("Failed to initialize VAD context.") - return nil - } - self.vadContext = context - logger.notice("VAD context initialized successfully.") - } - - deinit { - whisper_vad_free(vadContext) - logger.notice("VAD context freed.") - } - - /// Processes audio samples to detect speech segments and returns an array of (start: TimeInterval, end: TimeInterval) tuples. - func process(audioSamples: [Float]) -> [(start: TimeInterval, end: TimeInterval)] { - // 1. Detect speech and get probabilities internally in the context - let success = audioSamples.withUnsafeBufferPointer { buffer in - whisper_vad_detect_speech(vadContext, buffer.baseAddress!, Int32(audioSamples.count)) - } - - guard success else { - logger.error("Failed to detect speech probabilities.") - return [] - } - - // 2. Get segments from probabilities - var vadParams = whisper_vad_default_params() - vadParams.threshold = 0.45 - vadParams.min_speech_duration_ms = 150 - vadParams.min_silence_duration_ms = 750 - vadParams.max_speech_duration_s = Float.greatestFiniteMagnitude // Use the largest representable Float value for no max duration - vadParams.speech_pad_ms = 100 - vadParams.samples_overlap = 0.1 // Add samples_overlap parameter - - guard let segments = whisper_vad_segments_from_probs(vadContext, vadParams) else { - logger.error("Failed to get VAD segments from probabilities.") - return [] - } - defer { - // Ensure segments are freed - whisper_vad_free_segments(segments) - } - - let nSegments = whisper_vad_segments_n_segments(segments) - logger.notice("Detected \(nSegments) speech segments.") - - var speechSegments: [(start: TimeInterval, end: TimeInterval)] = [] - for i in 0.. Void - let onClose: () -> Void - let onTextChange: ((String) -> Void)? - - @State private var editableText: String = "" - @State private var isHoveringTitleBar = false - - var body: some View { - VStack(spacing: 0) { - // Title Bar - HStack { - Spacer().frame(width: 20, height: 20) - - Spacer() - - Text("VoiceInk") - .font(.system(size: 13, weight: .medium, design: .rounded)) - .foregroundColor(.secondary) - - Spacer() - - if isHoveringTitleBar { - Button(action: { - ClipboardManager.copyToClipboard(editableText) - onCopy() - }) { - Image(systemName: "doc.on.doc") - .font(.system(size: 9, weight: .semibold)) - } - .buttonStyle(TitleBarButtonStyle(color: .blue)) - } else { - Spacer().frame(width: 20, height: 20) - } - } - .padding(.horizontal, 8) - .padding(.vertical, 5) - .background(.ultraThinMaterial) - .onHover { hovering in - withAnimation(.easeInOut(duration: 0.2)) { - isHoveringTitleBar = hovering - } - } - - // Text Editor - TextEditor(text: $editableText) - .font(.system(size: 14, weight: .regular, design: .rounded)) - .scrollContentBackground(.hidden) - .background(Color.clear) - .padding(.horizontal, 16) - .onAppear { - editableText = transcriptionText - } - .onChange(of: editableText) { newValue in - onTextChange?(newValue) - } - } - .background(.regularMaterial) - .cornerRadius(16) - .background( - Button("", action: onClose) - .keyboardShortcut("w", modifiers: .command) - .hidden() - ) - } -} - -private struct TitleBarButtonStyle: ButtonStyle { - let color: Color - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .foregroundColor(.white) - .padding(3) - .background(Circle().fill(color)) - .scaleEffect(configuration.isPressed ? 0.9 : 1.0) - } -} - -#Preview { - VStack { - TranscriptionFallbackView( - transcriptionText: "Short text.", - onCopy: {}, - onClose: {}, - onTextChange: nil - ) - TranscriptionFallbackView( - transcriptionText: "This is a much longer piece of transcription text to demonstrate how the view will adaptively resize to accommodate more content while still respecting the maximum constraints.", - onCopy: {}, - onClose: {}, - onTextChange: nil - ) - } - .padding() -}