class IPtray(object): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # self.Lock=threading.RLock() self.ip = "0.0.0.0" self.app = QApplication(sys.argv) self.wMain = QWidget() self.tray = QSystemTrayIcon(self.wMain) def Ui(self): self.sub_getIP() self.actExit = QAction("退出") self.actExit.triggered.connect(self.forceEx) self.tray.setIcon(QIcon(".\icon\ip.png")) self.tray.setVisible(True) self.trayMenu = QMenu() self.trayMenu.addAction(self.actExit) self.tray.setContextMenu(self.trayMenu) # print("showing message") self.tray.showMessage("IP", "你的IP是" + self.ip) self.sub_setToolTip() #getIP运行比较慢, setToolTip放在后面. sys.exit(self.app.exec_()) def forceEx(self): sys.exit(self.app.exec_()) exit() def getIP(self): # self.Lock.acquire() while 1: try: s = socket(AF_INET, SOCK_DGRAM) s.connect(("114.114.114.114", 80)) self.ip = s.getsockname()[0] finally: s.close() # print("exit getIP") sleep(5) # self.Lock.release() def setToolTip(self): while 1: self.tray.setToolTip(self.ip) # print("exit setToopTip") sleep(5) def sub_getIP(self): self.subThread_getIP = Thread(target=self.getIP, daemon=True) self.subThread_getIP.setName("sub_getIP") # self.subThread_getIP.setDaemon = True # 设置为后台线程 self.subThread_getIP.start() # 这个线程要一直运行 def sub_setToolTip(self): self.subThread_setToolTip = Thread(target=self.setToolTip, daemon=True) self.subThread_setToolTip.setName("sub_setToolTip") # self.subThread_setToolTip.setDaemon(True) # 设置为后台线程 self.subThread_setToolTip.start()
def Ui(self): app = QApplication(sys.argv) W = QWidget() #生成动作 act_copy = QAction('复制到剪切板') act_about = QAction("关于: 0.1 FoxZno") act_exit = QAction("退出") act_copy.triggered.connect(self.copy) act_exit.triggered.connect(self.fouceEx) tray = QSystemTrayIcon(W) tray.setToolTip(self.ip) tray.setIcon(QIcon('.\icon\ip.png')) tray.setVisible(True) trayMenu = QMenu() #添加动作 trayMenu.addAction(act_copy) trayMenu.addAction(act_about) trayMenu.addAction(act_exit) tray.setContextMenu(trayMenu) sys.exit(app.exec_())
color = dialog.currentColor() clipboard.setText("rgb(%d, %d, %d)" % (color.red(), color.green(), color.blue())) def copy_color_hsv(): if dialog.exec_(): color = dialog.currentColor() clipboard.setText("hsv(%d, %d, %d)" % (color.hue(), color.saturation(), color.value())) # Create the tray tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) # Create the menu menu = QMenu() action1 = QAction("Hex") action1.triggered.connect(copy_color_hex) menu.addAction(action1) action2 = QAction("RGB") action2.triggered.connect(copy_color_rgb) menu.addAction(action2) action3 = QAction("HSV") action3.triggered.connect(copy_color_hsv) menu.addAction(action3)
class Indicator(): APP_NAME = 'FortiVPN Quick Tray' def __init__(self): self.app = QApplication([]) self.app.setQuitOnLastWindowClosed(False) self.indicator = QSystemTrayIcon() self.indicator.setIcon(QIcon(self._get_file('./icons/icon.png'))) self.indicator.setContextMenu(self._build_menu()) self.indicator.setVisible(True) self.indicator.setToolTip('OFF') self.indicator.activated.connect(self._click_indicator) self.logs_dialog = QTextEdit() self.logs_dialog.setWindowTitle(f'{self.APP_NAME} - Logs') self.logs_dialog.setFixedSize(440, 440) self.logs_dialog.setReadOnly(True) self.logs_dialog.setWindowIcon( QIcon(self._get_file('./icons/icon.png'))) self.vpn_config = '/etc/openfortivpn/config' self.vpn_process = None self.vpn_logs_file = NamedTemporaryFile(delete=False) self.vpn_thread = VPNThread(self.vpn_logs_file) self.vpn_thread.status.connect(self._update_vpn_status) self.vpn_thread.log.connect(self.logs_dialog.append) self.app_update_thread = AppUpdateThread(self._get_file('./version')) self.app_update_thread.update_available.connect( self._show_update_notification) self.app_update_thread.start() def run(self): self.app.exec_() sys.exit() def _build_menu(self): menu = QMenu() self.connect_action = QAction('Connect') self.disconnect_action = QAction('Disconnect') self.config_action = QAction('Config') self.logs_action = QAction('Logs') self.exit_action = QAction('Exit') self.disconnect_action.setDisabled(True) self.connect_action.triggered.connect(self._click_connect) self.disconnect_action.triggered.connect(self._click_disconnect) self.config_action.triggered.connect(self._click_config) self.logs_action.triggered.connect(self._click_logs) self.exit_action.triggered.connect(self._click_exit) menu.addAction(self.connect_action) menu.addAction(self.disconnect_action) menu.addSeparator() menu.addAction(self.config_action) menu.addAction(self.logs_action) menu.addSeparator() menu.addAction(self.exit_action) return menu def _click_connect(self): self.indicator.setIcon(QIcon(self._get_file('./icons/try.png'))) self.indicator.setToolTip('TRYING') self.connect_action.setDisabled(True) self.config_action.setDisabled(True) with open(self.vpn_logs_file.name, 'w+b') as f: try: self.vpn_process = Popen(split('pkexec openfortivpn -c ' + self.vpn_config), stdin=PIPE, stdout=f, stderr=f) self.vpn_process.communicate(timeout=1) except TimeoutExpired: pass self.vpn_thread.start() def _click_disconnect(self): try: run(split('pkexec kill ' + str(self.vpn_process.pid))) except ChildProcessError: pass def _click_config(self): config_file, _ = QFileDialog.getOpenFileName( caption='Select config file', dir='/', filter='All files (*)', options=QFileDialog.DontUseNativeDialog) if config_file: self.vpn_config = config_file def _click_logs(self): if not self.vpn_thread.isRunning(): with open(self.vpn_logs_file.name) as logs: self.logs_dialog.setPlainText(logs.read()) self.logs_dialog.show() def _click_exit(self): if self.indicator.toolTip() == 'ON': _ = QMessageBox.warning( None, self.APP_NAME, 'VPN is still ON. Please Disconnect first before exiting') return if self.vpn_logs_file.name: remove_log_file(self.vpn_logs_file.name) self.app.quit() def _get_file(self, file_path): try: base = sys._MEIPASS except Exception: base = path.abspath('.') return path.join(base, file_path) def _click_indicator(self, event): if event == QSystemTrayIcon.ActivationReason.Trigger: self._click_logs() def _update_vpn_status(self, message): if message == 'ERR': self.indicator.setIcon(QIcon(self._get_file('./icons/err.png'))) self.indicator.setToolTip('ERROR') self.connect_action.setDisabled(False) self.config_action.setDisabled(False) pass if message == 'ON': self.indicator.setIcon(QIcon(self._get_file('./icons/on.png'))) self.indicator.setToolTip('ON') self.disconnect_action.setDisabled(False) pass if message == 'OFF': self.indicator.setIcon(QIcon(self._get_file('./icons/icon.png'))) self.indicator.setToolTip('OFF') self.disconnect_action.setDisabled(True) self.connect_action.setDisabled(False) self.config_action.setDisabled(False) pass def _show_update_notification(self, flag): if flag and self.indicator.supportsMessages: self.indicator.showMessage( self.APP_NAME, 'There is a new update available', QIcon(self._get_file('./icons/icon.png')))
def gui(pid, url): from PySide2.QtGui import QIcon, QPixmap from PySide2.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QAction xpm = b"""/* XPM */ static char * ecco_xpm[] = { "64 54 188 2", " c None", ". c #49CF23", "+ c #40CE0E", "@ c #44CE1C", "# c #46CF1D", "$ c #50D030", "% c #40CE0D", "& c #44CF1A", "* c #3FCE08", "= c #3FCE0B", "- c #46CF1F", "; c #42CE12", "> c #49CF24", ", c #45CF1A", "' c #41CE10", ") c #42CE13", "! c #3FCE09", "~ c #40CE0C", "{ c #3FCE0C", "] c #43CF13", "^ c #40CE0F", "/ c #4ED02E", "( c #43CE17", "_ c #4ACF27", ": c #61D00E", "< c #C8DC0A", "[ c #DFE108", "} c #E1E108", "| c #D3DE09", "1 c #8BD40D", "2 c #9BD60C", "3 c #A9D80B", "4 c #7CD20D", "5 c #47CF1D", "6 c #63D00D", "7 c #E8E208", "8 c #F5E507", "9 c #E4E208", "0 c #4BD028", "a c #C9DD0A", "b c #C4DC0A", "c c #C0DB0A", "d c #41CE0E", "e c #93D50C", "f c #CCDD0A", "g c #F9F0A9", "h c #FDFAE7", "i c #FCF8DD", "j c #F7EA72", "k c #44CE19", "l c #6ED10D", "m c #ECE308", "n c #F9F0AB", "o c #FFFFFF", "p c #FFFEFA", "q c #F7EB85", "r c #B0D90B", "s c #72D10D", "t c #FEFCF1", "u c #F6E741", "v c #EDE308", "w c #5AD00E", "x c #43CE16", "y c #53CF0E", "z c #FBF5CC", "A c #4DD02C", "B c #FEFBEE", "C c #E0E108", "D c #F8EE9A", "E c #FBF6D0", "F c #5DD00E", "G c #42CF12", "H c #ACD80B", "I c #FCF6D2", "J c #FEFEF8", "K c #82D30D", "L c #41CE13", "M c #F5E63A", "N c #FEFDF6", "O c #F7EC86", "P c #44CE18", "Q c #A2D70C", "R c #FAF3BC", "S c #F9F0AE", "T c #77D20D", "U c #DCE009", "V c #F7EB80", "W c #A8D80B", "X c #FAF1B5", "Y c #BCDB0A", "Z c #4BCF27", "` c #F1E407", " . c #FBF5CB", ".. c #F7EB7D", "+. c #CDDD0A", "@. c #F7EB82", "#. c #FCF8DC", "$. c #7BD76F", "%. c #FCF8DE", "&. c #F6E862", "*. c #D1EECE", "=. c #E6E208", "-. c #F5E626", ";. c #F6E85B", ">. c #E9E308", ",. c #C9EBC6", "'. c #A5D70C", "). c #ADD80B", "!. c #84D879", "~. c #55D03C", "{. c #C3E9BF", "]. c #64D352", "^. c #68D456", "/. c #D6DF09", "(. c #D0DE09", "_. c #54D03B", ":. c #C8EBC5", "<. c #AFE3AA", "[. c #C9EBC5", "}. c #A2E09B", "|. c #BAE7B5", "1. c #F8FCF7", "2. c #B1E4AB", "3. c #D9F1D6", "4. c #3FCE0A", "5. c #BBDA0B", "6. c #E7E208", "7. c #AFD90B", "8. c #44CE1A", "9. c #C0E8BB", "0. c #90DB87", "a. c #C7EBC4", "b. c #44CF15", "c. c #C4EAC0", "d. c #6AD458", "e. c #D4EFD2", "f. c #40CE0B", "g. c #97DD8F", "h. c #45CF1B", "i. c #A8E1A2", "j. c #99DD91", "k. c #A0DF99", "l. c #C7EBC3", "m. c #6FD55F", "n. c #7CD771", "o. c #C2E9BF", "p. c #B6E5B2", "q. c #66D355", "r. c #71D563", "s. c #7FD872", "t. c #44CF17", "u. c #7CD770", "v. c #BCE7B8", "w. c #62D24F", "x. c #D8F0D6", "y. c #8FDB84", "z. c #A8E1A3", "A. c #D6F0D3", "B. c #54D03C", "C. c #B6E6B1", "D. c #B0E3AB", "E. c #BDE7B9", "F. c #61D24D", "G. c #C8EBC4", "H. c #CEEDCC", "I. c #D8F0D5", "J. c #53D136", "K. c #7DD771", "L. c #70D561", "M. c #DFF3DD", "N. c #7CD76F", "O. c #54D03A", "P. c #4BCF28", "Q. c #C3E9C0", "R. c #CBECC8", "S. c #B9E6B4", "T. c #98DD91", "U. c #B6E5B1", "V. c #DAF1D8", "W. c #42CE14", "X. c #3DCE02", "Y. c #4DD02A", "Z. c #48CF22", "`. c #43CF14", " + c #4AD025", ".+ c #46CF1A", "++ c #48CF20", "@+ c #45CF19", " . + @ ", " # + + + + + ", " + + + + + + + ", " $ + + + + + + + ", " + + + + + + + % ", " + + + + + + + + ", " + + + + + + + ", " & + + + + + + * ", " + + + + + + = = - ; + + + > , ", " % + + + + + + ' + + + + + + + + + + + ", " + + + + + ' ) + + + + + + + + + + + + + + ", " + + + + + + ' + + + + + + + + + + + + + + + + + ", " ! + + + + + ~ * + + + + + + + + + + + + + + + + + + + { ", " ] ^ % + ; + / + + + + + + + + + + + + + + + + + + + + + + + + + + + ( ", " ' _ + + + + + + + + % + + + + + + + + + + : < [ } | 1 + : 2 3 3 4 + + + + + + + 5 ", " + ^ + + + + + + + + + + + + + + + + + + + + + + 6 7 8 8 8 8 8 9 8 8 8 8 8 9 2 + + + + + + ' ", " 0 + + + + + + + + + + + + + + + + + + + + + + + + + a 8 8 8 8 8 8 8 8 8 8 8 8 8 8 b + + + + + + ", " ^ + + + + + + + + + + + + + + + + + + + + + + + + + + + [ 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 c + + + + + % ", " d + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 e + + + + + + ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + f 8 8 8 8 8 8 8 8 g h h i j 8 8 8 9 + + + + + k ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + l m 8 8 8 8 8 8 n o o o o p q 8 8 8 r + + + + + ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + s } 8 8 8 8 8 t o o o o o t u 8 8 v w + + + + x ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + y 9 8 8 8 8 o o o o o o o z 8 8 8 r + + + + + ", " A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + e 8 8 8 8 B o o o o o o o j 8 8 C + + + + + ", " ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 9 8 8 8 D o o o o o o o E 8 8 8 F + + + + G ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + H 8 8 8 8 I o o o o o o J 8 8 8 K + + + + ^ ", " L + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + y v 8 8 8 M N o o o o o o O 8 8 K + + + + P ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Q 8 8 8 8 R o o o o o o S 8 8 T + + + + + ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + U 8 8 8 V o o o o o o z 8 v + + + + + + ", " ~ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + W 8 8 8 8 N o o o o o X 8 Y + + + + + Z ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + y ` 8 8 8 .o o o o o ..} y + + + + + + ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +.8 8 8 @.o o o o #.7 l + + + + + + ) ", " + + + + + + + + $.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + e 8 8 8 8 g p o %.&.K + + + + + + + ", " + + + + + + + + *.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + =.8 8 8 8 -.;.8 >.+ + + + + + + + ", "^ + + + + + + + + ,.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '.8 8 8 8 8 8 8 ).+ + + + + + + = ", "+ + + + + + !.~.+ {.].^.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /.8 8 8 8 8 (.+ + + + + + + + + ", "+ + + + _.:.<.[.}.|.1.2.3.4.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + y 5.7 8 6.7.+ + + + + + + + + ", "8.+ + + 9.].+ + :.{.0.a.,.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + b. ", "4.+ + * c.+ + + d.e.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " + + f.{.+ + + + g.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + h. ", " + + + i.j.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " Z + + + k.l.m.+ + + n.o.,._.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " + + + + _.p.[.i.q.e.r.s.o.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + % ", " t.+ + + + + + u.r.v.+ + w.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " % + + + + + + + x.y.+ + z.A.0.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " + + + + + + + B.C.*.D.E.+ + + F.G.H.I.r.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " J.+ + + + + + + + K.e.L.+ r.M.N.+ O.,.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + P. ", " k + + + + + + + u.Q.R.2.S.+ + T.U.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " + + + + + + + + + p.V.M.i.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", " W.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) ", " X.+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ~ ", " ; - + + + + + + + Y. Z.' + + + + + + + + + + + + + + + + + ", " { `. +~ ^ + + + + + + + + + + ) + ", " .+- + + ++@++ "}; """ app = QApplication([]) app.setQuitOnLastWindowClosed(False) def browse(): webbrowser.open(url) menu = QMenu() action = QAction("Open in browser") action.triggered.connect(browse) def stop(): os.kill(pid, signal.SIGINT) app.quit() menu.addAction(action) quit = QAction("Quit") quit.triggered.connect(stop) menu.addAction(quit) pix = QPixmap() pix.loadFromData(xpm) icon = QIcon(pix) tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) tray.setToolTip("ecco") tray.setContextMenu(menu) app.exec_()
class Window(QWidget): def __init__(self, parent=None): super(Window, self).__init__() self.path = None self.settings() self.create_widgets() self.create_layout() def settings(self): self.resize(300, 120) self.setWindowTitle('Mp3 Downloader') def create_widgets(self): self.edit_url = QLineEdit() self.edit_name = QLineEdit() self.btn_select_path = QPushButton('Select path', self) self.btn_select_path.clicked.connect(self.select_path) self.btn_download = QPushButton('Download mp3', self) self.btn_download.clicked.connect(self.download) def create_layout(self): self.layout = QFormLayout() self.layout.addRow('Nome:', self.edit_name) self.layout.addRow('Url:', self.edit_url) self.layout.addRow('Selecionar destino:', self.btn_select_path) self.layout.addRow(self.btn_download) self.setLayout(self.layout) def select_path(self): self.path = QFileDialog.getExistingDirectory(self, 'Selecionar Pasta') def download(self): if self.verify_fields(): self.manage_interface(False) self.thread_qt() def verify_fields(self): if self.path is None: return False else: strings = [self.edit_url, self.edit_name.text(), self.path] regex_validate = QRegExp('*.mp3') regex_validate.setPatternSyntax(QRegExp.Wildcard) emptys = 0 for string in strings: if len(string.split()) == 0: emptys += 1 if emptys == 0 and regex_validate.exactMatch( self.edit_url.text()): return True def thread_qt(self): url = self.edit_url.text() name = self.edit_name.text() path = self.edit_path.text() self.thre = DownloaderMusic() self.thre.finished.connect(self.downfin) self.thre.start() def manage_interface(self, state): self.btn_download.setEnabled(state) self.edit_name.setEnabled(state) self.edit_url.setEnabled(state) self.btn_select_path def downfin(self): self.notify_icon = QSystemTrayIcon() self.notify_icon.setVisible(True) self.notify_icon.showMessage( 'Download Finalizado', u'O download da sua música foi realizado com sucesso.', QSystemTrayIcon.Information, 3000) self.manage_interface(True)
class UI: """ WSL2 端口自动转发 """ def __init__(self, qt_application=None): self.qt_application = qt_application # 实例化配置管理类 self.settings_manage = SettingsManage() self.__setting = self.settings_manage.get() # 实例化windows命令处理类 self.wsl2 = WinCmd() # 初始化启动脚本 if not isfile(self.wsl2.WSL_VBS_PATH): copyfile(self.wsl2.WSL_VBS_PATH_TEMP, self.wsl2.WSL_VBS_PATH) if not isfile(self.wsl2.WSL_BAT_PATH): self.settings_manage.save_file_content( self.wsl2.WSL_BAT_PATH, self.__setting.get('wsl_bat_content', '')) # 加载UI文件 self.ui = QUiLoader().load(ResourcePath.resource_path('lib/wsl2.ui')) # 设置界面图标 app_icon = QIcon(ResourcePath.resource_path("lib/logo.ico")) self.ui.setWindowIcon(app_icon) # 设置选中状态 self.ui.auto_start_wsl.setChecked( self.__setting.get('auto_start_wsl', False)) self.ui.fire_wall_open.setChecked( self.__setting.get('fire_wall_open', False)) self.ui.fire_wall_close.setChecked( self.__setting.get('fire_wall_close', False)) # 设置文本框的值 self.ui.port_text.appendPlainText('\n'.join( self.__setting.get('ports', []))) self.ui.bat_text.appendPlainText(self.wsl2.get_bat_script()) # 按钮监听 self.ui.get_wsl2_ip.clicked.connect(self.__get_wsl2_ip) self.ui.port_add.clicked.connect(self.__port_add) self.ui.port_del.clicked.connect(self.__port_del) self.ui.port_info.clicked.connect(self.__port_info) self.ui.wsl_l_v.clicked.connect(self.__wsl_l_v) self.ui.port_reset.clicked.connect(self.__port_reset) self.ui.end_wsl.clicked.connect(self.__end_wsl) self.ui.start_wsl.clicked.connect(self.__start_wsl) self.ui.start_wsl_all.clicked.connect(self.start_wsl_all) self.ui.save_settings.clicked.connect(self.__save_settings) self.ui.save_settings_ports.clicked.connect(self.__save_settings) # 设置系统托盘图标的菜单 tp_icon = QIcon(ResourcePath.resource_path("lib/logo.ico")) self.tp = QSystemTrayIcon(self.ui) self.tp.setIcon(tp_icon) self.ui_hide = QAction(icon=tp_icon, text='隐藏(Hide)', triggered=self.ui.hide) self.ui_show = QAction(icon=tp_icon, text='显示(Show)', triggered=self.ui.show) self.ui_exit = QAction(icon=tp_icon, text='退出(Exit)', triggered=self.__quit_app) self.tp_menu = QMenu() self.tp_menu.addAction(self.ui_hide) self.tp_menu.addAction(self.ui_show) self.tp_menu.addAction(self.ui_exit) self.tp.setContextMenu(self.tp_menu) self.tp.activated.connect(self.__tp_connect_action) self.tp.show() self.tp.showMessage('WSL2AutoPortForward', 'WSL2端口自动转发工具已启动', QSystemTrayIcon.MessageIcon.Information) def __tp_connect_action(self, activation_reason): """ 监听托盘图标点击 :param activation_reason: 点击类型 :return: """ if activation_reason == QSystemTrayIcon.ActivationReason.Trigger: # 左单击 if self.ui.isHidden(): self.ui.show() else: self.ui.hide() elif activation_reason == QSystemTrayIcon.ActivationReason.Context: # 右单击 self.tp_menu.show() elif activation_reason == QSystemTrayIcon.ActivationReason.DoubleClick: # 双击 self.ui.show() def __quit_app(self): """ 退出APP :return: """ re = QMessageBox.question(self.ui, "提示", "退出系统", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if re == QMessageBox.Yes: # 关闭窗体程序 self.qt_application.quit() # 在应用程序全部关闭后,TrayIcon其实还不会自动消失, # 直到你的鼠标移动到上面去后,才会消失, # 这是个问题,(如同你terminate一些带TrayIcon的应用程序时出现的状况), # 这种问题的解决我是通过在程序退出前将其setVisible(False)来完成的。 self.tp.setVisible(False) def __wsl_l_v(self): """ 获取wsl信息 :return: """ wsl_l_v_txt = ' ' + self.wsl2.wsl_l_v(exec_run=True).replace( '\x00', '').strip() if not wsl_l_v_txt: # 未查询到wsl信息提示 wsl_l_v_txt = '未查询到wsl信息!' QMessageBox.information(self.ui, '系统提示', wsl_l_v_txt) self.ui.wsl_l_v_text.setPlainText(wsl_l_v_txt) def __get_wsl2_ip(self): wsl2_ip_info = self.wsl2.get_wsl2_ip() if not wsl2_ip_info: # 未查询到端口转发信息提示 QMessageBox.information(self.ui, '系统提示', '未查询到IP信息!') else: wsl2_ip_info = 'WSL2当前IP为:' + wsl2_ip_info self.ui.wsl_l_v_text.setPlainText(wsl2_ip_info) def __port_add(self): wsl2_ip_info = self.wsl2.get_wsl2_ip() if not wsl2_ip_info: # 未查询到端口转发信息提示 QMessageBox.information(self.ui, '系统提示', '未查询到IP信息!') else: self.ui.result_text.clear() ports = self.ui.port_text.toPlainText() port_str = '' for port in ports.splitlines(): if not port.strip(): continue self.__port_add_one(port, wsl2_ip_info) port_str += (',' + port if port_str else port) self.__fire_wall_rule_add(port_str) self.ui.result_text.appendPlainText('Succeed!') def __port_del(self, del_port=True, del_fire=True): self.ui.result_text.clear() ports = self.ui.port_text.toPlainText() if del_port: for port in ports.splitlines(): if not port.strip(): continue self.__port_del_one(port) if del_fire: self.__fire_wall_rule_del() self.ui.result_text.appendPlainText('Succeed!') def __port_reset(self): port_info = self.wsl2.port_info() self.ui.result_text.clear() for port in port_info: self.__port_del_one(port['port']) self.__fire_wall_rule_del() self.ui.result_text.appendPlainText('Succeed!') def __port_info(self): """ 获取端口转发信息 :return: """ info_str = self.wsl2.port_info(True).strip() if not info_str: # 未查询到端口转发信息提示 info_str = '未查询到端口转发信息!' QMessageBox.information(self.ui, '系统提示', info_str) self.ui.result_text.setPlainText(info_str) def __wsl2_auto_port_forward(self): """ 一键自动转发 @return: """ self.__port_del(del_port=False, del_fire=True) self.__port_add() def __end_wsl(self): """ 停止wsl :return: """ self.start_qt_process(self.wsl2.end_wsl(exec_run=False)) info_str = 'wsl 已全部停止' QMessageBox.information(self.ui, '系统提示', info_str) def __start_wsl(self): """ 启动wsl :return: """ self.start_qt_process(self.wsl2.start_wsl(exec_run=False)) def start_wsl_all(self): """ 启动wsl并转发端口 :return: """ self.__start_wsl() self.__wsl2_auto_port_forward() def __save_settings(self): """ 保存全部配置 :return: """ # 保存脚本 self.__save_bat_script() # 保存配置信息 self.settings_manage.set('fire_wall_open', self.ui.fire_wall_open.isChecked()) self.settings_manage.set('fire_wall_close', self.ui.fire_wall_close.isChecked()) self.settings_manage.set('auto_start_wsl', self.ui.auto_start_wsl.isChecked()) self.settings_manage.set('ports', self.ui.port_text.toPlainText().splitlines()) # 保存成功提示 QMessageBox.information(self.ui, '系统提示', '配置保存成功!') def __save_bat_script(self): """ 保存启动脚本 :return: """ content = self.ui.bat_text.toPlainText() self.settings_manage.set('wsl_bat_content', content) self.wsl2.save_bat_script(content) def __fire_wall_rule_add(self, port): """ 添加防火墙 :param port: 端口号,多个端口逗号隔开 :return: """ if self.ui.fire_wall_open.isChecked(): self.start_qt_process( self.wsl2.fire_wall_rule_add( wsl_port=port, wall_type=self.wsl2.FireWallRuleIn, exec_run=False)) self.ui.result_text.appendPlainText('>>> 添加防火墙:【' + self.wsl2.FireWallRuleIn + '】...') self.start_qt_process( self.wsl2.fire_wall_rule_add( wsl_port=port, wall_type=self.wsl2.FireWallRuleOut, exec_run=False)) self.ui.result_text.appendPlainText('>>> 添加防火墙:【' + self.wsl2.FireWallRuleOut + '】...') def __fire_wall_rule_del(self): """ 删除防火墙 :return: """ if self.ui.fire_wall_close.isChecked(): self.start_qt_process( self.wsl2.fire_wall_rule_del( wall_type=self.wsl2.FireWallRuleIn, exec_run=False)) self.ui.result_text.appendPlainText('>>> 删除防火墙:【' + self.wsl2.FireWallRuleIn + '】...') self.start_qt_process( self.wsl2.fire_wall_rule_del( wall_type=self.wsl2.FireWallRuleOut, exec_run=False)) self.ui.result_text.appendPlainText('>>> 删除防火墙:【' + self.wsl2.FireWallRuleOut + '】...') def __port_add_one(self, port, wsl2_ip_info): """ 添加单个端口 :param port: 端口号 :param wsl2_ip_info: 转发的IP :return: """ self.start_qt_process( self.wsl2.port_add(wsl_ip=wsl2_ip_info, wsl_port=port, exec_run=False)) self.ui.result_text.appendPlainText('>>> 添加端口:【' + port + '】...') def __port_del_one(self, port): """ 删除单个端口 :param port: 端口号 :return: """ self.start_qt_process(self.wsl2.port_del(wsl_port=port, exec_run=False)) self.ui.result_text.appendPlainText('>>> 删除端口:【' + port + '】...') def start_qt_process(self, cmd): """ 启动子进程执行耗时命令 :param cmd: :return: """ process = QProcess(self.ui) process.start(cmd) result = process.waitForStarted() return result
class SystemTrayIcon(QObject): clicked = pyqtSignal() double_clicked = pyqtSignal() def __init__(self, parent, menu, is_logging=False): QObject.__init__(self) def getIcon(name): return QIcon(':/images/tray_icons/' + name + '.png') self._icons = { STATUS_INIT: getIcon("disconnected") if is_logging \ else getIcon("sync"), STATUS_DISCONNECTED: getIcon("disconnected"), STATUS_WAIT: getIcon("default"), STATUS_PAUSE: getIcon("pause"), STATUS_IN_WORK: getIcon("sync"), STATUS_INDEXING: getIcon("sync"), } self._statuses = { STATUS_INIT: tr("Pvtbox"), STATUS_DISCONNECTED: tr("Pvtbox connecting..."), STATUS_WAIT: tr("Pvtbox"), STATUS_PAUSE: tr("Pvtbox paused"), STATUS_IN_WORK: tr("Pvtbox syncing..."), STATUS_INDEXING: tr("Pvtbox indexing...") } self._tray = QSystemTrayIcon(self._icons[STATUS_INIT], parent) self.set_tool_tip(self._statuses[STATUS_INIT]) self._tray.setContextMenu(menu) menu.aboutToShow.connect(self.clicked.emit) self._tray.activated.connect(self._on_activated) self._tray.installEventFilter(self) self._tray.setVisible(True) self._tray.show() self._tray_show_timer = QTimer(self) self._tray_show_timer.setInterval(3000) self._tray_show_timer.setSingleShot(False) self._tray_show_timer.timeout.connect(self.show) def eventFilter(self, obj, ev): if ev.type() == QEvent.ToolTip: self.clicked.emit() return False def _on_activated(self, reason): ''' Slot for system tray icon activated signal. See http://doc.qt.io/qt-5/qsystemtrayicon.html @param reason Tray activation reason ''' if reason == QSystemTrayIcon.Trigger: # This is usually when left mouse button clicked on tray icon self.clicked.emit() elif reason == QSystemTrayIcon.DoubleClick: self.double_clicked.emit() @property def menu(self): return self._tray.contextMenu() def set_tool_tip(self, tip): self._tray.setToolTip(tip) def show_tray_notification(self, text, title=""): if not title: title = tr('Pvtbox') # Convert strings to unicode if type(text) in (str, str): text = ensure_unicode(text) if type(title) in (str, str): title = ensure_unicode(title) logger.info("show_tray_notification: %s, title: %s", text, title) if self._tray.supportsMessages(): self._tray.showMessage(title, text) else: logger.warning("tray does not supports messages") def request_to_user( self, dialog_id, text, buttons=("Yes", "No"), title="", close_button_index=-1, close_button_off=False, parent=None, on_clicked_cb=None, details=''): msg_box = QMessageBox(parent) # msg_box = QMessageBox() if not title: title = tr('Pvtbox') msg_box.setWindowTitle(title) msg_box.setText(str(text)) pvtboxIcon = QIcon(':/images/icon.png') msg_box.setWindowIcon(pvtboxIcon) if details: msg_box.setDetailedText(details) if close_button_off: if get_platform() == 'Darwin': msg_box.setWindowFlags(Qt.Tool) else: msg_box.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint) msg_box.setAttribute(Qt.WA_MacFrameworkScaled) msg_box.setModal(True) buttons = list(buttons) for button in buttons: msg_box.addButton(button, QMessageBox.ActionRole) msg_box.show() msg_box.raise_() msg_box.exec_() try: button_index = buttons.index(msg_box.clickedButton().text()) except (ValueError, AttributeError): button_index = -1 # message box was closed with close button if len(buttons) == 1 and close_button_index == -1: # if only one button then call callback close_button_index = 0 if len(buttons) > close_button_index >= 0: button_index = close_button_index if button_index >= 0 and callable(on_clicked_cb): on_clicked_cb(dialog_id, button_index) def update_status_icon(self, new_status, new_substatus): icon = self._icons[STATUS_DISCONNECTED] if new_status == STATUS_INIT \ else self._icons[new_status] self._tray.setIcon(icon) tool_tip = self._statuses[new_status] if new_status == STATUS_IN_WORK and new_substatus == SUBSTATUS_SHARE: tool_tip = tr("Pvtbox downloading share...") self.set_tool_tip(tool_tip) self.show() def show(self): self._tray.setVisible(True) self._tray.show() def hide(self): if self._tray_show_timer.isActive(): self._tray_show_timer.stop() self._tray.hide() def __del__(self): self._tray.removeEventFilter(self) self._tray.activated.disconnect() self.hide()