Added a bluetooth widget

I think it looks really slick. Probably still many bugs
This commit is contained in:
scorpion-26 2023-01-14 23:18:34 +01:00
parent 7b91486441
commit ed32d70916
11 changed files with 245 additions and 25 deletions

View file

@ -41,6 +41,10 @@ gBar bar 0
```
gBar audio [monitor]
```
*Open bluetooth widget*
```
gBar bluetooth [monitor]
```
## Gallery
![The bar with default css](/assets/bar.png)
@ -51,6 +55,10 @@ gBar audio [monitor]
*Audio widget with default css*
![The bluetooth widget with default css](/assets/bt.png)
*Bluetooth widget with default css*
## Features / Widgets
Bar:
- Workspaces (Hyprland only)

View file

@ -25,7 +25,14 @@ pulse = dependency('libpulse')
executable(
'gBar',
['src/gBar.cpp', 'src/Window.cpp', 'src/Widget.cpp', 'src/System.cpp', 'src/Bar.cpp', 'src/AudioFlyin.cpp'],
['src/gBar.cpp',
'src/Window.cpp',
'src/Widget.cpp',
'src/System.cpp',
'src/Bar.cpp',
'src/AudioFlyin.cpp',
'src/BluetoothDevices.cpp',
],
dependencies: [gtk, gtk_layer_shell, pulse],
install: true
)

View file

@ -77,7 +77,7 @@ namespace Bar
}
#ifdef HAS_BLUEZ
static Text* btIconText;
static Button* btIconText;
static Text* btDevText;
static TimerResult UpdateBluetooth(Box&)
{
@ -102,6 +102,8 @@ namespace Bar
std::string tooltip;
for (auto& dev : info.devices)
{
if (!dev.connected)
continue;
std::string ico = "";
if (dev.type == "input-keyboard")
{
@ -126,6 +128,11 @@ namespace Bar
}
return TimerResult::Ok;
}
void OnBTClick(Button&)
{
System::OpenBTWidget();
}
#endif
void OnChangeVolume(Slider&, double value)
@ -265,7 +272,8 @@ namespace Bar
DynCtx::btDevText = devText.get();
devText->SetClass("bt-num");
auto iconText = Widget::Create<Text>();
auto iconText = Widget::Create<Button>();
iconText->OnClick(DynCtx::OnBTClick);
DynCtx::btIconText = iconText.get();
box->AddChild(std::move(devText));

View file

@ -60,3 +60,26 @@ namespace Utils
return buf;
}
}
struct Process
{
pid_t pid;
};
template<typename... Args>
inline Process OpenProcess(Args... args)
{
pid_t child = fork();
ASSERT(child != -1, "fork error");
if (child == 0)
{
// Child
execl(args...);
exit(0);
}
else
{
return {child};
}
}

View file

