在家看电视时,使用HDMI连接笔记本电脑却没有无线键盘,输入操作真是麻烦得让人头疼!屏幕键盘虽然是个选择,但用起来实在不够友好。为了让大家拥有更好的输入体验,今天和朋友们分享一款神奇的软件,该软件使用python编写,源代码见文末,可以直接把手机作为电脑的虚拟键盘输入设备,轻松解决输入难题。
这款软件的使用方法也非常简单,下面就简单的分享如何使用:
1. 解压缩下载的文件,完成后,双击运行软件。这个步骤不需要任何复杂的设置,就像启动任何普通程序一样简单,如果安装了python,可以直接运行python原代码。
2. 手机和笔记本电脑在同一个局域网内。打开手机的浏览器,输入软件上展示的地址,如:192.168.0.100:8080。
3. 在浏览器中输入您想要发送到电脑上的文字,点击发送按钮,文字就会立刻出现在您的电脑上,快捷又方便。
手机端:
电脑端:
有了这款软件,再也不用担心没有无线键盘导致输入不便的问题。无论是在家中还是在生活、工作中的其他需要输入的场景下,只要有手机在手,您就可以轻松完成输入任务!
好了,以上就是本期分享的内容,希望对朋友们有用!
源代码
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)
每个夜归的人,都是对生活有企图心的人!
愿不管多晚,总有一盏灯等你回家!