SiriBlog

siriyang的个人博客


  • 首页

  • 排行榜

  • 标签115

  • 分类37

  • 归档320

  • 关于

  • 搜索

Pythonista中文教程:100行代码实现一款远程键盘

发表于 2020-03-25 更新于 2021-10-29 分类于 计算机 , 技术 , Python 阅读次数: Valine:
本文字数: 6.3k 阅读时长 ≈ 6 分钟

前言

  最近在逛AppStore的时候看到了一个挺有意思的app叫“远程输入法”,意在使用电脑上使用键盘给iOS设备进行远程输入。出于好奇就买下来玩了玩,功能确实不错,但是总觉得少了些我想要的东西,还不够完美。那为什么不自己DIY一款远程输入法呢,正好可以学习使用Pythonista 3.3版本新添加的键盘功能!


正文

  我们的实现思路模仿“远程输入法”设计如下:

  通过在Pythonista上运行服务器,从PC通过浏览器以局域网访问服务器页面,在页面上进行输入后通过POST表单将输入数据提交给服务器,服务器收到数据后在iOS输入界面做相应操作。所以本程序实现的一个前提是两个设备要在同一局域网内,简单来说连接同一路由器或手机热点即可。当然理论上除了PC设备可以访问,连接到同一局域网的手机浏览器甚至iOS设备本身也是可以访问页面进行操作的。

  首先我们要实现一个函数用于获取本地ip,以便电脑通过局域网访问,该段代码摘抄自Pythonista官方示例:

1
2
3
4
5
6
7
8
9
def get_local_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('google.com', 80))
ip = s.getsockname()[0]
s.close()
except:
ip = 'N/A'
return ip

  接下来实现服务器端,服务器框架我选用的是Bottle,因为他比较轻量级,所有功能都实现在一个py文件中,适合于Pythonista键盘系统资源较少的环境下运行。同样还能选择的服务器框架还有flask,这两款框架都已集成在Pythonista脚本库中可直接导入使用。
  我们先要实现一个自定义服务类,以便在ui界面结束退出的时候调用其stop()方法结束服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MainWindow(ui.View):

def __init__(self,server,localIP,port):
self.server=server

self.name = 'Keyboard Preview' # 仅在调试窗口中才看到窗口名
self.flex='WHTBLF' # 尺寸边距都设为自动,以便填充满整个键盘

# 创建iplabel以显示当前服务器运行的ip及端口
iplabel=ui.Label()
iplabel.flex='WHTBLF'
iplabel.text="请在浏览器打开:"+localIP+":"+str(port)
iplabel.background_color="#ffffff"
iplabel.alignment=ui.ALIGN_CENTER

# 之后还可以继续开发更多组件件加入主窗口
self.add_subview(iplabel)

# 判断是否是在键盘中运行
if keyboard.is_keyboard():
keyboard.set_view(self, 'expanded')
else:
# 当在pythonista主应用中调试时启动:
self.frame=(0,0,500,200)
self.present('sheet')

# 在窗口关闭时停止服务
def will_close(self):
self.server.stop()

  接下来实现服务器的各个接口。

  index()接口用于用户使用GET方法访问服务器主页时获取输入操作界面。目前demo中的前端实现非常简单,仅有单行提交的功能,多行提交和实时异步提交等功能将会是以后的发展方向:

1
2
3
4
5
6
7
8
@route('/')
def index():
return '''
<form action="/" method="post">
请输入:<input name="input" type="text" />
<input value="发送" type="submit" />
</form>
'''

  input()接口用于用户使用POST方法向服务器提交数据时,对数据进行处理。对iOS编辑器的实际操作在此进行,将来可在此对以不同方式提交的数据做各种操作,由用户自由发挥:

