Initial commit!

This commit is contained in:
scorpion-26 2023-01-13 16:13:56 +01:00
commit b0ed0d17e5
26 changed files with 2702 additions and 0 deletions

36
.clang-format Normal file
View file

@ -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

7
LICENSE Normal file
View file

@ -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.

83
README.md Normal file
View file

@ -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)

BIN
assets/audioflyin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
assets/bar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

21
css/dracula/LICENSE Normal file
View file

@ -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.

185
css/style.css Normal file
View file

@ -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 */

1
css/style.css.map Normal file
View file

@ -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"}

207
css/style.scss Normal file
View file

@ -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;
}

31
meson.build Normal file
View file

@ -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
)

6
meson_options.txt Normal file
View file

@ -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)

105
src/AudioFlyin.cpp Normal file
View file

@ -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>();
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<Text>();
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<Box>();
mainWidget->SetSpacing({8, false});
mainWidget->SetVerticalTransform({16, true, Alignment::Fill});
mainWidget->SetClass("bar");
mainWidget->AddTimer<Box>(DynCtx::Main, 1);
auto padding = Widget::Create<Box>();
padding->SetHorizontalTransform({8, true, Alignment::Fill});
mainWidget->AddChild(std::move(padding));
WidgetAudio(*mainWidget);
padding = Widget::Create<Box>();
mainWidget->AddChild(std::move(padding));
window = Window(std::move(mainWidget), monitor);
window.SetExclusive(false);
window.SetAnchor(Anchor::Bottom);
}
}

8
src/AudioFlyin.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include "Widget.h"
#include "Window.h"
namespace AudioFlyin
{
void Create(Window& window, int32_t monitor);
}

444
src/Bar.cpp Normal file
View file

@ -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<Button*, 9> 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<CairoSensor>&& callback, const std::string& sensorClass, const std::string& textClass, Text*& textPtr)
{
auto eventBox = Widget::Create<EventBox>();
{
auto box = Widget::Create<Box>();
box->SetSpacing({0, false});
box->SetHorizontalTransform({-1, true, Alignment::Right});
{
auto revealer = Widget::Create<Revealer>();
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>();
text->SetClass(textClass);
textPtr = text.get();
revealer->AddChild(std::move(text));
}
auto cairoSensor = Widget::Create<CairoSensor>();
cairoSensor->SetClass(sensorClass);
cairoSensor->AddTimer<CairoSensor>(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>();
box->SetSpacing({8, false});
box->SetHorizontalTransform({-1, false, Alignment::Right});
{
auto slider = Widget::Create<Slider>();
slider->SetOrientation(Orientation::Horizontal);
slider->SetHorizontalTransform({100, true, Alignment::Fill});
slider->SetInverted(true);
slider->SetClass("audio-volume");
slider->AddTimer<Slider>(DynCtx::UpdateAudio, DynCtx::updateTimeFast);
slider->OnValueChange(DynCtx::OnChangeVolume);
slider->SetRange({0, 1, 0.01});
auto icon = Widget::Create<Text>();
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>();
box->SetSpacing({0, false});
box->AddTimer<Box>(DynCtx::UpdateBluetooth, DynCtx::updateTime);
{
auto devText = Widget::Create<Text>();
DynCtx::btDevText = devText.get();
devText->SetClass("bt-num");
auto iconText = Widget::Create<Text>();
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>();
eventBox->SetEventFn(DynCtx::PowerBoxEvent);
{
auto powerBox = Widget::Create<Box>();
powerBox->SetClass("power-box");
powerBox->SetHorizontalTransform({-1, false, Alignment::Right});
powerBox->SetSpacing({0, false});
{
auto revealer = Widget::Create<Revealer>();
DynCtx::powerBoxRevealer = revealer.get();
revealer->SetTransition({TransitionType::SlideLeft, 500});
{
auto powerBoxExpand = Widget::Create<Box>();
powerBoxExpand->SetClass("power-box-expand");
powerBoxExpand->SetSpacing({8, true});
{
auto exitButton = Widget::Create<Button>();
exitButton->SetClass("exit-button");
exitButton->SetText("");
exitButton->OnClick(
[](Button&)
{
System::ExitWM();
});
auto lockButton = Widget::Create<Button>();
lockButton->SetClass("sleep-button");
lockButton->SetText("");
lockButton->OnClick(
[](Button&)
{
System::Lock();
});
auto sleepButton = Widget::Create<Button>();
sleepButton->SetClass("sleep-button");
sleepButton->SetText("");
sleepButton->OnClick(
[](Button&)
{
System::Suspend();
});
auto rebootButton = Widget::Create<Button>();
rebootButton->SetClass("reboot-button");
rebootButton->SetText("");
rebootButton->OnClick(
[](Button&)
{
System::Reboot();
});
powerBoxExpand->AddChild(std::move(exitButton));
powerBoxExpand->AddChild(std::move(lockButton));
powerBoxExpand->AddChild(std::move(sleepButton));
powerBoxExpand->AddChild(std::move(rebootButton));
}
revealer->AddChild(std::move(powerBoxExpand));
}
auto powerButton = Widget::Create<Button>();
powerButton->SetClass("power-button");
powerButton->SetText("");
powerButton->SetHorizontalTransform({24, true, Alignment::Fill});
powerButton->OnClick(
[](Button&)
{
System::Shutdown();
});
powerBox->AddChild(std::move(revealer));
powerBox->AddChild(std::move(powerButton));
}
eventBox->AddChild(std::move(powerBox));
}
parent.AddChild(std::move(eventBox));
}
#ifdef HAS_HYPRLAND
void WidgetWorkspaces(Widget& parent)
{
auto margin = Widget::Create<Box>();
margin->SetHorizontalTransform({12, true, Alignment::Left});
parent.AddChild(std::move(margin));
auto box = Widget::Create<Box>();
box->SetSpacing({8, true});
box->SetHorizontalTransform({-1, true, Alignment::Left});
{
for (size_t i = 0; i < DynCtx::workspaces.size(); i++)
{
auto workspace = Widget::Create<Button>();
workspace->SetHorizontalTransform({8, false, Alignment::Fill});
workspace->OnClick([i](Button&)
{
System::GotoWorkspace((uint32_t)i + 1);
});
DynCtx::workspaces[i] = workspace.get();
box->AddChild(std::move(workspace));
}
}
box->AddTimer<Box>(DynCtx::UpdateWorkspaces, DynCtx::updateTimeFast);
parent.AddChild(std::move(box));
}
#endif
void Create(Window& window, int32_t monitor)
{
monitorID = monitor;
auto mainWidget = Widget::Create<Box>();
mainWidget->SetSpacing({0, true});
mainWidget->SetClass("bar");
{
#ifdef HAS_HYPRLAND
auto left = Widget::Create<Box>();
left->SetSpacing({0, false});
left->SetHorizontalTransform({-1, true, Alignment::Left});
WidgetWorkspaces(*left);
#else
auto left = Widget::Create<Box>();
left->SetSpacing({0, false});
left->SetHorizontalTransform({-1, true, Alignment::Left});
#endif
auto time = Widget::Create<Text>();
time->AddTimer<Text>(DynCtx::UpdateTime, 1000);
time->SetHorizontalTransform({-1, true, Alignment::Center});
time->SetClass("time-text");
time->SetText("Uninitialized");
auto right = Widget::Create<Box>();
right->SetClass("right");
right->SetSpacing({8, false});
right->SetHorizontalTransform({-1, true, Alignment::Right});
{
WidgetAudio(*right);
#ifdef HAS_BLUEZ
WidgetBluetooth(*right);
#endif
WidgetSensors(*right);
WidgetPower(*right);
}
mainWidget->AddChild(std::move(left));
mainWidget->AddChild(std::move(time));
mainWidget->AddChild(std::move(right));
}
window = Window(std::move(mainWidget), monitor);
window.SetAnchor(Anchor::Left | Anchor::Right | Anchor::Top);
}
}

8
src/Bar.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
#include "Widget.h"
#include "Window.h"
namespace Bar
{
void Create(Window& window, int32_t monitor);
}

55
src/Common.h Normal file
View file

@ -0,0 +1,55 @@
#pragma once
#include <iostream>
#define UNUSED [[maybe_unused]]
#define LOG(x) std::cout << x << '\n'
#define ASSERT(x, log) if (!(x)) { LOG(log << "\n[Exiting due to assert failed]"); exit(-1); }
// Flag helper macros
#define BIT(x) (1 << (x))
template<typename Enum, typename std::enable_if_t<std::is_enum_v<Enum>, bool> = true>
using EnumType = std::underlying_type_t<Enum>;
// Based on https://stackoverflow.com/questions/1448396/how-to-use-enums-as-flags-in-c
// (Answer https://stackoverflow.com/a/23152590): Licensed under CC BY-SA 3.0
#define DEFINE_ENUM_FLAGS(T) \
inline T operator~(T a) \
{ \
return (T) ~(EnumType<T>)a; \
} \
inline T operator|(T a, T b) \
{ \
return (T)((EnumType<T>)a | (EnumType<T>)b); \
} \
inline T operator&(T a, T b) \
{ \
return (T)((EnumType<T>)a & (EnumType<T>)b); \
} \
inline T operator^(T a, T b) \
{ \
return (T)((EnumType<T>)a ^ (EnumType<T>)b); \
} \
inline T& operator|=(T& a, T b) \
{ \
return (T&)((EnumType<T>&)a |= (EnumType<T>)b); \
} \
inline T& operator&=(T& a, T b) \
{ \
return (T&)((EnumType<T>&)a &= (EnumType<T>)b); \
} \
inline T& operator^=(T& a, T b) \
{ \
return (T&)((EnumType<T>&)a ^= (EnumType<T>)b); \
}
#define FLAG_CHECK(val, check) ((int)(val & (check)) == (int)check)
namespace Utils
{
inline std::string ToStringPrecision(double x, const char* fmt)
{
char buf[128];
snprintf(buf, sizeof(buf), fmt, x);
return buf;
}
}

95
src/Hyprland.h Normal file
View file

@ -0,0 +1,95 @@
#pragma once
#include "Common.h"
#include "System.h"
#include <cstdint>
#include <string>
#include <cstdlib>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#ifdef HAS_HYPRLAND
namespace Hyprland
{
inline std::string DispatchIPC(const std::string& arg)
{
int hyprSocket = socket(AF_UNIX, SOCK_STREAM, 0);
const char* instanceSignature = getenv("HYPRLAND_INSTANCE_SIGNATURE");
std::string socketPath = "/tmp/hypr/" + std::string(instanceSignature) + "/.socket.sock";
sockaddr_un addr = {};
addr.sun_family = AF_UNIX;
memcpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path));
int ret = connect(hyprSocket, (sockaddr*)&addr, SUN_LEN(&addr));
ASSERT(ret >= 0, "Couldn't connect to hyprland socket");
ssize_t written = write(hyprSocket, arg.c_str(), arg.size());
ASSERT(written >= 0, "Couldn't write to socket");
char buf[2056];
std::string res;
while (true)
{
ssize_t bytesRead = read(hyprSocket, buf, sizeof(buf));
if (bytesRead == 0)
{
break;
}
ASSERT(bytesRead >= 0, "Couldn't read");
res += std::string(buf, bytesRead);
}
close(hyprSocket);
return res;
}
inline System::WorkspaceStatus GetStatus(uint32_t monitorID, uint32_t workspaceId)
{
std::string workspaces = DispatchIPC("/workspaces");
if (workspaces.find("workspace ID " + std::to_string(workspaceId)) == std::string::npos)
{
// It's dead and there's nothing I can do about it
// [Doesn't exist, no need to check anything else]
return System::WorkspaceStatus::Dead;
}
std::string monitors = DispatchIPC("/monitors");
size_t beginMonitor = monitors.find("(ID " + std::to_string(monitorID) + ")");
ASSERT(beginMonitor != std::string::npos, "Monitor not found!");
size_t endMonitor = monitors.find("dpmsStatus", beginMonitor);
std::string_view selectedMon = std::string_view(monitors).substr(beginMonitor, endMonitor - beginMonitor);
size_t activeWorkspaceLoc = selectedMon.find("active workspace:");
size_t workspaceBeg = selectedMon.find("(", activeWorkspaceLoc);
size_t workspaceEnd = selectedMon.find(")", workspaceBeg);
std::string workspaceNum = std::string(selectedMon.substr(workspaceBeg + 1, workspaceEnd - workspaceBeg - 1));
// Active workspace
if (std::stoi(workspaceNum) == (int)workspaceId)
{
// Check if focused
if (selectedMon.find("focused: yes") != std::string::npos)
{
return System::WorkspaceStatus::Active;
}
else
{
return System::WorkspaceStatus::Current;
}
}
if (monitors.find("active workspace: " + std::to_string(workspaceId)) != std::string::npos)
{
return System::WorkspaceStatus::Visible;
}
else
{
return System::WorkspaceStatus::Inactive;
}
}
inline void Goto(uint32_t workspace)
{
system(("hyprctl dispatch workspace " + std::to_string(workspace)).c_str());
}
}
#endif

77
src/NvidiaGPU.h Normal file
View file

@ -0,0 +1,77 @@
#pragma once
#include "Common.h"
#include <dlfcn.h>
#ifdef HAS_NVIDIA
namespace NvidiaGPU
{
static void* nvmldl;
static void* nvmlGPUHandle;
inline void Init()
{
if (nvmldl) return;
nvmldl = dlopen("libnvidia-ml.so", RTLD_NOW);
ASSERT(nvmldl, "Cannot open libnvidia-ml.so");
typedef int (*PFN_nvmlInit)();
auto nvmlInit = (PFN_nvmlInit)dlsym(nvmldl, "nvmlInit");
int res = nvmlInit();
ASSERT(res == 0, "Failed initializing nvml!");
// Get GPU handle
typedef int (*PFN_nvmlDeviceGetHandle)(uint32_t, void**);
auto nvmlDeviceGetHandle = (PFN_nvmlDeviceGetHandle)dlsym(nvmldl, "nvmlDeviceGetHandleByIndex");
res = nvmlDeviceGetHandle(0, &nvmlGPUHandle);
ASSERT(res == 0, "Failed getting device");
}
inline void Shutdown()
{
dlclose(nvmldl);
}
struct GPUUtilization
{
uint32_t gpu;
uint32_t vram;
};
struct VRAM
{
uint64_t totalB;
uint64_t freeB;
uint64_t usedB;
};
inline GPUUtilization GetUtilization()
{
GPUUtilization util;
typedef int (*PFN_nvmlDeviceGetUtilizationRates)(void*, GPUUtilization*);
auto nvmlDeviceGetUtilizationRates = (PFN_nvmlDeviceGetUtilizationRates)dlsym(nvmldl, "nvmlDeviceGetUtilizationRates");
int res = nvmlDeviceGetUtilizationRates(nvmlGPUHandle, &util);
ASSERT(res == 0, "Failed getting utilization");
return util;
}
inline uint32_t GetTemperature()
{
typedef int (*PFN_nvmlDeviceGetTemperature)(void*, uint32_t, uint32_t*);
auto nvmlDeviceGetTemperature = (PFN_nvmlDeviceGetTemperature)dlsym(nvmldl, "nvmlDeviceGetTemperature");
uint32_t temp;
int res = nvmlDeviceGetTemperature(nvmlGPUHandle, 0, &temp);
ASSERT(res == 0, "Failed getting temperature");
return temp;
}
inline VRAM GetVRAM()
{
typedef int (*PFN_nvmlDeviceGetMemoryInfo)(void*, VRAM*);
auto nvmlDeviceGetMemoryInfo = (PFN_nvmlDeviceGetMemoryInfo)dlsym(nvmldl, "nvmlDeviceGetMemoryInfo");
VRAM mem;
int res = nvmlDeviceGetMemoryInfo(nvmlGPUHandle, &mem);
ASSERT(res == 0, "Failed getting memory");
return mem;
}
}
#endif

106
src/PulseAudio.h Normal file
View file

@ -0,0 +1,106 @@
#pragma once
#include "System.h"
#include "Common.h"
#include <pulse/pulseaudio.h>
#include <stdlib.h>
#include <algorithm>
namespace PulseAudio
{
static pa_mainloop* mainLoop;
static pa_context* context;
static uint32_t pending = 0;
inline void FlushLoop()
{
while (pending)
{
pa_mainloop_iterate(mainLoop, 0, nullptr);
}
}
inline void Init()
{
mainLoop = pa_mainloop_new();
pa_mainloop_api* api = pa_mainloop_get_api(mainLoop);
context = pa_context_new(api, "gBar PA context");
int res = pa_context_connect(context, nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr);
auto stateCallback = [](pa_context* c, void*)
{
switch (pa_context_get_state(c))
{
case PA_CONTEXT_TERMINATED:
case PA_CONTEXT_FAILED:
case PA_CONTEXT_UNCONNECTED: ASSERT(false, "PA Callback error!"); break;
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
case PA_CONTEXT_CONNECTING:
// Don't care
break;
case PA_CONTEXT_READY: pending--; break;
}
};
pa_context_set_state_callback(context, +stateCallback, nullptr);
pending++;
FlushLoop();
ASSERT(res >= 0, "pa_context_connect failed!");
}
inline System::AudioInfo GetInfo()
{
const char* defaultSink = nullptr;
System::AudioInfo info{};
// 1. Get default sink
auto serverInfo = [](pa_context*, const pa_server_info* info, void* sink)
{
pending--;
*(const char**)sink = info->default_sink_name;
};
pa_context_get_server_info(context, +serverInfo, &defaultSink);
pending++;
FlushLoop();
auto sinkInfo = [](pa_context*, const pa_sink_info* info, int, void* audioInfo)
{
if (!info)
return;
System::AudioInfo* out = (System::AudioInfo*)audioInfo;
double vol = (double)pa_cvolume_avg(&info->volume) / (double)PA_VOLUME_NORM;
out->volume = vol;
out->muted = info->mute;
pending--;
};
pa_context_get_sink_info_by_name(context, defaultSink, +sinkInfo, &info);
pending++;
FlushLoop();
return info;
}
inline void SetVolume(double value)
{
double valClamped = std::clamp(value, 0., 1.);
// I'm too lazy to implement the c api for this. Since it will only be called when needed and doesn't pipe, it shouldn't be a problem to
// fallback for a command
std::string cmd = "pamixer --set-volume " + std::to_string((uint32_t)(valClamped * 100));
system(cmd.c_str());
}
inline void Shutdown()
{
pa_mainloop_free(mainLoop);
}
}

336
src/System.cpp Normal file
View file

@ -0,0 +1,336 @@
#include "System.h"
#include "Common.h"
#include "NvidiaGPU.h"
#include "PulseAudio.h"
#include "Hyprland.h"
#include <cstdlib>
#include <fstream>
#include <string>
#include <sstream>
#include <iomanip>
#include <gio/gio.h>
#include <pulse/pulseaudio.h>
#include <dlfcn.h>
#include <sys/statvfs.h>
namespace System
{
struct CPUTimestamp
{
size_t total = 0;
size_t idle = 0;
};
static CPUTimestamp curCPUTime;
static CPUTimestamp prevCPUTime;
double GetCPUUsage()
{
// Gather curCPUTime
std::ifstream procstat("/proc/stat");
ASSERT(procstat.is_open(), "Cannot open /proc/stat");
std::string curLine;
while (std::getline(procstat, curLine))
{
if (curLine.find("cpu ") != std::string::npos)
{
// Found it
std::stringstream lineStr(curLine.substr(5));
std::string curLine;
uint32_t idx = 1;
size_t total = 0;
size_t idle = 0;
while (std::getline(lineStr, curLine, ' '))
{
if (idx == 4)
{
// Fourth col is idle
idle = atoi(curLine.c_str());
}
total += atoi(curLine.c_str());
idx++;
}
prevCPUTime = curCPUTime;
curCPUTime.total = total;
curCPUTime.idle = idle;
break;
}
}
// Get diffs and percentage of idle time
size_t diffTotal = curCPUTime.total - prevCPUTime.total;
size_t diffIdle = curCPUTime.idle - prevCPUTime.idle;
return 1 - ((double)diffIdle / (double)diffTotal);
}
double GetCPUTemp()
{
constexpr const char* tempFilePath = "/sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon2/temp1_input";
std::ifstream tempFile(tempFilePath);
if (!tempFile.is_open())
{
return 0.f;
}
std::string tempStr;
std::getline(tempFile, tempStr);
uint32_t intTemp = atoi(tempStr.c_str());
double temp = (double)intTemp / 1000;
return temp;
}
RAMInfo GetRAMInfo()
{
RAMInfo out{};
std::ifstream procstat("/proc/meminfo");
ASSERT(procstat.is_open(), "Cannot open /proc/meminfo");
std::string curLine;
while (std::getline(procstat, curLine))
{
if (curLine.find("MemTotal: ") != std::string::npos)
{
// Found total
std::string_view withoutMemTotal = std::string_view(curLine).substr(10);
size_t begNum = withoutMemTotal.find_first_not_of(' ');
std::string_view totalKiBStr = withoutMemTotal.substr(begNum, withoutMemTotal.find_last_of(' ') - begNum);
uint32_t totalKiB = std::stoi(std::string(totalKiBStr));
out.totalGiB = (double)totalKiB / (1024 * 1024);
}
else if (curLine.find("MemAvailable: ") != std::string::npos)
{
// Found available
std::string_view withoutMemAvail = std::string_view(curLine).substr(14);
size_t begNum = withoutMemAvail.find_first_not_of(' ');
std::string_view availKiBStr = withoutMemAvail.substr(begNum, withoutMemAvail.find_last_of(' ') - begNum);
uint32_t availKiB = std::stoi(std::string(availKiBStr));
out.freeGiB = (double)availKiB / (1024 * 1024);
}
}
return out;
}
#ifdef HAS_NVIDIA
GPUInfo GetGPUInfo()
{
NvidiaGPU::GPUUtilization util = NvidiaGPU::GetUtilization();
GPUInfo out;
out.utilisation = util.gpu;
out.coreTemp = NvidiaGPU::GetTemperature();
return out;
}
VRAMInfo GetVRAMInfo()
{
NvidiaGPU::VRAM vram = NvidiaGPU::GetVRAM();
VRAMInfo out;
out.totalGiB = (double)vram.totalB / (1024 * 1024 * 1024);
out.usedGiB = out.totalGiB - ((double)vram.freeB / (1024 * 1024 * 1024));
return out;
}
#endif
DiskInfo GetDiskInfo()
{
struct statvfs stat;
int err = statvfs("/", &stat);
ASSERT(err == 0, "Cannot stat root!");
DiskInfo out{};
out.totalGiB = (double)(stat.f_blocks * stat.f_frsize) / (1024 * 1024 * 1024);
out.usedGiB = (double)((stat.f_blocks - stat.f_bfree) * stat.f_frsize) / (1024 * 1024 * 1024);
return out;
}
#ifdef HAS_BLUEZ
BluetoothInfo GetBluetoothInfo()
{
BluetoothInfo out{};
// Init D-Bus
GDBusConnection* connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr);
ASSERT(connection, "Failed to connect to d-bus!");
GError* err = nullptr;
GVariant* objects = g_dbus_connection_call_sync(connection, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects",
nullptr, G_VARIANT_TYPE("(a{oa{sa{sv}}})"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err);
if (!objects)
{
LOG(err->message);
g_error_free(err);
exit(-1);
}
// First array
GVariantIter* topArray;
g_variant_get(objects, "(a{oa{sa{sv}}})", &topArray);
// Iterate the objects
GVariantIter* objectDescs;
while (g_variant_iter_next(topArray, "{oa{sa{sv}}}", NULL, &objectDescs))
{
// Iterate the descs
char* type = nullptr;
GVariantIter* propIter;
while (g_variant_iter_next(objectDescs, "{sa{sv}}", &type, &propIter))
{
if (strstr(type, "org.bluez.Adapter1"))
{
std::string adapterName;
bool powered = false;
// This is a controller/adapter -> The "host"
char* str = nullptr;
GVariant* var = nullptr;
while (g_variant_iter_next(propIter, "{sv}", &str, &var))
{
if (strstr(str, "Name"))
{
const char* name = g_variant_get_string(var, nullptr);
// Copy it for us
adapterName = name;
}
else if (strstr(str, "Powered"))
{
powered = g_variant_get_boolean(var);
}
g_free(str);
g_variant_unref(var);
}
if (powered)
{
out.defaultController = std::move(adapterName);
}
}
else if (strstr(type, "org.bluez.Device1"))
{
std::string deviceName;
std::string deviceType;
bool connected = false;
// This is a device -> One "client"
char* str = nullptr;
GVariant* var = nullptr;
while (g_variant_iter_next(propIter, "{sv}", &str, &var))
{
if (strstr(str, "Name"))
{
const char* name = g_variant_get_string(var, nullptr);
// Copy it for us
deviceName = name;
}
else if (strstr(str, "Icon"))
{
const char* icon = g_variant_get_string(var, nullptr);
// Copy it for us
deviceType = icon;
}
else if (strstr(str, "Connected"))
{
connected = g_variant_get_boolean(var);
}
g_free(str);
g_variant_unref(var);
}
if (connected)
{
out.devices.push_back(BluetoothDevice{std::move(deviceName), std::move(deviceType)});
}
}
g_variant_iter_free(propIter);
g_free(type);
}
g_variant_iter_free(objectDescs);
}
g_variant_iter_free(topArray);
g_variant_unref(objects);
return out;
}
#endif
AudioInfo GetAudioInfo()
{
return PulseAudio::GetInfo();
}
void SetVolume(double volume)
{
PulseAudio::SetVolume(volume);
}
#ifdef HAS_HYPRLAND
WorkspaceStatus GetWorkspaceStatus(uint32_t monitor, uint32_t workspace)
{
return Hyprland::GetStatus(monitor, workspace);
}
void GotoWorkspace(uint32_t workspace)
{
return Hyprland::Goto(workspace);
}
#endif
std::string GetTime()
{
time_t stdTime = time(NULL);
tm* localTime = localtime(&stdTime);
std::stringstream str;
str << std::put_time(localTime, "%a %D - %H:%M:%S %Z");
return str.str();
}
void Shutdown()
{
system("shutdown 0");
}
void Reboot()
{
system("reboot");
}
void ExitWM()
{
system("killall Hyprland");
}
void Lock()
{
#ifdef HAS_SYS
// My personal lock script
system("~/.config/scripts/sys.sh lock");
#else
LOG("Lock not implemented! Please implement me below!");
// system("XXX");
#endif
}
void Suspend()
{
#ifdef HAS_SYS
// My personal suspend script
system("~/.config/scripts/sys.sh suspend");
#else
system("systemctl suspend");
#endif
}
void Init()
{
#ifdef HAS_NVIDIA
NvidiaGPU::Init();
#endif
PulseAudio::Init();
}
void FreeResources()
{
#ifdef HAS_NVIDIA
NvidiaGPU::Shutdown();
#endif
PulseAudio::Shutdown();
}
}

89
src/System.h Normal file
View file

@ -0,0 +1,89 @@
#pragma once
#include <string>
#include <vector>
namespace System
{
// From 0-1, all cores
double GetCPUUsage();
// Tctl
double GetCPUTemp();
struct RAMInfo
{
double totalGiB;
double freeGiB;
};
RAMInfo GetRAMInfo();
#ifdef HAS_NVIDIA
struct GPUInfo
{
double utilisation;
double coreTemp;
};
GPUInfo GetGPUInfo();
struct VRAMInfo
{
double totalGiB;
double usedGiB;
};
VRAMInfo GetVRAMInfo();
#endif
struct DiskInfo
{
double totalGiB;
double usedGiB;
};
DiskInfo GetDiskInfo();
#ifdef HAS_BLUEZ
struct BluetoothDevice
{
std::string name;
// Known types: input-[keyboard,mouse]; audio-headset
std::string type;
};
struct BluetoothInfo
{
std::string defaultController;
std::vector<BluetoothDevice> devices;
};
BluetoothInfo GetBluetoothInfo();
#endif
struct AudioInfo
{
double volume;
bool muted;
};
AudioInfo GetAudioInfo();
void SetVolume(double volume);
#ifdef HAS_HYPRLAND
enum class WorkspaceStatus
{
Dead,
Inactive,
Visible,
Current,
Active
};
WorkspaceStatus GetWorkspaceStatus(uint32_t monitor, uint32_t workspace);
void GotoWorkspace(uint32_t workspace);
#endif
std::string GetTime();
void Shutdown();
void Reboot();
void ExitWM();
void Lock();
void Suspend();
void Init();
void FreeResources();
}

332
src/Widget.cpp Normal file
View file

@ -0,0 +1,332 @@
#include "Widget.h"
#include "Common.h"
#include <cmath>
// TODO: Currently setters only work pre-create. Make them react to changes after creation!
namespace Utils
{
GtkAlign ToGtkAlign(Alignment align)
{
switch (align)
{
case Alignment::Left: return GTK_ALIGN_START;
case Alignment::Right: return GTK_ALIGN_END;
case Alignment::Center: return GTK_ALIGN_CENTER;
case Alignment::Fill: return GTK_ALIGN_FILL;
}
}
GtkOrientation ToGtkOrientation(Orientation orientation)
{
switch (orientation)
{
case Orientation::Vertical: return GTK_ORIENTATION_VERTICAL;
case Orientation::Horizontal: return GTK_ORIENTATION_HORIZONTAL;
}
}
GtkRevealerTransitionType ToGtkRevealerTransitionType(TransitionType transition)
{
switch (transition)
{
case TransitionType::Fade: return GTK_REVEALER_TRANSITION_TYPE_CROSSFADE;
case TransitionType::SlideLeft: return GTK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT;
case TransitionType::SlideRight: return GTK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT;
case TransitionType::SlideDown: return GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN;
case TransitionType::SlideUp: return GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP;
}
}
}
Widget::~Widget()
{
m_Childs.clear();
LOG("Destroy widget");
gtk_widget_destroy(m_Widget);
}
void Widget::SetClass(const std::string& cssClass)
{
if (m_Widget)
{
auto style = gtk_widget_get_style_context(m_Widget);
gtk_style_context_remove_class(style, m_CssClass.c_str());
gtk_style_context_add_class(style, cssClass.c_str());
}
m_CssClass = cssClass;
}
void Widget::SetVerticalTransform(const Transform& transform)
{
m_VerticalTransform = transform;
}
void Widget::SetHorizontalTransform(const Transform& transform)
{
m_HorizontalTransform = transform;
}
void Widget::AddChild(std::unique_ptr<Widget>&& widget)
{
m_Childs.push_back(std::move(widget));
}
void Widget::SetVisible(bool visible)
{
gtk_widget_set_visible(m_Widget, visible);
}
void Widget::ApplyPropertiesToWidget()
{
// Apply style
auto style = gtk_widget_get_style_context(m_Widget);
gtk_style_context_add_class(style, m_CssClass.c_str());
// Apply transform
gtk_widget_set_size_request(m_Widget, m_HorizontalTransform.size, m_VerticalTransform.size);
gtk_widget_set_halign(m_Widget, Utils::ToGtkAlign(m_HorizontalTransform.alignment));
gtk_widget_set_valign(m_Widget, Utils::ToGtkAlign(m_VerticalTransform.alignment));
gtk_widget_set_hexpand(m_Widget, m_HorizontalTransform.expand);
gtk_widget_set_vexpand(m_Widget, m_VerticalTransform.expand);
}
void Box::SetOrientation(Orientation orientation)
{
m_Orientation = orientation;
}
void Box::SetSpacing(Spacing spacing)
{
m_Spacing = spacing;
}
void Box::Create()
{
m_Widget = gtk_box_new(Utils::ToGtkOrientation(m_Orientation), m_Spacing.free);
gtk_box_set_homogeneous((GtkBox*)m_Widget, m_Spacing.evenly);
ApplyPropertiesToWidget();
}
void CenterBox::SetOrientation(Orientation orientation)
{
m_Orientation = orientation;
}
void CenterBox::Create()
{
ASSERT(m_Childs.size() == 3, "CenterBox needs 3 children!")
m_Widget = gtk_box_new(Utils::ToGtkOrientation(m_Orientation), 0);
gtk_box_pack_start((GtkBox*)m_Widget, m_Childs[0]->Get(), true, true, 0);
gtk_box_set_center_widget((GtkBox*)m_Widget, m_Childs[1]->Get());
gtk_box_pack_end((GtkBox*)m_Widget, m_Childs[2]->Get(), true, true, 0);
ApplyPropertiesToWidget();
}
void EventBox::SetEventFn(std::function<void(EventBox&, bool)>&& fn)
{
m_EventFn = fn;
}
void EventBox::Create()
{
m_Widget = gtk_event_box_new();
auto enter = [](GtkWidget*, GdkEventCrossing*, gpointer data) -> gboolean
{
EventBox* box = (EventBox*)data;
box->m_EventFn(*box, true);
return false;
};
auto leave = [](GtkWidget*, GdkEventCrossing*, void* data) -> gboolean
{
EventBox* box = (EventBox*)data;
box->m_EventFn(*box, false);
return false;
};
gtk_widget_set_events(m_Widget, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
g_signal_connect(m_Widget, "enter-notify-event", G_CALLBACK(+enter), this);
g_signal_connect(m_Widget, "leave-notify-event", G_CALLBACK(+leave), this);
ApplyPropertiesToWidget();
}
void CairoSensor::Create()
{
m_Widget = gtk_drawing_area_new();
auto drawFn = [](GtkWidget*, cairo_t* c, void* data) -> gboolean
{
CairoSensor* sensor = (CairoSensor*)data;
sensor->Draw(c);
return false;
};
g_signal_connect(m_Widget, "draw", G_CALLBACK(+drawFn), this);
ApplyPropertiesToWidget();
}
void CairoSensor::SetValue(double val)
{
m_Val = val;
if (m_Widget)
{
gtk_widget_queue_draw(m_Widget);
}
}
void CairoSensor::SetStyle(SensorStyle style)
{
m_Style = style;
}
void CairoSensor::Draw(cairo_t* cr)
{
GtkAllocation dim;
gtk_widget_get_allocation(m_Widget, &dim);
double xStart = 0;
double yStart = 0;
double size = dim.width;
if (dim.height >= dim.width)
{
// Height greater than width; Fill in x and add margin at the top and bottom
size = dim.width;
xStart = 0;
yStart = ((double)dim.height - (double)dim.width) / 2;
}
else if (dim.width < dim.height)
{
// Height greater than width; Fill in y and add margin at the sides
size = dim.height;
yStart = 0;
xStart = ((double)dim.width - (double)dim.height) / 2;
}
double xCenter = xStart + size / 2;
double yCenter = yStart + size / 2;
double radius = (size / 2) - (m_Style.strokeWidth / 2);
double beg = m_Style.start * (M_PI / 180);
double angle = m_Val * 2 * M_PI;
auto style = gtk_widget_get_style_context(m_Widget);
GdkRGBA* bgCol;
GdkRGBA* fgCol;
gtk_style_context_get(style, GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_BACKGROUND_COLOR, &bgCol, NULL);
gtk_style_context_get(style, GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_COLOR, &fgCol, NULL);
cairo_set_line_width(cr, m_Style.strokeWidth);
// Outer
cairo_set_source_rgb(cr, bgCol->red, bgCol->green, bgCol->blue);
cairo_arc(cr, xCenter, yCenter, radius, 0, 2 * M_PI);
cairo_stroke(cr);
// Inner
cairo_set_source_rgb(cr, fgCol->red, fgCol->green, fgCol->blue);
cairo_arc(cr, xCenter, yCenter, radius, beg, beg + angle);
cairo_stroke(cr);
gdk_rgba_free(bgCol);
gdk_rgba_free(fgCol);
}
void Revealer::SetTransition(Transition transition)
{
m_Transition = transition;
}
void Revealer::Create()
{
m_Widget = gtk_revealer_new();
gtk_revealer_set_transition_type((GtkRevealer*)m_Widget, Utils::ToGtkRevealerTransitionType(m_Transition.type));
gtk_revealer_set_transition_duration((GtkRevealer*)m_Widget, m_Transition.durationMS);
ApplyPropertiesToWidget();
}
void Revealer::SetRevealed(bool revealed)
{
gtk_revealer_set_reveal_child((GtkRevealer*)m_Widget, revealed);
}
void Text::SetText(const std::string& text)
{
m_Text = text;
if (m_Widget)
{
gtk_label_set_text((GtkLabel*)m_Widget, m_Text.c_str());
}
}
void Text::Create()
{
m_Widget = gtk_label_new(m_Text.c_str());
ApplyPropertiesToWidget();
}
void Button::Create()
{
m_Widget = gtk_button_new_with_label(m_Text.c_str());
auto clickFn = [](UNUSED GtkButton* gtkButton, void* data)
{
Button* button = (Button*)data;
button->m_OnClick(*button);
};
g_signal_connect(m_Widget, "clicked", G_CALLBACK(+clickFn), this);
ApplyPropertiesToWidget();
}
void Button::SetText(const std::string& text)
{
m_Text = text;
if (m_Widget)
{
gtk_button_set_label((GtkButton*)m_Widget, m_Text.c_str());
}
}
void Button::OnClick(Callback<Button>&& callback)
{
m_OnClick = std::move(callback);
}
void Slider::OnValueChange(std::function<void(Slider&, double)>&& callback)
{
m_OnValueChange = callback;
}
void Slider::SetRange(SliderRange range)
{
m_Range = range;
}
void Slider::SetOrientation(Orientation orientation)
{
m_Orientation = orientation;
}
void Slider::SetValue(double value)
{
gtk_range_set_value((GtkRange*)m_Widget, value);
}
void Slider::SetInverted(bool inverted)
{
m_Inverted = inverted;
}
void Slider::Create()
{
m_Widget = gtk_scale_new_with_range(Utils::ToGtkOrientation(m_Orientation), m_Range.min, m_Range.max, m_Range.step);
gtk_range_set_inverted((GtkRange*)m_Widget, m_Inverted);
gtk_scale_set_draw_value((GtkScale*)m_Widget, false);
auto changedFn = [](GtkScale*, GtkScrollType*, double val, void* data)
{
Slider* slider = (Slider*)data;
if (slider->m_OnValueChange)
slider->m_OnValueChange(*slider, val);
return false;
};
g_signal_connect(m_Widget, "change-value", G_CALLBACK(+changedFn), this);
ApplyPropertiesToWidget();
}

250
src/Widget.h Normal file
View file

@ -0,0 +1,250 @@
#pragma once
#include <gtk/gtk.h>
#include <vector>
#include <memory>
#include <string>
#include <functional>
enum class Alignment
{
Fill,
Center,
Left,
Right,
};
struct Transform
{
int size = -1;
bool expand = true;
Alignment alignment = Alignment::Fill;
};
enum class Orientation
{
Vertical,
Horizontal
};
struct Spacing
{
uint32_t free = 0;
bool evenly = false;
};
enum class TransitionType
{
Fade,
SlideLeft,
SlideRight,
SlideUp,
SlideDown
};
struct Transition
{
TransitionType type;
uint32_t durationMS;
};
struct SensorStyle
{
double start = -90; // 0 = leftmost; -90 = topmost
double strokeWidth = 4;
};
struct SliderRange
{
double min, max, step;
};
enum class TimerResult
{
Ok,
Delete
};
template<typename TWidget>
using Callback = std::function<void(TWidget&)>;
template<typename TWidget>
using TimerCallback = std::function<TimerResult(TWidget&)>;
class Widget
{
public:
Widget() = default;
virtual ~Widget();
template<typename TWidget>
static std::unique_ptr<TWidget> Create()
{
return std::make_unique<TWidget>();
}
void SetClass(const std::string& cssClass);
void SetVerticalTransform(const Transform& transform);
void SetHorizontalTransform(const Transform& transform);
virtual void Create() = 0;
void AddChild(std::unique_ptr<Widget>&& widget);
template<typename TWidget>
void AddTimer(TimerCallback<TWidget>&& callback, uint32_t timeoutMS)
{
struct TimerPayload
{
TimerCallback<TWidget> timeoutFn;
Widget* thisWidget;
};
TimerPayload* payload = new TimerPayload();
payload->thisWidget = this;
payload->timeoutFn = std::move(callback);
auto fn = [](void* data) -> int
{
TimerPayload* payload = (TimerPayload*)data;
TimerResult result = payload->timeoutFn(*(TWidget*)payload->thisWidget);
if (result == TimerResult::Delete)
{
delete payload;
return false;
}
return true;
};
g_timeout_add(timeoutMS, +fn, payload);
}
GtkWidget* Get() { return m_Widget; };
const std::vector<std::unique_ptr<Widget>>& GetChilds() const { return m_Childs; };
void SetVisible(bool visible);
protected:
void ApplyPropertiesToWidget();
GtkWidget* m_Widget = nullptr;
std::vector<std::unique_ptr<Widget>> m_Childs;
std::string m_CssClass;
Transform m_HorizontalTransform; // X
Transform m_VerticalTransform; // Y
};
class Box : public Widget
{
public:
Box() = default;
virtual ~Box() = default;
void SetOrientation(Orientation orientation);
void SetSpacing(Spacing spacing);
virtual void Create() override;
private:
Orientation m_Orientation = Orientation::Horizontal;
Spacing m_Spacing;
};
class CenterBox : public Widget
{
public:
void SetOrientation(Orientation orientation);
virtual void Create() override;
private:
Orientation m_Orientation = Orientation::Horizontal;
};
class EventBox : public Widget
{
public:
void SetEventFn(std::function<void(EventBox&, bool)>&& fn);
virtual void Create() override;
private:
std::function<void(EventBox&, bool)> m_EventFn;
};
class CairoSensor : public Widget
{
public:
virtual void Create() override;
// Goes from 0-1
void SetValue(double val);
void SetStyle(SensorStyle style);
private:
void Draw(cairo_t* cr);
double m_Val;
SensorStyle m_Style{};
};
class Revealer : public Widget
{
public:
void SetTransition(Transition transition);
void SetRevealed(bool revealed);
virtual void Create() override;
private:
Transition m_Transition;
};
class Text : public Widget
{
public:
Text() = default;
virtual ~Text() = default;
void SetText(const std::string& text);
virtual void Create() override;
private:
std::string m_Text;
};
class Button : public Widget
{
public:
Button() = default;
virtual ~Button() = default;
void SetText(const std::string& text);
virtual void Create() override;
void OnClick(Callback<Button>&& callback);
private:
std::string m_Text;
Callback<Button> m_OnClick;
};
class Slider : public Widget
{
public:
Slider() = default;
virtual ~Slider() = default;
void SetValue(double value);
void SetOrientation(Orientation orientation);
void SetInverted(bool flipped);
void SetRange(SliderRange range);
void OnValueChange(std::function<void(Slider&, double)>&& callback);
virtual void Create() override;
private:
Orientation m_Orientation = Orientation::Horizontal;
SliderRange m_Range;
bool m_Inverted = false;
std::function<void(Slider&, double)> m_OnValueChange;
};

121
src/Window.cpp Normal file
View file

@ -0,0 +1,121 @@
#include "Window.h"
#include "Common.h"
#include <gtk-layer-shell.h>
Window::Window(std::unique_ptr<Widget>&& mainWidget, int32_t monitor) : m_MainWidget(std::move(mainWidget)), m_Monitor(monitor) {}
Window::~Window()
{
if (m_App)
{
g_object_unref(m_App);
m_App = nullptr;
}
}
void Window::Run(int argc, char** argv)
{
gtk_init(&argc, &argv);
// Style
GtkCssProvider* cssprovider = gtk_css_provider_new();
GError* err = nullptr;
const char* xdgConfig = getenv("XDG_CONFIG_HOME");
if (xdgConfig)
{
gtk_css_provider_load_from_path(cssprovider, (std::string(xdgConfig) + "/gBar/style.css").c_str(), &err);
}
else
{
const char* home = getenv("HOME");
gtk_css_provider_load_from_path(cssprovider, (std::string(home) + "/.config/gBar/style.css").c_str(), &err);
}
if (err)
{
printf("%s\n", err->message);
g_error_free(err);
}
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), (GtkStyleProvider*)cssprovider, GTK_STYLE_PROVIDER_PRIORITY_USER);
m_Window = (GtkWindow*)gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_layer_init_for_window(m_Window);
gtk_layer_set_layer(m_Window, GTK_LAYER_SHELL_LAYER_TOP);
if (m_Exclusive)
gtk_layer_auto_exclusive_zone_enable(m_Window);
GdkDisplay* defaultDisplay = gdk_display_get_default();
ASSERT(defaultDisplay != nullptr, "Cannot get display!");
GdkMonitor* selectedMon = nullptr;
if (m_Monitor != -1)
{
selectedMon = gdk_display_get_monitor(defaultDisplay, m_Monitor);
ASSERT(selectedMon != nullptr, "Cannot get monitor!");
gtk_layer_set_monitor(m_Window, selectedMon);
}
if (FLAG_CHECK(m_Anchor, Anchor::Left))
{
gtk_layer_set_anchor(m_Window, GTK_LAYER_SHELL_EDGE_LEFT, true);
}
if (FLAG_CHECK(m_Anchor, Anchor::Right))
{
gtk_layer_set_anchor(m_Window, GTK_LAYER_SHELL_EDGE_RIGHT, true);
}
if (FLAG_CHECK(m_Anchor, Anchor::Top))
{
gtk_layer_set_anchor(m_Window, GTK_LAYER_SHELL_EDGE_TOP, true);
}
if (FLAG_CHECK(m_Anchor, Anchor::Bottom))
{
gtk_layer_set_anchor(m_Window, GTK_LAYER_SHELL_EDGE_BOTTOM, true);
}
// Create widgets
CreateAndAddWidget(m_MainWidget.get(), (GtkWidget*)m_Window);
gtk_widget_show_all((GtkWidget*)m_Window);
gtk_main();
}
void Window::Close()
{
gtk_widget_hide((GtkWidget*)m_Window);
gtk_main_quit();
}
void Window::CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget)
{
// Create this widget
widget->Create();
// Add
gtk_container_add((GtkContainer*)parentWidget, widget->Get());
for (auto& child : widget->GetChilds())
{
CreateAndAddWidget(child.get(), widget->Get());
}
}
void Window::SetMargin(Anchor anchor, int32_t margin)
{
if (FLAG_CHECK(anchor, Anchor::Left))
{
gtk_layer_set_margin(m_Window, GTK_LAYER_SHELL_EDGE_LEFT, margin);
}
if (FLAG_CHECK(anchor, Anchor::Right))
{
gtk_layer_set_margin(m_Window, GTK_LAYER_SHELL_EDGE_RIGHT, margin);
}
if (FLAG_CHECK(anchor, Anchor::Top))
{
gtk_layer_set_margin(m_Window, GTK_LAYER_SHELL_EDGE_TOP, margin);
}
if (FLAG_CHECK(anchor, Anchor::Bottom))
{
gtk_layer_set_margin(m_Window, GTK_LAYER_SHELL_EDGE_BOTTOM, margin);
}
}

