mirror of
https://github.com/scorpion-26/gBar.git
synced 2024-11-22 03:02:49 +00:00
SNI implementation for tray icons 🎉 (#12)
Implementation for SNI (StatusNotifierItem) d-bus protocol for tray icons. Implements https://github.com/scorpion-26/gBar/issues/5
This commit is contained in:
commit
ea857e5ff6
22 changed files with 844 additions and 30 deletions
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
|
@ -19,10 +19,12 @@ jobs:
|
||||||
pacman-key --populate archlinux
|
pacman-key --populate archlinux
|
||||||
- name: Download pacman packages
|
- name: Download pacman packages
|
||||||
run: |
|
run: |
|
||||||
pacman -Syu --noconfirm base-devel gcc git ninja meson gtk-layer-shell pulseaudio wayland
|
pacman -Syu --noconfirm base-devel gcc git ninja meson gtk-layer-shell pulseaudio wayland libdbusmenu-gtk3
|
||||||
|
|
||||||
- name: Download gBar
|
- name: Download gBar
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v3.3.0
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Run meson
|
- name: Run meson
|
||||||
run: |
|
run: |
|
||||||
|
@ -35,14 +37,14 @@ jobs:
|
||||||
name: Build using Nix
|
name: Build using Nix
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Download gBar
|
|
||||||
uses: actions/checkout@v3.3.0
|
|
||||||
|
|
||||||
- name: Install Nix
|
- name: Install Nix
|
||||||
uses: cachix/install-nix-action@v20
|
uses: cachix/install-nix-action@v20
|
||||||
|
|
||||||
|
- name: Download gBar
|
||||||
|
uses: actions/checkout@v3.3.0
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Build Nix flake
|
- name: Build Nix flake
|
||||||
run: |
|
run: |
|
||||||
nix build --print-build-logs
|
nix build --print-build-logs
|
||||||
|
|
||||||
|
|
||||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "thirdparty/stb"]
|
||||||
|
path = thirdparty/stb
|
||||||
|
url = https://github.com/nothings/stb
|
|
@ -88,6 +88,7 @@ Bar:
|
||||||
- Disk: Free/Total
|
- Disk: Free/Total
|
||||||
- Network: Current upload and download speed
|
- Network: Current upload and download speed
|
||||||
- Update checking (Non-Arch systems need to be configured manually)
|
- Update checking (Non-Arch systems need to be configured manually)
|
||||||
|
- Tray icons
|
||||||
|
|
||||||
Bluetooth:
|
Bluetooth:
|
||||||
- Scanning of nearby bluetooth devices
|
- Scanning of nearby bluetooth devices
|
||||||
|
@ -162,3 +163,10 @@ See *Configuration for your system*
|
||||||
### The icons are not showing!
|
### 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). You _will_ a Nerd Font with version 2.3.0 or newer (For more details see [this comment](https://github.com/scorpion-26/gBar/issues/5#issuecomment-1442037005))
|
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). You _will_ a Nerd Font with version 2.3.0 or newer (For more details see [this comment](https://github.com/scorpion-26/gBar/issues/5#issuecomment-1442037005))
|
||||||
|
|
||||||
|
### The tray doesn't show
|
||||||
|
Some apps sometimes don't actively query for tray applications. A fix for this is to start gBar before the tray app
|
||||||
|
If it still doesn't show, please open an issue with your application
|
||||||
|
The tray icons are confirmed to work with Discord, Telegram, OBS and KeePassXC
|
||||||
|
|
||||||
|
### Clicking on the tray opens a glitchy transparent menu
|
||||||
|
This is semi-intentional and a known bug (See https://github.com/scorpion-26/gBar/pull/12#issuecomment-1529143790 for an explanation). You can make it opaque by setting the background-color property of .popup in style.css/style.scss
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
font-family: "CaskaydiaCove Nerd Font";
|
font-family: "CaskaydiaCove Nerd Font";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
|
||||||
.bar, tooltip {
|
.bar, tooltip {
|
||||||
background-color: #282a36;
|
background-color: #282a36;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
|
|
@ -27,6 +27,10 @@ $textsize: 16px;
|
||||||
//background-color: #00cc00
|
//background-color: #00cc00
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
color: #50fa7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.bar,tooltip{
|
.bar,tooltip{
|
||||||
background-color: $bg;
|
background-color: $bg;
|
||||||
|
|
14
data/config
14
data/config
|
@ -71,6 +71,20 @@ NetworkAdapter: eno1
|
||||||
# Disables the network widget when set to false
|
# Disables the network widget when set to false
|
||||||
NetworkWidget: true
|
NetworkWidget: true
|
||||||
|
|
||||||
|
# Enables tray icons
|
||||||
|
EnableSNI: true
|
||||||
|
|
||||||
|
# SNIIconSize sets the icon size for a SNI icon.
|
||||||
|
# SNIPaddingTop Can be used to push the Icon down. Negative values are allowed
|
||||||
|
# For both: The first parameter is a filter of the tooltip(The text that pops up, when the icon is hovered) of the icon
|
||||||
|
|
||||||
|
# Scale everything down to 25 pixels ('*' as filter means everything)
|
||||||
|
SNIIconSize: *, 25
|
||||||
|
# Explicitly make OBS a bit smaller than default
|
||||||
|
SNIIconSize: OBS, 23
|
||||||
|
# Nudges the Discord icon a bit down
|
||||||
|
# SNIPaddingTop: Discord, 5
|
||||||
|
|
||||||
# These set the range for the network widget. The widget changes colors at six intervals:
|
# These set the range for the network widget. The widget changes colors at six intervals:
|
||||||
# - Below Min...Bytes ("under")
|
# - Below Min...Bytes ("under")
|
||||||
# - Between ]0%;25%]. 0% = Min...Bytes; 100% = Max...Bytes ("low")
|
# - Between ]0%;25%]. 0% = Min...Bytes; 100% = Max...Bytes ("low")
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
gtk3
|
gtk3
|
||||||
gtk-layer-shell
|
gtk-layer-shell
|
||||||
libpulseaudio
|
libpulseaudio
|
||||||
|
stb
|
||||||
|
libdbusmenu-gtk3
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
in {
|
in {
|
||||||
|
|
83
meson.build
83
meson.build
|
@ -2,7 +2,7 @@ project('gBar',
|
||||||
['c', 'cpp'],
|
['c', 'cpp'],
|
||||||
version: '0.0.1',
|
version: '0.0.1',
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
meson_version: '>=0.49.0',
|
meson_version: '>=0.53.0',
|
||||||
default_options: ['cpp_std=c++17',
|
default_options: ['cpp_std=c++17',
|
||||||
'warning_level=3',
|
'warning_level=3',
|
||||||
'default_library=static',
|
'default_library=static',
|
||||||
|
@ -26,6 +26,8 @@ ext_workspace_header = custom_target('generate-ext-workspace-header',
|
||||||
gtk = dependency('gtk+-3.0')
|
gtk = dependency('gtk+-3.0')
|
||||||
gtk_layer_shell = dependency('gtk-layer-shell-0')
|
gtk_layer_shell = dependency('gtk-layer-shell-0')
|
||||||
|
|
||||||
|
pulse = dependency('libpulse')
|
||||||
|
|
||||||
headers = [
|
headers = [
|
||||||
'src/Common.h',
|
'src/Common.h',
|
||||||
'src/Log.h',
|
'src/Log.h',
|
||||||
|
@ -37,6 +39,25 @@ headers = [
|
||||||
'src/CSS.h'
|
'src/CSS.h'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
sources = [
|
||||||
|
ext_workspace_src,
|
||||||
|
ext_workspace_header,
|
||||||
|
'src/Window.cpp',
|
||||||
|
'src/Widget.cpp',
|
||||||
|
'src/System.cpp',
|
||||||
|
'src/Bar.cpp',
|
||||||
|
'src/Workspaces.cpp',
|
||||||
|
'src/AudioFlyin.cpp',
|
||||||
|
'src/BluetoothDevices.cpp',
|
||||||
|
'src/Plugin.cpp',
|
||||||
|
'src/Config.cpp',
|
||||||
|
'src/CSS.cpp',
|
||||||
|
'src/Log.cpp',
|
||||||
|
'src/SNI.cpp',
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [gtk, gtk_layer_shell, pulse, wayland_client ]
|
||||||
|
|
||||||
if get_option('WithHyprland')
|
if get_option('WithHyprland')
|
||||||
add_global_arguments('-DWITH_HYPRLAND', language: 'cpp')
|
add_global_arguments('-DWITH_HYPRLAND', language: 'cpp')
|
||||||
headers += 'src/Workspaces.h'
|
headers += 'src/Workspaces.h'
|
||||||
|
@ -59,27 +80,55 @@ endif
|
||||||
if get_option('WithSys')
|
if get_option('WithSys')
|
||||||
add_global_arguments('-DWITH_SYS', language: 'cpp')
|
add_global_arguments('-DWITH_SYS', language: 'cpp')
|
||||||
endif
|
endif
|
||||||
|
if get_option('WithSNI')
|
||||||
|
add_global_arguments('-DWITH_SNI', language: 'cpp')
|
||||||
|
|
||||||
|
# DBus protocols
|
||||||
|
dbus_gen = find_program('gdbus-codegen')
|
||||||
|
sni_item_src = custom_target('generate-sni-item-src',
|
||||||
|
input: ['protocols/sni-item.xml'],
|
||||||
|
output: ['sni-item.c'],
|
||||||
|
command: [dbus_gen, '--c-namespace', 'sni', '--body', '--output', '@OUTPUT@', '@INPUT@'])
|
||||||
|
|
||||||
|
sni_item_header = custom_target('generate-sni-item-header',
|
||||||
|
input: ['protocols/sni-item.xml'],
|
||||||
|
output: ['sni-item.h'],
|
||||||
|
command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@'])
|
||||||
|
|
||||||
|
sni_watcher_src = custom_target('generate-sni-watcher-src',
|
||||||
|
input: ['protocols/sni-watcher.xml'],
|
||||||
|
output: ['sni-watcher.c'],
|
||||||
|
command: [dbus_gen, '--c-namespace', 'sni', '--body', '--output', '@OUTPUT@', '@INPUT@'])
|
||||||
|
|
||||||
|
sni_watcher_header = custom_target('generate-sni-watcher-header',
|
||||||
|
input: ['protocols/sni-watcher.xml'],
|
||||||
|
output: ['sni-watcher.h'],
|
||||||
|
command: [dbus_gen, '--c-namespace', 'sni', '--header', '--output', '@OUTPUT@', '@INPUT@'])
|
||||||
|
libdbusmenu = dependency('dbusmenu-gtk3-0.4')
|
||||||
|
|
||||||
|
sources += sni_item_src
|
||||||
|
sources += sni_item_header
|
||||||
|
sources += sni_watcher_src
|
||||||
|
sources += sni_watcher_header
|
||||||
|
|
||||||
|
dependencies += libdbusmenu
|
||||||
|
endif
|
||||||
|
|
||||||
add_global_arguments('-DUSE_LOGFILE', language: 'cpp')
|
add_global_arguments('-DUSE_LOGFILE', language: 'cpp')
|
||||||
|
|
||||||
pulse = dependency('libpulse')
|
# stb
|
||||||
|
fs = import('fs')
|
||||||
|
stb = include_directories('thirdparty')
|
||||||
|
if fs.exists('thirdparty/stb/stb_image.h')
|
||||||
|
add_global_arguments('-DHAS_STB', language: 'cpp')
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
libgBar = library('gBar',
|
libgBar = library('gBar',
|
||||||
[ ext_workspace_src,
|
sources,
|
||||||
ext_workspace_header,
|
dependencies: dependencies,
|
||||||
'src/Window.cpp',
|
include_directories: stb,
|
||||||
'src/Widget.cpp',
|
|
||||||
'src/System.cpp',
|
|
||||||
'src/Bar.cpp',
|
|
||||||
'src/Workspaces.cpp',
|
|
||||||
'src/AudioFlyin.cpp',
|
|
||||||
'src/BluetoothDevices.cpp',
|
|
||||||
'src/Plugin.cpp',
|
|
||||||
'src/Config.cpp',
|
|
||||||
'src/CSS.cpp',
|
|
||||||
'src/Log.cpp',
|
|
||||||
],
|
|
||||||
dependencies: [gtk, gtk_layer_shell, pulse, wayland_client],
|
|
||||||
install: true)
|
install: true)
|
||||||
|
|
||||||
pkg = import('pkgconfig')
|
pkg = import('pkgconfig')
|
||||||
|
|
|
@ -4,6 +4,9 @@ option('WithHyprland', type: 'boolean', value : true)
|
||||||
# Workspaces general, enables Wayland protocol
|
# Workspaces general, enables Wayland protocol
|
||||||
option('WithWorkspaces', type: 'boolean', value : true)
|
option('WithWorkspaces', type: 'boolean', value : true)
|
||||||
|
|
||||||
|
# Tray icons, requires stb git submodule
|
||||||
|
option('WithSNI', type: 'boolean', value : true)
|
||||||
|
|
||||||
option('WithNvidia', type: 'boolean', value : true)
|
option('WithNvidia', type: 'boolean', value : true)
|
||||||
option('WithAMD', type: 'boolean', value : true)
|
option('WithAMD', type: 'boolean', value : true)
|
||||||
option('WithBlueZ', type: 'boolean', value : true)
|
option('WithBlueZ', type: 'boolean', value : true)
|
||||||
|
|
46
protocols/sni-item.xml
Normal file
46
protocols/sni-item.xml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||||
|
<node>
|
||||||
|
<interface name="org.kde.StatusNotifierItem">
|
||||||
|
<annotation name="org.gtk.GDBus.C.Name" value="item"/>
|
||||||
|
<property name="Category" type="s" access="read"/>
|
||||||
|
<property name="Id" type="s" access="read"/>
|
||||||
|
<property name="Title" type="s" access="read"/>
|
||||||
|
<property name="Status" type="s" access="read"/>
|
||||||
|
<property name="WindowId" type="u" access="read"/>
|
||||||
|
<property name="IconName" type="s" access="read"/>
|
||||||
|
<property name="IconPixmap" type="a(iiay)" access="read"/>
|
||||||
|
<property name="OverlayIconName" type="s" access="read"/>
|
||||||
|
<property name="OverlayIconPixmap" type="a(iiay)" access="read"/>
|
||||||
|
<property name="AttentionIconName" type="s" access="read"/>
|
||||||
|
<property name="AttentionIconPixmap" type="a(iiay)" access="read"/>
|
||||||
|
<property name="AttentionMovieName" type="s" access="read"/>
|
||||||
|
<property name="ToolTip" type="(sa(iiay)ss)" access="read"/>
|
||||||
|
<property name="ItemIsMenu" type="b" access="read"/>
|
||||||
|
<property name="Menu" type="o" access="read"/>
|
||||||
|
<method name="ContextMenu">
|
||||||
|
<arg type="i" direction="in" name="x"/>
|
||||||
|
<arg type="i" direction="in" name="y"/>
|
||||||
|
</method>
|
||||||
|
<method name="Activate">
|
||||||
|
<arg type="i" direction="in" name="x"/>
|
||||||
|
<arg type="i" direction="in" name="y"/>
|
||||||
|
</method>
|
||||||
|
<method name="SecondaryActivate">
|
||||||
|
<arg type="i" direction="in" name="x"/>
|
||||||
|
<arg type="i" direction="in" name="y"/>
|
||||||
|
</method>
|
||||||
|
<method name="Scroll">
|
||||||
|
<arg type="i" direction="in" name="delta"/>
|
||||||
|
<arg type="s" direction="in" name="orientation"/>
|
||||||
|
</method>
|
||||||
|
<signal name="NewTitle"/>
|
||||||
|
<signal name="NewIcon"/>
|
||||||
|
<signal name="NewAttentionIcon"/>
|
||||||
|
<signal name="NewOverlayIcon"/>
|
||||||
|
<signal name="NewToolTip"/>
|
||||||
|
<signal name="NewStatus">
|
||||||
|
<arg type="s" direction="in" name="status"/>
|
||||||
|
</signal>
|
||||||
|
</interface>
|
||||||
|
</node>
|
23
protocols/sni-watcher.xml
Normal file
23
protocols/sni-watcher.xml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||||
|
<node>
|
||||||
|
<interface name="org.kde.StatusNotifierWatcher">
|
||||||
|
<annotation name="org.gtk.GDBus.C.Name" value="watcher"/>
|
||||||
|
<property name="RegisteredStatusNotifierItems" type="as" access="read"/>
|
||||||
|
<property name="IsStatusNotifierHostRegistered" type="b" access="read"/>
|
||||||
|
<property name="ProtocolVersion" type="i" access="read"/>
|
||||||
|
<method name="RegisterStatusNotifierItem">
|
||||||
|
<arg type="s" direction="in" name="service"/>
|
||||||
|
</method>
|
||||||
|
<method name="RegisterStatusNotifierHost">
|
||||||
|
<arg type="s" direction="in" name="service"/>
|
||||||
|
</method>
|
||||||
|
<signal name="StatusNotifierItemRegistered">
|
||||||
|
<arg type="s" direction="out" name="service"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="StatusNotifierItemUnregistered">
|
||||||
|
<arg type="s" direction="out" name="service"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="StatusNotifierHostRegistered"/>
|
||||||
|
</interface>
|
||||||
|
</node>
|
|
@ -3,6 +3,7 @@
|
||||||
#include "System.h"
|
#include "System.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "SNI.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
namespace Bar
|
namespace Bar
|
||||||
|
@ -646,6 +647,10 @@ namespace Bar
|
||||||
right->SetSpacing({8, false});
|
right->SetSpacing({8, false});
|
||||||
right->SetHorizontalTransform({-1, true, Alignment::Right});
|
right->SetHorizontalTransform({-1, true, Alignment::Right});
|
||||||
{
|
{
|
||||||
|
#ifdef WITH_SNI
|
||||||
|
SNI::WidgetSNI(*right);
|
||||||
|
#endif
|
||||||
|
|
||||||
WidgetPackages(*right);
|
WidgetPackages(*right);
|
||||||
|
|
||||||
WidgetAudio(*right);
|
WidgetAudio(*right);
|
||||||
|
|
|
@ -27,6 +27,14 @@ void ApplyProperty<uint32_t>(uint32_t& propertyToSet, const std::string_view& va
|
||||||
propertyToSet = atoi(valStr.c_str());
|
propertyToSet = atoi(valStr.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void ApplyProperty<int32_t>(int32_t& propertyToSet, const std::string_view& value)
|
||||||
|
{
|
||||||
|
// Why, C++?
|
||||||
|
std::string valStr = std::string(value);
|
||||||
|
propertyToSet = atoi(valStr.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
void ApplyProperty<double>(double& propertyToSet, const std::string_view& value)
|
void ApplyProperty<double>(double& propertyToSet, const std::string_view& value)
|
||||||
{
|
{
|
||||||
|
@ -53,6 +61,30 @@ void ApplyProperty<bool>(bool& propertyToSet, const std::string_view& value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void ApplyProperty<std::pair<std::string, uint32_t>>(std::pair<std::string, uint32_t>& propertyToSet, const std::string_view& value)
|
||||||
|
{
|
||||||
|
// TODO: Ignore escaped delimiter (e.g. \, is the same as ,)
|
||||||
|
const char delim = ',';
|
||||||
|
const char* whitespace = " \t";
|
||||||
|
size_t delimPos = value.find(delim);
|
||||||
|
if (delimPos == std::string::npos)
|
||||||
|
{
|
||||||
|
propertyToSet = {std::string(value), 0};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string_view before = value.substr(0, delimPos);
|
||||||
|
std::string_view after = value.substr(delimPos + 1);
|
||||||
|
|
||||||
|
// Strip whitespaces for before
|
||||||
|
size_t beginBefore = before.find_first_not_of(whitespace);
|
||||||
|
size_t endBefore = before.find_last_not_of(whitespace);
|
||||||
|
before = before.substr(beginBefore, endBefore - beginBefore + 1);
|
||||||
|
|
||||||
|
ApplyProperty(propertyToSet.first, before);
|
||||||
|
ApplyProperty(propertyToSet.second, after);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void AddConfigVar(const std::string& propertyName, T& propertyToSet, std::string_view line, bool& setConfig)
|
void AddConfigVar(const std::string& propertyName, T& propertyToSet, std::string_view line, bool& setConfig)
|
||||||
{
|
{
|
||||||
|
@ -147,6 +179,7 @@ void Config::Load()
|
||||||
AddConfigVar("WorkspaceScrollOnMonitor", config.workspaceScrollOnMonitor, lineView, foundProperty);
|
AddConfigVar("WorkspaceScrollOnMonitor", config.workspaceScrollOnMonitor, lineView, foundProperty);
|
||||||
AddConfigVar("WorkspaceScrollInvert", config.workspaceScrollInvert, lineView, foundProperty);
|
AddConfigVar("WorkspaceScrollInvert", config.workspaceScrollInvert, lineView, foundProperty);
|
||||||
AddConfigVar("UseHyprlandIPC", config.useHyprlandIPC, lineView, foundProperty);
|
AddConfigVar("UseHyprlandIPC", config.useHyprlandIPC, lineView, foundProperty);
|
||||||
|
AddConfigVar("EnableSNI", config.enableSNI, lineView, foundProperty);
|
||||||
|
|
||||||
AddConfigVar("MinUploadBytes", config.minUploadBytes, lineView, foundProperty);
|
AddConfigVar("MinUploadBytes", config.minUploadBytes, lineView, foundProperty);
|
||||||
AddConfigVar("MaxUploadBytes", config.maxUploadBytes, lineView, foundProperty);
|
AddConfigVar("MaxUploadBytes", config.maxUploadBytes, lineView, foundProperty);
|
||||||
|
@ -160,6 +193,21 @@ void Config::Load()
|
||||||
AddConfigVar("AudioMinVolume", config.audioMinVolume, lineView, foundProperty);
|
AddConfigVar("AudioMinVolume", config.audioMinVolume, lineView, foundProperty);
|
||||||
AddConfigVar("AudioMaxVolume", config.audioMaxVolume, lineView, foundProperty);
|
AddConfigVar("AudioMaxVolume", config.audioMaxVolume, lineView, foundProperty);
|
||||||
|
|
||||||
|
std::pair<std::string, uint32_t> buf;
|
||||||
|
bool hasntFoundProperty = !foundProperty;
|
||||||
|
AddConfigVar("SNIIconSize", buf, lineView, foundProperty);
|
||||||
|
if (foundProperty && hasntFoundProperty)
|
||||||
|
{
|
||||||
|
// This was found
|
||||||
|
config.sniIconSizes[buf.first] = buf.second;
|
||||||
|
}
|
||||||
|
hasntFoundProperty = !foundProperty;
|
||||||
|
AddConfigVar("SNIPaddingTop", buf, lineView, foundProperty);
|
||||||
|
if (foundProperty && hasntFoundProperty)
|
||||||
|
{
|
||||||
|
config.sniPaddingTop[buf.first] = buf.second;
|
||||||
|
}
|
||||||
|
|
||||||
if (foundProperty == false)
|
if (foundProperty == false)
|
||||||
{
|
{
|
||||||
LOG("Warning: unknown config var: " << lineView);
|
LOG("Warning: unknown config var: " << lineView);
|
||||||
|
|
12
src/Config.h
12
src/Config.h
|
@ -2,6 +2,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
class Config
|
class Config
|
||||||
{
|
{
|
||||||
|
@ -27,6 +28,7 @@ public:
|
||||||
bool workspaceScrollOnMonitor = true; // Scroll through workspaces on monitor instead of all
|
bool workspaceScrollOnMonitor = true; // Scroll through workspaces on monitor instead of all
|
||||||
bool workspaceScrollInvert = false; // Up = +1, instead of Up = -1
|
bool workspaceScrollInvert = false; // Up = +1, instead of Up = -1
|
||||||
bool useHyprlandIPC = false; // Use Hyprland IPC instead of ext_workspaces protocol (Less buggy, but also less performant)
|
bool useHyprlandIPC = false; // Use Hyprland IPC instead of ext_workspaces protocol (Less buggy, but also less performant)
|
||||||
|
bool enableSNI = true; // Enable tray icon
|
||||||
|
|
||||||
// Controls for color progression of the network widget
|
// Controls for color progression of the network widget
|
||||||
uint32_t minUploadBytes = 0; // Bottom limit of the network widgets upload. Everything below it is considered "under"
|
uint32_t minUploadBytes = 0; // Bottom limit of the network widgets upload. Everything below it is considered "under"
|
||||||
|
@ -38,6 +40,10 @@ public:
|
||||||
|
|
||||||
uint32_t checkUpdateInterval = 5 * 60; // Interval to run the "checkPackagesCommand". In seconds
|
uint32_t checkUpdateInterval = 5 * 60; // Interval to run the "checkPackagesCommand". In seconds
|
||||||
|
|
||||||
|
// SNIIconSize: ["Title String"], ["Size"]
|
||||||
|
std::unordered_map<std::string, uint32_t> sniIconSizes;
|
||||||
|
std::unordered_map<std::string, int32_t> sniPaddingTop;
|
||||||
|
|
||||||
// Only affects outputs (i.e.: speakers, not microphones). This remaps the range of the volume; In percent
|
// Only affects outputs (i.e.: speakers, not microphones). This remaps the range of the volume; In percent
|
||||||
double audioMinVolume = 0.f; // Map the minimum volume to this value
|
double audioMinVolume = 0.f; // Map the minimum volume to this value
|
||||||
double audioMaxVolume = 100.f; // Map the maximum volume to this value
|
double audioMaxVolume = 100.f; // Map the maximum volume to this value
|
||||||
|
@ -73,6 +79,12 @@ public:
|
||||||
bool hasBlueZ = false;
|
bool hasBlueZ = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined WITH_SNI && defined HAS_STB
|
||||||
|
bool hasSNI = true;
|
||||||
|
#else
|
||||||
|
bool hasSNI = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool hasNet = true;
|
bool hasNet = true;
|
||||||
|
|
||||||
bool hasPackagesScript = true;
|
bool hasPackagesScript = true;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#ifdef USE_LOGFILE
|
#ifdef USE_LOGFILE
|
||||||
#define LOG(x) \
|
#define LOG(x) \
|
||||||
|
|
485
src/SNI.cpp
Normal file
485
src/SNI.cpp
Normal file
|
@ -0,0 +1,485 @@
|
||||||
|
#include "SNI.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Widget.h"
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
#ifdef WITH_SNI
|
||||||
|
|
||||||
|
#include <sni-watcher.h>
|
||||||
|
#include <sni-item.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <libdbusmenu-gtk/menu.h>
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include <stb/stb_image.h>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace SNI
|
||||||
|
{
|
||||||
|
sniWatcher* watcherSkeleton;
|
||||||
|
guint watcherID;
|
||||||
|
GDBusConnection* dbusConnection = nullptr;
|
||||||
|
|
||||||
|
guint hostID;
|
||||||
|
|
||||||
|
struct Item
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string object;
|
||||||
|
size_t w;
|
||||||
|
size_t h;
|
||||||
|
uint8_t* iconData = nullptr;
|
||||||
|
|
||||||
|
std::string tooltip;
|
||||||
|
|
||||||
|
std::string menuObjectPath;
|
||||||
|
|
||||||
|
EventBox* gtkEvent;
|
||||||
|
};
|
||||||
|
std::vector<Item> items;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, Item> clientsToQuery;
|
||||||
|
|
||||||
|
// Gtk stuff, TODO: Allow more than one instance
|
||||||
|
// Simply removing the gtk_drawing_areas doesn't trigger proper redrawing
|
||||||
|
// HACK: Make an outer permanent and an inner box, which will be deleted and readded
|
||||||
|
Widget* parentBox;
|
||||||
|
Widget* iconBox;
|
||||||
|
|
||||||
|
static Item CreateItem(std::string&& name, std::string&& object)
|
||||||
|
{
|
||||||
|
Item item{};
|
||||||
|
item.name = name;
|
||||||
|
item.object = object;
|
||||||
|
auto getProperty = [&](const char* prop) -> GVariant*
|
||||||
|
{
|
||||||
|
GError* err = nullptr;
|
||||||
|
GVariant* params[2];
|
||||||
|
params[0] = g_variant_new_string("org.kde.StatusNotifierItem");
|
||||||
|
params[1] = g_variant_new_string(prop);
|
||||||
|
GVariant* param = g_variant_new_tuple(params, 2);
|
||||||
|
GVariant* res = g_dbus_connection_call_sync(dbusConnection, name.c_str(), object.c_str(), "org.freedesktop.DBus.Properties", "Get", param,
|
||||||
|
G_VARIANT_TYPE("(v)"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
g_error_free(err);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// There's probably a better method than to use 3 variants
|
||||||
|
// g_variant_unref(params[0]);
|
||||||
|
// g_variant_unref(params[1]);
|
||||||
|
// g_variant_unref(param);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
GVariant* iconPixmap = getProperty("IconPixmap");
|
||||||
|
if (iconPixmap)
|
||||||
|
{
|
||||||
|
// Only get first item
|
||||||
|
GVariant* arr = nullptr;
|
||||||
|
g_variant_get(iconPixmap, "(v)", &arr);
|
||||||
|
|
||||||
|
GVariantIter* arrIter = nullptr;
|
||||||
|
g_variant_get(arr, "a(iiay)", &arrIter);
|
||||||
|
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
GVariantIter* data = nullptr;
|
||||||
|
g_variant_iter_next(arrIter, "(iiay)", &width, &height, &data);
|
||||||
|
|
||||||
|
LOG("SNI: Width: " << width);
|
||||||
|
LOG("SNI: Height: " << height);
|
||||||
|
item.w = width;
|
||||||
|
item.h = height;
|
||||||
|
item.iconData = new uint8_t[width * height * 4];
|
||||||
|
|
||||||
|
uint8_t px = 0;
|
||||||
|
int i = 0;
|
||||||
|
while (g_variant_iter_next(data, "y", &px))
|
||||||
|
{
|
||||||
|
item.iconData[i] = px;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < width * height; i++)
|
||||||
|
{
|
||||||
|
struct Px
|
||||||
|
{
|
||||||
|
// This should be bgra...
|
||||||
|
// Since source is ARGB32 in network order(=big-endian)
|
||||||
|
// and x86 Linux is little-endian, we *should* swap b and r...
|
||||||
|
uint8_t a, r, g, b;
|
||||||
|
};
|
||||||
|
Px& pixel = ((Px*)item.iconData)[i];
|
||||||
|
// Swap to create rgba
|
||||||
|
pixel = {pixel.r, pixel.g, pixel.b, pixel.a};
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_iter_free(data);
|
||||||
|
g_variant_iter_free(arrIter);
|
||||||
|
g_variant_unref(arr);
|
||||||
|
g_variant_unref(iconPixmap);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Get icon theme path
|
||||||
|
GVariant* themePathVariant = getProperty("IconThemePath"); // Not defined by freedesktop, I think ayatana does this...
|
||||||
|
GVariant* iconNameVariant = getProperty("IconName");
|
||||||
|
|
||||||
|
std::string iconPath;
|
||||||
|
if (themePathVariant && iconNameVariant)
|
||||||
|
{
|
||||||
|
// Why GLib?
|
||||||
|
GVariant* themePathStr = nullptr;
|
||||||
|
g_variant_get(themePathVariant, "(v)", &themePathStr);
|
||||||
|
GVariant* iconNameStr = nullptr;
|
||||||
|
g_variant_get(iconNameVariant, "(v)", &iconNameStr);
|
||||||
|
|
||||||
|
const char* themePath = g_variant_get_string(themePathStr, nullptr);
|
||||||
|
const char* iconName = g_variant_get_string(iconNameStr, nullptr);
|
||||||
|
iconPath = std::string(themePath) + "/" + iconName + ".png"; // TODO: Find out if this is always png
|
||||||
|
|
||||||
|
g_variant_unref(themePathVariant);
|
||||||
|
g_variant_unref(themePathStr);
|
||||||
|
g_variant_unref(iconNameVariant);
|
||||||
|
g_variant_unref(iconNameStr);
|
||||||
|
}
|
||||||
|
else if (iconNameVariant)
|
||||||
|
{
|
||||||
|
GVariant* iconNameStr = nullptr;
|
||||||
|
g_variant_get(iconNameVariant, "(v)", &iconNameStr);
|
||||||
|
|
||||||
|
const char* iconName = g_variant_get_string(iconNameStr, nullptr);
|
||||||
|
iconPath = std::string(iconName);
|
||||||
|
|
||||||
|
g_variant_unref(iconNameVariant);
|
||||||
|
g_variant_unref(iconNameStr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG("SNI: Unknown path!");
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
int width, height, channels;
|
||||||
|
stbi_uc* pixels = stbi_load(iconPath.c_str(), &width, &height, &channels, STBI_rgb_alpha);
|
||||||
|
if (!pixels)
|
||||||
|
{
|
||||||
|
LOG("SNI: Cannot open " << iconPath);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
item.w = width;
|
||||||
|
item.h = height;
|
||||||
|
item.iconData = new uint8_t[width * height * 4];
|
||||||
|
// Already rgba32
|
||||||
|
memcpy(item.iconData, pixels, width * height * 4);
|
||||||
|
stbi_image_free(pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query tooltip(Steam e.g. doesn't have one)
|
||||||
|
GVariant* tooltip = getProperty("ToolTip");
|
||||||
|
if (tooltip)
|
||||||
|
{
|
||||||
|
GVariant* tooltipVar;
|
||||||
|
g_variant_get_child(tooltip, 0, "v", &tooltipVar);
|
||||||
|
const gchar* title;
|
||||||
|
// Both telegram and discord only set the "title" component
|
||||||
|
g_variant_get_child(tooltipVar, 2, "s", &title);
|
||||||
|
item.tooltip = title;
|
||||||
|
LOG("SNI: Title: " << item.tooltip);
|
||||||
|
g_variant_unref(tooltip);
|
||||||
|
g_variant_unref(tooltipVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query menu
|
||||||
|
GVariant* menuPath = getProperty("Menu");
|
||||||
|
if (menuPath)
|
||||||
|
{
|
||||||
|
GVariant* menuVariant;
|
||||||
|
g_variant_get_child(menuPath, 0, "v", &menuVariant);
|
||||||
|
const char* objectPath;
|
||||||
|
g_variant_get(menuVariant, "o", &objectPath);
|
||||||
|
LOG("SNI: Menu object path: " << objectPath);
|
||||||
|
|
||||||
|
item.menuObjectPath = objectPath;
|
||||||
|
|
||||||
|
g_variant_unref(menuVariant);
|
||||||
|
g_variant_unref(menuPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InvalidateWidget();
|
||||||
|
|
||||||
|
static void DBusNameVanished(GDBusConnection*, const char* name, void*)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(items.begin(), items.end(),
|
||||||
|
[&](const Item& item)
|
||||||
|
{
|
||||||
|
return item.name == name;
|
||||||
|
});
|
||||||
|
if (it != items.end())
|
||||||
|
{
|
||||||
|
LOG("SNI: " << name << " vanished!");
|
||||||
|
items.erase(it);
|
||||||
|
InvalidateWidget();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG("SNI: Cannot remove unregistered bus name " << name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ItemPropertyChanged(GDBusConnection*, const char*, const char* object, const char*, const char*, GVariant*, void* name)
|
||||||
|
{
|
||||||
|
std::string nameStr = (const char*)name;
|
||||||
|
LOG("SNI: Reloading " << (const char*)name << " " << object);
|
||||||
|
// We don't care about *what* changed, just remove and reload
|
||||||
|
auto it = std::find_if(items.begin(), items.end(),
|
||||||
|
[&](const Item& item)
|
||||||
|
{
|
||||||
|
return item.name == nameStr;
|
||||||
|
});
|
||||||
|
if (it != items.end())
|
||||||
|
{
|
||||||
|
items.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG("SNI: Coudn't remove item " << nameStr << " when reloading");
|
||||||
|
}
|
||||||
|
clientsToQuery[nameStr] = {nameStr, std::string(object)};
|
||||||
|
}
|
||||||
|
|
||||||
|
static TimerResult UpdateWidgets(Box&)
|
||||||
|
{
|
||||||
|
if (RuntimeConfig::Get().hasSNI == false || Config::Get().enableSNI == false)
|
||||||
|
{
|
||||||
|
// Don't bother
|
||||||
|
return TimerResult::Delete;
|
||||||
|
}
|
||||||
|
for (auto& [name, client] : clientsToQuery)
|
||||||
|
{
|
||||||
|
LOG("SNI: Creating Item " << client.name << " " << client.object);
|
||||||
|
Item item = CreateItem(std::move(client.name), std::move(client.object));
|
||||||
|
// Add handler for removing
|
||||||
|
g_bus_watch_name_on_connection(dbusConnection, item.name.c_str(), G_BUS_NAME_WATCHER_FLAGS_NONE, nullptr, DBusNameVanished, nullptr,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
// Add handler for icon change
|
||||||
|
char* staticBuf = new char[item.name.size() + 1]{0x0};
|
||||||
|
memcpy(staticBuf, item.name.c_str(), item.name.size());
|
||||||
|
g_dbus_connection_signal_subscribe(
|
||||||
|
dbusConnection, item.name.c_str(), "org.kde.StatusNotifierItem", nullptr, nullptr, nullptr, G_DBUS_SIGNAL_FLAGS_NONE,
|
||||||
|
ItemPropertyChanged, staticBuf,
|
||||||
|
+[](void* ptr)
|
||||||
|
{
|
||||||
|
LOG("SNI: Delete static name buffer for " << (char*)ptr);
|
||||||
|
delete[] (char*)ptr;
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push_back(std::move(item));
|
||||||
|
}
|
||||||
|
if (clientsToQuery.size() > 0)
|
||||||
|
{
|
||||||
|
InvalidateWidget();
|
||||||
|
}
|
||||||
|
clientsToQuery.clear();
|
||||||
|
return TimerResult::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SNI implements the GTK-Thingies itself internally
|
||||||
|
static void InvalidateWidget()
|
||||||
|
{
|
||||||
|
LOG("SNI: Clearing old children");
|
||||||
|
parentBox->RemoveChild(iconBox);
|
||||||
|
|
||||||
|
auto container = Widget::Create<Box>();
|
||||||
|
container->SetSpacing({4, false});
|
||||||
|
iconBox = container.get();
|
||||||
|
for (auto& item : items)
|
||||||
|
{
|
||||||
|
if (item.iconData)
|
||||||
|
{
|
||||||
|
auto eventBox = Widget::Create<EventBox>();
|
||||||
|
item.gtkEvent = eventBox.get();
|
||||||
|
|
||||||
|
eventBox->SetOnCreate(
|
||||||
|
[&](Widget& w)
|
||||||
|
{
|
||||||
|
auto clickFn = [](GtkWidget*, GdkEventButton* event, void* data) -> gboolean
|
||||||
|
{
|
||||||
|
if (event->button == 1)
|
||||||
|
{
|
||||||
|
Item* item = (Item*)data;
|
||||||
|
|
||||||
|
GtkMenu* menu = (GtkMenu*)dbusmenu_gtkmenu_new(item->name.data(), item->menuObjectPath.data());
|
||||||
|
LOG(menu);
|
||||||
|
gtk_menu_attach_to_widget(menu, item->gtkEvent->Get(), nullptr);
|
||||||
|
gtk_menu_popup_at_pointer(menu, (GdkEvent*)event);
|
||||||
|
LOG(item->menuObjectPath << " click");
|
||||||
|
}
|
||||||
|
return GDK_EVENT_STOP;
|
||||||
|
};
|
||||||
|
g_signal_connect(w.Get(), "button-release-event", G_CALLBACK(+clickFn), &item);
|
||||||
|
});
|
||||||
|
|
||||||
|
LOG("SNI: Add " << item.name << " to widget");
|
||||||
|
auto texture = Widget::Create<Texture>();
|
||||||
|
bool wasExplicitOverride = false;
|
||||||
|
for (auto& [filter, size] : Config::Get().sniIconSizes)
|
||||||
|
{
|
||||||
|
if (item.tooltip.find(filter) != std::string::npos)
|
||||||
|
{
|
||||||
|
wasExplicitOverride = true;
|
||||||
|
texture->ForceHeight(size);
|
||||||
|
}
|
||||||
|
else if (filter == "*" && !wasExplicitOverride)
|
||||||
|
{
|
||||||
|
texture->ForceHeight(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wasExplicitOverride = false;
|
||||||
|
for (auto& [filter, padding] : Config::Get().sniPaddingTop)
|
||||||
|
{
|
||||||
|
if (item.tooltip.find(filter) != std::string::npos)
|
||||||
|
{
|
||||||
|
LOG("Padding " << padding);
|
||||||
|
wasExplicitOverride = true;
|
||||||
|
texture->AddPaddingTop(padding);
|
||||||
|
}
|
||||||
|
else if (filter == "*" && !wasExplicitOverride)
|
||||||
|
{
|
||||||
|
texture->AddPaddingTop(padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
texture->SetHorizontalTransform({0, true, Alignment::Fill});
|
||||||
|
texture->SetBuf(item.w, item.h, item.iconData);
|
||||||
|
texture->SetTooltip(item.tooltip);
|
||||||
|
|
||||||
|
eventBox->AddChild(std::move(texture));
|
||||||
|
iconBox->AddChild(std::move(eventBox));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parentBox->AddChild(std::move(container));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetSNI(Widget& parent)
|
||||||
|
{
|
||||||
|
if (RuntimeConfig::Get().hasSNI == false || Config::Get().enableSNI == false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Add parent box
|
||||||
|
auto box = Widget::Create<Box>();
|
||||||
|
auto container = Widget::Create<Box>();
|
||||||
|
container->AddTimer<Box>(UpdateWidgets, 1000, TimerDispatchBehaviour::LateDispatch);
|
||||||
|
iconBox = container.get();
|
||||||
|
parentBox = box.get();
|
||||||
|
InvalidateWidget();
|
||||||
|
box->AddChild(std::move(container));
|
||||||
|
parent.AddChild(std::move(box));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
static bool RegisterItem(sniWatcher* watcher, GDBusMethodInvocation* invocation, const char* service)
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string object;
|
||||||
|
if (strncmp(service, "/", 1) == 0)
|
||||||
|
{
|
||||||
|
// service is object (used by e.g. ayatana -> steam, discord)
|
||||||
|
object = service;
|
||||||
|
name = g_dbus_method_invocation_get_sender(invocation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// service is bus name (used by e.g. Telegram)
|
||||||
|
name = service;
|
||||||
|
object = "/StatusNotifierItem";
|
||||||
|
}
|
||||||
|
auto it = std::find_if(items.begin(), items.end(),
|
||||||
|
[&](const Item& item)
|
||||||
|
{
|
||||||
|
return item.name == name && item.object == object;
|
||||||
|
});
|
||||||
|
if (it != items.end())
|
||||||
|
{
|
||||||
|
LOG("Rejecting " << name << " " << object);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sni_watcher_emit_status_notifier_item_registered(watcher, service);
|
||||||
|
sni_watcher_complete_register_status_notifier_item(watcher, invocation);
|
||||||
|
LOG("SNI: Registered Item " << name << " " << object);
|
||||||
|
clientsToQuery[name] = {name, std::move(object)};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterHost(sniWatcher*, GDBusMethodInvocation*, const char*)
|
||||||
|
{
|
||||||
|
LOG("TODO: Implement RegisterHost!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
static void ItemRegistered(sniWatcher*, const char*, void*)
|
||||||
|
{
|
||||||
|
// Don't care, since watcher and host will always be from gBar (at least for now)
|
||||||
|
}
|
||||||
|
static void ItemUnregistered(sniWatcher*, const char*, void*)
|
||||||
|
{
|
||||||
|
// Don't care, since watcher and host will always be from gBar (at least for now)
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init()
|
||||||
|
{
|
||||||
|
if (RuntimeConfig::Get().hasSNI == false || Config::Get().enableSNI == false)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto busAcquired = [](GDBusConnection* connection, const char*, void*)
|
||||||
|
{
|
||||||
|
GError* err = nullptr;
|
||||||
|
g_dbus_interface_skeleton_export((GDBusInterfaceSkeleton*)watcherSkeleton, connection, "/StatusNotifierWatcher", &err);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
LOG("SNI: Failed to connect to dbus! Disabling SNI. Error: " << err->message);
|
||||||
|
RuntimeConfig::Get().hasSNI = false;
|
||||||
|
g_error_free(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dbusConnection = connection;
|
||||||
|
|
||||||
|
// Connect methods and signals
|
||||||
|
g_signal_connect(watcherSkeleton, "handle-register-status-notifier-item", G_CALLBACK(RegisterItem), nullptr);
|
||||||
|
g_signal_connect(watcherSkeleton, "handle-register-status-notifier-host", G_CALLBACK(RegisterHost), nullptr);
|
||||||
|
|
||||||
|
g_signal_connect(watcherSkeleton, "status-notifier-item-registered", G_CALLBACK(ItemRegistered), nullptr);
|
||||||
|
g_signal_connect(watcherSkeleton, "status-notifier-item-unregistered", G_CALLBACK(ItemUnregistered), nullptr);
|
||||||
|
|
||||||
|
// Host is always available
|
||||||
|
sni_watcher_set_is_status_notifier_host_registered(watcherSkeleton, true);
|
||||||
|
};
|
||||||
|
auto emptyCallback = [](GDBusConnection*, const char*, void*) {};
|
||||||
|
auto lostName = [](GDBusConnection*, const char* msg, void*)
|
||||||
|
{
|
||||||
|
LOG("SNI: Lost Name! Disabling SNI!");
|
||||||
|
RuntimeConfig::Get().hasSNI = false;
|
||||||
|
};
|
||||||
|
auto flags = G_BUS_NAME_OWNER_FLAGS_REPLACE | G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
|
||||||
|
g_bus_own_name(G_BUS_TYPE_SESSION, "org.kde.StatusNotifierWatcher", (GBusNameOwnerFlags)flags, +busAcquired, +emptyCallback, +lostName,
|
||||||
|
nullptr, nullptr);
|
||||||
|
watcherSkeleton = sni_watcher_skeleton_new();
|
||||||
|
|
||||||
|
std::string hostName = "org.kde.StatusNotifierHost-" + std::to_string(getpid());
|
||||||
|
g_bus_own_name(G_BUS_TYPE_SESSION, hostName.c_str(), (GBusNameOwnerFlags)flags, +emptyCallback, +emptyCallback, +emptyCallback, nullptr,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
// Host is always available
|
||||||
|
sni_watcher_set_is_status_notifier_host_registered(watcherSkeleton, true);
|
||||||
|
sni_watcher_emit_status_notifier_host_registered(watcherSkeleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shutdown() {}
|
||||||
|
}
|
||||||
|
#endif
|
10
src/SNI.h
Normal file
10
src/SNI.h
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
#ifdef WITH_SNI
|
||||||
|
class Widget;
|
||||||
|
namespace SNI
|
||||||
|
{
|
||||||
|
void Init();
|
||||||
|
void WidgetSNI(Widget& parent);
|
||||||
|
void Shutdown();
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -5,6 +5,7 @@
|
||||||
#include "PulseAudio.h"
|
#include "PulseAudio.h"
|
||||||
#include "Workspaces.h"
|
#include "Workspaces.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "SNI.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -646,6 +647,10 @@ namespace System
|
||||||
|
|
||||||
PulseAudio::Init();
|
PulseAudio::Init();
|
||||||
|
|
||||||
|
#ifdef WITH_SNI
|
||||||
|
SNI::Init();
|
||||||
|
#endif
|
||||||
|
|
||||||
CheckNetwork();
|
CheckNetwork();
|
||||||
}
|
}
|
||||||
void FreeResources()
|
void FreeResources()
|
||||||
|
@ -662,6 +667,10 @@ namespace System
|
||||||
#ifdef WITH_BLUEZ
|
#ifdef WITH_BLUEZ
|
||||||
StopBTScan();
|
StopBTScan();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_SNI
|
||||||
|
SNI::Shutdown();
|
||||||
|
#endif
|
||||||
|
|
||||||
Logging::Shutdown();
|
Logging::Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,10 +122,32 @@ void Widget::RemoveChild(size_t idx)
|
||||||
if (m_Widget)
|
if (m_Widget)
|
||||||
{
|
{
|
||||||
auto& child = *m_Childs[idx];
|
auto& child = *m_Childs[idx];
|
||||||
gtk_container_remove((GtkContainer*)child.m_Widget, m_Widget);
|
gtk_container_remove((GtkContainer*)m_Widget, child.m_Widget);
|
||||||
|
child.m_Widget = nullptr;
|
||||||
}
|
}
|
||||||
m_Childs.erase(m_Childs.begin() + idx);
|
m_Childs.erase(m_Childs.begin() + idx);
|
||||||
}
|
}
|
||||||
|
void Widget::RemoveChild(Widget* widget)
|
||||||
|
{
|
||||||
|
auto it = std::find_if(m_Childs.begin(), m_Childs.end(),
|
||||||
|
[&](std::unique_ptr<Widget>& other)
|
||||||
|
{
|
||||||
|
return other.get() == widget;
|
||||||
|
});
|
||||||
|
if (it != m_Childs.end())
|
||||||
|
{
|
||||||
|
if (m_Widget)
|
||||||
|
{
|
||||||
|
gtk_container_remove((GtkContainer*)m_Widget, it->get()->m_Widget);
|
||||||
|
it->get()->m_Widget = nullptr;
|
||||||
|
}
|
||||||
|
m_Childs.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG("Invalid child!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Widget::SetVisible(bool visible)
|
void Widget::SetVisible(bool visible)
|
||||||
{
|
{
|
||||||
|
@ -151,6 +173,9 @@ void Widget::ApplyPropertiesToWidget()
|
||||||
gtk_widget_set_valign(m_Widget, Utils::ToGtkAlign(m_VerticalTransform.alignment));
|
gtk_widget_set_valign(m_Widget, Utils::ToGtkAlign(m_VerticalTransform.alignment));
|
||||||
gtk_widget_set_hexpand(m_Widget, m_HorizontalTransform.expand);
|
gtk_widget_set_hexpand(m_Widget, m_HorizontalTransform.expand);
|
||||||
gtk_widget_set_vexpand(m_Widget, m_VerticalTransform.expand);
|
gtk_widget_set_vexpand(m_Widget, m_VerticalTransform.expand);
|
||||||
|
|
||||||
|
if (m_OnCreate)
|
||||||
|
m_OnCreate(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Box::SetOrientation(Orientation orientation)
|
void Box::SetOrientation(Orientation orientation)
|
||||||
|
@ -475,6 +500,38 @@ void NetworkSensor::Draw(cairo_t* cr)
|
||||||
gdk_rgba_free(colDown);
|
gdk_rgba_free(colDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Texture::~Texture()
|
||||||
|
{
|
||||||
|
if (m_Pixbuf)
|
||||||
|
g_free(m_Pixbuf);
|
||||||
|
if (m_Bytes)
|
||||||
|
g_free(m_Bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::SetBuf(size_t width, size_t height, uint8_t* buf)
|
||||||
|
{
|
||||||
|
m_Width = width;
|
||||||
|
m_Height = height;
|
||||||
|
m_Bytes = g_bytes_new(buf, m_Width * m_Height * 4);
|
||||||
|
m_Pixbuf = gdk_pixbuf_new_from_bytes((GBytes*)m_Bytes, GDK_COLORSPACE_RGB, true, 8, m_Width, m_Height, m_Width * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::Draw(cairo_t* cr)
|
||||||
|
{
|
||||||
|
GtkAllocation dim;
|
||||||
|
gtk_widget_get_allocation(m_Widget, &dim);
|
||||||
|
|
||||||
|
double height = m_ForcedHeight != 0 ? m_ForcedHeight : dim.height;
|
||||||
|
double scale = (double)height / (double)m_Height;
|
||||||
|
double width = (double)m_Width * scale;
|
||||||
|
|
||||||
|
gtk_widget_set_size_request(m_Widget, width + 2, height);
|
||||||
|
cairo_scale(cr, scale, scale);
|
||||||
|
cairo_rectangle(cr, (dim.width - width) / 2.0, m_Padding + (dim.height - height) / 2.0, m_Width, m_Height);
|
||||||
|
gdk_cairo_set_source_pixbuf(cr, m_Pixbuf, (dim.width - width) / 2.0, m_Padding + (dim.height - height) / 2.0);
|
||||||
|
cairo_fill(cr);
|
||||||
|
}
|
||||||
|
|
||||||
void Revealer::SetTransition(Transition transition)
|
void Revealer::SetTransition(Transition transition)
|
||||||
{
|
{
|
||||||
m_Transition = transition;
|
m_Transition = transition;
|
||||||
|
|
28
src/Widget.h
28
src/Widget.h
|
@ -116,6 +116,7 @@ public:
|
||||||
|
|
||||||
void AddChild(std::unique_ptr<Widget>&& widget);
|
void AddChild(std::unique_ptr<Widget>&& widget);
|
||||||
void RemoveChild(size_t idx);
|
void RemoveChild(size_t idx);
|
||||||
|
void RemoveChild(Widget* widget);
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Widget>>& GetWidgets() { return m_Childs; }
|
std::vector<std::unique_ptr<Widget>>& GetWidgets() { return m_Childs; }
|
||||||
|
|
||||||
|
@ -156,6 +157,8 @@ public:
|
||||||
|
|
||||||
void SetVisible(bool visible);
|
void SetVisible(bool visible);
|
||||||
|
|
||||||
|
void SetOnCreate(Callback<Widget>&& onCreate) { m_OnCreate = onCreate; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void PropagateToParent(GdkEvent* event);
|
void PropagateToParent(GdkEvent* event);
|
||||||
void ApplyPropertiesToWidget();
|
void ApplyPropertiesToWidget();
|
||||||
|
@ -168,6 +171,8 @@ protected:
|
||||||
std::string m_Tooltip;
|
std::string m_Tooltip;
|
||||||
Transform m_HorizontalTransform; // X
|
Transform m_HorizontalTransform; // X
|
||||||
Transform m_VerticalTransform; // Y
|
Transform m_VerticalTransform; // Y
|
||||||
|
|
||||||
|
Callback<Widget> m_OnCreate;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Box : public Widget
|
class Box : public Widget
|
||||||
|
@ -263,6 +268,29 @@ private:
|
||||||
std::unique_ptr<Box> contextDown;
|
std::unique_ptr<Box> contextDown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Texture : public CairoArea
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Texture() = default;
|
||||||
|
virtual ~Texture();
|
||||||
|
|
||||||
|
// Non-Owning, ARGB32
|
||||||
|
void SetBuf(size_t width, size_t height, uint8_t* buf);
|
||||||
|
|
||||||
|
void ForceHeight(size_t height) { m_ForcedHeight = height; };
|
||||||
|
void AddPaddingTop(int32_t topPadding) { m_Padding = topPadding; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Draw(cairo_t* cr) override;
|
||||||
|
|
||||||
|
size_t m_Width;
|
||||||
|
size_t m_Height;
|
||||||
|
size_t m_ForcedHeight = 0;
|
||||||
|
int32_t m_Padding = 0;
|
||||||
|
GBytes* m_Bytes;
|
||||||
|
GdkPixbuf* m_Pixbuf;
|
||||||
|
};
|
||||||
|
|
||||||
class Revealer : public Widget
|
class Revealer : public Widget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
1
thirdparty/stb
vendored
Submodule
1
thirdparty/stb
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9
|
Loading…
Reference in a new issue