@ -9,6 +9,7 @@
#include <string>
#include <sstream>
#include <iomanip>
#include <thread>
#include <gio/gio.h>
@ -16,6 +17,7 @@
#include <dlfcn.h>
#include <sys/statvfs.h>
#include <unistd.h>
namespace System
{
@ -207,16 +209,24 @@ namespace System
}
else if (strstr(type, "org.bluez.Device1"))
{
std::string deviceMac;
std::string deviceName;
std::string deviceType;
bool connected = false;
bool paired = 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"))
if (strcmp(str, "Address") == 0)
{
const char* mac = g_variant_get_string(var, nullptr);
// Copy it for us
deviceMac = mac;
}
else if (strstr(str, "Name"))
{
const char* name = g_variant_get_string(var, nullptr);
// Copy it for us
@ -232,13 +242,14 @@ namespace System
{
connected = g_variant_get_boolean(var);
}
else if (strstr(str, "Paired"))
{
paired = 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)});
}
out.devices.push_back(BluetoothDevice{connected, paired, std::move(deviceMac), std::move(deviceName), std::move(deviceType)});
}
g_variant_iter_free(propIter);
g_free(type);
@ -251,6 +262,76 @@ namespace System
return out;
}
static Process btctlProcess{-1};
void StartScan()
{
StopScan();
btctlProcess = OpenProcess("/bin/sh", "/bin/sh", "-c", "bluetoothctl scan on", NULL);
}
void StopScan()
{
if (btctlProcess.pid != -1)
{
// Ctrl-C stops bluetoothctl
kill(btctlProcess.pid, SIGINT);
btctlProcess = {-1};
}
}
void Connect(BluetoothDevice& device, std::function<void(bool, BluetoothDevice&)> onFinish)
{
auto thread = [&, mac = device.mac, onFinish]()
{
// 1. Pair
if (!device.paired)
{
int success = system(("bluetoothctl pair " + mac).c_str());
if (success != 0)
{
onFinish(false, device);
return;
}
}
// 2. Connect
if (!device.connected)
{
int success = system(("bluetoothctl connect " + mac).c_str());
if (success != 0)
{
onFinish(false, device);
return;
}
}
onFinish(true, device);
};
std::thread worker(thread);
worker.detach();
}
void Disconnect(BluetoothDevice& device, std::function<void(bool, BluetoothDevice&)> onFinish)
{
auto thread = [&, mac = device.mac, onFinish]()
{
// 1. Disconnect
if (device.connected)
{
int success = system(("bluetoothctl disconnect " + mac).c_str());
if (success != 0)
{
onFinish(false, device);
return;
}
}
onFinish(true, device);
};
std::thread worker(thread);
worker.detach();
}
void OpenBTWidget()
{
OpenProcess("/bin/sh", "/bin/sh", "-c", "gBar bluetooth");
}
#endif
AudioInfo GetAudioInfo()
@ -322,7 +403,6 @@ namespace System
#endif
}
void Init()
{
#ifdef HAS_NVIDIA
@ -336,5 +416,8 @@ namespace System
NvidiaGPU::Shutdown();
#endif
PulseAudio::Shutdown();
#ifdef HAS_BLUEZ
StopScan();
#endif
}
}

View file

@ -1,4 +1,5 @@
#pragma once
#include <functional>
#include <string>
#include <vector>
@ -42,6 +43,9 @@ namespace System
#ifdef HAS_BLUEZ
struct BluetoothDevice
{
bool connected;
bool paired;
std::string mac;
std::string name;
// Known types: input-[keyboard,mouse]; audio-headset
std::string type;
@ -53,6 +57,14 @@ namespace System
std::vector<BluetoothDevice> devices;
};
BluetoothInfo GetBluetoothInfo();
void StartScan();
void StopScan();
// MT functions, callback, is from different thread
void Connect(BluetoothDevice& device, std::function<void(bool, BluetoothDevice&)> onFinish);
void Disconnect(BluetoothDevice& device, std::function<void(bool, BluetoothDevice&)> onFinish);
void OpenBTWidget();
#endif
struct AudioInfo

View file

@ -45,6 +45,21 @@ Widget::~Widget()
gtk_widget_destroy(m_Widget);
}
void Widget::CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget)
{
// Create this widget
widget->Create();
// Add
gtk_container_add((GtkContainer*)parentWidget, widget->Get());
gtk_widget_show(widget->m_Widget);
for (auto& child : widget->GetChilds())
{
CreateAndAddWidget(child.get(), widget->Get());
}
}
void Widget::SetClass(const std::string& cssClass)
{
if (m_Widget)
@ -55,6 +70,22 @@ void Widget::SetClass(const std::string& cssClass)
}
m_CssClass = cssClass;
}
void Widget::AddClass(const std::string& cssClass)
{
if (m_Widget)
{
auto style = gtk_widget_get_style_context(m_Widget);
gtk_style_context_add_class(style, cssClass.c_str());
}
}
void Widget::RemoveClass(const std::string& cssClass)
{
if (m_Widget)
{
auto style = gtk_widget_get_style_context(m_Widget);
gtk_style_context_remove_class(style, cssClass.c_str());
}
}
void Widget::SetVerticalTransform(const Transform& transform)
{
@ -77,9 +108,24 @@ void Widget::SetTooltip(const std::string& tooltip)
void Widget::AddChild(std::unique_ptr<Widget>&& widget)
{
if (m_Widget)
{
CreateAndAddWidget(widget.get(), m_Widget);
}
m_Childs.push_back(std::move(widget));
}
void Widget::RemoveChild(size_t idx)
{
ASSERT(idx < m_Childs.size(), "RemoveChild: Invalid index");
if (m_Widget)
{
auto& child = *m_Childs[idx];
gtk_container_remove((GtkContainer*)child.m_Widget, m_Widget);
}
m_Childs.erase(m_Childs.begin() + idx);
}
void Widget::SetVisible(bool visible)
{
gtk_widget_set_visible(m_Widget, visible);
@ -281,7 +327,8 @@ void Button::Create()
auto clickFn = [](UNUSED GtkButton* gtkButton, void* data)
{
Button* button = (Button*)data;
button->m_OnClick(*button);
if (button->m_OnClick)
button->m_OnClick(*button);
};
g_signal_connect(m_Widget, "clicked", G_CALLBACK(+clickFn), this);
ApplyPropertiesToWidget();

View file

@ -41,7 +41,7 @@ enum class TransitionType
SlideDown
};
struct Transition
struct Transition
{
TransitionType type;
uint32_t durationMS;
@ -81,7 +81,11 @@ public:
return std::make_unique<TWidget>();
}
static void CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget);
void SetClass(const std::string& cssClass);
void AddClass(const std::string& cssClass);
void RemoveClass(const std::string& cssClass);
void SetVerticalTransform(const Transform& transform);
void SetHorizontalTransform(const Transform& transform);
void SetTooltip(const std::string& tooltip);
@ -89,6 +93,10 @@ public:
virtual void Create() = 0;
void AddChild(std::unique_ptr<Widget>&& widget);
void RemoveChild(size_t idx);
std::vector<std::unique_ptr<Widget>>& GetWidgets() {return m_Childs;}
template<typename TWidget>
void AddTimer(TimerCallback<TWidget>&& callback, uint32_t timeoutMS)
{

View file

@ -72,9 +72,10 @@ void Window::Run(int argc, char** argv)
{
gtk_layer_set_anchor(m_Window, GTK_LAYER_SHELL_EDGE_BOTTOM, true);
}
UpdateMargin();
// Create widgets
CreateAndAddWidget(m_MainWidget.get(), (GtkWidget*)m_Window);
Widget::CreateAndAddWidget(m_MainWidget.get(), (GtkWidget*)m_Window);
gtk_widget_show_all((GtkWidget*)m_Window);
@ -87,16 +88,26 @@ void Window::Close()
gtk_main_quit();
}
void Window::CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget)
void Window::UpdateMargin()
{
// Create this widget
widget->Create();
// Add
gtk_container_add((GtkContainer*)parentWidget, widget->Get());
for (auto& child : widget->GetChilds())
for (auto [anchor, margin] : m_Margin)
{
CreateAndAddWidget(child.get(), widget->Get());
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);
}
}
}
@ -104,18 +115,23 @@ 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);
m_Margin[0] = {Anchor::Left, margin};
}
if (FLAG_CHECK(anchor, Anchor::Right))
{
gtk_layer_set_margin(m_Window, GTK_LAYER_SHELL_EDGE_RIGHT, margin);
m_Margin[1] = {Anchor::Right, margin};
}
if (FLAG_CHECK(anchor, Anchor::Top))
{
gtk_layer_set_margin(m_Window, GTK_LAYER_SHELL_EDGE_TOP, margin);
m_Margin[2] = {Anchor::Top, margin};
}
if (FLAG_CHECK(anchor, Anchor::Bottom))
{
gtk_layer_set_margin(m_Window, GTK_LAYER_SHELL_EDGE_BOTTOM, margin);
m_Margin[2] = {Anchor::Bottom, margin};
}
if (m_Window)
{
UpdateMargin();
}
}

View file

@ -30,7 +30,7 @@ public:
void SetExclusive(bool exclusive) { m_Exclusive = exclusive; }
private:
void CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget);
void UpdateMargin();
GtkWindow* m_Window;
GtkApplication* m_App = nullptr;
@ -38,6 +38,7 @@ private:
std::unique_ptr<Widget> m_MainWidget;
Anchor m_Anchor;
std::array<std::pair<Anchor, int32_t>, 4> m_Margin;
bool m_Exclusive = true;
int32_t m_Monitor;

View file

@ -3,6 +3,7 @@
#include "System.h"
#include "Bar.h"
#include "AudioFlyin.h"
#include "BluetoothDevices.h"
#include <gtk/gtk.h>
#include <gtk-layer-shell.h>
@ -43,6 +44,12 @@ int main(int argc, char** argv)
exit(0);
}
}
#ifdef HAS_BLUEZ
else if (strcmp(argv[1], "bluetooth") == 0)
{
BluetoothDevices::Create(window, monitor);
}
#endif
window.Run(argc, argv);