44
src/Window.h Normal file
View file

@ -0,0 +1,44 @@
#pragma once
#include <gtk/gtk.h>
#include "Widget.h"
#include "Common.h"
enum class Anchor
{
Top = BIT(0),
Bottom = BIT(1),
Left = BIT(2),
Right = BIT(3)
};
DEFINE_ENUM_FLAGS(Anchor);
class Window
{
public:
Window() = default;
Window(std::unique_ptr<Widget>&& mainWidget, int32_t monitor);
Window(Window&& window) noexcept = default;
Window& operator=(Window&& other) noexcept = default;
~Window();
void Run(int argc, char** argv);
void Close();
void SetAnchor(Anchor anchor) { m_Anchor = anchor; }
void SetMargin(Anchor anchor, int32_t margin);
void SetExclusive(bool exclusive) { m_Exclusive = exclusive; }
private:
void CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget);
GtkWindow* m_Window;
GtkApplication* m_App = nullptr;
std::unique_ptr<Widget> m_MainWidget;
Anchor m_Anchor;
bool m_Exclusive = true;
int32_t m_Monitor;
};

55
src/gBar.cpp Normal file
View file

@ -0,0 +1,55 @@
#include "Window.h"
#include "Common.h"
#include "System.h"
#include "Bar.h"
#include "AudioFlyin.h"
#include <gtk/gtk.h>
#include <gtk-layer-shell.h>
#include <cmath>
#include <cstdio>
#include <unistd.h>
const char* audioTmpFileOpen = "/tmp/gBar__audio";
int main(int argc, char** argv)
{
System::Init();
int32_t monitor = -1;
if (argc >= 3)
{
monitor = atoi(argv[2]);
}
Window window;
ASSERT(argc >= 2, "Too little arguments!");
if (strcmp(argv[1], "bar") == 0)
{
Bar::Create(window, monitor);
}
else if (strcmp(argv[1], "audio") == 0)
{
if (access(audioTmpFileOpen, F_OK) != 0)
{
FILE* audioTempFile = fopen(audioTmpFileOpen, "w");
AudioFlyin::Create(window, monitor);
fclose(audioTempFile);
}
else
{
// Already open, close
LOG("Audio already open");
exit(0);
}
}
window.Run(argc, argv);
System::FreeResources();
if (strcmp(argv[1], "audio") == 0)
{
remove(audioTmpFileOpen);
}
return 0;
}