手机投屏电脑的软件之一,Scrcpy

两年前Scrcpy还不成熟时,试用过一下,不好用。就没有用了,现在的版本好了很多,也稳定了好多。趁电脑安装ubuntu20.04最新版时,又试用了一下。相当不错,链接两个小时,没有一次掉线。又怕忘记如何链接的,就做个笔记。
一 安装
1
2
$ sudo snap install scrcpy # 用apt install scrcpy也可以,但是采用snap安装,可以安装比较新的版本
$ sudo apt install android-tools-adb
二 手机与电脑先物理连接

打开手机“允许USB调试”,用用USB线与电脑链接

三 配置
1
$ lsusb  # 查看链接到USB接品的设备

USB device
可以看到,我的手机的ID号是:18d1:4ee2

1
2
3
4
5
6
7
8
$ mkdir ~/.android
$ eho 0x18d1 > ~/.android/adb_usb.ini
$ cd /etc/udev/rules.d
$ vim android.rules
# 写入
SUBSYTEM=="usb", SYSFS{idVendor}=="04e8", MODE="0666"
# 保存退出
$ sudo chmod 777 android.rules
四 启动服务
1
2
3
$ sudo service udev restart
$ adb start-server
$ adb devices

终端会显示
说明己经连接上了手机。则可以启动scrcpy了。

1
$ scrcpy

可以链接了。

五 无线链接

首先 手机与电脑要保持在一个局域网内,先用USB线连接手机

1
$ adb tcpip 5555 #设端口为5555

把线拔掉

1
2
3
4
$ adb connect 192.168.8.218:5555 # 192.168.8.218是手机的IP地址
$ scrcpy
$ scrcpy --turn-screen-off # 连接时,可关掉手机屏幕
$ scrcpy --bit-rate 2M --max-size 800 # 降低无线链接的速率(2M),与屏幕分辨率(800),以保持无线链接不卡线

修改Anlinux的shell,以安装ubuntu的新版本

多年前采用anlinux的app,来在termux上安装linux,后面有大神写出了proot-distro的东东来安装linux非常方便,还能通过修改/etc/proot-distro/目录下的文件,选择各种版本的linux,于是果断放弃了anlinux转用proot-distoo。然而proot-distro的作者却自己封装了linux的rootfs文件,没有辣么方便了,又从proot-distro更新到2.9.3后,竟然连我以前装的linux版本都启动不了了。于是想起了以前的anlinux的app,拿它与proot-distro的shell比较过,觉得它更简单,于是修改之,自己安装Canonical官方发布的arm版 ubuntu21.04,例子如下:
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
#!/data/data/com.termux/files/usr/bin/bash
folder=ubuntu-hirsute
if [ -d "$folder" ]; then
first=1
echo "skipping downloading"
fi
# 下载Canonical官方发布的 ubutnu21.04 的 rootfs
wget "https://partner-images.canonical.com/core/hirsute/current/ubuntu-hirsute-core-cloudimg-arm64-root.tar.gz"
tarball="ubuntu-hirsute-core-cloudimg-arm64-root.tar.gz"
cur=`pwd`
mkdir -p "$folder"
cd "$folder"
echo "Decompressing Rootfs, please be patient."
proot --link2symlink tar -zxvf ${cur}/${tarball}||:
cd "$cur"

