keyboard | 手机变身输入神器,输入更便捷!

科技   2025-01-08 21:39   四川  
keyboard

在家看电视时,使用HDMI连接笔记本电脑却没有无线键盘,输入操作真是麻烦得让人头疼!屏幕键盘虽然是个选择,但用起来实在不够友好。为了让大家拥有更好的输入体验,今天和朋友们分享一款神奇的软件,该软件使用python编写,源代码见文末,可以直接把手机作为电脑的虚拟键输入设备,轻松解决输入难题。

这款软件的使用方法也非常简单,下面就简单的分享如何使用:

1. 解压缩下载的文件,完成后,双击运行软件。这个步骤不需要任何复杂的设置,就像启动任何普通程序一样简单,如果安装了python,可以直接运行python原代码。

2. 手机和笔记本电脑在同一个局域网内。打开手机的浏览器,输入软件上展示的地址,如:192.168.0.100:8080。

3. 在浏览器中输入您想要发送到电脑上的文字,点击发送按钮,文字就会立刻出现在您的电脑上,快捷又方便。

手机端:


电脑端:

有了这款软件,再也不用担心没有无线键盘导致输入不便的问题。无论是在家中还是在生活、工作中的其他需要输入的场景下,只要有手机在手,您就可以轻松完成输入任务!

下载地址:
https://wwyr.lanzouw.com/b00uyphzgj 密码:9twv

好了,以上就是本期分享的内容,希望对朋友们有用!

源代码


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;

        }

    </style>

</head>

<body>

<div class="container">

    <div id="status">等待连接...</div>


    <div class="keyboard" id="keyboard">

        <!-- 键盘布局将通过JavaScript生成 -->

    </div>


    <div class="text-input-section">

        <h3>文本输入</h3>

        <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)




每个夜归的人,都是对生活有企图心的人!

愿不管多晚,总有一盏灯等你回家!

社区e电脑店
分享各类电脑实用工具、收银系统相关操作文档及视频。以帮助朋友们提高学习、工作效率为快乐,提供收银系统解决方案,让学习、工作更简单,更省心!每个夜归的人,都是对生活有企图心的人!
 最新文章