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()
class PingIndicator(QMainWindow): def __init__(self, address="8.8.8.8"): super(PingIndicator, self).__init__() self.icon = QImage(QSize(packet_amount, indicator_image_height), QImage.Format_RGBA8888) self.online = True self.destination = address self.packets = deque([], packet_amount) self.tray_icon = QSystemTrayIcon(self) self.tray_icon.setToolTip(address) self.update_timer = QTimer(self) self.update_timer.setInterval(int(timeout * 1.05)) self.update_timer.timeout.connect(self.update_indicator) self.update_timer.start() self.last_time_online = strftime("%H:%M:%S") self.reset() def update_icon(self): self.icon.fill(QColor(0, 0, 0, 0)) painter = QPainter(self.icon) (width, height) = self.icon.size().toTuple() width -= 1 height -= 1 painter.fillRect(QRect(0, 0, width, height), QColor(0, 0, 0, 0)) try: scale = min(1.0 / max(self.packets), min_scale) except ValueError: scale = min_scale for index, ping in enumerate(list(reversed(self.packets))): x = ping / float(timeout) color = QColor( int(-324 * x**2 + 390 * x + 138), # R int(-480 * x**2 + 254 * x + 226), # G int(-212 * x**2 + 160 * x + 52), # B 255, ) scaled_height = ceil(scale * ping * height) painter.fillRect( QRect(width - index, height - scaled_height, 1, scaled_height), color) self.tray_icon.setIcon(QIcon(QPixmap(self.icon))) self.tray_icon.show() def update_indicator(self): try: new_env = dict(os.environ) new_env['LC_ALL'] = 'C' output = check_output( [ "ping", "-c", "1", "-W", str(timeout / 1000), self.destination ], stderr=STDOUT, env=new_env, ).decode("ascii") # man ping for line in output.splitlines(): pos = line.find("time=") if pos != -1: new_label = line[pos + 5:-3].center(4) self.packets.append(round(float(new_label), 2)) if not self.online: self.online = True self.tray_icon.contextMenu().actions()[0].setText( "Last disconnect: " + self.last_time_online) else: self.last_time_online = strftime("%H:%M:%S") break else: raise ValueError("No time could be parsed.") except CalledProcessError as cpe: self.packets.append(timeout) if self.online: self.online = False self.tray_icon.contextMenu().actions()[0].setText( "Offline since: " + strftime("%H:%M:%S")) print(cpe) except KeyboardInterrupt: self.close() self.update_icon() self.update_menu() return True def reset(self): self.packets.clear() self.update_icon() menu = QMenu() menu.addAction("Online since: " + strftime("%H:%M:%S")) menu.addAction("Lost: -, Avg: -") menu.addAction("Max: -, Min: -") menu.addSeparator() menu.addAction("Reset").triggered.connect(self.reset) menu.addAction("Quit").triggered.connect(self.close) self.tray_icon.setContextMenu(menu) def update_menu(self): self.tray_icon.contextMenu().actions()[1].setText( "Lost: %d, Avg: %dms" % (self.packets.count(timeout), avg(self.packets)), ) self.tray_icon.contextMenu().actions()[2].setText( "Max: %dms, Min: %dms" % (max(self.packets), min(self.packets)), )
class MyWidget(QWidget): def __init__(self): super().__init__() self.streamfile = "" self.streams = {} scriptdir = os.path.dirname(os.path.realpath(__file__)) icon = (scriptdir + os.path.sep + "icon/pyradio.ico") self.setWindowIcon(QtGui.QIcon(icon)) self.setStuff() # Tray self.tray = QSystemTrayIcon() self.tray.setIcon(QtGui.QIcon(icon)) self.tray.activated.connect(self.call) self.icon = QtGui.QIcon() self.icon.addFile(icon) self.setWindowIcon(self.icon) # tray menu self.trayIconMenu = QtWidgets.QMenu() self.quitAction = QtWidgets.QAction("&Quit", triggered=self.close) self.trayIconMenu.addAction(self.quitAction) self.tray.setContextMenu(self.trayIconMenu) self.trayIconMenu.setStyleSheet(open("css/main.css", "r").read()) # Media player self.radio = vlc.MediaPlayer() self.playing = False self.pal = QtGui.QPalette(self.palette()) self.playing_label = QLabel("Stopped") self.label = QLabel("Radios:") self.label.setAlignment(Qt.AlignCenter) self.playing_label.setAlignment(Qt.AlignCenter) self.btn = QPushButton("Play/Stop") self.btn.clicked.connect(self.control) self.list = QListWidget() self.list.itemDoubleClicked.connect(self.control) self.edit = QPushButton("Edit Radios") self.edit.clicked.connect(self.openfile) self.refresh = QPushButton("Refresh") self.refresh.clicked.connect(self.refreshstreams) self.slider = QSlider(QtGui.Qt.Horizontal) self.slider.setMaximum(100) self.slider.setValue(self.volume) self.slider.valueChanged.connect(self.changeVolume) self.setStyleSheet(open("css/main.css", "r").read()) self.refreshstreams() self.current = "" self.buttons = QHBoxLayout() self.layout = QVBoxLayout() self.layout.addWidget(self.label) self.layout.addWidget(self.list) self.layout.addWidget(self.playing_label) self.layout.addWidget(self.slider) self.buttons.addWidget(self.btn) self.buttons.addWidget(self.edit) self.buttons.addWidget(self.refresh) self.layout.addLayout(self.buttons) self.setLayout(self.layout) def setStuff(self): info = self.readInfo() print(info) if len(info) == 0: info = ["", "", "", "", ""] if (info[0] == ""): self.volume = 80 else: self.volume = int(info[0]) if info[3].strip() == "false" or info[3] == "": if info[1] == "": self.resize(800, 600) else: w, h = info[1].split(" ") self.resize(int(w), int(h)) if info[2] != "": x, y = info[2].split(" ") self.move(int(x), int(y)) else: self.showMaximized() if len(info) < 5: # show dialog self.chooseStreamfile() else: self.streamfile = info[4].strip() def chooseStreamfile(self): self.dialog = QFileDialog(self) self.dialog.setFileMode(QFileDialog.AnyFile) if self.dialog.exec_(): filename = self.dialog.selectedFiles() self.streamfile = filename[0] def changeVolume(self): self.volume = self.slider.value() self.radio.audio_set_volume(self.volume) def getVolume(self): try: with open("data", "r") as file: return int(file.readline()) except: with open("data", "w") as file: file.write(str(80)) return 80 def control(self): if self.playing and self.current == self.streams[ self.list.currentItem().text()]: self.stop() else: self.radio.stop() self.play() def stop(self): self.radio.stop() self.playing_label.setText("Stopped") self.playing = False def play(self): self.current = self.list.currentItem().text() for i in self.streams: if self.current == i: self.current = self.streams[i] break self.radio = vlc.MediaPlayer(self.current) self.radio.play() self.radio.audio_set_volume(self.slider.value()) self.playing_label.setText("Playing") self.playing = True self.tray.showMessage(self.list.currentItem().text(), "", self.tray.icon(), 1000) def next(self): isthis = False self.current = self.list.currentItem().text() for n, i in enumerate(self.streams): if isthis: self.list.setCurrentRow(n) break else: if self.current == i: isthis = True if n + 1 >= len(self.streams): self.list.setCurrentRow(0) self.stop() self.play() def previous(self): isthis = False self.current = self.list.currentItem().text() for n, i in enumerate(self.streams): if isthis: self.list.setCurrentRow(n - 2) break else: if self.current == i: isthis = True if n - 1 < 0: self.list.setCurrentRow(len(self.streams) - 1) break elif n == len(self.streams) - 1: self.list.setCurrentRow(n - 1) self.stop() self.play() def openfile(self): # Opens radios.txt webbrowser.open(self.streamfile) def refreshstreams(self): # Refreshes the stream list when button pressed if self.list.currentItem(): current = self.list.currentItem().text() else: current = None self.streams = {} try: with open(self.streamfile, "r") as file: lines = file.readlines() for line in lines: nline = line.strip().split(":", 1) self.streams[nline[0]] = nline[1].split("#")[0] except: self.chooseStreamfile() self.refreshstreams() return self.list.clear() for i, n in enumerate(self.streams): self.list.addItem(n) if n == current: self.list.setCurrentRow(i) if not self.list.currentItem(): self.list.setCurrentRow(0) def changeEvent(self, event): # This minimizes the program to tray when Minimize button pressed if event.type() == QEvent.WindowStateChange: if self.windowState() & Qt.WindowMinimized: print(QSystemTrayIcon.isSystemTrayAvailable()) if QSystemTrayIcon.isSystemTrayAvailable( ) and self.isActiveWindow(): event.ignore() self.tray.show() self.hide() self.listener = keyboard.Listener( on_release=self.on_release) self.listener.start() def closeEvent(self, event): file = open("data", "w+") info = str(self.volume) + "\n" + str(self.size().width()) + " " + str(self.size().height()) + "\n" +\ str(self.pos().x()) + " " + str(self.pos().y()) + "\n" if (self.isMaximized()): info += "true" else: info += "false" info += "\n" info += self.streamfile + "\n" file.write(info) file.close() def readInfo(self): try: with open("data", "r", encoding="utf-8") as file: info = file.readlines() return info except: with open("data", "w", encoding="utf-8") as file: file.write("") return "" def keyReleaseEvent(self, event): # This is for media controls when radio is opened key = event.key() if key == Qt.Key_MediaPlay or key == Qt.Key_MediaTogglePlayPause or \ key == Qt.Key_MediaPause: self.control() elif key == Qt.Key_MediaNext: self.next() elif key == Qt.Key_MediaPrevious: self.previous() def call(self, reason): # This is caled when tray icon is pressed if reason == QSystemTrayIcon.ActivationReason.Trigger: self.show() self.setFocus() self.listener.stop() del self.listener self.tray.hide() self.setWindowState(Qt.WindowActive) elif reason == QSystemTrayIcon.ActivationReason.Context: self.tray.contextMenu().show() elif reason == QSystemTrayIcon.ActivationReason.MiddleClick: print("Middle click on tray icon") else: print("Unknown reason") def on_release(self, key): # This is for media controls when program in tray. try: if key == keyboard.Key.media_play_pause: # might need a different key self.control() elif keyboard.Key.media_next == key: # might need a different key self.next() elif keyboard.Key.media_previous == key: # might need a different key self.previous() except AttributeError as e: print(e)