mirror of
https://github.com/scorpion-26/gBar.git
synced 2024-11-23 11:42:51 +00:00
Initial commit!
This commit is contained in:
commit
b0ed0d17e5
26 changed files with 2702 additions and 0 deletions
36
.clang-format
Normal file
36
.clang-format
Normal 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
7
LICENSE
Normal 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
83
README.md
Normal 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
BIN
assets/audioflyin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
BIN
assets/bar.png
Normal file
BIN
assets/bar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
21
css/dracula/LICENSE
Normal file
21
css/dracula/LICENSE
Normal 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
185
css/style.css
Normal 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
1
css/style.css.map
Normal 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
207
css/style.scss
Normal 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
31
meson.build
Normal 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
6
meson_options.txt
Normal 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
105
src/AudioFlyin.cpp
Normal 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
8
src/AudioFlyin.h
Normal 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
444
src/Bar.cpp
Normal 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
8
src/Bar.h
Normal 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
55
src/Common.h
Normal 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
95
src/Hyprland.h
Normal 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
77
src/NvidiaGPU.h
Normal 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
106
src/PulseAudio.h
Normal 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
336
src/System.cpp
Normal 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
89
src/System.h
Normal 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
332
src/Widget.cpp
Normal 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
250
src/Widget.h
Normal 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
121
src/Window.cpp
Normal 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
44
src/Window.h
Normal 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
55
src/gBar.cpp
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue