Merge branch 'cpplib'

This commit is contained in:
Jussi Kuokkanen 2023-09-26 18:08:10 +03:00
commit 883f6dd88e
189 changed files with 11592 additions and 40295 deletions

20
.clang-format Normal file
View File

@ -0,0 +1,20 @@
Language: Cpp
# Unindent access modifiers after regular indenting
AccessModifierOffset: -8
AlignAfterOpenBracket: DontAlign
AllowShortEnumsOnASingleLine: false
ColumnLimit: 100
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: Never
IndentAccessModifiers: false
IndentPPDirectives: BeforeHash
IndentWrappedFunctionNames: true
PointerAlignment: Right
SortIncludes: Never # src/plugins/Nvidia.cpp breaks with this on
SpaceAfterCStyleCast: true
# 8 column tabs
IndentWidth: 8
TabWidth: 8
UseTab: Always

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build
/*kdev*
**/*swp*

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "src/include/deps/patterns"]
path = src/include/deps/patterns
url = https://github.com/mpark/patterns
[submodule "src/include/deps/FunctionalPlus"]
path = src/include/deps/FunctionalPlus
url = https://github.com/Dobiasd/FunctionalPlus

191
README.md
View File

@ -1,33 +1,49 @@
# TuxClocker - A GUI overclocking utility for GNU/Linux
# About
TuxClocker is a hardware controlling and monitoring program. TuxClocker consists of a DBus daemon and a Qt GUI that uses the daemon.
TuxClocker is a Qt5 overclocking tool. Currently supported cards are nvidia 600-series cards and newer, and AMD GPUs using the amdgpu driver until (not including) Radeon VII.
## Chat
If you have any questions or suggestions, you can join the chat on [Matrix](https://matrix.to/#/#tuxclocker:matrix.org) or [IRC](https://webchat.oftc.net/?nick=&channels=%23tuxclocker&uio=d4)
# Support
You can use IRC if you don't want to create an account. Both chats are bridged between each other.
Matrix room: #tuxclocker:matrix.org [Direct Riot link](https://riot.im/app/#/room/#tuxclocker:matrix.org)
# Screenshots
![Imgur](https://i.imgur.com/fn8MoNj.png) ![Imgur](https://i.imgur.com/fuKIVW7.png) ![Imgur](https://i.imgur.com/cZCNzmN.png) ![Imgur](https://i.imgur.com/qkp2p7V.png) ![Imgur](https://i.imgur.com/TpmU8PD.png)
# Current features
- GPU monitoring (list and graph)
- Overclocking
- Overvolting
- Change power limit
- (AMD) pstate editing
- Fan mode selection
- Custom fan curve
- Provisional multi-GPU support
## Features
- Supports any number of devices at once
- Read and write device properties (Click on a selected node to edit)
- Connect any writable property to any readable property, for more possibilities than just fan curves. Currently only possible with range-based writable properties (Right click on a node)
- Reset writable properties to default (Right click on a node)
- Profiles
- Option to apply profile settings on startup/profile change
# Prerequisites
### Currently missing from earlier releases
These are missing from the 1.0.0 release, but present in the 0.1.1 release.
Refer to the [0.1.1 release readme](https://github.com/Lurkki14/tuxclocker/tree/76369ef24283364b4111c5970797062432044cbc) if you wish to use these.
For AMD under any distribution:
- AMD GPU support
- Minimize to tray
- Graphs for properties
- NOTE: headers are usually included in a package named \*-dev, if they are separate
- libdrm and headers
## Currently supported devices and features
### Nvidia GPUs
#### 600 -series and above
- Support for multiple fans on one GPU
- Fan mode
- Fan speed
- Core and memory clocks
- Power limit
- Temperatures
- Utilizations
- Voltage reading
#### 600 to 900 -series
- Voltage setting
## Possible future improvements
- Support for more devices
- Support for more platforms than Linux
- Easier to discover UI
- CLI interface
For AMD under Ubuntu:
@ -66,55 +82,118 @@ For Nvidia under Ubuntu:
# Installation (nvidia)
### Compilation
## Prerequisites
NVIDIA GPUs require [Coolbits](https://wiki.archlinux.org/index.php/NVIDIA/Tips_and_tricks#Enabling_overclocking) set to enable editing of most writable properties (31 for all functionality)
NOTE: on some systems, qmake is linked to qt4-qmake. If qmake fails, run qmake-qt5 in place of qmake
## Using prebuilt binaries
You can use the `tuxclocker.tar` from the release page if you don't want to compile. The tarball is generated from the `mkTarball.sh` script.
- Download the tarball into some empty directory
- Extract the contents eg. (`tar xf tuxlocker.tar`)
- Run `sudo echo && ./run.sh` in the same folder (sudo is needed for the daemon)
## Dependencies
`qt (charts, base, dbus), boost-system, boost-filesystem, libnvml (cuda), libxnvctrl, xlib, libdrm, meson`
Note that these packages are likely called something different on each distribution.
#### For Nix
`nix-shell release.nix`
#### For Ubuntu (possibly outdated)
```
sudo apt install --yes --quiet --quiet \
libqt5x11extras5-dev \
qtbase5-dev \
libqt5x11extras5 \
libdrm-amdgpu1 \
libdrm-common \
libdrm-dev \
nvidia-utils-440-server \
nvidia-settings \
libxnvctrl-dev
```
## Compiling
#### Meson options
```
--prefix=<path> (install location prefix, usually '/usr')
-Dplugins=<true/false>
-Ddaemon=<true/false> (builds and installs 'tuxclockerd' binary/daemon)
```
#### Clone, build and install
```
git clone https://github.com/Lurkki14/tuxclocker
cd tuxclocker
qmake rojekti.pro
make
make install (installs into /opt/tuxclocker/bin)
git checkout cpplib
git submodule init
git submodule update
meson build <meson options>
cd build
ninja && sudo ninja install
```
### Arch Linux
#### Running
Once you have installed everything into a proper location, TuxClocker is available with `tuxclocker-qt` from the terminal. There is currently no desktop entry so TuxClocker won't come up in any launcher.
#### AUR package
[https://aur.archlinux.org/packages/tuxclocker/](https://aur.archlinux.org/packages/tuxclocker/)
If TuxClocker shows up with no items, there may be a problem with connecting to the DBus daemon. Refer to your system documentation on where DBus system service entries should be located. Alternatively, you can launch the needed components manually as explained in the Developing/Scripts section of the README.
# Requirements (AMD)
# Screenshots
- amdgpu.ppfeaturemask boot paramter set to the value you want. To view the current value, run
### Main view
![Main view](screenshots/mainview.png)
### Editing an item
![Editing an item](screenshots/itemedit.png)
### Parametrizing an item
![Parametrizing an item](screenshots/paramEditor.png)
### Showing pending changes
![Showing pending changes](screenshots/stateChange.png)
### Settings
![Settings](screenshots/settings.png)
# Developing
### Formatting
TuxClocker uses `clang-format`. Code should be formatted with the provided `clangFormat.sh` script.
NOTE: to get designated initializers formatted like so:
``` cpp
auto Foo = Foo{
.bar = 1,
.baz = 2,
};
```
printf "0x%08x\n" $(cat /sys/module/amdgpu/parameters/ppfeaturemask)
```
a trailing comma should be used after the last member (`clang-format` weirdness).
Example grub line (usually /etc/default/grub):
### Scripts
```
GRUB_CMDLINE_LINUX_DEFAULT="quiet radeon.si_support=0 amdgpu.si_support=1 amdgpu.dpm=1 amdgpu.ppfeaturemask=0xffffffff"
```
After editing, update grub, usually
There are a few scripts in `dev/` for development convenience, mainly to deal with DBus. A separate DBus instance and custom config file is used, so the TuxClocker daemon is able to be registered without installing service files into the system.
```
sudo update-grub
```
Note: the following scripts assume TuxClocker is installed to `inst/`, so `meson` should be called as follows:
# Installation (AMD)
`meson build --prefix=$(pwd)/inst`
### Compilation
NOTE: on some, systems, qmake is linked to qt4-qmake. If qmake fails, run qmake-qt5 in place of qmake
The scripts should be used in this order (they all have to be running simultaneously, so probably best to run in separate terminals):
```
git clone https://github.com/Lurkki14/tuxclocker
cd tuxclocker
git checkout pstatetest
qmake rojekti.pro
make
make install (installs into /opt/tuxclocker/bin)
```
NOTE: to use fancurves on the AMD version, you need to run as root.
`dev/dbus-start.sh` Starts a separate DBus instance.
`dev/tuxclockerd-start.sh` Launches `tuxclockerd` making it connect to our separate DBus instance and LD_LIBRARY_PATH set to find the built `libtuxclocker`.
`dev/gui-start.sh` Launches the TuxClocker GUI making it connect to our separate DBus instance, so it can find the TuxClocker DBus service.
You can also use a program like `d-feet` if you are only making changes to the daemon. (To be documented)

8
clangFormat.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env sh
cppPat=(-name "*.cpp" -o -name "*.hpp" -o -name "*.h")
clang-format -i $(
(find src/tuxclocker-qt/ ${cppPat[@]}
find src/tuxclockerd/ ${cppPat[@]}
find src/plugins/ ${cppPat[@]}
find src/lib/ ${cppPat[@]}
find src/include/ -maxdepth 1 ${cppPat[@]}))

BIN
cog.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

60
default.nix Normal file
View File

@ -0,0 +1,60 @@
{ lib
, stdenv
, boost
, cmake
, cudatoolkit
, git
, fetchFromGitHub
, libdrm
, libX11
, libXext
, libXNVCtrl # this is supposed to work, but with the qt5.callPackages thing doesn't?
, meson
, mkDerivation
, ninja
, nvidia_x11
, pkg-config
, qtbase
, qtcharts
}:
mkDerivation rec {
pname = "tuxclocker";
version = "0.1";
src = fetchFromGitHub {
fetchSubmodules = true;
owner = "Lurkki14";
repo = "tuxclocker";
rev = "8460bce332e399a1e9bb2eaff128ae71ce9d2f6a";
hash = "sha256-ez8NMJ5Lrx0X2xBj2WE6eG7xDoC16y5IK8qBdSFkm/M=";
};
# meson 0.57 should fix having to have these
BOOST_INCLUDEDIR = "${lib.getDev boost}/include";
BOOST_LIBRARYDIR = "${lib.getLib boost}/lib";
preConfigure = ''
NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -I${libXNVCtrl}/include"
NIX_LDFLAGS="$NIX_LDFLAGS -L${libXNVCtrl}/lib"
'';
nativeBuildInputs = [
pkg-config
];
buildInputs = [
boost
cudatoolkit
libdrm
libXext
libX11
libXNVCtrl
meson
ninja
nvidia_x11
pkg-config
qtbase
qtcharts
];
}

3
dev/dbus-start.sh Executable file
View File

@ -0,0 +1,3 @@
cd "$(dirname "$0")"
DBUS_VERBOSE=1 sudo dbus-daemon --config-file=dbusconf.conf --nofork

89
dev/dbusconf.conf Normal file
View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE busconfig SYSTEM "busconfig.dtd">
<busconfig>
<!-- Our well-known bus type, do not change this -->
<type>system</type>
<!-- Run as special user -->
<user>messagebus</user>
<!--<user>root</user>-->
<!-- Fork into daemon mode -->
<fork/>
<!-- We use system service launching using a helper -->
<!-- This is a setuid helper that is used to launch system services -->
<!-- Write a pid file -->
<!--pidfile>/run/dbus/pid</pidfile -->
<!--<pidfile>/home/jussi/tmp/dbus-pidfile</pidfile> -->
<!-- Enable logging to syslog -->
<syslog/>
<!-- Only allow socket-credentials-based authentication -->
<auth>EXTERNAL</auth>
<!-- Only listen on a local socket. (abstract=/path/to/socket
means use abstract namespace, don't really create filesystem
file; only Linux supports this. Use path=/whatever on other
systems.) -->
<!--listen>unix:path=/run/dbus/system_bus_socket</listen -->
<listen>unix:path=/tmp/tuxclocker-dbus-socket</listen>
<policy context="default">
<!-- All users can connect to system bus -->
<allow user="*"/>
<!-- Allow anyone to own -->
<allow own="*"/>
<allow send_type="method_call"/>
<!-- Signals and reply messages (method returns, errors) are allowed
by default -->
<allow send_type="signal"/>
<allow send_requested_reply="true" send_type="method_return"/>
<allow send_requested_reply="true" send_type="error"/>
<!-- All messages may be received by default -->
<allow receive_type="method_call"/>
<allow receive_type="method_return"/>
<allow receive_type="error"/>
<allow receive_type="signal"/>
<!-- Allow anyone to talk to the message bus -->
<allow send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.DBus"/>
<allow send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.DBus.Introspectable"/>
<allow send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.DBus.Properties"/>
<!-- But disallow some specific bus services -->
<deny send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.DBus" send_member="UpdateActivationEnvironment"/>
<deny send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.DBus.Debug.Stats"/>
<deny send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.systemd1.Activator"/>
</policy>
<!-- Only systemd, which runs as root, may report activation failures. -->
<policy user="root">
<allow send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.systemd1.Activator"/>
</policy>
<!-- root may monitor the system bus. -->
<policy user="root">
<allow send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.DBus.Monitoring"/>
</policy>
<!-- If the Stats interface was enabled at compile-time, root may use it.
Copy this into system.local.conf or system.d/*.conf if you want to
enable other privileged users to view statistics and debug info -->
<policy user="root">
<allow send_destination="org.freedesktop.DBus" send_interface="org.freedesktop.DBus.Debug.Stats"/>
</policy>
<servicehelper>/run/wrappers/bin/dbus-daemon-launch-helper</servicehelper>
<includedir>/usr/share/dbus-1/system.d</includedir>
<servicedir>/usr/share/dbus-1/system-services</servicedir>
</busconfig>

3
dev/gui-start.sh Executable file
View File

@ -0,0 +1,3 @@
cd "$(dirname "$0")"
DBUS_SYSTEM_BUS_ADDRESS='unix:path=/tmp/tuxclocker-dbus-socket' ../inst/bin/tuxclocker-qt

3
dev/tuxclockerd-start.sh Executable file
View File

@ -0,0 +1,3 @@
cd "$(dirname "$0")"
DBUS_SYSTEM_BUS_ADDRESS=unix:path=/tmp/tuxclocker-dbus-socket sudo -E LD_LIBRARY_PATH=../inst/lib ../inst/bin/tuxclockerd

BIN
doc/tuxclocker_arch.odp Normal file

Binary file not shown.

View File

@ -1,374 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#include "editprofile.h"
#include "ui_editprofile.h"
#include "mainwindow.h"
editProfile::editProfile(QWidget *parent) :
QDialog(parent),
ui(new Ui::editProfile)
{
ui->setupUi(this);
// Get the main widget backgorund palette and use the colors for the plots
QPalette palette;
palette.setCurrentColorGroup(QPalette::Active);
QColor color = palette.color(QPalette::Background);
QColor textColor = palette.color(QPalette::Text);
QColor graphColor = palette.color(QPalette::Highlight);
QPen graphPen;
graphPen.setWidth(2);
graphPen.setColor(graphColor);
QPen tickPen;
tickPen.setWidthF(0.5);
tickPen.setColor(textColor);
// Define the filler line vectors and graphs so they don't need to be recreated every update
leftLineX.append(x_lower);
leftLineX.append(0);
leftLineY.append(0);
leftLineY.append(0);
rightLineX.append(0);
rightLineX.append(x_upper);
rightLineY.append(0);
rightLineY.append(0);
ui->curvePlot->addGraph();
ui->curvePlot->addGraph();
ui->curvePlot->addGraph();
ui->curvePlot->graph(0)->setScatterStyle(QCPScatterStyle::ssCircle);
ui->curvePlot->graph(0)->setLineStyle(QCPGraph::lsLine);
ui->curvePlot->setBackground(color);
ui->curvePlot->xAxis->setLabelColor(textColor);
ui->curvePlot->yAxis->setLabelColor(textColor);
ui->curvePlot->xAxis->setTickLabelColor(textColor);
ui->curvePlot->yAxis->setTickLabelColor(textColor);
ui->curvePlot->graph(0)->setPen(graphPen);
ui->curvePlot->graph(1)->setPen(graphPen);
ui->curvePlot->graph(2)->setPen(graphPen);
ui->curvePlot->xAxis->setTickPen(tickPen);
ui->curvePlot->yAxis->setTickPen(tickPen);
ui->curvePlot->xAxis->setSubTickPen(tickPen);
ui->curvePlot->yAxis->setSubTickPen(tickPen);
ui->curvePlot->xAxis->setBasePen(tickPen);
ui->curvePlot->yAxis->setBasePen(tickPen);
ui->curvePlot->xAxis->setLabel("Temperature (°C)");
ui->curvePlot->yAxis->setLabel("Fan speed (%)");
ui->curvePlot->xAxis->setRange(x_lower, (x_upper+5));
ui->curvePlot->yAxis->setRange(y_lower, (y_upper+5));
connect(ui->curvePlot, SIGNAL(mouseDoubleClick(QMouseEvent*)), SLOT(clickedGraph(QMouseEvent*)));
connect(ui->curvePlot, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)), SLOT(clickedPoint(QCPAbstractPlottable*,int,QMouseEvent*)));
connect(ui->curvePlot, SIGNAL(mousePress(QMouseEvent*)), SLOT(detectPress(QMouseEvent*)));
connect(ui->curvePlot, SIGNAL(mouseMove(QMouseEvent*)), SLOT(detectMove(QMouseEvent*)));
connect(ui->curvePlot, SIGNAL(mouseRelease(QMouseEvent*)), SLOT(detectRelease(QMouseEvent*)));
connect(ui->curvePlot, SIGNAL(mouseMove(QMouseEvent*)), SLOT(getClosestCoords(QMouseEvent*)));
connect(ui->curvePlot, SIGNAL(mouseMove(QMouseEvent*)), SLOT(drawPointHoveredText(QMouseEvent*)));
// Load the existing points to the graph
MainWindow mw;
for (int i=0; i<mw.xCurvePoints.length(); i++) {
qv_x.append(mw.xCurvePoints[i]);
qv_y.append(mw.yCurvePoints[i]);
}
ui->curvePlot->graph(0)->setData(qv_x, qv_y);
drawFillerLines();
QCPItemText *text = new QCPItemText(ui->curvePlot);
coordText = text;
coordText->setColor(textColor);
}
editProfile::~editProfile()
{
delete ui;
}
void editProfile::addPoint(double x, double y)
{
y = round(y);
x = round(x);
if (qv_x.length() != 0) {
checkForDuplicatePoint(x, y);
}
if ((x_lower<=x) && (x<=x_upper) && (y_lower<=y) && (y<=y_upper) && !duplicatePoint) {
qv_x.append(x);
qv_y.append(y);
index_y = qv_y.size()-1;
index_x = qv_x.size()-1;
}
}
void editProfile::rePlot()
{
ui->curvePlot->replot();
ui->curvePlot->update();
}
void editProfile::clickedGraph(QMouseEvent *event)
{
QPoint point = event->pos();
addPoint(ui->curvePlot->xAxis->pixelToCoord(point.x()), ui->curvePlot->yAxis->pixelToCoord(point.y()));
ui->curvePlot->graph(0)->setData(qv_x, qv_y);
drawFillerLines();
}
void editProfile::clickedPoint(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
{
checkForNearbyPoints(event);
ycoord = round(ycoord);
xcoord = round(xcoord);
if (isNearbyPoint && qv_x.length() != 0) {
for (int i=0; i<qv_y.length(); i++ ) {
qv_y[i] = round(qv_y[i]);
qv_x[i] = round(qv_x[i]);
if ((qv_x[i] == xcoord) && (qv_y[i] == ycoord)) {
qv_x.remove(i);
qv_y.remove(i);
break;
}
}
ui->curvePlot->graph(0)->setData(qv_x, qv_y);
rePlot();
drawFillerLines();
}
}
bool editProfile::checkForNearbyPoints(QMouseEvent *event)
{
if (qv_x.length() != 0) {
QPoint point = event->pos();
double pointerxcoord = ui->curvePlot->xAxis->pixelToCoord(point.x());
double pointerycoord = ui->curvePlot->yAxis->pixelToCoord(point.y());
for (int i=0; i<qv_x.length(); i++) {
if ((abs(pointerxcoord - qv_x[i]) < selectionPrecision) && abs(pointerycoord - qv_y[i]) < selectionPrecision) {
isNearbyPoint = true;
break;
} else {
isNearbyPoint = false;
}
}
return isNearbyPoint;
}
}
void editProfile::drawPointHoveredText(QMouseEvent *event)
{
checkForNearbyPoints(event);
if (isNearbyPoint && !draggingPoint) {
if (xcoord < x_upper*0.1) {
coordText->position->setCoords(x_upper*0.1, xcoord + 4);
} else if (xcoord > x_upper*0.9) {
coordText->position->setCoords(x_upper*0.9, xcoord + 4);
} else {
coordText->position->setCoords(xcoord, ycoord + 4);
}
coordText->setText(QString::number(xcoord) + ", " + QString::number(ycoord));
rePlot();
}
if (!isNearbyPoint && !draggingPoint) {
coordText->setText("");
rePlot();
}
}
int editProfile::getDataIndex(QCPAbstractPlottable *plottable, int dataIndex)
{
dataIndex = pointIndex;
return pointIndex;
}
bool editProfile::checkForDuplicatePoint(int x, int y)
// Return true if there is a duplicate point
{
for (int i=0; i<qv_x.length(); i++) {
qv_x[i] = round(qv_x[i]);
qv_y[i] = round(qv_y[i]);
if ((x == qv_x[i]) && (y == qv_y[i])) {
duplicatePoint = true;
}
}
return duplicatePoint;
}
void editProfile::getClosestCoords(QMouseEvent *event)
{
// Get the coordinates of the point that the mouse is over
if (qv_y.size() > 0) {
QPoint cursor = event->pos();
double pointerycoord = ui->curvePlot->yAxis->pixelToCoord(cursor.y());
double pointerxcoord = ui->curvePlot->xAxis->pixelToCoord(cursor.x());
for (int i=0; i<qv_y.size(); i++) {
if (sqrt(abs(pointerxcoord - qv_x[i]) * (abs(pointerxcoord - qv_x[i])) + abs(pointerycoord - qv_y[i]) * abs(pointerycoord - qv_y[i])) < selectionPrecision) {
xcoord = qv_x[i];
ycoord = qv_y[i];
break;
}
}
}
getPointIndices();
}
bool editProfile::detectMove(QMouseEvent *event)
{
mouseMoving = true;
if (mouseMoving && mousePressed) {
initializeDragging(event);
}
return mouseMoving;
}
bool editProfile::detectPress(QMouseEvent *event)
{
mousePressed = true;
if (mouseMoving && mousePressed) {
initializeDragging(event);
}
return mousePressed;
}
bool editProfile::initializeDragging(QMouseEvent *event)
{
// Decides whether to start dragging the point
if (!pressTimer->isActive() && !mouseDragging) {
pressTimer->start(20);
pressTimer->setSingleShot(true);
}
mouseDragging = true;
checkForNearbyPoints(event);
if ((isNearbyPoint || draggingPoint) && pressTimer->remainingTime() <= 0) {
dragPoint(index_x, index_y, event);
draggingPoint = true;
}
return mouseDragging;
}
void editProfile::getPointIndices()
{
if (qv_y.size() > 0) {
for (int i=0; i<qv_y.size(); i++) {
if ((qv_x[i] == xcoord) && (qv_y[i] == ycoord)) {
index_x = i;
index_y = i;
break;
}
}
}
}
void editProfile::dragPoint(int index_x, int index_y, QMouseEvent* event)
{
// Moves the selected point by index
// Sleep here so we don't take up so much CPU time
QThread::msleep(10);
QPoint point = event->pos();
qv_y[index_y] = round(ui->curvePlot->yAxis->pixelToCoord(point.y()));
qv_x[index_x] = round(ui->curvePlot->xAxis->pixelToCoord(point.x()));
if (qv_x[index_x] > x_upper) {
qv_x[index_x] = x_upper;
}
if (qv_x[index_x] < x_lower) {
qv_x[index_x] = x_lower;
}
if (qv_y[index_y] > y_upper) {
qv_y[index_y] = y_upper;
}
if (qv_y[index_y] < y_lower) {
qv_y[index_y] = y_lower;
}
// Display the coordinates
if (ui->curvePlot->xAxis->pixelToCoord(point.x()) < x_upper*0.1) {
coordText->position->setCoords(x_upper*0.1, qv_y[index_y] + 4);
} else if (ui->curvePlot->xAxis->pixelToCoord(point.x()) > x_upper*0.9) {
coordText->position->setCoords(x_upper*0.9, qv_y[index_y] + 4);
} else {
coordText->position->setCoords(qv_x[index_x], qv_y[index_y] + 4);
}
QString xString = QString::number(qv_x[index_x]);
QString yString = QString::number(qv_y[index_y]);
coordText->setText(xString + ", " + yString);
ui->curvePlot->graph(0)->setData(qv_x, qv_y);
drawFillerLines();
}
void editProfile::drawFillerLines()
{
// Draw the filler lines separately so they don't interfere with the main data. graph(1) = leftward line, graph(2) = rightward line
leftLineX[1] = ui->curvePlot->graph(0)->dataSortKey(0);
leftLineY[0] = ui->curvePlot->graph(0)->dataMainValue(0);
leftLineY[1] = ui->curvePlot->graph(0)->dataMainValue(0);
ui->curvePlot->graph(1)->setData(leftLineX, leftLineY);
rightLineX[0] = ui->curvePlot->graph(0)->dataSortKey(qv_x.length() -1);
rightLineY[0] = ui->curvePlot->graph(0)->dataMainValue(qv_x.length() -1);
rightLineY[1] = ui->curvePlot->graph(0)->dataMainValue(qv_x.length() -1);
ui->curvePlot->graph(2)->setData(rightLineX, rightLineY);
rePlot();
}
void editProfile::detectRelease(QMouseEvent *event)
{
mousePressed = false;
mouseMoving = false;
mouseDragging = false;
draggingPoint = false;
coordText->setText("");
rePlot();
}
void editProfile::on_saveButton_clicked()
{
QSettings settings("tuxclocker");
settings.beginGroup("General");
QString currentProfile = settings.value("currentProfile").toString();
QString latestUUID = settings.value("latestUUID").toString();
settings.endGroup();
settings.beginGroup(currentProfile);
settings.beginGroup(latestUUID);
QString xString;
QString yString;
settings.beginWriteArray("curvepoints");
for (int i=0; i<qv_x.length(); i++) {
settings.setArrayIndex(i);
settings.setValue("xpoints", ui->curvePlot->graph(0)->dataSortKey(i));
settings.setValue("ypoints", ui->curvePlot->graph(0)->dataMainValue(i));
}
settings.endArray();
close();
}
void editProfile::on_clearButton_clicked()
{
qv_x.clear();
qv_y.clear();
ui->curvePlot->graph(0)->setData(qv_x, qv_y);
drawFillerLines();
}
void editProfile::on_cancelButton_pressed()
{
close();
}

View File

@ -1,103 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#ifndef EDITPROFILE_H
#define EDITPROFILE_H
#include <QDialog>
#include <QVector>
#include "qcustomplot.h"
#include <QString>
namespace Ui {
class editProfile;
}
class editProfile : public QDialog
{
Q_OBJECT
public:
explicit editProfile(QWidget *parent = nullptr);
~editProfile();
QVector<double> qv_x, qv_y;
QVector<int> cpoints_x, cpoints_y;
signals:
void on_clickedPoint(QMouseEvent *event);
void on_dragPoint(bool);
void on_clickedGraph(bool);
private slots:
void clickedGraph(QMouseEvent *event);
void rePlot();
void addPoint(double x, double y);
void clickedPoint(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event);
void on_saveButton_clicked();
bool initializeDragging(QMouseEvent *event);
bool detectMove(QMouseEvent *event);
bool detectPress(QMouseEvent *event);
void detectRelease(QMouseEvent *event);
bool checkForDuplicatePoint(int x, int y);
int getDataIndex(QCPAbstractPlottable *plottable, int dataIndex);
void getClosestCoords(QMouseEvent *event);
void getPointIndices();
bool checkForNearbyPoints(QMouseEvent *event);
void dragPoint(int index_x, int index_y, QMouseEvent *event);
void drawFillerLines();
void on_clearButton_clicked();
void drawPointHoveredText(QMouseEvent *event);
void on_cancelButton_pressed();
private:
Ui::editProfile *ui;
//QVector<double> qv_x, qv_y;
QVector<double> leftLineX;
QVector<double> leftLineY;
QVector<double> rightLineX;
QVector<double> rightLineY;
QVector<int> curvepoints;
QPair<int, int> curvepoint;
QCPItemText *coordText;
int x_lower = 0;
int x_upper = 100;
int y_lower = 0;
int y_upper = 100;
double pixelLength;
double selectionPrecision = 2;
bool mouseMoving = false;
bool mousePressed = false;
bool mouseDragging = false;
bool duplicatePoint = false;
bool isNearbyPoint = false;
bool coordTextCreated = false;
int pointIndex;
int y_length;
int x_length;
int xcoord;
int ycoord;
int index_x = 0;
int index_y = 0;
bool indicesSet = false;
bool draggingPoint = false;
QTimer *pressTimer = new QTimer(this);
int timerTime = 1000;
};
#endif // EDITPROFILE_H

View File

@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>editProfile</class>
<widget class="QDialog" name="editProfile">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>642</width>
<height>557</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>599</width>
<height>523</height>
</size>
</property>
<property name="windowTitle">
<string>Edit fan curve</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="3">
<widget class="QCustomPlot" name="curvePlot" native="true">
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This graph defines the relation of fan speed to the GPU temperature.&lt;/p&gt;&lt;p&gt;To add a point, double click on the area.&lt;/p&gt;&lt;p&gt;To remove a point, click on it.&lt;/p&gt;&lt;p&gt;To move a point, drag it.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="clearButton">
<property name="text">
<string>Clear curve points</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="saveButton">
<property name="maximumSize">
<size>
<width>794</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QCustomPlot</class>
<extends>QWidget</extends>
<header>qcustomplot.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -1,28 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

File diff suppressed because it is too large Load Diff

View File

@ -1,260 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "editprofile.h"
#include <QProcess>
#include <QList>
#include <QByteArray>
#include "nvidia.h"
//#include <NVCtrl/NVCtrl.h>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
QString currentProfile;
QString nvFanQ = "/bin/sh -c \"nvidia-smi --query-gpu=fan.speed --format=csv | egrep -o '[0-9]{1,4}'\"";
QString nvVoltQ = "nvidia-settings -q GPUCurrentCoreVoltage -t";
QString nvVoltOfsQ = "nvidia-settings -q GPUOverVoltageOffset -t";
QString nvVoltOfsLimQ = "/bin/sh -c \"nvidia-settings -a GPUOverVoltageOffset=99999999 | egrep -o '[0-9]{1,9}'\"";
QString nvCoreClkOfsQ = "nvidia-settings -q GPUGraphicsClockOffset[3] -t";
QString nvCurMaxClkQ = "/bin/sh -c \"nvidia-smi --query-supported-clocks=gr --format=csv | egrep -o '[0-9]{2,9}'\"";
QString nvMaxPowerLimQ = "/bin/sh -c \"nvidia-smi --query-gpu=power.max_limit --format=csv | egrep -o '[0-9]{1,7}'\"";
QString nvMinPowerLimQ = "/bin/sh -c \"nvidia-smi --query-gpu=power.min_limit --format=csv | egrep -o '[0-9]{1,7}'\"";
QString nvCurPowerLimQ = "/bin/sh -c \"nvidia-smi --query-gpu=power.limit --format=csv | egrep -o '[0-9]{1,7}'\"";
QString nvClockLimQ = "/bin/sh -c \"nvidia-settings -a GPUGraphicsClockOffset[3]=999999 | egrep -o '[-0-9]{2,9}'\"";
QString nvMemClkLimQ = "/bin/sh -c \"nvidia-settings -a GPUMemoryTransferRateOffset[3]=999999 | egrep -o '[-0-9]{2,9}'\"";
QString nvCurMaxMemClkQ = "/bin/sh -c \"nvidia-smi --query-supported-clocks=mem --format=csv | egrep -o '[0-9]{2,9}'\"";
QString nvCurMemClkOfsQ = "nvidia-settings -q GPUMemoryTransferRateOffset[3] -t";
QString nvTempQ = "nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits";
QString nvCoreClkSet = "nvidia-settings -a GPUGraphicsClockOffset[3]=";
QString nvMemClkSet = "nvidia-settings -a GPUMemoryTransferRateOffset[3]=";
QString nvPowerLimSet = "nvidia-smi -pl ";
QString nvFanSpeedSet = "nvidia-settings -a GPUTargetFanSpeed=";
QString nvVoltageSet = "nvidia-settings -a GPUOverVoltageOffset=";
QString nvFanCtlStateSet = "nvidia-settings -a GPUFanControlState=";
QString nvFanCtlStateQ = "nvidia-settings -q GPUFanControlState -t";
QString grepStringToInt = " | egrep -o '[0-9]{0,100}'\"";
QString queryForNvidiaProp = "/bin/sh -c \"lspci -vnn | grep -c 'Kernel driver in use: nvidia'\"";
QString queryGPUName = "nvidia-smi --query-gpu=gpu_name --format=csv,noheader";
QString nvGPUCountQ = "nvidia-smi --query-gpu=count --format=csv,noheader";
QString nvUUIDQ = "nvidia-smi --query-gpu=uuid --format=csv,noheader";
QString errorText = "Failed to apply these settings: ";
QString gpuDriver;
QVector <int> xCurvePoints, yCurvePoints;
int currentGPUIndex = 0;
int voltInt = 0;
int voltOfsInt = 0;
int coreFreqOfsInt = 0;
int maxPowerLimInt = 0;
int minPowerLimInt = 0;
int curPowerLimInt = 0;
int minCoreClkOfsInt = 0;
int maxCoreClkOfsInt = 0;
int curMaxClkInt = 0;
int minMemClkOfsInt = 0;
int maxMemClkOfsInt = 0;
int minVoltOfsInt = 0;
int maxVoltOfsInt = 0;
int curMaxMemClkInt = 0;
int memClkOfsInt = 0;
int fanSpeed = 0;
int temp = 0;
int targetFanSpeed = 0;
int fanControlMode = 0;
int defCoreClk = 0;
int defMemClk = 0;
int defVolt = 0;
int latestClkOfs = 0;
int latestPowerLim = 0;
int latestMemClkOfs = 0;
int latestVoltOfs = 0;
bool isRoot = false;
public slots:
private slots:
void on_actionEdit_current_profile_triggered(bool checked);
void on_profileComboBox_activated(const QString &arg1);
void on_frequencySlider_valueChanged(int value);
void on_frequencySpinBox_valueChanged(int arg1);
void on_newProfile_clicked();
void on_powerLimSlider_valueChanged(int value);
void on_powerLimSpinBox_valueChanged(int arg1);
void on_newProfile_closed();
void on_memClkSlider_valueChanged(int value);
void on_memClkSpinBox_valueChanged(int arg1);
void on_voltageSlider_valueChanged(int value);
void on_voltageSpinBox_valueChanged(int arg1);
void fanSpeedUpdater();
void applyGPUSettings();
void on_fanSlider_valueChanged(int value);
void on_fanSpinBox_valueChanged(int arg1);
void on_applyButton_clicked();
void getGPUDriver();
void generateFanPoint();
void checkForRoot();
void tempUpdater();
void resetChanges();
void resetTimer();
void on_editFanCurveButton_pressed();
void on_editProfile_closed();
void applyFanMode();
void enableFanUpdater();
void setupMonitorTab();
void updateMonitor();
void saveProfileSettings();
void loadProfileSettings();
void checkForProfiles();
void on_fanModeComboBox_currentIndexChanged(int index);
void tabHandler(int index);
void setupGraphMonitorTab();
void plotHovered(QMouseEvent *event);
void clearPlots();
void clearExtremeValues();
void on_actionManage_profiles_triggered();
void on_GPUComboBox_currentIndexChanged(int index);
private:
Ui::MainWindow *ui;
bool noProfiles = true;
QStringList UUIDList;
QString latestUUID;
nvidia *nv;
QTimer *resettimer = new QTimer(this);
QTimer *fanUpdateTimer = new QTimer(this);
QTimer *statusLabelResetTimer = new QTimer(this);
QTimer *fanUpdaterDisablerTimer = new QTimer(this);
QTimer *monitorUpdater = new QTimer(this);
QTimer *plotHoverUpdater = new QTimer(this);
QTreeWidgetItem *gputemp = new QTreeWidgetItem;
QTreeWidgetItem *powerdraw = new QTreeWidgetItem;
QTreeWidgetItem *voltage = new QTreeWidgetItem;
QTreeWidgetItem *coreclock = new QTreeWidgetItem;
QTreeWidgetItem *memclock = new QTreeWidgetItem;
QTreeWidgetItem *coreutil = new QTreeWidgetItem;
QTreeWidgetItem *memutil = new QTreeWidgetItem;
QTreeWidgetItem *fanspeed = new QTreeWidgetItem;
QTreeWidgetItem *memusage = new QTreeWidgetItem;
QTreeWidgetItem *curmaxclk = new QTreeWidgetItem;
QTreeWidgetItem *curmaxmemclk = new QTreeWidgetItem;
QTreeWidgetItem *curpowerlim = new QTreeWidgetItem;
// Widgets for the graph monitor
QWidget *plotWidget = new QWidget;
QScrollArea *plotScrollArea = new QScrollArea;
QVBoxLayout *lo = new QVBoxLayout;
QVBoxLayout *plotLayout = new QVBoxLayout;
QCustomPlot *tempPlot = new QCustomPlot(this);
QCustomPlot *powerDrawPlot = new QCustomPlot(this);
QCustomPlot *coreClkPlot = new QCustomPlot(this);
QCustomPlot *memClkPlot = new QCustomPlot(this);
QCustomPlot *coreUtilPlot = new QCustomPlot(this);
QCustomPlot *memUtilPlot = new QCustomPlot(this);
QCustomPlot *voltagePlot = new QCustomPlot(this);
QCustomPlot *fanSpeedPlot = new QCustomPlot(this);
//QVector <double> qv_time;
struct plotCmds
{
QVector <double> vector;
double valueq;
double maxval;
double minval;
QCustomPlot *plot;
QVBoxLayout *layout;
QWidget *widget;
QCPTextElement *mintext;
QCPTextElement *maxtext;
QCPTextElement *curtext;
QCPItemTracer *tracer;
QCPItemText *valText;
};
struct datavector {
QVector <double> vector;
};
struct GPUData {
QVector <datavector> data;
QVector <double> qv_time;
};
QVector <GPUData> GPU;
int counter = 0;
// The maximum size of plot data vectors (range +1)
int plotVectorSize = 181;
plotCmds powerdrawplot;
plotCmds tempplot;
plotCmds coreclkplot;
plotCmds memclkplot;
plotCmds coreutilplot;
plotCmds memutilplot;
plotCmds voltageplot;
plotCmds fanspeedplot;
QVector <plotCmds> plotCmdsList;
QSystemTrayIcon* trayIcon;
QMenu* createMenu();
void closeEvent(QCloseEvent *);
bool ignore_closeEvent = true;
};
#endif // MAINWINDOW_H

View File

@ -1,263 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>507</width>
<height>672</height>
</rect>
</property>
<property name="windowTitle">
<string>TuxClocker</string>
</property>
<property name="windowIcon">
<iconset resource="resources.qrc">
<normaloff>:/icons/gpuonfire.svg</normaloff>:/icons/gpuonfire.svg</iconset>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="perfEditorTab">
<attribute name="title">
<string>Performance</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="fanModeLabel">
<property name="text">
<string>Fan mode:</string>
</property>
</widget>
</item>
<item row="12" column="0" colspan="3">
<widget class="QPushButton" name="applyButton">
<property name="text">
<string>Apply changes</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
<widget class="QSlider" name="fanSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="7" column="0" colspan="5">
<widget class="QSlider" name="memClkSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="0" colspan="3">
<widget class="QLabel" name="clockFreqLabel">
<property name="text">
<string>Clock Frequency Offset (MHz)</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="fanModeComboBox">
<item>
<property name="text">
<string>Driver defined</string>
</property>
</item>
<item>
<property name="text">
<string>Static</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
<item row="11" column="5">
<widget class="QSpinBox" name="frequencySpinBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="fanSpeedLabel">
<property name="text">
<string>Fan Speed (%)</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QLabel" name="voltgeLabel">
<property name="text">
<string>Voltage Offset (mV)</string>
</property>
</widget>
</item>
<item row="7" column="5">
<widget class="QSpinBox" name="memClkSpinBox"/>
</item>
<item row="6" column="0" colspan="3">
<widget class="QLabel" name="memClockLabel">
<property name="text">
<string>Memory Clock Offset (MHz)</string>
</property>
</widget>
</item>
<item row="3" column="5">
<widget class="QSpinBox" name="fanSpinBox"/>
</item>
<item row="9" column="5">
<widget class="QSpinBox" name="powerLimSpinBox"/>
</item>
<item row="2" column="5">
<widget class="QPushButton" name="editFanCurveButton">
<property name="maximumSize">
<size>
<width>87</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Edit fan curve&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/cog.png</normaloff>:/icons/cog.png</iconset>
</property>
</widget>
</item>
<item row="12" column="3" colspan="3">
<widget class="QComboBox" name="profileComboBox"/>
</item>
<item row="5" column="5">
<widget class="QSpinBox" name="voltageSpinBox"/>
</item>
<item row="8" column="0" colspan="2">
<widget class="QLabel" name="powerLimLabel">
<property name="text">
<string>Power Limit (W)</string>
</property>
</widget>
</item>
<item row="11" column="0" colspan="5">
<widget class="QSlider" name="frequencySlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="0" colspan="5">
<widget class="QSlider" name="voltageSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="9" column="0" colspan="5">
<widget class="QSlider" name="powerLimSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="monitorTab">
<attribute name="title">
<string>Graph Monitor</string>
</attribute>
</widget>
<widget class="QWidget" name="monitorListTab">
<attribute name="title">
<string>List Monitor</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QTreeWidget" name="monitorTree">
<property name="columnCount">
<number>2</number>
</property>
<column>
<property name="text">
<string>Property</string>
</property>
</column>
<column>
<property name="text">
<string>Value</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="GPUComboBox"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>507</width>
<height>28</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>Profi&amp;les</string>
</property>
<addaction name="actionManage_profiles"/>
</widget>
<addaction name="menuFile"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionQuit">
<property name="text">
<string>&amp;Quit</string>
</property>
</action>
<action name="actionEdit_current_profile">
<property name="text">
<string>&amp;Edit current profile</string>
</property>
</action>
<action name="actionAdd_profile_from_file">
<property name="text">
<string>Add profile from file...</string>
</property>
</action>
<action name="actionAdd_new_profile">
<property name="text">
<string>&amp;Add new profile</string>
</property>
</action>
<action name="actionManage_profiles">
<property name="text">
<string>&amp;Manage profiles</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
</ui>

9
meson.build Normal file
View File

@ -0,0 +1,9 @@
project('tuxclocker', 'c', 'cpp',
default_options : ['cpp_std=c++17'])
cc = meson.get_compiler('c')
cppc = meson.get_compiler('cpp')
incdir = include_directories('src/include', 'src/include/deps')
subdir('src')

7
meson_options.txt Normal file
View File

@ -0,0 +1,7 @@
# By default, library and modules (except interfaces) are built
option('daemon', type: 'boolean', value: 'true', description: 'Build daemon')
option('plugins', type: 'boolean', value: 'true', description: 'Build plugins')
option('interfaces', type: 'boolean', value: 'true', description: 'Build interfaces')
option('library', type: 'boolean', value: 'true', description: 'Build library')
option('gui', type: 'boolean', value: 'true', description: 'Build Qt GUI')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

31
mkTarball.sh Executable file
View File

@ -0,0 +1,31 @@
#!/usr/bin/env sh
storePaths=($(nix-store -qR $(nix-build release.nix)))
cp -n result/bin/{.tuxclockerd-wrapped,.tuxclocker-qt-wrapped} .
libPaths=( "${storePaths[@]/%/\/lib\/}" )
libPaths=( "${libPaths[@]/#/.}" )
libPathsColonSep=$(echo ${libPaths[@]} | sed 's/ /:/g')
glibPath=$(nix-build '<nixpkgs>' -A glibc --no-out-link)
# TODO: don't hardcode version!
qtPluginPath=.$(nix-build '<nixpkgs>' -A libsForQt5.qt5.qtbase --no-out-link)/lib/qt-5.15.9/plugins/
tuxclockerPluginPath=.$(nix-build release.nix)/lib/tuxclocker/plugins/
chmod 777 ./.tuxclockerd-wrapped ./.tuxclocker-qt-wrapped
patchelf --set-rpath \.$glibPath/lib ./.tuxclockerd-wrapped ./.tuxclocker-qt-wrapped
patchelf --set-interpreter \.$glibPath"/lib/ld-linux-x86-64.so.2" ./.tuxclockerd-wrapped ./.tuxclocker-qt-wrapped
echo "
export DBUS_SYSTEM_BUS_ADDRESS='unix:path=/tmp/tuxclocker-dbus-socket'
export LD_LIBRARY_PATH=\"${libPathsColonSep[@]}\"
export QT_PLUGIN_PATH=\"$qtPluginPath\"
export TUXCLOCKER_PLUGIN_PATH=\"$tuxclockerPluginPath\"
sudo -E dbus-run-session --config-file=dev/dbusconf.conf \
sudo -E LD_LIBRARY_PATH=\"\$LD_LIBRARY_PATH\" ./.tuxclockerd-wrapped & \
(unset LD_LIBRARY_PATH; sleep 2) && ./.tuxclocker-qt-wrapped; \
unset LD_LIBRARY_PATH && \
sudo kill \$(pidof .tuxclockerd-wrapped)
" > run.sh
chmod +x run.sh
# Only copy libraries from Nix store (.so's)
neededLibs=$(find $(nix-store -qR $(nix-build release.nix)) | grep ".*.so")
tar cavf tuxclocker.tar ${neededLibs[@]} ./.tuxclocker-qt-wrapped ./.tuxclockerd-wrapped ./run.sh ./dev/dbusconf.conf

View File

@ -1,126 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#include "newprofile.h"
#include "ui_newprofile.h"
#include "mainwindow.h"
newProfile::newProfile(QWidget *parent) :
QDialog(parent),
ui(new Ui::newProfile)
{
ui->setupUi(this);
listProfiles();
//connect(ui->profileList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), SLOT(editEntryName(QListWidgetItem*)));
SignalItemDelegate *delegate = new SignalItemDelegate(ui->profileList);
connect(delegate, SIGNAL(editFinished()), SLOT(saveChange()));
ui->profileList->setItemDelegate(delegate);
}
newProfile::~newProfile()
{
delete ui;
}
void newProfile::on_profileNameEdit_textChanged(const QString &arg1)
{
newProfileName = arg1;
}
void newProfile::saveChange()
{
//qDebug() << "edit finished";
//QListWidgetItem *item = ui->profileList->item(latestIndex);
newProfileList.clear();
for (int i=0; i<ui->profileList->count(); i++) {
newProfileList.append(ui->profileList->item(i)->text());
//qDebug() << newProfileList[i];
ui->profileList->item(i)->setFlags(Qt::ItemIsEnabled);
}
}
void newProfile::on_saveButton_clicked()
{
QSettings settings("tuxclocker");
// Add the new ones
for (int i=0; i<newProfileList.size(); i++) {
settings.setValue(newProfileList[i] + "/isProfile", true);
}
for (int i=0; i<removedList.size(); i++) {
settings.beginGroup(removedList[i]);
settings.remove("");
settings.endGroup();
}
close();
}
void newProfile::listProfiles()
{
QSettings settings("tuxclocker");
QString isProfile = "/isProfile";
QStringList groups = settings.childGroups();
//qDebug() << "testi";
for (int i=0; i<groups.length(); i++) {
// Make a query $profile/isProfile
QString group = groups[i];
QString query = group.append(isProfile);
//qDebug() << query;
if (settings.value(query).toBool()) {
//qDebug() << groups[i];
ui->profileList->addItem(groups[i]);
origProfileList.append(groups[i]);
newProfileList.append(groups[i]);
}
}
// Make the items editable
for (int i=0; i<ui->profileList->count(); i++) {
ui->profileList->item(i)->setFlags(Qt::ItemIsEnabled);
}
}
/*void newProfile::editEntryName(QListWidgetItem *item)
{
qDebug() << "item dblclicked";
ui->profileList->editItem(item);
latestIndex = ui->profileList->currentRow();
}*/
void newProfile::on_cancelButton_clicked()
{
close();
}
void newProfile::on_addButton_pressed()
{
ui->profileList->addItem("");
int itemCount = ui->profileList->count()-1;
latestIndex = itemCount;
ui->profileList->item(itemCount)->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled);
ui->profileList->editItem(ui->profileList->item(itemCount));
}
void newProfile::on_removeButton_pressed()
{
if (ui->profileList->count() > 1) {
int index = ui->profileList->currentRow();
QListWidgetItem *item = ui->profileList->item(index);
removedList.append(item->text());
newProfileList.removeAt(index);
ui->profileList->takeItem(index);
delete item;
}
}

View File

@ -1,81 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#ifndef NEWPROFILE_H
#define NEWPROFILE_H
#include <QDialog>
#include <QAbstractButton>
#include <QSettings>
#include <QListWidget>
#include <QStyledItemDelegate>
namespace Ui {
class newProfile;
}
class newProfile : public QDialog
{
Q_OBJECT
public:
explicit newProfile(QWidget *parent = nullptr);
~newProfile();
signals:
void mousePressEvent(QMouseEvent* event);
private slots:
void on_saveButton_clicked();
void on_profileNameEdit_textChanged(const QString &arg1);
void on_cancelButton_clicked();
void listProfiles();
//void editEntryName(QListWidgetItem *item);
void on_addButton_pressed();
void saveChange();
void on_removeButton_pressed();
private:
Ui::newProfile *ui;
QString newProfileName;
QStyledItemDelegate *deleg = new QStyledItemDelegate(this);
QStringList origProfileList;
QStringList newProfileList;
QStringList removedList;
int latestIndex = 0;
};
// New class for editing so we can detect when the editing has finished
class SignalItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
Q_DISABLE_COPY(SignalItemDelegate)
public:
explicit SignalItemDelegate(QObject* parent = Q_NULLPTR):QStyledItemDelegate(parent){
QObject::connect(this,&SignalItemDelegate::closeEditor,this,&SignalItemDelegate::editFinished);
}
void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE {
void editStarted();
return QStyledItemDelegate::setEditorData(editor,index);
}
Q_SIGNALS:
void editStarted();
void editFinished();
};
#endif // NEWPROFILE_H

View File

@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>newProfile</class>
<widget class="QDialog" name="newProfile">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>418</width>
<height>534</height>
</rect>
</property>
<property name="windowTitle">
<string>Manage profiles</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="4">
<widget class="QListWidget" name="profileList"/>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="saveButton">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="addButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/plusicon.png</normaloff>:/icons/plusicon.png</iconset>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QPushButton" name="removeButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icons/minusicon.png</normaloff>:/icons/minusicon.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="resources.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -1,490 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#include "nvidia.h"
#include <X11/Xlib.h>
#include "NVCtrl/NVCtrlLib.h"
nvidia::nvidia(QObject *parent) : QObject(parent)
{
}
bool nvidia::setupXNVCtrl()
{
// Open the x-server connection and check if the extension exists
dpy = XOpenDisplay(nullptr);
Bool ret;
int *event_basep = nullptr;
int *error_basep = nullptr;
ret = XNVCTRLQueryExtension(dpy,
event_basep,
error_basep);
qDebug() << ret;
queryGPUCount();
queryGPUNames();
queryGPUUIDs();
queryGPUFeatures();
return ret;
}
void nvidia::queryGPUCount()
{
Bool ret;
ret = XNVCTRLQueryTargetCount(dpy, NV_CTRL_TARGET_TYPE_GPU, &gpuCount);
if (!ret) {
qDebug() << "Failed to query amount of GPUs";
}
// Create an appropriate number of GPU objects
for (int i=0; i<gpuCount; i++) {
GPU gpu;
GPUList.append(gpu);
}
qDebug() << GPUList.size() << "gpus";
//setupNVML(0);
}
void nvidia::queryGPUNames()
{
Bool ret;
for (int i=0; i<gpuCount; i++) {
ret = XNVCTRLQueryTargetStringAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
i,
0,
NV_CTRL_STRING_PRODUCT_NAME,
&GPUList[i].name);
if (!ret) {
qDebug() << "Failed to query GPU Name";
}
qDebug() << GPUList[i].name;
}
}
void nvidia::queryGPUUIDs()
{
Bool ret;
for (int i=0; i<gpuCount; i++) {
ret = XNVCTRLQueryTargetStringAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
i,
0,
NV_CTRL_STRING_GPU_UUID,
&GPUList[i].uuid);
if (!ret) {
qDebug() << "Failed to query GPU UUID";
}
qDebug() << GPUList[i].uuid;
}
}
void nvidia::queryGPUFeatures()
{
// Query static features related to the GPU such as maximum offsets here
Bool ret;
NVCTRLAttributeValidValuesRec values;
for (int i=0; i<gpuCount; i++) {
// Query all performance modes
queryGPUMaxPerfMode(i);
// Query if voltage offset is writable/readable
ret = XNVCTRLQueryValidTargetAttributeValues(dpy,
NV_CTRL_TARGET_TYPE_GPU,
i,
0,
NV_CTRL_GPU_OVER_VOLTAGE_OFFSET,
&values);
if ((values.permissions & ATTRIBUTE_TYPE_WRITE) == ATTRIBUTE_TYPE_WRITE) {
GPUList[i].overVoltAvailable = true;
GPUList[i].voltageReadable = true;
// If the feature is writable save the offset range
GPUList[i].minVoltageOffset = values.u.range.min;
GPUList[i].maxVoltageOffset = values.u.range.max;
//qDebug() << values.u.range.min << values.u.range.max << "offset range";
} else {
// Check if it's readable
if ((values.permissions & ATTRIBUTE_TYPE_READ) == ATTRIBUTE_TYPE_READ) {
GPUList[i].voltageReadable = true;
}
}
// Query if core clock offset is writable
ret = XNVCTRLQueryValidTargetAttributeValues(dpy,
NV_CTRL_TARGET_TYPE_GPU,
i,
GPUList[i].maxPerfMode,
NV_CTRL_GPU_NVCLOCK_OFFSET,
&values);
if ((values.permissions & ATTRIBUTE_TYPE_WRITE) == ATTRIBUTE_TYPE_WRITE) {
GPUList[i].overClockAvailable = true;
GPUList[i].coreClkReadable = true;
GPUList[i].minCoreClkOffset = values.u.range.min;
GPUList[i].maxCoreClkOffset = values.u.range.max;
//qDebug() << values.u.range.min << values.u.range.max << "offset range";
} else {
if ((values.permissions & ATTRIBUTE_TYPE_READ) == ATTRIBUTE_TYPE_READ) {
GPUList[i].coreClkReadable = true;
}
}
// Query if memory clock offset is writable
ret = XNVCTRLQueryValidTargetAttributeValues(dpy,
NV_CTRL_TARGET_TYPE_GPU,
i,
GPUList[i].maxPerfMode,
NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET,
&values);
if ((values.permissions & ATTRIBUTE_TYPE_WRITE) == ATTRIBUTE_TYPE_WRITE) {
GPUList[i].memOverClockAvailable = true;
GPUList[i].memClkReadable = true;
GPUList[i].minMemClkOffset = values.u.range.min;
GPUList[i].maxMemClkOffset = values.u.range.max;
qDebug() << values.u.range.min << values.u.range.max << "offset range";
} else {
if ((values.permissions & ATTRIBUTE_TYPE_READ) == ATTRIBUTE_TYPE_READ) {
GPUList[i].memClkReadable = true;
}
}
// Query if fan control mode is writable
ret = XNVCTRLQueryValidTargetAttributeValues(dpy,
NV_CTRL_TARGET_TYPE_GPU,
i,
0,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL,
&values);
if ((values.permissions & ATTRIBUTE_TYPE_WRITE) == ATTRIBUTE_TYPE_WRITE) {
GPUList[i].manualFanCtrlAvailable = true;
qDebug() << "fan control available";
// Query fan control mode
int retval;
ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
i,
0,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL,
&retval);
if ((retval & NV_CTRL_GPU_COOLER_MANUAL_CONTROL_TRUE) == NV_CTRL_GPU_COOLER_MANUAL_CONTROL_TRUE) {
qDebug() << "fanctl on";
GPUList[i].fanControlMode = 1;
} else {
GPUList[i].fanControlMode = 0;
qDebug() << "fanctl off";
}
}
// Query amount of VRAM
ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
i,
0,
NV_CTRL_TOTAL_DEDICATED_GPU_MEMORY,
&GPUList[i].totalVRAM);
qDebug() << GPUList[i].totalVRAM << "vram";
}
}
void nvidia::queryGPUMaxPerfMode(int GPUIndex)
{
char* result;
Bool ret = XNVCTRLQueryTargetStringAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
0,
NV_CTRL_STRING_PERFORMANCE_MODES,
&result);
if(ret){
QString modes(result);
free(result);
// Each perf mode is separated by a ";" symbol, so for obtaining the number
// of maximal performance mode it should be enough to just count number of
// ";" occurences
int maxPerfMode = modes.count(';');
GPUList[GPUIndex].maxPerfMode = uint(maxPerfMode);
qDebug() << modes;
return;
}
else {
GPUList[GPUIndex].maxPerfMode = 3;
}
}
void nvidia::queryGPUVoltage(int GPUIndex)
{
Bool ret;
ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
0,
NV_CTRL_GPU_CURRENT_CORE_VOLTAGE,
&GPUList[GPUIndex].voltage);
if (!ret) {
qDebug() << "Couldn't query voltage for GPU";
}
qDebug() << GPUList[GPUIndex].voltage;
}
void nvidia::queryGPUTemp(int GPUIndex)
{
qDebug() << GPUList.size();
Bool ret;
ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_THERMAL_SENSOR,
GPUIndex,
0,
NV_CTRL_THERMAL_SENSOR_READING,
&GPUList[GPUIndex].temp);
if (!ret) {
qDebug() << "failed to query GPU temperature";
}
qDebug() << GPUList[GPUIndex].temp;
}
void nvidia::queryGPUFrequencies(int GPUIndex)
{
Bool ret;
int packedInt = 0;
int mem = 0;
int core = 0;
ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
0,
NV_CTRL_GPU_CURRENT_CLOCK_FREQS,
&packedInt);
// The function returns a 32-bit packed integer, GPU Clock is the upper 16 and Memory Clock lower 16
mem = (packedInt) & 0xFFFF;
core = (packedInt & (0xFFFF << (32 - 16))) >> (32 - 16);
qDebug() << GPUList[GPUIndex].coreFreq << "freq" << core << mem;
GPUList[GPUIndex].coreFreq = core;
GPUList[GPUIndex].memFreq = mem;
}
void nvidia::queryGPUFanSpeed(int GPUIndex)
{
Bool ret;
ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_COOLER,
GPUIndex,
0,
NV_CTRL_THERMAL_COOLER_CURRENT_LEVEL,
&GPUList[GPUIndex].fanSpeed);
}
void nvidia::queryGPUUsedVRAM(int GPUIndex)
{
Bool ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
0,
NV_CTRL_USED_DEDICATED_GPU_MEMORY,
&GPUList[GPUIndex].usedVRAM);
if (!ret) {
qDebug() << "failed to query used VRAM";
}
qDebug() << GPUList[GPUIndex].usedVRAM << "usedvram";
}
void nvidia::queryGPUFreqOffset(int GPUIndex)
{
Bool ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
GPUList[GPUIndex].maxPerfMode,
NV_CTRL_GPU_NVCLOCK_OFFSET,
&GPUList[GPUIndex].coreClkOffset);
}
void nvidia::queryGPUMemClkOffset(int GPUIndex)
{
Bool ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
GPUList[GPUIndex].maxPerfMode,
NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET,
&GPUList[GPUIndex].memClkOffset);
}
void nvidia::queryGPUVoltageOffset(int GPUIndex)
{
Bool ret = XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
0,
NV_CTRL_GPU_OVER_VOLTAGE_OFFSET,
&GPUList[GPUIndex].voltageOffset);
qDebug() << GPUList[GPUIndex].voltageOffset << "offset";
}
bool nvidia::assignGPUFanSpeed(int GPUIndex, int targetValue)
{
Bool ret;
ret = XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_COOLER,
GPUIndex,
0,
NV_CTRL_THERMAL_COOLER_LEVEL,
targetValue);
return ret;
}
bool nvidia::assignGPUFanCtlMode(int GPUIndex, int targetValue)
{
Bool ret = XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
0,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL,
targetValue);
return ret;
}
bool nvidia::assignGPUFreqOffset(int GPUIndex, int targetValue)
{
Bool ret = XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
GPUList[GPUIndex].maxPerfMode, // This argument is the performance mode
NV_CTRL_GPU_NVCLOCK_OFFSET,
targetValue);
qDebug() << ret << "freqassign";
return ret;
}
bool nvidia::assignGPUMemClockOffset(int GPUIndex, int targetValue)
{
Bool ret = XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
GPUList[GPUIndex].maxPerfMode,
NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET,
targetValue);
qDebug() << ret << "memassign";
return ret;
}
bool nvidia::assignGPUVoltageOffset(int GPUIndex, int targetValue)
{
Bool ret = XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_GPU,
GPUIndex,
0,
NV_CTRL_GPU_OVER_VOLTAGE_OFFSET,
targetValue);
qDebug() << ret;
return ret;
}
/*bool nvidia::setup()
{
dpy = XOpenDisplay(nullptr);
int *temp;
XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_THERMAL_SENSOR,
0,
0,
NV_CTRL_THERMAL_SENSOR_READING,
temp);
qDebug() << temp;
return true;
}*/
bool nvidia::setupNVML(int GPUIndex)
{
nvmlDevice_t *dev = new nvmlDevice_t;
device = dev;
nvmlReturn_t ret = nvmlInit();
char name[64];
nvmlDeviceGetHandleByIndex(GPUIndex, dev);
//nvmlDeviceGetName(dev, name, sizeof(name)/sizeof(name[0]));
qDebug() << name << "from nvml";
if (NVML_SUCCESS != ret) {
return false;
}
//queryGPUUtils(0);
//queryGPUPowerLimitLimits(0);
//queryGPUPowerDraw(0);
//qDebug() << assignGPUPowerLimit(150000);
return true;
}
void nvidia::queryGPUUtils(int GPUIndex)
{
nvmlUtilization_t utils;
nvmlReturn_t ret = nvmlDeviceGetUtilizationRates(*device, &utils);
if (NVML_SUCCESS != ret) {
qDebug() << "failed to query GPU utilizations";
}
qDebug() << utils.gpu << utils.memory << "utils";
GPUList[GPUIndex].memUtil = utils.memory;
GPUList[GPUIndex].coreUtil = utils.gpu;
}
void nvidia::queryGPUPowerDraw(int GPUIndex)
{
uint usg;
nvmlReturn_t ret = nvmlDeviceGetPowerUsage(*device, &usg);
if (NVML_SUCCESS != ret) {
qDebug() << "failed to query power usage";
} else {
GPUList[GPUIndex].powerDraw = usg;
qDebug() << usg << "pwrusg";
}
}
void nvidia::queryGPUPowerLimitLimits(int GPUIndex)
{
uint maxlim;
uint minlim;
nvmlReturn_t ret = nvmlDeviceGetPowerManagementLimitConstraints(*device, &minlim, &maxlim);
if (NVML_SUCCESS != ret) {
qDebug() << "failed to query power limit constraints";
} else {
GPUList[GPUIndex].minPowerLim = minlim;
GPUList[GPUIndex].maxPowerLim = maxlim;
qDebug() << minlim << maxlim << "powerlims";
}
}
void nvidia::queryGPUPowerLimit(int GPUIndex)
{
uint lim;
nvmlReturn_t ret = nvmlDeviceGetPowerManagementLimit(*device, &lim);
if (NVML_SUCCESS != ret) {
qDebug() << "failed to query power limit";
} else {
GPUList[GPUIndex].powerLim = lim;
}
}
void nvidia::queryGPUCurrentMaxClocks(int GPUIndex)
{
uint curmax;
// Query maximum core clock
nvmlReturn_t ret = nvmlDeviceGetMaxClockInfo(*device, NVML_CLOCK_GRAPHICS, &curmax);
if (NVML_SUCCESS != ret) {
qDebug() << "Failed to query maximum core clock";
} else {
GPUList[GPUIndex].maxCoreClk = curmax;
}
// Query maximum memory clock
ret = nvmlDeviceGetMaxClockInfo(*device, NVML_CLOCK_MEM, &curmax);
if (NVML_SUCCESS != ret) {
qDebug() << "Failed to query maximum memory clock";
} else {
GPUList[GPUIndex].maxMemClk = curmax;
qDebug() << curmax << "current max clock";
}
}
void nvidia::queryGPUPowerLimitAvailability(int GPUIndex)
{
// Assign the current power limit to see if modifying it is available
nvmlReturn_t ret = nvmlDeviceSetPowerManagementLimit(*device, GPUList[GPUIndex].powerLim);
if (NVML_ERROR_NOT_SUPPORTED == ret) {
GPUList[GPUIndex].powerLimitAvailable = false;
} else {
GPUList[GPUIndex].powerLimitAvailable = true;
}
}
bool nvidia::assignGPUPowerLimit(uint targetValue)
{
nvmlReturn_t ret = nvmlDeviceSetPowerManagementLimit(*device, targetValue);
if (NVML_SUCCESS != ret) {
return false;
}
return true;
}

118
nvidia.h
View File

@ -1,118 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#ifndef NVIDIA_H
#define NVIDIA_H
#include <QObject>
#include <QDebug>
#include <QtX11Extras/QX11Info>
#include <QProcess>
#include <nvml.h>
class nvidia : public QObject
{
Q_OBJECT
public:
explicit nvidia(QObject *parent = nullptr);
struct GPU
{
char *name;
char *uuid;
bool overVoltAvailable = false;
bool overClockAvailable = false;
bool memOverClockAvailable = false;
bool powerLimitAvailable = false;
bool voltageReadable = false;
bool coreClkReadable = false;
bool memClkReadable = false;
bool manualFanCtrlAvailable = false;
int fanControlMode;
int maxVoltageOffset;
int minVoltageOffset;
int minCoreClkOffset;
int maxCoreClkOffset;
int minMemClkOffset;
int maxMemClkOffset;
uint maxCoreClk;
uint maxMemClk;
int voltageOffset;
int coreClkOffset;
int memClkOffset;
int coreFreq;
int memFreq;
int temp;
int voltage;
int fanSpeed;
double powerDraw;
uint coreUtil;
uint memUtil;
uint minPowerLim;
uint maxPowerLim;
uint powerLim;
int totalVRAM;
int usedVRAM;
uint maxPerfMode;
};
QVector <GPU> GPUList;
int gpuCount = 0;
private:
Display *dpy;
nvmlDevice_t *device;
signals:
public slots:
bool setupXNVCtrl();
bool setupNVML(int GPUIndex);
void queryGPUCount();
void queryGPUNames();
void queryGPUUIDs();
void queryGPUFeatures();
void queryGPUVoltage(int GPUIndex);
void queryGPUTemp(int GPUIndex);
void queryGPUFrequencies(int GPUIndex);
void queryGPUFanSpeed(int GPUIndex);
void queryGPUUsedVRAM(int GPUIndex);
void queryGPUFreqOffset(int GPUIndex);
void queryGPUMemClkOffset(int GPUIndex);
void queryGPUVoltageOffset(int GPUIndex);
void queryGPUMaxPerfMode(int GPUIndex);
void queryGPUUtils(int GPUIndex);
void queryGPUPowerDraw(int GPUIndex);
void queryGPUPowerLimit(int GPUIndex);
void queryGPUPowerLimitLimits(int GPUIndex);
void queryGPUCurrentMaxClocks(int GPUIndex);
void queryGPUPowerLimitAvailability(int GPUIndex);
bool assignGPUFanSpeed(int GPUIndex, int targetValue);
bool assignGPUFanCtlMode(int GPUIndex, int targetValue);
bool assignGPUFreqOffset(int GPUIndex, int targetValue);
bool assignGPUMemClockOffset(int GPUIndex, int targetValue);
bool assignGPUVoltageOffset(int GPUIndex, int targetValue);
// NVML functions know the GPU index already based on the dev object passed in setupNVML()
bool assignGPUPowerLimit(uint targetValue);
private slots:
};
#endif // NVIDIA_H

View File

@ -1,27 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#include "plotwidget.h"
PlotWidget::PlotWidget(QWidget *parent) : QWidget(parent)
{
}
void PlotWidget::leaveEvent(QEvent *event)
{
emit leftPlot();
}

View File

@ -1,36 +0,0 @@
/*This file is part of TuxClocker.
Copyright (c) 2019 Jussi Kuokkanen
TuxClocker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TuxClocker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.*/
#ifndef PLOTWIDGET_H
#define PLOTWIDGET_H
#include <QWidget>
class PlotWidget : public QWidget
{
Q_OBJECT
public:
explicit PlotWidget(QWidget *parent = nullptr);
signals:
void leftPlot();
protected:
void leaveEvent(QEvent *event);
public slots:
};
#endif // PLOTWIDGET_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

13
release.nix Normal file
View File

@ -0,0 +1,13 @@
let
sources = import ./npins;
pkgs =
if (builtins.pathExists ./npins)
then import sources.nixpkgs {}
else import <nixpkgs> {};
in with pkgs;
libsForQt5.callPackage ./default.nix {
libXNVCtrl = linuxPackages.nvidia_x11.settings.libXNVCtrl;
nvidia_x11 = linuxPackages.nvidia_x11;
#boost = boost179;
# TODO: I'd like to pin this, but specifying 1.79 gives a compile error even though the exact same hash is used if 'boost' is used
}

View File

@ -1,8 +0,0 @@
<RCC>
<qresource prefix="/icons">
<file>cog.png</file>
<file>plusicon.png</file>
<file>minusicon.png</file>
<file>gpuonfire.svg</file>
</qresource>
</RCC>

View File

@ -1,80 +0,0 @@
#-------------------------------------------------
#
# Project created by QtCreator 2018-11-02T23:09:52
#
#-------------------------------------------------
# This file is part of TuxClocker.
# Copyright (c) 2019 Jussi Kuokkanen
# TuxClocker is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# TuxClocker is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with TuxClocker. If not, see <https://www.gnu.org/licenses/>.
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport
TARGET = tuxclocker
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
CONFIG += c++11
CONFIG (release, debug|release) {
DEFINES += QT_NO_DEBUG_OUTPUT
}
SOURCES += \
main.cpp \
mainwindow.cpp \
qcustomplot.cpp \
editprofile.cpp \
newprofile.cpp \
plotwidget.cpp \
nvidia.cpp
HEADERS += \
mainwindow.h \
qcustomplot.h \
editprofile.h \
newprofile.h \
plotwidget.h \
nvidia.h \
nvml.h
FORMS += \
mainwindow.ui \
editprofile.ui \
newprofile.ui
INCLUDEPATH += "/usr/lib"
INCLUDEPATH += $$(INCLUDEPATH)
LIBS += -lXext -lXNVCtrl -lX11 -lnvidia-ml
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
RESOURCES += \
resources.qrc

BIN
screenshots/itemedit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
screenshots/mainview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
screenshots/paramEditor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
screenshots/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
screenshots/stateChange.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

10
src/include/Crypto.hpp Normal file
View File

@ -0,0 +1,10 @@
#pragma once
#include <string>
namespace TuxClocker::Crypto {
std::string sha256(std::string s);
std::string md5(std::string s);
}; // namespace TuxClocker::Crypto

127
src/include/DBusTypes.hpp Normal file
View File

@ -0,0 +1,127 @@
#pragma once
#include <Device.hpp>
#include <optional>
#include <QDBusArgument>
#include <QDBusVariant>
#include <QDBusMetaType>
#include <QVector>
namespace TC = TuxClocker;
namespace TuxClocker::DBus {
template <typename T> struct Result {
bool error;
T value;
friend QDBusArgument &operator<<(QDBusArgument &arg, const Result<T> r) {
arg.beginStructure();
arg << r.error << r.value;
arg.endStructure();
return arg;
}
friend const QDBusArgument &operator>>(const QDBusArgument &arg, Result<T> &r) {
arg.beginStructure();
arg >> r.error >> r.value;
arg.endStructure();
return arg;
}
std::optional<T> toOptional() {
std::optional<T> r = (error) ? std::optional(value) : std::nullopt;
return r;
}
};
struct Range {
QDBusVariant min, max;
friend QDBusArgument &operator<<(QDBusArgument &arg, const Range r) {
arg.beginStructure();
arg << r.min << r.max;
arg.endStructure();
return arg;
}
friend const QDBusArgument &operator>>(const QDBusArgument &arg, Range &r) {
arg.beginStructure();
arg >> r.min >> r.max;
arg.endStructure();
return arg;
}
TC::Device::AssignableInfo toAssignableInfo() {
auto min_v = min.variant();
auto max_v = max.variant();
auto im = static_cast<QVariant::Type>(QMetaType::Int);
auto dm = static_cast<QVariant::Type>(QMetaType::Double);
if (min_v.type() == im && max_v.type() == im)
return TC::Device::Range<int>(min_v.value<int>(), max_v.value<int>());
if (min_v.type() == dm && max_v.type() == dm)
return TC::Device::Range<double>(
min_v.value<double>(), max_v.value<double>());
// Should never reach here
return TC::Device::Range<int>(0, 0);
}
};
struct Enumeration {
uint key;
QString name;
friend QDBusArgument &operator<<(QDBusArgument &arg, const Enumeration e) {
arg.beginStructure();
arg << e.key << e.name;
arg.endStructure();
return arg;
}
friend const QDBusArgument &operator>>(const QDBusArgument &arg, Enumeration &e) {
arg.beginStructure();
arg >> e.key >> e.name;
arg.endStructure();
return arg;
}
};
/* MOC crap doubles this somewhere
TC::Device::AssignableInfo enumVecToAssignableInfo(QVector<Enumeration> enums) {
TC::Device::EnumerationVec v;
for (const auto &e : enums)
v.push_back(TC::Device::Enumeration(e.name.toStdString(), e.key));
return v;
}
*/
struct DeviceNode {
QString interface;
QString path;
friend QDBusArgument &operator<<(QDBusArgument &arg, const DeviceNode d) {
arg.beginStructure();
arg << d.interface << d.path;
arg.endStructure();
return arg;
}
friend const QDBusArgument &operator>>(const QDBusArgument &arg, DeviceNode &d) {
arg.beginStructure();
arg >> d.interface >> d.path;
arg.endStructure();
return arg;
}
};
template <typename T> struct FlatTreeNode {
T value;
QVector<int> childIndices;
friend QDBusArgument &operator<<(QDBusArgument &arg, const FlatTreeNode<T> f) {
arg.beginStructure();
arg << f.value << f.childIndices;
arg.endStructure();
return arg;
}
friend const QDBusArgument &operator>>(const QDBusArgument &arg, FlatTreeNode<T> &f) {
arg.beginStructure();
arg >> f.value >> f.childIndices;
arg.endStructure();
return arg;
}
};
}; // namespace TuxClocker::DBus

118
src/include/Device.hpp Normal file
View File

@ -0,0 +1,118 @@
#pragma once
#include <functional>
#include <optional>
#include <string>
#include <variant>
#include <vector>
namespace TuxClocker {
namespace Device {
enum class AssignmentError {
InvalidArgument,
InvalidType,
NoPermission,
OutOfRange,
UnknownError
};
enum class ReadError {
UnknownError
};
template <typename T> struct Range {
Range(){};
Range(const T &min_, const T &max_) { min = min_, max = max_; }
T min, max;
};
struct Enumeration {
Enumeration(const std::string &name_, const uint &key_) {
name = name_;
key = key_;
}
std::string name;
uint key;
};
using AssignmentArgument = std::variant<int, double, uint>;
using ReadableValue = std::variant<int, uint, double, std::string>;
using ReadResult = std::variant<ReadError, ReadableValue>;
using RangeInfo = std::variant<Range<int>, Range<double>>;
using EnumerationVec = std::vector<Enumeration>;
using AssignableInfo = std::variant<RangeInfo, std::vector<Enumeration>>;
class Assignable {
public:
Assignable(
const std::function<std::optional<AssignmentError>(AssignmentArgument)> assignmentFunc,
AssignableInfo info,
const std::function<std::optional<AssignmentArgument>()> currentValueFunc,
std::optional<std::string> unit = std::nullopt) {
m_assignmentFunc = assignmentFunc;
m_assignableInfo = info;
m_currentValueFunc = currentValueFunc;
m_unit = unit;
}
std::optional<AssignmentError> assign(AssignmentArgument arg) {
return m_assignmentFunc(arg);
}
// What the Assignable is currently set to
std::optional<AssignmentArgument> currentValue() { return m_currentValueFunc(); }
AssignableInfo assignableInfo() { return m_assignableInfo; }
std::optional<std::string> unit() { return m_unit; }
private:
AssignableInfo m_assignableInfo;
std::function<std::optional<AssignmentError>(AssignmentArgument)> m_assignmentFunc;
std::function<std::optional<AssignmentArgument>()> m_currentValueFunc;
std::optional<std::string> m_unit;
};
class DynamicReadable {
public:
DynamicReadable() {}
DynamicReadable(const std::function<std::variant<ReadError, ReadableValue>()> readFunc,
std::optional<std::string> unit = std::nullopt) {
m_readFunc = readFunc;
m_unit = unit;
}
/*std::variant<ReadError, ReadableValue>*/ ReadResult read() { return m_readFunc(); }
auto unit() { return m_unit; }
private:
std::function<std::variant<ReadError, ReadableValue>()> m_readFunc;
std::optional<std::string> m_unit;
};
class StaticReadable {
public:
StaticReadable(ReadableValue value, std::optional<std::string> unit) {
m_value = value;
m_unit = unit;
}
ReadableValue value() { return m_value; }
std::optional<std::string> unit() { return m_unit; }
private:
ReadableValue m_value;
std::optional<std::string> m_unit;
};
// Either a function to reset an Assignable or its default value
using ResetInfo = std::variant<std::function<void()>, AssignmentArgument>;
class Resettable {
private:
};
/* DeviceNode has a name, and optionally implements one of
[Assignable, DynamicReadable, StaticReadable] */
using DeviceInterface = std::variant<Assignable, DynamicReadable, StaticReadable>;
struct DeviceNode {
std::string name;
std::optional<DeviceInterface> interface;
std::string hash;
};
}; // namespace Device
}; // namespace TuxClocker

View File

@ -0,0 +1,30 @@
#pragma once
#include <functional>
#include <tuple>
namespace TuxClocker {
// Creates a function with no arguments from a function that takes some from inputs to the function
// and the function
template <typename T, typename... Args>
auto specializeFunction(Args... args, std::function<T(Args...)> func) {
return [args = std::make_tuple(std::move(args)...), &func]() {
return std::apply([&func](auto... args) { func(args...); }, std::move(args));
};
}
template <template <typename> typename List, typename In, typename Out>
List<Out> map(List<In> list, std::function<Out(In)> func) {
List<Out> retval;
std::transform(list.begin(), list.end(), std::back_inserter(retval), func);
return retval;
}
template <typename List, typename T> List filter(List list, std::function<bool(T)> func) {
List retval;
std::copy_if(list.begin(), list.end(), std::back_inserter(retval), func);
return retval;
}
}; // namespace TuxClocker

48
src/include/Plugin.hpp Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include <boost/config.hpp>
#include <boost/dll/import.hpp>
#include <optional>
#include <string>
#include "Device.hpp"
#include "Tree.hpp"
#define TUXCLOCKER_PLUGIN_EXPORT(PluginType) \
extern "C" BOOST_SYMBOL_EXPORT PluginType __plugin; \
PluginType __plugin;
#define TUXCLOCKER_PLUGIN_SYMBOL_NAME "__plugin"
namespace TuxClocker {
namespace Plugin {
namespace dll = boost::dll;
enum class InitializationError {
UnknownError
};
using namespace TuxClocker::Device;
class Plugin {
public:
static std::string pluginDirName() { return "plugins"; }
// Full path is efined at compile time
static std::string pluginPath();
};
class DevicePlugin {
public:
// Communicate plugin initialization success in this way since constructors cannot
// communicate it.
virtual std::optional<InitializationError> initializationError() = 0;
virtual TreeNode<DeviceNode> deviceRootNode() = 0;
virtual ~DevicePlugin() {}
// Helper for loading all DevicePlugin's
static std::optional<std::vector<boost::shared_ptr<DevicePlugin>>> loadPlugins();
};
}; // namespace Plugin
}; // namespace TuxClocker

90
src/include/Tree.hpp Normal file
View File

@ -0,0 +1,90 @@
#pragma once
#include <functional>
#include <iostream>
#include <vector>
namespace TuxClocker {
template <typename T> struct FlatTreeNode {
T value;
std::vector<int> childIndices;
};
template <typename T> class TreeNode;
template <typename T> struct FlatTree {
std::vector<FlatTreeNode<T>> nodes;
static TreeNode<T> toTree(FlatTree<T> flatTree) {
std::function<void(TreeNode<T> *, uint)> recur;
recur = [&recur, flatTree](TreeNode<T> *node, uint i) {
auto c_indices = flatTree.nodes[i].childIndices;
for (auto index : c_indices)
node->appendChild(TreeNode<T>(flatTree.nodes[index].value));
uint j = 0;
for (auto &c_node : *(node->childrenPtr())) {
recur(&c_node, c_indices[j]);
j++;
}
};
TreeNode<T> root;
if (flatTree.nodes.size() > 0) {
auto realRoot = TreeNode<T>(flatTree.nodes.front().value);
recur(&realRoot, 0);
root = realRoot;
}
return root;
}
};
template <typename T> class TreeNode {
public:
TreeNode(){};
TreeNode(T value) { m_value = value; }
void appendChild(T value) { m_children.push_back(TreeNode{value}); }
void appendChild(TreeNode<T> node) { m_children.push_back(node); }
std::vector<TreeNode<T>> children() { return m_children; }
// Needed for recursive tree construction
std::vector<TreeNode<T>> *childrenPtr() { return &m_children; }
static void preorder(const TreeNode<T> node, std::function<void(const T)> func) {
func(node.m_value);
for (const auto child : node.m_children) {
preorder(child, func);
}
}
T value() { return m_value; }
// Convert tree to array
FlatTree<T> toFlatTree() {
std::vector<FlatTreeNode<T>> nodes;
auto node_ptr = this;
preorderByRef(node_ptr, [&nodes, node_ptr](TreeNode<T> *n) {
FlatTreeNode<T> node;
node.value = n->value();
for (auto &c_node : *(n->childrenPtr())) {
// Find the index of this child node
int j = 0, index = 0;
preorderByRef(node_ptr, [&j, &index, &c_node](TreeNode<T> *n) {
if (n == &c_node)
index = j;
j++;
});
node.childIndices.push_back(index);
}
nodes.push_back(node);
});
return FlatTree<T>{nodes};
}
private:
T m_value;
std::vector<TreeNode<T>> m_children;
static void preorderByRef(TreeNode<T> *node, std::function<void(TreeNode<T> *)> func) {
func(node);
for (auto &c_node : *(node->childrenPtr()))
preorderByRef(&c_node, func);
}
};
}; // namespace TuxClocker

@ -0,0 +1 @@
Subproject commit e306deecd519c2358b4f338c88b2bc269552c78a

View File

@ -104,6 +104,7 @@ extern "C" {
#define nvmlDeviceGetHandleByPciBusId nvmlDeviceGetHandleByPciBusId_v2
#define nvmlDeviceGetNvLinkRemotePciInfo nvmlDeviceGetNvLinkRemotePciInfo_v2
#define nvmlDeviceRemoveGpu nvmlDeviceRemoveGpu_v2
#define nvmlDeviceGetGridLicensableFeatures nvmlDeviceGetGridLicensableFeatures_v2
/***************************************************************************************************/
/** @defgroup nvmlDeviceStructs Device Structs
@ -242,7 +243,7 @@ typedef enum nvmlNvLinkUtilizationCountUnits_enum
NVML_NVLINK_COUNTER_UNIT_CYCLES = 0, // count by cycles
NVML_NVLINK_COUNTER_UNIT_PACKETS = 1, // count by packets
NVML_NVLINK_COUNTER_UNIT_BYTES = 2, // count by bytes
NVML_NVLINK_COUNTER_UNIT_RESERVED = 3, // count reserved for internal use
// this must be last
NVML_NVLINK_COUNTER_UNIT_COUNT
} nvmlNvLinkUtilizationCountUnits_t;
@ -681,7 +682,7 @@ typedef enum nvmlPStates_enum
/**
* GPU Operation Mode
*
* GOM allows to reduce power usage and optimize GPU throughput by disabling GPU features.
* GOM allows one to reduce power usage and optimize GPU throughput by disabling GPU features.
*
* Each GOM is designed to meet specific user needs.
*/
@ -714,6 +715,7 @@ typedef enum nvmlInforomObject_enum
*/
typedef enum nvmlReturn_enum
{
// cppcheck-suppress *
NVML_SUCCESS = 0, //!< The operation was successful
NVML_ERROR_UNINITIALIZED = 1, //!< NVML was not first initialized with nvmlInit()
NVML_ERROR_INVALID_ARGUMENT = 2, //!< A supplied argument is invalid
@ -736,7 +738,7 @@ typedef enum nvmlReturn_enum
NVML_ERROR_IN_USE = 19, //!< An operation cannot be performed because the GPU is currently in use
NVML_ERROR_MEMORY = 20, //!< Insufficient memory
NVML_ERROR_NO_DATA = 21, //!<No data
NVML_ERROR_VGPU_ECC_NOT_SUPPORTED = 22, //!< The requested vgpu operation is not available on target device, becasue ECC is enabled
NVML_ERROR_VGPU_ECC_NOT_SUPPORTED = 22, //!< The requested vgpu operation is not available on target device, because ECC is enabled
NVML_ERROR_UNKNOWN = 999 //!< An internal driver error occurred
} nvmlReturn_t;
@ -1406,6 +1408,7 @@ typedef struct nvmlGridLicensableFeature_st
nvmlGridLicenseFeatureCode_t featureCode; //!< Licensed feature code
unsigned int featureState; //!< Non-zero if feature is currently licensed, otherwise zero
char licenseInfo[NVML_GRID_LICENSE_BUFFER_SIZE];
char productName[NVML_GRID_LICENSE_BUFFER_SIZE];
} nvmlGridLicensableFeature_t;
/**
@ -1729,8 +1732,8 @@ nvmlReturn_t DECLDIR nvmlSystemGetNVMLVersion(char *version, unsigned int length
*
* For all products.
*
* The returned CUDA driver version is the same as the CUDA API
* cuDriverGetVersion() would return on the system.
* The CUDA driver version returned will be retrieved from the currently installed version of CUDA.
* If the cuda library is not found, this function will return a known supported version number.
*
* @param cudaDriverVersion Reference in which to return the version identifier
*
@ -1740,6 +1743,29 @@ nvmlReturn_t DECLDIR nvmlSystemGetNVMLVersion(char *version, unsigned int length
*/
nvmlReturn_t DECLDIR nvmlSystemGetCudaDriverVersion(int *cudaDriverVersion);
/**
* Retrieves the version of the CUDA driver from the shared library.
*
* For all products.
*
* The returned CUDA driver version by calling cuDriverGetVersion()
*
* @param cudaDriverVersion Reference in which to return the version identifier
*
* @return
* - \ref NVML_SUCCESS if \a cudaDriverVersion has been set
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a cudaDriverVersion is NULL
* - \ref NVML_ERROR_LIBRARY_NOT_FOUND if \a libcuda.so.1 or libcuda.dll is not found
* - \ref NVML_ERROR_FUNCTION_NOT_FOUND if \a cuDriverGetVersion() is not found in the shared library
*/
nvmlReturn_t DECLDIR nvmlSystemGetCudaDriverVersion_v2(int *cudaDriverVersion);
/**
* Macros for converting the CUDA driver version number to Major and Minor version numbers.
*/
#define NVML_CUDA_DRIVER_VERSION_MAJOR(v) ((v)/1000)
#define NVML_CUDA_DRIVER_VERSION_MINOR(v) (((v)%1000)/10)
/**
* Gets name of the process with provided process id
*
@ -2335,7 +2361,7 @@ nvmlReturn_t DECLDIR nvmlSystemGetTopologyGpuSet(unsigned int cpuNumber, unsigne
* @param device1 The first device
* @param device2 The second device
* @param p2pIndex p2p Capability Index being looked for between \a device1 and \a device2
* @param p2pStatus Reference in which to return the status of the \a p2pIndex
* @param p2pStatus Reference in which to return the status of the \a p2pIndex
* between \a device1 and \a device2
* @return
* - \ref NVML_SUCCESS if \a p2pStatus has been populated
@ -3034,6 +3060,32 @@ nvmlReturn_t DECLDIR nvmlDeviceSetDefaultAutoBoostedClocksEnabled(nvmlDevice_t d
*/
nvmlReturn_t DECLDIR nvmlDeviceGetFanSpeed(nvmlDevice_t device, unsigned int *speed);
/**
* Retrieves the intended operating speed of the device's specified fan.
*
* Note: The reported speed is the intended fan speed. If the fan is physically blocked and unable to spin, the
* output will not match the actual fan speed.
*
* For all discrete products with dedicated fans.
*
* The fan speed is expressed as a percentage of the maximum, i.e. full speed is 100%
*
* @param device The identifier of the target device
* @param fan The index of the target fan, zero indexed.
* @param speed Reference in which to return the fan speed percentage
*
* @return
* - \ref NVML_SUCCESS if \a speed has been set
* - \ref NVML_ERROR_UNINITIALIZED if the library has not been successfully initialized
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a device is invalid, \a fan is not an acceptable index, or \a speed is NULL
* - \ref NVML_ERROR_NOT_SUPPORTED if the device does not have a fan or is newer than Maxwell
* - \ref NVML_ERROR_GPU_IS_LOST if the target GPU has fallen off the bus or is otherwise inaccessible
* - \ref NVML_ERROR_UNKNOWN on any unexpected error
*/
nvmlReturn_t DECLDIR nvmlDeviceGetFanSpeed_v2(nvmlDevice_t device, unsigned int fan, unsigned int * speed);
/**
* Retrieves the current temperature readings for the device, in degrees C.
*
@ -3681,7 +3733,7 @@ nvmlReturn_t DECLDIR nvmlDeviceGetEncoderStats (nvmlDevice_t device, unsigned in
* array elememt count is passed in \a sessionCount, and \a sessionCount is used to return the number of sessions
* written to the buffer.
*
* If the supplied buffer is not large enough to accomodate the active session array, the function returns
* If the supplied buffer is not large enough to accommodate the active session array, the function returns
* NVML_ERROR_INSUFFICIENT_SIZE, with the element count of nvmlEncoderSessionInfo_t array required in \a sessionCount.
* To query the number of active encoder sessions, call this function with *sessionCount = 0. The code will return
* NVML_SUCCESS with number of active encoder sessions updated in *sessionCount.
@ -3727,7 +3779,7 @@ nvmlReturn_t DECLDIR nvmlDeviceGetDecoderUtilization(nvmlDevice_t device, unsign
* For Maxwell &tm; or newer fully supported devices.
*
* @param device The identifier of the target device
* @param fbcStats Reference to nvmlFBCStats_t structure contianing NvFBC stats
* @param fbcStats Reference to nvmlFBCStats_t structure containing NvFBC stats
*
* @return
* - \ref NVML_SUCCESS if \a fbcStats is fetched
@ -3741,8 +3793,8 @@ nvmlReturn_t DECLDIR nvmlDeviceGetFBCStats(nvmlDevice_t device, nvmlFBCStats_t *
/**
* Retrieves information about active frame buffer capture sessions on a target device.
*
* An array of active encoder sessions is returned in the caller-supplied buffer pointed at by \a sessionInfo. The
* array elememt count is passed in \a sessionCount, and \a sessionCount is used to return the number of sessions
* An array of active FBC sessions is returned in the caller-supplied buffer pointed at by \a sessionInfo. The
* array element count is passed in \a sessionCount, and \a sessionCount is used to return the number of sessions
* written to the buffer.
*
* If the supplied buffer is not large enough to accomodate the active session array, the function returns
@ -4319,6 +4371,12 @@ nvmlReturn_t DECLDIR nvmlUnitSetLedState(nvmlUnit_t unit, nvmlLedColor_t color);
*
* See \ref nvmlEnableState_t for available modes.
*
* After calling this API with mode set to NVML_FEATURE_DISABLED on a device that has its own NUMA
* memory, the given device handle will no longer be valid, and to continue to interact with this
* device, a new handle should be obtained from one of the nvmlDeviceGetHandleBy*() APIs. This
* limitation is currently only applicable to devices that have a coherent NVLink connection to
* system memory.
*
* @param device The identifier of the target device
* @param mode The target persistence mode
*
@ -5297,7 +5355,7 @@ nvmlReturn_t DECLDIR nvmlDeviceSetVirtualizationMode(nvmlDevice_t device, nvmlGp
* pointed at by \a vgpuTypeIds. The element count of nvmlVgpuTypeId_t array is passed in \a vgpuCount, and \a vgpuCount
* is used to return the number of vGPU types written to the buffer.
*
* If the supplied buffer is not large enough to accomodate the vGPU type array, the function returns
* If the supplied buffer is not large enough to accommodate the vGPU type array, the function returns
* NVML_ERROR_INSUFFICIENT_SIZE, with the element count of nvmlVgpuTypeId_t array required in \a vgpuCount.
* To query the number of vGPU types supported for the GPU, call this function with *vgpuCount = 0.
* The code will return NVML_ERROR_INSUFFICIENT_SIZE, or NVML_SUCCESS if no vGPU types are supported.
@ -5312,7 +5370,7 @@ nvmlReturn_t DECLDIR nvmlDeviceSetVirtualizationMode(nvmlDevice_t device, nvmlGp
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a vgpuCount is NULL or \a device is invalid
* - \ref NVML_ERROR_NOT_SUPPORTED if vGPU is not supported by the device
* - \ref NVML_ERROR_VGPU_ECC_NOT_SUPPORTED if ECC is enabled on the device
* - \ref NVML_ERROR_UNKNOWN on any unexpected error
* - \ref NVML_ERROR_UNKNOWN on any unexpected error
*/
nvmlReturn_t DECLDIR nvmlDeviceGetSupportedVgpus(nvmlDevice_t device, unsigned int *vgpuCount, nvmlVgpuTypeId_t *vgpuTypeIds);
@ -5327,7 +5385,7 @@ nvmlReturn_t DECLDIR nvmlDeviceGetSupportedVgpus(nvmlDevice_t device, unsigned i
* can concurrently run on a device. For example, if only one vGPU type is allowed at a time on a device, then the creatable
* list will be restricted to whatever vGPU type is already running on the device.
*
* If the supplied buffer is not large enough to accomodate the vGPU type array, the function returns
* If the supplied buffer is not large enough to accommodate the vGPU type array, the function returns
* NVML_ERROR_INSUFFICIENT_SIZE, with the element count of nvmlVgpuTypeId_t array required in \a vgpuCount.
* To query the number of vGPU types createable for the GPU, call this function with *vgpuCount = 0.
* The code will return NVML_ERROR_INSUFFICIENT_SIZE, or NVML_SUCCESS if no vGPU types are creatable.
@ -5392,7 +5450,7 @@ nvmlReturn_t DECLDIR nvmlVgpuTypeGetName(nvmlVgpuTypeId_t vgpuTypeId, char *vgpu
*
* @param vgpuTypeId Handle to vGPU type
* @param deviceID Device ID and vendor ID of the device contained in single 32 bit value
* @param subsystemID Subsytem ID and subsytem vendor ID of the device contained in single 32 bit value
* @param subsystemID Subsystem ID and subsystem vendor ID of the device contained in single 32 bit value
*
* @return
* - \ref NVML_SUCCESS successful completion
@ -5489,7 +5547,7 @@ nvmlReturn_t DECLDIR nvmlVgpuTypeGetLicense(nvmlVgpuTypeId_t vgpuTypeId, char *v
* - \ref NVML_SUCCESS successful completion
* - \ref NVML_ERROR_NOT_SUPPORTED if frame rate limiter is turned off for the vGPU type
* - \ref NVML_ERROR_UNINITIALIZED if the library has not been successfully initialized
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a device is invalid, or \a frameRateLimit is NULL
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a vgpuTypeId is invalid, or \a frameRateLimit is NULL
* - \ref NVML_ERROR_UNKNOWN on any unexpected error
*/
nvmlReturn_t DECLDIR nvmlVgpuTypeGetFrameRateLimit(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *frameRateLimit);
@ -5512,6 +5570,21 @@ nvmlReturn_t DECLDIR nvmlVgpuTypeGetFrameRateLimit(nvmlVgpuTypeId_t vgpuTypeId,
*/
nvmlReturn_t DECLDIR nvmlVgpuTypeGetMaxInstances(nvmlDevice_t device, nvmlVgpuTypeId_t vgpuTypeId, unsigned int *vgpuInstanceCount);
/**
* Retrieve the maximum number of vGPU instances supported per VM for given vGPU type
*
* For Kepler &tm; or newer fully supported devices.
*
* @param vgpuTypeId Handle to vGPU type
* @param vgpuInstanceCountPerVm Pointer to get the max number of vGPU instances supported per VM for given \a vgpuTypeId
* @return
* - \ref NVML_SUCCESS successful completion
* - \ref NVML_ERROR_UNINITIALIZED if the library has not been successfully initialized
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a vgpuTypeId is invalid, or \a vgpuInstanceCountPerVm is NULL
* - \ref NVML_ERROR_UNKNOWN on any unexpected error
*/
nvmlReturn_t DECLDIR nvmlVgpuTypeGetMaxInstancesPerVm(nvmlVgpuTypeId_t vgpuTypeId, unsigned int *vgpuInstanceCountPerVm);
/**
* Retrieve the active vGPU instances on a device.
*
@ -5519,7 +5592,7 @@ nvmlReturn_t DECLDIR nvmlVgpuTypeGetMaxInstances(nvmlDevice_t device, nvmlVgpuTy
* array elememt count is passed in \a vgpuCount, and \a vgpuCount is used to return the number of vGPU instances
* written to the buffer.
*
* If the supplied buffer is not large enough to accomodate the vGPU instance array, the function returns
* If the supplied buffer is not large enough to accommodate the vGPU instance array, the function returns
* NVML_ERROR_INSUFFICIENT_SIZE, with the element count of nvmlVgpuInstance_t array required in \a vgpuCount.
* To query the number of active vGPU instances, call this function with *vgpuCount = 0. The code will return
* NVML_ERROR_INSUFFICIENT_SIZE, or NVML_SUCCESS if no vGPU Types are supported.
@ -5702,7 +5775,7 @@ nvmlReturn_t DECLDIR nvmlVgpuInstanceGetFrameRateLimit(nvmlVgpuInstance_t vgpuIn
* @param encoderCapacity Reference to an unsigned int for the encoder capacity
*
* @return
* - \ref NVML_SUCCESS if \a encoderCapacity has been retrived
* - \ref NVML_SUCCESS if \a encoderCapacity has been retrieved
* - \ref NVML_ERROR_UNINITIALIZED if the library has not been successfully initialized
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a vgpuInstance is 0, or \a encoderQueryType is invalid
* - \ref NVML_ERROR_NOT_FOUND if \a vgpuInstance does not match a valid active vGPU instance on the system
@ -5721,7 +5794,7 @@ nvmlReturn_t DECLDIR nvmlVgpuInstanceGetEncoderCapacity(nvmlVgpuInstance_t vgpuI
* @return
* - \ref NVML_SUCCESS if \a encoderCapacity has been set
* - \ref NVML_ERROR_UNINITIALIZED if the library has not been successfully initialized
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a vgpuInstance is 0
* - \ref NVML_ERROR_INVALID_ARGUMENT if \a vgpuInstance is 0, or \a encoderCapacity is out of range of 0-100.
* - \ref NVML_ERROR_NOT_FOUND if \a vgpuInstance does not match a valid active vGPU instance on the system
* - \ref NVML_ERROR_UNKNOWN on any unexpected error
*/
@ -5863,10 +5936,10 @@ nvmlReturn_t DECLDIR nvmlVgpuInstanceGetEncoderStats(nvmlVgpuInstance_t vgpuInst
* Retrieves information about all active encoder sessions on a vGPU Instance.
*
* An array of active encoder sessions is returned in the caller-supplied buffer pointed at by \a sessionInfo. The
* array elememt count is passed in \a sessionCount, and \a sessionCount is used to return the number of sessions
* array element count is passed in \a sessionCount, and \a sessionCount is used to return the number of sessions
* written to the buffer.
*
* If the supplied buffer is not large enough to accomodate the active session array, the function returns
* If the supplied buffer is not large enough to accommodate the active session array, the function returns
* NVML_ERROR_INSUFFICIENT_SIZE, with the element count of nvmlEncoderSessionInfo_t array required in \a sessionCount.
* To query the number of active encoder sessions, call this function with *sessionCount = 0. The code will return
* NVML_SUCCESS with number of active encoder sessions updated in *sessionCount.
@ -5896,7 +5969,7 @@ nvmlReturn_t DECLDIR nvmlVgpuInstanceGetEncoderSessions(nvmlVgpuInstance_t vgpuI
* For Maxwell &tm; or newer fully supported devices.
*
* @param vgpuInstance Identifier of the target vGPU instance
* @param fbcStats Reference to nvmlFBCStats_t structure contianing NvFBC stats
* @param fbcStats Reference to nvmlFBCStats_t structure containing NvFBC stats
*
* @return
* - \ref NVML_SUCCESS if \a fbcStats is fetched
@ -5910,7 +5983,7 @@ nvmlReturn_t DECLDIR nvmlVgpuInstanceGetFBCStats(nvmlVgpuInstance_t vgpuInstance
/**
* Retrieves information about active frame buffer capture sessions on a vGPU Instance.
*
* An array of active encoder sessions is returned in the caller-supplied buffer pointed at by \a sessionInfo. The
* An array of active FBC sessions is returned in the caller-supplied buffer pointed at by \a sessionInfo. The
* array element count is passed in \a sessionCount, and \a sessionCount is used to return the number of sessions
* written to the buffer.
*
@ -6071,6 +6144,15 @@ nvmlReturn_t DECLDIR nvmlVgpuInstanceGetAccountingStats(nvmlVgpuInstance_t vgpuI
*/
/***************************************************************************************************/
/**
* Structure representing range of vGPU versions.
*/
typedef struct nvmlVgpuVersion_st
{
unsigned int minVersion; //!< Minimum vGPU version.
unsigned int maxVersion; //!< Maximum vGPU version.
} nvmlVgpuVersion_t;
/**
* vGPU metadata structure.
*/
@ -6081,7 +6163,8 @@ typedef struct nvmlVgpuMetadata_st
nvmlVgpuGuestInfoState_t guestInfoState; //!< Current state of Guest-dependent fields
char guestDriverVersion[NVML_SYSTEM_DRIVER_VERSION_BUFFER_SIZE]; //!< Version of driver installed in guest
char hostDriverVersion[NVML_SYSTEM_DRIVER_VERSION_BUFFER_SIZE]; //!< Version of driver installed in host
unsigned int reserved[8]; //!< Reserved for internal use
unsigned int reserved[7]; //!< Reserved for internal use
unsigned int guestVgpuVersion; //!< vGPU version of guest driver
unsigned int opaqueDataSize; //!< Size of opaque data field in bytes
char opaqueData[4]; //!< Opaque data
} nvmlVgpuMetadata_t;
@ -6095,7 +6178,8 @@ typedef struct nvmlVgpuPgpuMetadata_st
unsigned int revision; //!< Current revision of the structure
char hostDriverVersion[NVML_SYSTEM_DRIVER_VERSION_BUFFER_SIZE]; //!< Host driver version
unsigned int pgpuVirtualizationCaps; //!< Pgpu virtualizaion capabilities bitfileld
unsigned int reserved[7]; //!< Reserved for internal use
unsigned int reserved[5]; //!< Reserved for internal use
nvmlVgpuVersion_t hostSupportedVgpuRange; //!< vGPU version range supported by host driver
unsigned int opaqueDataSize; //!< Size of opaque data field in bytes
char opaqueData[4]; //!< Opaque data
} nvmlVgpuPgpuMetadata_t;
@ -6118,7 +6202,7 @@ typedef enum nvmlVgpuVmCompatibility_enum
typedef enum nvmlVgpuPgpuCompatibilityLimitCode_enum
{
NVML_VGPU_COMPATIBILITY_LIMIT_NONE = 0x0, //!< Compatibility is not limited.
NVML_VGPU_COMPATIBILITY_LIMIT_HOST_DRIVER = 0x1, //!< Compatibility is limited by host driver version.
NVML_VGPU_COMPATIBILITY_LIMIT_HOST_DRIVER = 0x1, //!< ompatibility is limited by host driver version.
NVML_VGPU_COMPATIBILITY_LIMIT_GUEST_DRIVER = 0x2, //!< Compatibility is limited by guest driver version.
NVML_VGPU_COMPATIBILITY_LIMIT_GPU = 0x4, //!< Compatibility is limited by GPU hardware.
NVML_VGPU_COMPATIBILITY_LIMIT_OTHER = 0x80000000, //!< Compatibility is limited by an undefined factor.
@ -6140,7 +6224,7 @@ typedef struct nvmlVgpuPgpuCompatibility_st
*
* nvmlVgpuInstanceGetMetadata() may be called at any time for a vGPU instance. Some fields in the returned structure are
* dependent on information obtained from the guest VM, which may not yet have reached a state where that information
* is available. The current state of these dependent fields is reflected in the info structure's \ref guestInfoState field.
* is available. The current state of these dependent fields is reflected in the info structure's \ref nvmlVgpuGuestInfoState_t field.
*
* The VMM may choose to read and save the vGPU's VM info as persistent metadata associated with the VM, and provide
* it to GRID Virtual GPU Manager when creating a vGPU for subsequent instances of the VM.
@ -6191,7 +6275,7 @@ nvmlReturn_t DECLDIR nvmlDeviceGetVgpuMetadata(nvmlDevice_t device, nvmlVgpuPgpu
*
* The caller passes in a buffer via \a compatibilityInfo, into which a compatibility information structure is written. The
* structure defines the states in which the vGPU / VM may be booted on the physical GPU. If the vGPU / VM compatibility
* with the physical GPU is limited, a limit code indicates the factor limiting compability.
* with the physical GPU is limited, a limit code indicates the factor limiting compatibility.
* (see \ref nvmlVgpuPgpuCompatibilityLimitCode_t for details).
*
* Note: vGPU compatibility does not take into account dynamic capacity conditions that may limit a system's ability to
@ -6208,6 +6292,65 @@ nvmlReturn_t DECLDIR nvmlDeviceGetVgpuMetadata(nvmlDevice_t device, nvmlVgpuPgpu
*/
nvmlReturn_t DECLDIR nvmlGetVgpuCompatibility(nvmlVgpuMetadata_t *vgpuMetadata, nvmlVgpuPgpuMetadata_t *pgpuMetadata, nvmlVgpuPgpuCompatibility_t *compatibilityInfo);
/*
* Virtual GPU (vGPU) version
*
* The NVIDIA vGPU Manager and the guest drivers are tagged with a range of supported vGPU versions. This determines the range of NVIDIA guest driver versions that
* are compatible for vGPU feature support with a given NVIDIA vGPU Manager. For vGPU feature support, the range of supported versions for the NVIDIA vGPU Manager
* and the guest driver must overlap. Otherwise, the guest driver fails to load in the VM.
*
* When the NVIDIA guest driver loads, either when the VM is booted or when the driver is installed or upgraded, a negotiation occurs between the guest driver
* and the NVIDIA vGPU Manager to select the highest mutually compatible vGPU version. The negotiated vGPU version stays the same across VM migration.
*/
/**
* Query the ranges of supported vGPU versions.
*
* This function gets the linear range of supported vGPU versions that is preset for the NVIDIA vGPU Manager and the range set by an administrator.
* If the preset range has not been overridden by \ref nvmlSetVgpuVersion, both ranges are the same.
*
* The caller passes pointers to the following \ref nvmlVgpuVersion_t structures, into which the NVIDIA vGPU Manager writes the ranges:
* 1. \a supported structure that represents the preset range of vGPU versions supported by the NVIDIA vGPU Manager.
* 2. \a current structure that represents the range of supported vGPU versions set by an administrator. By default, this range is the same as the preset range.
*
* @param supported Pointer to the structure in which the preset range of vGPU versions supported by the NVIDIA vGPU Manager is written
* @param current Pointer to the structure in which the range of supported vGPU versions set by an administrator is written
*
* @return
* - \ref NVML_SUCCESS The vGPU version range structures were successfully obtained.
* - \ref NVML_ERROR_NOT_SUPPORTED The API is not supported.
* - \ref NVML_ERROR_INVALID_ARGUMENT The \a supported parameter or the \a current parameter is NULL.
* - \ref NVML_ERROR_UNKNOWN An error occurred while the data was being fetched.
*/
nvmlReturn_t DECLDIR nvmlGetVgpuVersion(nvmlVgpuVersion_t *supported, nvmlVgpuVersion_t *current);
/**
* Override the preset range of vGPU versions supported by the NVIDIA vGPU Manager with a range set by an administrator.
*
* This function configures the NVIDIA vGPU Manager with a range of supported vGPU versions set by an administrator. This range must be a subset of the
* preset range that the NVIDIA vGPU Manager supports. The custom range set by an administrator takes precedence over the preset range and is advertised to
* the guest VM for negotiating the vGPU version. See \ref nvmlGetVgpuVersion for details of how to query the preset range of versions supported.
*
* This function takes a pointer to vGPU version range structure \ref nvmlVgpuVersion_t as input to override the preset vGPU version range that the NVIDIA vGPU Manager supports.
*
* After host system reboot or driver reload, the range of supported versions reverts to the range that is preset for the NVIDIA vGPU Manager.
*
* @note 1. The range set by the administrator must be a subset of the preset range that the NVIDIA vGPU Manager supports. Otherwise, an error is returned.
* 2. If the range of supported guest driver versions does not overlap the range set by the administrator, the guest driver fails to load.
* 3. If the range of supported guest driver versions overlaps the range set by the administrator, the guest driver will load with a negotiated
* vGPU version that is the maximum value in the overlapping range.
* 4. No VMs must be running on the host when this function is called. If a VM is running on the host, the call to this function fails.
*
* @param vgpuVersion Pointer to a caller-supplied range of supported vGPU versions.
*
* @return
* - \ref NVML_SUCCESS The preset range of supported vGPU versions was successfully overridden.
* - \ref NVML_ERROR_NOT_SUPPORTED The API is not supported.
* - \ref NVML_ERROR_IN_USE The range was not overridden because a VM is running on the host.
* - \ref NVML_ERROR_INVALID_ARGUMENT The \a vgpuVersion parameter specifies a range that is outside the range supported by the NVIDIA vGPU Manager or if \a vgpuVersion is NULL.
*/
nvmlReturn_t DECLDIR nvmlSetVgpuVersion(nvmlVgpuVersion_t *vgpuVersion);
/** @} */
/***************************************************************************************************/
@ -6265,6 +6408,7 @@ nvmlReturn_t DECLDIR nvmlGetBlacklistDeviceInfoByIndex(unsigned int index, nvmlB
* NVML API versioning support
*/
#if defined(__NVML_API_VERSION_INTERNAL)
#undef nvmlDeviceGetGridLicensableFeatures
#undef nvmlDeviceRemoveGpu
#undef nvmlDeviceGetNvLinkRemotePciInfo
#undef nvmlDeviceGetPciInfo

@ -0,0 +1 @@
Subproject commit b3270e0dd7b6312f7a4fe8647e2333dbb86e355e

View File

@ -0,0 +1,90 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <tc_common.h>
#include <stdint.h>
#include <stdbool.h>
/* Defines the interface for modifying writable properties in controlled hardware.
It is a tree structure provided by a module. */
enum tc_assignable_value_category {
TC_ASSIGNABLE_NONE,
TC_ASSIGNABLE_RANGE,
TC_ASSIGNABLE_ENUM
};
// Is the range double or integer
enum tc_assignable_range_data_type {
TC_ASSIGNABLE_RANGE_INT,
TC_ASSIGNABLE_RANGE_DOUBLE
};
typedef struct {
double min, max;
} tc_assignable_range_double_t;
typedef struct {
int64_t min, max;
} tc_assignable_range_int_t;
typedef struct {
enum tc_assignable_range_data_type range_data_type;
union {
tc_assignable_range_double_t double_range;
tc_assignable_range_int_t int_range;
};
} tc_assignable_range_t;
typedef struct {
uint16_t property_count;
char **properties;
} tc_assignable_enum_t;
typedef struct tc_assignable_node_t {
// Assignable name eg. fan speed
char *name;
// Unit for assignable
char *unit;
// Callback for assignment (use NULL for a placeholder node)
int8_t (*assign_callback)(tc_variant_t value, const struct tc_assignable_node_t *node);
// Possible values for tunables are either values from a range or enumerations
enum tc_assignable_value_category value_category;
union {
tc_assignable_enum_t enum_info;
tc_assignable_range_t range_info;
};
struct tc_assignable_node_t *parent;
uint16_t children_count;
struct tc_assignable_node_t **children_nodes;
} tc_assignable_node_t;
// Master data structure loaded by module loader
typedef struct {
tc_assignable_node_t *root_node;
const char *(*sha256_hash)(
const tc_assignable_node_t *); // Callback to get a unique hash for a node
} tc_assignable_module_data_t;
/* Utility functions for assignables */
// Allocates memory for a tunable node
tc_assignable_node_t *tc_assignable_node_new();
// Deallocates memory of the node
void tc_assignable_node_destroy(tc_assignable_node_t *node);
// Add a child to a node
int8_t tc_assignable_node_add_child(tc_assignable_node_t *node, tc_assignable_node_t *child);
/* Utility functions for range and property info*/
void tc_assignable_node_set_data(tc_assignable_node_t *node, char *unit, char *name,
int8_t (*assign_callback)(tc_variant_t, const tc_assignable_node_t *));
#ifdef __cplusplus
}
#endif

75
src/include/tc_common.h Normal file
View File

@ -0,0 +1,75 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
// Common definitions for tuxclocker
// Error values
#define TC_SUCCESS 0
#define TC_EGENERIC (-1)
#define TC_ENOMEM (-2)
#define TC_EINVAL (-3) // Invalid argument
#define TC_ENOPERM (-4) // Insufficient permissions
#define TC_EINVALPREREQ \
(-5) // Invalid prerequisite value (eg. trying to set fan speed with automatic mode active)
// Tagged union of data types for simulating function overloading
enum tc_data_types {
TC_TYPE_NONE,
TC_TYPE_INT,
TC_TYPE_UINT,
TC_TYPE_DOUBLE,
TC_TYPE_STRING,
TC_TYPE_STRING_ARR
};
typedef struct {
enum tc_data_types data_type;
union {
int64_t int_value;
uint64_t uint_value;
double double_value;
char *string_value;
};
} tc_variant_t;
typedef struct {
enum tc_data_types arg_type;
union {
int int_arg;
char **string_arr_arg;
};
} tc_arg_t;
// Utility functions
// Allocate a string array on the heap
char **tc_str_arr_dup(uint16_t str_count, char **const strings);
// Deallocate string array
void tc_str_arr_free(uint16_t str_count, char **strings);
// Binary search tree whose left node contains the smaller value
typedef struct tc_bin_node_ {
void *key;
void *value;
struct tc_bin_node_ *left;
struct tc_bin_node_ *right;
} tc_bin_node_t;
// Create a new node with key and data in the appropriate position
tc_bin_node_t *tc_bin_node_insert(tc_bin_node_t *node, void *key, void *value);
// Find the value associated with the key
void *tc_bin_node_find_value(tc_bin_node_t *node, const void *key);
// Destroy a node and its children
void tc_bin_node_destroy(tc_bin_node_t *node);
// Function for const char* -> SHA256. Don't free().
const char *tc_sha256(const char *string, uint32_t string_length);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,21 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <sys/stat.h>
// Contains functions and definitions for dealing with files
#ifdef __cplusplus
extern "C" {
#endif
// Get filename list from a directory. Return value needs to be freed.
char **tc_fs_dir_filenames(const char *dir_name, uint16_t *file_count);
// Check if a file exists
bool tc_fs_file_exists(const char *path);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1 @@
#pragma once

123
src/include/tc_module.h Normal file
View File

@ -0,0 +1,123 @@
#pragma once
#include <stdint.h>
#include <tc_common.h>
#include <tc_readable.h>
#include <tc_assignable.h>
// Use unmangled symbols for C++
#ifdef __cplusplus
extern "C" {
#endif
// Bitmask values for module categories
#define TC_ASSIGNABLE (1)
#define TC_READABLE (1 << 1)
#define TC_INTERFACE (1 << 2)
#define TC_READABLE_DYNAMIC (1)
#define TC_READABLE_STATIC (1 << 1)
// Categories for modules.
enum tc_module_category {
TC_CATEGORY_ASSIGNABLE,
TC_CATEGORY_READABLE,
TC_CATEGORY_INTERFACE
};
// Maximum amount of modules loaded at once
#define TC_MAX_LOADED_MODULES 64
// Default module path in case not defined
#ifndef TC_MODULE_PATH
#define TC_MODULE_PATH "/usr/lib/tuxclocker/modules"
#endif
#define TC_MODULE_DATABASE_NAME "tc_modules.json"
#define TC_MODULE_DATABASE_PATH TC_MODULE_PATH "/" TC_MODULE_DATABASE_NAME
// Env variable name to load modules from in addition
#define TC_MODULE_PATH_ENV "TC_MODULE_PATH"
// Function name the module loader uses to get the module_t describing the module
#define TC_MODULE_INFO_FUNCTION_NAME "tc_get_module_handle"
#define TC_MODULE_INFO_FUNCTION tc_get_module_handle
// Maximum argument count for "overloaded" functions
#define TC_MAX_FUNCTION_ARGC 16
union module_data_callback_t {
tc_readable_module_data_t (*readable_data)();
tc_assignable_module_data_t (*assignable_data)();
};
// Tagged union for category specific data
typedef struct {
// TODO : move initialization/close callback here
int8_t (*init)();
int8_t (*close)();
uint64_t category;
// Since category specific data might be generated after calling module's 'init' callback,
// use function pointers to fetch it.
union {
tc_readable_module_data_t (*readable_data)();
tc_assignable_module_data_t (*assignable_data)();
};
} tc_module_category_data_t;
typedef struct {
uint64_t category_mask;
uint16_t num_categories;
tc_module_category_data_t category_data_list[64];
} tc_module_category_info_t;
typedef struct tc_module_t {
enum tc_module_category category;
// Short name of the module like nvidia, qt
const char *name;
// Longer description
const char *description;
// Initializes the module's internal state
int8_t (*init_callback)();
// Arguments for init_callback
uint8_t init_callback_argc;
enum tc_data_types init_callback_args[TC_MAX_FUNCTION_ARGC];
// Frees the internal memory of the module
int8_t (*close_callback)();
// Callback for category specific main data structure of the module
void *(*category_data_callback)();
tc_module_category_info_t category_info;
} tc_module_t;
// Try to return the module handle matching the category and name. If it doesn't exist or there was
// a problem loading the module, returns NULL.
tc_module_t *tc_module_find(enum tc_module_category category, const char *name);
// Try to return all module handles matching 'category'. The return value needs to be freed in
// addition to tc_module_close()
tc_module_t **tc_module_find_all_from_category(enum tc_module_category category, uint16_t *count);
// Convenience functions
tc_module_category_info_t tc_module_category_info_create(
uint64_t mask, uint16_t num_categories, const tc_module_category_data_t *categories);
tc_module_category_data_t tc_module_category_data_create(
uint64_t category, union module_data_callback_t u);
// Close the module after successful find
void tc_module_close(tc_module_t *module);
// Wrappers for platform-specific functions for loading libraries (modules) at runtime
void *tc_dlopen(const char *path);
void *tc_dlsym(void *handle, const char *name);
void tc_dlclose(void *handle);
char *tc_dlerror();
#ifdef __cplusplus
}
#endif

67
src/include/tc_readable.h Normal file
View File

@ -0,0 +1,67 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// Defines the structure of the read-only properties of hardware
#include <tc_common.h>
#include <stdint.h>
#include <stdbool.h>
// Return value type for querying values
typedef struct {
bool valid;
tc_variant_t data;
} tc_readable_result_t;
typedef struct tc_readable_node_t_ {
char *name;
char *unit;
// Is the value of this node constant
bool constant;
// Instead of an update callback store the value for constants
union {
tc_readable_result_t (*value_callback)(const struct tc_readable_node_t_ *);
tc_variant_t data;
};
uint16_t children_count;
struct tc_readable_node_t_ **children_nodes;
} tc_readable_node_t;
// Master data structure loaded by module loader
typedef struct {
uint64_t category_mask; // Which types of readable (dynamic or static) are implemented
tc_readable_node_t *root_static_node;
tc_readable_node_t *root_node;
const char *(*sha256_hash)(
const tc_readable_node_t *); // Callback to get a unique hash for a node
} tc_readable_module_data_t;
// Utility functions
// Create a new node
tc_readable_node_t *tc_readable_node_new();
// Destroys a node and all its children
void tc_readable_node_destroy(tc_readable_node_t *node);
// Add a child to a node
int8_t tc_readable_node_add_child(tc_readable_node_t *parent, tc_readable_node_t *child);
// Convinience function for creating a new node and adding it to parent
tc_readable_node_t *tc_readable_node_add_new_child(tc_readable_node_t *parent);
// Set node data
void tc_readable_node_set_data(tc_readable_node_t *node, const char *name, const char *unit);
// Create a tc_readable_result from data, data type and validity. Avoids boilerplate in returning
// values from readable nodes.
tc_readable_result_t tc_readable_result_create(enum tc_data_types type, void *data, bool valid);
#ifdef __cplusplus
}
#endif

32
src/lib/Crypto.cpp Normal file
View File

@ -0,0 +1,32 @@
#include <Crypto.hpp>
#include <openssl/md5.h>
#include <openssl/sha.h>
namespace TuxClocker::Crypto {
std::string sha256(std::string s) {
auto d = SHA256(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(), 0);
char out[(SHA256_DIGEST_LENGTH * 2) + 1];
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
sprintf(out + (i * 2), "%02x", d[i]);
out[SHA256_DIGEST_LENGTH * 2] = '\0';
return std::string(out);
}
std::string md5(std::string s) {
unsigned char data[MD5_DIGEST_LENGTH];
MD5(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(), data);
char out[(MD5_DIGEST_LENGTH * 2) + 1];
for (int i = 0; i < MD5_DIGEST_LENGTH; i++)
sprintf(out + (i * 2), "%02x", data[i]);
out[MD5_DIGEST_LENGTH * 2] = '\0';
return std::string(out);
}
}; // namespace TuxClocker::Crypto

37
src/lib/Plugin.cpp Normal file
View File

@ -0,0 +1,37 @@
#include <Plugin.hpp>
#include <filesystem>
#include <sstream>
using namespace TuxClocker::Plugin;
namespace fs = std::filesystem;
std::string Plugin::pluginPath() { return TC_PLUGIN_PATH; }
std::optional<std::vector<boost::shared_ptr<DevicePlugin>>> DevicePlugin::loadPlugins() {
std::vector<boost::shared_ptr<DevicePlugin>> retval;
std::string pluginPath;
const char *pluginPathEnv = std::getenv("TUXCLOCKER_PLUGIN_PATH");
// Use plugin path from environment if it exists
if (pluginPathEnv)
pluginPath = pluginPathEnv;
else
pluginPath = Plugin::pluginPath();
for (const fs::directory_entry &entry : fs::directory_iterator(pluginPath)) {
// Bleh, have to catch this unless I do more manual checks
try {
auto plugin = boost::dll::import_symbol<DevicePlugin>(
entry.path().string(), TUXCLOCKER_PLUGIN_SYMBOL_NAME);
retval.push_back(plugin);
} catch (boost::system::system_error &e) {
}
}
if (retval.empty())
return std::nullopt;
return retval;
}

0
src/lib/meson.build Normal file
View File

View File

@ -0,0 +1,42 @@
#include <tc_filesystem.h>
#include <tc_common.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
char **tc_fs_dir_filenames(const char *dir_name, uint16_t *file_count) {
char *file_names[256];
struct dirent *entry;
DIR *dir = opendir(dir_name);
uint16_t i = 0;
if (dir != NULL) {
while ((entry = readdir(dir)) != NULL) {
file_names[i] = strdup(entry->d_name);
i++;
}
closedir(dir);
}
else {
// Couldn't open dir
return NULL;
}
*file_count = i;
char **retval = tc_str_arr_dup(i, file_names);
for (int j = 0; j < i; j++) {
free(file_names[j]);
}
return retval;
}
bool tc_fs_file_exists(const char *path) {
struct stat buf;
return (stat(path, &buf) == 0);
}

View File

@ -0,0 +1 @@

20
src/lib/posix/module.c Normal file
View File

@ -0,0 +1,20 @@
#include <tc_module.h>
#include <tc_common.h>
#include <dlfcn.h>
void *tc_dlopen(const char *path) {
return dlopen(path, RTLD_LAZY);
}
void *tc_dlsym(void *handle, const char *name) {
return dlsym(handle, name);
}
void tc_dlclose(void *handle) {
dlclose(handle);
}
char *tc_dlerror() {
return dlerror();
}

54
src/lib/tc_assignable.c Normal file
View File

@ -0,0 +1,54 @@
#include <stdlib.h>
#include <string.h>
#include <tc_common.h>
#include <tc_assignable.h>
tc_assignable_node_t *tc_assignable_node_new() {
tc_assignable_node_t *node = calloc(1, sizeof(tc_assignable_node_t));
return node;
}
void tc_assignable_node_destroy(tc_assignable_node_t *node) {
if (node->parent != NULL) {
// Update the parent node
// Copy all members not being deleted
// Allocate memory for n-1 array members and copy nodes not being deleted
tc_assignable_node_t **new_children_ptr = calloc(node->parent->children_count - 1, sizeof(tc_assignable_node_t));
uint16_t saved = 0;
for (uint16_t i = 0; i < node->parent->children_count; i++) {
if (node != node->parent->children_nodes[i]) {
memcpy(node->parent->children_nodes[saved], new_children_ptr[saved], sizeof(tc_assignable_node_t));
saved++;
}
}
// Free memory of previous array
for (uint16_t i = 0; i < node->parent->children_count; i++) {
tc_assignable_node_destroy(node->parent->children_nodes[i]);
}
free(node->parent->children_nodes);
node->parent->children_count -= 1;
node->parent->children_nodes = new_children_ptr;
}
// The name is allocated on the heap
free(node->name);
free(node->unit);
free(node);
}
int8_t tc_assignable_node_add_child(tc_assignable_node_t *parent, tc_assignable_node_t *child) {
parent->children_count++;
if ((parent->children_nodes = realloc(parent->children_nodes, parent->children_count * sizeof(parent))) == NULL) {
return TC_ENOMEM;
}
parent->children_nodes[parent->children_count - 1] = child;
return TC_SUCCESS;
}
void tc_assignable_node_set_data(tc_assignable_node_t* node, char* unit, char* name, int8_t (*assign_callback)(tc_variant_t, const tc_assignable_node_t*)) {
node->unit = unit;
node->name = name;
node->assign_callback = assign_callback;
}

88
src/lib/tc_common.c Normal file
View File

@ -0,0 +1,88 @@
#include <tc_common.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>
char **tc_str_arr_dup(uint16_t str_count, char **const strings) {
char **ptr_arr = calloc(str_count, sizeof(char*));
for (uint16_t i = 0; i < str_count; i++) {
ptr_arr[i] = strdup(strings[i]);
}
return ptr_arr;
}
void tc_str_arr_free(uint16_t str_count, char **strings) {
for (uint16_t i = 0; i < str_count; i++) {
free(strings[i]);
}
free(strings);
}
static tc_bin_node_t *new_bin_node(void *key, void *value) {
tc_bin_node_t *new_node = calloc(1, sizeof(tc_bin_node_t));
new_node->key = key;
new_node->value = value;
return new_node;
}
static void single_destroy(tc_bin_node_t *node) {
free(node);
}
static void traverse_postorder(tc_bin_node_t *node, void (*func)(tc_bin_node_t*)) {
if (node->left) {
traverse_postorder(node, func);
}
if (node->right) {
traverse_postorder(node, func);
}
func(node);
}
tc_bin_node_t *tc_bin_node_insert(tc_bin_node_t *node, void *key, void *value) {
if (node == NULL) {
return new_bin_node(key, value);
}
if (key < node->key) {
node->left = tc_bin_node_insert(node->left, key, value);
}
else if (key > node->key) {
node->right = tc_bin_node_insert(node->right, key, value);
}
return node;
}
void *tc_bin_node_find_value(tc_bin_node_t *node, const void *key) {
if (node == NULL) {
return NULL;
}
if (node->key == key) {
return node->value;
}
if (node->key < key) {
return tc_bin_node_find_value(node->right, key);
}
return tc_bin_node_find_value(node->left, key);
}
void tc_bin_node_destroy(tc_bin_node_t *node) {
traverse_postorder(node, &single_destroy);
}
const char *tc_sha256(const char *string, uint32_t string_length) {
unsigned char *d = SHA256((unsigned char*) string, string_length, 0);
static char out[(SHA256_DIGEST_LENGTH * 2) + 1];
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(out + (i * 2), "%02x", d[i]);
}
out[SHA256_DIGEST_LENGTH * 2] = '\0';
return out;
}

369
src/lib/tc_module.c Normal file
View File

@ -0,0 +1,369 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jansson.h>
#include <tc_module.h>
#include <tc_filesystem.h>
#define FILENAME_SIZE 64
// Local data structure that contains the handle of the module and the module data
typedef struct {
tc_module_t *module;
void *lib_handle;
char filename[FILENAME_SIZE];
} mod_handle_pair;
static mod_handle_pair mod_handle_pairs[TC_MAX_LOADED_MODULES];
static uint32_t mod_handle_pairs_len = 0;
// Local function for getting the path of the module. Requires freeing.
static char *module_filename(const char *category, const char *mod_name);
// Local function for adding a mod_handle_pair to the array
static int8_t add_mod_handle_pair(mod_handle_pair pair);
// Local function for closing a library matching mod
static void close_lib_by_module(const tc_module_t *mod);
static tc_module_t *is_module_opened(const char *filename) {
for (uint32_t i = 0; i < mod_handle_pairs_len; i++) {
if (strcmp(filename, mod_handle_pairs[i].filename) == 0) {
return mod_handle_pairs[i].module;
}
}
return NULL;
}
// Try to open module by filename. dlopen's a module if returns non-null.
tc_module_t *module_open(const char *filename) {
char mod_path[128];
snprintf(mod_path, 128, "%s/%s", TC_MODULE_PATH, filename);
void *handle = tc_dlopen(mod_path);
if (!handle) {
return NULL;
}
// Call the function to get the module handle
tc_module_t *(*mod_info_func)() = tc_dlsym(handle, TC_MODULE_INFO_FUNCTION_NAME);
tc_module_t *mod;
if (!(mod = mod_info_func())) {
tc_dlclose(handle);
return NULL;
}
// Loading was successful, save the data
mod_handle_pair pair = {
.module = mod,
.lib_handle = handle
};
snprintf(pair.filename, FILENAME_SIZE, "%s", filename);
if (add_mod_handle_pair(pair) != TC_SUCCESS) {
// Not enough space to add to module list
tc_dlclose(handle);
return NULL;
}
printf("successful open of %s\n", mod_path);
return mod;
}
// Get list of file names implementing category matching 'path'. Multiple levels is currently not used
static char **mod_filenames_from_json(const char *path, uint16_t *file_count) {
json_t *root = json_load_file(TC_MODULE_DATABASE_PATH, JSON_DECODE_ANY, NULL);
if (!root) {
return NULL;
}
// All values except root should be arrays
json_t *category_arr;
if (!(category_arr = json_object_get(root, path))) {
return NULL;
}
const char *strings[64];
uint16_t i = 0;
json_t *value;
size_t s;
json_array_foreach(category_arr, s, value) {
strings[i] = json_string_value(value);
i++;
}
*file_count = i;
return tc_str_arr_dup(i, strings);
}
// Create module json database
static void maybe_create_module_database() {
/*if (tc_fs_file_exists(TC_MODULE_DATABASE_PATH)) {
return;
}*/
// Try to open all files in the database
uint16_t count = 0;
char **file_names = tc_fs_dir_filenames(TC_MODULE_PATH, &count);
if (!file_names) {
return;
}
// Json document root
json_t *root = json_object();
json_t *readables = json_array();
json_t *assignables = json_array();
char abs_path[128];
void *handle = NULL;
tc_module_t *(*mod_func)() = NULL;
tc_module_t *mod = NULL;
for (uint16_t i = 0; i < count; i++) {
snprintf(abs_path, 128, "%s/%s", TC_MODULE_PATH, file_names[i]);
//puts(abs_path);
// Try to open the module
if (!(handle = tc_dlopen(abs_path))) {
continue;
}
if (!(mod_func = tc_dlsym(handle, TC_MODULE_INFO_FUNCTION_NAME))) {
tc_dlclose(handle);
continue;
}
if (!(mod = mod_func())) {
tc_dlclose(handle);
continue;
}
// Check what categories are implemented
if (mod->category_info.category_mask & TC_READABLE) {
json_array_append_new(readables, json_string(file_names[i]));
}
if (mod->category_info.category_mask & TC_ASSIGNABLE) {
json_array_append_new(assignables, json_string(file_names[i]));
}
tc_dlclose(handle);
}
json_object_set(root, "TC_READABLE", readables);
json_object_set(root, "TC_ASSIGNABLE", assignables);
json_dump_file(root, TC_MODULE_DATABASE_PATH, JSON_INDENT(4));
json_decref(readables);
json_decref(root);
tc_str_arr_free(count, file_names);
}
static int8_t add_mod_handle_pair(mod_handle_pair pair) {
mod_handle_pairs_len++;
if (mod_handle_pairs_len > TC_MAX_LOADED_MODULES) {
return TC_ENOMEM;
}
mod_handle_pairs[mod_handle_pairs_len - 1] = pair;
return TC_SUCCESS;
}
static void close_lib_by_module(const tc_module_t* mod) {
// Copy entries not being deleted to a new array
mod_handle_pair new_pairs[TC_MAX_LOADED_MODULES];
uint32_t saved = 0;
for (uint32_t i = 0; i < mod_handle_pairs_len; i++) {
if (mod_handle_pairs[i].module != mod) {
saved++;
new_pairs[saved] = mod_handle_pairs[i];
}
else {
// Close the library
tc_dlclose(mod_handle_pairs[i].lib_handle);
}
}
mod_handle_pairs_len = saved;
for (uint32_t i = 0; i < saved; i++) {
mod_handle_pairs[i] = new_pairs[i];
}
}
static char *module_filename(const char *category, const char *mod_name) {
// TC_MODULE_PATH should be defined as an absolute path
// Check if file by the exact name exists
char path[128];
snprintf(path, 128, "%s/%s/%s", TC_MODULE_PATH, category, mod_name);
if (tc_fs_file_exists(path)) {
return strdup(path);
}
// Form a filename of the form lib<mod_name>.so
// Posix
char affixed_path[128];
snprintf(affixed_path, 128, "%s/%s/lib%s.so", TC_MODULE_PATH, category, mod_name);
return strdup(affixed_path);
}
tc_module_t *tc_module_find(enum tc_module_category category, const char *name) {
maybe_create_module_database();
char *mod_abs_path = NULL;
switch (category) {
case TC_CATEGORY_ASSIGNABLE:
mod_abs_path = module_filename("assignable", name);
break;
case TC_CATEGORY_INTERFACE:
mod_abs_path = module_filename("interface", name);
break;
case TC_CATEGORY_READABLE:
mod_abs_path = module_filename("readable", name);
break;
default:
return NULL;
}
void *handle = tc_dlopen(mod_abs_path);
if (handle == NULL) {
free(mod_abs_path);
return NULL;
}
free(mod_abs_path);
// Call the function to get the module handle
tc_module_t *(*mod_info_func)() = tc_dlsym(handle, TC_MODULE_INFO_FUNCTION_NAME);
tc_module_t *mod;
if ((mod = mod_info_func()) == NULL) {
tc_dlclose(handle);
return NULL;
}
// Loading was successful, save the data
mod_handle_pair pair = {
.module = mod,
.lib_handle = handle
};
if (add_mod_handle_pair(pair) != TC_SUCCESS) {
// Not enough space to add to module list
tc_dlclose(handle);
return NULL;
}
return mod;
}
tc_module_t **tc_module_find_all_from_category(enum tc_module_category category, uint16_t *count) {
maybe_create_module_database();
// Get the file name list of the category modules
/*uint16_t file_count = 0;
char mod_dir_name[512];
switch (category) {
case TC_CATEGORY_ASSIGNABLE:
snprintf(mod_dir_name, 512, "%s/%s", TC_MODULE_PATH, "assignable");
break;
case TC_CATEGORY_READABLE:
snprintf(mod_dir_name, 512, "%s/%s", TC_MODULE_PATH, "readable");
break;
default:
return NULL;
}
// FIXME: file_names members are sometimes corrupted
char **file_names = tc_fs_dir_filenames(mod_dir_name, &file_count);
if (file_names == NULL) {
return NULL;
}*/
uint16_t file_count;
char **filenames;
switch (category) {
case TC_CATEGORY_READABLE:
filenames = mod_filenames_from_json("TC_READABLE", &file_count);
break;
case TC_CATEGORY_ASSIGNABLE:
filenames = mod_filenames_from_json("TC_ASSIGNABLE", &file_count);
break;
default:
return NULL;
}
// Open all modules using the file names
tc_module_t *mod_list[TC_MAX_LOADED_MODULES];
uint16_t mod_count = 0;
tc_module_t *mod;
for (uint16_t i = 0; i < file_count; i++) {
// Check if a module by this filename is already opened
if ((mod = is_module_opened(filenames[i]))) {
mod_list[mod_count] = mod;
mod_count++;
continue;
}
// Try to get the module by this file name
if (!(mod = module_open(filenames[i]))) {
continue;
}
mod_list[mod_count] = mod;
mod_count++;
}
tc_str_arr_free(file_count, filenames);
// Allocate pointer array on the heap
tc_module_t **retval = calloc(mod_count, sizeof(tc_module_t*));
for (uint16_t i = 0; i < mod_count; i++) {
retval[i] = mod_list[i];
}
*count = mod_count;
return retval;
}
void tc_module_close(tc_module_t* module) {
// Free internal state of module
if (module->close_callback != NULL) {
if (module->close_callback() != TC_SUCCESS) {
// Add some sort of log message here
// It's probably good to abort here
//abort();
}
}
//TODO : add reference count check
// Free the library matching the module
close_lib_by_module(module);
}
tc_module_category_info_t tc_module_category_info_create(uint64_t mask, uint16_t num_categories, const tc_module_category_data_t *categories) {
tc_module_category_info_t retval = {
.category_mask = mask,
.num_categories = num_categories
};
if (num_categories > 0 && categories) {
for (uint16_t i = 0; i < num_categories; i++) {
retval.category_data_list[i] = categories[i];
}
}
return retval;
}
tc_module_category_data_t tc_module_category_data_create(uint64_t category, union module_data_callback_t u) {
tc_module_category_data_t retval = {
.category = category
};
switch (category) {
case TC_READABLE:
retval.readable_data = u.readable_data;
return retval;
case TC_ASSIGNABLE:
retval.assignable_data = u.assignable_data;
return retval;
default:
break;
}
return retval;
}

82
src/lib/tc_readable.c Normal file
View File

@ -0,0 +1,82 @@
#include <tc_readable.h>
#include <stdlib.h>
// Local function for postorder traversal
static void destroy_node(tc_readable_node_t *node) {
free(node->name);
free(node->unit);
free(node->children_nodes);
free(node);
}
static void postorder_traverse(tc_readable_node_t *node, void (*func)(tc_readable_node_t*)) {
for (uint16_t i = 0; i < node->children_count; i++) {
postorder_traverse(node->children_nodes[i], func);
}
func(node);
}
tc_readable_node_t *tc_readable_node_new() {
tc_readable_node_t *node = calloc(1, sizeof(tc_readable_node_t));
return node;
}
void tc_readable_node_destroy(tc_readable_node_t *node) {
postorder_traverse(node, &destroy_node);
}
int8_t tc_readable_node_add_child(tc_readable_node_t *parent, tc_readable_node_t *child) {
parent->children_count++;
if ((parent->children_nodes = realloc(parent->children_nodes, parent->children_count * sizeof(parent))) == NULL) {
return TC_ENOMEM;
}
parent->children_nodes[parent->children_count - 1] = child;
return TC_SUCCESS;
}
tc_readable_node_t *tc_readable_node_add_new_child(tc_readable_node_t *parent) {
tc_readable_node_t *node = tc_readable_node_new();
if (!node) {
return NULL;
}
if (tc_readable_node_add_child(parent, node) != TC_SUCCESS) {
tc_readable_node_destroy(node);
return NULL;
}
return node;
}
void tc_readable_node_set_data(tc_readable_node_t *node, const char *name, const char *unit) {
node->name = name;
node->unit = unit;
}
tc_readable_result_t tc_readable_result_create(enum tc_data_types type, void *data, bool valid) {
tc_readable_result_t res;
res.valid = valid;
res.data.data_type = type;
if (!data || !valid) {
return res;
}
switch (type) {
case TC_TYPE_UINT:
res.data.uint_value = *(uint64_t*) data;
break;
case TC_TYPE_INT:
res.data.int_value = *(int64_t*) data;
break;
case TC_TYPE_DOUBLE:
res.data.double_value = *(double*) data;
break;
default:
res.valid = false;
break;
}
return res;
}

5
src/main/meson.build Normal file
View File

@ -0,0 +1,5 @@
posix_bin = executable('tuxclocker',
'tuxclocker.c',
include_directories : incdir,
link_with : libtuxclocker,
install : true)

20
src/main/tuxclocker.c Normal file
View File

@ -0,0 +1,20 @@
// Main executable for TuxClocker. Responsible for loading an interface
#include <tc_common.h>
#include <tc_module.h>
#include <stdio.h>
int main(int argc, char **argv) {
// Load an interface here
/*tc_module_t *mod = tc_module_find(TC_CATEGORY_INTERFACE, "qt");
if (mod != NULL) {
printf("successful load for %s\n", mod->name);
mod->init_callback(argc, argv);
tc_module_close(mod);
}*/
return 0;
}

59
src/meson.build Normal file
View File

@ -0,0 +1,59 @@
# Define libtuxclocker target here since others depend on it
# OpenSSL is used for hash functions
openssl_dep = dependency('openssl')
# For posix only
dl = cppc.find_library('dl')
boost_dep = dependency('boost',
modules : ['system',
'filesystem'])
libtuxclocker_posix_sources = ['lib/posix/module.c',
'lib/posix/filesystem.c']
libtuxclocker_posix_libs = [cc.find_library('dl')]
# Compile time definition for module path
plugin_path_def_template = '-DTC_PLUGIN_PATH="@0@/@1@/tuxclocker/plugins"'
plugin_path_def = plugin_path_def_template.format(get_option('prefix'), get_option('libdir'))
#libtuxclocker = shared_library('libtuxclocker',
# ['lib/tc_assignable.c',
# 'lib/tc_module.c',
# 'lib/tc_readable.c',
# 'lib/tc_common.c',
# libtuxclocker_posix_sources],
# include_directories : incdir,
# dependencies : [libtuxclocker_posix_libs, openssl_dep, jansson_dep],
# c_args : module_path_def,
# install : true)
libtuxclocker = shared_library('tuxclocker',
['lib/Crypto.cpp',
'lib/Plugin.cpp'],
override_options : ['cpp_std=c++17'],
include_directories : incdir,
dependencies : [boost_dep, dl, openssl_dep],
cpp_args : plugin_path_def,
install : true)
if get_option('plugins')
subdir('plugins')
endif
subdir('main')
if get_option('library')
subdir('lib')
endif
if get_option('daemon')
subdir('tuxclockerd')
endif
if get_option('gui')
subdir('tuxclocker-qt')
endif

View File

@ -0,0 +1,24 @@
libnvml = cc.find_library('nvidia-ml', required : false)
libx = cc.find_library('X11', required : false)
libxnvctrl = cc.find_library('XNVCtrl', required : false)
nvidia_linux_libs = [libnvml, libx, libxnvctrl]
all_nvidia_linux_libs = true
foreach lib : nvidia_linux_libs
if not lib.found()
all_nvidia_linux_libs = false
break
endif
endforeach
if all_nvidia_linux_libs
shared_library('nvidia', 'nvidia_linux.c',
include_directories : incdir,
dependencies : nvidia_linux_libs,
link_with : libtuxclocker,
install_dir : get_option('libdir') / 'tuxclocker' / 'modules' / 'assignable',
install : true)
endif

View File

@ -0,0 +1,568 @@
//#include "/opt/cuda/include/nvml.h"
#include <nvml.h>
#include <X11/Xlib.h>
#include <NVCtrl/NVCtrlLib.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <tc_assignable.h>
#include <tc_common.h>
#include <tc_module.h>
#define MAX_GPUS 32
enum node_type {
NODE_BASE,
NODE_CLOCK,
NODE_FAN
};
typedef struct {
tc_assignable_node_t node;
struct base_data {
nvmlDevice_t dev;
uint8_t device_index;
} base_data;
enum node_type type;
union {
struct clock_info {
uint32_t max_perf_state, clock_type;
} clock_info;
uint8_t fan_index;
};
} callback_data;
static callback_data *callback_data_new() {
tc_assignable_node_t node;
// Zero the node structure
memset(&node, 0, sizeof(node));
callback_data *cd = calloc(1, sizeof(callback_data));
cd->node = node;
return cd;
}
static void callback_data_destroy(callback_data *data) {
free(data);
}
// Function for module loader to get the module structure
tc_module_t *TC_MODULE_INFO_FUNCTION();
// Local function declarations
static int8_t init();
static int8_t close();
static tc_assignable_node_t *category_callback();
tc_assignable_module_data_t cat_data_cb();
static int8_t generate_assignable_tree();
// Functions for creating fan speed and mode nodes
static void add_fanmode_item(tc_assignable_node_t *node, uint32_t dev_index);
static void add_fanspeed_item(tc_assignable_node_t *node, uint32_t dev_index, uint8_t fan_index);
// Functions for adding nodes to the GPU
void add_power_limit_item(tc_assignable_node_t *parent, nvmlDevice_t dev);
void add_fan_items(tc_assignable_node_t *parent, int32_t index);
void add_clock_items(tc_assignable_node_t *parent, int32_t index, nvmlDevice_t dev);
// Assignment functions
int8_t assign_power_limit(tc_variant_t value, const tc_assignable_node_t *node);
int8_t assign_fan_mode(tc_variant_t value, const tc_assignable_node_t *node);
int8_t assign_fan_speed(tc_variant_t value, const tc_assignable_node_t *node);
int8_t assign_clock(tc_variant_t value, const tc_assignable_node_t *node);
static uint32_t gpu_count;
static nvmlDevice_t nvml_handles[MAX_GPUS];
static Display *dpy;
static tc_assignable_node_t *root_node;
static callback_data *root_callback_data;
static tc_assignable_module_data_t mod_data;
tc_module_t mod_info = {
.category = TC_CATEGORY_ASSIGNABLE,
.name = "nvidia",
.description = "Nvidia assignables",
.init_callback = &init,
.close_callback = &close,
.category_data_callback = (void *(*)()) &category_callback
};
tc_module_t *TC_MODULE_INFO_FUNCTION() {
tc_module_category_data_t cat_data = {
.category = TC_ASSIGNABLE,
.assignable_data = &cat_data_cb
};
tc_module_category_info_t cat_info = {
.category_mask = TC_ASSIGNABLE,
.num_categories = 1
};
cat_info.category_data_list[0] = cat_data;
mod_info.category_info = cat_info;
return &mod_info;
}
static tc_assignable_node_t *category_callback() {
return root_node;
}
tc_assignable_module_data_t cat_data_cb() {
return mod_data;
}
static int8_t init() {
// Initialize library
if (nvmlInit_v2() != NVML_SUCCESS) {
return TC_EGENERIC;
}
// Query GPU count
if (nvmlDeviceGetCount(&gpu_count) != NVML_SUCCESS) {
return TC_EGENERIC;
}
if (gpu_count > MAX_GPUS) {
return TC_ENOMEM;
}
// Get nvml handles
uint32_t valid_count = 0;
for (uint8_t i = 0; i < gpu_count; i++) {
nvmlDevice_t dev;
if (nvmlDeviceGetHandleByIndex_v2(i, &dev) == NVML_SUCCESS) {
nvml_handles[valid_count] = dev;
valid_count++;
}
}
gpu_count = valid_count;
// Get X11 display
if ((dpy = XOpenDisplay(NULL)) == NULL) {
return TC_EGENERIC;
}
// Generate the tree structure of assignables for every GPU. Free in close().
generate_assignable_tree();
return TC_SUCCESS;
}
static int8_t close() {
}
static int8_t generate_assignable_tree() {
// Allocate memory for root node
root_callback_data = callback_data_new();
root_node = &(root_callback_data->node);
root_node->value_category = TC_ASSIGNABLE_NONE;
// Assign module data
mod_data.root_node = root_node;
for (uint32_t i = 0; i < gpu_count; i++) {
// Get GPU name and use it as the root item for GPU
char gpu_name[NVML_DEVICE_NAME_BUFFER_SIZE];
if (nvmlDeviceGetName(nvml_handles[i], gpu_name, NVML_DEVICE_NAME_BUFFER_SIZE) != NVML_SUCCESS) {
continue;
}
// Got the name, append the item to the root item
tc_assignable_node_t *gpu_name_node = tc_assignable_node_new();
gpu_name_node->name = strdup(gpu_name);
// Append to the root node
if (tc_assignable_node_add_child(root_node, gpu_name_node) != TC_SUCCESS) {
// Couldn't allocate memory, destroy the node
tc_assignable_node_destroy(gpu_name_node);
continue;
}
// Try to add tunables that don't have children first
add_power_limit_item(gpu_name_node, nvml_handles[i]);
add_fan_items(gpu_name_node, i);
add_clock_items(gpu_name_node, i, nvml_handles[i]);
}
return TC_SUCCESS;
}
static void add_fanmode_item(tc_assignable_node_t *parent, uint32_t dev_index) {
callback_data *fanmode_data = callback_data_new();
fanmode_data->base_data.device_index = dev_index;
tc_assignable_node_t *fanmode_node = &(fanmode_data->node);
if (tc_assignable_node_add_child(parent, fanmode_node) != TC_SUCCESS) {
callback_data_destroy(fanmode_data);
return;
}
// Create the string array of the options
char *opts[64] = {"auto", "manual"};
char **list = tc_str_arr_dup(2, opts);
tc_assignable_enum_t enum_info = {
2,
list
};
fanmode_node->value_category = TC_ASSIGNABLE_ENUM;
fanmode_node->enum_info = enum_info;
tc_assignable_node_set_data(fanmode_node, NULL, "Fan Mode", &assign_fan_mode);
}
static void add_fanspeed_item(tc_assignable_node_t *parent, uint32_t dev_index, uint8_t fan_index) {
// Fan speed node
callback_data *fanspeed_data = callback_data_new();
fanspeed_data->base_data.device_index = dev_index;
fanspeed_data->type = NODE_FAN;
fanspeed_data->fan_index = fan_index;
tc_assignable_node_t *fanspeed_node = &(fanspeed_data->node);
if (tc_assignable_node_add_child(parent, fanspeed_node) != TC_SUCCESS) {
callback_data_destroy(fanspeed_data);
return;
}
tc_assignable_range_int_t speed_range = {
.min = 0,
.max = 100
};
tc_assignable_range_t range = {
.range_data_type = TC_ASSIGNABLE_RANGE_INT,
.int_range = speed_range
};
fanspeed_node->value_category = TC_ASSIGNABLE_RANGE;
fanspeed_node->range_info = range;
tc_assignable_node_set_data(fanspeed_node, "%", "Fan Speed", &assign_fan_speed);
}
void add_power_limit_item(tc_assignable_node_t *parent, nvmlDevice_t dev) {
uint32_t min, max;
if (nvmlDeviceGetPowerManagementLimitConstraints(dev, &min, &max) != NVML_SUCCESS) {
return;
}
// Create a new node
callback_data *power_data = callback_data_new();
power_data->base_data.dev = dev;
tc_assignable_node_t *power_node = &(power_data->node);
if (power_node == NULL) {
return;
}
// Assign the parent
if (tc_assignable_node_add_child(parent, power_node) != TC_SUCCESS) {
tc_assignable_node_destroy(power_node);
return;
}
// Create the assignable range
tc_assignable_range_double_t double_range = {
.min = (min / 1000),
.max = (max / 1000)
};
tc_assignable_range_t range = {
.range_data_type = TC_ASSIGNABLE_RANGE_DOUBLE,
.double_range = double_range
};
power_node->value_category = TC_ASSIGNABLE_RANGE;
power_node->range_info = range;
power_node->name = "Power Limit";
power_node->assign_callback = &assign_power_limit;
tc_variant_t v = {
.data_type = TC_TYPE_DOUBLE,
.double_value = 3.14f
};
//assign_power_limit(v, power_node);
}
void add_fan_items(tc_assignable_node_t* parent, int32_t index) {
// Query fan count for GPU
unsigned char *data;
int32_t data_len;
if (!XNVCTRLQueryTargetBinaryData(dpy,
NV_CTRL_TARGET_TYPE_GPU,
0,
index,
NV_CTRL_BINARY_DATA_COOLERS_USED_BY_GPU,
&data,
&data_len)) {
return;
}
int gpu_fan_count = (int) *data;
// Check if manual fan mode is available
NVCTRLAttributeValidValuesRec values;
if (!XNVCTRLQueryValidTargetAttributeValues(dpy,
NV_CTRL_TARGET_TYPE_GPU,
index,
0,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL,
&values)) {
return;
}
if (!(values.permissions & ATTRIBUTE_TYPE_WRITE)) {
// Fan mode nor speed will be writable
return;
}
if (gpu_fan_count == 1) {
// Only add direct children
add_fanmode_item(parent, index);
add_fanspeed_item(parent, index, 0);
return;
}
else if (gpu_fan_count < 1) {
// Multiple controllable fans
// Add a node as a parent for fan mode
tc_assignable_node_t *fan_parent = tc_assignable_node_new();
tc_assignable_node_set_data(fan_parent, NULL, "Fans", NULL);
if (tc_assignable_node_add_child(parent, fan_parent) != TC_SUCCESS) {
tc_assignable_node_destroy(fan_parent);
return;
}
// Add the fan mode node (should control the mode of both fans)
add_fanmode_item(fan_parent, index);
for (int i = 0; i < gpu_fan_count; i++) {
tc_assignable_node_t *fanspeed_parent = tc_assignable_node_new();
char n_str[16];
snprintf(n_str, 16, "%d", i);
tc_assignable_node_set_data(fanspeed_parent, NULL, strdup(n_str), NULL);
if (tc_assignable_node_add_child(fan_parent, fanspeed_parent) != TC_SUCCESS) {
tc_assignable_node_destroy(fanspeed_parent);
return;
}
add_fanspeed_item(fanspeed_parent, index, (uint8_t) i);
}
}
}
void add_clock_items(tc_assignable_node_t *parent, int32_t index, nvmlDevice_t dev) {
// Get the amount of performance states
uint32_t mem_clocks[16];
uint32_t len = 16;
if (nvmlDeviceGetSupportedMemoryClocks(dev, &len, mem_clocks) != NVML_SUCCESS) {
return;
}
NVCTRLAttributeValidValuesRec values;
// Query writability of memory clock (transfer rate / 2)
do {
if (XNVCTRLQueryValidTargetAttributeValues(dpy,
NV_CTRL_TARGET_TYPE_GPU,
index,
len - 1,
NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET,
&values)) {
if (values.permissions & ATTRIBUTE_TYPE_WRITE) {
// Is writable
callback_data *memclock_data = callback_data_new();
memclock_data->base_data.device_index = index;
memclock_data->type = NODE_CLOCK;
memclock_data->clock_info.max_perf_state = len - 1;
memclock_data->clock_info.clock_type = NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET;
tc_assignable_node_t *memclock_node = &(memclock_data->node);
if (tc_assignable_node_add_child(parent, memclock_node) != TC_SUCCESS) {
tc_assignable_node_destroy(memclock_node);
break;
}
tc_assignable_range_int_t clock_range = {
.min = values.u.range.min / 2,
.max = values.u.range.max / 2
};
tc_assignable_range_t range = {
.range_data_type = TC_ASSIGNABLE_RANGE_INT,
.int_range = clock_range
};
memclock_node->value_category = TC_ASSIGNABLE_RANGE;
memclock_node->range_info = range;
tc_assignable_node_set_data(memclock_node, "MHz", "Memory Clock Offset", &assign_clock);
}
}
} while (0);
if (XNVCTRLQueryValidTargetAttributeValues(dpy,
NV_CTRL_TARGET_TYPE_GPU,
index,
len - 1,
NV_CTRL_GPU_NVCLOCK_OFFSET,
&values)) {
if (values.permissions & ATTRIBUTE_TYPE_WRITE) {
callback_data *coreclock_data = callback_data_new();
coreclock_data->base_data.device_index = index;
coreclock_data->type = NODE_CLOCK;
coreclock_data->clock_info.max_perf_state = len - 1;
coreclock_data->clock_info.clock_type = NV_CTRL_GPU_NVCLOCK_OFFSET;
tc_assignable_node_t *coreclock_node = &(coreclock_data->node);
if (tc_assignable_node_add_child(parent, coreclock_node) != TC_SUCCESS) {
tc_assignable_node_destroy(coreclock_node);
return;
}
tc_assignable_range_int_t clock_range = {
.min = values.u.range.min,
.max = values.u.range.max
};
tc_assignable_range_t range = {
.range_data_type = TC_ASSIGNABLE_RANGE_INT,
.int_range = clock_range
};
coreclock_node->value_category = TC_ASSIGNABLE_RANGE;
coreclock_node->range_info = range;
tc_assignable_node_set_data(coreclock_node, "MHz", "Core Clock Offset", &assign_clock);
}
}
}
int8_t assign_power_limit(tc_variant_t value, const tc_assignable_node_t *node) {
const callback_data *data = (const callback_data*) node;
if (value.data_type != TC_TYPE_DOUBLE) {
return TC_EINVAL;
}
// Watts -> milliwatts
uint32_t in_arg = (uint32_t) (value.double_value * 1000);
nvmlReturn_t retval = nvmlDeviceSetPowerManagementLimit(data->base_data.dev, in_arg);
switch (retval) {
case NVML_SUCCESS:
return TC_SUCCESS;
case NVML_ERROR_NO_PERMISSION:
return TC_ENOPERM;
case NVML_ERROR_INVALID_ARGUMENT:
return TC_EINVAL;
default:
return TC_EGENERIC;
}
return TC_EGENERIC;
}
int8_t assign_fan_mode(tc_variant_t value, const tc_assignable_node_t *node) {
if (!node || value.data_type != TC_TYPE_UINT || (value.uint_value > node->enum_info.property_count + 1)) {
return TC_EINVAL;
}
// TODO : add a mapping in the node for NVCtrl enumerations
const callback_data *data = (const callback_data*) node;
switch (value.uint_value) {
case 0:
if (!XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_GPU,
data->base_data.device_index,
0,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL_FALSE)) {
return TC_EINVAL;
}
return TC_SUCCESS;
case 1:
if (!XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_GPU,
data->base_data.device_index,
0,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL_TRUE)) {
return TC_EINVAL;
}
return TC_SUCCESS;
default:
return TC_EINVAL;
}
return TC_EGENERIC;
}
int8_t assign_fan_speed(tc_variant_t value, const tc_assignable_node_t *node) {
if (!node || value.data_type != TC_TYPE_INT) {
return TC_EINVAL;
}
const callback_data *data = (const callback_data*) node;
if (!XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_COOLER,
data->base_data.device_index,
0,
NV_CTRL_THERMAL_COOLER_LEVEL,
value.uint_value)) {
// Return TC_EINVALPREREQ if fan mode is automatic
int value;
if (!XNVCTRLQueryTargetAttribute(dpy,
NV_CTRL_TARGET_TYPE_GPU,
data->base_data.device_index,
0,
NV_CTRL_GPU_COOLER_MANUAL_CONTROL,
&value)) {
return TC_EINVAL;
}
if (!(value & NV_CTRL_GPU_COOLER_MANUAL_CONTROL)) {
return TC_EINVALPREREQ;
}
return TC_EINVAL;
}
return TC_SUCCESS;
}
int8_t assign_clock(tc_variant_t value, const tc_assignable_node_t *node) {
if (!node || value.data_type != TC_TYPE_INT) {
return TC_EINVAL;
}
const callback_data *data = (const callback_data*) node;
if (data->type != NODE_CLOCK) {
return TC_EINVAL;
}
// Memory clock -> transfer rate
int arg = (data->clock_info.clock_type == NV_CTRL_GPU_MEM_TRANSFER_RATE_OFFSET) ? value.int_value * 2 : value.int_value;
// Nvidia driver might crash on an invalid argument so check it here (good job novideo)
if (arg > node->range_info.int_range.max || arg < node->range_info.int_range.min) {
return TC_EINVAL;
}
if (!XNVCTRLSetTargetAttributeAndGetStatus(dpy,
NV_CTRL_TARGET_TYPE_GPU,
data->base_data.device_index,
data->clock_info.max_perf_state,
data->clock_info.clock_type,
arg)) {
return TC_EINVAL;
}
return TC_SUCCESS;
}

View File

@ -0,0 +1 @@
subdir('qt')

View File

@ -0,0 +1,77 @@
#include "MainWindow.h"
#include <AssignableWidget.h>
#include <QLabel>
#include <QIcon>
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
m_mainWidget = new QWidget;
m_mainLayout = new QGridLayout;
// Toolbar can't be dragged if it's not a direct child of MainWindow
m_mainToolBar = new QToolBar;
m_mainToolBar->setMovable(true);
m_mainToolBar->setOrientation(Qt::Horizontal);
m_mainToolBar->setFloatable(true);
m_mainLayout->addWidget(m_mainToolBar);
m_mainStackedWidget = new QStackedWidget;
m_assignableWidget = new AssignableWidget;
m_mainStackedWidget->addWidget(m_assignableWidget);
m_readableWidget = new ReadableWidget;
m_mainStackedWidget->addWidget(m_readableWidget);
m_settingWidget = new QWidget;
m_mainStackedWidget->addWidget(m_settingWidget);
m_mainLayout->addWidget(m_mainStackedWidget);
// Add toolbar buttons
// Connect tool buttons to changing the active stacked widget
QAction *activateEditor = new QAction;
setupWidgetTriggerAction(activateEditor, m_assignableWidget, "edit-entry");
QAction *activateSettings = new QAction;
setupWidgetTriggerAction(activateSettings, m_settingWidget, "configure");
QAction *activateViewer = new QAction;
setupWidgetTriggerAction(activateViewer, m_readableWidget, "document-properties");
// Set editor as default widget
changeActiveWidget(m_assignableWidget, activateEditor);
m_mainWidget->setLayout(m_mainLayout);
setCentralWidget(m_mainWidget);
}
MainWindow::~MainWindow() {
}
void MainWindow::setupWidgetTriggerAction(QAction *action, QWidget *widget, const QString &iconName) {
// Add action to triggers
m_widgetSwitchTriggers.append(action);
action->setCheckable(true);
action->setIcon(QIcon::fromTheme(iconName));
m_mainToolBar->addAction(action);
// Connect action and widget switch
connect(action, &QAction::triggered, [=]() {
changeActiveWidget(widget, action);
});
}
void MainWindow::changeActiveWidget(QWidget *widget, QAction *action) {
action->setChecked(true);
for (QAction *i_action : m_widgetSwitchTriggers) {
if (i_action != action) {
// Uncheck other switch actions
i_action->setChecked(false);
}
}
m_mainStackedWidget->setCurrentWidget(widget);
}

View File

@ -0,0 +1,42 @@
#pragma once
#include <AssignableWidget.h>
#include <ReadableWidget.h>
#include <QMainWindow>
#include <QLayout>
#include <QToolBar>
#include <QToolButton>
#include <QStackedWidget>
#include <QMap>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QGridLayout *m_mainLayout;
QStatusBar *m_mainStatusBar;
QWidget *m_mainWidget;
QToolBar *m_mainToolBar;
// Stacked widgets for the main view
QStackedWidget *m_mainStackedWidget;
AssignableWidget *m_assignableWidget;
ReadableWidget *m_readableWidget;
QWidget *m_settingWidget;
// List of widget switch triggers so we know which ones to uncheck
QVector <QAction*> m_widgetSwitchTriggers;
// Setup connection between action and widget
void setupWidgetTriggerAction(QAction *action, QWidget *widget, const QString &iconName);
// Change current stacked widget according to the action that was triggered
void changeActiveWidget(QWidget *widget, QAction *action);
};

View File

@ -0,0 +1,32 @@
#include "AssignableData.h"
AssignableData::AssignableData(const tc_assignable_node_t *node) {
// Copy the values used by the editor
m_valueCategory = node->value_category;
switch (m_valueCategory) {
case TC_ASSIGNABLE_ENUM:
m_enumInfo = node->enum_info;
break;
case TC_ASSIGNABLE_RANGE:
m_rangeInfo = node->range_info;
break;
default:
break;
}
name = QString(node->name);
unit = QString(node->unit);
}
AssignableData::AssignableData() {
}
AssignableData::~AssignableData() {
}
QVariant AssignableData::value() {
return m_currentValue;
}
void AssignableData::setValue(const QVariant &value) {
m_currentValue = value;
}

View File

@ -0,0 +1,46 @@
#pragma once
#include <QMetaType>
#include <QString>
#include <QVariant>
#include <tc_assignable.h>
// Contains the information for editing data for each node whose value can be modified.
class AssignableData {
public:
AssignableData();
// Create the data from an assignable node
AssignableData(const tc_assignable_node_t* node);
~AssignableData();
// Return type for getting the value category and assignable type
struct AssignableTypeInfo {
tc_assignable_value_category m_valueCategory;
union {
tc_assignable_enum_t m_enumInfo;
tc_assignable_range_t m_rangeInfo;
};
};
// Returns the currently set value
QVariant value();
void setValue(const QVariant &value);
//private:
QString name;
QString unit;
const int8_t (*m_assignCallback)();
tc_assignable_value_category m_valueCategory;
union {
tc_assignable_enum_t m_enumInfo;
tc_assignable_range_t m_rangeInfo;
};
private:
// Stores the latest value
QVariant m_currentValue;
};
// Declare as metatype for this to be used as a QVariant in QStandardItem
Q_DECLARE_METATYPE(AssignableData)

View File

@ -0,0 +1,176 @@
#include "AbstractExpandableItemEditor.h"
#include "AssignableEditorDelegate.h"
#include "AssignableData.h"
#include "AssignableParametrizationData.h"
#include <AbstractAssignableEditor.h>
#include <AssignableParametrizationEditor.h>
#include <EnumEditor.h>
#include <IntRangeEditor.h>
#include <QDebug>
#include <QTreeView>
AssignableEditorDelegate::AssignableEditorDelegate(QTreeView *treeView, QObject *parent) : QStyledItemDelegate(parent) {
m_treeView = treeView;
m_spanAllColumns = false;
}
QWidget *AssignableEditorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
m_editorIndex = index;
m_viewItem = option;
m_originalItemSize = index.data(Qt::SizeHintRole).toSize();
QVariant v_data = index.model()->data(index, Qt::UserRole);
// Widget for parametrization data
if (v_data.canConvert<AssignableParametrizationData>()) {
auto a_editor = new AssignableParametrizationEditor(parent);
connect(a_editor, &AbstractExpandableItemEditor::expansionSizeRequested, [=](const QSize size) {
qDebug() << "expansion requested";
setExpansionSize(size);
setSpanAllColumns(true);
});
connect(a_editor, &AbstractExpandableItemEditor::expansionDisableRequested, [=]() {
setSpanAllColumns(false);
setExpansionDisabled(true);
});
return a_editor;
}
// Check which type the editor should be
AssignableData data = qvariant_cast<AssignableData>(index.model()->data(index, Qt::UserRole));
AbstractAssignableEditor *editor = nullptr;
switch (data.m_valueCategory) {
case TC_ASSIGNABLE_RANGE:
switch (data.m_rangeInfo.range_data_type) {
case TC_ASSIGNABLE_RANGE_INT:
editor = new IntRangeEditor(parent);
return editor;
default:
return nullptr;
}
case TC_ASSIGNABLE_ENUM:
editor = new EnumEditor(parent);
return editor;
default:
return nullptr;
}
}
void AssignableEditorDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const {
if (m_spanAllColumns) {
// Span editor across all columns
const int maxCol = index.model()->columnCount(index.parent());
QRect tempRect = m_treeView->visualRect(index.model()->index(index.row(), 0, index.parent()));
int top = tempRect.top();
int left = tempRect.left();
int bottom = tempRect.bottom();
int right= tempRect.right();
for(int i = 1; i < maxCol ; i++){
tempRect = m_treeView->visualRect(index.model()->index(index.row(), i, index.parent()));
if (tempRect.top()<top) {
top = tempRect.top();
}
if (Q_UNLIKELY(tempRect.left() < left)) {
left= tempRect.left();
}
if (tempRect.bottom() > bottom) {
bottom = tempRect.bottom();
}
if (Q_LIKELY(tempRect.right() > right)) {
right= tempRect.right();
}
}
editor->setGeometry(QRect(QPoint(left, top),QPoint(right, bottom)));
}
else {
editor->setGeometry(option.rect);
m_editorWidget = editor;
}
}
void AssignableEditorDelegate::setEditorData(QWidget* editor, const QModelIndex &index) const {
QVariant v_data = index.model()->data(index, Qt::UserRole);
if (v_data.canConvert<AssignableData>()) {
AssignableData data = qvariant_cast<AssignableData>(v_data);
AbstractAssignableEditor *a_editor = static_cast<AbstractAssignableEditor*>(editor);
a_editor->setAssignableData(data);
a_editor->setValue(data.value());
return;
}
if (v_data.canConvert<AssignableParametrizationData>()) {
auto data = qvariant_cast<AssignableParametrizationData>(v_data);
static_cast<AssignableParametrizationEditor*>(editor)->setData(data);
return;
}
}
void AssignableEditorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
QVariant v_data = model->data(index, Qt::UserRole);
if (v_data.canConvert<AssignableData>()) {
AbstractAssignableEditor *a_editor = static_cast<AbstractAssignableEditor*>(editor);
AssignableData data = qvariant_cast<AssignableData>(v_data);
data.setValue(a_editor->value());
QVariant v;
v.setValue(data);
model->setData(index, v, Qt::UserRole);
model->setData(index, a_editor->text(), Qt::DisplayRole);
}
if (v_data.canConvert<AssignableParametrizationData>()) {
auto p_editor = static_cast<AssignableParametrizationEditor*>(editor);
auto data = qvariant_cast<AssignableParametrizationData>(v_data);
QVariant v;
v.setValue(p_editor->data());
model->setData(index, v, Qt::UserRole);
model->setData(index, p_editor->text(), Qt::DisplayRole);
}
// Set original size
model->setData(index, m_originalItemSize, Qt::SizeHintRole);
m_spanAllColumns = false;
}
QSize AssignableEditorDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
return QStyledItemDelegate::sizeHint(option, index);
}
void AssignableEditorDelegate::setExpansionSize(QSize size) const {
qDebug() << "set size hint to" << size;
auto model = const_cast<QAbstractItemModel*>(m_editorIndex.model());
if (!model) {
return;
}
model->setData(m_editorIndex, size, Qt::SizeHintRole);
}
void AssignableEditorDelegate::setSpanAllColumns(bool on) const {
m_spanAllColumns = on;
qDebug() << on;
updateEditorGeometry(m_editorWidget, m_viewItem, m_editorIndex);
}
void AssignableEditorDelegate::setExpansionDisabled(bool off) const {
auto model = const_cast<QAbstractItemModel*>(m_editorIndex.model());
if (!model) {
return;
}
if (off) {
// FIXME : this doesn't work properly if the column is resized during expansion
model->setData(m_editorIndex, m_originalItemSize, Qt::SizeHintRole);
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <QStyledItemDelegate>
#include <QStandardItem>
#include <QTreeView>
class AssignableEditorDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
AssignableEditorDelegate(QTreeView *treeView, QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setSpanAllColumns(bool on) const;
public slots:
void setExpansionSize(QSize size) const;
void setExpansionDisabled(bool off) const;
private:
QSize m_customExpansionSize;
bool m_customExpansionSizeNeeded;
mutable bool m_spanAllColumns;
QTreeView *m_treeView; // Tree view that the delegate belongs to
// Store these for setting column span/row height on demand
mutable QModelIndex m_editorIndex;
mutable QWidget *m_editorWidget;
mutable QStyleOptionViewItem m_viewItem;
mutable QSize m_originalItemSize;
};

View File

@ -0,0 +1,52 @@
#include "AssignableManager.h"
#include <QDebug>
AssignableManager::AssignableManager() {
// Open all assignable modules
uint16_t mod_count;
tc_module_t **assignableModules = tc_module_find_all_from_category(TC_CATEGORY_ASSIGNABLE, &mod_count);
// Try to initialize every module
for (uint16_t i = 0; i < mod_count; i++) {
qDebug() << assignableModules[i];
if (assignableModules[i]->init_callback != NULL) {
if (assignableModules[i]->init_callback() != TC_SUCCESS) {
// Couldn't initialize, close module
tc_module_close(assignableModules[i]);
continue;
}
// Add module to the list
m_assignableModules.append(assignableModules[i]);
}
}
// Obtain root node from each module
for (tc_module_t *module : m_assignableModules) {
tc_assignable_node_t *root = (tc_assignable_node_t*) module->category_data_callback();
if (root == NULL) {
//m_assignableModules.remove(m_assignableModules.indexOf(module));
}
m_assignableRootNodes.append(root);
}
printf("found %d modules\n", mod_count);
qDebug() << m_assignableRootNodes;
for (tc_assignable_node_t *node : m_assignableRootNodes) {
qDebug() << node;
}
qDebug() << m_assignableRootNodes[0]->children_nodes[0]->name;
//delete assignableModules;
}
AssignableManager::~AssignableManager() {
qDebug() << "descructor";
// Close all modules
for (tc_module_t *module : m_assignableModules) {
qDebug() << "closing module at" << module;
tc_module_close(module);
}
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <tc_assignable.h>
#include <tc_module.h>
#include <QVector>
#include <QList>
// Initializes, holds and closes the list of assignable modules
class AssignableManager {
public:
AssignableManager();
~AssignableManager();
tc_assignable_node_t *first() {return m_assignableRootNodes[0];}
// Return a list of root assignable nodes
QList <tc_assignable_node_t*> rootNodes() {return m_assignableRootNodes;}
private:
QVector <tc_module_t*> m_assignableModules;
QList <tc_assignable_node_t*> m_assignableRootNodes;
tc_assignable_node_t **m_rootNodes;
};

View File

@ -0,0 +1,33 @@
#pragma once
#include "AssignableData.h"
#include "ReadableData.h"
#include <QMetaType>
#include <QVector>
// Class for assignable editor to store data about parametrized assignables
class AssignableParametrizationData {
private:
bool m_active; // Should target be currently parametrized
ReadableData m_parameterReadable; // Readable node that the recepient is parametrized from
AssignableData m_controlledAssignable;
std::chrono::milliseconds m_updateInterval;
QVector <QPointF> m_pointsVector;
public:
AssignableParametrizationData() {
m_active = false;
};
AssignableParametrizationData(AssignableData &data) {
m_controlledAssignable = data;
m_active = false;
}
const AssignableData assignableData() {return m_controlledAssignable;}
const QVector <QPointF> pointsVector() {return m_pointsVector;}
void setPointsVector(const QVector <QPointF> vector) {m_pointsVector = vector;}
bool enabled() {return m_active;}
void setEnabled(bool enabled) {m_active = enabled;}
};
Q_DECLARE_METATYPE(AssignableParametrizationData);

View File

@ -0,0 +1,50 @@
#pragma once
#include <QMetaType>
#include <QString>
#include <QDataStream>
#include <tc_readable.h>
class ReadableData {
private:
tc_readable_node_t *m_readableNode;
public:
ReadableData() {m_readableNode = nullptr;}
ReadableData(tc_readable_node_t *node) {m_readableNode = node;}
static QString mimeType() {return QString("tc-readable-data");}
bool isConstant() {return m_readableNode->constant;}
QString name() {return QString(m_readableNode->name);}
QString unit() {return QString(m_readableNode->unit);}
const tc_readable_node_t *node() {return m_readableNode;}
const tc_variant_t constData() {
if (!isConstant()) {
tc_variant_t retval;
retval.data_type = TC_TYPE_NONE;
return retval;
}
return m_readableNode->data;
}
const tc_readable_result_t value() {
if (isConstant() || !m_readableNode->value_callback) {
return tc_readable_result_create(TC_TYPE_NONE, NULL, 0);
}
return m_readableNode->value_callback(m_readableNode);
}
// Streaming operator for converting from QByteArray
friend QDataStream & operator << (QDataStream &out, const ReadableData &data) {
ReadableData dataCpy = data;
char *rawData = reinterpret_cast<char*>(&dataCpy);
out.writeRawData(rawData, sizeof(data));
return out;
}
friend QDataStream & operator >> (QDataStream &in, ReadableData &data) {
in.readRawData(reinterpret_cast<char*>(&data), sizeof(ReadableData));
return in;
}
};
Q_DECLARE_METATYPE(ReadableData)

View File

@ -0,0 +1,31 @@
#pragma once
#include <QStandardItemModel>
#include <QMimeData>
#include <QDebug>
#include <ReadableData.h>
class ReadableItemModel : public QStandardItemModel {
public:
ReadableItemModel() : QStandardItemModel() {}
protected:
QStringList mimeTypes() const override {return QStringList(ReadableData::mimeType());}
QMimeData *mimeData(const QModelIndexList &indices) const override {
// FIXME: Pretty messy to have this here
qRegisterMetaTypeStreamOperators<ReadableData>("ReadableData");
if (indices.size() < 1) {
return nullptr;
}
QByteArray b_data;
QDataStream stream(&b_data, QIODevice::WriteOnly);
stream << itemFromIndex(indices[0])->data(Qt::UserRole);
QMimeData *data = new QMimeData;
data->setData(ReadableData::mimeType(), b_data);
return data;
}
};

View File

@ -0,0 +1,31 @@
#include "ReadableManager.h"
ReadableManager::ReadableManager() {
uint16_t mod_count = 0;
tc_module_t **modules = tc_module_find_all_from_category(TC_CATEGORY_READABLE, &mod_count);
if (!modules) {
return;
}
for (uint16_t i = 0; i < mod_count; i++) {
if (modules[i]->init_callback && modules[i]->init_callback() == TC_SUCCESS) {
// Module was initialized successfully
tc_readable_module_data_t *mod_data = static_cast<tc_readable_module_data_t*>(modules[i]->category_data_callback());
if (mod_data && mod_data->root_node) {
// Add to the list
m_rootNodes.append(mod_data->root_node);
m_root = mod_data->root_node;
}
}
}
/* tc_module_t *nv_mod = tc_module_find(TC_CATEGORY_READABLE, "nvidia");
nv_mod->init_callback();
tc_readable_node_t *nv_node = static_cast<tc_readable_node_t*>(nv_mod->category_data_callback());
m_rootNodes.append(nv_node);*/
}
ReadableManager::~ReadableManager() {
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <QVector>
#include <tc_module.h>
#include <tc_readable.h>
class ReadableManager {
public:
ReadableManager();
~ReadableManager();
QVector <tc_readable_node_t*> rootNodes() {return m_rootNodes;}
tc_readable_node_t *root() {return m_root;}
private:
QVector <tc_readable_node_t*> m_rootNodes;
tc_readable_node_t *m_root;
};

View File

@ -0,0 +1,39 @@
#include "ReadableMasterObservable.h"
#include <QDebug>
ReadableMasterObservable::ReadableMasterObservable(ReadableData *data, QObject *parent) : QObject(parent) {
m_lowestInterval = std::chrono::milliseconds(-1);
//m_readableData = data;
m_readableData = ReadableData(*data);
m_emitTimer = new QTimer;
}
ReadableMasterObservable::~ReadableMasterObservable() {
delete m_emitTimer;
}
void ReadableMasterObservable::notifyChangedInterval(std::chrono::milliseconds interval) {
// Interval hasn't been set yet or new one is in the range 0 < new < old
if ((m_lowestInterval < std::chrono::milliseconds(0) || interval < m_lowestInterval) && interval > std::chrono::milliseconds(0)) {
m_lowestInterval = interval;
}
m_emitTimer->start(m_lowestInterval);
connect(m_emitTimer, &QTimer::timeout, [=]() {
emit valueUpdated(m_readableData.value());
});
}
ReadableObservable *ReadableMasterObservable::createObservable(std::chrono::milliseconds updateInterval) {
ReadableObservable *observable = new ReadableObservable(this, this); // Set as parent so it gets deleted along with this object and we can keep track of it
observable->setInterval(updateInterval);
notifyChangedInterval(updateInterval);
return observable;
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <QObject>
#include <QHash>
#include <QTimer>
#include "ReadableManager.h"
#include "ReadableData.h"
#include "ReadableObservable.h"
// Class that updates readable values from nodes and emits signals for observers. This prevents CPU overhead when the same value is being observed in multiple places.
class ReadableObservable;
class ReadableMasterObservable : public QObject {
public:
ReadableMasterObservable(ReadableData *data, QObject *parent = nullptr);
~ReadableMasterObservable();
void notifyChangedInterval(std::chrono::milliseconds interval); // Called when an observable changes its emission interval
//ReadableObservable *observable(ReadableData *data, std::chrono::milliseconds updateInterval);
ReadableObservable *createObservable(std::chrono::milliseconds updateInterval);
signals:
void valueUpdated(tc_readable_result_t value);
private:
Q_OBJECT
ReadableData m_readableData; // Node to update value from
std::chrono::milliseconds m_lowestInterval; // The lowest interval that an observer wants to update its value
QTimer *m_emitTimer; // Updates the value
};

View File

@ -0,0 +1,19 @@
#include "ReadableObservable.h"
ReadableObservable::ReadableObservable(ReadableMasterObservable *masterObservable, QObject *parent) : QObject(parent) {
connect(masterObservable, &ReadableMasterObservable::valueUpdated, [=](tc_readable_result_t value) {
m_latestValue = value;
});
m_emitTimer = new QTimer(this);
}
void ReadableObservable::setInterval(std::chrono::milliseconds interval) {
m_emitTimer->start(interval);
emit intervalChanged(interval);
connect(m_emitTimer, &QTimer::timeout, [=]() {
emit valueUpdated(m_latestValue);
});
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <QObject>
#include <QTimer>
#include "ReadableData.h"
#include "ReadableMasterObservable.h"
class ReadableMasterObservable;
class ReadableObservable : public QObject {
public:
ReadableObservable(ReadableMasterObservable *masterObservable, QObject *parent = nullptr);
void setInterval(std::chrono::milliseconds interval); // Set the emission interval
signals:
void valueUpdated(tc_readable_result_t value);
void intervalChanged(std::chrono::milliseconds interval);
private:
Q_OBJECT
QTimer *m_emitTimer;
tc_readable_result_t m_latestValue;
};

View File

@ -0,0 +1,15 @@
#include "ReadableObservableManager.h"
ReadableObservable *ReadableObservableManager::observable(ReadableData *data, std::chrono::milliseconds updateInterval) {
if (m_nodeMap.contains(data->node())) {
// MasterObservable for this node exists
ReadableMasterObservable *mo = m_nodeMap.value(data->node());
return mo->createObservable(updateInterval);
}
ReadableMasterObservable *mo = new ReadableMasterObservable(data);
m_nodeMap.insert(data->node(), mo);
return mo->createObservable(updateInterval);
}

View File

@ -0,0 +1,17 @@
#pragma once
// Holds a list of MasterObservables so they aren't duplicated and creates Observables
#include "ReadableMasterObservable.h"
#include <ReadableData.h>
#include <QHash>
class ReadableObservableManager {
public:
ReadableObservableManager() {};
// Create master observable for data if doesn't exist, and return an observable connected to it
ReadableObservable *observable(ReadableData *data, std::chrono::milliseconds updateInterval);
private:
QHash <const tc_readable_node_t*, ReadableMasterObservable*> m_nodeMap; // Hash for checking if there already is a MasterObservable with a node
};

View File

@ -0,0 +1,39 @@
#include "MainWindow.h"
#include <tc_interface.h>
#include <tc_module.h>
#include <QApplication>
int main(int argc, char **argv);
// Don't scramble the interaction point symbols
extern "C" {
// Module Information
static tc_module_t mod_info = {
.category = TC_CATEGORY_INTERFACE,
.name = "qt",
.description = "Qt Interface",
.init_callback = (int8_t (*)()) &main,
.init_callback_argc = 2,
.init_callback_args = {TC_TYPE_INT, TC_TYPE_STRING_ARR},
.close_callback = NULL
};
tc_module_t *TC_MODULE_INFO_FUNCTION();
tc_module_t *TC_MODULE_INFO_FUNCTION() {
return &mod_info;
}
}
int main(int argc, char **argv) {
QApplication app(argc, argv);
MainWindow mw;
mw.show();
return app.exec();
}

View File

@ -0,0 +1,52 @@
qt5 = import('qt5')
qt5_dep = dependency('qt5',
modules : ['Widgets', 'Charts'])
moc_files = qt5.preprocess(moc_headers : [ 'MainWindow.h',
'widgets/AbstractExpandableItemEditor.h',
'widgets/AssignableWidget.h',
'widgets/IntRangeEditor.h',
'widgets/EnumEditor.h',
'widgets/DragChartView.h',
'widgets/GraphEditor.h',
'widgets/ReadableWidget.h',
'widgets/ReadableBrowser.h',
'widgets/ReadableDisplay.h',
'widgets/ReadableGraphDisplay.h',
'widgets/ReadableTreeView.h',
'data/AssignableEditorDelegate.h',
'data/AssignableManager.h',
'data/ReadableMasterObservable.h',
'data/ReadableObservable.h'],
dependencies : qt5_dep)
sources = ['main.cpp',
'MainWindow.cpp',
'widgets/AssignableWidget.cpp',
'widgets/AssignableParametrizationEditor.cpp',
'widgets/IntRangeEditor.cpp',
'widgets/EnumEditor.cpp',
'widgets/DragChartView.cpp',
'widgets/GraphEditor.cpp',
'widgets/ReadableWidget.cpp',
'widgets/ReadableBrowser.cpp',
'widgets/ReadableDisplay.cpp',
'widgets/ReadableGraphDisplay.cpp',
'data/AssignableData.cpp',
'data/AssignableEditorDelegate.cpp',
'data/AssignableManager.cpp',
'data/ReadableManager.cpp',
'data/ReadableMasterObservable.cpp',
'data/ReadableObservable.cpp',
'data/ReadableObservableManager.cpp']
local_qt_incdirs = ['widgets', 'data']
shared_library('qt',
sources,
moc_files,
include_directories : [incdir, local_qt_incdirs],
dependencies : qt5_dep,
link_with : libtuxclocker,
install_dir : get_option('libdir') / 'tuxclocker' / 'modules' / 'interface',
install : true)

View File

@ -0,0 +1,18 @@
#pragma once
// Defines the common interface for assignable editors
#include <QVariant>
#include <QWidget>
#include <AssignableData.h>
class AbstractAssignableEditor : public QWidget {
public:
AbstractAssignableEditor(QWidget *parent = nullptr) : QWidget(parent) {}
virtual void setValue(QVariant value) = 0;
virtual void setAssignableData(const AssignableData &data) = 0;
virtual QVariant value() = 0;
// Get the textual representation of the current value
virtual QString text() = 0;
};

View File

@ -0,0 +1,15 @@
#pragma once
// Interface for editor widgets that cannot fit into the space normally provided by a delegate or otherwise need expanding.
#include <QWidget>
class AbstractExpandableItemEditor : public QWidget {
public:
AbstractExpandableItemEditor(QWidget *parent) : QWidget(parent) {}
signals:
void expansionSizeRequested(const QSize size);
void expansionDisableRequested();
private:
Q_OBJECT
};

View File

@ -0,0 +1,91 @@
#include "AssignableParametrizationEditor.h"
#include <AssignableParametrizationData.h>
#include <QLabel>
#include <QDebug>
AssignableParametrizationEditor::AssignableParametrizationEditor(QWidget *parent) : AbstractExpandableItemEditor(parent) {
setAutoFillBackground(true);
setLayout(new QHBoxLayout);
m_stackedWidget = new QStackedWidget;
m_initialWidget = new QWidget;
m_initialWidget->setLayout(new QHBoxLayout);
m_initialWidget->layout()->setMargin(0);
// Group enablement combobox and label together
auto enableWidget = new QWidget;
enableWidget->setLayout(new QHBoxLayout);
enableWidget->layout()->setMargin(0);
enableWidget->layout()->addWidget(new QLabel("Enabled"));
enableWidget->layout()->addWidget((m_enabledCheckBox = new QCheckBox));
// FIXME : this makes the layout unable to shrink
enableWidget->layout()->setSizeConstraint(QLayout::SetFixedSize);
m_initialWidget->layout()->addWidget(enableWidget);
m_initialWidget->layout()->addWidget((m_editButton = new QPushButton("Edit")));
m_stackedWidget->addWidget(m_initialWidget);
m_stackedWidget->setCurrentWidget(m_initialWidget);
m_stackedWidget->addWidget((m_editorWidget = new GraphEditor));
connect(m_editButton, &QPushButton::clicked, [=]() {
m_editorWidget->dragChartView()->setVector(m_parametrizationData.pointsVector());
m_stackedWidget->setCurrentWidget(m_editorWidget);
emit expansionSizeRequested(QSize(200, 300));
});
connect(m_editorWidget, &GraphEditor::cancelled, [=]() {
m_stackedWidget->setCurrentWidget(m_initialWidget);
emit expansionDisableRequested();
});
connect(m_editorWidget, &GraphEditor::saved, [=]() {
m_stackedWidget->setCurrentWidget(m_initialWidget);
emit expansionDisableRequested();
m_parametrizationData.setPointsVector(m_editorWidget->dragChartView()->vector());
});
connect(m_enabledCheckBox, &QCheckBox::stateChanged, [=](int state) {
if (state == Qt::Unchecked) {
m_parametrizationData.setEnabled(false);
return;
}
if (state == Qt::Checked) {
m_parametrizationData.setEnabled(true);
}
});
layout()->addWidget(m_stackedWidget);
layout()->setMargin(0);
}
void AssignableParametrizationEditor::setData(AssignableParametrizationData &data) {
m_parametrizationData = data;
// Update y-axis label
m_editorWidget->dragChartView()->yAxis()->setTitleText(m_parametrizationData.assignableData().name);
// Set y-axis range
switch (m_parametrizationData.assignableData().m_rangeInfo.range_data_type) {
case TC_ASSIGNABLE_RANGE_DOUBLE:
m_editorWidget->dragChartView()->yAxis()->setMin(m_parametrizationData.assignableData().m_rangeInfo.double_range.min);
m_editorWidget->dragChartView()->yAxis()->setMax(m_parametrizationData.assignableData().m_rangeInfo.double_range.max);
break;
case TC_ASSIGNABLE_RANGE_INT:
m_editorWidget->dragChartView()->yAxis()->setMin(m_parametrizationData.assignableData().m_rangeInfo.int_range.min);
m_editorWidget->dragChartView()->yAxis()->setMax(m_parametrizationData.assignableData().m_rangeInfo.int_range.max);
break;
default:
break;
}
qDebug() << "enable state:" << m_parametrizationData.enabled();
// Set enablement checkbox state
if (m_parametrizationData.enabled()) {
m_enabledCheckBox->setChecked(true);
}
else {
m_enabledCheckBox->setChecked(false);
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <QWidget>
#include <QPushButton>
#include <QCheckBox>
#include <QGridLayout>
#include <QStackedWidget>
#include <AssignableParametrizationData.h>
#include "AbstractExpandableItemEditor.h"
#include "GraphEditor.h"
class AssignableParametrizationEditor : public AbstractExpandableItemEditor {
public:
AssignableParametrizationEditor(QWidget *parent = nullptr);
void setData(AssignableParametrizationData &data);
AssignableParametrizationData data() {return m_parametrizationData;}
// Get the text representation of data (enabled or not)
QString text() {
QString text = (data().enabled()) ? QString("Enabled") : QString("Disabled");
return text;
}
private:
QVBoxLayout *m_layout;
QCheckBox *m_enabledCheckBox;
QPushButton *m_editButton;
QStackedWidget *m_stackedWidget; // Displays editor and initial widget at different times
GraphEditor *m_editorWidget; // Editor graph and Save/Cancel buttons
QWidget *m_initialWidget; // Checkbox for enablement and button to start editing
AssignableParametrizationData m_parametrizationData;
};

View File

@ -0,0 +1,147 @@
#include "AssignableWidget.h"
#include <AssignableEditorDelegate.h>
#include <AssignableManager.h>
#include <tc_module.h>
#include <tc_assignable.h>
#include <tc_common.h>
#include <QDebug>
AssignableWidget::AssignableWidget(QWidget *parent) : QWidget(parent) {
m_mainLayout = new QGridLayout;
m_splitter = new QSplitter;
m_mainLayout->addWidget(m_splitter);
m_assignableTreeView = new QTreeView;
genAssignableTree(m_assignableTreeView);
m_splitter->addWidget(m_assignableTreeView);
setLayout(m_mainLayout);
m_assignableManager = new AssignableManager;
}
AssignableWidget::~AssignableWidget() {
}
void AssignableWidget::genAssignableTree(QTreeView *treeView) {
tc_module_t *nv_mod = tc_module_find(TC_CATEGORY_ASSIGNABLE, "nvidia");
if (nv_mod != NULL) {
if (nv_mod->init_callback() != TC_SUCCESS) {
return;
}
printf("opened nv mod\n");
}
else {
return;
}
tc_assignable_node_t *root = (tc_assignable_node_t*) nv_mod->category_data_callback();
if (root == NULL) {
return;
}
QStandardItemModel *assignableModel = new QStandardItemModel(0, 2);
// Add header items
QStandardItem *propertyHeader = new QStandardItem;
propertyHeader->setText("Property");
assignableModel->setHorizontalHeaderItem(0, propertyHeader);
QStandardItem *valueHeader = new QStandardItem;
valueHeader->setText("Value");
assignableModel->setHorizontalHeaderItem(1, valueHeader);
QStandardItem *parametrizationHeader = new QStandardItem;
parametrizationHeader->setText("Parametrization");
assignableModel->setHorizontalHeaderItem(2, parametrizationHeader);
std::function<void(tc_assignable_node_t*, QStandardItem*)> traverse;
traverse = [=, &traverse](tc_assignable_node_t *node, QStandardItem *item) {
if (node == NULL) {
return;
}
if (node->name != NULL) {
qDebug() << node->name;
}
QStandardItem *newItem = addAssignableItem(node, item);
for (uint32_t i = 0; i < node->children_count; i++) {
traverse(node->children_nodes[i], newItem);
}
};
QStandardItem *parentItem = assignableModel->invisibleRootItem();
// Get root nodes from manager
//QList <tc_assignable_node_t*> rootNodes = m_assignableManager->rootNodes();
/*for (tc_assignable_node_t *root : rootNodes) {
traverse(root, parentItem);
}*/
// We don't want to display root nodes from the modules
for (uint32_t i = 0; i < root->children_count; i++) {
traverse(root->children_nodes[i], parentItem);
}
m_assignableTreeView->setModel(assignableModel);
AssignableEditorDelegate *delegate = new AssignableEditorDelegate(m_assignableTreeView);
m_assignableTreeView->setItemDelegateForColumn(1, delegate);
m_assignableTreeView->setItemDelegateForColumn(2, delegate);
m_assignableTreeView->setEditTriggers(QAbstractItemView::AllEditTriggers);
m_assignableTreeView->header()->setStretchLastSection(true);
//m_assignableTreeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
/*connect(m_assignableTreeView, &QTreeView::activated, [=](const QModelIndex &index) {
qDebug() << index;
delegate->setSpanAllColumns(true);
});*/
}
QStandardItem *AssignableWidget::addAssignableItem(tc_assignable_node_t *node, QStandardItem *parent) {
if (!node || !node->name) {
return nullptr;
}
QList <QStandardItem*> rowItems;
QStandardItem *nameItem = new QStandardItem;
nameItem->setText(node->name);
nameItem->setEditable(false);
rowItems.append(nameItem);
// Don't add editor item for TC_ASSIGNABLE_NONE nodes
if (node->value_category != TC_ASSIGNABLE_NONE) {
QStandardItem *editorItem = new QStandardItem;
AssignableData data(node);
QVariant v;
v.setValue(data);
editorItem->setData(v, Qt::UserRole);
//editorItem->setText(node->name);
rowItems.append(editorItem);
// Add parametrization item
AssignableParametrizationData p_data(data);
QStandardItem *p_item = new QStandardItem;
QVariant pv;
pv.setValue(p_data);
p_item->setData(pv, Qt::UserRole);
//p_item->setData(QSize(100, 100), Qt::SizeHintRole);
rowItems.append(p_item);
}
parent->appendRow(rowItems);
return nameItem;
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <AssignableData.h>
#include <AssignableManager.h>
#include <AssignableParametrizationData.h>
#include <QWidget>
#include <QSplitter>
#include <QLayout>
#include <QStandardItemModel>
#include <QTreeView>
#include <QHeaderView>
#include <QTreeWidgetItem>
class AssignableWidget : public QWidget {
Q_OBJECT
public:
AssignableWidget(QWidget *parent = nullptr);
~AssignableWidget();
private:
QGridLayout *m_mainLayout;
// Splitter for editor and viewer
QSplitter *m_splitter;
QTreeView *m_assignableTreeView;
// Assignable manager instance - maybe move this somewhere else?
AssignableManager *m_assignableManager;
void genAssignableTree(QTreeView *treeView);
QStandardItem *addAssignableItem(tc_assignable_node_t *node, QStandardItem *parent);
};

View File

@ -0,0 +1,356 @@
#include "DragChartView.h"
#include <QDebug>
#include <QToolTip>
#include <QApplication>
#include <QValueAxis>
#include <QScreen>
#include <QWindow>
DragChartView::DragChartView(QWidget *parent) : QChartView(parent)
{
chart()->installEventFilter(this);
setRenderHint(QPainter::Antialiasing);
m_toolTipLabel = new QLabel;
m_toolTipLabel->setWindowFlag(Qt::ToolTip);
m_dragCanStart = false;
m_mouseInLimitArea = false;
m_scatterPressed = false;
m_leftLineFillerItem = new QGraphicsLineItem;
m_rightLineFillerItem = new QGraphicsLineItem;
chart()->scene()->addItem(m_leftLineFillerItem);
chart()->scene()->addItem(m_rightLineFillerItem);
m_chartMargin = m_series.markerSize() / 2;
// Resize axes by margin
connect(&m_xAxis, &QValueAxis::rangeChanged, [=](qreal min, qreal max) {
m_limitRect.setLeft(min);
m_limitRect.setRight(max);
m_limitRect.setTopLeft(QPointF(min, m_limitRect.top()));
m_limitRect.setBottomRight(QPointF(max, m_limitRect.bottom()));
if (chart()->rect().isNull()) {
return;
}
// Convert m_chartMargin to chart value
auto valueDelta = abs(chart()->mapToValue(QPointF(0, 0)).x() - chart()->mapToValue(QPointF(m_chartMargin, 0)).x());
qDebug() << "value delta:" << valueDelta;
m_xAxis.blockSignals(true); // Don't go to an infinite loop
m_xAxis.setRange(min - valueDelta, max + valueDelta);
m_xAxis.blockSignals(false);
});
connect(&m_yAxis, &QValueAxis::rangeChanged, [=](qreal min, qreal max) {
// Update limit rect
m_limitRect.setBottom(min);
m_limitRect.setTop(max);
m_limitRect.setTopLeft(QPointF(m_limitRect.left(), max));
m_limitRect.setBottomRight(QPointF(m_limitRect.right(), min));
if (chart()->rect().isNull()) {
return;
}
//auto valueDelta = abs(chart()->mapToValue(QPointF(0, 0)).y() - chart()->mapToValue(QPointF(0, m_chartMargin)).y());
auto valueDelta = abs(chart()->mapToValue(QPointF(0, 0)).x() - chart()->mapToValue(QPointF(m_chartMargin, 0)).x());
qDebug() << "value delta:" << valueDelta;
m_yAxis.blockSignals(true); // Don't go to an infinite loop
m_yAxis.setRange(min - valueDelta, max + valueDelta);
m_yAxis.blockSignals(false);
});
// Delete filler items when points are removed
connect(&m_series, &QScatterSeries::pointRemoved, [=]() {
chart()->scene()->removeItem(m_lineFillerItems.last());
delete m_lineFillerItems.last();
m_lineFillerItems.pop_back();
});
// Add filler item when point is added
connect(&m_series, &QScatterSeries::pointAdded, [=]() {
auto item = new QGraphicsLineItem;
item->setPen(QPen(QBrush(QColor(Qt::blue)), 3));
m_lineFillerItems.append(item);
chart()->scene()->addItem(item);
});
connect(&m_series, &QScatterSeries::pointsReplaced, [=]() {
// Delete filler items
for (auto item : m_lineFillerItems) {
delete item;
}
m_lineFillerItems.clear();
// Create new ones
for (int i = 0; i < m_series.pointsVector().length(); i++) {
auto item = new QGraphicsLineItem;
item->setPen(QPen(QBrush(QColor(Qt::blue)), 3));
m_lineFillerItems.append(item);
chart()->scene()->addItem(item);
}
});
connect(&m_series, &QScatterSeries::pressed, [=](QPointF point) {
m_dragCanStart = true;
m_latestScatterPoint = point;
m_scatterPressed = true;
});
connect(&m_series, &QScatterSeries::clicked, [=](const QPointF point) {
m_scatterPressed = false;
if (m_dragActive) {
m_dragActive = false;
emit dragEnded(point);
}
else {
// Just a click, delete point
m_series.remove(point);
}
});
chart()->addSeries(&m_series);
chart()->addAxis(&m_xAxis, Qt::AlignBottom);
chart()->addAxis(&m_yAxis, Qt::AlignLeft);
m_series.attachAxis(&m_xAxis);
m_series.attachAxis(&m_yAxis);
m_xAxis.setRange(0, 25);
m_yAxis.setRange(0, 25);
chart()->setBackgroundRoundness(0);
// Set theme colors
chart()->setBackgroundBrush(QBrush(QPalette().color(QPalette::Background)));
chart()->legend()->setLabelColor(QPalette().color(QPalette::Text));
m_yAxis.setLabelsColor(QPalette().color(QPalette::Text));
m_xAxis.setLabelsColor(QPalette().color(QPalette::Text));
m_yAxis.setTitleBrush(QBrush(QPalette().color(QPalette::Text)));
m_xAxis.setTitleBrush(QBrush(QPalette().color(QPalette::Text)));
// Set cursor to indicate dragging
connect(this, &DragChartView::dragStarted, [=]() {
setCursor(Qt::ClosedHandCursor);
});
connect(this, &DragChartView::dragEnded, [=]() {
if (m_mouseInLimitArea) {
setCursor(Qt::CrossCursor);
}
else {
setCursor(Qt::ArrowCursor);
}
});
connect(this, &DragChartView::limitAreaEntered, [=]() {
setCursor(Qt::CrossCursor);
});
connect(this, &DragChartView::limitAreaExited, [=]() {
if (cursor().shape() != Qt::ClosedHandCursor) {
setCursor(Qt::ArrowCursor);
}
});
}
void DragChartView::setVector(const QVector <QPointF> vector) {
m_series.replace(vector);
}
bool DragChartView::event(QEvent *event) {
//qDebug() << event->type();
if (event->type() == QEvent::Resize || event->type() == QEvent::UpdateLater) {
// Chart has a geometry when this is true
drawFillerLines(&m_series);
}
if (event->type() == QEvent::Leave && !m_dragActive) {
// Set to normal cursor
setCursor(Qt::ArrowCursor);
}
return QChartView::event(event);
}
void DragChartView::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
m_dragStartPosition = event->pos();
}
QChartView::mousePressEvent(event);
}
void DragChartView::mouseMoveEvent(QMouseEvent *event) {
if (m_limitRect.contains(chart()->mapToValue(event->pos())) && !m_mouseInLimitArea) {
m_mouseInLimitArea = true;
emit limitAreaEntered();
}
if (!m_limitRect.contains(chart()->mapToValue(event->pos())) && m_mouseInLimitArea) {
m_mouseInLimitArea = false;
emit limitAreaExited();
}
if (!(event->buttons() & Qt::LeftButton)) {
return;
}
if ((event->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance() || !m_dragCanStart) {
return;
}
// Start drag
emit dragStarted(m_dragStartPosition);
m_dragActive = true;
if (m_toolTipLabel->isHidden()) {
m_toolTipLabel->show();
}
m_toolTipLabel->setText(QString("%1, %2").arg(QString::number(m_latestScatterPoint.x()), QString::number(m_latestScatterPoint.y())));
// FIXME : doesn't work properly when screen is switched(?)
m_toolTipLabel->move(event->screenPos().toPoint() + toolTipOffset(this, event->windowPos().toPoint()));
// Don't move point out of bounds
if (m_limitRect.contains(chart()->mapToValue(event->pos()))) {
replaceMovedPoint(m_latestScatterPoint, chart()->mapToValue(event->pos(), &m_series));
}
else {
QPointF point(chart()->mapToValue(event->pos()));
// Set the point value to the constraint where it exceeds it
if (chart()->mapToValue(event->pos()).x() > m_limitRect.right()) {
point.setX(m_limitRect.right());
}
if (chart()->mapToValue(event->pos()).x() < m_limitRect.left()) {
point.setX(m_limitRect.left());
}
if (chart()->mapToValue(event->pos()).y() > m_limitRect.top()) {
point.setY(m_limitRect.top());
}
if (chart()->mapToValue(event->pos()).y() < m_limitRect.bottom()) {
point.setY(m_limitRect.bottom());
}
replaceMovedPoint(m_latestScatterPoint, point);
}
drawFillerLines(&m_series);
}
void DragChartView::mouseReleaseEvent(QMouseEvent *event) {
m_dragCanStart = false;
if (!m_scatterPressed) {
// Add a new point to series
m_series.append(chart()->mapToValue(event->pos()));
drawFillerLines(&m_series);
}
m_toolTipLabel->hide();
QChartView::mouseReleaseEvent(event);
}
void DragChartView::wheelEvent(QWheelEvent *event) {
qDebug() << event->angleDelta();
qreal factor = event->angleDelta().y() > 0 ? 0.5 : 2.0;
//chart()->zoom(factor);
event->accept();
drawFillerLines(&m_series);
QChartView::wheelEvent(event);
}
void DragChartView::replaceMovedPoint(const QPointF old, const QPointF new_) {
m_series.replace(old, new_);
m_latestScatterPoint = new_;
}
QVector <QPointF> DragChartView::sortPointFByAscendingX(const QVector<QPointF> points) {
QVector <qreal> ascendingX;
QVector <qreal> originalY;
for (QPointF point : points) {
ascendingX.append(point.x());
originalY.append(point.y());
}
std::sort(ascendingX.begin(), ascendingX.end());
QVector <QPointF> sorted;
// Find the original y values for x values
for (qreal x : ascendingX) {
for (QPointF point : points) {
if (qFuzzyCompare(x, point.x())) {
sorted.append(QPointF(x, point.y()));
break;
}
}
}
return sorted;
}
void DragChartView::drawFillerLines(QScatterSeries *series) {
// Sort points by ascending x
QVector <QPointF> sorted = sortPointFByAscendingX(series->pointsVector());
if (sorted.isEmpty()) {
return;
}
for (int i = 0; i < sorted.length() - 1; i++) {
m_lineFillerItems[i]->setLine(QLineF(chart()->mapToPosition(sorted[i]),
chart()->mapToPosition(sorted[i + 1])));
}
m_leftLineFillerItem->setLine(QLineF(chart()->mapToPosition(QPointF(m_xAxis.min(), sorted[0].y())),
chart()->mapToPosition(sorted[0])));
m_rightLineFillerItem->setLine(QLineF(chart()->mapToPosition(sorted.last()),
chart()->mapToPosition(QPointF(m_xAxis.max(), sorted.last().y()))));
chart()->update();
}
bool DragChartView::eventFilter(QObject *obj, QEvent *event) {
static bool resized = false;
if (obj == chart() && event->type() == QEvent::WindowActivate && !resized) {
resized = true;
emit m_xAxis.rangeChanged(m_xAxis.min(), m_xAxis.max());
emit m_yAxis.rangeChanged(m_yAxis.min(), m_yAxis.max());
}
return QObject::eventFilter(obj, event);
}
QPoint DragChartView::toolTipOffset(QWidget *widget, const QPoint windowCursorPos) {
QRect screenRect = widget->window()->windowHandle()->screen()->geometry();
if (screenRect.width() > screenRect.height()) {
// Use x offset for screens that are wider than high
// Set the offset to the side that has more space
int xOffset = (windowCursorPos.x() > widget->window()->rect().width() / 2) ? -qRound(toolTipMargin() * screenRect.width()) : qRound(toolTipMargin() * screenRect.width());
return QPoint(xOffset, 0);
}
int yOffset = (windowCursorPos.y() > widget->window()->rect().height() / 2) ? -qRound(toolTipMargin() * screenRect.height()) :qRound(toolTipMargin() * screenRect.height());
return QPoint(0, yOffset);
}

Some files were not shown because too many files have changed in this diff Show More