Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
2 changes: 1 addition & 1 deletion PKGBUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ arch=('x86_64')

depends=('systemd-libs'
'fmt'
'sdbus-cpp')
'sdbus-cpp>=2.0.0')

makedepends=('git'
'cmake')
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,23 @@ This tool depends on `systemd-libs`, `sdbus-cpp` and `fmt`.
There also submodule dependencies: `cxxopts` and `magic_enum` but you should generally not worry about them since they are included as submodules into this repo.

## Installation
### Archlinux
### Arch Linux
Check out the repo and use [makepkg](https://wiki.archlinux.org/title/makepkg):

```shell
git checkout https://github.com/andrewerf/tray-control.git
git clone https://github.com/andrewerf/tray-control.git
cd tray-control
makepkg -si
```
I hope I'll add this tool to AUR in the near future.

### Other linux
Checkout the repo and build with cmake + make:
Checkout the repo, pull submodules, and build with cmake + make:
```shell
git checkout https://github.com/andrewerf/tray-control.git
git clone https://github.com/andrewerf/tray-control.git
cd tray-control
git submodule init
git submodule update
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
Expand Down
5 changes: 3 additions & 2 deletions src/StatusNotifierItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
#include <sdbus-c++/sdbus-c++.h>

#include "DBusUtils.h"
#include "Types.h"


template <typename T>
static constexpr auto safelyGetSNIProperty = std::bind( safelyGetProperty<T>, std::placeholders::_1, "org.kde.StatusNotifierItem", std::placeholders::_2 );


StatusNotifierItem::StatusNotifierItem( std::string_view destination ):
StatusNotifierItem::StatusNotifierItem( SniAddress destination ):
destination_( destination )
{}

Expand All @@ -22,7 +23,7 @@ StatusNotifierItem::~StatusNotifierItem() = default;
std::expected<void, Error> StatusNotifierItem::connect()
{
return safelyExec( [this] -> std::expected<void, Error> {
if ( proxy_ = sdbus::createProxy( sdbus::createSessionBusConnection(), std::string( destination_ ), "/StatusNotifierItem" ) )
if ( proxy_ = sdbus::createProxy( sdbus::createSessionBusConnection(), sdbus::ServiceName{ destination_.serviceName }, sdbus::ObjectPath{ destination_.objectPath } ) )
return {};
else
return makeError( ErrorKind::ConnectionError );
Expand Down
5 changes: 3 additions & 2 deletions src/StatusNotifierItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <expected>

#include "Errors.h"
#include "Types.h"

namespace sdbus
{
Expand All @@ -27,7 +28,7 @@ class StatusNotifierItem
std::string description;
};

StatusNotifierItem( std::string_view destination );
StatusNotifierItem( SniAddress destination );
~StatusNotifierItem();

std::expected<void, Error> connect();
Expand Down Expand Up @@ -85,5 +86,5 @@ class StatusNotifierItem

private:
std::unique_ptr<sdbus::IProxy> proxy_;
std::string_view destination_;
SniAddress destination_;
};
46 changes: 46 additions & 0 deletions src/StatusNotifierItemJson.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include <nlohmann/json.hpp>
#include "StatusNotifierItem.h"

using json = nlohmann::json;

/*
Simple helper for nulling a key in output JSON if there's an error when fetching it.
*/
template <typename T, typename E>
inline void json_set_or_null(json& j, std::string_view key, const std::expected<T, E>& value)
{
j[key] = value ? json(*value) : json(nullptr);
}

/*
nlohmann/json finds `to_json` methods using ADL, so we define some helper methods
here to enable automatic JSON serialization of a few types.
*/
inline void to_json(json& j, const StatusNotifierItem::ToolTip& t)
{
j = nlohmann::json{
{"icon_name", t.iconName},
{"title", t.title},
{"description", t.description},
};
}

inline void to_json(json& j, StatusNotifierItem& t)
{
j = json::object();

json_set_or_null(j, "category", t.getCategory());
json_set_or_null(j, "id", t.getId());
json_set_or_null(j, "title", t.getTitle());
json_set_or_null(j, "status", t.getStatus());
json_set_or_null(j, "windowId", t.getWindowId());
json_set_or_null(j, "iconName", t.getIconName());
json_set_or_null(j, "overlayIconName", t.getOverlayIconName());
json_set_or_null(j, "attentionIconName", t.getAttentionIconName());
json_set_or_null(j, "attentionMovieName", t.getAttentionMovieName());

// TODO: re-add after getToolTip is implemented
// json_set_or_null(j, "toolTip", t.getToolTip());
}
30 changes: 25 additions & 5 deletions src/StatusNotifierWatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "Utils.h"
#include "DBusUtils.h"
#include "Types.h"


StatusNotifierWatcher::StatusNotifierWatcher() = default;
Expand All @@ -16,24 +17,43 @@ StatusNotifierWatcher::~StatusNotifierWatcher() = default;
std::expected<void, Error> StatusNotifierWatcher::connect()
{
return safelyExec( [this] -> std::expected<void, Error> {
if ( proxy_ = sdbus::createProxy( sdbus::createSessionBusConnection(), "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher" ) )
if ( proxy_ = sdbus::createProxy( sdbus::createSessionBusConnection(), sdbus::ServiceName{ "org.kde.StatusNotifierWatcher" }, sdbus::ObjectPath{ "/StatusNotifierWatcher" } ) )
return {};
else
return makeError( ErrorKind::ConnectionError );
} );
}

std::expected<std::vector<std::string>, Error> StatusNotifierWatcher::getRegisteredStatusNotifierItemAddresses()
std::expected<std::vector<SniAddress>, Error> StatusNotifierWatcher::getRegisteredStatusNotifierItemAddresses()
{
return
safelyGetProperty<std::vector<std::string>>( proxy_, "org.kde.StatusNotifierWatcher", "RegisteredStatusNotifierItems" ) >>
[] ( std::vector<std::string>&& addrs ) {
[] ( std::vector<std::string>&& addrs )
-> std::expected<std::vector<SniAddress>, Error>
{
std::vector<SniAddress> out;
out.reserve(addrs.size());

for ( auto& addr : addrs )
{
addr = addr.substr( 0, addr.find( '/' ) );
/*
StatusNotifierItems created by Ayatana are not stored at the normal path of /StatusNotifierItem,
so we'll split the path based on the first occurrence of `/` to separate the service name of the
process exposing a status item and its object path.
*/
auto slashIndex = addr.find( '/' );
std::string serviceName = addr.substr( 0, slashIndex );

// leading `/` in object path must be retained
std::string objectPath = addr.substr( slashIndex );

out.push_back(SniAddress{
std::move( serviceName ),
std::move( objectPath ),
});
}

return addrs;
return out;
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/StatusNotifierWatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <expected>

#include "Errors.h"
#include "Types.h"

namespace sdbus
{
Expand All @@ -22,7 +23,7 @@ class StatusNotifierWatcher
~StatusNotifierWatcher();

std::expected<void, Error> connect();
std::expected<std::vector<std::string>, Error> getRegisteredStatusNotifierItemAddresses();
std::expected<std::vector<SniAddress>, Error> getRegisteredStatusNotifierItemAddresses();

private:
std::unique_ptr<sdbus::IProxy> proxy_;
Expand Down
28 changes: 28 additions & 0 deletions src/Types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <ostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;


struct SniAddress {
std::string serviceName;
std::string objectPath;
};


// Overload the concatenation operator so that we can easily reference SNI addresses in log/error messages
inline std::ostream& operator<<(std::ostream& os, const SniAddress& addr)
{
return os << addr.serviceName << addr.objectPath;
}

// Implement JSON serialization for SniAddress
inline void to_json(json& j, const SniAddress& t)
{
j = nlohmann::json{
{"serviceName", t.serviceName},
{"objectPath", t.objectPath},
};
}
1 change: 1 addition & 0 deletions src/tray-activate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "StatusNotifierWatcher.h"
#include "StatusNotifierItem.h"
#include "Utils.h"
#include "Types.h"


void exitWithMsg( std::string_view msg, int code = -1 )
Expand Down
77 changes: 62 additions & 15 deletions src/tray-show.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
#include <cxxopts.hpp>
#include <iostream>
#include <fmt/printf.h>
#include <nlohmann/json.hpp>

#include "StatusNotifierWatcher.h"
#include "StatusNotifierItem.h"
#include "StatusNotifierItemJson.h"
#include "Utils.h"
#include "Types.h"


#define WRAP_FUNC_TO_LAMBDA( func ) \
[] <typename ...Args> ( Args&& ...args ) -> decltype( auto ) \
{ return func( std::forward<Args>( args )... ); }

using json = nlohmann::json;


void exitWithMsg( std::string_view msg, int code = -1 )
Expand All @@ -22,12 +21,32 @@ void exitWithMsg( std::string_view msg, int code = -1 )
exit( code );
}

void print_json_string (const json& obj, const char* key, const char* label_fmt) {
auto it = obj.find(key);
if (it == obj.end() || it->is_null()) return;

// if the value isn't a string, skip (or coerce)
if (!it->is_string()) return;

fmt::printf(label_fmt, it->get_ref<const std::string&>().c_str());
};

void print_json_u32 (const json& obj, const char* key, const char* label_fmt) {
auto it = obj.find(key);
if (it == obj.end() || it->is_null()) return;

if (!it->is_number_unsigned()) return;

fmt::printf(label_fmt, it->get<uint32_t>());
};


int main( int argc, char** argv )
{
cxxopts::Options optionsDecl( "tray-show", "Show items in the system tray" );
optionsDecl.add_options()
( "h,help", "Print help and exit", cxxopts::value<bool>()->default_value("false") )
( "j,json", "Print all item properties in JSON format", cxxopts::value<bool>()->default_value("false") )
( "v,verbose", "Show full info about each item", cxxopts::value<bool>()->default_value("false") );

const auto options = optionsDecl.parse( argc, argv );
Expand All @@ -37,35 +56,63 @@ int main( int argc, char** argv )
return 0;
}
const bool verboseOutput = options["verbose"].as<bool>();
const bool jsonOutput = options["json"].as<bool>();

StatusNotifierWatcher watcher;
if ( auto connRes = watcher.connect(); !connRes )
exitWithMsg( "Could not connect to the StatusNotifierWatcher with error: " + connRes.error().show(), -1 );

if ( auto maybeAddrs = watcher.getRegisteredStatusNotifierItemAddresses() )
{
// collect all item data as JSON first, store in a vector
std::vector<json> itemData;
itemData.reserve( maybeAddrs.value().size() );

for ( const auto& addr : maybeAddrs.value() )
{
StatusNotifierItem item( addr );
if ( auto connRes = item.connect() )
{
item.getCategory() >> std::bind_front( WRAP_FUNC_TO_LAMBDA( fmt::printf ), "Category: %s\n" );
item.getTitle() >> std::bind_front( WRAP_FUNC_TO_LAMBDA( fmt::printf ), "Title: %s\n" );
// Serialize, print item data with 4-space indent
json j;
to_json( j, item );

if ( verboseOutput )
{
item.getId() >> std::bind_front( WRAP_FUNC_TO_LAMBDA( fmt::printf ), "Id: %s\n" );
item.getStatus() >> std::bind_front( WRAP_FUNC_TO_LAMBDA( fmt::printf ), "Status: %s\n" );
item.getWindowId() >> std::bind_front( WRAP_FUNC_TO_LAMBDA( fmt::printf ), "WindowId: %d\n" );
item.getIconName() >> std::bind_front( WRAP_FUNC_TO_LAMBDA( fmt::printf ), "IconName: %s\n" );
}
// Add the D-Bus address we used to fetch the information
json jAddr;
to_json( jAddr, addr );
j["address"] = jAddr;

// Push item data onto the vector
itemData.push_back( j );
}
else
{
std::cerr << "Could not connect to the StatusNotifierItem on address: " << addr << " with error: " << connRes.error().show() << '\n';
}
}

// Print collected data in chosen output format
if ( jsonOutput ) {
// Serialize, print item data with 4-space indent
json j = itemData;
std::cout << j.dump( 4 ) << '\n';
}
else
{
for ( const json& item : itemData )
{
print_json_string( item, "category", "Category: %s\n" );
print_json_string( item, "title", "Title: %s\n" );

std::cout << '\n';
if (verboseOutput)
{
print_json_string( item, "id", "Id: %s\n" );
print_json_string( item, "status", "Status: %s\n" );
print_json_u32( item, "windowId", "WindowId: %u\n" );
print_json_string( item, "iconName", "IconName: %s\n" );
}
std::cout << '\n';
}
}
}
}