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

View file

@ -25,7 +25,14 @@ pulse = dependency('libpulse')
executable( executable(
'gBar', '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], dependencies: [gtk, gtk_layer_shell, pulse],
install: true install: true
) )

View file

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

View file

@ -60,3 +60,26 @@ namespace Utils
return buf; 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 <string>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <thread>
#include <gio/gio.h> #include <gio/gio.h>
@ -16,6 +17,7 @@
#include <dlfcn.h> #include <dlfcn.h>
#include <sys/statvfs.h> #include <sys/statvfs.h>
#include <unistd.h>
namespace System namespace System
{ {
@ -207,16 +209,24 @@ namespace System
} }
else if (strstr(type, "org.bluez.Device1")) else if (strstr(type, "org.bluez.Device1"))
{ {
std::string deviceMac;
std::string deviceName; std::string deviceName;
std::string deviceType; std::string deviceType;
bool connected = false; bool connected = false;
bool paired = false;
// This is a device -> One "client" // This is a device -> One "client"
char* str = nullptr; char* str = nullptr;
GVariant* var = nullptr; GVariant* var = nullptr;
while (g_variant_iter_next(propIter, "{sv}", &str, &var)) 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); const char* name = g_variant_get_string(var, nullptr);
// Copy it for us // Copy it for us
@ -232,13 +242,14 @@ namespace System
{ {
connected = g_variant_get_boolean(var); connected = g_variant_get_boolean(var);
} }
else if (strstr(str, "Paired"))
{
paired = g_variant_get_boolean(var);
}
g_free(str); g_free(str);
g_variant_unref(var); g_variant_unref(var);
} }
if (connected) out.devices.push_back(BluetoothDevice{connected, paired, std::move(deviceMac), std::move(deviceName), std::move(deviceType)});
{
out.devices.push_back(BluetoothDevice{std::move(deviceName), std::move(deviceType)});
}
} }
g_variant_iter_free(propIter); g_variant_iter_free(propIter);
g_free(type); g_free(type);
@ -251,6 +262,76 @@ namespace System
return out; 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 #endif
AudioInfo GetAudioInfo() AudioInfo GetAudioInfo()
@ -322,7 +403,6 @@ namespace System
#endif #endif
} }
void Init() void Init()
{ {
#ifdef HAS_NVIDIA #ifdef HAS_NVIDIA
@ -336,5 +416,8 @@ namespace System
NvidiaGPU::Shutdown(); NvidiaGPU::Shutdown();
#endif #endif
PulseAudio::Shutdown(); PulseAudio::Shutdown();
#ifdef HAS_BLUEZ
StopScan();
#endif
} }
} }

View file

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <functional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -42,6 +43,9 @@ namespace System
#ifdef HAS_BLUEZ #ifdef HAS_BLUEZ
struct BluetoothDevice struct BluetoothDevice
{ {
bool connected;
bool paired;
std::string mac;
std::string name; std::string name;
// Known types: input-[keyboard,mouse]; audio-headset // Known types: input-[keyboard,mouse]; audio-headset
std::string type; std::string type;
@ -53,6 +57,14 @@ namespace System
std::vector<BluetoothDevice> devices; std::vector<BluetoothDevice> devices;
}; };
BluetoothInfo GetBluetoothInfo(); 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 #endif
struct AudioInfo struct AudioInfo

View file

@ -45,6 +45,21 @@ Widget::~Widget()
gtk_widget_destroy(m_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) void Widget::SetClass(const std::string& cssClass)
{ {
if (m_Widget) if (m_Widget)
@ -55,6 +70,22 @@ void Widget::SetClass(const std::string& cssClass)
} }
m_CssClass = 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) 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) void Widget::AddChild(std::unique_ptr<Widget>&& widget)
{ {
if (m_Widget)
{
CreateAndAddWidget(widget.get(), m_Widget);
}
m_Childs.push_back(std::move(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) void Widget::SetVisible(bool visible)
{ {
gtk_widget_set_visible(m_Widget, visible); gtk_widget_set_visible(m_Widget, visible);
@ -281,7 +327,8 @@ void Button::Create()
auto clickFn = [](UNUSED GtkButton* gtkButton, void* data) auto clickFn = [](UNUSED GtkButton* gtkButton, void* data)
{ {
Button* button = (Button*)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); g_signal_connect(m_Widget, "clicked", G_CALLBACK(+clickFn), this);
ApplyPropertiesToWidget(); ApplyPropertiesToWidget();

View file

@ -81,7 +81,11 @@ public:
return std::make_unique<TWidget>(); return std::make_unique<TWidget>();
} }
static void CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget);
void SetClass(const std::string& cssClass); void SetClass(const std::string& cssClass);
void AddClass(const std::string& cssClass);
void RemoveClass(const std::string& cssClass);
void SetVerticalTransform(const Transform& transform); void SetVerticalTransform(const Transform& transform);
void SetHorizontalTransform(const Transform& transform); void SetHorizontalTransform(const Transform& transform);
void SetTooltip(const std::string& tooltip); void SetTooltip(const std::string& tooltip);
@ -89,6 +93,10 @@ public:
virtual void Create() = 0; virtual void Create() = 0;
void AddChild(std::unique_ptr<Widget>&& widget); 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> template<typename TWidget>
void AddTimer(TimerCallback<TWidget>&& callback, uint32_t timeoutMS) 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); gtk_layer_set_anchor(m_Window, GTK_LAYER_SHELL_EDGE_BOTTOM, true);
} }
UpdateMargin();
// Create widgets // Create widgets
CreateAndAddWidget(m_MainWidget.get(), (GtkWidget*)m_Window); Widget::CreateAndAddWidget(m_MainWidget.get(), (GtkWidget*)m_Window);
gtk_widget_show_all((GtkWidget*)m_Window); gtk_widget_show_all((GtkWidget*)m_Window);
@ -87,16 +88,26 @@ void Window::Close()
gtk_main_quit(); gtk_main_quit();
} }
void Window::CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget) void Window::UpdateMargin()
{ {
// Create this widget for (auto [anchor, margin] : m_Margin)
widget->Create();
// Add
gtk_container_add((GtkContainer*)parentWidget, widget->Get());
for (auto& child : widget->GetChilds())
{ {
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)) 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)) 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)) 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)) 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; } void SetExclusive(bool exclusive) { m_Exclusive = exclusive; }
private: private:
void CreateAndAddWidget(Widget* widget, GtkWidget* parentWidget); void UpdateMargin();
GtkWindow* m_Window; GtkWindow* m_Window;
GtkApplication* m_App = nullptr; GtkApplication* m_App = nullptr;
@ -38,6 +38,7 @@ private:
std::unique_ptr<Widget> m_MainWidget; std::unique_ptr<Widget> m_MainWidget;
Anchor m_Anchor; Anchor m_Anchor;
std::array<std::pair<Anchor, int32_t>, 4> m_Margin;
bool m_Exclusive = true; bool m_Exclusive = true;
int32_t m_Monitor; int32_t m_Monitor;

View file

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