0%

项目地址:https://github.com/LittleWalnutX/Multiple_PDF_Printer

为了补足linux个人桌面在多样化的打印功能上的不足,写了这个项目,例如Windows可以使用Adobe Acrobat DC来打印小册子,而linux没有这样的软件;甚至大多数软件不能双面打印,需要手动选择先逆页序打印偶数页,再打印奇数页。

项目生成PDF文件然后再手动打印。以后可能加入直接打印的方式。

目前已经开发完成的功能有:

双面打印
小册子打印

下面介绍一下思路: 双面打印的思路比较简单,只需要先把偶数页逆序提取出来,然后再提取奇数页,最后手动进行打印就可以了

小册子打印的思路比较复杂,其中的难点主要在于拼合PDF。几乎是直接引用了一个日本网站上的代码,然后修改了一下。

小册子打印需要解决另外一个问题就是页码的排布问题。观察一本钉好的小册子,可以发现,以16页的小册子为例,它的页码排布分别是,16,1,2,15, 14, 3, 4, 13 …..。这也就是说,第一页是最后一页,然后从头取两页,从后面取两页。

然后我们把它分成奇数页面和偶数页面,可以得到它的规律。详细的规律见代码。

然后写一下图形界面工具。这是用AI写的,把代码搬过来,然后改一下bug,再把自己的功能填上去就可以了。

这个地方还有一个问题,就是用tkinter写的代码,在linux的X11上,存在一个极大概率触发的问题,就是文件拖拽上去之后会直接闪退,并且报如下的错误:

X Error of failed request:  BadWindow (invalid Window parameter)
  Major opcode of failed request:  20 (X_GetProperty)
  Resource id in failed request:  0x1417efd
  Serial number of failed request:  1225
  Current serial number in output stream:  1225

排查了很长时间,我觉得这个问题不是我的问题,可能是因为AI写的代码里面用了tkinterdnd2库的问题。

这个问题在wayland上不会出现。尽管如此,这个问题仍然难以接受,所以使用AI工具,把这个界面用pyside重写,然后就得到了pyside版的gui。也在项目里面。

希望这么个项目可以对linux的桌面应用有一点微薄的帮助。以后如果有时间的话,可能再考虑尝试一下开发预览功能,以及直接打印而不是生成PDF的功能。

KDE Plasma面板经常崩溃,然而killall plasmashell不管用。网上没有找到解决办法。

经实测,发现killall -9 plasmashell就行了。

这就类似windows explorer重启。

还有其他意想不到的效果:

本来KDE wayland和x11的鼠标指针大小不是同一个标准,如果电脑屏幕有150%缩放,则wayland的24号鼠标指针等于x11的36号。在x11里面修改鼠标指针大小只会对新打开的应用生效,然而桌面不会,很难受。现在我们知道了,包括桌面和panel全部都是plasmashell这个进程,我们把他杀掉他就会自动重启,鼠标指针大小正常了。

闲着没事的阿玮提出了这么个问题:在一副扑克牌中任抽4张,算24点,有解的概率是多少。

(注:不允许使用乘方、开根、求导、阶乘等计算。)

于是开始研究:

  1. 计算任意一组数一个的二十四点表达式的算法解决:

上bing搜索,copilot太热情了,直接给出了算法。这个算法的原理就是枚举四个数之间所有可能的数学四则运算表达式,然后一个个算。测试代码发现可用。既然可以使用,那么具体原理不深究了。代码如下(cal24.py):

from itertools import permutations, product

def calculate(a, b, op):
    if op == '+':
        return a + b
    elif op == '-':
        return a - b
    elif op == '*':
        return a * b
    elif op == '/':
        if b != 0:
            return a / b
        else:
            return None

