Merge branch 'cpplib'
20
.clang-format
Normal 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
@ -0,0 +1,3 @@
|
||||
build
|
||||
/*kdev*
|
||||
**/*swp*
|
6
.gitmodules
vendored
Normal 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
@ -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
|
||||
|
||||
    
|
||||
|
||||
# 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
|
||||
|
||||

|
||||
|
||||
### Editing an item
|
||||
|
||||

|
||||
|
||||
### Parametrizing an item
|
||||

|
||||
|
||||
### Showing pending changes
|
||||

|
||||
|
||||
### Settings
|
||||

|
||||
|
||||
# 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
@ -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[@]}))
|
60
default.nix
Normal 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
@ -0,0 +1,3 @@
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
DBUS_VERBOSE=1 sudo dbus-daemon --config-file=dbusconf.conf --nofork
|
89
dev/dbusconf.conf
Normal 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
@ -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
@ -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
374
editprofile.cpp
@ -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();
|
||||
}
|
103
editprofile.h
@ -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
|
||||
|
@ -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><html><head/><body><p>This graph defines the relation of fan speed to the GPU temperature.</p><p>To add a point, double click on the area.</p><p>To remove a point, click on it.</p><p>To move a point, drag it.</p></body></html></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>
|
28
main.cpp
@ -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();
|
||||
}
|
1200
mainwindow.cpp
260
mainwindow.h
@ -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
|
263
mainwindow.ui
@ -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><html><head/><body><p>Edit fan curve</p></body></html></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&les</string>
|
||||
</property>
|
||||
<addaction name="actionManage_profiles"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusBar"/>
|
||||
<action name="actionQuit">
|
||||
<property name="text">
|
||||
<string>&Quit</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEdit_current_profile">
|
||||
<property name="text">
|
||||
<string>&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>&Add new profile</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManage_profiles">
|
||||
<property name="text">
|
||||
<string>&Manage profiles</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources>
|
||||
<include location="resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
9
meson.build
Normal 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
@ -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')
|
BIN
minusicon.png
Before Width: | Height: | Size: 227 B |
31
mkTarball.sh
Executable 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
|
||||
|
126
newprofile.cpp
@ -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;
|
||||
}
|
||||
}
|
81
newprofile.h
@ -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
|
@ -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>
|
490
nvidia.cpp
@ -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
@ -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
|
@ -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();
|
||||
}
|
36
plotwidget.h
@ -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
|
BIN
plusicon.png
Before Width: | Height: | Size: 64 KiB |
30214
qcustomplot.cpp
6673
qcustomplot.h
13
release.nix
Normal 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
|
||||
}
|
@ -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>
|
80
rojekti.pro
@ -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
After Width: | Height: | Size: 13 KiB |
BIN
screenshots/mainview.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
screenshots/paramEditor.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
screenshots/settings.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
screenshots/stateChange.png
Normal file
After Width: | Height: | Size: 54 KiB |
10
src/include/Crypto.hpp
Normal 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
@ -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
@ -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
|
30
src/include/Functional.hpp
Normal 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
@ -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
@ -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
|
1
src/include/deps/FunctionalPlus
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit e306deecd519c2358b4f338c88b2bc269552c78a
|
@ -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
|
1
src/include/deps/patterns
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit b3270e0dd7b6312f7a4fe8647e2333dbb86e355e
|
90
src/include/tc_assignable.h
Normal 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
@ -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
|
21
src/include/tc_filesystem.h
Normal 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
|
1
src/include/tc_interface.h
Normal file
@ -0,0 +1 @@
|
||||
#pragma once
|
123
src/include/tc_module.h
Normal 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
@ -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
@ -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
@ -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
42
src/lib/posix/filesystem.c
Normal 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);
|
||||
}
|
1
src/lib/posix/meson.build
Normal file
@ -0,0 +1 @@
|
||||
|
20
src/lib/posix/module.c
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
|
24
src/modules/assignable/meson.build
Normal 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
|
568
src/modules/assignable/nvidia_linux.c
Normal 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;
|
||||
}
|
1
src/modules/interface/meson.build
Normal file
@ -0,0 +1 @@
|
||||
subdir('qt')
|
77
src/modules/interface/qt/MainWindow.cpp
Normal 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);
|
||||
}
|
42
src/modules/interface/qt/MainWindow.h
Normal 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);
|
||||
};
|
32
src/modules/interface/qt/data/AssignableData.cpp
Normal 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;
|
||||
}
|
46
src/modules/interface/qt/data/AssignableData.h
Normal 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)
|
||||
|
176
src/modules/interface/qt/data/AssignableEditorDelegate.cpp
Normal 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);
|
||||
}
|
||||
}
|
35
src/modules/interface/qt/data/AssignableEditorDelegate.h
Normal 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;
|
||||
};
|
52
src/modules/interface/qt/data/AssignableManager.cpp
Normal 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);
|
||||
}
|
||||
}
|
24
src/modules/interface/qt/data/AssignableManager.h
Normal 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;
|
||||
};
|
@ -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);
|
50
src/modules/interface/qt/data/ReadableData.h
Normal 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)
|
31
src/modules/interface/qt/data/ReadableItemModel.h
Normal 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;
|
||||
}
|
||||
};
|
31
src/modules/interface/qt/data/ReadableManager.cpp
Normal 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() {
|
||||
}
|
17
src/modules/interface/qt/data/ReadableManager.h
Normal 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;
|
||||
};
|
39
src/modules/interface/qt/data/ReadableMasterObservable.cpp
Normal 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;
|
||||
}
|
30
src/modules/interface/qt/data/ReadableMasterObservable.h
Normal 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
|
||||
};
|
19
src/modules/interface/qt/data/ReadableObservable.cpp
Normal 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);
|
||||
});
|
||||
}
|
23
src/modules/interface/qt/data/ReadableObservable.h
Normal 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;
|
||||
};
|
15
src/modules/interface/qt/data/ReadableObservableManager.cpp
Normal 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);
|
||||
}
|
17
src/modules/interface/qt/data/ReadableObservableManager.h
Normal 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
|
||||
};
|
39
src/modules/interface/qt/main.cpp
Normal 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();
|
||||
}
|
52
src/modules/interface/qt/meson.build
Normal 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)
|
18
src/modules/interface/qt/widgets/AbstractAssignableEditor.h
Normal 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;
|
||||
};
|
@ -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
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
147
src/modules/interface/qt/widgets/AssignableWidget.cpp
Normal 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;
|
||||
}
|
33
src/modules/interface/qt/widgets/AssignableWidget.h
Normal 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);
|
||||
};
|
356
src/modules/interface/qt/widgets/DragChartView.cpp
Normal 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);
|
||||
}
|