blog/_posts/2022-09-05-trayicon.md

149 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
layout: post
title: 制作程序运行提示标志的历程
tags: [Python, 程序, 标志]
---
有图形界面的程序可真是难做啊……<!--more-->
# 起因
最近我做了一个程序类似于守护进程那样的一个用Python制作的脚本。脚本做出来很简单可是我做出来之后需要向其他人证明我做的脚本正在运行而且是给一个不懂电脑的人知道。在这个前提下我不可能让其他人去看任务管理器、或者执行`ps -ef | grep xxx`这种东西吧。所以还是得让它在运行的时候在桌面这样的图形界面显示一些东西才行。
# 制作的历程
## 使用托盘区图标
像一般的后台程序证明自己存在的方式就是任务栏的托盘区显示一个图标。于是首先我就按照这个想法先用PyQt5然后在网上找了一段代码然后自己改了改
### 使用PyQt5库实现
```python
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon("pic.ico"), w)
trayIcon.show()
Thread(target=some_random_function).start() # 我的脚本函数
sys.exit(app.exec_())
```
这样做完之后东西确实可以运行了不过我的代码还要在其他人的电脑上运行所以我先用Pyinstaller打包了一下不过打包出来的程序很大就几十行代码就算加上Python解析器最多也就不到10MiB结果这加上PyQt库之后直接上升到40MiB左右就算用upx压缩完也有30多MiB实在是让人无法忍受于是我就考虑看看能不能用其他方式来制作这个图标。
后来我在网上找到有一个叫做pystray的库似乎是专门干这个活的。那既然是专门干这个的那大小肯定比什么Qt库要小得多吧于是我按照示例的代码随便写了一段
### 使用pystray库实现
```python
from threading import Thread
import pystray
from PIL import Image
image = Image.open("pic.png")
icon = pystray.Icon(name="SomeRandom", icon=image, title="SomeRandom", menu=None)
Thread(target=some_random_function).start() # 我的脚本函数
icon.run()
```
这个代码我没有测试过不过在我写完之后首先用Pyinstaller打包了一下结果大小比用PyQt5还要大😂打包完大小要60多MiB所以没办法我就只能继续用Qt的那个版本了……
## 制作悬浮图标
后来我的脚本由于应用面广泛需要在Ubuntu上使用最新的Ubuntu使用的GNOME桌面[不再支持托盘区](https://blogs.gnome.org/aday/2017/08/31/status-icons-and-gnome/)了😓,所以没办法,只能想别的办法了。
现在的程序除了托盘区图标证明自己的存在可能还有就是悬浮球了吧国产很多软件喜欢把自己程序整成悬浮球那样放在桌面上吸引用户的注意力所以我的程序也要这样搞。我想了想用PyQt库实在是太重了我想整个轻量的图形引擎像Python自带的Tkinter就挺不错的所以我首先用Tkinter做了一个版本出来
### 通过Tkinter实现
```python
import tkinter
from threading import Thread
root = tkinter.Tk()
height = 100
width = 100
root.overrideredirect(True)
root.attributes('-transparentcolor', "white")
root.attributes("-alpha", 0.9) # 窗口透明度10 %
root.attributes("-topmost", 1)
root.geometry(f"{height}x{width}-40+60")
canvas = tkinter.Canvas(root, height=height, width=width, bg="white")
canvas.config(highlightthickness=0)
image_file = tkinter.PhotoImage(file=r'pic.png')
image = canvas.create_image(
height//2, width//2, anchor=tkinter.CENTER, image=image_file)
canvas.pack()
x, y = 0, 0
show_menu = tkinter.Menu(root, tearoff=0)
show_menu.add_command(label="SomeRandom")
def move(event):
global x, y
new_x = (event.x-x)+root.winfo_x()-height//2
new_y = (event.y-y)+root.winfo_y()-width//2
s = f"{height}x{width}+" + str(new_x)+"+" + str(new_y)
root.geometry(s)
canvas.bind("<B1-Motion>", move)
canvas.bind("<Enter>", lambda event: show_menu.post(event.x_root, event.y_root))
canvas.bind("<Leave>", lambda e: show_menu.unpost())
Thread(target=some_random_function).start() # 我的脚本函数
root.mainloop()
```
这个代码在Windows上工作还算可以问题不是很多但是在Linux上就出现了很糟糕的问题根据tcl/tk [documentation](https://wiki.tcl-lang.org/page/wm+attributes) ~~好像也没写🤣~~ ,“-transparentcolor”属性只能在Windows等系统使用貌似MacOS也能用因此在Linux中会报错。如果不能使用透明背景效果就会很差我看Stack Overflow上有人说可以安装一个pqiv图片查看器然后使用`os.popen()`或者`subprocess.Popen()`执行`pqiv -c -c -i pic.png`也能达到类似的效果不过这种东西先不说还要安装而且这个东西点两下就能看见它的窗口还能关闭那肯定是不符合我们的要求的。所以没办法……只能再考虑Qt的办法了。
### 使用PyQt5库实现
我在网上又找了些资料把PyQt的版本也做出来了而且还加了支持Gif动态图片的效果
```python
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def mouseMoveEvent(self, e: QMouseEvent): # 重写移动事件
if self._tracking:
self._endPos = e.pos() - self._startPos
self.move(self.pos() + self._endPos)
def mousePressEvent(self, e: QMouseEvent):
if e.button() == Qt.LeftButton:
self._startPos = QPoint(e.x(), e.y())
self._tracking = True
def mouseReleaseEvent(self, e: QMouseEvent):
if e.button() == Qt.LeftButton:
self._tracking = False
self._startPos = None
self._endPos = None
def initUI(self):
layout = QStackedLayout()
self.lbl1 = QLabel(self)
self.movie = QMovie("pic.gif")
self.lbl1.setMovie(self.movie)
self.movie.start()
self.lbl1.setToolTip("SomeRandom")
layout.addWidget(self.lbl1)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint | Qt.Tool)
#layout area for widgets
layout.setCurrentIndex(1)
self.setLayout(layout)
self.setGeometry(QApplication.desktop().width()-130,50,100,100)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
Thread(target=some_random_function).start() # 我的脚本函数
ex = Example()
sys.exit(app.exec_())
```
最终做出来效果还不错说不定加点功能放组简单的立绘动画就能做一个像我博客左下角的看板娘一样的东西呢🤣虽然我也见过用Electron写的[PPet](https://github.com/zenghongtu/PPet)不过用Python写的话可能对更多人更友好吧 ~~Gif哪能和Live2D比😂~~
# 感想
这次做这么一个Python程序运行的提示标志还真是复杂啊尤其是为了跨平台其实专门对应一个平台做起来可能也没有很复杂不过想能在各个平台上都能使用还是挺难的。这次来看Qt的跨平台性确实很强无论是在哪个平台上都能获得不错的体验就是用起来感觉比较麻烦其实说来如果能用C++之类的语言去开发Qt程序应该更好Python这个基本上也就只能当作一个玩具算是熟悉一下Qt的各种功能了。