mkdir -p ubuntu-binds
bin=start-hirsute.sh
echo "writing launch script"
cat > $bin <<- EOM
#!/bin/bash
cd \$(dirname \$0)
## unset LD_PRELOAD in case termux-exec is installed
unset LD_PRELOAD
command="proot"
command+=" --link2symlink"
command+=" -0"
command+=" -r $folder"
if [ -n "\$(ls -A ubuntu-binds)" ]; then
for f in ubuntu-binds/* ;do
. \$f
done
fi
command+=" -b /dev"
command+=" -b /proc"
command+=" -b ubuntu-hirsute/root:/dev/shm"
## uncomment the following line to have access to the home directory of termux
#command+=" -b /data/data/com.termux/files/home:/root"
## uncomment the following line to mount /sdcard directly to /
command+=" -b /sdcard"
command+=" -b /storage"
command+=" -w /root"
command+=" /usr/bin/env -i"
command+=" HOME=/root"
command+=" PATH=/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/games:/usr/local/games"
command+=" TERM=\$TERM"
command+=" LANG=C.UTF-8"
command+=" /bin/bash --login"
com="\$@"
if [ -z "\$1" ];then
exec \$command
else
\$command -c "\$com"
fi
EOM

echo "fixing shebang of $bin"
termux-fix-shebang $bin
echo "making $bin executable"
chmod +x $bin
echo "removing image for some space"
rm $tarball
echo "You can now launch Ubuntu with the ./${bin} script"
安装完成后,执行 ./start-hirsute.sh 进入ubuntu21.04,发现不能apt update,后来发现是没有dns解析,于是
1
2
3
4
5
6
cd /etc
vim resolv.conf
添加
nameserver 114.114.114.114
nameserver 8.8.8.8
保存后退出
进入linux后,出现以下错误
1
2
3
4
goups: cannot find name for group ID 3003
groups: cannot find name for group ID 9997
goups: cannot find name for group ID 20xxx
groups: cannot find name for group ID 50xxx

这是由于termux安装ubuntu后没有给原本Android的系统用户组赋予名称导致的。由于每次在不同设备上安装termux.apk分配的u0_id (whoamis查到是u0_axxx)中的id值是不同的,这个需要每次改一下
解决方法:
1.用startubuntu进入系统su
2.执行以下命令

1
2
3
4
$ addgroup --system --gid 3003 inet
$ addgroup --system --gid 9997 sdcard_rw
$ addgroup --system --gid 50xxx all_axxx
$ reset

streamlit学习笔记五-----暂称全局变量吧

在编写一个streamlit代码时,发现有一个变量会在几个函数中使用,可是可这个变量如果定义在外的话,每个函数调用改变它后,虽的函数再调用,这个变量并不是上次改过的值,值还是初始化时赋的值。比如 代码一
1
2
3
4
5
6
7
8
9
10
11
12
import streamlit as st

st.title('代码一例子')
count = 0

increment = st.button('增加')
if increment:
count += 1

st.write('Count = ', count)

# 这个代码执行后,不管你咋按那个按钮,count都是1。
肿么才能避免呢? 采用 st.session_state,streamlit把它叫做 ”会话状态“
1
2
3
4
5
6
7
8
9
10
import streamlit as st

# 检查 'key' 是否存在于 session_state 中
# 如果不存在,则初始化之
if 'key' not in st.session_state:
st.session_state['key'] = 'value'

# 另一种语法
if 'key' not in st.session_state:
st.session_state.key = 'value'

一. 采用 “会话状态”,那么代码一可以改为代码二

1
2
3
4
5
6
7
8
9
10
11
12
import streamlit as st

st.title('代码一改为代码二')
# 检查 'count' 是否在 session_state中
if 'count' not in st.session_state:
st.session_state.count = 0

increment = st.button('增加')
if increment:
st.session_state.count += 1

st.write('Count = ', st.session_state.count)

二. 使用 “会话状态” 与 “回调函数” 来修改代码一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import streamlit as st

st.title('采用回调来更改代码一')
# 检查 'count' 是否在 session_state中
if 'count' not in st.session_state:
st.session_state.count = 0

# 定义函数,一会儿就要回调它
def increment_counter():
st.session_state.count += 1

# 在 st.button里回调函数 increment_counter()
st.button('Increment', on_click=increment_counter)

st.write('Count = ', st.session_state.count)

三. 以上的回调函数是木有参数的,那么如果有参数呢?

1. 使用 args 小部件来传递参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import streamlit as st

st.title('回调函数有参数')
if 'count' not in st.session_state:
st.session_state.count = 0

increment_value = st.number_input('输入一个数值', value=0, step=1)

# 函数有参数
def increment_counter(increment_value):
st.session_state.count += increment_value
# 采用 args 把参数导入
increment = st.button('Increment', on_click=increment_counter,
args=(increment_value, ))

st.write('Count = ', st.session_state.count)
2. 使用 kwargs 传递命名参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import streamlit as st

st.title('回调参数用 kwargs 传递参数')
if 'count' not in st.session_state:
st.session_state.count = 0

def increment_counter(increment_value=0):
st.session_state.count += increment_value

def decrement_counter(decrement_value=0):
st.session_state.count -= decrement_value

st.button('Increment', on_click=increment_counter,
kwargs=dict(increment_value=5))

st.button('Decrement', on_click=decrement_counter,
kwargs=dict(decrement_value=1))

st.write('Count = ', st.session_state.count)

四. 会话状态(全局变量)与 小部件中的值关联,采用 “key” 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import streamlit as st

if "celsius" not in st.session_state:
# set the initial default value of the slider widget
st.session_state.celsius = 50.0

st.slider(
"Temperature in Celsius",
min_value=-100.0,
max_value=100.0,
key="celsius" # 采用 key 把slider小部件中的值与全局变量celsius关联
)

# This will get the value of the slider widget
st.write(st.session_state.celsius)

flask全局变量三,视图间的参数传递

在”flask全局变量一“,时,我讲了采用session[“变量名”],来定义全局变量,实现在视图间传递变量。现在用用另一名方法,用上下文的方法,采用g变量,来定义全局变量。比方法一要简单
有几个上下文装饰器
装饰器名 方式
@app.before_request 函数会在请求处理之前被调用
@app.after_request 函数会在请求处理完成后才被调用
@app.teardown_request 函数会在请求处理完成后才被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask
from flask import g, request

app=Flask(__name__)

@app.before_request
def span_num_init():
g.start_num = 0 # 定义g变量
g.span_mum = 5 # 定义g变量

@app.route("/fun1")
def fun1():
g.start_num=9
g.span_num=16

@app.route("/fun2")
def fun2():
print("start_num=",g.start_num) # 此处start_num又变为0
print("span_num=",g.span_num]) # 此处start_num又变为5

flask全局变量二,自定义上下文参数

如果我要在模板中一个多选菜单的项目是个变量。那么要每个视图函数读一次变量,再发送到模版中去。比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_option_item():
item = ["项目1""项目2""项目3""项目4""项目5""项目6""项目7",]
return item
#视图1
@app.route(/index1)
def index1():
item=get_option_item()
return render_template("index1.html", item=itme)
....
#视图n
@app.route(/index_n)
def index_n():
item=get_option_item()
return render_template("indexn.html", item=itme)

在模板中

1
2
3
4
5
<select>
{% for select_i in item %}
<option> select_i </option>
{% endfor %}
</select>
每个视图要读一次很烦,那么采用上下文变量的方式就很好的解决了这个问题。修改一下python代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# current_app对象是一个定义在应用上下文中的代理
from flask import current_app
app=Flask(__name__)

# 函数用@app.context_processor装饰器修饰,它是一个上下文处理器,它的作用是在模板被渲染前运行其所修饰的函数,并将函数返回的字典导入到模板上下文环境中,与模板上下文合并。
@app.context_processor
def get_option_item():
item = ["项目1""项目2""项目3""项目4""项目5""项目6""项目7",]
return dict(item=item) #注意是字典
# 视图函数不用再调用了
#视图n
@app.route(/index_n)
def index_n():
# item=get_option_item()
return render_template("indexn.html")
模板不用改变,直接就可用了。

streamlit学习笔记五-----多个小部件分组

默认情况下,每次用户与您的streamlit应用(表单)交互时,Streamlit 都会重新运行您的脚本。然而,有时在实际重新运行脚本之前等待一组相关的小部件(表单项)被填充完后,再执行更好,这里st.form()诞生了。

格式

st.form(key: str, clear_on_submit: bool = False)
key: 字符串,表示这个表单的标识
clear_on_submit:布而值,如果为 True,则在用户按下提交按钮后,表单内的所有小部件都将重置为其默认值。

一 with用法
1
2
3
4
5
6
7
8
9
10
11
12
with st.form("my_form"):
st.write("Inside the form")
slider_val = st.slider("Form slider")
checkbox_val = st.checkbox("Form checkbox")

# 每一个form必须有一个 submit 按钮.
submitted = st.form_submit_button("Submit")
if submitted:
st.write("slider", slider_val, "checkbox", checkbox_val)

st.write("己通退了表单j")

二 指名用法
1
2
3
4
5
6
7
8

form23 = st.form("my_form")
form23.slider("Inside the form")
st.slider("在此表单外面了")

# 增加一个submit按钮到此表单:
form23.form_submit_button("Submit")

flask 的form学习之三,一个小form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MockCreate(Form):
user_email = StringField("email address",[validators.Email()])
api = StringField("api",[Required()])
submit = SubmitField("Submit")
code = IntegerField("code example: 200",[Required()])
alias = StringField("alias for api")
data = TextAreaField("json format",[Required()])


@app.route("/mockservice",methods=['GET','POST'])
def MockController():
form = MockCreate()
code = form['code']
api = form['api']
return render_template("testf.html",api=api,data=code)

flask全局变量一,视图间的参数传递

提出的问题:

flask视图间如何传递变量呢?开始我用的全局变量,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from flask import Flask,request,render_template

app=Flask(__name__)

app.secret_key = '就得是要一个key' # 必须有
app.config['SESSION_TYPE'] = 'filesystem' # 必须有

start_num=0
span_num=5

@app.route("/fun1")
def fun1():
start_num=9
span_num=16

@app.route("/fun2")
def fun2():
print("start_num=",start_num) #此处start_num又变为0
print("span_num=",span_num]) #此处start_num又变为5

在执行视图fun2时, session[“start_num”]与session[“span_num”]的值又变为初始值了。
咋整呢?

网上找到一种办法是 session法。修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask,request,render_template
from flask import session

app=Flask(__name__)

app.secret_key = '我的key'
app.config['SESSION_TYPE'] = 'filesystem'

session["start_num"]=0
session["span_num"]=5

@app.route("/fun1")
def fun1():
session["start_num"]=9
session["span_num"]=16

@app.route("/fun2")
def fun2():
print("session["start_num"]",session["start_num"]) #此处session["start_num"]又变为9
print("session["span_num"]",session["span_num"]) #此处session["start_num"]又变为16
还有一种办法就是把两个临时值传到数据库,或保存为json数据,存入文件中,程序不难,但是如果参数频繁改变的话,要反复读写文件就不好玩了。

flask 的form学习之二,Flask-WTF

使用 Flask-WTF的好处是直接在python中定义表单中的字段,比较直观

1
$ pip install flask-WTF

一. WTForms支持的全部HTML标准字段

字段对象 说明 相当于html的语句
StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密码字段
HiddenField 隐藏文件字段
DateField 文本字段,值为datetime.date文本格式
DateTimeField 文本字段,值为datetime.datetime文本格式
IntegerField 文本字段,值为整数
DecimalField 文本字段,值为decimal.Decimal数
FloatField 文本字段,值为浮点数
BooleanField 复选框,值为True或False <input type=”radio”
RadioField 一组单选框
SelectField 下接列表
SelecMultipleField 可选择多个值的下接列表
FileField 文件上传
SubmitField 表单提交按钮
FormField 把表单做为字段嵌入另一个表单
FieldList 一组指定类型的字段

二 python代码

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
from flask import Flask
from flask import render_template
from flask_wtf import FlaskForm
from wtforms.fields import (StringField, PasswordField, DateField, BooleanField,
SelectField, SelectMultipleField, TextAreaField,
RadioField, IntegerField, DecimalField, SubmitField)
from wtforms.validators import DataRequired, InputRequired, Length, Email, EqualTo, NumberRange


app = Flask(__name__)
app.secret_key = 'asdfs'


class WtfForm(FlaskForm):
# StringField 文本输入框,必填,用户名长度为4到25之间,占位符
username = StringField('用户名:', validators=[Length(min=4, max=25)], render_kw={'placeholder': '请输入用户名'})

# Email格式
email = StringField('邮箱地址:', validators=[Email()], render_kw={'placeholder': '请输入邮箱地址'})

# PasswordField,密码输入框,必填
password = PasswordField('密码:', validators=[DataRequired()], render_kw={'placeholder': '请输入密码'})

# 确认密码,必须和密码一致
password2 = PasswordField('确认密码:', validators=[InputRequired(), EqualTo('password', '两次密码要一致')])

# IntegerField,文本输入框,必须输入整型数值,范围在16到70之间
age = IntegerField('年龄:', validators=[NumberRange(min=16, max=70)])

# DecimalField,文本输入框,必须输入数值,显示时保留一位小数
height = DecimalField('身高(cm):', places=1)

# DateField,文本输入框,必须输入是"年-月-日"格式的日期
birthday = DateField('出生日期:', format='%Y-%m-%d')

# RadioField,单选框,choices里的内容会在ul标签里,里面每个项是(值,显示名)对
gender = RadioField('性别:', choices=[('0', '男'), ('1', '女')], validators=[DataRequired()])

# SelectField,下拉单选框,choices里的内容会在Option里,里面每个项是(值,显示名)对
job = SelectField('职业:', choices=[
('teacher', '教师'),
('doctor', '医生'),
('engineer', '工程师'),
('lawyer', '律师')
])

# Select类型,多选框,choices里的内容会在Option里,里面每个项是(值,显示名)对
hobby = SelectMultipleField('爱好:', choices=[
('0', '吃饭'),
('1', '睡觉'),
('2', '敲代码')
])

# TextAreaField,段落输入框
description = TextAreaField('自我介绍:', validators=[InputRequired()], render_kw={'placeholder': '例:小明,18岁,未婚找女友'})

# BooleanField,Checkbox类型,加上default='checked'即默认是选上的
accept_terms = BooleanField('是否接受上述条款', default='checked', validators=[DataRequired()])

# SubmitField,Submit按钮
submit = SubmitField('提交')


@app.route('/', methods=['POST', 'GET'])
def index():
form = WtfForm()
return render_template('wtf.html', form=form)

if __name__ == "__main__":
app.run(debug=True)

三. html代码

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="">
{{ form.csrf_token }}
{{ form.username.label }}{{ form.username }}
<br>
{{ form.email.label }}{{ form.email }}
<br>
{{ form.password.label }}{{ form.password }}
<br>
{{ form.password2.label }}{{ form.password2 }}
<br>
{{ form.age.label }}{{ form.age }}
<br>
{{ form.height.label }}{{ form.height }}
<br>
{{ form.birthday.label }}{{ form.birthday }}
<hr>
{{ form.gender.label }}{{ form.gender }}
<br>
{{ form.job.label }}{{ form.job }}
<br>
{{ form.hobby.label }}{{ form.hobby }}
<br>
{{ form.description.label }}{{ form.description }}
<br>
{{ form.accept_terms.label }}{{ form.accept_terms }}
<br>
{{ form.submit }}
<br>

</form>
</body>
</html>

flask 的form学习之一,传统form

传统的方法与php一样,先在html里建立form,并用action指向视图函数处理,并用POST方法传递参数。不多说,代码说明。

一. html文件 form.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" >
<title>表单示便</title>
</head>
<body>
<form action="regist",method="POST" >
用户名:<input type="text",name="username"><br>
密码:<input type="text",name="pawd"><br>
再次密码::<input type="text",name="re-pawd"><br>
<input type="submit",value="注册"><br>
</form>
</body>
</html>
二. python代码中的视图函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask,request,render_template

app=Flask(__name__)

@app.route(/)
def index():
return render_template("index.html")

@app.route("regist")
def regist():
if request.method=="GET":
return render_template("regist.html") # 没有输入的样子就是这个。
if request.methom=="POST":
uname=request.form.get("username")
password=request.form.get("pawd")
re_password=request.form.get("re-pawd")
#下面添加处理语句

if __name__=="__main__":
app.debug=True
app.run(host = '127.0.0.1',port = 8080 )