def solve_24(nums):
    operators = ['+', '-', '*', '/']
    for num_perm in permutations(nums):
        for ops in product(operators, repeat=3):
            # Try different parenthesis combinations
            expressions = [
                f"(({num_perm[0]} {ops[0]} {num_perm[1]}) {ops[1]} {num_perm[2]}) {ops[2]} {num_perm[3]}",
                f"({num_perm[0]} {ops[0]} ({num_perm[1]} {ops[1]} {num_perm[2]})) {ops[2]} {num_perm[3]}",
                f"{num_perm[0]} {ops[0]} (({num_perm[1]} {ops[1]} {num_perm[2]}) {ops[2]} {num_perm[3]})",
                f"{num_perm[0]} {ops[0]} ({num_perm[1]} {ops[1]} ({num_perm[2]} {ops[2]} {num_perm[3]}))",
                f"(({num_perm[0]} {ops[0]} {num_perm[1]}) {ops[1]} ({num_perm[2]} {ops[2]} {num_perm[3]}))"
            ]
            for expr in expressions:
                try:
                    if abs(eval(expr) - 24) < 1e-6:
                        return expr
                except ZeroDivisionError:
                    continue
    return 0

# 示例使用
nums = [8, 12, 6, 6]
solution = solve_24(nums)
print(solution)
  1. 一副52张的扑克里面抽牌,抽到一个特定组合的概率是多少:

分析这个问题可以发现,所有的组合可以分类如下:

AAAA型, AAAB型, AABB型, AABC型, ABCD型。

没有其他可能的分布。

计算这5种组合中,特定一种组合出现的概率。

总抽法是comb(52, 4)。(注:comb是qalculate写法,表示组合数; perm是qalculate写法, 表示permutation, 排列数)

AAAA型:1 / comb(52, 4)

AAAB型:考虑花色。A牌是在四个花色中选了3个,B牌是在四个花色里面选了一个。全部考虑顺序。则概率为4/52 * 3/51 * 2/50 * 4/49 * perm(4, 4)。

AABB型:同理。4/52 * 3/51 * 4/50 * 3/49 * perm(4, 4)

AABC型:同理。4/52 * 3/51 * 4/50 * 4/49 * perm(4, 4)

ABCD型:同理。4/52 * 4/51 * 4/50 * 4/49 * perm(4, 4)

具体的数据见代码。

把所有情况列出,所有概率加和,发现略小于1。正常现象,因为python浮点数存在误差。

代码如下(stat.py):

import cal24
pa = 0
for f in range(1, 14):
    for i in range(1, f+1):
        for j in range(1, i+1):
            for k in range(1, j+1):
                if f != i and i != j and j != k:
                    # case1: abcd
                    # p = 2816/4165
                    p = 24*32/812175
                elif f == i and i != j and j != k or f != i and i == j and j != k or f != i and i != j and j == k:
                    # case2: aabc
                    # p = 2112/20825
                    p = 12*8/270725
                elif f == i and i == j and j != k or f != i and i == j and j == k or f == i and i != j and j == k:
                    # case3: aaab
                    # p = 96/20825
                    p = 4*4/270725
                elif f == i and i != j and j == k:
                    # case4: aabb
                    # p = 216/20825
                    p = 6*6/270725
                elif f == i and i == j and j == k:
                    # case5: aaaa
                    # p = 1/20825
                    p = 1/270725
                expr = cal24.solve_24([f, i, j, k])
                print(f, i, j, k, expr)
                if expr:
                    pa += p


print(pa)

执行python stat.py > 24.txt可以得到:

1 1 1 1 0
2 1 1 1 0
2 2 1 1 0
2 2 2 1 0
2 2 2 2 0
3 1 1 1 0
3 2 1 1 0
3 2 2 1 0
3 2 2 2 (3 * (2 + 2)) * 2
3 3 1 1 0
3 3 2 1 (3 * (3 + 1)) * 2
3 3 2 2 ((3 + 3) * (2 + 2))
3 3 3 1 ((3 + 3) * (3 + 1))
3 3 3 2 (3 + (3 * 3)) * 2
3 3 3 3 ((3 * 3) * 3) - 3
4 1 1 1 0
4 2 1 1 0
4 2 2 1 4 * (2 * (2 + 1))
4 2 2 2 ((4 + 2) * (2 + 2))
4 3 1 1 4 * (3 * (1 + 1))
4 3 2 1 4 * ((3 + 2) + 1)
4 3 2 2 ((4 + 2) + 2) * 3
4 3 3 1 (4 * (3 + 3)) * 1
4 3 3 2 0
4 3 3 3 ((4 + 3) * 3) + 3
4 4 1 1 4 * ((4 + 1) + 1)
4 4 2 1 ((4 + 4) * (2 + 1))
4 4 2 2 (4 + (4 * 2)) * 2

……

