class Chrono(QMainWindow): def __init__(self, parent=None): super(Chrono, self).__init__(parent) self.createMenus() self.createSystemTrayIcon() self.timer = QTimer(self) self.timer.timeout.connect(self.tick) self.isRunning = False self.refresh_rate = 100 # ms self.progressBar = QProgressBar() self.progressBar.setValue(0) self.begin_time = self.end_time = 0 self.label = QLabel(" ") self.button = QPushButton() self.button.setIcon(self.style().standardIcon(QStyle.SP_MediaPause)) self.end_delay = self.begin_delay = 0 bottomLayout = QHBoxLayout() bottomLayout.addWidget(self.progressBar) bottomLayout.addWidget(self.button) self.button.clicked.connect(self.pause) mainLayout = QVBoxLayout() mainLayout.addWidget(self.label) mainLayout.addLayout(bottomLayout) centralWidget = QWidget() centralWidget.setLayout(mainLayout) self.setCentralWidget(centralWidget) self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) self.notification = self.notification_popup = self.notification_tray = self.notification_sound = True self.notification_soundfile = os.path.dirname( sys.argv[0]) + '/notification.mp3' # os.path.dirname(__file__) + self.setWindowTitle(TITLE) self.resize(400, self.sizeHint().height()) self.setFixedHeight(self.sizeHint().height()) def createMenus(self): menus = QMenuBar() fileMenu = menus.addMenu("&Fichier") file_newMenu = fileMenu.addMenu( self.style().standardIcon(QStyle.SP_FileIcon), "Nouveau") file_newMenu.addAction("Date", self.createDateDialog, 'CTRL+D') file_newMenu.addAction("Durée", self.createDurationDialog, 'CTRL+N') fileMenu.addSeparator() fileMenu.addAction(self.style().standardIcon(QStyle.SP_BrowserStop), "Quitter", sys.exit, 'CTRL+Q') optionMenu = menus.addMenu("&Options") optionMenu.addAction( self.style().standardIcon(QStyle.SP_MessageBoxInformation), "Évènements", self.createNotificationPopup, 'CTRL+O') optionMenu.addAction( QAction("Rester au premier plan", optionMenu, triggered=self.stayOnTop, checkable=True)) aideMenu = menus.addMenu("&Aide") aideMenu.addAction( self.style().standardIcon(QStyle.SP_DialogHelpButton), "À propos", lambda: QMessageBox.information( self, "À propos", TITLE + " " + str(VERSION)), 'CTRL+H') aideMenu.addSeparator() aideMenu.addAction( self.style().standardIcon(QStyle.SP_TitleBarMenuButton), "À propos de Qt", QApplication.aboutQt, 'CTRL+A') self.setMenuBar(menus) def createSystemTrayIcon(self): self.tray = QSystemTrayIcon() self.tray.setIcon(QIcon(os.path.dirname(sys.argv[0]) + '/icon.svg')) # os.path.dirname(__file__) + self.tray.setToolTip(TITLE) self.tray.show() systemTrayMenu = QMenu() pauseAction = QAction(self.style().standardIcon(QStyle.SP_MediaPause), "Pause / Reprendre", systemTrayMenu) pauseAction.triggered.connect(self.pause) systemTrayMenu.addAction(pauseAction) systemTrayMenu.addSeparator() systemTrayMenu.addAction( self.style().standardIcon(QStyle.SP_BrowserStop), "Quitter", sys.exit) self.tray.setContextMenu(systemTrayMenu) self.tray.activated.connect(self.show) def stayOnTop(self): self.setWindowFlags(self.windowFlags() ^ Qt.WindowStaysOnTopHint) # self.windowFlags() | Qt.CustomizeWindowHint | Qt.Window | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) # Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) self.show() def createNotificationPopup(self): popup = QDialog(self) popup.setFixedSize(popup.sizeHint().height(), popup.sizeHint().width()) popup.setWindowTitle("Évènements") innerLayout = QVBoxLayout() GroupBox = QGroupBox("Activer les notifications") GroupBox.setCheckable(True) GroupBox.setChecked(self.notification) checkBox_popup = QCheckBox("Afficher une popup") checkBox_notification = QCheckBox("Afficher une notification") checkBox_sound = QCheckBox("Jouer un son") if self.notification_popup: checkBox_popup.setCheckState(Qt.Checked) if self.notification_tray: checkBox_notification.setCheckState(Qt.Checked) if self.notification_sound: checkBox_sound.setCheckState(Qt.Checked) innerLayout.addWidget(checkBox_popup) innerLayout.addWidget(checkBox_notification) innerLayout.addWidget(checkBox_sound) innerLayout.addStretch(1) GroupBox.setLayout(innerLayout) button = QPushButton("Ok") button.clicked.connect(lambda: self.changeNotificationSettings( popup, GroupBox, checkBox_popup, checkBox_notification, checkBox_sound)) outerLayout = QVBoxLayout() outerLayout.addWidget(GroupBox) outerLayout.addWidget(button) popup.setLayout(outerLayout) popup.exec_() def changeNotificationSettings(self, popup, GroupBox, checkBox_popup, checkBox_notification, checkBox_sound): self.notification, self.notification_popup, self.notification_tray, self.notification_sound = GroupBox.isChecked( ), checkBox_popup.isChecked(), checkBox_notification.isChecked( ), checkBox_sound.isChecked() if not any([ self.notification_popup, self.notification_tray, self.notification_sound ]): self.notification = False popup.close() def createDateDialog(self): popup = QDialog(self) popup.setFixedSize(270, 60) popup.setWindowTitle("Nouvelle date") layout = QHBoxLayout() prefix = QLabel("Heure cible: ") layout.addWidget(prefix) qline = QTimeEdit() qline.setDisplayFormat("hh:mm:ss") qline.setTime(QTime.currentTime()) layout.addWidget(qline) button = QPushButton("Ok") button.clicked.connect(lambda: self.createDate(popup, qline.time().hour(), qline.time().minute(), qline.time().second())) layout.addWidget(button) popup.setLayout(layout) popup.exec_() def createDurationDialog(self): popup = QDialog(self) popup.setFixedSize(150, 150) popup.setWindowTitle("Nouvelle durée") layout = QVBoxLayout() hourLayout = QHBoxLayout() hourLabel = QLabel("Heures:") hourSpin = QSpinBox() hourLayout.addWidget(hourLabel) hourLayout.addWidget(hourSpin) minuteLayout = QHBoxLayout() minuteLabel = QLabel("Minutes:") minuteSpin = QSpinBox() minuteLayout.addWidget(minuteLabel) minuteLayout.addWidget(minuteSpin) secondLayout = QHBoxLayout() secondLabel = QLabel("Secondes:") secondSpin = QSpinBox() secondLayout.addWidget(secondLabel) secondLayout.addWidget(secondSpin) layout.addLayout(hourLayout) layout.addLayout(minuteLayout) layout.addLayout(secondLayout) button = QPushButton("Ok") button.clicked.connect(lambda: self.createDuration( popup, hourSpin.value(), minuteSpin.value(), secondSpin.value())) layout.addWidget(button) popup.setLayout(layout) popup.exec_() def createDuration(self, popup: QDialog, hours: int, minutes: int, seconds: int): popup.close() self.begin_time = datetime.timestamp(datetime.now()) self.end_time = self.begin_time + seconds + minutes * 60 + hours * 3600 self.progressBar.setRange(0, 100) self.progressBar.setValue(0) self.isRunning = True self.timer.stop() self.timer.start(self.refresh_rate) def createDate(self, popup: QDialog, hours: int, minutes: int, seconds: int): popup.close() self.begin_time = datetime.timestamp(datetime.now()) now = datetime.now().time() target = time(hours, minutes, seconds) now_delta = timedelta(hours=now.hour, minutes=now.minute, seconds=now.second) target_delta = timedelta(hours=target.hour, minutes=target.minute, seconds=target.second) if target_delta == now_delta: self.end_time = self.begin_time + 60 * 60 * 24 else: d = target_delta - now_delta self.end_time = self.begin_time + d.seconds self.progressBar.setRange(0, 100) self.progressBar.setValue(0) self.isRunning = True self.timer.stop() self.timer.start(self.refresh_rate) def tick(self): self.progressBar.setValue( 100 * (datetime.timestamp(datetime.now()) - self.begin_time) / (self.end_time - self.begin_time)) seconds = int( ceil(self.end_time - datetime.timestamp(datetime.now())) % 60) minutes = int( ceil(self.end_time - datetime.timestamp(datetime.now())) / 60 % 60) hours = int( ceil(self.end_time - datetime.timestamp(datetime.now())) / 3600) self.label.setText(f'{hours:02}:{minutes:02}:{seconds:02}') self.setWindowTitle(f'{TITLE} - {hours:02}:{minutes:02}:{seconds:02}') self.tray.setToolTip(f'{hours:02}:{minutes:02}:{seconds:02}') if datetime.timestamp(datetime.now()) >= self.end_time: self.isRunning = False self.timer.stop() self.progressBar.setRange(0, 0) self.show() self.notify() def notify(self): if not self.notification: return if self.notification_tray: self.tray.showMessage( "Finished", "Le décompte est terminé", self.style().standardIcon(QStyle.SP_MessageBoxInformation)) if self.notification_sound: test = QMediaPlayer() test.setMedia(QUrl.fromLocalFile(self.notification_soundfile)) test.play() if self.notification_popup: QMessageBox.information(self, "Finished", "Le décompte est terminé") def pause(self): if not self.isRunning: return self.progressBar.setDisabled(self.timer.isActive()) if self.timer.isActive(): self.end_delay = self.end_time - datetime.timestamp(datetime.now()) self.begin_delay = datetime.timestamp( datetime.now()) - self.begin_time print(self.begin_time) print(self.end_time) print(self.end_delay) self.statusBar.showMessage("Pause") self.tray.setToolTip(self.tray.toolTip() + ' - Pause') self.timer.stop() self.button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) else: self.begin_time = datetime.timestamp( datetime.now()) - self.begin_delay self.end_time = datetime.timestamp(datetime.now()) + self.end_delay print(self.begin_time) print(self.end_time) self.statusBar.clearMessage() self.timer.start() self.button.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) # Override def closeEvent(self, event): self.hide() event.ignore()
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')))