diff --git a/README.md b/README.md index 8b04d6f..592abc5 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ This Python project automates switching between a monitor and a TV when launching Steam Big Picture on Windows. *Looking for the [linux version](https://github.com/Odizinne/BigpictureTV-Linux)?* - - **If you appreciate my work and would like to support me:** @@ -30,19 +28,36 @@ Place the directory wherever you like (`%localappdata%\Programs` is a good one) ## Usage +### Window check rate + +In ms, from 100 to 1000, the interval for checking Steam Big Picture window presence. +1000 is the default value. +100 ms will lead to next to no delay mode switch but will consume more resources. +Do not change if unsure. + +### Audio configuration +Install [AudioDeviceCmdlets](https://github.com/frgnca/AudioDeviceCmdlets) to allow audio output switching and restart BigPictureTV. +You can now uncheck `Disable audio switching`. Specify your audio outputs. You can use a short name. BigPictureTV will try to find the correct audio output from keywords. Less is more. +### Monitor configuration For monitor switching, BigPictureTV relies on Windows built in `displayswitch.exe`. +There are 4 modes available: +- Internal: Default monitor enabled, all others disabled +- Extend: Default monitor enabled, selected monitors enabled. +- Clone: Every monitors enabled, mirrored. +- External: Default monitor disabled, selected monitors enabled. + **If you have multiple monitors, follow these steps to configure your preferred external monitor:** 1. Press Win + P and select the "External" option. 2. Go to Windows Settings > System > Displays. 3. Choose your desired monitor setup by disconnecting all monitors except the one you want to use in game mode. -4. Press Win + P again and switch back to "Internal." +4. Press Win + P again and switch back to "Internal" or "Extend". -If you'd prefer to skip the screen configuration, simply select `Clone screen instead of switching`. +If you'd prefer to skip the screen configuration, simply select `Clone` in BigPictureTV settings. **You're all set!** You can now close the settings window. ## Build diff --git a/assets/screenshot.png b/assets/screenshot.png index d64f582..9738e9a 100644 Binary files a/assets/screenshot.png and b/assets/screenshot.png differ diff --git a/requirements.txt b/requirements.txt index d03eb53..6caa438 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/src/BigPictureTV.py b/src/BigPictureTV.py index eb2954c..08d439a 100644 --- a/src/BigPictureTV.py +++ b/src/BigPictureTV.py @@ -4,9 +4,10 @@ import darkdetect from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QMainWindow from PyQt6.QtGui import QIcon, QAction -from PyQt6.QtCore import QTimer, QSharedMemory +from PyQt6.QtCore import Qt, QTimer, QSharedMemory from design import Ui_MainWindow -from monitor_manager import enable_clone_mode, enable_external_mode, enable_internal_mode +from tooltiped_slider import TooltipedSlider +from monitor_manager import enable_clone_mode, enable_external_mode, enable_internal_mode, enable_extend_mode from audio_manager import switch_audio from mode_manager import Mode, read_current_mode, write_current_mode from shortcut_manager import check_startup_shortcut, handle_startup_checkbox_state_changed @@ -37,6 +38,7 @@ def __init__(self): self.first_run = False self.paused = False self.timer = QTimer() + self.init_checkrate_tooltipedslider() self.load_settings() self.initialize_ui() self.get_audio_capabilities() @@ -50,19 +52,44 @@ def __init__(self): self.first_run = False def initialize_ui(self): + self.ui.gamemode_monitor_combobox.addItems([" External", " Clone"]) + self.ui.desktop_monitor_combobox.addItems([" Internal", " Extend"]) self.ui.disableAudioCheckbox.stateChanged.connect(self.handle_disableaudio_checkbox_state_changed) self.ui.startupCheckBox.stateChanged.connect(handle_startup_checkbox_state_changed) self.ui.gamemodeEntry.textChanged.connect(self.save_settings) self.ui.desktopEntry.textChanged.connect(self.save_settings) - self.ui.checkRateSpinBox.valueChanged.connect(self.save_settings) self.ui.startupCheckBox.setChecked(check_startup_shortcut()) - self.ui.clone_checkbox.stateChanged.connect(self.save_settings) self.ui.close_discord_checkbox.stateChanged.connect(self.save_settings) - self.ui.close_discord_checkbox.setEnabled(is_discord_installed()) self.ui.close_discord_label.setEnabled(is_discord_installed()) + self.ui.gamemode_monitor_combobox.currentIndexChanged.connect(self.save_settings) + self.ui.desktop_monitor_combobox.currentIndexChanged.connect(self.save_settings) + self.ui.checkrate_slider.valueChanged.connect(self.handle_checkrate_slider_value_changed) + self.ui.checkrate_slider.sliderReleased.connect(self.save_settings) - self.apply_settings() + def init_checkrate_tooltipedslider(self): + self.checkrate_tooltipedslider = TooltipedSlider(Qt.Orientation.Horizontal, self.ui.centralwidget) + layout = self.ui.settings_layout + index = layout.indexOf(self.ui.checkrate_label) + position = layout.getItemPosition(index) + row = position[0] + column = position[1] + layout.addWidget(self.checkrate_tooltipedslider, row, column + 1) + self.ui.checkrate_slider.deleteLater() + self.ui.checkrate_slider = self.checkrate_tooltipedslider + self.ui.checkrate_slider.setTickInterval(100) + self.ui.checkrate_slider.setRange(100, 1000) + + def handle_checkrate_slider_value_changed(self, value): + remainder = value % 100 + if remainder != 0: + if remainder < 50: + value -= remainder + else: + value += 100 - remainder + self.ui.checkrate_slider.setValue(value) + + self.update_mode_timer_interval(value) def handle_disableaudio_checkbox_state_changed(self, state): self.toggle_audio_settings(not state) @@ -100,19 +127,21 @@ def apply_settings(self): self.ui.gamemodeEntry.setText(self.settings.get("GAMEMODE_AUDIO", "")) self.ui.desktopEntry.setText(self.settings.get("DESKTOP_AUDIO", "")) self.ui.disableAudioCheckbox.setChecked(self.settings.get("DisableAudioSwitch", False)) - self.ui.checkRateSpinBox.setValue(self.settings.get("CheckRate", 1000)) + self.ui.checkrate_slider.setValue(self.settings.get("CheckRate", 1000)) self.toggle_audio_settings(not self.ui.disableAudioCheckbox.isChecked()) - self.ui.clone_checkbox.setChecked(self.settings.get("use_clone", False)) self.ui.close_discord_checkbox.setChecked(self.settings.get("discord_action", False)) + self.ui.gamemode_monitor_combobox.setCurrentIndex(self.settings.get("GAMEMODE_MONITOR", 0)) + self.ui.desktop_monitor_combobox.setCurrentIndex(self.settings.get("DESKTOP_MONITOR", 0)) def save_settings(self): self.settings = { "GAMEMODE_AUDIO": self.ui.gamemodeEntry.text(), "DESKTOP_AUDIO": self.ui.desktopEntry.text(), "DisableAudioSwitch": self.ui.disableAudioCheckbox.isChecked(), - "CheckRate": self.ui.checkRateSpinBox.value(), - "use_clone": self.ui.clone_checkbox.isChecked(), + "CheckRate": self.ui.checkrate_slider.value(), "discord_action": self.ui.close_discord_checkbox.isChecked(), + "GAMEMODE_MONITOR": self.ui.gamemode_monitor_combobox.currentIndex(), + "DESKTOP_MONITOR": self.ui.desktop_monitor_combobox.currentIndex(), } os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True) with open(SETTINGS_FILE, "w") as f: @@ -138,12 +167,14 @@ def switch_mode(self, mode): self.update_tray_icon() def switch_screen(self, screen): - if screen == "gamemode" and self.ui.clone_checkbox.isChecked(): - enable_clone_mode() - elif screen == "gamemode" and not self.ui.clone_checkbox.isChecked(): + if screen == "gamemode" and self.ui.gamemode_monitor_combobox.currentIndex() == 0: enable_external_mode() - elif screen == "desktop": + elif screen == "gamemode" and self.ui.gamemode_monitor_combobox.currentIndex() == 1: + enable_clone_mode() + elif screen == "desktop" and self.ui.desktop_monitor_combobox.currentIndex() == 0: enable_internal_mode() + elif screen == "desktop" and self.ui.desktop_monitor_combobox.currentIndex() == 1: + enable_extend_mode() def get_audio_capabilities(self): if is_audio_device_cmdlets_installed() is False: diff --git a/src/design.py b/src/design.py index fe40eef..884bfcd 100644 --- a/src/design.py +++ b/src/design.py @@ -12,7 +12,7 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") - MainWindow.resize(372, 406) + MainWindow.resize(452, 371) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -22,13 +22,6 @@ def setupUi(self, MainWindow): self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName("gridLayout_2") - self.label_2 = QtWidgets.QLabel(parent=self.centralwidget) - self.label_2.setMinimumSize(QtCore.QSize(0, 25)) - font = QtGui.QFont() - font.setBold(True) - self.label_2.setFont(font) - self.label_2.setObjectName("label_2") - self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1) self.audioOutputLabel = QtWidgets.QLabel(parent=self.centralwidget) self.audioOutputLabel.setMinimumSize(QtCore.QSize(0, 25)) font = QtGui.QFont() @@ -36,59 +29,66 @@ def setupUi(self, MainWindow): self.audioOutputLabel.setFont(font) self.audioOutputLabel.setObjectName("audioOutputLabel") self.gridLayout_2.addWidget(self.audioOutputLabel, 2, 0, 1, 1) - self.settingsFrame = QtWidgets.QFrame(parent=self.centralwidget) - self.settingsFrame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) - self.settingsFrame.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.settingsFrame.setObjectName("settingsFrame") - self.gridLayout_5 = QtWidgets.QGridLayout(self.settingsFrame) - self.gridLayout_5.setContentsMargins(9, 9, 9, 9) - self.gridLayout_5.setSpacing(12) - self.gridLayout_5.setObjectName("gridLayout_5") + self.label_3 = QtWidgets.QLabel(parent=self.centralwidget) + self.label_3.setMinimumSize(QtCore.QSize(0, 25)) + font = QtGui.QFont() + font.setBold(True) + self.label_3.setFont(font) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 4, 0, 1, 2) + self.gridFrame = QtWidgets.QFrame(parent=self.centralwidget) + self.gridFrame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) + self.gridFrame.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.gridFrame.setObjectName("gridFrame") + self.gridLayout_3 = QtWidgets.QGridLayout(self.gridFrame) + self.gridLayout_3.setContentsMargins(9, 9, 9, 9) + self.gridLayout_3.setSpacing(12) + self.gridLayout_3.setObjectName("gridLayout_3") + self.close_discord_label = QtWidgets.QLabel(parent=self.gridFrame) + self.close_discord_label.setMinimumSize(QtCore.QSize(0, 25)) + self.close_discord_label.setObjectName("close_discord_label") + self.gridLayout_3.addWidget(self.close_discord_label, 0, 0, 1, 1) + self.close_discord_checkbox = QtWidgets.QCheckBox(parent=self.gridFrame) + self.close_discord_checkbox.setMinimumSize(QtCore.QSize(0, 25)) + self.close_discord_checkbox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) + self.close_discord_checkbox.setText("") + self.close_discord_checkbox.setObjectName("close_discord_checkbox") + self.gridLayout_3.addWidget(self.close_discord_checkbox, 0, 1, 1, 1) + self.gridLayout_2.addWidget(self.gridFrame, 5, 0, 1, 2) + self.gridFrame1 = QtWidgets.QFrame(parent=self.centralwidget) + self.gridFrame1.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) + self.gridFrame1.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.gridFrame1.setObjectName("gridFrame1") + self.gridLayout_4 = QtWidgets.QGridLayout(self.gridFrame1) + self.gridLayout_4.setContentsMargins(9, 9, 9, 9) + self.gridLayout_4.setSpacing(12) + self.gridLayout_4.setObjectName("gridLayout_4") + self.gamemode_monitor_combobox = QtWidgets.QComboBox(parent=self.gridFrame1) + self.gamemode_monitor_combobox.setMinimumSize(QtCore.QSize(0, 25)) + self.gamemode_monitor_combobox.setObjectName("gamemode_monitor_combobox") + self.gridLayout_4.addWidget(self.gamemode_monitor_combobox, 0, 2, 1, 1) + self.desktop_monitor_combobox = QtWidgets.QComboBox(parent=self.gridFrame1) + self.desktop_monitor_combobox.setMinimumSize(QtCore.QSize(0, 25)) + self.desktop_monitor_combobox.setObjectName("desktop_monitor_combobox") + self.gridLayout_4.addWidget(self.desktop_monitor_combobox, 1, 2, 1, 1) + self.label_8 = QtWidgets.QLabel(parent=self.gridFrame1) + self.label_8.setMinimumSize(QtCore.QSize(0, 25)) + self.label_8.setObjectName("label_8") + self.gridLayout_4.addWidget(self.label_8, 1, 0, 1, 1) + self.label_7 = QtWidgets.QLabel(parent=self.gridFrame1) + self.label_7.setMinimumSize(QtCore.QSize(0, 25)) + self.label_7.setObjectName("label_7") + self.gridLayout_4.addWidget(self.label_7, 0, 0, 1, 1) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) - self.gridLayout_5.addItem(spacerItem, 3, 1, 1, 1) - self.label_4 = QtWidgets.QLabel(parent=self.settingsFrame) - self.label_4.setMinimumSize(QtCore.QSize(0, 25)) - self.label_4.setObjectName("label_4") - self.gridLayout_5.addWidget(self.label_4, 3, 0, 1, 1) - self.disableAudioCheckbox = QtWidgets.QCheckBox(parent=self.settingsFrame) - self.disableAudioCheckbox.setMinimumSize(QtCore.QSize(0, 25)) - self.disableAudioCheckbox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) - self.disableAudioCheckbox.setText("") - self.disableAudioCheckbox.setObjectName("disableAudioCheckbox") - self.gridLayout_5.addWidget(self.disableAudioCheckbox, 1, 1, 1, 2) - self.label_5 = QtWidgets.QLabel(parent=self.settingsFrame) - self.label_5.setMinimumSize(QtCore.QSize(0, 25)) - self.label_5.setObjectName("label_5") - self.gridLayout_5.addWidget(self.label_5, 0, 0, 1, 1) - self.label_6 = QtWidgets.QLabel(parent=self.settingsFrame) - self.label_6.setMinimumSize(QtCore.QSize(0, 25)) - self.label_6.setObjectName("label_6") - self.gridLayout_5.addWidget(self.label_6, 1, 0, 1, 1) - self.checkRateSpinBox = QtWidgets.QSpinBox(parent=self.settingsFrame) - self.checkRateSpinBox.setMinimumSize(QtCore.QSize(90, 25)) - self.checkRateSpinBox.setFrame(False) - self.checkRateSpinBox.setMinimum(1) - self.checkRateSpinBox.setMaximum(1000) - self.checkRateSpinBox.setProperty("value", 1000) - self.checkRateSpinBox.setObjectName("checkRateSpinBox") - self.gridLayout_5.addWidget(self.checkRateSpinBox, 3, 2, 1, 1) - self.startupCheckBox = QtWidgets.QCheckBox(parent=self.settingsFrame) - self.startupCheckBox.setMinimumSize(QtCore.QSize(0, 25)) - self.startupCheckBox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) - self.startupCheckBox.setText("") - self.startupCheckBox.setObjectName("startupCheckBox") - self.gridLayout_5.addWidget(self.startupCheckBox, 0, 1, 1, 2) - self.label = QtWidgets.QLabel(parent=self.settingsFrame) + self.gridLayout_4.addItem(spacerItem, 0, 1, 1, 1) + self.gridLayout_2.addWidget(self.gridFrame1, 3, 1, 1, 1) + self.label = QtWidgets.QLabel(parent=self.centralwidget) self.label.setMinimumSize(QtCore.QSize(0, 25)) + font = QtGui.QFont() + font.setBold(True) + self.label.setFont(font) self.label.setObjectName("label") - self.gridLayout_5.addWidget(self.label, 2, 0, 1, 1) - self.clone_checkbox = QtWidgets.QCheckBox(parent=self.settingsFrame) - self.clone_checkbox.setMinimumSize(QtCore.QSize(0, 25)) - self.clone_checkbox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) - self.clone_checkbox.setText("") - self.clone_checkbox.setObjectName("clone_checkbox") - self.gridLayout_5.addWidget(self.clone_checkbox, 2, 2, 1, 1) - self.gridLayout_2.addWidget(self.settingsFrame, 1, 0, 1, 1) + self.gridLayout_2.addWidget(self.label, 2, 1, 1, 1) self.audioFrame = QtWidgets.QFrame(parent=self.centralwidget) self.audioFrame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) self.audioFrame.setObjectName("audioFrame") @@ -117,51 +117,78 @@ def setupUi(self, MainWindow): self.gamemodeEntry.setObjectName("gamemodeEntry") self.gridLayout.addWidget(self.gamemodeEntry, 0, 1, 1, 1) self.gridLayout_2.addWidget(self.audioFrame, 3, 0, 1, 1) - self.label_3 = QtWidgets.QLabel(parent=self.centralwidget) - self.label_3.setMinimumSize(QtCore.QSize(0, 25)) + self.settingsFrame = QtWidgets.QFrame(parent=self.centralwidget) + self.settingsFrame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) + self.settingsFrame.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.settingsFrame.setObjectName("settingsFrame") + self.settings_layout = QtWidgets.QGridLayout(self.settingsFrame) + self.settings_layout.setContentsMargins(9, 9, 9, 9) + self.settings_layout.setSpacing(12) + self.settings_layout.setObjectName("settings_layout") + self.label_5 = QtWidgets.QLabel(parent=self.settingsFrame) + self.label_5.setMinimumSize(QtCore.QSize(0, 25)) + self.label_5.setObjectName("label_5") + self.settings_layout.addWidget(self.label_5, 0, 0, 1, 1) + self.label_6 = QtWidgets.QLabel(parent=self.settingsFrame) + self.label_6.setMinimumSize(QtCore.QSize(0, 25)) + self.label_6.setObjectName("label_6") + self.settings_layout.addWidget(self.label_6, 1, 0, 1, 1) + self.checkrate_label = QtWidgets.QLabel(parent=self.settingsFrame) + self.checkrate_label.setMinimumSize(QtCore.QSize(0, 25)) + self.checkrate_label.setObjectName("checkrate_label") + self.settings_layout.addWidget(self.checkrate_label, 2, 0, 1, 1) + self.disableAudioCheckbox = QtWidgets.QCheckBox(parent=self.settingsFrame) + self.disableAudioCheckbox.setMinimumSize(QtCore.QSize(0, 25)) + self.disableAudioCheckbox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) + self.disableAudioCheckbox.setText("") + self.disableAudioCheckbox.setObjectName("disableAudioCheckbox") + self.settings_layout.addWidget(self.disableAudioCheckbox, 1, 1, 1, 1) + self.startupCheckBox = QtWidgets.QCheckBox(parent=self.settingsFrame) + self.startupCheckBox.setMinimumSize(QtCore.QSize(0, 25)) + self.startupCheckBox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) + self.startupCheckBox.setText("") + self.startupCheckBox.setObjectName("startupCheckBox") + self.settings_layout.addWidget(self.startupCheckBox, 0, 1, 1, 1) + self.checkrate_slider = QtWidgets.QSlider(parent=self.settingsFrame) + self.checkrate_slider.setMinimumSize(QtCore.QSize(0, 25)) + self.checkrate_slider.setMinimum(100) + self.checkrate_slider.setMaximum(1000) + self.checkrate_slider.setSingleStep(100) + self.checkrate_slider.setPageStep(100) + self.checkrate_slider.setProperty("value", 1000) + self.checkrate_slider.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.checkrate_slider.setTickPosition(QtWidgets.QSlider.TickPosition.TicksBothSides) + self.checkrate_slider.setTickInterval(100) + self.checkrate_slider.setObjectName("checkrate_slider") + self.settings_layout.addWidget(self.checkrate_slider, 2, 1, 1, 1) + self.gridLayout_2.addWidget(self.settingsFrame, 1, 0, 1, 2) + self.label_2 = QtWidgets.QLabel(parent=self.centralwidget) + self.label_2.setMinimumSize(QtCore.QSize(0, 25)) font = QtGui.QFont() font.setBold(True) - self.label_3.setFont(font) - self.label_3.setObjectName("label_3") - self.gridLayout_2.addWidget(self.label_3, 4, 0, 1, 1) - self.gridFrame = QtWidgets.QFrame(parent=self.centralwidget) - self.gridFrame.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) - self.gridFrame.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.gridFrame.setObjectName("gridFrame") - self.gridLayout_3 = QtWidgets.QGridLayout(self.gridFrame) - self.gridLayout_3.setContentsMargins(9, 9, 9, 9) - self.gridLayout_3.setSpacing(12) - self.gridLayout_3.setObjectName("gridLayout_3") - self.close_discord_label = QtWidgets.QLabel(parent=self.gridFrame) - self.close_discord_label.setMinimumSize(QtCore.QSize(0, 25)) - self.close_discord_label.setObjectName("close_discord_label") - self.gridLayout_3.addWidget(self.close_discord_label, 0, 0, 1, 1) - self.close_discord_checkbox = QtWidgets.QCheckBox(parent=self.gridFrame) - self.close_discord_checkbox.setMinimumSize(QtCore.QSize(0, 25)) - self.close_discord_checkbox.setLayoutDirection(QtCore.Qt.LayoutDirection.RightToLeft) - self.close_discord_checkbox.setText("") - self.close_discord_checkbox.setObjectName("close_discord_checkbox") - self.gridLayout_3.addWidget(self.close_discord_checkbox, 0, 1, 1, 1) - self.gridLayout_2.addWidget(self.gridFrame, 5, 0, 1, 1) + self.label_2.setFont(font) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 2) MainWindow.setCentralWidget(self.centralwidget) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) MainWindow.setTabOrder(self.startupCheckBox, self.disableAudioCheckbox) - MainWindow.setTabOrder(self.disableAudioCheckbox, self.checkRateSpinBox) - MainWindow.setTabOrder(self.checkRateSpinBox, self.gamemodeEntry) + MainWindow.setTabOrder(self.disableAudioCheckbox, self.gamemodeEntry) MainWindow.setTabOrder(self.gamemodeEntry, self.desktopEntry) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.label_2.setText(_translate("MainWindow", "Settings")) self.audioOutputLabel.setText(_translate("MainWindow", "Audio output configuration")) - self.label_4.setText(_translate("MainWindow", "Window check rate")) - self.label_5.setText(_translate("MainWindow", "Run at startup")) - self.label_6.setText(_translate("MainWindow", "Disable audio switching")) - self.label.setText(_translate("MainWindow", "Clone screen instead of switching")) - self.gamemodeLabel.setText(_translate("MainWindow", "Gamemode")) - self.desktopLabel.setText(_translate("MainWindow", "Desktop")) self.label_3.setText(_translate("MainWindow", "Gamemode actions")) self.close_discord_label.setText(_translate("MainWindow", "Close discord")) + self.label_8.setText(_translate("MainWindow", "Desktop")) + self.label_7.setText(_translate("MainWindow", "Gamemode")) + self.label.setText(_translate("MainWindow", "Monitor configuration")) + self.gamemodeLabel.setText(_translate("MainWindow", "Gamemode")) + self.desktopLabel.setText(_translate("MainWindow", "Desktop")) + self.label_5.setText(_translate("MainWindow", "Run at startup")) + self.label_6.setText(_translate("MainWindow", "Disable audio switching")) + self.checkrate_label.setText(_translate("MainWindow", "Window check rate")) + self.label_2.setText(_translate("MainWindow", "Settings")) diff --git a/src/monitor_manager.py b/src/monitor_manager.py index 788ad39..df843bc 100644 --- a/src/monitor_manager.py +++ b/src/monitor_manager.py @@ -4,6 +4,7 @@ CLONE = "/clone" INTERNAL = "/internal" EXTERNAL = "/external" +EXTEND = "/extend" def enable_clone_mode(): @@ -16,3 +17,7 @@ def enable_external_mode(): def enable_internal_mode(): subprocess.run([DISPLAYSWITCH, INTERNAL]) + + +def enable_extend_mode(): + subprocess.run([DISPLAYSWITCH, EXTEND]) diff --git a/src/tooltiped_slider.py b/src/tooltiped_slider.py new file mode 100644 index 0000000..e8495e8 --- /dev/null +++ b/src/tooltiped_slider.py @@ -0,0 +1,25 @@ +from PyQt6.QtWidgets import QSlider, QToolTip +from PyQt6.QtCore import QPoint +from PyQt6.QtGui import QMouseEvent + + +class TooltipedSlider(QSlider): + def __init__(self, orientation, parent=None): + super().__init__(orientation, parent) + + def mousePressEvent(self, event: QMouseEvent): + super().mousePressEvent(event) + self.show_tooltip() + + def mouseMoveEvent(self, event: QMouseEvent): + super().mouseMoveEvent(event) + self.show_tooltip() + + def show_tooltip(self): + value = self.value() + position = QPoint(self.value_to_pixel_pos(value), -25) + QToolTip.showText(self.mapToGlobal(position), str(value), self) + + def value_to_pixel_pos(self, value): + """Convert slider value to pixel position along the slider""" + return int((value - self.minimum()) / (self.maximum() - self.minimum()) * self.width()) diff --git a/src/ui/design.ui b/src/ui/design.ui index 5b360c7..65aa126 100644 --- a/src/ui/design.ui +++ b/src/ui/design.ui @@ -6,8 +6,8 @@ 0 0 - 372 - 406 + 452 + 371 @@ -20,9 +20,9 @@ MainWindow - - - + + + 0 @@ -35,12 +35,12 @@ - Settings + Audio output configuration - - + + 0 @@ -53,19 +53,19 @@ - Audio output configuration + Gamemode actions - - + + QFrame::Shape::StyledPanel QFrame::Shadow::Sunken - + 9 @@ -81,21 +81,8 @@ 12 - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - + + 0 @@ -103,12 +90,12 @@ - Window check rate + Close discord - - + + 0 @@ -123,72 +110,68 @@ - - + + + + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Sunken + + + + 9 + + + 9 + + + 9 + + + 9 + + + 12 + + + 0 25 - - Run at startup - - - + + 0 25 - - Disable audio switching - - - - - - 90 - 25 - - - - false - - - 1 - - - 1000 - - - 1000 - - - - - + + 0 25 - - Qt::LayoutDirection::RightToLeft - - + Desktop - - + + 0 @@ -196,29 +179,44 @@ - Clone screen instead of switching + Gamemode - - - + + + + Qt::Orientation::Horizontal + + - 0 - 25 + 40 + 20 - - Qt::LayoutDirection::RightToLeft - - - - - + + + + + + 0 + 25 + + + + + true + + + + Monitor configuration + + + @@ -301,33 +299,15 @@ - - - - - 0 - 25 - - - - - true - - - - Gamemode actions - - - - - + + QFrame::Shape::StyledPanel QFrame::Shadow::Sunken - + 9 @@ -344,7 +324,7 @@ 12 - + 0 @@ -352,12 +332,54 @@ - Close discord + Run at startup + + + + + + + + 0 + 25 + + + + Disable audio switching + + + + + + + + 0 + 25 + + + + Window check rate + + + + + + + + 0 + 25 + + + + Qt::LayoutDirection::RightToLeft + + + - + 0 @@ -372,16 +394,67 @@ + + + + + 0 + 25 + + + + 100 + + + 1000 + + + 100 + + + 100 + + + 1000 + + + Qt::Orientation::Horizontal + + + QSlider::TickPosition::TicksBothSides + + + 100 + + + + + + + + 0 + 25 + + + + + true + + + + Settings + + + startupCheckBox disableAudioCheckbox - checkRateSpinBox gamemodeEntry desktopEntry