13 13 11 4 0
13 13 11 5 0
13 13 11 6 0
13 13 11 7 0
13 13 11 8 0
13 13 11 9 ((13 + 13) - 11) + 9
13 13 11 10 0
13 13 11 11 0
13 13 12 1 ((13 / 13) + 1) * 12
13 13 12 2 ((13 - 13) + 12) * 2
13 13 12 3 12 * (3 - (13 / 13))
13 13 12 4 0
13 13 12 5 0
13 13 12 6 13 + (13 - (12 / 6))
13 13 12 7 0
13 13 12 8 0
13 13 12 9 0
13 13 12 10 ((13 + 13) - 12) + 10
13 13 12 11 13 + ((13 - 12) * 11)
13 13 12 12 ((13 - 13) + 12) + 12
13 13 13 1 0
13 13 13 2 (13 - (13 / 13)) * 2
13 13 13 3 0
13 13 13 4 0
13 13 13 5 0
13 13 13 6 0
13 13 13 7 0
13 13 13 8 0
13 13 13 9 0
13 13 13 10 (13 + (13 / 13)) + 10
13 13 13 11 ((13 + 13) - 13) + 11
13 13 13 12 ((13 + 13) / 13) * 12
13 13 13 13 0
0.8012448056145496

可以看到,有解的概率大概是80%多点。

后来发现,可以参考知乎这篇回答
他计算的结果和我相近。

房里被装了监控,有一两年了,一直想想办法干扰,现在做了一个完美的解决方案,供参考。

本来希望通过DNS来阻断,但是DNS有缓存之类的东西,而且猾伪路由器篡改我的DNS,而且我在Adguard Home里面也没有找到有关小米API的DNS解析记录,所以放弃。没想到最后采用的方案效果比之前还要好。

买一个树莓派全天候运行,装好firefox, geckodriver, python的selenium, 使用screen -S ban -s ban后台运行ban脚本,把脚本放在PATH下面。

原理是自动模拟人登录路由器后台手动禁止联网,过一段时间模拟人手动允许他联网。
我一开始绝对不会想到这么离谱的想法最后会采用而且效果这么好。

ban.py


#!/bin/python3

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC  # 和下面WebDriverWait一起用的
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.firefox.service import Service
from time import sleep
from random import randint
from threading import Thread

def ban(driver, sec):
    url = "http://192.168.6.6"
    
    
    driver.get(url)

    wait = WebDriverWait(driver, 20)
    # wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'inputArea paddingleft_14')))
    wait.until(EC.presence_of_element_located((By.ID, 'userpassword_ctrl')))

    pw_input = driver.find_element(By.ID, 'userpassword_ctrl')
    login_btn = driver.find_element(By.ID, 'loginbtn')
    pw_input.send_keys("freedom")
    sleep(0.6)
    login_btn.click()
    #sleep(6)
    sleep(0.6)

    #term_mng = driver.find_element(By.CLASS_NAME, 'nav_title')
    #term_mng = driver.find_element(By.CLASS_NAME, 'small_icon want_devicecontrol')
    #wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'small_icon want_devicecontrol')))
    #term_mng = driver.find_element(By.CLASS_NAME, 'small_icon want_devicecontrol')
    #term_mng.click()
    sleep(0.6)

    driver.get("http://192.168.6.6/html/index.html#/devicecontrol")
    actionchains = ActionChains(driver)

    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')))
    #btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')
    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')))
    # btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')
    #btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__blackon')
    wait.until(EC.presence_of_element_located((By.XPATH, "//*[text()='MAC 地址: 不给你看']/../../../../div[2]/div[1]/div[1]")))
    btn = driver.find_element(By.XPATH, "//*[text()='MAC 地址:不给你看']/../../../../div[2]/div[1]/div[1]")
    btn.click()

    sleep(sec)

    div = driver.find_element(By.ID, 'device_selectlist_parenselect')
    actionchains.click(div).perform()

    wait.until(EC.presence_of_element_located((By.ID, 'disallow_selectlist_SmallSelectBoxScrollItemID')))
    disal = driver.find_element(By.ID, 'disallow_selectlist_SmallSelectBoxScrollItemID')
    actionchains.click(disal).perform()

    # actionchains.move_to_element(div).click().perform()

    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_29__blackoff')))

    # btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_29__blackoff')
    # btn.click()

    wait.until(EC.presence_of_element_located((By.XPATH, "//*[text()='MAC 地址:不给你看']/../../../../div[2]/div[1]/div[1]")))
    btn = driver.find_element(By.XPATH, "//*[text()='MAC 地址: 不给你看']/../../../../div[2]/div[1]/div[1]")
    btn.click()

    driver.get("about:blank")
    driver.refresh()



    #print(driver.page_source)



if __name__ == '__main__':
    while True:
        print("drv init", flush=True)
        opt = webdriver.FirefoxOptions()
        # 设置无界面
        opt.add_argument("--headless")


        service = Service(log_path='/dev/null')

        # 禁用 gpu
        # opt.add_argument('--disable-gpu')

        driver = webdriver.Firefox(options=opt, service=service)
        for i in range(64):
            print("ban", s:=randint(8, 16), flush=True)
            ban(driver, s)
            print("wait", s:=randint(16, 48), flush=True)
            sleep(s)
        driver.quit()

踩到的坑稍微做一下总结:

service对象不能再driver对象退出以后重复使用,也就是service = S……这句不能写在while True外面。否则报错。很奇怪的坑,网上没有解决方案,最后是我自己改对的。

对于要点击的对象一定要wait.until等他加载出来。

路由器里面设备的序号是会变的,正如我注释掉的代码,所以最好的方式使用MAC地址来定位。通过XPATH寻找与定位MAC文本同框的按钮,非常稳定。

driver如果每次都新实例化比较慢和占CPU, 如果每次只用同一个driver会内存泄漏,所以折中,每64次ban,关掉浏览器重新实例化一个driver.

总结

每隔几十秒断网十几秒,实测监控不会显示断线,APP上面只能看到卡顿转圈圈,卡很久能进去,看了十几秒画面又不动了。

这绝不是阿Q式的或者扎纸小人式的自我安慰,而是我想要搞你 想要恶心一下你就有能力做到恶心一下你,你想偷窥我的生活就给你看,岂有此理,没那么容易!而且这个东西的应用也在客观上确实降低了他们看的频率,毕竟我实测感觉还是非常卡的。

另外这一次也算是点亮了一点浏览器自动控制的科技树,当初云原神每天只送15分钟的时候我都是手动登录,如果有这东西那么也许不会那么麻烦,不过可惜现在云原神免费了,没用了

更新

通过故障分析,情况判断,我的程序得到了一个很好的鲁棒性提升。

有的时候程序会找不到那个小米摄像机,那是因为等待的时间太短了,此时所有的2.4G联网设备全部离线,所以导致浏览器默认进入分类是5G设备,等再久也等不到摄像机重新连上。

还有别的概率bug不可复现。随他去吧

以下是更新之后的代码

#!/bin/python3

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC  # 和下面WebDriverWait一起用的
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.firefox.service import Service
from selenium.common.exceptions import TimeoutException
from time import sleep, strftime, localtime
from random import randint, choice



def ban(driver, sec):
    url = "http://192.168.6.6"
    
    driver.get(url)

    # wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'inputArea paddingleft_14')))
    wait.until(EC.presence_of_element_located((By.ID, 'userpassword_ctrl')))
    pw_input = driver.find_element(By.ID, 'userpassword_ctrl')
    wait.until(EC.presence_of_element_located((By.ID, 'loginbtn')))
    login_btn = driver.find_element(By.ID, 'loginbtn')
    pw_input.send_keys("freedom")
    sleep(0.06)
    login_btn.click()
    #sleep(6)
    #sleep(1)
    sleep(0.06)
    wait.until(EC.presence_of_element_located((By.ID, "devicecontrol")))

    #term_mng = driver.find_element(By.CLASS_NAME, 'nav_title')
    #term_mng = driver.find_element(By.CLASS_NAME, 'small_icon want_devicecontrol')
    #wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'small_icon want_devicecontrol')))
    #term_mng = driver.find_element(By.CLASS_NAME, 'small_icon want_devicecontrol')
    #term_mng.click()
    #sleep(0.6)

    driver.get("http://192.168.6.6/html/index.html#/devicecontrol")
    actionchains = ActionChains(driver)

    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')))
    #btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')
    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')))
    # btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__qoson')
    #btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_1__blackon')

    # check if 2.4G section
    while True:
        wait.until(EC.presence_of_element_located((By.ID, "device_selectlist_parenselect")))
        div = driver.find_element(By.ID, 'device_selectlist_parenselect')
        if div.text == "2.4G 联网设备":
            # print(div.text)
            break
        else:
            print("LOCATED" + div.text)
            sleep(1)
            driver.refresh()
            sleep(0.2)



    try:
        wait.until(EC.presence_of_element_located((By.XPATH, "//*[text()='MAC 地址: 94:F8:27:78:20:A1']/../../../../div[2]/div[1]/div[1]")))
        btn = driver.find_element(By.XPATH, "//*[text()='MAC 地址: 94:F8:27:78:20:A1']/../../../../div[2]/div[1]/div[1]")
        btn.click()
    except TimeoutException:
        time_str = strftime("%Y-%m-%d--%H-%M-%S", localtime())
        print(time_str, "START BAN FAILED")
        with open(time_str + "start_failed.log", 'a') as f:
            f.write(time_str + '\n' + driver.page_source + "\nSTART BAN FAILED")


    sleep(sec - 1.6)

    wait.until(EC.presence_of_element_located((By.ID, 'device_selectlist_parenselect')))
    div = driver.find_element(By.ID, 'device_selectlist_parenselect')
    actionchains.click(div).perform()

    wait.until(EC.presence_of_element_located((By.ID, 'disallow_selectlist_SmallSelectBoxScrollItemID')))
    disal = driver.find_element(By.ID, 'disallow_selectlist_SmallSelectBoxScrollItemID')
    actionchains.click(disal).perform()

    # actionchains.move_to_element(div).click().perform()

    #wait.until(EC.presence_of_element_located((By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_29__blackoff')))

    # btn = driver.find_element(By.ID, 'InternetGatewayDevice_LANDevice_1_Hosts_Host_29__blackoff')
    # btn.click()

    try:
        wait.until(EC.presence_of_element_located((By.XPATH, "//*[text()='MAC 地址: 94:F8:27:78:20:A1']/../../../../div[2]/div[1]/div[1]")))
        btn = driver.find_element(By.XPATH, "//*[text()='MAC 地址: 94:F8:27:78:20:A1']/../../../../div[2]/div[1]/div[1]")
        btn.click()
    except TimeoutException:
        time_str = strftime("%Y-%m-%d--%H-%M-%S", localtime())
        print(time_str, "END BAN FAILED")
        with open(time_str + "end_failed.log", 'a') as f:
            f.write(time_str + '\n' + driver.page_source + "\nEND BAN FAILED")

    driver.get("about:blank")
    sleep(0.1)
    driver.refresh()



    #print(driver.page_source)



if __name__ == '__main__':
    while True:
        print("drv init", flush=True)
        opt = webdriver.FirefoxOptions()
        # 设置无界面
        opt.add_argument("--headless")
        # opt.page_load_strategy = 'none'

        # firefox_profile = webdriver.FirefoxProfile()
        # firefox_profile.set_preference('permissions.default.image', 2)#某些firefox只需要这个
        # firefox_profile.set_preference('permissions.default.stylesheet', 2)


        service = Service(log_path='/dev/null')

        # 禁用 gpu
        # opt.add_argument('--disable-gpu')

        driver = webdriver.Firefox(options=opt, service=service)
        wait = WebDriverWait(driver, 10)
        waittimelist = list(range(8, 10)) * 4 + list(range(8, 17))
        for i in range(50):
            time_str = strftime("%Y-%m-%d--%H-%M-%S", localtime())
            print(time_str)
            print(i, "ban", s:=randint(9, 16), flush=True)
            ban(driver, s)
            print(i, "wait", s:=choice(waittimelist), flush=True)
            sleep(s)
        driver.quit()

使用flask和简单的html写了一个简单的信息课聊天室

from flask import Flask, request

app = Flask(__name__)

msglist = []

@app.route("/", methods=["POST", "GET"])
def root():
    global msglist
    if request.method == "GET":
        ip = request.remote_addr
        reqnum = ip[ip.rfind(".")+1:]
        user = request.values.get("user")
        message = request.values.get("message")
        print(reqnum, user, message)
        if (user and message) and (not msglist or msglist[0] != (reqnum, user, message)):
            msglist.insert(0, (reqnum, user, message))
    

    with open("chat.html", 'r') as f:
        text = ""
        name = ""
        for fornum, user, message in msglist:
            text += "<p>%s,%s: %s</p>" % (fornum, user, message)
            if not name and reqnum == fornum:
                name = user
        # 防止老师直接偷看
        if ip == "10.10.50.49":
            return ""
        return f.read() % (name, text)

