#!/usr/bin/env python3
"""
X11窗口拖拽事件接收器

使用X11的Xdnd协议实现真正的窗口拖拽功能
"""

import os
import sys
import time
import subprocess
import threading
from typing import List, Optional, Callable
from collections import deque

class X11DragReceiver:
    """X11窗口拖拽事件接收器"""
    
    def __init__(self, supported_formats: List[str]):
        self.supported_formats = supported_formats
        self.window_id = None
        self.is_running = False
        self.drop_callback = None
        self.dropped_files = deque()
        self.monitor_thread = None
        
        # X11相关
        self.x11_display = os.environ.get('DISPLAY', ':0')
        self.xdnd_aware = True  # 标记窗口支持拖拽
        
    def set_window(self, window_id: int):
        """设置要监控的窗口ID"""
        self.window_id = window_id
        print(f"设置拖拽监控窗口: {window_id}")
        
    def set_drop_callback(self, callback: Callable[[List[str]], None]):
        """设置拖拽回调函数"""
        self.drop_callback = callback
        
    def start_monitoring(self):
        """开始监控拖拽事件"""
        if self.is_running:
            return
            
        self.is_running = True
        self.monitor_thread = threading.Thread(target=self._monitor_drag_events)
        self.monitor_thread.daemon = True
        self.monitor_thread.start()
        print("X11拖拽监控已启动")
        
    def stop_monitoring(self):
        """停止监控"""
        self.is_running = False
        if self.monitor_thread:
            self.monitor_thread.join(timeout=1)
            
    def get_dropped_files(self) -> List[str]:
        """获取拖拽的文件列表"""
        files = list(self.dropped_files)
        self.dropped_files.clear()
        return files
        
    def add_dropped_file(self, file_path: str):
        """添加拖拽的文件"""
        if os.path.exists(file_path):
            file_ext = os.path.splitext(file_path)[1].lower()
            if file_ext in self.supported_formats:
                self.dropped_files.append(file_path)
                print(f"检测到拖拽文件: {file_path}")
                
    def _monitor_drag_events(self):
        """监控拖拽事件"""
        if not self.window_id:
            print("窗口ID未设置，无法监控拖拽事件")
            return
            
        try:
            # 首先注册窗口为拖拽目标
            self._register_drag_target()
            
            # 使用xev监控窗口事件
            cmd = ['xev', '-id', str(self.window_id), '-event', 'dnd']
            process = subprocess.Popen(cmd,
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE,
                                     text=True)
            
            print(f"开始监控窗口 {self.window_id} 的拖拽事件")
            
            while self.is_running:
                line = process.stdout.readline()
                if not line:
                    break
                    
                # 解析拖拽事件
                if 'XdndEnter' in line:
                    self._handle_drag_enter(line)
                elif 'XdndPosition' in line:
                    self._handle_drag_position(line)
                elif 'XdndDrop' in line:
                    self._handle_drag_drop(line)
                elif 'XdndLeave' in line:
                    self._handle_drag_leave(line)
                    
        except Exception as e:
            print(f"拖拽事件监控错误: {e}")
            # 降级到文件监控
            self._fallback_to_file_monitor()
        finally:
            if 'process' in locals():
                process.terminate()
                
    def _register_drag_target(self):
        """注册窗口为拖拽目标"""
        try:
            # 使用xprop设置窗口属性，标记支持拖拽
            cmd = ['xprop', '-id', str(self.window_id), '-f', '_NET_WM_WINDOW_TYPE', '32a', 
                   '-set', '_NET_WM_WINDOW_TYPE', '_NET_WM_WINDOW_TYPE_NORMAL']
            subprocess.run(cmd, capture_output=True, timeout=2)
            
            # 设置XdndAware属性
            cmd = ['xprop', '-id', str(self.window_id), '-f', 'XdndAware', '32c', 
                   '-set', 'XdndAware', '5']
            subprocess.run(cmd, capture_output=True, timeout=2)
            
            print(f"窗口 {self.window_id} 已注册为拖拽目标")
            
        except Exception as e:
            print(f"注册拖拽目标失败: {e}")
            
    def _handle_drag_enter(self, event_line: str):
        """处理拖拽进入事件"""
        print("检测到拖拽进入")
        
    def _handle_drag_position(self, event_line: str):
        """处理拖拽位置事件"""
        # 可以在这里更新拖拽位置显示
        pass
        
    def _handle_drag_drop(self, event_line: str):
        """处理拖拽释放事件"""
        print("检测到拖拽释放")
        
        # 尝试获取拖拽的文件
        files = self._extract_dropped_files()
        if files:
            for file_path in files:
                self.add_dropped_file(file_path)
                
            # 调用回调函数
            if self.drop_callback:
                self.drop_callback(list(self.dropped_files))
                
    def _handle_drag_leave(self, event_line: str):
        """处理拖拽离开事件"""
        print("拖拽离开窗口")
        
    def _extract_dropped_files(self) -> List[str]:
        """提取拖拽的文件路径"""
        files = []
        
        try:
            # 方法1：尝试从剪贴板获取
            result = subprocess.run(['xclip', '-selection', 'clipboard', '-o'],
                                  capture_output=True, text=True, timeout=1)
            if result.returncode == 0 and result.stdout.strip():
                files.extend(self._parse_uri_list(result.stdout.strip()))
                
            # 方法2：尝试从主选择获取
            result = subprocess.run(['xclip', '-selection', 'primary', '-o'],
                                  capture_output=True, text=True, timeout=1)
            if result.returncode == 0 and result.stdout.strip():
                files.extend(self._parse_uri_list(result.stdout.strip()))
                
            # 方法3：使用xsel作为备选
            if not files:
                result = subprocess.run(['xsel', '--clipboard', '--output'],
                                      capture_output=True, text=True, timeout=1)
                if result.returncode == 0 and result.stdout.strip():
                    files.extend(self._parse_uri_list(result.stdout.strip()))
                    
        except Exception as e:
            print(f"提取拖拽文件失败: {e}")
            
        return files
        
    def _parse_uri_list(self, uri_list: str) -> List[str]:
        """解析URI列表，提取文件路径"""
        files = []
        
        for line in uri_list.split('\n'):
            line = line.strip()
            if not line or line.startswith('#'):
                continue
                
            if line.startswith('file://'):
                # URI格式
                file_path = line[7:]  # 移除 'file://' 前缀
                file_path = file_path.replace('%20', ' ')  # 解码空格
                file_path = file_path.strip()
                
                # 移除可能的回车符
                if file_path.endswith('\r'):
                    file_path = file_path[:-1]
                    
                if os.path.exists(file_path):
                    files.append(file_path)
            elif os.path.isabs(line):
                # 绝对路径
                if os.path.exists(line):
                    files.append(line)
                    
        return files
        
    def _fallback_to_file_monitor(self):
        """降级到文件监控"""
        print("降级到文件监控模式")
        
        # 监控桌面和下载目录
        watch_dirs = [
            os.path.expanduser('~/Desktop'),
            os.path.expanduser('~/Downloads'),
            '/tmp'
        ]
        
        known_files = set()
        
        # 初始化已知文件
        for watch_dir in watch_dirs:
            if os.path.exists(watch_dir):
                for filename in os.listdir(watch_dir):
                    filepath = os.path.join(watch_dir, filename)
                    if self._is_supported_format(filepath):
                        try:
                            known_files.add(filepath)
                        except OSError:
                            pass
                            
        while self.is_running:
            try:
                current_time = time.time()
                
                for watch_dir in watch_dirs:
                    if not os.path.exists(watch_dir):
                        continue
                        
                    try:
                        for filename in os.listdir(watch_dir):
                            filepath = os.path.join(watch_dir, filename)
                            if self._is_supported_format(filepath):
                                try:
                                    file_time = os.path.getmtime(filepath)
                                    
                                    # 检查是否是新文件（最近5秒内创建）
                                    if filepath not in known_files and current_time - file_time < 5.0:
                                        print(f"检测到新文件: {filepath}")
                                        self.add_dropped_file(filepath)
                                        
                                        # 调用回调函数
                                        if self.drop_callback:
                                            self.drop_callback([filepath])
                                            
                                    known_files.add(filepath)
                                    
                                except OSError:
                                    continue
                                    
                    except PermissionError:
                        continue
                    except Exception as e:
                        print(f"监控目录 {watch_dir} 时出错: {e}")
                        
            except Exception as e:
                print(f"文件监控错误: {e}")
                
            time.sleep(0.5)
            
    def _is_supported_format(self, file_path: str) -> bool:
        """检查文件格式是否支持"""
        if not os.path.isfile(file_path):
            return False
            
        file_ext = os.path.splitext(file_path)[1].lower()
        return file_ext in self.supported_formats
        
    def is_supported(self) -> bool:
        """检查是否支持拖拽功能"""
        # 检查X11环境
        if not os.environ.get('DISPLAY'):
            return False
            
        # 检查必要工具
        required_tools = ['xev', 'xprop', 'xclip']
        for tool in required_tools:
            try:
                subprocess.run(['which', tool], capture_output=True, check=True)
            except subprocess.CalledProcessError:
                print(f"缺少必要工具: {tool}")
                return False
                
        return True
        
    def get_platform_info(self) -> dict:
        """获取平台信息"""
        return {
            'system': 'Linux',
            'detector_type': 'X11DragReceiver',
            'supported': self.is_supported(),
            'display': self.x11_display,
            'window_id': self.window_id
        }