class CameraIcon(QObject): formats = [] ui_controls = {} ui_format = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.v4l2_ctl = V4l2Ctl() self.app = QApplication(sys.argv) self.app.setQuitOnLastWindowClosed(False) self.app.setApplicationName(APP_NAME) with open(LINUX_CSS, 'r') as css_file: self.app.setStyleSheet(css_file.read()) self.create_icon() self.create_menu() self.run() def create_icon(self): self.icon = QSystemTrayIcon(QIcon(ICON_PATH)) self.icon.activated.connect(self.activate) def create_menu(self): self.menu = QMenu() self.action = Action(self.menu) self.menu.addAction(self.action) def activate_exit(self, *args): os._exit(0) return True def on_format_value_changed(self, name, value): pix, res, fps = self.formats[value] self.v4l2_ctl.set_format(pix, res, fps) def on_format_change_finished(self): self.current_format = self.v4l2_ctl.get_format() self.update_format() def on_control_value_changed(self, name, value): self.v4l2_ctl.set_control(name, value) def on_control_change_finished(self): self.controls_data = self.v4l2_ctl.read_controls() self.update_controls() def on_export(self): file_name, _ = QFileDialog.getSaveFileName() if file_name: script = self.v4l2_ctl.export_script() with open(filename, 'w') as f: f.write(script) def append_widget_to_layout(self, widget): layout = self.action.layout layout.rowCount() layout.addWidget(widget, layout.rowCount(), 0, 1, 2) def add_export(self): button = QPushButton('Export settings script') button.clicked.connect(self.on_export) self.append_widget_to_layout(button) def add_quit(self): button = QPushButton('Exit') button.clicked.connect(lambda: os._exit(0)) self.append_widget_to_layout(button) def add_format(self): if len(self.formats): formats = { str(k): f'{p} {r} {f}' for k, [p, r, f] in enumerate(self.formats) } self.ui_format = Dropdown(formats, 'formats') self.ui_format.valueChanged.connect(self.on_format_value_changed) self.ui_format.changeFinished.connect( self.on_format_change_finished) self.append_widget_to_layout(self.ui_format) else: self.ui_format = None def add_controls(self): self.ui_controls = {} layout = self.action.layout for row, [k, v] in enumerate(self.controls_data.items()): label = QLabel(k) layout.addWidget(label, row, 0, 1, 1, Qt.AlignRight) ctl_type = v[0] params = v[1] control = None if ctl_type == 'bool': control = CheckBox(k) elif ctl_type == 'int': control = Slider( int(params['min']), int(params['max']), int(params['step']), k, ) elif ctl_type == 'menu': control = Dropdown(v[2], k) if control is None: continue control.valueChanged.connect(self.on_control_value_changed) control.changeFinished.connect(self.on_control_change_finished) add = getattr( self.action.layout, 'addWidget' if control.isWidgetType() else 'addLayout') add(control, row, 1) self.ui_controls[k] = control def readd_widgets(self): clear_layout(self.action.layout) self.add_controls() self.add_format() self.add_export() self.add_quit() def update_controls(self): for k, v in self.ui_controls.items(): data = self.controls_data[k][1] v.set_value(int(data['value'])) disabled = 'flags' in data and data['flags'] == 'inactive' v.setDisabled(disabled) def update_format(self): if self.current_format != [None, None, None]: self.ui_format.set_value(self.formats.index(self.current_format)) def update_menu_size(self): self.action.layout.invalidate() self.action.layout.activate() hint = self.action.widget.sizeHint() width = hint.width() + 20 height = min(MENU_HEIGHT, hint.height()) self.action.scroll.setFixedWidth(width) self.action.scroll.setFixedHeight(height) self.menu.setFixedWidth(width) self.menu.setFixedHeight(height) self.menu.setAttribute(Qt.WA_DontShowOnScreen, True) self.menu.show() self.menu.hide() self.menu.setAttribute(Qt.WA_DontShowOnScreen, False) def activate(self, reason): self.controls_data = self.v4l2_ctl.read_controls() self.formats = self.v4l2_ctl.read_formats() self.current_format = self.v4l2_ctl.get_format() self.readd_widgets() self.update_controls() self.update_format() icon_pos = self.icon.geometry().bottomRight() self.update_menu_size() self.menu.popup(icon_pos) return True def run(self): self.icon.show() sys.exit(self.app.exec_())
color = QColor('#29AB87') if self.ok else QColor(255, 0, 0, 128) painter = QPainter(self) painter.setBrush(color) painter.setPen(color) painter.drawRect(self.rect()) # TODO: Нарисовать график if __name__ == '__main__': app = QApplication([]) app.setQuitOnLastWindowClosed(False) tray = QSystemTrayIcon(QIcon(TRAY_ICON)) job_report_widget = JobReportWidget() job_report_widget.setFixedSize(230, 130) job_report_widget_action = QWidgetAction(job_report_widget) job_report_widget_action.setDefaultWidget(job_report_widget) menu = QMenu() menu.addAction(job_report_widget_action) tray.setContextMenu(menu) tray.activated.connect(lambda x: menu.exec(tray.geometry().center())) tray.setToolTip('Compass Plus. Рапорт учета рабочего времени') tray.show() app.exec()
class SoundIcon(QObject, VolumeMixin, MediaKeysMixin): icon_updated = pyqtSignal(object) media_key_pressed = pyqtSignal(list) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.app = QApplication(sys.argv) self.app.setQuitOnLastWindowClosed(False) self.app.setApplicationName(APP_NAME) self.screen_height = self.app.primaryScreen().geometry().height() with open(LINUX_CSS, 'r') as css_file: self.app.setStyleSheet(css_file.read()) self.mixer = PulseMixer() self.init_dbus() self.create_icon() self.mixer.start_listener(self.get_pulse_callback) self.icon_updated.connect(self.update_icon) self.create_menu() self.update_icon() self.init_keys() self.media_key_pressed.connect(self.on_media_key_pressed) self.run() def get_pulse_callback(self): return lambda e: self.icon_updated.emit(e) def get_notify_callback(self): return lambda k, t: self.media_key_pressed.emit([k, t]) def create_icon(self): self.icon_name = '' self.icon = QSystemTrayIcon() self.icon.activated.connect(self.activate) self.icon.eventFilter = self.on_scroll self.icon.installEventFilter(self.icon) def create_menu(self): self.menu = QMenu() self.slider_item = SliderItem(self.menu) self.slider_item.value_changed.connect(self.on_value_changed) mixer_item = self.create_mixer() exit_item = self.create_exit() self.menu.addAction(self.slider_item) self.menu.addAction(mixer_item) self.menu.addSeparator() self.menu.addSeparator() self.menu.addAction(exit_item) def create_mixer(self): item = MenuItem(LABEL_MIXER, self.menu) item.triggered.connect(self.activate_mixer) return item def create_exit(self): item = MenuItem(LABEL_EXIT, self.menu) item.triggered.connect(self.activate_exit) return item def activate_exit(self, *args): os._exit(0) return True def on_value_changed(self, value): self.mixer.set_volume(value) return True def activate(self, reason): if reason == QSystemTrayIcon.Context: self.mixer.toggle_mute() return True if reason != QSystemTrayIcon.Trigger: return False self.update_menu() volume, mute = self.mixer.get_sink_volume_and_mute() self.slider_item.setValue(int(volume)) self.slider_item.setEnabled(not mute) icon_pos = self.icon.geometry().bottomRight() width = min(self.menu.geometry().width(), VOLUME_WIDTH) if icon_pos.y() > self.screen_height / 2: pos = icon_pos - QPoint(width, 40) self.menu.popup(pos, self.menu.actions()[-1]) else: pos = icon_pos - QPoint(width, 0) self.menu.popup(pos) return True def destroy_item(self, item): self.menu.removeAction(item) item.deleteLater() def insert_label_item(self, label, pos): item = MenuItem(label, self.menu) item.label.setProperty('objectName', 'section') before = self.menu.actions()[pos] self.menu.insertAction(before, item) item.setEnabled(False) self.profile_items.append(item) def insert_subaction_item(self, profile, link, pos): item = MenuItem(profile, self.menu) item.label.setProperty('objectName', 'subaction') item.triggered.connect(lambda c: self.mixer.set_profile(link)) before = self.menu.actions()[pos] self.menu.insertAction(before, item) if link == self.mixer.current_profile: item.setEnabled(False) self.profile_items.append(item) def on_scroll(self, obj, event): if not isinstance(event, QWheelEvent): return False delta_y = event.angleDelta().y() if delta_y == 0: return change = SCROLL_BY if delta_y > 0 else -SCROLL_BY self.mixer.change_volume(change) return True def set_theme_icon(self, icon_name): qicon = QIcon.fromTheme(icon_name) self.icon.setIcon(qicon) def is_menu_visible(self): return self.menu.isVisible() def run(self): self.icon.show() sys.exit(self.app.exec_())
class LoginDialog(QDialog): def __init__(self, steam, theme): super().__init__() self.steam = steam self.theme = theme self.trayicon = None self.wait_task = None self.process = None self._exit = False self._login = None self.user_widgets = [] self.setLayout(QVBoxLayout()) self.setWindowTitle("Steam Acolyte") self.setWindowIcon(theme.window_icon) self.setStyleSheet(theme.window_style) steam.command_received.connect(lambda *_: self.activateWindow()) self.update_userlist() def update_userlist(self): """Update the user list widget from the config file.""" self.clear_layout() users = sorted(self.steam.users(), key=lambda u: (u.persona_name.lower(), u.account_name.lower())) users.append(SteamUser('', '', '', '')) for user in users: self.layout().addWidget(UserWidget(self, user)) def clear_layout(self): """Remove all users from the user list widget.""" # The safest way I found to clear a QLayout is to reparent it to a # temporary widget. This also recursively reparents, hides and later # destroys any child widgets. layout = self.layout() if layout is not None: dump = QWidget() dump.setLayout(layout) dump.deleteLater() self.setLayout(QVBoxLayout()) @trace.method def wait_for_lock(self): """Start waiting for the steam instance lock asynchronously, and show/activate the window when we acquire the lock.""" if self._exit: self.close() return self.wait_task = AsyncTask(self.steam.wait_for_lock) self.wait_task.finished.connect(self._on_locked) self.wait_task.start() @trace.method def _on_locked(self): """Executed when steam instance lock is acquired. Executes any queued login command, or activates the user list widget if no command was queued.""" if self._exit: self.close() return self.stopAction.setEnabled(False) self.wait_task = None self.steam.store_login_cookie() self.update_userlist() if self._login: self.run_steam(self._login) self._login = None return self.show() @trace.method def show_trayicon(self): """Create and show the tray icon.""" # The conversion to QPixmap and back to QIcon is needed to prevent a # bug that leads to the icon not being displayed in plasma. See: # - https://github.com/coldfix/steam-acolyte/issues/8 # - https://bugreports.qt.io/browse/QTBUG-53550 icon = QIcon(self.theme.window_icon.pixmap(64)) self.trayicon = QSystemTrayIcon(icon) self.trayicon.setVisible(True) self.trayicon.setToolTip("acolyte - lightweight steam account manager") self.trayicon.activated.connect(self.trayicon_clicked) self.trayicon.setContextMenu(self.createMenu()) @trace.method def hide_trayicon(self): """Hide and destroy the tray icon.""" if self.trayicon is not None: self.trayicon.setVisible(False) self.trayicon.deleteLater() self.trayicon = None @trace.method def trayicon_clicked(self, reason): """Activate window when tray icon is left-clicked.""" if reason == QSystemTrayIcon.Trigger: if self.steam.has_steam_lock(): self.activateWindow() def createMenu(self): """Compose tray menu.""" style = self.style() stop = self.stopAction = QAction('&Exit Steam', self) stop.setToolTip('Signal steam to exit.') stop.setIcon(style.standardIcon(QStyle.SP_MediaStop)) stop.triggered.connect(self.exit_steam) stop.setEnabled(False) exit = QAction('&Quit', self) exit.setToolTip('Exit acolyte.') exit.setIcon(style.standardIcon(QStyle.SP_DialogCloseButton)) exit.triggered.connect(self._on_exit, QueuedConnection) self.newUserAction = make_user_action(self, SteamUser('', '', '', '')) self.userActions = [] menu = QMenu() menu.addSection('Login') menu.addAction(self.newUserAction) menu.addSeparator() menu.addAction(stop) menu.addAction(exit) menu.aboutToShow.connect(self.update_menu, QueuedConnection) return menu def update_menu(self): """Update menu just before showing: populate with current user list and set position from tray icon.""" self.populate_menu() self.position_menu() def populate_menu(self): """Update user list menuitems in tray menu.""" menu = self.trayicon.contextMenu() for action in self.userActions: menu.removeAction(action) users = sorted(self.steam.users(), key=lambda u: (u.persona_name.lower(), u.account_name.lower())) self.userActions = [make_user_action(self, user) for user in users] menu.insertActions(self.newUserAction, self.userActions) def position_menu(self): """Set menu position from tray icon.""" menu = self.trayicon.contextMenu() desktop = QApplication.desktop() screen = QApplication.screens()[desktop.screenNumber(menu)] screen_geom = screen.availableGeometry() menu_size = menu.sizeHint() icon_geom = self.trayicon.geometry() if icon_geom.left() + menu_size.width() <= screen_geom.right(): left = icon_geom.left() elif icon_geom.right() - menu_size.width() >= screen_geom.left(): left = icon_geom.right() - menu_size.width() else: return if icon_geom.bottom() + menu_size.height() <= screen_geom.bottom(): top = icon_geom.bottom() elif icon_geom.top() - menu_size.height() >= screen_geom.top(): top = icon_geom.top() - menu_size.height() else: return menu.move(left, top) @trace.method def exit_steam(self): """Send shutdown command to steam.""" self.stopAction.setEnabled(False) self.steam.stop() @trace.method def _on_exit(self): """Exit acolyte.""" # We can't quit if steam is still running because QProcess would # terminate the child with us. In this case, we hide the trayicon and # set an exit flag to remind us about to exit as soon as steam is # finished. self.hide_trayicon() if self.steam.has_steam_lock(): self.close() else: self._exit = True self.steam.unlock() self.steam.release_acolyte_instance_lock() @trace.method def login(self, username): """ Exit steam if open, and login the user with the given username. """ if self.steam.has_steam_lock(): self.run_steam(username) else: self._login = username self.exit_steam() @trace.method def run_steam(self, username): """Run steam as the given user.""" # Close and recreate after steam is finished. This serves two purposes: # 1. update user list and widget state # 2. fix ":hover" selector not working on linux after hide+show self.hide() self.steam.switch_user(username) self.steam.unlock() self.stopAction.setEnabled(True) self.process = self.steam.run() self.process.finished.connect(self.wait_for_lock) @trace.method def show_waiting_message(self): """If we are in the background, show waiting message as balloon.""" if self.trayicon is not None: self.trayicon.showMessage("steam-acolyte", "The damned stand ready.")
class Reminder(ShowCenterMixin, OsxMenuMixin, TimerMixin): def __init__(self): self.app = QApplication(sys.argv) self.app.setQuitOnLastWindowClosed(False) self.app.setApplicationName(SPINBOX_WINDOW_TITLE) self.screen_height = self.app.primaryScreen().geometry().height() styleSheet = open(APP_STYLESHEET).read() if sys.platform == 'darwin': c = self.app.palette().color(QPalette.Highlight) color = 'rgba({},{},{},10%)'.format(c.red(), c.green(), c.blue()) styleSheet = styleSheet.replace('palette(highlight)', color) self.app.setStyleSheet(styleSheet) self.setup_icon() self.setup_menu() self.setup_popup() self.setup_window() self.init_saved_alarm() self.idle() self.run() def on_button_clicked(self, *args): self.window.hide() h = self.spins[0].value() m = self.spins[1].value() s = self.spins[2].value() self.start_timer(h, m, s) def setup_window(self): self.window = QWidget() self.window.setProperty('objectName', 'window') self.window.setWindowTitle(SPINBOX_WINDOW_TITLE) self.window.setWindowFlags(self.window.windowFlags() | Qt.WindowStaysOnTopHint | Qt.Dialog) vlayout = QVBoxLayout(self.window) hlayout = QHBoxLayout() hlayout.setSpacing(SPINBOX_WINDOW_SPACING) self.spins = [] for label in ['h', 'm', 's']: button_spin = ButtonSpinBox(label=label) spin = button_spin.spin self.spins.append(spin) hlayout.addLayout(button_spin) button = QPushButton(BUTTON_LABEL) button.setProperty('objectName', 'start') vlayout.addLayout(hlayout) vlayout.addWidget(button) button.clicked.connect(self.on_button_clicked) self.window.closeEvent = self.on_window_close def on_window_close(self, *args): self.window.hide() def setup_icon(self): self.icon_normal = QIcon(ALARM_PATH) self.icon = QSystemTrayIcon(self.icon_normal) self.icon_urgent = QIcon(ALARM_URGENT_PATH) self.icon_active = QIcon(ALARM_ACTIVE_PATH) self.icon.activated.connect(self.activate_menu) def setup_menu(self): self.menu = QMenu() clock_item = QWidgetAction(self.menu) self.clock = QPushButton(' ') self.clock.setProperty('objectName', 'menu') font = self.clock.font() font.setPixelSize(CLOCK_FONT_SIZE) self.clock.setFont(font) clock_item.setDefaultWidget(self.clock) self.clock.clicked.connect(self.activate_window) exit_item = QWidgetAction(self.menu) label = QPushButton(EXIT_LABEL) label.setProperty('objectName', 'menu') exit_item.setDefaultWidget(label) label.clicked.connect(self.activate_exit) self.menu.addAction(clock_item) self.menu.addAction(exit_item) if sys.platform == 'darwin': clock_item.button = self.clock exit_item.button = label self.menu.actions = [clock_item, exit_item] self.set_up_menu_macos(self.menu) def setup_popup(self): self.popup = QWidget() self.popup.setProperty('objectName', 'popup') vlayout = QVBoxLayout(self.popup) label = QLabel(ALERT_TEXT) vlayout.addWidget(label) self.popup.setWindowFlags(Qt.Sheet | Qt.Popup | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) self.popup.mouseReleaseEvent = self.on_popup_release def on_popup_release(self, *args): self.popup.hide() def activate_menu(self, reason): if reason != QSystemTrayIcon.Trigger: return if self.icon.icon().cacheKey() == self.icon_urgent.cacheKey(): self.clear_alarm() self.set_icon(self.icon_normal) self.update_clock() if sys.platform == 'darwin': self.on_menu_activated_macos() self.icon.setContextMenu(self.menu) self.icon.setContextMenu(None) return icon_pos = self.icon.geometry().bottomRight() width = min(self.menu.geometry().width(), 135) if icon_pos.y() > self.screen_height / 2: pos = icon_pos - QPoint(width, 40) self.menu.popup(pos, self.menu.actions()[-1]) else: pos = icon_pos - QPoint(width, 0) self.menu.popup(pos) def activate_window(self, *args): if sys.platform == 'darwin': clock_action = self.menu.actions[0] clock_action.activate(clock_action.Trigger) self.show_center(self.window) def activate_exit(self, *args): os._exit(0) def set_icon(self, icon): self.icon.setIcon(icon) def update_clock_text(self, text): self.clock.setText(text) def show_popup(self): self.show_center(self.popup) def is_menu_visible(self): if sys.platform == 'darwin': return True return self.menu.isVisible() def run(self): timer = QTimer() timer.setInterval(1000) timer.timeout.connect(self.idle) timer.start(1000) self.icon.show() sys.exit(self.app.exec_())