app.run(host="0.0.0.0", port=8080, debug=False)
<!DOCTYPE html>
<html>
    <title>
        聊天室
    </title>
    <body>
        <h2>聊天室!</h2>
        <p>输入完成之后点击send发送</p>
        <p>如果不点击页面上的刷新按钮是不能看到新的信息的</p>
        <p>不要手动使用浏览器的刷新</p>
        <div>
            <button type="button" onclick="javascript:location.replace('/')">刷新</button>
            <form action="/" method="get">
                <div>
                    <label for="name">name:</label>
                    <input type="text" name="user" id="user" value="%s" required /> 
                </div>
                <div>
                    <label for="message">message:</label>
                    <input type="text" name="message" id="message" required />
                </div>
                <div>
                    <input type="submit" value="send" />
                </div>
            </form>
        </div>
        <p></p><p></p>
        <div>
            %s
        </div>

    </body>
</html>

自制云原神

关于这个的想法我在很久之前就产生了,然而当时我下载的sunshine是在第三方渠道下载的,因为神秘力量导致我不能访问github。于是我多点触控的配置失败了。今天我重新下载安装sunshine,成功多点触控玩原神,在此记录。

需要软件、文件:

  1. sunshine windows >= 0.20

  2. usbmmidd_v2(虚拟显示器驱动)(如果使用HDMI转VGA的线空接来欺骗显卡,分辨率没有奇奇怪怪的选项)

  3. moonlight Android 阿西西修改版

安装所有软件。

配置sunshine输入显示屏路径。(\,\DISPLAY5)音频的配置非必须。

平板不做副屏而是玩游戏时,电脑音频改为Steam Streaming Speakers。

虚拟显示器驱动要

deviceinstaller64 install usbmmidd.inf usbmmidd
deviceinstaller64 enableidd 1

参见WIN版虚拟显示器usbmmidd_v2 - 小脚丫走天下 - 博客园

moonlight连接windows成功。

云原神启动参数 yuanshen.exe use_mobile_platform -is_cloud 1 -platform_type CLOUD_THIRD_PARTY_MOBILE -popupwindow -borderless

记得设置副屏为主显示器否则原神会启动在电脑屏幕,拖过去之后会导致纵横比失调。

愉快玩耍吧。

电车惊魂

那是一个美好的一天的早晨,我怀着轻松的心情去上班。那天天气晴的恰到好处,被淡淡的云遮挡掉一部分的太阳光线让我觉得很舒服。

“又是美好的一天。”我心想。

我像往常一样走入电车,今天电车里人反常得很多,不对,不如说这段日子里面电车里人都反常得很多。座位几乎坐满了。

过了几站上来一个十五六岁的中学生。他走进来的时候时不时看看手表,我知道了他好像快要迟到了。没有地方坐下,他就站着,拉着电车中央的那根竖铁杆。

滴滴的警铃声响起,门关上,列车就启动。我马上感觉到加速度压在我身上,重力像是该换了方向。于是我看见那中学生一个趔趄,他站不稳了,马上用手抓住栏杆,另一只手里提着的包向后甩去。

加速完成了,他得到了一片刻的休息机会。他手扶着栏杆站稳,像是没力气没睡醒似的揉眼睛,这时我才注意到他的眼睛上重重的黑眼圈。

过了几站车外面开始风雨大作起来,本来阳光在建筑物的缝隙之间洒下来,有间隔地透过车窗玻璃进入我们的电车里面,我们的电车就像是穿梭在时空的隧道里面。现在乌云密布了,外面的乌云遮蔽太阳,外面的风吹得树都要朝侧面倒过去,风连树上的叶子也扯下来。

外面风雨大作起来,我们的电车里面也风雨飘摇。风把我们的电车吹得左摇右晃,开得慢的时候我觉得我们像是坐在船里飘在海上。车轮和轨道摩擦的啸叫与风雨的声音混合在一起很吵闹,把我的耳膜都向里压得难受,并且使我有了不祥的预感。

