mirror of
https://github.com/tuxdotrs/tpanel.git
synced 2026-09-19 18:19:02 +05:30
Compare commits
6 Commits
4453c280bf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
352eb99568
|
|||
|
f2268f65e7
|
|||
|
6357b949b3
|
|||
|
77c56f9945
|
|||
|
570eea59ae
|
|||
|
c9310e802b
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
result
|
||||||
|
result-bin
|
||||||
|
plugin/build/
|
||||||
|
plugin/dist/
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
[general]
|
[general]
|
||||||
bars = 14
|
bars = 14
|
||||||
framerate = 60
|
framerate = 60
|
||||||
|
sensitivity=140
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
method = pulse
|
method = pulse
|
||||||
|
|||||||
1
assets/icons/network.svg
Normal file
1
assets/icons/network.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M14 22H10C6.22876 22 4.34315 22 3.17157 20.7881C2 19.5763 2 17.6258 2 13.725V12.2039C2 9.91549 2 8.77128 2.5192 7.82274C3.0384 6.87421 3.98695 6.28551 5.88403 5.10813L7.88403 3.86687C9.88939 2.62229 10.8921 2 12 2C13.1079 2 14.1106 2.62229 16.116 3.86687L18.116 5.10812C20.0131 6.28551 20.9616 6.87421 21.4808 7.82274C22 8.77128 22 9.91549 22 12.2039V13.725C22 17.6258 22 19.5763 20.8284 20.7881C19.6569 22 17.7712 22 14 22ZM17.4498 12.192C14.4329 8.93571 9.56706 8.93571 6.55017 12.192C6.26866 12.4959 5.79413 12.514 5.49028 12.2325C5.18643 11.951 5.16832 11.4764 5.44983 11.1726C9.06036 7.27552 14.9396 7.27552 18.5502 11.1726C18.8317 11.4764 18.8136 11.951 18.5097 12.2325C18.2059 12.514 17.7313 12.4959 17.4498 12.192ZM15.4498 14.3505C13.5375 12.2864 10.4625 12.2864 8.55019 14.3505C8.26868 14.6544 7.79415 14.6725 7.4903 14.391C7.18645 14.1095 7.16834 13.6349 7.44985 13.3311C9.95581 10.6262 14.0442 10.6262 16.5502 13.3311C16.8317 13.6349 16.8136 14.1095 16.5097 14.391C16.2059 14.6725 15.7314 14.6544 15.4498 14.3505ZM13.4499 16.5095C12.6421 15.6377 11.358 15.6377 10.5502 16.5095C10.2687 16.8134 9.79417 16.8315 9.49031 16.55C9.18646 16.2684 9.16835 15.7939 9.44986 15.4901C10.8513 13.9775 13.1488 13.9775 14.5502 15.4901C14.8317 15.7939 14.8136 16.2684 14.5098 16.55C14.2059 16.8315 13.7314 16.8134 13.4499 16.5095Z" fill="#000"></path> </g></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
35
assets/icons/nvidia.svg
Normal file
35
assets/icons/nvidia.svg
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="128" height="128" version="1.1">
|
||||||
|
<title>Nvidia Logo</title>
|
||||||
|
<desc>This is shape (source) for Clarity vector icon theme for gtk</desc>
|
||||||
|
<metadata>
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:title>Nvidia Logo</dc:title>
|
||||||
|
<dc:description>This is shape (source) for Clarity vector icon theme for gtk</dc:description>
|
||||||
|
<dc:creator>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>Jakub Jankiewicz</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:creator>
|
||||||
|
<dc:rights>
|
||||||
|
<cc:Agent>
|
||||||
|
<dc:title>Jakub Jankiewicz</dc:title>
|
||||||
|
</cc:Agent>
|
||||||
|
</dc:rights>
|
||||||
|
<dc:date>2010</dc:date>
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<path d="m 19.777894,58.887563 c 0,0 10.05027,-14.828437 30.117316,-16.362058 l 0,-5.380182 c -22.2268,1.785057 -41.4742019,20.61011 -41.4742019,20.61011 0,0 10.9013539,31.514726 41.4742019,34.400204 l 0,-5.718258 C 27.458955,83.614426 19.777894,58.887563 19.777894,58.887563 z m 30.117316,16.177165 0,5.236824 C 32.938729,77.278522 28.232018,59.651693 28.232018,59.651693 c 0,0 8.141505,-9.019072 21.663192,-10.481238 l 0,5.746393 c -0.01032,0 -0.01741,-0.0031 -0.0259,-0.0031 -7.09691,-0.851665 -12.640994,5.777655 -12.640994,5.777655 0,0 3.106991,11.16141 12.666896,14.373351 m 0,-47.841948 0,9.922543 c 0.652482,-0.05047 1.304964,-0.09245 1.961018,-0.114779 25.269481,-0.851665 41.734256,20.724887 41.734256,20.724887 0,0 -18.910353,22.994504 -38.611635,22.994504 -1.805601,0 -3.495979,-0.167475 -5.083639,-0.448385 l 0,6.135827 c 1.357662,0.172387 2.765343,0.273767 4.234208,0.273767 18.333347,0 31.59154,-9.362955 44.429483,-20.444422 2.128489,1.704667 10.842539,5.851342 12.633849,7.667216 -12.206453,10.219531 -40.654381,18.457501 -56.782421,18.457501 -1.554612,0 -3.047594,-0.09424 -4.515119,-0.235803 l 0,8.6216 69.68378,0 0,-73.554458 z m 0,21.947675 0,-6.64495 c 0.645782,-0.04511 1.296477,-0.07995 1.961018,-0.100929 18.171232,-0.570753 30.0932,15.615345 30.0932,15.615345 0,0 -12.876351,17.883175 -26.682522,17.883175 -1.98692,0 -3.768404,-0.319318 -5.371696,-0.858364 l 0,-20.147881 c 7.074133,0.854792 8.497892,3.979647 12.750856,11.06941 l 9.458973,-7.976263 c 0,0 -6.904871,-9.055693 -18.544589,-9.055693 -1.266109,0 -2.476839,0.08888 -3.66524,0.216154"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -11,7 +11,7 @@ Singleton {
|
|||||||
return `${Quickshell.shellPath("modules/bar")}/${name}.qml`;
|
return `${Quickshell.shellPath("modules/bar")}/${name}.qml`;
|
||||||
}
|
}
|
||||||
|
|
||||||
property var left: [root.widget("LauncherButton"), root.widget("PowerProfileWidget"), root.widget("HomeWidget"), root.widget("MusicWidget")]
|
property var left: [root.widget("LauncherButton"), root.widget("NetworkWidget"), root.widget("PowerProfileWidget"), root.widget("GPUWidget"), root.widget("HomeWidget"), root.widget("MusicWidget")]
|
||||||
|
|
||||||
property var center: [root.widget("CavaVisualizer"), root.widget("Workspaces"), root.widget("CavaVisualizer"),]
|
property var center: [root.widget("CavaVisualizer"), root.widget("Workspaces"), root.widget("CavaVisualizer"),]
|
||||||
|
|
||||||
|
|||||||
28
flake.nix
28
flake.nix
@@ -15,14 +15,12 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs = {
|
||||||
{
|
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
quickshell,
|
quickshell,
|
||||||
treefmt-nix,
|
treefmt-nix,
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
|
||||||
@@ -37,12 +35,12 @@
|
|||||||
projectRootFile = "flake.nix";
|
projectRootFile = "flake.nix";
|
||||||
|
|
||||||
programs = {
|
programs = {
|
||||||
nixfmt.enable = true;
|
alejandra.enable = true;
|
||||||
qmlformat.enable = true;
|
qmlformat.enable = true;
|
||||||
|
clang-format.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in rec {
|
||||||
rec {
|
|
||||||
formatter.${system} = treefmtEval.config.build.wrapper;
|
formatter.${system} = treefmtEval.config.build.wrapper;
|
||||||
|
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
@@ -57,7 +55,23 @@
|
|||||||
quickshellWithModules
|
quickshellWithModules
|
||||||
pkgs.cava
|
pkgs.cava
|
||||||
pkgs.wallust
|
pkgs.wallust
|
||||||
|
pkgs.dbus
|
||||||
|
pkgs.cmake
|
||||||
|
pkgs.ninja
|
||||||
|
pkgs.qt6.qtbase
|
||||||
|
pkgs.qt6.qtdeclarative
|
||||||
];
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
if [ -d "$PWD/plugin/dist/lib/qt-6/qml" ]; then
|
||||||
|
export NIXPKGS_QT6_QML_IMPORT_PATH="$PWD/plugin/dist/lib/qt-6/qml''${NIXPKGS_QT6_QML_IMPORT_PATH:+:$NIXPKGS_QT6_QML_IMPORT_PATH}"
|
||||||
|
export QML2_IMPORT_PATH="$PWD/plugin/dist/lib/qt-6/qml''${QML2_IMPORT_PATH:+:$QML2_IMPORT_PATH}"
|
||||||
|
else
|
||||||
|
echo "gpu plugin is not built; run from the repo root to enable 'import Tshell.Cardwire':"
|
||||||
|
echo " cmake -S plugin -B plugin/dist -DCMAKE_INSTALL_PREFIX=\$PWD/plugin/dist"
|
||||||
|
echo " cmake --build plugin/dist --target install"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,14 +31,28 @@ Rectangle {
|
|||||||
id: bar
|
id: bar
|
||||||
|
|
||||||
required property int index
|
required property int index
|
||||||
readonly property real value: Cava.values[index] ?? 0
|
readonly property real level: Math.min(1, (Cava.values[index] ?? 0) / root.maxValue)
|
||||||
|
|
||||||
width: (barRow.width - barRow.spacing * (root.barCount - 1)) / root.barCount
|
width: (barRow.width - barRow.spacing * (root.barCount - 1)) / root.barCount
|
||||||
height: Math.max(Theme.cava.barMinHeight, (bar.value / root.maxValue) * barRow.height)
|
height: Math.max(Theme.cava.barMinHeight, Math.pow(bar.level, 0.6) * barRow.height)
|
||||||
anchors.bottom: parent.bottom
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
radius: bar.width / 2
|
radius: bar.width / 2
|
||||||
color: Theme.colors.accent
|
color: Theme.colors.accent
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
opacity: 0.45 + (bar.level * 0.55)
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 90
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 150
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
modules/bar/GPUWidget.qml
Normal file
23
modules/bar/GPUWidget.qml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import qs.config
|
||||||
|
import qs.ui
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
hoverHighlight: false
|
||||||
|
pointerCursor: false
|
||||||
|
iconSource: GPU.icon
|
||||||
|
|
||||||
|
visible: GPU.connected && GPU.gpu !== null
|
||||||
|
opacity: GPU.gpu !== null && GPU.gpu.known ? 1 : 0
|
||||||
|
|
||||||
|
label: {
|
||||||
|
if (!GPU.gpu)
|
||||||
|
return "NA";
|
||||||
|
return GPU.gpu.blocked ? "Blocked" : "Active";
|
||||||
|
}
|
||||||
|
}
|
||||||
11
modules/bar/NetworkWidget.qml
Normal file
11
modules/bar/NetworkWidget.qml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import qs.ui
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
hoverHighlight: false
|
||||||
|
pointerCursor: false
|
||||||
|
label: Network.statusText
|
||||||
|
iconSource: Network.icon
|
||||||
|
}
|
||||||
@@ -4,11 +4,24 @@ import QtQuick
|
|||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import qs.config
|
import qs.config
|
||||||
|
|
||||||
Row {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int count: 7
|
property int count: 7
|
||||||
|
|
||||||
|
implicitWidth: pillsRow.implicitWidth
|
||||||
|
implicitHeight: Theme.workspaces.height
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.duration
|
||||||
|
easing.type: Easing.OutQuint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: pillsRow
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
spacing: Theme.spacing
|
spacing: Theme.spacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
@@ -21,14 +34,17 @@ Row {
|
|||||||
property var ws: Hyprland.workspaces.values.find(w => w.id === pill.index + 1)
|
property var ws: Hyprland.workspaces.values.find(w => w.id === pill.index + 1)
|
||||||
property bool isActive: Hyprland.focusedWorkspace?.id === (pill.index + 1)
|
property bool isActive: Hyprland.focusedWorkspace?.id === (pill.index + 1)
|
||||||
|
|
||||||
|
z: 1
|
||||||
radius: Theme.workspaces.radius
|
radius: Theme.workspaces.radius
|
||||||
implicitHeight: Theme.workspaces.height
|
implicitHeight: Theme.workspaces.height
|
||||||
implicitWidth: pill.isActive ? pill.implicitHeight * Theme.workspaces.activeWidthRatio : pill.implicitHeight
|
implicitWidth: pill.isActive ? pill.implicitHeight * Theme.workspaces.activeWidthRatio : pill.implicitHeight
|
||||||
color: {
|
color: {
|
||||||
|
if (pill.isActive)
|
||||||
|
return "transparent";
|
||||||
if (handler.hovered)
|
if (handler.hovered)
|
||||||
return Theme.colors.accent;
|
return Theme.colors.accent;
|
||||||
if (pill.isActive || pill.ws)
|
if (pill.ws)
|
||||||
return Theme.colors.accent;
|
return Theme.colors.secondaryForeground;
|
||||||
return Theme.colors.inActive;
|
return Theme.colors.inActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +52,7 @@ Row {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: Hyprland.dispatch("workspace " + (pill.index + 1))
|
onClicked: Hyprland.dispatch("hl.dsp.focus({ workspace = " + (pill.index + 1) + " })")
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
@@ -45,7 +61,7 @@ Row {
|
|||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Theme.duration
|
duration: Theme.duration + 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,3 +74,71 @@ Row {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: highlight
|
||||||
|
|
||||||
|
z: 2
|
||||||
|
radius: Theme.workspaces.radius
|
||||||
|
color: Theme.colors.accent
|
||||||
|
|
||||||
|
readonly property real activeW: Theme.workspaces.height * Theme.workspaces.activeWidthRatio
|
||||||
|
readonly property real inactiveW: Theme.workspaces.height
|
||||||
|
property int curIdx: Hyprland.focusedWorkspace ? Hyprland.focusedWorkspace.id - 1 : -1
|
||||||
|
property int prevIdx: 0
|
||||||
|
|
||||||
|
function getX(index, activeIndex) {
|
||||||
|
if (index < 0)
|
||||||
|
return 0;
|
||||||
|
let x = 0;
|
||||||
|
for (let i = 0; i < index; i++)
|
||||||
|
x += (i === activeIndex ? activeW : inactiveW) + pillsRow.spacing;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
property real targetLeft: curIdx >= 0 ? getX(curIdx, curIdx) : 0
|
||||||
|
property real targetRight: curIdx >= 0 ? targetLeft + activeW : 0
|
||||||
|
property real actualLeft: targetLeft
|
||||||
|
property real actualRight: targetRight
|
||||||
|
|
||||||
|
Behavior on actualLeft {
|
||||||
|
NumberAnimation {
|
||||||
|
id: leftAnim
|
||||||
|
duration: Theme.duration
|
||||||
|
easing.type: Easing.OutQuint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on actualRight {
|
||||||
|
NumberAnimation {
|
||||||
|
id: rightAnim
|
||||||
|
duration: Theme.duration
|
||||||
|
easing.type: Easing.OutQuint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCurIdxChanged: {
|
||||||
|
if (curIdx >= 0 && prevIdx >= 0) {
|
||||||
|
if (curIdx > prevIdx) {
|
||||||
|
leftAnim.duration = 400;
|
||||||
|
rightAnim.duration = 300;
|
||||||
|
} else if (curIdx < prevIdx) {
|
||||||
|
leftAnim.duration = 300;
|
||||||
|
rightAnim.duration = 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (curIdx >= 0)
|
||||||
|
prevIdx = curIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
x: actualLeft
|
||||||
|
width: actualRight - actualLeft
|
||||||
|
height: parent.height
|
||||||
|
opacity: curIdx >= 0 ? 1 : 0
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: 180
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
64
package.nix
64
package.nix
@@ -3,16 +3,19 @@
|
|||||||
stdenv,
|
stdenv,
|
||||||
makeWrapper,
|
makeWrapper,
|
||||||
makeFontsConf,
|
makeFontsConf,
|
||||||
|
cmake,
|
||||||
|
ninja,
|
||||||
quickshellWithModules,
|
quickshellWithModules,
|
||||||
|
qt6,
|
||||||
cava,
|
cava,
|
||||||
wallust,
|
wallust,
|
||||||
nerd-fonts,
|
nerd-fonts,
|
||||||
extraRuntimeDeps ? [],
|
extraRuntimeDeps ? [],
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
|
|
||||||
runtimeDeps = [
|
runtimeDeps =
|
||||||
|
[
|
||||||
cava
|
cava
|
||||||
wallust
|
wallust
|
||||||
]
|
]
|
||||||
@@ -21,41 +24,50 @@ let
|
|||||||
fontconfig = makeFontsConf {
|
fontconfig = makeFontsConf {
|
||||||
fontDirectories = [nerd-fonts.fira-code];
|
fontDirectories = [nerd-fonts.fira-code];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Unlike lib.fileset, this keeps untracked files (the flake only sees
|
||||||
|
# git-tracked paths) while dropping build artifacts.
|
||||||
|
src = lib.cleanSourceWith {
|
||||||
|
src = lib.cleanSource ./.;
|
||||||
|
filter = path: _: let
|
||||||
|
name = baseNameOf path;
|
||||||
|
in
|
||||||
|
!(name == "dist" || name == "result" || name == "result-bin" || lib.hasPrefix "build" name);
|
||||||
|
};
|
||||||
in
|
in
|
||||||
stdenv.mkDerivation {
|
stdenv.mkDerivation {
|
||||||
inherit version;
|
inherit version src;
|
||||||
pname = "tshell";
|
pname = "tshell";
|
||||||
src = lib.fileset.toSource {
|
|
||||||
root = ./.;
|
|
||||||
fileset = lib.fileset.unions [
|
|
||||||
./shell.qml
|
|
||||||
./assets
|
|
||||||
./config
|
|
||||||
./modules
|
|
||||||
./services
|
|
||||||
./ui
|
|
||||||
./windows
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
nativeBuildInputs = [ makeWrapper ];
|
dontWrapQtApps = true;
|
||||||
buildInputs = [ quickshellWithModules ];
|
|
||||||
|
preConfigure = ''
|
||||||
|
cd plugin
|
||||||
|
'';
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
cmake
|
||||||
|
ninja
|
||||||
|
makeWrapper
|
||||||
|
];
|
||||||
|
buildInputs = [
|
||||||
|
qt6.qtbase
|
||||||
|
qt6.qtdeclarative
|
||||||
|
];
|
||||||
propagatedBuildInputs = runtimeDeps;
|
propagatedBuildInputs = runtimeDeps;
|
||||||
|
|
||||||
dontBuild = true;
|
postInstall = ''
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
|
|
||||||
mkdir -p $out/bin $out/share/tshell
|
mkdir -p $out/bin $out/share/tshell
|
||||||
cp -r . $out/share/tshell
|
|
||||||
|
for entry in shell.qml assets config modules services ui windows; do
|
||||||
|
cp -r "$src/$entry" $out/share/tshell/
|
||||||
|
done
|
||||||
|
|
||||||
makeWrapper ${quickshellWithModules}/bin/qs $out/bin/tshell \
|
makeWrapper ${quickshellWithModules}/bin/qs $out/bin/tshell \
|
||||||
--prefix PATH : "${lib.makeBinPath runtimeDeps}" \
|
--prefix PATH : "${lib.makeBinPath runtimeDeps}" \
|
||||||
--set FONTCONFIG_FILE "${fontconfig}" \
|
--set FONTCONFIG_FILE "${fontconfig}" \
|
||||||
|
--suffix NIXPKGS_QT6_QML_IMPORT_PATH : "$out/lib/qt-6/qml" \
|
||||||
--add-flags "-p $out/share/tshell"
|
--add-flags "-p $out/share/tshell"
|
||||||
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
|
|||||||
46
plugin/CMakeLists.txt
Normal file
46
plugin/CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(tshell-cardwire LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Core Qml DBus)
|
||||||
|
|
||||||
|
qt_add_library(tshellcardwire SHARED src/cardwire.cpp src/cardwire.hpp)
|
||||||
|
target_include_directories(tshellcardwire PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||||
|
|
||||||
|
qt_add_qml_module(tshellcardwire
|
||||||
|
URI Tshell.Cardwire
|
||||||
|
VERSION 0.1
|
||||||
|
DEPENDENCIES QtQml
|
||||||
|
OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/Tshell/Cardwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(tshellcardwire PRIVATE Qt6::Core Qt6::Qml Qt6::DBus)
|
||||||
|
|
||||||
|
# Keep the backing library next to the plugin so "$ORIGIN" rpaths resolve both
|
||||||
|
# in the build tree and after install.
|
||||||
|
set(module_build_dir "${CMAKE_CURRENT_BINARY_DIR}/Tshell/Cardwire")
|
||||||
|
set_target_properties(tshellcardwire tshellcardwireplugin PROPERTIES
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${module_build_dir}"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${module_build_dir}"
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${module_build_dir}"
|
||||||
|
BUILD_RPATH "$ORIGIN"
|
||||||
|
INSTALL_RPATH "$ORIGIN"
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_query_qml_module(tshellcardwire
|
||||||
|
PLUGIN_TARGET module_plugin_target
|
||||||
|
TARGET_PATH module_target_path
|
||||||
|
QMLDIR module_qmldir
|
||||||
|
TYPEINFO module_typeinfo
|
||||||
|
)
|
||||||
|
|
||||||
|
set(module_dir "${CMAKE_INSTALL_PREFIX}/lib/qt-6/qml/${module_target_path}")
|
||||||
|
|
||||||
|
install(TARGETS tshellcardwire "${module_plugin_target}"
|
||||||
|
LIBRARY DESTINATION "${module_dir}"
|
||||||
|
RUNTIME DESTINATION "${module_dir}"
|
||||||
|
)
|
||||||
|
install(FILES "${module_qmldir}" "${module_typeinfo}" DESTINATION "${module_dir}")
|
||||||
405
plugin/src/cardwire.cpp
Normal file
405
plugin/src/cardwire.cpp
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
#include "cardwire.hpp"
|
||||||
|
|
||||||
|
#include <qdbusargument.h>
|
||||||
|
#include <qdbusconnection.h>
|
||||||
|
#include <qdbusextratypes.h>
|
||||||
|
#include <qdbusmessage.h>
|
||||||
|
#include <qdbusmetatype.h>
|
||||||
|
#include <qdbuspendingcall.h>
|
||||||
|
#include <qdbusservicewatcher.h>
|
||||||
|
#include <qloggingcategory.h>
|
||||||
|
#include <qqmlengine.h>
|
||||||
|
#include <qvariant.h>
|
||||||
|
|
||||||
|
// a{oa{sa{sv}}} as returned by GetManagedObjects
|
||||||
|
using InterfacesMap = QMap<QString, QVariantMap>;
|
||||||
|
using ManagedObjects = QMap<QDBusObjectPath, InterfacesMap>;
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(InterfacesMap)
|
||||||
|
Q_DECLARE_METATYPE(ManagedObjects)
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(logCardwire, "tshell.cardwire", QtWarningMsg);
|
||||||
|
|
||||||
|
const QString SERVICE = "org.opengamingcollective.cardwire";
|
||||||
|
const QString ROOT_PATH = "/org/opengamingcollective/cardwire";
|
||||||
|
const QString GPU_PREFIX = "/org/opengamingcollective/cardwire/Gpu/";
|
||||||
|
const QString GPU_IFACE = "org.opengamingcollective.cardwire.Gpu";
|
||||||
|
const QString PROPS_IFACE = "org.freedesktop.DBus.Properties";
|
||||||
|
const QString OBJMGR_IFACE = "org.freedesktop.DBus.ObjectManager";
|
||||||
|
|
||||||
|
void registerMetaTypes() {
|
||||||
|
qDBusRegisterMetaType<InterfacesMap>();
|
||||||
|
qDBusRegisterMetaType<ManagedObjects>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusMessage methodCall(const QString &path, const QString &iface,
|
||||||
|
const QString &member) {
|
||||||
|
return QDBusMessage::createMethodCall(SERVICE, path, iface, member);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusPendingCallWatcher *asyncCall(QObject *parent,
|
||||||
|
const QDBusMessage &message) {
|
||||||
|
return new QDBusPendingCallWatcher(
|
||||||
|
QDBusConnection::systemBus().asyncCall(message), parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void logError(const QString &what, const QDBusMessage &reply) {
|
||||||
|
if (reply.type() == QDBusMessage::ErrorMessage) {
|
||||||
|
qCWarning(logCardwire).nospace()
|
||||||
|
<< what << ": " << reply.errorName() << " " << reply.errorMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CardwireGpu::CardwireGpu(QString objectPath, QObject *parent)
|
||||||
|
: QObject(parent), mObjectPath(std::move(objectPath)) {
|
||||||
|
auto bus = QDBusConnection::systemBus();
|
||||||
|
bus.connect(SERVICE, this->mObjectPath, PROPS_IFACE, "PropertiesChanged",
|
||||||
|
this, SLOT(onPropertiesChanged(QDBusMessage)));
|
||||||
|
bus.connect(SERVICE, this->mObjectPath, GPU_IFACE, "PowerStateChanged", this,
|
||||||
|
SLOT(onPowerStateChanged(QString)));
|
||||||
|
|
||||||
|
this->refreshDevice();
|
||||||
|
this->refreshProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::refreshDevice() {
|
||||||
|
QObject::connect(
|
||||||
|
asyncCall(this, methodCall(this->mObjectPath, GPU_IFACE, "GetDevice")),
|
||||||
|
&QDBusPendingCallWatcher::finished, this,
|
||||||
|
&CardwireGpu::onGetDeviceFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::onGetDeviceFinished() {
|
||||||
|
auto *watcher = qobject_cast<QDBusPendingCallWatcher *>(this->sender());
|
||||||
|
if (watcher == nullptr)
|
||||||
|
return;
|
||||||
|
watcher->deleteLater();
|
||||||
|
|
||||||
|
const QDBusMessage reply = watcher->reply();
|
||||||
|
logError("GetDevice failed for " + this->mObjectPath, reply);
|
||||||
|
if (reply.type() == QDBusMessage::ErrorMessage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// (ssuubbbbssbs): name pci render card default discrete virtual available
|
||||||
|
// vendor driver nvidia nvidia_minor
|
||||||
|
QString pci;
|
||||||
|
QString vendor;
|
||||||
|
QString driver;
|
||||||
|
QString nvidiaMinor;
|
||||||
|
uint render = 0;
|
||||||
|
uint card = 0;
|
||||||
|
bool virtualGpu = false;
|
||||||
|
|
||||||
|
const auto arguments = reply.arguments();
|
||||||
|
if (arguments.first().userType() != qMetaTypeId<QDBusArgument>()) {
|
||||||
|
// The daemon actually sends the fields as separate out arguments.
|
||||||
|
if (arguments.size() < 12) {
|
||||||
|
qCWarning(logCardwire)
|
||||||
|
<< "GetDevice returned" << arguments.size() << "arguments";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->mName = arguments.at(0).toString();
|
||||||
|
pci = arguments.at(1).toString();
|
||||||
|
render = arguments.at(2).toUInt();
|
||||||
|
card = arguments.at(3).toUInt();
|
||||||
|
this->mDefault = arguments.at(4).toBool();
|
||||||
|
this->mDiscrete = arguments.at(5).toBool();
|
||||||
|
virtualGpu = arguments.at(6).toBool();
|
||||||
|
this->mAvailable = arguments.at(7).toBool();
|
||||||
|
vendor = arguments.at(8).toString();
|
||||||
|
driver = arguments.at(9).toString();
|
||||||
|
this->mNvidia = arguments.at(10).toBool();
|
||||||
|
nvidiaMinor = arguments.at(11).toString();
|
||||||
|
} else {
|
||||||
|
// Documented shape: all fields packed into a single struct.
|
||||||
|
// Must be const: demarshalling uses the const (read) overloads.
|
||||||
|
const QDBusArgument arg = arguments.first().value<QDBusArgument>();
|
||||||
|
arg.beginStructure();
|
||||||
|
arg >> this->mName >> pci >> render >> card >> this->mDefault >>
|
||||||
|
this->mDiscrete >> virtualGpu >> this->mAvailable >> vendor >> driver >>
|
||||||
|
this->mNvidia >> nvidiaMinor;
|
||||||
|
arg.endStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->mKnown) {
|
||||||
|
this->mKnown = true;
|
||||||
|
qCInfo(logCardwire).nospace() << "found gpu \"" << this->mName << "\" at "
|
||||||
|
<< this->mObjectPath << " (" << pci << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->deviceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::refreshProperties() {
|
||||||
|
QDBusMessage call = methodCall(this->mObjectPath, PROPS_IFACE, "GetAll");
|
||||||
|
call << GPU_IFACE;
|
||||||
|
|
||||||
|
QObject::connect(asyncCall(this, call), &QDBusPendingCallWatcher::finished,
|
||||||
|
this, &CardwireGpu::onGetAllFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::onGetAllFinished() {
|
||||||
|
auto *watcher = qobject_cast<QDBusPendingCallWatcher *>(this->sender());
|
||||||
|
if (watcher == nullptr)
|
||||||
|
return;
|
||||||
|
watcher->deleteLater();
|
||||||
|
|
||||||
|
const QDBusMessage reply = watcher->reply();
|
||||||
|
logError("GetAll failed for " + this->mObjectPath, reply);
|
||||||
|
if (reply.type() == QDBusMessage::ErrorMessage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto properties = qdbus_cast<QVariantMap>(reply.arguments().at(0));
|
||||||
|
for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) {
|
||||||
|
if (it.key() == "Block")
|
||||||
|
this->setBlockedValue(it.value().toBool());
|
||||||
|
else if (it.key() == "Launchable")
|
||||||
|
this->setLaunchableValue(it.value().toBool());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::setBlocked(bool blocked) {
|
||||||
|
QDBusMessage call = methodCall(this->mObjectPath, PROPS_IFACE, "Set");
|
||||||
|
call << GPU_IFACE << QStringLiteral("Block")
|
||||||
|
<< QVariant::fromValue(QDBusVariant(blocked));
|
||||||
|
|
||||||
|
// The resulting state is confirmed asynchronously via PropertiesChanged.
|
||||||
|
asyncCall(this, call);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::onPropertiesChanged(const QDBusMessage &message) {
|
||||||
|
const auto arguments = message.arguments();
|
||||||
|
if (arguments.isEmpty() || arguments.first().toString() != GPU_IFACE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (arguments.size() > 1) {
|
||||||
|
const auto properties = qdbus_cast<QVariantMap>(arguments.at(1));
|
||||||
|
for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) {
|
||||||
|
if (it.key() == "Block")
|
||||||
|
this->setBlockedValue(it.value().toBool());
|
||||||
|
else if (it.key() == "Launchable")
|
||||||
|
this->setLaunchableValue(it.value().toBool());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.size() > 2) { // invalidated properties: refetch everything
|
||||||
|
const auto invalidated = qdbus_cast<QStringList>(arguments.at(2));
|
||||||
|
if (invalidated.contains("Block") || invalidated.contains("Launchable")) {
|
||||||
|
this->refreshProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::onPowerStateChanged(const QString &state) {
|
||||||
|
emit this->powerStateChanged(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::setBlockedValue(bool blocked) {
|
||||||
|
if (blocked == this->mBlocked)
|
||||||
|
return;
|
||||||
|
this->mBlocked = blocked;
|
||||||
|
qCDebug(logCardwire).nospace()
|
||||||
|
<< this->mObjectPath << ": block = " << blocked;
|
||||||
|
emit this->blockedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CardwireGpu::setLaunchableValue(bool launchable) {
|
||||||
|
if (launchable == this->mLaunchable)
|
||||||
|
return;
|
||||||
|
this->mLaunchable = launchable;
|
||||||
|
emit this->launchableChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cardwire::Cardwire(QObject *parent) : QObject(parent), mWatcher(this) {}
|
||||||
|
|
||||||
|
Cardwire *Cardwire::instance() {
|
||||||
|
static Cardwire *instance =
|
||||||
|
nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
if (instance == nullptr) {
|
||||||
|
registerMetaTypes();
|
||||||
|
instance = new Cardwire();
|
||||||
|
instance->connectSignals();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cardwire *Cardwire::create(QQmlEngine * /*qmlEngine*/,
|
||||||
|
QJSEngine * /*jsEngine*/) {
|
||||||
|
auto *instance = Cardwire::instance();
|
||||||
|
QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
CardwireGpu *Cardwire::nvidiaGpu() const {
|
||||||
|
for (auto *gpu : this->mGpus) {
|
||||||
|
if (gpu->isNvidia())
|
||||||
|
return gpu;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::connectSignals() {
|
||||||
|
// cardwired runs as a system service, so its API is on the system bus.
|
||||||
|
this->mWatcher.setConnection(QDBusConnection::systemBus());
|
||||||
|
QObject::connect(&this->mWatcher, &QDBusServiceWatcher::serviceOwnerChanged,
|
||||||
|
this, &Cardwire::onServiceOwnerChanged);
|
||||||
|
this->mWatcher.setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
|
||||||
|
|
||||||
|
auto bus = QDBusConnection::systemBus();
|
||||||
|
bus.connect(SERVICE, ROOT_PATH, OBJMGR_IFACE, "InterfacesAdded", this,
|
||||||
|
SLOT(onInterfacesAdded(QDBusMessage)));
|
||||||
|
bus.connect(SERVICE, ROOT_PATH, OBJMGR_IFACE, "InterfacesRemoved", this,
|
||||||
|
SLOT(onInterfacesRemoved(QDBusMessage)));
|
||||||
|
|
||||||
|
// Kick off the initial sync; if the daemon is not running yet the service
|
||||||
|
// watcher triggers another sync once it appears.
|
||||||
|
this->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::onServiceOwnerChanged(const QString &serviceName,
|
||||||
|
const QString &oldOwner,
|
||||||
|
const QString &newOwner) {
|
||||||
|
if (serviceName != SERVICE)
|
||||||
|
return;
|
||||||
|
qCInfo(logCardwire).nospace()
|
||||||
|
<< SERVICE << " owner: " << oldOwner << " -> " << newOwner;
|
||||||
|
|
||||||
|
this->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::sync() {
|
||||||
|
const auto generation = ++this->mGeneration;
|
||||||
|
auto *watcher =
|
||||||
|
asyncCall(this, methodCall(ROOT_PATH, OBJMGR_IFACE, "GetManagedObjects"));
|
||||||
|
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
||||||
|
[this, generation]() { this->handleSyncReply(generation); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::handleSyncReply(quint32 generation) {
|
||||||
|
auto *watcher = qobject_cast<QDBusPendingCallWatcher *>(this->sender());
|
||||||
|
if (watcher == nullptr)
|
||||||
|
return;
|
||||||
|
watcher->deleteLater();
|
||||||
|
|
||||||
|
if (generation != this->mGeneration)
|
||||||
|
return; // a newer sync superseded this one
|
||||||
|
|
||||||
|
const QDBusMessage reply = watcher->reply();
|
||||||
|
logError("GetManagedObjects failed", reply);
|
||||||
|
if (reply.type() == QDBusMessage::ErrorMessage) {
|
||||||
|
if (this->mConnected) {
|
||||||
|
this->mConnected = false;
|
||||||
|
emit this->connectedChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto *gpu : this->mGpus)
|
||||||
|
gpu->deleteLater();
|
||||||
|
if (!this->mGpus.isEmpty()) {
|
||||||
|
this->mGpus.clear();
|
||||||
|
emit this->gpusChanged();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<CardwireGpu *> seen;
|
||||||
|
const auto objects = qdbus_cast<ManagedObjects>(reply.arguments().at(0));
|
||||||
|
for (auto it = objects.constBegin(); it != objects.constEnd(); ++it) {
|
||||||
|
if (!it.value().contains(GPU_IFACE))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const QString &path = it.key().path();
|
||||||
|
if (!path.startsWith(GPU_PREFIX))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this->ensureGpu(path);
|
||||||
|
seen.append(this->findGpu(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove GPUs that disappeared (hotplug).
|
||||||
|
for (auto *gpu : this->mGpus) {
|
||||||
|
if (!seen.contains(gpu))
|
||||||
|
gpu->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seen != this->mGpus) {
|
||||||
|
this->mGpus = seen;
|
||||||
|
emit this->gpusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->mConnected) {
|
||||||
|
this->mConnected = true;
|
||||||
|
qCInfo(logCardwire) << "connected to" << SERVICE;
|
||||||
|
emit this->connectedChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::ensureGpu(const QString &path) {
|
||||||
|
if (this->findGpu(path) != nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto *gpu = new CardwireGpu(path, this);
|
||||||
|
// The nvidia flag only becomes known once GetDevice replies.
|
||||||
|
QObject::connect(gpu, &CardwireGpu::deviceChanged, this,
|
||||||
|
&Cardwire::updateNvidiaGpu);
|
||||||
|
this->mGpus.append(gpu);
|
||||||
|
qCInfo(logCardwire) << "tracking" << path;
|
||||||
|
emit this->gpusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
CardwireGpu *Cardwire::findGpu(const QString &path) const {
|
||||||
|
for (auto *gpu : this->mGpus) {
|
||||||
|
if (gpu->objectPath() == path)
|
||||||
|
return gpu;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::removeGpu(const QString &path) {
|
||||||
|
auto *gpu = this->findGpu(path);
|
||||||
|
if (gpu == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gpu->deleteLater();
|
||||||
|
this->mGpus.removeOne(gpu);
|
||||||
|
qCInfo(logCardwire) << "stopped tracking" << path;
|
||||||
|
emit this->gpusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::onInterfacesAdded(const QDBusMessage &message) {
|
||||||
|
const auto arguments = message.arguments();
|
||||||
|
if (arguments.size() < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto path = arguments.first().toString();
|
||||||
|
if (!path.startsWith(GPU_PREFIX))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (qdbus_cast<InterfacesMap>(arguments.at(1)).contains(GPU_IFACE))
|
||||||
|
this->ensureGpu(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::onInterfacesRemoved(const QDBusMessage &message) {
|
||||||
|
const auto arguments = message.arguments();
|
||||||
|
if (arguments.size() < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto path = arguments.first().toString();
|
||||||
|
if (!path.startsWith(GPU_PREFIX))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (qdbus_cast<QStringList>(arguments.at(1)).contains(GPU_IFACE))
|
||||||
|
this->removeGpu(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cardwire::updateNvidiaGpu() {
|
||||||
|
auto *current = this->nvidiaGpu();
|
||||||
|
if (current == this->mCurrentNvidiaGpu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->mCurrentNvidiaGpu = current;
|
||||||
|
emit this->nvidiaGpuChanged();
|
||||||
|
}
|
||||||
129
plugin/src/cardwire.hpp
Normal file
129
plugin/src/cardwire.hpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qdbusmessage.h>
|
||||||
|
#include <qdbusservicewatcher.h>
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qstring.h>
|
||||||
|
|
||||||
|
class QDBusPendingCallWatcher;
|
||||||
|
class QJSEngine;
|
||||||
|
class QQmlEngine;
|
||||||
|
|
||||||
|
/// A single GPU exposed by cardwire at
|
||||||
|
/// /org/opengamingcollective/cardwire/Gpu/{id}.
|
||||||
|
///
|
||||||
|
/// Mirrors org.opengamingcollective.cardwire.Gpu: device details are fetched
|
||||||
|
/// once via GetDevice, while Block/Launchable are kept in sync through
|
||||||
|
/// org.freedesktop.DBus.Properties.PropertiesChanged.
|
||||||
|
class CardwireGpu : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("Gpu objects are managed by the Cardwire singleton")
|
||||||
|
|
||||||
|
Q_PROPERTY(QString objectPath READ objectPath CONSTANT)
|
||||||
|
Q_PROPERTY(bool known READ known NOTIFY deviceChanged)
|
||||||
|
Q_PROPERTY(QString name READ name NOTIFY deviceChanged)
|
||||||
|
Q_PROPERTY(bool nvidia READ isNvidia NOTIFY deviceChanged)
|
||||||
|
Q_PROPERTY(bool defaultGpu READ isDefault NOTIFY deviceChanged)
|
||||||
|
Q_PROPERTY(bool discrete READ isDiscrete NOTIFY deviceChanged)
|
||||||
|
Q_PROPERTY(bool available READ isAvailable NOTIFY deviceChanged)
|
||||||
|
Q_PROPERTY(bool blocked READ isBlocked NOTIFY blockedChanged)
|
||||||
|
Q_PROPERTY(bool launchable READ isLaunchable NOTIFY launchableChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CardwireGpu(QString objectPath, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] const QString &objectPath() const { return this->mObjectPath; }
|
||||||
|
[[nodiscard]] bool known() const { return this->mKnown; }
|
||||||
|
[[nodiscard]] const QString &name() const { return this->mName; }
|
||||||
|
[[nodiscard]] bool isNvidia() const { return this->mNvidia; }
|
||||||
|
[[nodiscard]] bool isDefault() const { return this->mDefault; }
|
||||||
|
[[nodiscard]] bool isDiscrete() const { return this->mDiscrete; }
|
||||||
|
[[nodiscard]] bool isAvailable() const { return this->mAvailable; }
|
||||||
|
[[nodiscard]] bool isBlocked() const { return this->mBlocked; }
|
||||||
|
[[nodiscard]] bool isLaunchable() const { return this->mLaunchable; }
|
||||||
|
|
||||||
|
// Writes the Block property. Only succeeds when the daemon mode allows it
|
||||||
|
// (the daemon rejects the write otherwise and the state stays unchanged).
|
||||||
|
Q_INVOKABLE void setBlocked(bool blocked);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void deviceChanged();
|
||||||
|
void blockedChanged();
|
||||||
|
void launchableChanged();
|
||||||
|
void powerStateChanged(const QString &state);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onPropertiesChanged(const QDBusMessage &message);
|
||||||
|
void onPowerStateChanged(const QString &state);
|
||||||
|
void onGetDeviceFinished();
|
||||||
|
void onGetAllFinished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void refreshDevice();
|
||||||
|
void refreshProperties();
|
||||||
|
void setBlockedValue(bool blocked);
|
||||||
|
void setLaunchableValue(bool launchable);
|
||||||
|
|
||||||
|
QString mObjectPath;
|
||||||
|
bool mKnown = false;
|
||||||
|
QString mName;
|
||||||
|
bool mNvidia = false;
|
||||||
|
bool mDefault = false;
|
||||||
|
bool mDiscrete = false;
|
||||||
|
bool mAvailable = false;
|
||||||
|
bool mBlocked = false;
|
||||||
|
bool mLaunchable = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Exposes the GPUs tracked by the cardwire daemon over D-Bus.
|
||||||
|
class Cardwire : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_SINGLETON
|
||||||
|
|
||||||
|
Q_PROPERTY(bool connected READ isConnected NOTIFY connectedChanged)
|
||||||
|
Q_PROPERTY(QList<CardwireGpu *> gpus READ gpus NOTIFY gpusChanged)
|
||||||
|
Q_PROPERTY(CardwireGpu *nvidiaGpu READ nvidiaGpu NOTIFY nvidiaGpuChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
static Cardwire *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
|
||||||
|
static Cardwire *instance();
|
||||||
|
|
||||||
|
[[nodiscard]] bool isConnected() const { return this->mConnected; }
|
||||||
|
[[nodiscard]] QList<CardwireGpu *> gpus() const { return this->mGpus; }
|
||||||
|
|
||||||
|
/// The first NVIDIA GPU found by the daemon, or nullptr if there is none.
|
||||||
|
[[nodiscard]] CardwireGpu *nvidiaGpu() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void connectedChanged();
|
||||||
|
void gpusChanged();
|
||||||
|
void nvidiaGpuChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onServiceOwnerChanged(const QString &serviceName,
|
||||||
|
const QString &oldOwner, const QString &newOwner);
|
||||||
|
void onInterfacesAdded(const QDBusMessage &message);
|
||||||
|
void onInterfacesRemoved(const QDBusMessage &message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Cardwire(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void connectSignals();
|
||||||
|
void sync();
|
||||||
|
void handleSyncReply(quint32 generation);
|
||||||
|
void ensureGpu(const QString &path);
|
||||||
|
[[nodiscard]] CardwireGpu *findGpu(const QString &path) const;
|
||||||
|
void removeGpu(const QString &path);
|
||||||
|
void updateNvidiaGpu();
|
||||||
|
|
||||||
|
QDBusServiceWatcher mWatcher;
|
||||||
|
QList<CardwireGpu *> mGpus;
|
||||||
|
CardwireGpu *mCurrentNvidiaGpu = nullptr;
|
||||||
|
bool mConnected = false;
|
||||||
|
quint32 mGeneration = 0;
|
||||||
|
};
|
||||||
12
services/GPU.qml
Normal file
12
services/GPU.qml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Tshell.Cardwire
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property bool connected: Cardwire.connected
|
||||||
|
readonly property CardwireGpu gpu: Cardwire.nvidiaGpu
|
||||||
|
readonly property url icon: `${Quickshell.shellPath("assets")}/icons/nvidia.svg`
|
||||||
|
}
|
||||||
81
services/Network.qml
Normal file
81
services/Network.qml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Networking
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property url icon: `${Quickshell.shellPath("assets")}/icons/network.svg`
|
||||||
|
|
||||||
|
property var wifiDevice: {
|
||||||
|
for (const device of Networking.devices.values) {
|
||||||
|
if (device.type === DeviceType.Wifi)
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
property var wiredDevice: {
|
||||||
|
for (const device of Networking.devices.values) {
|
||||||
|
if (device.type === DeviceType.Wired)
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool ethernetConnected: !!(root.wiredDevice && root.wiredDevice.connected)
|
||||||
|
|
||||||
|
readonly property bool wifiConnected: !!(root.wifiDevice && root.wifiDevice.connected)
|
||||||
|
|
||||||
|
readonly property bool wifiConnecting: !!(root.wifiDevice && root.wifiDevice.state === ConnectionState.Connecting)
|
||||||
|
|
||||||
|
property var activeNetwork: null
|
||||||
|
|
||||||
|
function refreshActiveNetwork() {
|
||||||
|
root.activeNetwork = null;
|
||||||
|
|
||||||
|
if (!root.wifiDevice || !root.wifiDevice.networks)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const network of root.wifiDevice.networks.values) {
|
||||||
|
if (network.connected) {
|
||||||
|
root.activeNetwork = network;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property string statusText: {
|
||||||
|
if (root.ethernetConnected)
|
||||||
|
return "Wired";
|
||||||
|
|
||||||
|
if (root.wifiConnecting)
|
||||||
|
return "Connecting";
|
||||||
|
|
||||||
|
if (root.wifiConnected && root.activeNetwork)
|
||||||
|
return root.activeNetwork.name;
|
||||||
|
|
||||||
|
return "NA";
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.wifiDevice ? root.wifiDevice.networks : null
|
||||||
|
|
||||||
|
function onValuesChanged() {
|
||||||
|
root.refreshActiveNetwork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 1000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
root.refreshActiveNetwork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user