UNPKG

node-mac-recorder

Version:

Native macOS screen recording package for Node.js applications

469 lines (416 loc) 11.8 kB
const { EventEmitter } = require("events"); const path = require("path"); // Native modülü yükle let nativeBinding; try { nativeBinding = require("./build/Release/mac_recorder.node"); } catch (error) { try { nativeBinding = require("./build/Debug/mac_recorder.node"); } catch (debugError) { throw new Error( 'Native module not found. Please run "npm run build" to compile the native module.\n' + "Original error: " + error.message ); } } class WindowSelector extends EventEmitter { constructor() { super(); this.isSelecting = false; this.selectionTimer = null; this.selectedWindow = null; this.lastStatus = null; } /** * Pencere seçim modunu başlatır * İmleç hangi pencerenin üstüne gelirse o pencereyi highlight eder * Select butonuna basılınca seçim tamamlanır */ async startSelection() { if (this.isSelecting) { throw new Error("Window selection is already in progress"); } return new Promise((resolve, reject) => { try { // Native window selection başlat const success = nativeBinding.startWindowSelection(); if (success) { this.isSelecting = true; this.selectedWindow = null; // Status polling timer başlat (higher frequency for overlay updates) this.selectionTimer = setInterval(() => { this.checkSelectionStatus(); }, 50); // 20 FPS status check for smooth overlay this.emit("selectionStarted"); resolve(true); } else { reject(new Error("Failed to start window selection")); } } catch (error) { reject(error); } }); } /** * Pencere seçim modunu durdurur */ async stopSelection() { if (!this.isSelecting) { return false; } return new Promise((resolve, reject) => { try { const success = nativeBinding.stopWindowSelection(); // Timer'ı durdur if (this.selectionTimer) { clearInterval(this.selectionTimer); this.selectionTimer = null; } this.isSelecting = false; this.lastStatus = null; this.emit("selectionStopped"); resolve(success); } catch (error) { reject(error); } }); } /** * Selection durumunu kontrol eder ve event yayar */ checkSelectionStatus() { if (!this.isSelecting) return; try { const status = nativeBinding.getWindowSelectionStatus(); // Seçim tamamlandı mı kontrol et if (status.hasSelectedWindow && !this.selectedWindow) { const windowInfo = nativeBinding.getSelectedWindowInfo(); if (windowInfo) { this.selectedWindow = windowInfo; this.isSelecting = false; // Timer'ı durdur if (this.selectionTimer) { clearInterval(this.selectionTimer); this.selectionTimer = null; } this.emit("windowSelected", windowInfo); return; } } // Mevcut pencere değişti mi kontrol et if (this.lastStatus) { const lastWindow = this.lastStatus.currentWindow; const currentWindow = status.currentWindow; if (!lastWindow && currentWindow) { // Yeni pencere üstüne gelindi this.emit("windowEntered", currentWindow); } else if (lastWindow && !currentWindow) { // Pencere üstünden ayrıldı this.emit("windowLeft", lastWindow); } else if (lastWindow && currentWindow && (lastWindow.id !== currentWindow.id || lastWindow.title !== currentWindow.title || lastWindow.appName !== currentWindow.appName)) { // Farklı bir pencereye geçildi this.emit("windowLeft", lastWindow); this.emit("windowEntered", currentWindow); } } else if (!this.lastStatus && status.currentWindow) { // İlk pencere detection this.emit("windowEntered", status.currentWindow); } this.lastStatus = status; } catch (error) { this.emit("error", error); } } /** * Seçilen pencere bilgisini döndürür */ getSelectedWindow() { return this.selectedWindow; } /** * Seçim durumunu döndürür */ getStatus() { try { const nativeStatus = nativeBinding.getWindowSelectionStatus(); return { isSelecting: this.isSelecting && nativeStatus.isSelecting, hasSelectedWindow: !!this.selectedWindow, selectedWindow: this.selectedWindow, nativeStatus: nativeStatus }; } catch (error) { return { isSelecting: this.isSelecting, hasSelectedWindow: !!this.selectedWindow, selectedWindow: this.selectedWindow, error: error.message }; } } /** * Promise tabanlı pencere seçimi * Kullanıcı bir pencere seçene kadar bekler */ async selectWindow() { if (this.isSelecting) { throw new Error("Selection already in progress"); } return new Promise(async (resolve, reject) => { try { // Event listener'ları ayarla const onWindowSelected = (windowInfo) => { this.removeAllListeners("windowSelected"); this.removeAllListeners("error"); resolve(windowInfo); }; const onError = (error) => { this.removeAllListeners("windowSelected"); this.removeAllListeners("error"); reject(error); }; this.once("windowSelected", onWindowSelected); this.once("error", onError); // Seçimi başlat await this.startSelection(); } catch (error) { this.removeAllListeners("windowSelected"); this.removeAllListeners("error"); reject(error); } }); } /** * Pencereyi en öne getirir (focus yapar) * @param {number} windowId - Window ID * @returns {Promise<boolean>} Success/failure */ async bringWindowToFront(windowId) { if (!windowId) { throw new Error("Window ID is required"); } try { const success = nativeBinding.bringWindowToFront(windowId); return success; } catch (error) { throw new Error(`Failed to bring window to front: ${error.message}`); } } /** * Otomatik pencere en öne getirme özelliğini aktif/pasif yapar * Cursor hangi pencereye gelirse otomatik olarak en öne getirir * @param {boolean} enabled - Enable/disable auto bring to front */ setBringToFrontEnabled(enabled) { try { nativeBinding.setBringToFrontEnabled(enabled); // Only log if explicitly setting, not on startup if (arguments.length > 0) { console.log(`🔄 Auto bring-to-front: ${enabled ? 'ENABLED' : 'DISABLED'}`); } } catch (error) { throw new Error(`Failed to set bring to front: ${error.message}`); } } /** * Cleanup - tüm kaynakları temizle */ async cleanup() { if (this.isSelecting) { await this.stopSelection(); } // Timer'ı temizle if (this.selectionTimer) { clearInterval(this.selectionTimer); this.selectionTimer = null; } // Event listener'ları temizle this.removeAllListeners(); // State'i sıfırla this.selectedWindow = null; this.lastStatus = null; this.isSelecting = false; } /** * Seçilen pencere için kayıt önizleme overlay'ini gösterir * Tüm ekranı siyah yapar, sadece pencere alanını şeffaf bırakır * @param {Object} windowInfo - Pencere bilgileri * @returns {Promise<boolean>} Success/failure */ async showRecordingPreview(windowInfo) { if (!windowInfo) { throw new Error("Window info is required"); } try { const success = nativeBinding.showRecordingPreview(windowInfo); return success; } catch (error) { throw new Error(`Failed to show recording preview: ${error.message}`); } } /** * Kayıt önizleme overlay'ini gizler * @returns {Promise<boolean>} Success/failure */ async hideRecordingPreview() { try { const success = nativeBinding.hideRecordingPreview(); return success; } catch (error) { throw new Error(`Failed to hide recording preview: ${error.message}`); } } /** * Ekran seçimi başlatır * Tüm ekranları overlay ile gösterir ve seçim yapılmasını bekler * @returns {Promise<boolean>} Success/failure */ async startScreenSelection() { try { const success = nativeBinding.startScreenSelection(); if (success) { this._isScreenSelecting = true; } return success; } catch (error) { throw new Error(`Failed to start screen selection: ${error.message}`); } } /** * Ekran seçimini durdurur * @returns {Promise<boolean>} Success/failure */ async stopScreenSelection() { try { const success = nativeBinding.stopScreenSelection(); this._isScreenSelecting = false; return success; } catch (error) { throw new Error(`Failed to stop screen selection: ${error.message}`); } } /** * Seçilen ekran bilgisini döndürür * @returns {Object|null} Screen info or null */ getSelectedScreen() { try { const selectedScreen = nativeBinding.getSelectedScreenInfo(); if (selectedScreen) { // Screen selected, update status this._isScreenSelecting = false; } return selectedScreen; } catch (error) { console.error(`Failed to get selected screen: ${error.message}`); return null; } } /** * Ekran seçim durumunu döndürür * @returns {boolean} Is selecting screens */ get isScreenSelecting() { // Screen selection durum bilgisi için native taraftan status alalım try { // Bu fonksiyon henüz yok, eklemek gerekiyor return this._isScreenSelecting || false; } catch (error) { return false; } } /** * Promise tabanlı ekran seçimi * Kullanıcı bir ekran seçene kadar bekler * @returns {Promise<Object>} Selected screen info */ async selectScreen() { try { // Start screen selection await this.startScreenSelection(); // Poll for selection completion return new Promise((resolve, reject) => { let isResolved = false; const checkSelection = () => { if (isResolved) return; // Prevent multiple resolutions const selectedScreen = this.getSelectedScreen(); if (selectedScreen) { isResolved = true; resolve(selectedScreen); } else if (this.isScreenSelecting) { // Still selecting, check again setTimeout(checkSelection, 100); } else { // Selection was cancelled (probably ESC key) isResolved = true; reject(new Error('Screen selection was cancelled')); } }; // Start polling checkSelection(); // Timeout after 60 seconds setTimeout(() => { if (!isResolved) { isResolved = true; this.stopScreenSelection(); reject(new Error('Screen selection timed out')); } }, 60000); }); } catch (error) { throw new Error(`Failed to select screen: ${error.message}`); } } /** * Seçilen ekran için kayıt önizleme overlay'ini gösterir * Diğer ekranları siyah yapar, sadece seçili ekranı şeffaf bırakır * @param {Object} screenInfo - Ekran bilgileri * @returns {Promise<boolean>} Success/failure */ async showScreenRecordingPreview(screenInfo) { if (!screenInfo) { throw new Error("Screen info is required"); } try { const success = nativeBinding.showScreenRecordingPreview(screenInfo); return success; } catch (error) { throw new Error(`Failed to show screen recording preview: ${error.message}`); } } /** * Ekran kayıt önizleme overlay'ini gizler * @returns {Promise<boolean>} Success/failure */ async hideScreenRecordingPreview() { try { const success = nativeBinding.hideScreenRecordingPreview(); return success; } catch (error) { throw new Error(`Failed to hide screen recording preview: ${error.message}`); } } /** * macOS'ta pencere seçim izinlerini kontrol eder */ async checkPermissions() { try { // Mevcut MacRecorder'dan permission check'i kullan const MacRecorder = require("./index.js"); const recorder = new MacRecorder(); return await recorder.checkPermissions(); } catch (error) { return { screenRecording: false, accessibility: false, error: error.message }; } } } module.exports = WindowSelector;