我们的车厢风雨飘摇起来,他内心好像也风雨大作了。他用手紧紧抓着竖杆,他手上的青筋暴起,脸上露出痛苦的神情,他咬牙切齿把牙龈整个地都露出来,他的脸上汗如雨下。他像是在和被挡在车外面进不来的风雨搏斗着,他一会被甩到左边,一会被甩到右边,他的两个脚快要拧起来了,他花了好一会把自己稳住。

我们一车厢人静静看他的表演。

突然他松开一只手捂住自己的胃部,他把面部的肌肉也要拧起来了,他似乎是胃痛到不能稳定的站立,于是他艰难地蹲下了,他蹲下的时候我看见一张卡片样的东西,从他的裤子口袋里面滚出来。

那张卡片滚到下一个车厢的连接处卡在地面的突起上停住了。

突然他像是想起了什么似的,他用一只手捂住耳朵,然后马上开始念咒语似的大声背诵古诗词“僵卧孤村不自哀,尚思为国戍轮台。夜阑卧听风吹雨,铁马冰河入梦来。僵卧孤村不自哀,尚思为国戍轮台。夜阑卧听风吹雨,铁马冰河入梦来……”背得很大声。我知道这是一首什么风雨大作的古诗。

背完古诗他又“静摩擦力略大于滑动摩擦力,所以我的卡不会回来……”

我不知道他的咒语和当前情况有没有关系,不过事实上他的咒语没有缓解他的痛苦,他脸上的神情还是那么痛苦。

然后我们车厢中的有一个人扑哧的笑了,开始是掩口胡卢而笑。他的样子确实很好笑,我也想着。

笑声一个传染俩,马上变成了哄堂大笑,有的人笑他上学迟到,肯定是昨晚不好好睡觉打游戏;有的人笑他读书读傻了,只知道念古诗咒语了;有的人笑他弱不禁风,连列车的一点点加速度也把握不住;有的人笑他的头发那么长,肯定不好好学习;有的人笑他不带伞,要遭殃;还有两个带行李箱的情侣大学生,笑你们高中不准谈恋爱,笑你们八月中旬就开学,我们九月了赶到学校城市还有三两天玩耍的光景。

我呢?我也跟着他们笑,笑得上气不接下气,我伸出我的右手,一个大拇指指向老天爷,一个食指指向他,还有三四五指弯回来朝向自己。

我也一直这么笑着,上气不接下气,直到我的站点要到了,于是我收起笑容和东西站起来,在我离开座位的一瞬间,我就被巨大的加速度甩到了车厢后面,头重重地撞在后面车厢的竖杆上。

于是我残存的意识听到新的一轮哄堂大笑开始了,指向我。

这个问题的发现和解决是有将近一年了,当时我在开发一个背单词的一个小项目,但是我在想打包的时候出现问题了。

我想更换窗口的图标,就是windows下左上角的那个小图标,但是tkinter给的方法的参数却是一个文件名,这意味着我不能完美地把这个项目打包成一个单文件,而是需要把这个icon文件和打包好的exe放在一起,变成两个文件发布。

网上所给出的方法一般是先把这个ico文件打包进去,需要的时候在写到cache, 然后再让tkinter用这个文件名来导入。这个方法一看就极其不优雅,虽然性能也就这样了,但是这样的方法是我所不能接受的。

最后,我查看了ttkbootstrap的源码解决了问题。

我想,ttkbootstrap有一个自带的图标,是不管在哪个代码里面执行都会有这个图标,于是我想参考官方的做法。

我当初发现的ttkbootstrap的代码是在哪里我早就已经忘记了,但是我解决问题的思路仍清晰。

之前我看到ttkbootstrap的源码里面有一段base64,然后使用了这段base64来导入,并非网上的先写到cache, 这也符合我对一个成熟的项目的预期。

所以我贴上我解决的代码:

root = tk.Tk()
root.title("Xdict背单词")
photo = ttk.PhotoImage(master=root, data=myIcon.img)
root.iconphoto(True, photo)

这段代码中的data项就是一个base64字符串。

制作myIcon.py需要编写一个mkicon.py.

#!/bin/python3
import base64
with open("icon.py","a") as f:
    f.write("img='")