1
2
3
4
5
6
7
@route('/',method='POST')
def input():
inputdata = request.forms.getunicode('input') # 以unicode编码获取提交的数据,否则中文将会是乱码
print(inputdata) # 输出以便在控制台调试时观察
if keyboard.is_keyboard(): # 判断是否是在键盘中运行
keyboard.insert_text(inputdata) # 将提交的数据插入当前光标位置
return redirect('/') # 重定向到输入页面,今后将前端页面的数据提交以Ajax异步提交来实现以后可省去重定向

  error404()接口,用于当用户输入错误的网址时,引导用户回到主页面。

1
2
3
@error(404)
def error404(error):
return '对不起,这里什么也没有...<br/><a href="/">返回主页</a>'

  前端ui实现如下,我们通过继承实现一个MainWindow主窗口类,该类在初始化的时候获取服务器、IP和端口的信息,并添加一个ui.Label子视图以显示IP、端口信息,提示用户访问网页。今后还可以在主窗口添加更多的功能组件。重载will_close()方法,以便在窗口关闭时自动调用服务的stop()方法停止服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MainWindow(ui.View):

def __init__(self,server,localIP,port):
self.server=server

self.name = 'Keyboard Preview' # 仅在调试窗口中才看到窗口名
self.flex='WHTBLF' # 尺寸边距都设为自动,以便填充满整个键盘

# 创建iplabel以显示当前服务器运行的ip及端口
iplabel=ui.Label()
iplabel.flex='WHTBLF'
iplabel.text="请在浏览器打开:"+localIP+":"+str(port)
iplabel.background_color="#ffffff"
iplabel.alignment=ui.ALIGN_CENTER

# 之后还可以继续开发更多组件件加入主窗口
self.add_subview(iplabel)

# 判断是否是在键盘中运行
if keyboard.is_keyboard():
keyboard.set_view(self, 'expanded')
else:
# 当在pythonista主应用中调试时启动:
self.frame=(0,0,500,200)
self.present('sheet')

# 在窗口关闭时停止服务
def will_close(self):
self.server.stop()

  完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# -*- coding:utf-8 -*-
"""
@author:SiriYang
@file: remoteKeyboard
@time: 2020.03.25
"""

from bottle import run,route,request,redirect,error,ServerAdapter
import socket
import ui
import keyboard

# 该函数用于获取本地ip地址
def get_local_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('google.com', 80))
ip = s.getsockname()[0]
s.close()
except:
ip = 'N/A'
return ip

# 主页,用于用户访问输入。
# 目前demo中的前端实现非常简单,仅有单行提交的功能,多行提交和实时异步提交等功能将会是以后的发展方向。
@route('/')
def index():
return '''
<form action="/" method="post">
请输入:<input name="input" type="text" />
<input value="发送" type="submit" />
</form>
'''

# 当用户提交数据时调用
# 将来可在此对以不同方式提交的数据做各种操作,由用户自由发挥
@route('/',method='POST')
def input():
inputdata = request.forms.getunicode('input') # 以unicode编码获取提交的数据,否则中文将会是乱码
print(inputdata) # 输出以便在控制台调试时观察
if keyboard.is_keyboard(): # 判断是否是在键盘中运行
keyboard.insert_text(inputdata) # 将提交的数据插入当前光标位置
return redirect('/') # 重定向到输入页面,今后将前端页面的数据提交以Ajax异步提交来实现以后可省去重定向

# 当用户输入错误网址时调用
@error(404)
def error404(error):
return '对不起,这里什么也没有...<br/><a href="/">返回主页</a>'

# 单独实现一个我们自己的server类,主要是为了实现stop方法
class MyWSGIRefServer (ServerAdapter):
server=None

def run(self,handler):
from wsgiref.simple_server import make_server,WSGIRequestHandler
if self.quiet:
class QuietHandler(WSGIRequestHandler):
def log_request(*arg,**kw):pass
self.options['handler_class']=QuietHandler
self.server=make_server(self.host,self.port,handler,**self.options)
self.server.serve_forever()

# 该方法用于在主窗口线程中调用,以停止服务器线程
def stop(self):
print('server stop')
self.server.shutdown()

