公告
  
重要通知:网站网络变更中可能出现站点图片无法加载的问题,点击此处可解决!
更多资讯可访问:点击查看消息详情!

朕已阅

手机充当电脑虚拟输入设备

admin 千秋月 关注 管理组 论坛神话
发表于电脑软件I版块 实用软件

这款软件旨在让手机能够充当电脑的输入设备。


在家通过HDMI连接笔记本电脑观看电视节目时,由于没有无线键盘,输入操作变得十分繁琐。通常只能使用屏幕键盘进行输入,然而其操作起来也很不方便。


于是,我们开发出了这款软件。借助这款软件,就能够使用手机将输入内容传输到电脑当中。


其使用方法如下:

1. 首先对软件进行解压缩,然后双击运行程序。

2. 在处于同一局域网的手机浏览器中,输入软件所显示的地址。

3. 在手机浏览器的相应界面输入文字后,点击发送按钮,这样就可以在电脑上实现输入操作了。


下载地址:


您需要登录并回复后才可以查看隐藏的内容


main.py源代码:


import asyncio
import json
import pyautogui
import pyperclip
from aiohttp import web
import os
from pathlib import Path

html = '''
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>虚拟键盘</title>
    <style>
        * {
            box-sizing: border-box;
        }
        body {
            margin: 0;
            padding: 20px;
            touch-action: manipulation;
            user-select: none;
            font-family: Arial, sans-serif;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
        }

        .keyboard {
            display: grid;
            grid-template-columns: repeat(10, 1fr);
            gap: 5px;
            margin-top: 20px;
        }

        .key {
            background: #e0e0e0;
            border: none;
            border-radius: 5px;
            padding: 15px 5px;
            font-size: 16px;
            touch-action: manipulation;
        }

        .key:active {
            background: #bdbdbd;
        }

        .key.wide {
            grid-column: span 2;
        }

        #status {
            text-align: center;
            margin: 20px 0;
            padding: 10px;
            background: #f5f5f5;
            border-radius: 5px;
        }

        .text-input-section {
            margin: 20px 0;
            padding: 20px;
            background: #f5f5f5;
            border-radius: 5px;
        }

        .text-input-section textarea {
            width: 100%;
            height: 100px;
            margin: 10px 0;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            resize: vertical;
        }

        .button-group {
            display: flex;
            gap: 10px;
            margin: 10px 0;
        }

        .button-group button {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            flex: 1;
        }

        .button-group button:active {
            background: #45a049;
        }

        .history-section {
            margin: 20px 0;
            padding: 20px;
            background: #f5f5f5;
            border-radius: 5px;
        }

        .history-list {
            max-height: 200px;
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 5px;
            background: white;
        }

        .history-item {
            padding: 10px;
            border-bottom: 1px solid #eee;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .history-item:last-child {
            border-bottom: none;
        }

        .history-text {
            flex: 1;
            margin-right: 10px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .history-actions {
            display: flex;
            gap: 5px;
        }

        .history-actions button {
            background: #2196F3;
            color: white;
            border: none;
            padding: 5px 10px;
            border-radius: 3px;
            cursor: pointer;
            font-size: 12px;
        }

        .history-actions button.delete {
            background: #f44336;
        }

        .history-actions button:active {
            opacity: 0.8;
        } 
        .link {
            display: inline-block;
            margin-top: 5px; /* 调整间距 */
            font-size: 14px; /* 调整字体大小 */
            color: #007BFF;
            text-decoration: none;
            border-bottom: 2px solid #007BFF;
            padding-bottom: 3px; /* 调整间距 */
            transition: color 0.3s ease;
        }
        .link:hover {
            color: #0056b3;
        }
    </style>
</head>
<body>
<div class="container">
    <div id="status">等待连接...</div>

    <div class="keyboard" id="keyboard">
        <!-- 键盘布局将通过JavaScript生成 -->
    </div>

    <div class="text-input-section">
        <h3>文本输入</h3>
          <a href="http://www.3qpd.com" class="link">更多黑科技资源,请访问www.3qpd.com!</a>
        <textarea id="customText" placeholder="在这里输入要发送的文本..."></textarea>
        <div class="button-group">
            <button onclick="sendCustomText()">发送文本</button>
            <button onclick="clearInput()">清空输入</button>
        </div>
    </div>

    <div class="history-section">
        <h3>历史记录</h3>
        <div class="history-list" id="historyList">
            <!-- 历史记录将通过JavaScript动态添加 -->
        </div>
    </div>

</div>

<script>
    let ws = null;
    const keyboard = document.getElementById('keyboard');
    const status = document.getElementById('status');
    const historyList = document.getElementById('historyList');
    const MAX_HISTORY = 10;

    // 从localStorage加载历史记录
    let inputHistory = JSON.parse(localStorage.getItem('inputHistory') || '[]');

    // 键盘布局
    // const keys = [
    //     '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
    //     'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P',
    //     'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';',
    //     'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/',
    //     'Space', 'Backspace', 'Enter'
    // ];
    const keys = [
        'Space', 'Backspace', 'Enter'
    ];

    // 生成键盘按钮
    keys.forEach(key => {
        const button = document.createElement('button');
        button.className = 'key';
        if (key === 'Space' || key === 'Backspace' || 'Enter') {
            button.className = 'key wide';
        }
        button.textContent = key === 'Space' ? '空格' :
            key === 'Backspace' ? '删除' : key === 'Enter'? '回车' : key;

        button.addEventListener('touchend', (e) => {
            e.preventDefault();
            sendKey(key);
        });

        keyboard.appendChild(button);
    });

    // 连接WebSocket服务器
    function connect() {
        const protocol = location.protocol === 'https:' ? 'wss://' : 'ws://';
        ws = new WebSocket(protocol + location.host + '/ws');

        ws.onopen = () => {
            status.textContent = '已连接';
            status.style.background = '#c8e6c9';
        };

        ws.onclose = () => {
            status.textContent = '连接断开,尝试重新连接...';
            status.style.background = '#ffcdd2';
            setTimeout(connect, 3000);
        };
    }

    // 发送按键信息
    function sendKey(key) {
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({
                type: 'keypress',
                key: key
            }));
        }
    }

    // 更新历史记录显示
    function updateHistoryDisplay() {
        historyList.innerHTML = '';
        inputHistory.forEach((text, index) => {
            const historyItem = document.createElement('div');
            historyItem.className = 'history-item';

            const textSpan = document.createElement('span');
            textSpan.className = 'history-text';
            textSpan.textContent = text;

            const actions = document.createElement('div');
            actions.className = 'history-actions';

            const sendButton = document.createElement('button');
            sendButton.textContent = '发送';
            sendButton.onclick = () => resendHistoryText(text);

            const deleteButton = document.createElement('button');
            deleteButton.textContent = '删除';
            deleteButton.className = 'delete';
            deleteButton.onclick = () => deleteHistoryItem(index);

            actions.appendChild(sendButton);
            actions.appendChild(deleteButton);

            historyItem.appendChild(textSpan);
            historyItem.appendChild(actions);
            historyList.appendChild(historyItem);
        });
    }

    // 添加到历史记录
    function addToHistory(text) {
        if (text && !inputHistory.includes(text)) {
            inputHistory.unshift(text);
            if (inputHistory.length > MAX_HISTORY) {
                inputHistory.pop();
            }
            localStorage.setItem('inputHistory', JSON.stringify(inputHistory));
            updateHistoryDisplay();
        }
    }

    // 删除历史记录项
    function deleteHistoryItem(index) {
        inputHistory.splice(index, 1);
        localStorage.setItem('inputHistory', JSON.stringify(inputHistory));
        updateHistoryDisplay();
    }

    // 重新发送历史记录中的文本
    function resendHistoryText(text) {
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({
                type: 'text',
                content: text
            }));
        }
    }

    // 发送自定义文本
    function sendCustomText() {
        const textarea = document.getElementById('customText');
        const text = textarea.value;

        if (text && ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({
                type: 'text',
                content: text
            }));
            addToHistory(text);
            textarea.value = ''; // 清空输入框
        }
    }

    // 清空输入框
    function clearInput() {
        document.getElementById('customText').value = '';
    }

    // 初始化
    connect();
    updateHistoryDisplay();
</script>
</body>
</html>
'''