with open("bell.png","rb") as i:
    b64str = base64.b64encode(i.read())
    with open("icon.py","ab+") as f:
        f.write(b64str)
with open("icon.py","a") as f:
    f.write("'")

这段代码的意思就是读取目录下的bell.png的文件内容并把他转换为base64, 输出到myIcon.py, 这样pyinstaller也可以直接知道要调用。

最后要在main.py里面引入myIcon

写的不太清楚,若不能理解请通过文章“博客介绍”里面的联系方式联系我

我刚开始尝试linux系统的时候是一年前,那时候我装的是linux mint, 碰巧这个系统是基于ubuntu 20.04, 而这个系统出来的时候我的笔记本都没有出来,所以里面好多的问题,比如屏幕缩放的问题、视频硬解的问题,chromium鼠标滚动速度慢的问题、触控板不支持多点触控的问题……

好在我最终没有放弃,用到了现在,然而时间过去了一年,我也亲眼见证了linux系统的好多严重妨碍使用体验的小小bug被修复了,可喜可贺。

让我们一起见证linux变得越来越好!

-1. 关于选linux发行版的一个建议

建议如果选择LTS发行版用作个人linux电脑,则不要选择太老的,比如你像我一样,在2022年7月初装了linuxMint, 然而这个时候LinuxMint只有20.x版,Ubuntu的22.04已经出了,LinuxMint开发有一定的时间延迟,但是我的电脑是2020年的款,所以我阴差阳错的装上了2年前、我的电脑出生之前的distrobution, 所以导致了很多的驱动、软件等问题

建议选择新一点的,最好滚动更新,因为linux作为PC是个还在进步的系统,直接装上大概率是不如Windows的完美体验的,总有点小毛病,比如视频硬解、比如Firefox的触控板和触摸屏支持。过了一段时间这些bug可能会一个一个修复,但是你的发行版是LTS就收不到更新了。好像现在python3.11都出了但是ubuntu 20.04还是最高支持到3.8

1. linux firefox支持触控板和触屏平滑滚动

linux的firefox默认是不支持触控板的平和滚动的,所以滚动起来就是模拟鼠标,然后是一顿一顿的,卡顿不跟手。还有一个问题是他不支持平滑缩放,对于笔记本电脑来说这是一个十分影响使用体验的问题。更离谱的是如果你的笔记本电脑支持触屏,那么你用手指在屏幕上滑动的时候火狐会认为你是用鼠标在屏幕滑动,于是选中了很多文字。好在现在我们可以通过更改配置来实现这两个功能。

引自网页:

使linux版firefox支持触屏操作

一共有两个地方需要修改:

第一个是在about:config中找到dom.w3c_touch_events.enabled项改为1(启用),默认为2(自动)。

第二个地方是修改文件/etc/security/pam_env.conf,在文件最后添加下面代码

MOZ_USE_XINPUT2 DEFAULT=1

修改完成后重启Firefox就可以使用触摸屏进行操作了。支持的操作有拖拽和双指缩放。

此外还发现鼠标滚轮滚送速度有些慢,会经常抽动。通过设置mousewheel.min_line_scroll_amount项为40,设置 general.smoothScroll项为true,设置 general.smoothScroll.pages项为false。

这样设置完重启firefox可能是不够的,可能还要重启系统

如果还不行就下载一个Wayland的桌面,然后你去Wayland里面看看开了没,开了以后应该X11也能用的

装完后好像还是不如windows丝滑

我这么弄能成功纯属瞎折腾瞎猫碰上死耗子

2. Firefox开启浏览器的视频硬件加速

linux作为一台个人电脑的一个很重要的功能是放视频,毕竟如果用CPU软解然后卡顿发热,那么是真的不可接受的

Firefox开启视频硬件加速的方法,你首先要在电脑上装好VA-API的显卡驱动,我只知道intel的。

然后要开启firefox里面的一些选项

推荐直接看这篇文章

https://zhuanlan.zhihu.com/p/268401890

能看懂Archwiki的推荐Archwiki

3. 总结

linux的一些小毛病是很难折腾的,说不定不是我的问题干脆就是软件bug, 比如我最近在KDE看到我的CPU总是有一个核心100%占用,但是我下载了gnome的系统monitor, 又用htop发现全正常。所以折腾很久是很正常的,但是linux是在进步的!我非常高兴看到进步。