Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
bindir = /usr/bin
datadir = /usr/share

# Set this to the desired Qt-Version (5 or 6).
# Zero means all versions shall be supported.
Expand Down Expand Up @@ -65,12 +66,18 @@ install:
install -Dm 755 nbfc-qt.py $(DESTDIR)$(bindir)/nbfc-qt
install -Dm 755 nbfc-qt-tray.py $(DESTDIR)$(bindir)/nbfc-qt-tray
#install -Dm 755 nbfc-qt-config.py $(DESTDIR)$(bindir)/nbfc-qt-config
install -Dm 644 share/applications/nbfc-qt.desktop $(DESTDIR)$(datadir)/applications/nbfc-qt.desktop
install -Dm 644 share/applications/nbfc-qt-tray.desktop $(DESTDIR)$(datadir)/applications/nbfc-qt-tray.desktop
install -Dm 644 share/icons/hicolor/256x256/apps/nbfc-qt.png $(DESTDIR)$(datadir)/icons/hicolor/256x256/apps/nbfc-qt.png

uninstall:
rm -f $(DESTDIR)$(bindir)/nbfc-qt
rm -f $(DESTDIR)$(bindir)/nbfc-qt-tray
rm -f $(DESTDIR)$(bindir)/nbfc-qt-config

rm -f $(DESTDIR)$(datadir)/applications/nbfc-qt.desktop
rm -f $(DESTDIR)$(datadir)/applications/nbfc-qt-tray.desktop
rm -f $(DESTDIR)$(datadir)/icons/hicolor/256x256/apps/nbfc-qt.png

clean:
rm -rf __pycache__
rm -f nbfc-qt.py nbfc-qt-tray.py nbfc-qt-config.py
Expand Down
10 changes: 10 additions & 0 deletions share/applications/nbfc-qt-tray.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Desktop Entry]
Name=NBFC Qt Tray
Comment=NBFC fan control tray icon
Exec=nbfc-qt-tray
Icon=nbfc-qt
Terminal=false
Type=Application
Categories=System;Hardware;Settings;
StartupNotify=false
NoDisplay=true
9 changes: 9 additions & 0 deletions share/applications/nbfc-qt.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Desktop Entry]
Name=NBFC Qt
Comment=Qt-based GUI for NBFC-Linux fan control
Exec=nbfc-qt
Icon=nbfc-qt
Terminal=false
Type=Application
Categories=System;Hardware;Settings;
StartupNotify=false
Binary file added share/icons/hicolor/256x256/apps/nbfc-qt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 12 additions & 12 deletions src/client/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,40 +79,40 @@ def make_qt5_compatible():
if opts.qt_version is None:
try:
from PyQt6.QtWidgets import *
from PyQt6.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal
from PyQt6.QtGui import QAction, QPixmap
from PyQt6.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal, QSettings
from PyQt6.QtGui import QAction, QIcon, QPixmap
make_qt5_compatible()
except ImportError:
try:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal, QSettings
from PyQt5.QtGui import QIcon, QPixmap
except ImportError:
print("Please install Python Qt bindings (PyQt5 or PyQt6)")
sys.exit(1)

elif opts.qt_version == 5:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal, QSettings
from PyQt5.QtGui import QIcon, QPixmap

elif opts.qt_version == 6:
from PyQt6.QtWidgets import *
from PyQt6.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal
from PyQt6.QtGui import QAction, QPixmap
from PyQt6.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal, QSettings
from PyQt6.QtGui import QAction, QIcon, QPixmap
make_qt5_compatible()
#endif

#ifeq QT_VERSION 5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal, QSettings
from PyQt5.QtGui import QIcon, QPixmap
#endif

#ifeq QT_VERSION 6
from PyQt6.QtWidgets import *
from PyQt6.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal
from PyQt6.QtGui import QAction, QPixmap
from PyQt6.QtCore import Qt, QTimer, QThread, QObject, pyqtSignal, QSettings
from PyQt6.QtGui import QAction, QIcon, QPixmap
make_qt5_compatible()
#endif

Expand Down
64 changes: 51 additions & 13 deletions src/client/widgets/fan_widget.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
class JumpSlider(QSlider):
"""QSlider that jumps to the click position instead of page step."""

def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
if self.orientation() == Qt.Horizontal:
pos = event.pos().x()
else:
pos = event.pos().y()

# Click on track -> jump to position
val = QStyle.sliderValueFromPosition(
self.minimum(), self.maximum(), pos,
self.width() if self.orientation() == Qt.Horizontal else self.height()
)
cur = self.value()
diff = abs(val - cur)
if diff > 1:
self.setValue(val)

super().mousePressEvent(event)
# Always emit on press so update_fan_speed disables auto mode
self.valueChanged.emit(self.value())


class FanWidget(QWidget):
def __init__(self):
super().__init__()
Expand Down Expand Up @@ -68,31 +93,44 @@ def __init__(self):
# Slider
# =====================================================================

self.speed_slider = QSlider(Qt.Horizontal)
self.speed_slider = JumpSlider(Qt.Horizontal)
self.speed_slider.setMinimum(0)
self.speed_slider.setMaximum(100)
self.speed_slider.setTickInterval(1)
self.speed_slider.valueChanged.connect(self.update_fan_speed)
layout.addWidget(self.speed_slider)

def update_fan_speed(self, *_):
# Disable auto mode when user moves the slider
if self.sender() == self.speed_slider:
self.auto_mode_checkbox.setChecked(False)

auto_mode = self.auto_mode_checkbox.isChecked()

if auto_mode:
GLOBALS.nbfc_client.set_fan_speed('auto', self.fan_index)
GLOBALS.nbfc_client.set_fan_speed("auto", self.fan_index)
op = QGraphicsOpacityEffect()
op.setOpacity(0.4)
self.speed_slider.setGraphicsEffect(op)
self.auto_mode_checkbox.setGraphicsEffect(None)
else:
GLOBALS.nbfc_client.set_fan_speed(self.speed_slider.value(), self.fan_index)

self.speed_slider.setEnabled(not auto_mode)
self.speed_slider.setGraphicsEffect(None)
op = QGraphicsOpacityEffect()
op.setOpacity(0.4)
self.auto_mode_checkbox.setGraphicsEffect(op)

def update(self, fan_index, fan_data):
self.fan_index = fan_index
self.name_label.setText(fan_data['Name'])
self.temperature_label.setText(f'{fan_data['Temperature']:.2f}')
self.auto_mode_label.setText(str(fan_data['AutoMode']))
self.critical_label.setText(str(fan_data['Critical']))
self.current_speed_label.setText(f'{fan_data['CurrentSpeed']:.2f}')
self.target_speed_label.setText(f'{fan_data['TargetSpeed']:.2f}')
self.speed_steps_label.setText(str(fan_data['SpeedSteps']))
self.auto_mode_checkbox.setChecked(fan_data['AutoMode'])
self.speed_slider.setValue(int(fan_data['RequestedSpeed']))
self.name_label.setText(fan_data["Name"])
self.temperature_label.setText(f"{fan_data['Temperature']:.2f}")
self.auto_mode_label.setText(str(fan_data["AutoMode"]))
self.critical_label.setText(str(fan_data["Critical"]))
self.current_speed_label.setText(f"{fan_data['CurrentSpeed']:.2f}")
self.target_speed_label.setText(f"{fan_data['TargetSpeed']:.2f}")
self.speed_steps_label.setText(str(fan_data["SpeedSteps"]))
self.auto_mode_checkbox.setChecked(fan_data["AutoMode"])
# Block signals so setValue doesn't trigger valueChanged -> update_fan_speed
self.speed_slider.blockSignals(True)
self.speed_slider.setValue(int(fan_data["RequestedSpeed"]))
self.speed_slider.blockSignals(False)
73 changes: 52 additions & 21 deletions src/client/widgets/main_window.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import subprocess


class ImageLoaderWorker(QObject):
finished = pyqtSignal(bytes)

Expand All @@ -18,6 +19,7 @@ def run(self):
except Exception:
pass


class SponsorWidget(QLabel):
def __init__(self, parent):
super().__init__(parent)
Expand All @@ -27,24 +29,24 @@ def __init__(self, parent):
self.setAlignment(Qt.AlignCenter)

try:
sponsor = GLOBALS.nbfc_client.get_model_configuration()['Sponsor']
self.url = sponsor['URL']
sponsor = GLOBALS.nbfc_client.get_model_configuration()["Sponsor"]
self.url = sponsor["URL"]

if 'Description' in sponsor:
if "Description" in sponsor:
self.setToolTip(f"{sponsor['Name']} - {sponsor['Description']}")
else:
self.setToolTip(sponsor['Name'])
self.setToolTip(sponsor["Name"])

self.thread = QThread()
self.worker = ImageLoaderWorker(sponsor['BannerURL'])
self.worker = ImageLoaderWorker(sponsor["BannerURL"])
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.on_image_loaded)
self.worker.finished.connect(self.thread.quit)
self.thread.start()
except Exception:
pass

def on_image_loaded(self, content):
pixmap = QPixmap()
pixmap.loadFromData(content)
Expand All @@ -54,7 +56,8 @@ def on_image_loaded(self, content):

def mousePressEvent(self, event):
if self.url:
subprocess.run(['xdg-open', self.url])
subprocess.run(["xdg-open", self.url])


class MainWindow(QMainWindow):
def __init__(self):
Expand All @@ -66,6 +69,15 @@ def __init__(self):

self.setWindowTitle("NBFC Client")
self.resize(400, 400)
self.setWindowIcon(QIcon.fromTheme("nbfc-qt"))

# Restore window geometry from previous session
settings = QSettings("nbfc-qt", "nbfc-qt")
geom = settings.value("window/geometry")
if geom is not None:
self.restoreGeometry(geom)
else:
self.resize(400, 400)

# =====================================================================
# Container widget
Expand Down Expand Up @@ -94,22 +106,28 @@ def __init__(self):
# =====================================================================

self.widgets = {}
self.widgets['service'] = ServiceControlWidget()
self.widgets['fans'] = FanControlWidget()
self.widgets['basic'] = BasicConfigWidget()
self.widgets['sensors'] = TemperatureSourcesWidget()
self.widgets['update'] = UpdateWidget()
self.widgets['rated'] = RateConfigsWidget()

self.tab_widget.addTab(self.widgets['service'], "Service")
self.tab_widget.addTab(self.widgets['fans'], "Fans")
self.tab_widget.addTab(self.widgets['basic'], "Basic Configuration")
self.tab_widget.addTab(self.widgets['rated'], "Rated Configs")
self.tab_widget.addTab(self.widgets['sensors'], "Sensors")
self.tab_widget.addTab(self.widgets['update'], "Update")
self.widgets["service"] = ServiceControlWidget()
self.widgets["fans"] = FanControlWidget()
self.widgets["basic"] = BasicConfigWidget()
self.widgets["sensors"] = TemperatureSourcesWidget()
self.widgets["update"] = UpdateWidget()
self.widgets["rated"] = RateConfigsWidget()

self.tab_widget.addTab(self.widgets["service"], "Service")
self.tab_widget.addTab(self.widgets["fans"], "Fans")
self.tab_widget.addTab(self.widgets["basic"], "Basic Configuration")
self.tab_widget.addTab(self.widgets["rated"], "Rated Configs")
self.tab_widget.addTab(self.widgets["sensors"], "Sensors")
self.tab_widget.addTab(self.widgets["update"], "Update")

settings = QSettings("nbfc-qt", "nbfc-qt")
initial_tab = int(settings.value("window/active_tab", 0))
if initial_tab >= self.tab_widget.count():
initial_tab = 0

self.tab_widget.currentChanged.connect(self.tab_widget_changed)
self.tab_widget_changed(0)
self.tab_widget.setCurrentIndex(initial_tab)
self.tab_widget_changed(initial_tab)

# =====================================================================
# Set widget
Expand All @@ -133,6 +151,15 @@ def __init__(self):
quitAction.triggered.connect(lambda: QApplication.quit())
applicationMenu.addAction(quitAction)

# =========================================================================
# Events
# =========================================================================

def closeEvent(self, event):
settings = QSettings("nbfc-qt", "nbfc-qt")
settings.setValue("window/geometry", self.saveGeometry())
super().closeEvent(event)

# =========================================================================
# Public functions
# =========================================================================
Expand All @@ -154,3 +181,7 @@ def tab_widget_changed(self, current_index):
widget.start()
else:
widget.stop()

# Remember last active tab
settings = QSettings("nbfc-qt", "nbfc-qt")
settings.setValue("window/active_tab", current_index)
Loading