class MainWindow(ui.View):

def __init__(self,server,localIP,port):
self.server=server

self.name = 'Keyboard Preview' # 仅在调试窗口中才看到窗口名
self.flex='WHTBLF' # 尺寸边距都设为自动,以便填充满整个键盘

# 创建iplabel以显示当前服务器运行的ip及端口
iplabel=ui.Label()
iplabel.flex='WHTBLF'
iplabel.text="请在浏览器打开:"+localIP+":"+str(port)
iplabel.background_color="#ffffff"
iplabel.alignment=ui.ALIGN_CENTER

# 之后还可以继续开发更多组件件加入主窗口
self.add_subview(iplabel)

# 判断是否是在键盘中运行
if keyboard.is_keyboard():
keyboard.set_view(self, 'expanded')
else:
# 当在pythonista主应用中调试时启动:
self.frame=(0,0,500,200)
self.present('sheet')

# 在窗口关闭时停止服务
def will_close(self):
self.server.stop()

def main():
localIP=get_local_ip() # 本地ip地址
port=2333 # 服务器端口,用户可自行设置
server=MyWSGIRefServer(host=localIP,port=port) # 创建服务

v = MainWindow(server,localIP,port) # 创建主窗口

run(server=server) # 运行服务,这一步要在最后启动,不然会阻塞线程

if __name__ == '__main__':
main()

运行效果

  我们分别在控制台调试窗口和其他文本编辑应用窗口中做如下输入测试:

浏览器输入



控制台输出



文本编辑应用输出



  使用本脚本前请先在iOS设置中启用Pythonista,然后将其添加到Pythonista键盘的脚本列表中。具体操作请参考:应用程序扩展和快捷方式


结语

  由于则仅仅是一个demo,除了功能上的欠缺,还存在很多不完善的地方。由于Pythonista主程序和键盘是运行在两个进程中的,并且当脚本运行结束而没有关闭应用程序的时候,申请的端口会持续占用,所以请不要同时运行两个程序,否则会遇到“Address already in use”的报错,测试发现主要是因为键盘在运行结束后持续占用端口,并且键盘下滑隐藏后也仍然在后台运行,解决方法为点击键盘上的“齿轮”按钮,然后下滑到最下方点击“Exit keyboard”即可。还有一种bug就是当你在浏览器界面输入提交以后,浏览器一直在加载并没有提交成功,这个时候你再次点击提交按钮就会提交成功,但是加上之前阻塞的数据一共会向服务器提交两次输入数据,即你会在iOS编辑器界面看到两次重复的文本输入。
  接下来你可以在我当前的基础上继续进行开发,添加自己喜欢的功能,我之后有时间也会慢慢将其完善为一个成熟的项目。

Pythonista中文教程

-------- 本文结束 感谢阅读 --------
相关文章
  • Transista for iPadOS
  • 基于Pythonista和百度API的图片文本识别翻译脚本
  • Pythonista中文文档:scene
  • Pythonista中文文档:cb
  • Pythonista中文文档:contacts
觉得文章写的不错的话,请我喝瓶怡宝吧!😀
SiriYang 微信支付

微信支付

SiriYang 支付宝

支付宝

  • 本文标题: Pythonista中文教程:100行代码实现一款远程键盘
  • 本文作者: SiriYang
  • 创建时间: 2020年03月25日 - 22时03分
  • 修改时间: 2021年10月29日 - 18时10分
  • 本文链接: https://blog.siriyang.cn/posts/20200325225007id.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
Python Pythonista
Pythonista中文文档:editor
Pythonista中文文档:keyboard
SiriYang

SiriYang

努力搬砖攒钱买镜头的摄影迷
320 日志
33 分类
88 标签
RSS
GitHub E-Mail
Creative Commons
Links
  • 友情链接
  • 作品商铺

蜀ICP备19008337号 © 2019 – 2025 SiriYang | 1.7m | 25:41
0%