commit b0ed0d17e5fd64c903f6bad6c3a16f60f8fffb24 Author: scorpion-26 <58082714+scorpion-26@users.noreply.github.com> Date: Fri Jan 13 16:13:56 2023 +0100 Initial commit! diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..bae3e34 --- /dev/null +++ b/.clang-format @@ -0,0 +1,36 @@ +BasedOnStyle: Microsoft + +AlignAfterOpenBracket: Align +AlignArrayOfStructures: Left +AlignEscapedNewlines: Left +AlignOperands: Align +PenaltyBreakAssignment: 100 +PenaltyBreakBeforeFirstCallParameter: 20 +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: Yes +SpaceAfterTemplateKeyword: false +Cpp11BracedListStyle: true +IncludeBlocks: Preserve +SortIncludes: false +IndentAccessModifiers: false +AccessModifierOffset: -4 # Stop indenting my access modifiers wierdly! I want them the same level as the class god damn! +NamespaceIndentation: All +FixNamespaceComments: false +PointerAlignment: Left +ColumnLimit: 150 +KeepEmptyLinesAtTheStartOfBlocks: false +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterCaseLabel: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + BeforeLambdaBody: true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..284f50d --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2023 scorpion_26 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..84457bd --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# gBar +My personal blazingly fast and efficient status bar + widgets, in case anyone finds a use for it. + +## Prerequisites +*If you don't have the optional dependencies, some features are not available and you must manually disable them.* +- wayland +- Hyprland(Optional -> For workspaces widget) +- nvidia-utils(Optional -> For GPU status) +- bluez(Optional -> For Bluetooth status) +- GTK 3.0 +- gtk-layer-shell +- PulseAudio server (PipeWire works too!) +- meson, gcc/clang, ninja + +## Building and installation +1. Configure with meson + *All optional dependencies enabled* + ``` + meson build --buildtype=release + ``` + *All optional dependencies are disabled* + ``` + meson build --buildtype=release -DHasHyprland=false -DHasNvidia=false -DHasBlueZ=false + ``` +4. Build and install + ``` + ninja -C build && sudo ninja -C build install + ``` +5. Copy css styling into your config directory($XDG_CONFIG_HOME). This will most likely be ~/.config + ``` + mkdir ~/.config/gBar && cp css/* ~/.config/gBar/ + ``` + +## Running gBar +*Open bar on monitor 0* +``` +gBar bar 0 +``` +*Open audio flyin (either on current monitor or on the specified monitor)* +``` +gBar audio [monitor] +``` + +## Gallery +![The bar with default css](/assets/bar.png) +*Bar with default css* +![The audio flyin with default css](/assets/audioflyin.png) +*Audio widget with default css* + +## FAQ +### There are already many GTK bars out there, why not use them? +- Waybar: +Great performance, though limited styling(Almost no dynamic sliders, revealers, ...) and (at least for me) buggy css. +- eww: +Really solid project with many great customization options. There is one problem though: Performance +Due to the way eww configuration is set up, for each dynamic variable (the number of them quickly grows) you need a shell command which opens a process. +This became quickly a bottleneck, where the bar took up 10% of the CPU-time due to the creation of many processes all the time (without even considering the workspace widget). +gBar implements all of the information gathering(CPU, RAM, GPU, Disk, ...) in native C++ code, which is WAY faster. In fact, gBar was meant to be a fast replacement/alternative for eww for me. + +And lastly: Implementing it myself is fun and a great excuse to learn something new! + +### What scheme are you using? +The colors are from the Dracula theme: https://draculatheme.com + +### I want to customize the colors +If you have SASS installed: Edit ~/.config/gBar/style.scss and regenerate style.css with it +Else: Edit ~/.config/gBar/style.css directly! + +### I want to modify the widgets behaviour/Add my own widgets +Unfortunately, you need to implement it yourself in C++. For inspiration look into src/Bar.cpp or src/AudioFlyin.cpp, or open an issue(Maybe I'll implement it for you). + +### The Audio widget doesn't open +Delete /tmp/gBar__audio. +This happens, when you kill the widget before it closes automatically after a few seconds. + +### CPU Temperature is wrong/Lock doesn't work +*This is caused by the way my system and/or Linux is setup.* +Temperature: Edit the variable ```tempFilePath``` in ```src/System.cpp``` to the correct thermal zone file and recompile. The one for my system is *very* likely wrong. +Lock: There is no generic way to lock a system. So please, implement it to suit your needs (Replace XXX by a shell command in ```src/System.cpp```) + +### The icons are not showing! +Please install a Nerd Font from https://www.nerdfonts.com (I use Caskaydia Cove NF), and change style.css/style.scss accordingly (Refer to 'I want to customize the colors' for that) + diff --git a/assets/audioflyin.png b/assets/audioflyin.png new file mode 100644 index 0000000..f8efaba Binary files /dev/null and b/assets/audioflyin.png differ diff --git a/assets/bar.png b/assets/bar.png new file mode 100644 index 0000000..7dbaac7 Binary files /dev/null and b/assets/bar.png differ diff --git a/css/dracula/LICENSE b/css/dracula/LICENSE new file mode 100644 index 0000000..b76b064 --- /dev/null +++ b/css/dracula/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Dracula Theme + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..5ef81b1 --- /dev/null +++ b/css/style.css @@ -0,0 +1,185 @@ +* { + all: unset; + font-family: "CaskaydiaCove Nerd Font"; +} + +.bar { + background-color: #282a36; + border-radius: 16px; +} + +.right { + margin-right: 10px; + border-radius: 16px; +} + +.time-text { + font-size: 16px; +} + +.reboot-button { + font-size: 28px; + margin-right: 6px; + color: #6272a4; +} + +.sleep-button { + font-size: 28px; + color: #6272a4; +} + +.exit-button { + font-size: 28px; + color: #6272a4; +} + +.power-button { + font-size: 28px; + color: #ff5555; +} + +.power-box-expand { + margin-right: 6px; +} + +.audio-icon { + font-size: 24px; + color: #ffb86c; + margin-right: 12px; +} + +.bt-num { + font-size: 16px; + color: #1793D1; + margin-left: -6px; +} + +.bt-label-on { + font-size: 20px; + color: #1793D1; + margin-right: 6px; +} + +.bt-label-off { + font-size: 24px; + color: #1793D1; + margin-right: 6px; +} + +.bt-label-connected { + font-size: 28px; + color: #1793D1; + margin-right: 6px; +} + +.disk-util-progress { + color: #bd93f9; + background-color: #44475a; + font-size: 16px; +} + +.disk-data-text { + color: #bd93f9; + margin-right: 6px; + font-size: 16px; +} + +.vram-util-progress { + color: #ffb86c; + background-color: #44475a; +} + +.vram-data-text { + color: #ffb86c; + margin-right: 6px; + font-size: 16px; +} + +.ram-util-progress { + color: #f1fa8c; + background-color: #44475a; +} + +.ram-data-text { + color: #f1fa8c; + margin-right: 6px; + font-size: 16px; +} + +.gpu-util-progress { + color: #8be9fd; + background-color: #44475a; +} + +.gpu-data-text { + color: #8be9fd; + margin-right: 6px; + font-size: 16px; +} + +.cpu-util-progress { + color: #50fa7b; + background-color: #44475a; + font-size: 16px; +} + +.cpu-data-text { + color: #50fa7b; + margin-right: 6px; + font-size: 16px; +} + +.ws-dead { + color: #44475a; + font-size: 16px; +} + +.ws-inactive { + color: #6272a4; + font-size: 16px; +} + +.ws-visible { + color: #8be9fd; + font-size: 16px; +} + +.ws-current { + color: #f1fa8c; + font-size: 16px; +} + +.ws-active { + color: #50fa7b; + font-size: 16px; +} + +trough { + border-radius: 3px; + border-width: 1px; + border-style: none; + background-color: #44475a; + margin-top: 2px; + min-width: 4px; + min-height: 4px; +} + +slider { + border-radius: 0%; + border-width: 1px; + border-style: none; + margin: -9px -9px -9px -9px; + min-width: 16px; + min-height: 16px; + background-color: transparent; +} + +highlight { + border-radius: 3px; + border-width: 1px; + border-style: none; + min-height: 6px; + background-color: #ffb86c; +} + +/*# sourceMappingURL=style.css.map */ diff --git a/css/style.css.map b/css/style.css.map new file mode 100644 index 0000000..978ea4c --- /dev/null +++ b/css/style.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAkBA;EACI;EACA;;;AAQJ;EACI,kBA5BC;EA6BD;;;AAGJ;EACI;EACA;;;AAGJ;EACI,WAvBO;;;AA0BX;EACI;EAEA;EAEA,OA3CO;;;AA6CX;EACI;EAGA,OAjDO;;;AAmDX;EACI;EAGA,OAvDO;;;AA0DX;EACI;EAGA,OAxDE;;;AA8DN;EACI;;;AAGJ;EACI;EACA,OAvEK;EAwEL;;;AAGJ;EACI,WAnEO;EAoEP,OAtEK;EAuEL;;;AAEJ;EACI;EACA,OA3EK;EA4EL;;;AAEJ;EACI;EACA,OAhFK;EAiFL;;;AAEJ;EACI;EACA,OArFK;EAsFL;;;AAGJ;EACI,OA/FK;EAgGL,kBAtGO;EAuGP,WA1FO;;;AA4FX;EACI,OApGK;EAqGL;EACA,WA/FO;;;AAkGX;EACI,OA5GK;EA6GL,kBAjHO;;;AAmHX;EACI,OAhHK;EAiHL;EACA,WAzGO;;;AA4GX;EACI,OAlHK;EAmHL,kBA3HO;;;AA6HX;EACI,OAtHK;EAuHL;EACA,WAnHO;;;AAsHX;EACI,OAlIG;EAmIH,kBArIO;;;AAuIX;EACI,OAtIG;EAuIH;EACA,WA7HO;;;AAgIX;EACI,OA3II;EA4IJ,kBA/IO;EAgJP,WAnIO;;;AAqIX;EACI,OAhJI;EAiJJ;EACA,WAxIO;;;AA2IX;EACI,OAzJO;EA0JP,WA7IO;;;AA+IX;EACI,OA5JO;EA6JP,WAjJO;;;AAmJX;EACI,OA/JG;EAgKH,WArJO;;;AAuJX;EACI,OA7JK;EA8JL,WAzJO;;;AA2JX;EACI,OAtKI;EAuKJ,WA7JO;;;AAiKX;EACI;EACA;EACH;EACG,kBAlLO;EAmLP;EACH;EACA;;;AAGD;EAEC;EACG;EACH;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACG;EACH;EACG;EACH,kBApMQ","file":"style.css"} \ No newline at end of file diff --git a/css/style.scss b/css/style.scss new file mode 100644 index 0000000..7e0b344 --- /dev/null +++ b/css/style.scss @@ -0,0 +1,207 @@ +// colorscheme(dracula) +// See https://github.com/dracula/dracula-theme/blob/master/LICENSE or dracula/LICENSE for the license +$bg: #282a36; +$fg: #f8f8f2; +$inactive: #44475a; +$darkblue: #6272a4; +$cyan: #8be9fd; +$green: #50fa7b; +$orange: #ffb86c; +$pink: #ff79c6; +$purple: #bd93f9; +$red: #ff5555; +$yellow: #f1fa8c; + + +$btblue: #1793D1; + +$textsize: 16px; + +*{ + all: unset; + font-family: "CaskaydiaCove Nerd Font"; +} + +// debug +.cpu-box{ + //background-color: #00cc00 +} + +.bar { + background-color: $bg; + border-radius: 16px; +} + +.right { + margin-right: 10px; + border-radius: 16px; +} + +.time-text { + font-size: $textsize; +} + +.reboot-button { + font-size: 28px; + //margin: -6px 6px -6px 0px; + margin-right: 6px; + + color: $darkblue; +} +.sleep-button { + font-size: 28px; + //margin: -6px 0px -6px 0px; + + color: $darkblue; +} +.exit-button { + font-size: 28px; + //margin: -6px 0px -6px 0px; + + color: $darkblue; +} + +.power-button { + font-size: 28px; + //margin: -16px -24px -16px 0px; + + color: $red; +} + +.power-box { +} + +.power-box-expand { + margin-right: 6px; +} + +.audio-icon { + font-size: 24px; + color: $orange; + margin-right: 12px; +} + +.bt-num { + font-size: $textsize; + color: $btblue; + margin-left: -6px; +} +.bt-label-on { + font-size: 20px; + color: $btblue; + margin-right: 6px; +} +.bt-label-off { + font-size: 24px; + color: $btblue; + margin-right: 6px; +} +.bt-label-connected { + font-size: 28px; + color: $btblue; + margin-right: 6px; +} + +.disk-util-progress { + color: $purple; + background-color: $inactive; + font-size: $textsize; +} +.disk-data-text { + color: $purple; + margin-right: 6px; + font-size: $textsize; +} + +.vram-util-progress { + color: $orange; + background-color: $inactive; +} +.vram-data-text { + color: $orange; + margin-right: 6px; + font-size: $textsize; +} + +.ram-util-progress { + color: $yellow; + background-color: $inactive; +} +.ram-data-text { + color: $yellow; + margin-right: 6px; + font-size: $textsize; +} + +.gpu-util-progress { + color: $cyan; + background-color: $inactive; +} +.gpu-data-text { + color: $cyan; + margin-right: 6px; + font-size: $textsize; +} + +.cpu-util-progress { + color: $green; + background-color: $inactive; + font-size: $textsize; +} +.cpu-data-text { + color: $green; + margin-right: 6px; + font-size: $textsize; +} + +.ws-dead { + color: $inactive; + font-size: $textsize; +} +.ws-inactive { + color: $darkblue; + font-size: $textsize; +} +.ws-visible { + color: $cyan; + font-size: $textsize; +} +.ws-current { + color: $yellow; + font-size: $textsize; +} +.ws-active { + color: $green; + font-size: $textsize; +} + +// Sliders +trough { + border-radius: 3px; + border-width: 1px; + border-style: none; + background-color: $inactive; + margin-top: 2px; + min-width: 4px; + min-height: 4px; +} + +slider { + // Controls the size of the control area (set border-style to solid to see) + border-radius: 0%; + border-width: 1px; + border-style: none; + margin: -9px -9px -9px -9px; + min-width: 16px; + min-height: 16px; + background-color: transparent; +} + +highlight { + border-radius: 3px; + border-width: 1px; + border-style: none; + min-height: 6px; + background-color: $orange; +} + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..66f5429 --- /dev/null +++ b/meson.build @@ -0,0 +1,31 @@ +project('gBar', + ['cpp'], + version: '0.0.1', + license: 'MIT', + meson_version: '>=0.45.1', + default_options: ['c_std=c++17', 'warning_level=3']) + +gtk = dependency('gtk+-3.0') +gtk_layer_shell = dependency('gtk-layer-shell-0') + +if get_option('HasHyprland') + add_global_arguments('-DHAS_HYPRLAND', language: 'cpp') +endif +if get_option('HasNvidia') + add_global_arguments('-DHAS_NVIDIA', language: 'cpp') +endif +if get_option('HasBlueZ') + add_global_arguments('-DHAS_BLUEZ', language: 'cpp') +endif +if get_option('HasSys') + add_global_arguments('-DHAS_SYS', language: 'cpp') +endif + +pulse = dependency('libpulse') + +executable( + 'gBar', + ['src/gBar.cpp', 'src/Window.cpp', 'src/Widget.cpp', 'src/System.cpp', 'src/Bar.cpp', 'src/AudioFlyin.cpp'], + dependencies: [gtk, gtk_layer_shell, pulse], + install: true +) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..5488172 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,6 @@ +option('HasHyprland', type: 'boolean', value : true) +option('HasNvidia', type: 'boolean', value : true) +option('HasBlueZ', type: 'boolean', value : true) + +# You shouldn't enable this, unless you know what you are doing! +option('HasSys', type: 'boolean', value : false) diff --git a/src/AudioFlyin.cpp b/src/AudioFlyin.cpp new file mode 100644 index 0000000..65758a8 --- /dev/null +++ b/src/AudioFlyin.cpp @@ -0,0 +1,105 @@ +#include "AudioFlyin.h" +#include "System.h" + +namespace AudioFlyin +{ + namespace DynCtx + { + Window* win; + Slider* slider; + Text* icon; + double sliderVal; + bool muted = false; + + int32_t msOpen = 0; + constexpr int32_t closeTime = 2000; + constexpr int32_t height = 50; + int32_t curCloseTime = closeTime; + int32_t transitionTime = 50; + + void OnChangeVolume(Slider&, double value) + { + System::SetVolume(value); + } + + TimerResult Main(Box&) + { + System::AudioInfo info = System::GetAudioInfo(); + if (sliderVal != info.volume || muted != info.muted) + { + // Extend timer + curCloseTime = msOpen + closeTime; + + sliderVal = info.volume; + slider->SetValue(info.volume); + + muted = info.muted; + if (info.muted) + { + icon->SetText("ﱝ"); + } + else + { + icon->SetText("墳"); + } + } + + msOpen++; + auto marginFunction = [](int32_t x) -> int32_t + { + // A inverted, cutoff 'V' shape + // Fly in -> hover -> fly out + double steepness = (double)height / (double)transitionTime; + return (int32_t)std::min(-std::abs((double)x - (double)curCloseTime / 2) * steepness + (double)curCloseTime / 2, (double)height); + }; + win->SetMargin(Anchor::Bottom, marginFunction(msOpen)); + if (msOpen >= curCloseTime) + { + win->Close(); + } + return TimerResult::Ok; + } + } + void WidgetAudio(Widget& parent) + { + auto slider = Widget::Create(); + slider->SetOrientation(Orientation::Horizontal); + slider->SetHorizontalTransform({100, true, Alignment::Fill}); + slider->SetInverted(true); + slider->SetClass("audio-volume"); + slider->OnValueChange(DynCtx::OnChangeVolume); + slider->SetRange({0, 1, 0.01}); + DynCtx::slider = slider.get(); + + auto icon = Widget::Create(); + icon->SetClass("audio-icon"); + icon->SetText("墳"); + DynCtx::icon = icon.get(); + + parent.AddChild(std::move(slider)); + parent.AddChild(std::move(icon)); + } + + void Create(Window& window, int32_t monitor) + { + DynCtx::win = &window; + auto mainWidget = Widget::Create(); + mainWidget->SetSpacing({8, false}); + mainWidget->SetVerticalTransform({16, true, Alignment::Fill}); + mainWidget->SetClass("bar"); + mainWidget->AddTimer(DynCtx::Main, 1); + + auto padding = Widget::Create(); + padding->SetHorizontalTransform({8, true, Alignment::Fill}); + mainWidget->AddChild(std::move(padding)); + + WidgetAudio(*mainWidget); + + padding = Widget::Create(); + mainWidget->AddChild(std::move(padding)); + + window = Window(std::move(mainWidget), monitor); + window.SetExclusive(false); + window.SetAnchor(Anchor::Bottom); + } +} diff --git a/src/AudioFlyin.h b/src/AudioFlyin.h new file mode 100644 index 0000000..9764105 --- /dev/null +++ b/src/AudioFlyin.h @@ -0,0 +1,8 @@ +#pragma once +#include "Widget.h" +#include "Window.h" + +namespace AudioFlyin +{ + void Create(Window& window, int32_t monitor); +} diff --git a/src/Bar.cpp b/src/Bar.cpp new file mode 100644 index 0000000..2ad0491 --- /dev/null +++ b/src/Bar.cpp @@ -0,0 +1,444 @@ +#include "Bar.h" + +#include "System.h" +#include "Common.h" + +namespace Bar +{ + static int32_t monitorID; + + namespace DynCtx + { + constexpr uint32_t updateTime = 1000; + constexpr uint32_t updateTimeFast = 100; + + static Revealer* powerBoxRevealer; + static void PowerBoxEvent(EventBox&, bool hovered) + { + powerBoxRevealer->SetRevealed(hovered); + } + + static Text* cpuText; + static TimerResult UpdateCPU(CairoSensor& sensor) + { + double usage = System::GetCPUUsage(); + double temp = System::GetCPUTemp(); + + cpuText->SetText("CPU: " + Utils::ToStringPrecision(usage * 100, "%0.1f") + "% " + Utils::ToStringPrecision(temp, "%0.1f") + "°C"); + sensor.SetValue(usage); + return TimerResult::Ok; + } + + static Text* ramText; + static TimerResult UpdateRAM(CairoSensor& sensor) + { + System::RAMInfo info = System::GetRAMInfo(); + double used = info.totalGiB - info.freeGiB; + double usedPercent = used / info.totalGiB; + + ramText->SetText("RAM: " + Utils::ToStringPrecision(used, "%0.2f") + "GiB/" + Utils::ToStringPrecision(info.totalGiB, "%0.2f") + "GiB"); + sensor.SetValue(usedPercent); + return TimerResult::Ok; + } + +#ifdef HAS_NVIDIA + static Text* gpuText; + static TimerResult UpdateGPU(CairoSensor& sensor) + { + System::GPUInfo info = System::GetGPUInfo(); + + gpuText->SetText("GPU: " + Utils::ToStringPrecision(info.utilisation, "%0.1f") + "% " + Utils::ToStringPrecision(info.coreTemp, "%0.1f") + + "°C"); + sensor.SetValue(info.utilisation / 100); + return TimerResult::Ok; + } + + static Text* vramText; + static TimerResult UpdateVRAM(CairoSensor& sensor) + { + System::VRAMInfo info = System::GetVRAMInfo(); + + vramText->SetText("VRAM: " + Utils::ToStringPrecision(info.usedGiB, "%0.2f") + "GiB/" + Utils::ToStringPrecision(info.totalGiB, "%0.2f") + + "GiB"); + sensor.SetValue(info.usedGiB / info.totalGiB); + return TimerResult::Ok; + } +#endif + + static Text* diskText; + static TimerResult UpdateDisk(CairoSensor& sensor) + { + System::DiskInfo info = System::GetDiskInfo(); + + diskText->SetText("Disk: " + Utils::ToStringPrecision(info.usedGiB, "%0.2f") + "GiB/" + Utils::ToStringPrecision(info.totalGiB, "%0.2f") + + "GiB"); + sensor.SetValue(info.usedGiB / info.totalGiB); + return TimerResult::Ok; + } + +#ifdef HAS_BLUEZ + static Text* btIconText; + static Text* btDevText; + static TimerResult UpdateBluetooth(Box&) + { + System::BluetoothInfo info = System::GetBluetoothInfo(); + if (info.defaultController.empty()) + { + btIconText->SetClass("bt-label-off"); + btIconText->SetText(""); + btDevText->SetText(""); + } + else if (info.devices.empty()) + { + btIconText->SetClass("bt-label-on"); + btIconText->SetText(""); + btDevText->SetText(""); + } + else + { + btIconText->SetClass("bt-label-connected"); + btIconText->SetText(""); + std::string btDev; + + for (auto& dev : info.devices) + { + std::string ico = " "; + if (dev.type == "input-keyboard") + { + ico = " "; + } + else if (dev.type == "input-mouse") + { + ico = " "; + } + else if (dev.type == "audio-headset") + { + ico = " "; + } + btDev += ico; + } + + btDevText->SetText(std::move(btDev)); + } + return TimerResult::Ok; + } +#endif + + void OnChangeVolume(Slider&, double value) + { + System::SetVolume(value); + } + + Text* audioIcon; + TimerResult UpdateAudio(Slider& slider) + { + System::AudioInfo info = System::GetAudioInfo(); + slider.SetValue(info.volume); + if (info.muted) + { + audioIcon->SetText("ﱝ"); + } + else + { + audioIcon->SetText("墳"); + } + return TimerResult::Ok; + } + + TimerResult UpdateTime(Text& text) + { + text.SetText(System::GetTime()); + return TimerResult::Ok; + } + +#ifdef HAS_HYPRLAND + static std::array workspaces; + TimerResult UpdateWorkspaces(Box&) + { + for (size_t i = 0; i < workspaces.size(); i++) + { + switch (System::GetWorkspaceStatus((uint32_t)monitorID, i + 1)) + { + case System::WorkspaceStatus::Dead: + workspaces[i]->SetClass("ws-dead"); + workspaces[i]->SetText(" "); + break; + case System::WorkspaceStatus::Inactive: + workspaces[i]->SetClass("ws-inactive"); + workspaces[i]->SetText(" "); + break; + case System::WorkspaceStatus::Visible: + workspaces[i]->SetClass("ws-visible"); + workspaces[i]->SetText(" "); + break; + case System::WorkspaceStatus::Current: + workspaces[i]->SetClass("ws-current"); + workspaces[i]->SetText(" "); + break; + case System::WorkspaceStatus::Active: + workspaces[i]->SetClass("ws-active"); + workspaces[i]->SetText(" "); + break; + } + } + return TimerResult::Ok; + } +#endif + } + + void Sensor(Widget& parent, TimerCallback&& callback, const std::string& sensorClass, const std::string& textClass, Text*& textPtr) + { + auto eventBox = Widget::Create(); + { + auto box = Widget::Create(); + box->SetSpacing({0, false}); + box->SetHorizontalTransform({-1, true, Alignment::Right}); + { + auto revealer = Widget::Create(); + revealer->SetTransition({TransitionType::SlideLeft, 500}); + // Add event to eventbox for the revealer to open + eventBox->SetEventFn( + [textRevealer = revealer.get()](EventBox&, bool hovered) + { + textRevealer->SetRevealed(hovered); + }); + { + auto text = Widget::Create(); + text->SetClass(textClass); + textPtr = text.get(); + revealer->AddChild(std::move(text)); + } + + auto cairoSensor = Widget::Create(); + cairoSensor->SetClass(sensorClass); + cairoSensor->AddTimer(std::move(callback), DynCtx::updateTime); + cairoSensor->SetHorizontalTransform({24, true, Alignment::Fill}); + + box->AddChild(std::move(revealer)); + box->AddChild(std::move(cairoSensor)); + } + eventBox->AddChild(std::move(box)); + } + + parent.AddChild(std::move(eventBox)); + } + + void WidgetAudio(Widget& parent) + { + auto box = Widget::Create(); + box->SetSpacing({8, false}); + box->SetHorizontalTransform({-1, false, Alignment::Right}); + { + auto slider = Widget::Create(); + slider->SetOrientation(Orientation::Horizontal); + slider->SetHorizontalTransform({100, true, Alignment::Fill}); + slider->SetInverted(true); + slider->SetClass("audio-volume"); + slider->AddTimer(DynCtx::UpdateAudio, DynCtx::updateTimeFast); + slider->OnValueChange(DynCtx::OnChangeVolume); + slider->SetRange({0, 1, 0.01}); + + auto icon = Widget::Create(); + icon->SetClass("audio-icon"); + icon->SetText("墳"); + DynCtx::audioIcon = icon.get(); + + box->AddChild(std::move(slider)); + box->AddChild(std::move(icon)); + } + + parent.AddChild(std::move(box)); + } + +#ifdef HAS_BLUEZ + void WidgetBluetooth(Widget& parent) + { + auto box = Widget::Create(); + box->SetSpacing({0, false}); + box->AddTimer(DynCtx::UpdateBluetooth, DynCtx::updateTime); + { + auto devText = Widget::Create(); + DynCtx::btDevText = devText.get(); + devText->SetClass("bt-num"); + + auto iconText = Widget::Create(); + DynCtx::btIconText = iconText.get(); + + box->AddChild(std::move(devText)); + box->AddChild(std::move(iconText)); + } + + parent.AddChild(std::move(box)); + } +#endif + + void WidgetSensors(Widget& parent) + { + Sensor(parent, DynCtx::UpdateDisk, "disk-util-progress", "disk-data-text", DynCtx::diskText); +#ifdef HAS_NVIDIA + Sensor(parent, DynCtx::UpdateVRAM, "vram-util-progress", "vram-data-text", DynCtx::vramText); + Sensor(parent, DynCtx::UpdateGPU, "gpu-util-progress", "gpu-data-text", DynCtx::gpuText); +#endif + Sensor(parent, DynCtx::UpdateRAM, "ram-util-progress", "ram-data-text", DynCtx::ramText); + Sensor(parent, DynCtx::UpdateCPU, "cpu-util-progress", "cpu-data-text", DynCtx::cpuText); + } + + void WidgetPower(Widget& parent) + { + auto eventBox = Widget::Create(); + eventBox->SetEventFn(DynCtx::PowerBoxEvent); + { + auto powerBox = Widget::Create(); + powerBox->SetClass("power-box"); + powerBox->SetHorizontalTransform({-1, false, Alignment::Right}); + powerBox->SetSpacing({0, false}); + { + auto revealer = Widget::Create(); + DynCtx::powerBoxRevealer = revealer.get(); + revealer->SetTransition({TransitionType::SlideLeft, 500}); + { + auto powerBoxExpand = Widget::Create(); + powerBoxExpand->SetClass("power-box-expand"); + powerBoxExpand->SetSpacing({8, true}); + { + auto exitButton = Widget::Create