# WebSocket处理函数
# WebSocket处理函数
async def websocket_handler(request):
    ws = web.WebSocketResponse()
    await ws.prepare(request)

    try:
        async for msg in ws:
            if msg.type == web.WSMsgType.TEXT:
                data = json.loads(msg.data)

                if data['type'] == 'keypress':
                    key = data['key']
                    if key == 'Space':
                        pyautogui.press('space')
                    elif key == 'Backspace':
                        pyautogui.press('backspace')
                    elif key == 'Enter':
                        pyautogui.press('enter')
                    else:
                        pyautogui.press(key)

                elif data['type'] == 'text':
                    # 使用剪贴板来处理文本输入
                    text = data['content']
                    original_clipboard = pyperclip.paste()  # 保存原始剪贴板内容

                    try:
                        pyperclip.copy(text)  # 复制新文本到剪贴板
                        pyautogui.hotkey('ctrl', 'v')  # 模拟粘贴操作
                    finally:
                        # 恢复原始剪贴板内容
                        pyperclip.copy(original_clipboard)

    except Exception as e:
        print(f"WebSocket error: {e}")
    finally:
        return ws

# HTTP首页处理函数
async def index_handler(request):
    # 从文件读取HTML内容
    # html_path = Path(__file__).parent / 'templates' / 'keyboard.html'
    # with open(html_path, 'r', encoding='utf-8') as f:
    #     content = f.read()
    return web.Response(text=html, content_type='text/html')

# 获取本地IP地址
def get_local_ip():
    import socket
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
        s.close()
        return ip
    except:
        return '127.0.0.1'

async def init_app():
    app = web.Application()
    app.router.add_get('/', index_handler)
    app.router.add_get('/ws', websocket_handler)
    return app

if __name__ == '__main__':
    # 需要安装: pip install aiohttp pyautogui
    ip = get_local_ip()
    port = 8080

    print(f"正在启动服务器...")
    print(f"请在手机浏览器访问: http://{ip}:{port}")

    app = init_app() # asyncio.get_event_loop().run_until_complete(init_app())
    web.run_app(app, host='0.0.0.0', port=port)

本文章最后由 admin2025-01-13 22:10 编辑
评论列表 评论
a4267267 吊你吊我~ 普通用户 常驻会员 2#
强烈支持楼主ing……
发布评论

评论: 手机充当电脑虚拟输入设备



点击进入免费吃瓜群!吃大瓜! 广告位支持代码、文字、图片展示 Image


免责声明
本站资源,均来自网络,版权归原作者,所有资源和文章仅限用于学习和研究目的 。 不得用于商业或非法用途,否则,一切责任由该用户承担 !

请求资源或报告无效资源,请点击[反馈中心]


侵权删除请致信 E-Mail:chengfengad@gmail.com
已有0次打赏
(0) 分享
分享
取消