【记录】Ubuntu26|通过网页和ydotool用手机远程输入文本到电脑上,方便接入手机上优越的语音输入法

Linux Wayland 手机远程输入方案

背景

在 Linux Wayland 环境下,GSConnect 等蓝牙键盘工具发送的是底层键盘扫描码,手机输入法和电脑输入法会互相打架,无法做到"手机打出什么字,电脑就输出什么字"。

本方案通过在电脑上搭建一个极简 Web 服务器,手机浏览器访问后自由使用手机输入法(拼音/语音/手写),点发送后文字自动粘贴到电脑当前光标位置。

核心原理:手机浏览器完成所有输入法运算,电脑只接收最终文本,通过剪贴板+模拟按键实现"立即上屏"。

环境要求

  • Ubuntu 22.04+(Wayland,GNOME 桌面)
  • Python 3(系统自带)
  • 手机和电脑在同一局域网

安装依赖

bash 复制代码
sudo apt install -y wl-clipboard ydotool
  • wl-clipboard :Wayland 剪贴板读写工具(wl-copy / wl-paste
  • ydotool :通过 /dev/uinput 在内核层面注入键盘事件,绕过 Wayland 沙盒

安装后需要确保用户在 input 组中:

bash 复制代码
sudo usermod -aG input $USER

重新登录或重启生效。启动 ydotoold 守护进程:

bash 复制代码
sudo systemctl enable --now ydotoold
# 或者用户级服务
systemctl --user enable --now ydotool

完整代码

代码已上传到 GitHub:https://github.com/shandianchengzi/remote_input_page

可以直接 clone:

bash 复制代码
git clone https://github.com/shandianchengzi/remote_input_page.git
cd remote_input_page

也可以手动新建文件 remote_input.py,把下面的代码复制进去:

python 复制代码
#!/usr/bin/env python3
"""Remote input server - use phone browser to type into PC."""

from http.server import BaseHTTPRequestHandler, HTTPServer
import subprocess
import time

HTML = """
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Remote Input</title>
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body { background: #121212; color: white; padding: 20px; font-family: -apple-system, sans-serif; }
        h2 { margin-bottom: 15px; font-weight: 400; color: #888; }
        textarea {
            width: 100%; height: 200px; font-size: 18px; padding: 12px;
            background: #1e1e1e; color: white; border: 1px solid #333;
            border-radius: 8px; resize: vertical;
        }
        button {
            width: 100%; height: 56px; font-size: 20px; margin-top: 12px;
            background: #2563eb; color: white; border: none;
            border-radius: 8px; cursor: pointer; transition: background 0.2s;
        }
        button:active { background: #1d4ed8; }
        #status { margin-top: 10px; color: #4ade80; font-size: 14px; min-height: 20px; }
    </style>
</head>
<body>
    <h2>Remote Input</h2>
    <textarea id="text" placeholder="Type here... (use phone IME freely)"></textarea>
    <button onclick="send()">Send to PC</button>
    <div id="status"></div>
    <script>
        let sending = false;
        function send() {
            if (sending) return;
            const t = document.getElementById('text');
            const s = document.getElementById('status');
            if (t.value.trim() === '') return;
            sending = true;
            fetch('/', { method: 'POST', body: t.value })
                .then(r => { s.textContent = 'Sent!'; t.value = ''; sending = false; setTimeout(() => s.textContent = '', 2000); })
                .catch(() => { s.textContent = 'Failed'; sending = false; });
        }
    </script>
</body>
</html>
"""


class Handler(BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        print(f"[LOG] {format % args}")

    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=utf-8")
        self.end_headers()
        self.wfile.write(HTML.encode("utf-8"))

    def do_POST(self):
        length = int(self.headers["Content-Length"])
        text = self.rfile.read(length).decode("utf-8")
        if text:
            import os
            env = os.environ.copy()
            env["DISPLAY"] = ":0"
            # 写入 CLIPBOARD(供 Ctrl+V 粘贴)
            subprocess.run(["wl-copy"], input=text.encode("utf-8"), env=env)
            # 写入 PRIMARY(供 Shift+Insert 粘贴)
            subprocess.run(["wl-copy", "-p"], input=text.encode("utf-8"), env=env)
            time.sleep(0.15)  # 等待剪贴板同步
            # ydotool 模拟 Shift+Insert
            subprocess.run(["ydotool", "key", "-d", "50",
                           "42:1", "110:1", "110:0", "42:0"], env=env)
        self.send_response(200)
        self.end_headers()


if __name__ == "__main__":
    server = HTTPServer(("0.0.0.0", 8080), Handler)
    # 替换为你电脑的实际局域网 IP
    print("Server running. Open http://<YOUR_IP>:8080 on your phone")
    server.serve_forever()

使用方法

1. 查看电脑局域网 IP

bash 复制代码
ip -4 addr show | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d/ -f1

2. 启动服务器

bash 复制代码
python3 remote_input.py

3. 手机访问

手机连同一 Wi-Fi,浏览器打开 http://<电脑IP>:8080,会看到一个黑色输入框。自由使用手机输入法输入文字,点击 "Send to PC" 即可粘贴到电脑当前光标位置。

关键技术细节

为什么不用 xdotool?

xdotool 通过 X11 协议注入按键,在 Wayland 下只能对 XWayland 应用生效。GNOME 原生应用(终端、文件管理器、Chrome Wayland 模式等)会直接拦截丢弃 xdotool 的事件。ydotool 通过 /dev/uinput 在内核层面注入,不受 Wayland 沙盒限制。

为什么不用 wtype?

wtype 依赖 Wayland 的 virtual keyboard protocol,GNOME (Mutter) 不支持该协议,直接报错退出。

为什么用 Shift+Insert 而不是 Ctrl+V?

Linux 终端中 Ctrl+V 被保留为终端控制信号(字面量输入),粘贴快捷键是 Ctrl+Shift+V。但浏览器中粘贴是 Ctrl+V。Shift+Insert 是 Linux 下的通用粘贴键,终端和浏览器都认。

为什么要同时写入 CLIPBOARD 和 PRIMARY?

Linux 有两个独立的剪贴板:

  • CLIPBOARD:Ctrl+C/V 操作的常规剪贴板
  • PRIMARY:鼠标选中即复制的主选择区,Shift+Insert 读取的就是这个

终端在处理 Shift+Insert 时会读 PRIMARY,浏览器则读 CLIPBOARD。只写一个会导致"终端粘出老内容,浏览器粘出新内容"的问题。wl-copy 默认写 CLIPBOARD,wl-copy -p 写 PRIMARY。

为什么 ydotool 需要 -d 50 延迟?

ydotool 通过 /dev/uinput 注入按键事件。如果按键事件之间没有间隔,系统输入状态机来不及同步"修饰键已按下"的状态,会导致按键被当作字面字符输出(比如出现 ^V 字面量)。-d 50 表示每个按键事件之间延迟 50ms。

常见问题

手机打不开网页

  • 确认手机和电脑在同一 Wi-Fi
  • 检查电脑防火墙是否放行 8080 端口:sudo ufw allow 8080
  • 确认服务器正在运行:ss -tlnp | grep 8080

粘贴后没有反应

  • 确认 ydotoold 正在运行:systemctl --user status ydotool
  • 确认用户在 input 组:id -nG $USER | grep input
  • 检查终端输出的 [LOG] 日志

中文粘贴为空

  • 检查 wl-copy 是否正常:echo "测试" | wl-copy && wl-paste
  • 如果 wl-paste 输出为空,可能是 Wayland 剪贴板守护进程问题,尝试重启 GNOME Shell(Alt+F2 输入 r

本账号所有文章均为原创,欢迎转载,请注明文章出处:https://shandianchengzi.blog.csdn.net/article/details/161542561。百度和各类采集站皆不可信,搜索请谨慎鉴别。技术类文章一般都有时效性,本人习惯不定期对自己的博文进行修正和更新,因此请访问出处以查看本文的最新版本。

相关推荐
拥抱太阳061620 分钟前
HarmonyOS 应用开发《掌上英语》第2篇:ArkTS 模块化开发:Index.ets 统一导出模式深度解析
pytorch·ubuntu·harmonyos
FREEDOM_X5 小时前
Linux 多线程编程——总结
java·linux·运维·ubuntu
筱羽_筱羽7 小时前
跟着“Ubuntu18.04/20.04编译Linux5.10.76+Xenomai-3.2.1+IgH”教程遇到的问题
linux·运维·ubuntu
私人珍藏库8 小时前
【PC】抖音视频解析器v3.1.0
pc·软件
私人珍藏库9 小时前
[Android] VOKA -人体解剖3D图谱+医学人体解剖
3d·智能手机·app·工具·软件·多功能
私人珍藏库17 小时前
[Android] PeakFinder AR v4.8.89 (山峰全景识别+增强现实山峰查看器)
android·人工智能·智能手机·ar·工具·软件
Felix-lxd19 小时前
Ubuntu 22.04 配置 Nginx
linux·nginx·ubuntu
啵啵鱼爱吃小猫咪1 天前
Ubuntu 20.04 + ROS Noetic 源码安装 libfranka 0.8.0 和 franka_ros 0.8.0 完整教程
linux·运维·ubuntu
huy1n91 天前
Ubuntu 22.04部署Claude Code并接入DeepSeek-V4模型完整教程
linux·ubuntu·deepseek·claude code
问简1 天前
【共享盘】ubuntu、windows
linux·windows·ubuntu