Merge pull request #2575 from Syndelis/feat/slider
commit
42741c963e
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AModule.hpp"
|
||||||
|
#include "gtkmm/scale.h"
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
class ASlider : public AModule {
|
||||||
|
public:
|
||||||
|
ASlider(const Json::Value& config, const std::string& name, const std::string& id);
|
||||||
|
virtual void onValueChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool vertical_ = false;
|
||||||
|
int min_ = 0, max_ = 100, curr_ = 50;
|
||||||
|
Gtk::Scale scale_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar
|
|
@ -1,14 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "ALabel.hpp"
|
#include "ALabel.hpp"
|
||||||
#include "giomm/dbusproxy.h"
|
#include "util/backlight_backend.hpp"
|
||||||
#include "util/json.hpp"
|
#include "util/json.hpp"
|
||||||
#include "util/sleeper_thread.hpp"
|
|
||||||
|
|
||||||
struct udev;
|
struct udev;
|
||||||
struct udev_device;
|
struct udev_device;
|
||||||
|
@ -16,54 +16,17 @@ struct udev_device;
|
||||||
namespace waybar::modules {
|
namespace waybar::modules {
|
||||||
|
|
||||||
class Backlight : public ALabel {
|
class Backlight : public ALabel {
|
||||||
class BacklightDev {
|
|
||||||
public:
|
|
||||||
BacklightDev() = default;
|
|
||||||
BacklightDev(std::string name, int actual, int max, bool powered);
|
|
||||||
std::string_view name() const;
|
|
||||||
int get_actual() const;
|
|
||||||
void set_actual(int actual);
|
|
||||||
int get_max() const;
|
|
||||||
void set_max(int max);
|
|
||||||
bool get_powered() const;
|
|
||||||
void set_powered(bool powered);
|
|
||||||
friend inline bool operator==(const BacklightDev &lhs, const BacklightDev &rhs) {
|
|
||||||
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string name_;
|
|
||||||
int actual_ = 1;
|
|
||||||
int max_ = 1;
|
|
||||||
bool powered_ = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Backlight(const std::string &, const Json::Value &);
|
Backlight(const std::string &, const Json::Value &);
|
||||||
virtual ~Backlight();
|
virtual ~Backlight() = default;
|
||||||
auto update() -> void override;
|
auto update() -> void override;
|
||||||
|
|
||||||
private:
|
|
||||||
template <class ForwardIt>
|
|
||||||
static const BacklightDev *best_device(ForwardIt first, ForwardIt last, std::string_view);
|
|
||||||
template <class ForwardIt, class Inserter>
|
|
||||||
static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter, udev_device *dev);
|
|
||||||
template <class ForwardIt, class Inserter>
|
|
||||||
static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev);
|
|
||||||
|
|
||||||
bool handleScroll(GdkEventScroll *e) override;
|
bool handleScroll(GdkEventScroll *e) override;
|
||||||
|
|
||||||
const std::string preferred_device_;
|
const std::string preferred_device_;
|
||||||
static constexpr int EPOLL_MAX_EVENTS = 16;
|
|
||||||
|
|
||||||
std::optional<BacklightDev> previous_best_;
|
|
||||||
std::string previous_format_;
|
std::string previous_format_;
|
||||||
|
|
||||||
std::mutex udev_thread_mutex_;
|
util::BacklightBackend backend;
|
||||||
std::vector<BacklightDev> devices_;
|
|
||||||
// thread must destruct before shared data
|
|
||||||
util::SleeperThread udev_thread_;
|
|
||||||
|
|
||||||
Glib::RefPtr<Gio::DBus::Proxy> login_proxy_;
|
|
||||||
};
|
};
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "ASlider.hpp"
|
||||||
|
#include "util/backlight_backend.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
class BacklightSlider : public ASlider {
|
||||||
|
public:
|
||||||
|
BacklightSlider(const std::string&, const Json::Value&);
|
||||||
|
virtual ~BacklightSlider() = default;
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void onValueChanged() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::chrono::milliseconds interval_;
|
||||||
|
std::string preferred_device_;
|
||||||
|
util::BacklightBackend backend;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules
|
|
@ -1,54 +1,27 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <pulse/pulseaudio.h>
|
|
||||||
#include <pulse/volume.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "ALabel.hpp"
|
#include "ALabel.hpp"
|
||||||
|
#include "util/audio_backend.hpp"
|
||||||
|
|
||||||
namespace waybar::modules {
|
namespace waybar::modules {
|
||||||
|
|
||||||
class Pulseaudio : public ALabel {
|
class Pulseaudio : public ALabel {
|
||||||
public:
|
public:
|
||||||
Pulseaudio(const std::string&, const Json::Value&);
|
Pulseaudio(const std::string&, const Json::Value&);
|
||||||
virtual ~Pulseaudio();
|
virtual ~Pulseaudio() = default;
|
||||||
auto update() -> void override;
|
auto update() -> void override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
|
|
||||||
static void contextStateCb(pa_context*, void*);
|
|
||||||
static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
|
|
||||||
static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data);
|
|
||||||
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
|
|
||||||
static void volumeModifyCb(pa_context*, int, void*);
|
|
||||||
|
|
||||||
bool handleScroll(GdkEventScroll* e) override;
|
bool handleScroll(GdkEventScroll* e) override;
|
||||||
const std::vector<std::string> getPulseIcon() const;
|
const std::vector<std::string> getPulseIcon() const;
|
||||||
|
|
||||||
pa_threaded_mainloop* mainloop_;
|
std::shared_ptr<util::AudioBackend> backend = nullptr;
|
||||||
pa_mainloop_api* mainloop_api_;
|
|
||||||
pa_context* context_;
|
|
||||||
// SINK
|
|
||||||
uint32_t sink_idx_{0};
|
|
||||||
uint16_t volume_;
|
|
||||||
pa_cvolume pa_volume_;
|
|
||||||
bool muted_;
|
|
||||||
std::string port_name_;
|
|
||||||
std::string form_factor_;
|
|
||||||
std::string desc_;
|
|
||||||
std::string monitor_;
|
|
||||||
std::string current_sink_name_;
|
|
||||||
bool current_sink_running_;
|
|
||||||
// SOURCE
|
|
||||||
uint32_t source_idx_{0};
|
|
||||||
uint16_t source_volume_;
|
|
||||||
bool source_muted_;
|
|
||||||
std::string source_port_name_;
|
|
||||||
std::string source_desc_;
|
|
||||||
std::string default_source_name_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "ASlider.hpp"
|
||||||
|
#include "util/audio_backend.hpp"
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
enum class PulseaudioSliderTarget {
|
||||||
|
Sink,
|
||||||
|
Source,
|
||||||
|
};
|
||||||
|
|
||||||
|
class PulseaudioSlider : public ASlider {
|
||||||
|
public:
|
||||||
|
PulseaudioSlider(const std::string&, const Json::Value&);
|
||||||
|
virtual ~PulseaudioSlider() = default;
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void onValueChanged() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<util::AudioBackend> backend = nullptr;
|
||||||
|
PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::modules
|
|
@ -0,0 +1,95 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <json/value.h>
|
||||||
|
#include <pulse/context.h>
|
||||||
|
#include <pulse/introspect.h>
|
||||||
|
#include <pulse/thread-mainloop.h>
|
||||||
|
#include <pulse/volume.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "util/backend_common.hpp"
|
||||||
|
|
||||||
|
namespace waybar::util {
|
||||||
|
|
||||||
|
class AudioBackend {
|
||||||
|
private:
|
||||||
|
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
|
||||||
|
static void contextStateCb(pa_context*, void*);
|
||||||
|
static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*);
|
||||||
|
static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data);
|
||||||
|
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
|
||||||
|
static void volumeModifyCb(pa_context*, int, void*);
|
||||||
|
|
||||||
|
pa_threaded_mainloop* mainloop_;
|
||||||
|
pa_mainloop_api* mainloop_api_;
|
||||||
|
pa_context* context_;
|
||||||
|
pa_cvolume pa_volume_;
|
||||||
|
|
||||||
|
// SINK
|
||||||
|
uint32_t sink_idx_{0};
|
||||||
|
uint16_t volume_;
|
||||||
|
bool muted_;
|
||||||
|
std::string port_name_;
|
||||||
|
std::string form_factor_;
|
||||||
|
std::string desc_;
|
||||||
|
std::string monitor_;
|
||||||
|
std::string current_sink_name_;
|
||||||
|
bool current_sink_running_;
|
||||||
|
// SOURCE
|
||||||
|
uint32_t source_idx_{0};
|
||||||
|
uint16_t source_volume_;
|
||||||
|
bool source_muted_;
|
||||||
|
std::string source_port_name_;
|
||||||
|
std::string source_desc_;
|
||||||
|
std::string default_source_name_;
|
||||||
|
|
||||||
|
std::vector<std::string> ignored_sinks_;
|
||||||
|
|
||||||
|
std::function<void()> on_updated_cb_ = NOOP;
|
||||||
|
|
||||||
|
/* Hack to keep constructor inaccessible but still public.
|
||||||
|
* This is required to be able to use std::make_shared.
|
||||||
|
* It is important to keep this class only accessible via a reference-counted
|
||||||
|
* pointer because the destructor will manually free memory, and this could be
|
||||||
|
* a problem with C++20's copy and move semantics.
|
||||||
|
*/
|
||||||
|
struct private_constructor_tag {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
static std::shared_ptr<AudioBackend> getInstance(std::function<void()> on_updated_cb = NOOP);
|
||||||
|
|
||||||
|
AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag);
|
||||||
|
~AudioBackend();
|
||||||
|
|
||||||
|
void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100);
|
||||||
|
void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100);
|
||||||
|
|
||||||
|
void setIgnoredSinks(const Json::Value& config);
|
||||||
|
|
||||||
|
std::string getSinkPortName() const { return port_name_; }
|
||||||
|
std::string getFormFactor() const { return form_factor_; }
|
||||||
|
std::string getSinkDesc() const { return desc_; }
|
||||||
|
std::string getMonitor() const { return monitor_; }
|
||||||
|
std::string getCurrentSinkName() const { return current_sink_name_; }
|
||||||
|
bool getCurrentSinkRunning() const { return current_sink_running_; }
|
||||||
|
uint16_t getSinkVolume() const { return volume_; }
|
||||||
|
bool getSinkMuted() const { return muted_; }
|
||||||
|
uint16_t getSourceVolume() const { return source_volume_; }
|
||||||
|
bool getSourceMuted() const { return source_muted_; }
|
||||||
|
std::string getSourcePortName() const { return source_port_name_; }
|
||||||
|
std::string getSourceDesc() const { return source_desc_; }
|
||||||
|
std::string getDefaultSourceName() const { return default_source_name_; }
|
||||||
|
|
||||||
|
void toggleSinkMute();
|
||||||
|
void toggleSinkMute(bool);
|
||||||
|
|
||||||
|
void toggleSourceMute();
|
||||||
|
void toggleSourceMute(bool);
|
||||||
|
|
||||||
|
bool isBluetooth();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::util
|
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AModule.hpp"
|
||||||
|
|
||||||
|
namespace waybar::util {
|
||||||
|
|
||||||
|
const static auto NOOP = []() {};
|
||||||
|
enum class ChangeType : char { Increase, Decrease };
|
||||||
|
|
||||||
|
} // namespace waybar::util
|
|
@ -0,0 +1,93 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libudev.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "giomm/dbusproxy.h"
|
||||||
|
#include "util/backend_common.hpp"
|
||||||
|
#include "util/sleeper_thread.hpp"
|
||||||
|
|
||||||
|
#define GET_BEST_DEVICE(varname, backend, preferred_device) \
|
||||||
|
decltype((backend).devices_) __devices; \
|
||||||
|
{ \
|
||||||
|
std::scoped_lock<std::mutex> lock((backend).udev_thread_mutex_); \
|
||||||
|
__devices = (backend).devices_; \
|
||||||
|
} \
|
||||||
|
auto varname = (backend).best_device(__devices.cbegin(), __devices.cend(), preferred_device);
|
||||||
|
|
||||||
|
namespace waybar::util {
|
||||||
|
|
||||||
|
class BacklightDevice {
|
||||||
|
public:
|
||||||
|
BacklightDevice() = default;
|
||||||
|
BacklightDevice(std::string name, int actual, int max, bool powered);
|
||||||
|
|
||||||
|
std::string name() const;
|
||||||
|
int get_actual() const;
|
||||||
|
void set_actual(int actual);
|
||||||
|
int get_max() const;
|
||||||
|
void set_max(int max);
|
||||||
|
bool get_powered() const;
|
||||||
|
void set_powered(bool powered);
|
||||||
|
friend inline bool operator==(const BacklightDevice &lhs, const BacklightDevice &rhs) {
|
||||||
|
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
int actual_ = 1;
|
||||||
|
int max_ = 1;
|
||||||
|
bool powered_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BacklightBackend {
|
||||||
|
public:
|
||||||
|
BacklightBackend(std::chrono::milliseconds interval, std::function<void()> on_updated_cb = NOOP);
|
||||||
|
|
||||||
|
// const inline BacklightDevice *get_best_device(std::string_view preferred_device);
|
||||||
|
const BacklightDevice *get_previous_best_device();
|
||||||
|
|
||||||
|
void set_previous_best_device(const BacklightDevice *device);
|
||||||
|
|
||||||
|
void set_brightness(std::string preferred_device, ChangeType change_type, double step);
|
||||||
|
|
||||||
|
void set_scaled_brightness(std::string preferred_device, int brightness);
|
||||||
|
int get_scaled_brightness(std::string preferred_device);
|
||||||
|
|
||||||
|
template <class ForwardIt, class Inserter>
|
||||||
|
static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter, udev_device *dev);
|
||||||
|
|
||||||
|
template <class ForwardIt, class Inserter>
|
||||||
|
static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev);
|
||||||
|
|
||||||
|
bool is_login_proxy_initialized() const { return static_cast<bool>(login_proxy_); }
|
||||||
|
|
||||||
|
template <class ForwardIt>
|
||||||
|
static const BacklightDevice *best_device(ForwardIt first, ForwardIt last, std::string_view);
|
||||||
|
|
||||||
|
std::vector<BacklightDevice> devices_;
|
||||||
|
std::mutex udev_thread_mutex_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void set_brightness_internal(std::string device_name, int brightness, int max_brightness);
|
||||||
|
|
||||||
|
std::function<void()> on_updated_cb_;
|
||||||
|
std::chrono::milliseconds polling_interval_;
|
||||||
|
|
||||||
|
std::optional<BacklightDevice> previous_best_;
|
||||||
|
// thread must destruct before shared data
|
||||||
|
util::SleeperThread udev_thread_;
|
||||||
|
|
||||||
|
Glib::RefPtr<Gio::DBus::Proxy> login_proxy_;
|
||||||
|
|
||||||
|
static constexpr int EPOLL_MAX_EVENTS = 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace waybar::util
|
|
@ -0,0 +1,88 @@
|
||||||
|
waybar-backlight-slider(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - backlight slider module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *backlight slider* module displays and controls the current brightness of the default or preferred device.
|
||||||
|
|
||||||
|
The brightness can be controlled by dragging the slider accross the bar, or clicking on a specific position.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
*min*: ++
|
||||||
|
typeof: int ++
|
||||||
|
default: 0 ++
|
||||||
|
The minimum volume value the slider should display and set.
|
||||||
|
|
||||||
|
*max*: ++
|
||||||
|
typeof: int ++
|
||||||
|
default: 100 ++
|
||||||
|
The maximum volume value the slider should display and set.
|
||||||
|
|
||||||
|
*orientation*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: horizontal ++
|
||||||
|
The orientation of the slider. Can be either `horizontal` or `vertical`.
|
||||||
|
|
||||||
|
*device*: ++
|
||||||
|
typeof: string ++
|
||||||
|
The name of the preferred device to control. If left empty, a device will be chosen automatically.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"modules-right": [
|
||||||
|
"backlight-slider",
|
||||||
|
],
|
||||||
|
"backlight/slider": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"orientation": "horizontal",
|
||||||
|
"device": "intel_backlight"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
The slider is a component with multiple CSS Nodes, of which the following are exposed:
|
||||||
|
|
||||||
|
*#backlight-slider*: ++
|
||||||
|
Controls the style of the box *around* the slider and bar.
|
||||||
|
|
||||||
|
*#backlight-slider slider*: ++
|
||||||
|
Controls the style of the slider handle.
|
||||||
|
|
||||||
|
*#backlight-slider trough*: ++
|
||||||
|
Controls the style of the part of the bar that has not been filled.
|
||||||
|
|
||||||
|
*#backlight-slider highlight*: ++
|
||||||
|
Controls the style of the part of the bar that has been filled.
|
||||||
|
|
||||||
|
## STYLE EXAMPLE
|
||||||
|
|
||||||
|
```
|
||||||
|
#backlight-slider slider {
|
||||||
|
min-height: 0px;
|
||||||
|
min-width: 0px;
|
||||||
|
opacity: 0;
|
||||||
|
background-image: none;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#backlight-slider trough {
|
||||||
|
min-height: 80px;
|
||||||
|
min-width: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#backlight-slider highlight {
|
||||||
|
min-width: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,83 @@
|
||||||
|
waybar-pulseaudio-slider(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
waybar - pulseaudio slider module
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
The *pulseaudio slider* module displays and controls the current volume of the default sink or source as a bar.
|
||||||
|
|
||||||
|
The volume can be controlled by dragging the slider accross the bar, or clicking on a specific position.
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
*min*: ++
|
||||||
|
typeof: int ++
|
||||||
|
default: 0 ++
|
||||||
|
The minimum volume value the slider should display and set.
|
||||||
|
|
||||||
|
*max*: ++
|
||||||
|
typeof: int ++
|
||||||
|
default: 100 ++
|
||||||
|
The maximum volume value the slider should display and set.
|
||||||
|
|
||||||
|
*orientation*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: horizontal ++
|
||||||
|
The orientation of the slider. Can be either `horizontal` or `vertical`.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
```
|
||||||
|
"modules-right": [
|
||||||
|
"pulseaudio-slider",
|
||||||
|
],
|
||||||
|
"pulseaudio/slider": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
"orientation": "horizontal"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# STYLE
|
||||||
|
|
||||||
|
The slider is a component with multiple CSS Nodes, of which the following are exposed:
|
||||||
|
|
||||||
|
*#pulseaudio-slider*: ++
|
||||||
|
Controls the style of the box *around* the slider and bar.
|
||||||
|
|
||||||
|
*#pulseaudio-slider slider*: ++
|
||||||
|
Controls the style of the slider handle.
|
||||||
|
|
||||||
|
*#pulseaudio-slider trough*: ++
|
||||||
|
Controls the style of the part of the bar that has not been filled.
|
||||||
|
|
||||||
|
*#pulseaudio-slider highlight*: ++
|
||||||
|
Controls the style of the part of the bar that has been filled.
|
||||||
|
|
||||||
|
## STYLE EXAMPLE
|
||||||
|
|
||||||
|
```
|
||||||
|
#pulseaudio-slider slider {
|
||||||
|
min-height: 0px;
|
||||||
|
min-width: 0px;
|
||||||
|
opacity: 0;
|
||||||
|
background-image: none;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pulseaudio-slider trough {
|
||||||
|
min-height: 80px;
|
||||||
|
min-width: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pulseaudio-slider highlight {
|
||||||
|
min-width: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
```
|
|
@ -166,6 +166,7 @@ src_files = files(
|
||||||
'src/modules/image.cpp',
|
'src/modules/image.cpp',
|
||||||
'src/modules/temperature.cpp',
|
'src/modules/temperature.cpp',
|
||||||
'src/modules/user.cpp',
|
'src/modules/user.cpp',
|
||||||
|
'src/ASlider.cpp',
|
||||||
'src/main.cpp',
|
'src/main.cpp',
|
||||||
'src/bar.cpp',
|
'src/bar.cpp',
|
||||||
'src/client.cpp',
|
'src/client.cpp',
|
||||||
|
@ -273,6 +274,8 @@ endif
|
||||||
if libpulse.found()
|
if libpulse.found()
|
||||||
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
|
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
|
||||||
src_files += 'src/modules/pulseaudio.cpp'
|
src_files += 'src/modules/pulseaudio.cpp'
|
||||||
|
src_files += 'src/modules/pulseaudio_slider.cpp'
|
||||||
|
src_files += 'src/util/audio_backend.cpp'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if libjack.found()
|
if libjack.found()
|
||||||
|
@ -298,6 +301,8 @@ endif
|
||||||
if libudev.found() and (is_linux or libepoll.found())
|
if libudev.found() and (is_linux or libepoll.found())
|
||||||
add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp')
|
add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp')
|
||||||
src_files += 'src/modules/backlight.cpp'
|
src_files += 'src/modules/backlight.cpp'
|
||||||
|
src_files += 'src/modules/backlight_slider.cpp'
|
||||||
|
src_files += 'src/util/backlight_backend.cpp'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found())
|
if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found())
|
||||||
|
@ -425,6 +430,7 @@ if scdoc.found()
|
||||||
man_files = [
|
man_files = [
|
||||||
main_manpage_path,
|
main_manpage_path,
|
||||||
'waybar-backlight.5.scd',
|
'waybar-backlight.5.scd',
|
||||||
|
'waybar-backlight-slider.5.scd',
|
||||||
'waybar-battery.5.scd',
|
'waybar-battery.5.scd',
|
||||||
'waybar-cava.5.scd',
|
'waybar-cava.5.scd',
|
||||||
'waybar-clock.5.scd',
|
'waybar-clock.5.scd',
|
||||||
|
@ -440,6 +446,7 @@ if scdoc.found()
|
||||||
'waybar-mpris.5.scd',
|
'waybar-mpris.5.scd',
|
||||||
'waybar-network.5.scd',
|
'waybar-network.5.scd',
|
||||||
'waybar-pulseaudio.5.scd',
|
'waybar-pulseaudio.5.scd',
|
||||||
|
'waybar-pulseaudio-slider.5.scd',
|
||||||
'waybar-river-mode.5.scd',
|
'waybar-river-mode.5.scd',
|
||||||
'waybar-river-tags.5.scd',
|
'waybar-river-tags.5.scd',
|
||||||
'waybar-river-window.5.scd',
|
'waybar-river-window.5.scd',
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
#include "ASlider.hpp"
|
||||||
|
|
||||||
|
#include "gtkmm/adjustment.h"
|
||||||
|
#include "gtkmm/enums.h"
|
||||||
|
|
||||||
|
namespace waybar {
|
||||||
|
|
||||||
|
ASlider::ASlider(const Json::Value& config, const std::string& name, const std::string& id)
|
||||||
|
: AModule(config, name, id, false, false),
|
||||||
|
vertical_(config_["orientation"].asString() == "vertical"),
|
||||||
|
scale_(vertical_ ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL) {
|
||||||
|
scale_.set_name(name);
|
||||||
|
if (!id.empty()) {
|
||||||
|
scale_.get_style_context()->add_class(id);
|
||||||
|
}
|
||||||
|
event_box_.add(scale_);
|
||||||
|
scale_.signal_value_changed().connect(sigc::mem_fun(*this, &ASlider::onValueChanged));
|
||||||
|
|
||||||
|
if (config_["min"].isUInt()) {
|
||||||
|
min_ = config_["min"].asUInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_["max"].isUInt()) {
|
||||||
|
max_ = config_["max"].asUInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
scale_.set_inverted(vertical_);
|
||||||
|
scale_.set_draw_value(false);
|
||||||
|
scale_.set_adjustment(Gtk::Adjustment::create(curr_, min_, max_ + 1, 1, 1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ASlider::onValueChanged() {}
|
||||||
|
|
||||||
|
} // namespace waybar
|
|
@ -1,5 +1,13 @@
|
||||||
#include "factory.hpp"
|
#include "factory.hpp"
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBPULSE
|
||||||
|
#include "modules/pulseaudio_slider.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBUDEV
|
||||||
|
#include "modules/backlight_slider.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
waybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {}
|
waybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {}
|
||||||
|
|
||||||
waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
|
@ -126,6 +134,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
if (ref == "backlight") {
|
if (ref == "backlight") {
|
||||||
return new waybar::modules::Backlight(id, config_[name]);
|
return new waybar::modules::Backlight(id, config_[name]);
|
||||||
}
|
}
|
||||||
|
if (ref == "backlight/slider") {
|
||||||
|
return new waybar::modules::BacklightSlider(id, config_[name]);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_LIBEVDEV
|
#ifdef HAVE_LIBEVDEV
|
||||||
if (ref == "keyboard-state") {
|
if (ref == "keyboard-state") {
|
||||||
|
@ -136,6 +147,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||||
if (ref == "pulseaudio") {
|
if (ref == "pulseaudio") {
|
||||||
return new waybar::modules::Pulseaudio(id, config_[name]);
|
return new waybar::modules::Pulseaudio(id, config_[name]);
|
||||||
}
|
}
|
||||||
|
if (ref == "pulseaudio/slider") {
|
||||||
|
return new waybar::modules::PulseaudioSlider(id, config_[name]);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_LIBMPDCLIENT
|
#ifdef HAVE_LIBMPDCLIENT
|
||||||
if (ref == "mpd") {
|
if (ref == "mpd") {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <libudev.h>
|
#include <libudev.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
@ -9,179 +10,26 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace {
|
#include "util/backend_common.hpp"
|
||||||
class FileDescriptor {
|
#include "util/backlight_backend.hpp"
|
||||||
public:
|
|
||||||
explicit FileDescriptor(int fd) : fd_(fd) {}
|
|
||||||
FileDescriptor(const FileDescriptor &other) = delete;
|
|
||||||
FileDescriptor(FileDescriptor &&other) noexcept = delete;
|
|
||||||
FileDescriptor &operator=(const FileDescriptor &other) = delete;
|
|
||||||
FileDescriptor &operator=(FileDescriptor &&other) noexcept = delete;
|
|
||||||
~FileDescriptor() {
|
|
||||||
if (fd_ != -1) {
|
|
||||||
if (close(fd_) != 0) {
|
|
||||||
fmt::print(stderr, "Failed to close fd: {}\n", errno);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int get() const { return fd_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int fd_;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UdevDeleter {
|
|
||||||
void operator()(udev *ptr) { udev_unref(ptr); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UdevDeviceDeleter {
|
|
||||||
void operator()(udev_device *ptr) { udev_device_unref(ptr); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UdevEnumerateDeleter {
|
|
||||||
void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UdevMonitorDeleter {
|
|
||||||
void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); }
|
|
||||||
};
|
|
||||||
|
|
||||||
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
|
|
||||||
if (rc != expected) {
|
|
||||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
|
|
||||||
if (rc == bad_rc) {
|
|
||||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, message); }
|
|
||||||
|
|
||||||
void check_gte(int rc, int gte, const char *message = "rc was: ") {
|
|
||||||
if (rc < gte) {
|
|
||||||
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void check_nn(const void *ptr, const char *message = "ptr was null") {
|
|
||||||
if (ptr == nullptr) {
|
|
||||||
throw std::runtime_error(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max,
|
|
||||||
bool powered)
|
|
||||||
: name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {}
|
|
||||||
|
|
||||||
std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; }
|
|
||||||
|
|
||||||
int waybar::modules::Backlight::BacklightDev::get_actual() const { return actual_; }
|
|
||||||
|
|
||||||
void waybar::modules::Backlight::BacklightDev::set_actual(int actual) { actual_ = actual; }
|
|
||||||
|
|
||||||
int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
|
|
||||||
|
|
||||||
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
|
|
||||||
|
|
||||||
bool waybar::modules::Backlight::BacklightDev::get_powered() const { return powered_; }
|
|
||||||
|
|
||||||
void waybar::modules::Backlight::BacklightDev::set_powered(bool powered) { powered_ = powered; }
|
|
||||||
|
|
||||||
waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
|
waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
|
||||||
: ALabel(config, "backlight", id, "{percent}%", 2),
|
: ALabel(config, "backlight", id, "{percent}%", 2),
|
||||||
preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
|
preferred_device_(config["device"].isString() ? config["device"].asString() : ""),
|
||||||
// Get initial state
|
backend(interval_, [this] { dp.emit(); }) {
|
||||||
{
|
dp.emit();
|
||||||
std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};
|
|
||||||
check_nn(udev_check.get(), "Udev check new failed");
|
|
||||||
enumerate_devices(devices_.begin(), devices_.end(), std::back_inserter(devices_),
|
|
||||||
udev_check.get());
|
|
||||||
if (devices_.empty()) {
|
|
||||||
throw std::runtime_error("No backlight found");
|
|
||||||
}
|
|
||||||
dp.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up scroll handler
|
// Set up scroll handler
|
||||||
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||||
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll));
|
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll));
|
||||||
|
|
||||||
// Connect to the login interface
|
|
||||||
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
|
|
||||||
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
|
|
||||||
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
|
|
||||||
|
|
||||||
udev_thread_ = [this] {
|
|
||||||
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
|
|
||||||
check_nn(udev.get(), "Udev new failed");
|
|
||||||
|
|
||||||
std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{
|
|
||||||
udev_monitor_new_from_netlink(udev.get(), "udev")};
|
|
||||||
check_nn(mon.get(), "udev monitor new failed");
|
|
||||||
check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "backlight", nullptr), 0,
|
|
||||||
"udev failed to add monitor filter: ");
|
|
||||||
udev_monitor_enable_receiving(mon.get());
|
|
||||||
|
|
||||||
auto udev_fd = udev_monitor_get_fd(mon.get());
|
|
||||||
|
|
||||||
auto epoll_fd = FileDescriptor{epoll_create1(EPOLL_CLOEXEC)};
|
|
||||||
check_neq(epoll_fd.get(), -1, "epoll init failed: ");
|
|
||||||
epoll_event ctl_event{};
|
|
||||||
ctl_event.events = EPOLLIN;
|
|
||||||
ctl_event.data.fd = udev_fd;
|
|
||||||
|
|
||||||
check0(epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event),
|
|
||||||
"epoll_ctl failed: {}");
|
|
||||||
epoll_event events[EPOLL_MAX_EVENTS];
|
|
||||||
|
|
||||||
while (udev_thread_.isRunning()) {
|
|
||||||
const int event_count = epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS,
|
|
||||||
std::chrono::milliseconds{interval_}.count());
|
|
||||||
if (!udev_thread_.isRunning()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
decltype(devices_) devices;
|
|
||||||
{
|
|
||||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
|
||||||
devices = devices_;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < event_count; ++i) {
|
|
||||||
const auto &event = events[i];
|
|
||||||
check_eq(event.data.fd, udev_fd, "unexpected udev fd");
|
|
||||||
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_monitor_receive_device(mon.get())};
|
|
||||||
check_nn(dev.get(), "epoll dev was null");
|
|
||||||
upsert_device(devices.begin(), devices.end(), std::back_inserter(devices), dev.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh state if timed out
|
|
||||||
if (event_count == 0) {
|
|
||||||
enumerate_devices(devices.begin(), devices.end(), std::back_inserter(devices), udev.get());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
|
||||||
devices_ = devices;
|
|
||||||
}
|
|
||||||
dp.emit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
waybar::modules::Backlight::~Backlight() = default;
|
|
||||||
|
|
||||||
auto waybar::modules::Backlight::update() -> void {
|
auto waybar::modules::Backlight::update() -> void {
|
||||||
decltype(devices_) devices;
|
GET_BEST_DEVICE(best, backend, preferred_device_);
|
||||||
{
|
|
||||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
|
||||||
devices = devices_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
|
const auto previous_best_device = backend.get_previous_best_device();
|
||||||
if (best != nullptr) {
|
if (best != nullptr) {
|
||||||
if (previous_best_.has_value() && previous_best_.value() == *best &&
|
if (previous_best_device != nullptr && *previous_best_device == *best &&
|
||||||
!previous_format_.empty() && previous_format_ == format_) {
|
!previous_format_.empty() && previous_format_ == format_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -211,84 +59,16 @@ auto waybar::modules::Backlight::update() -> void {
|
||||||
event_box_.hide();
|
event_box_.hide();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!previous_best_.has_value()) {
|
if (previous_best_device == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
label_.set_markup("");
|
label_.set_markup("");
|
||||||
}
|
}
|
||||||
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
|
backend.set_previous_best_device(best);
|
||||||
previous_format_ = format_;
|
previous_format_ = format_;
|
||||||
// Call parent update
|
|
||||||
ALabel::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class ForwardIt>
|
|
||||||
const waybar::modules::Backlight::BacklightDev *waybar::modules::Backlight::best_device(
|
|
||||||
ForwardIt first, ForwardIt last, std::string_view preferred_device) {
|
|
||||||
const auto found = std::find_if(
|
|
||||||
first, last, [preferred_device](const auto &dev) { return dev.name() == preferred_device; });
|
|
||||||
if (found != last) {
|
|
||||||
return &(*found);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto max = std::max_element(
|
|
||||||
first, last, [](const auto &l, const auto &r) { return l.get_max() < r.get_max(); });
|
|
||||||
|
|
||||||
return max == last ? nullptr : &(*max);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ForwardIt, class Inserter>
|
|
||||||
void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last, Inserter inserter,
|
|
||||||
udev_device *dev) {
|
|
||||||
const char *name = udev_device_get_sysname(dev);
|
|
||||||
check_nn(name);
|
|
||||||
|
|
||||||
const char *actual_brightness_attr =
|
|
||||||
strncmp(name, "amdgpu_bl", 9) == 0 || strcmp(name, "apple-panel-bl") == 0
|
|
||||||
? "brightness"
|
|
||||||
: "actual_brightness";
|
|
||||||
|
|
||||||
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
|
|
||||||
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
|
|
||||||
const char *power = udev_device_get_sysattr_value(dev, "bl_power");
|
|
||||||
|
|
||||||
auto found =
|
|
||||||
std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
|
|
||||||
if (found != last) {
|
|
||||||
if (actual != nullptr) {
|
|
||||||
found->set_actual(std::stoi(actual));
|
|
||||||
}
|
|
||||||
if (max != nullptr) {
|
|
||||||
found->set_max(std::stoi(max));
|
|
||||||
}
|
|
||||||
if (power != nullptr) {
|
|
||||||
found->set_powered(std::stoi(power) == 0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
|
|
||||||
const int max_int = max == nullptr ? 0 : std::stoi(max);
|
|
||||||
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0;
|
|
||||||
*inserter = BacklightDev{name, actual_int, max_int, power_bool};
|
|
||||||
++inserter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ForwardIt, class Inserter>
|
|
||||||
void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt last,
|
|
||||||
Inserter inserter, udev *udev) {
|
|
||||||
std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{udev_enumerate_new(udev)};
|
|
||||||
udev_enumerate_add_match_subsystem(enumerate.get(), "backlight");
|
|
||||||
udev_enumerate_scan_devices(enumerate.get());
|
|
||||||
udev_list_entry *enum_devices = udev_enumerate_get_list_entry(enumerate.get());
|
|
||||||
udev_list_entry *dev_list_entry;
|
|
||||||
udev_list_entry_foreach(dev_list_entry, enum_devices) {
|
|
||||||
const char *path = udev_list_entry_get_name(dev_list_entry);
|
|
||||||
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_device_new_from_syspath(udev, path)};
|
|
||||||
check_nn(dev.get(), "dev new failed");
|
|
||||||
upsert_device(first, last, inserter, dev.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
|
bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
|
||||||
// Check if the user has set a custom command for scrolling
|
// Check if the user has set a custom command for scrolling
|
||||||
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
||||||
|
@ -296,14 +76,33 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail fast if the proxy could not be initialized
|
// Fail fast if the proxy could not be initialized
|
||||||
if (!login_proxy_) {
|
if (!backend.is_login_proxy_initialized()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check scroll direction
|
// Check scroll direction
|
||||||
auto dir = AModule::getScrollDir(e);
|
auto dir = AModule::getScrollDir(e);
|
||||||
if (dir == SCROLL_DIR::NONE) {
|
|
||||||
return true;
|
// No worries, it will always be set because of the switch below. This is purely to suppress a
|
||||||
|
// warning
|
||||||
|
util::ChangeType ct = util::ChangeType::Increase;
|
||||||
|
|
||||||
|
switch (dir) {
|
||||||
|
case SCROLL_DIR::UP:
|
||||||
|
[[fallthrough]];
|
||||||
|
case SCROLL_DIR::RIGHT:
|
||||||
|
ct = util::ChangeType::Increase;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCROLL_DIR::DOWN:
|
||||||
|
[[fallthrough]];
|
||||||
|
case SCROLL_DIR::LEFT:
|
||||||
|
ct = util::ChangeType::Decrease;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCROLL_DIR::NONE:
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get scroll step
|
// Get scroll step
|
||||||
|
@ -313,38 +112,7 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
|
||||||
step = config_["scroll-step"].asDouble();
|
step = config_["scroll-step"].asDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the best device
|
backend.set_brightness(preferred_device_, ct, step);
|
||||||
decltype(devices_) devices;
|
|
||||||
{
|
|
||||||
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
|
||||||
devices = devices_;
|
|
||||||
}
|
|
||||||
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
|
|
||||||
|
|
||||||
if (best == nullptr) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the absolute step
|
|
||||||
const auto abs_step = static_cast<int>(round(step * best->get_max() / 100.0f));
|
|
||||||
|
|
||||||
// Compute the new value
|
|
||||||
int new_value = best->get_actual();
|
|
||||||
|
|
||||||
if (dir == SCROLL_DIR::UP) {
|
|
||||||
new_value += abs_step;
|
|
||||||
} else if (dir == SCROLL_DIR::DOWN) {
|
|
||||||
new_value -= abs_step;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp the value
|
|
||||||
new_value = std::clamp(new_value, 0, best->get_max());
|
|
||||||
|
|
||||||
// Set the new value
|
|
||||||
auto call_args = Glib::VariantContainerBase(
|
|
||||||
g_variant_new("(ssu)", "backlight", std::string(best->name()).c_str(), new_value));
|
|
||||||
|
|
||||||
login_proxy_->call_sync("SetBrightness", call_args);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
#include "modules/backlight_slider.hpp"
|
||||||
|
|
||||||
|
#include "ASlider.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
BacklightSlider::BacklightSlider(const std::string& id, const Json::Value& config)
|
||||||
|
: ASlider(config, "backlight-slider", id),
|
||||||
|
interval_(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1000),
|
||||||
|
preferred_device_(config["device"].isString() ? config["device"].asString() : ""),
|
||||||
|
backend(interval_, [this] { this->dp.emit(); }) {}
|
||||||
|
|
||||||
|
void BacklightSlider::update() {
|
||||||
|
uint16_t brightness = backend.get_scaled_brightness(preferred_device_);
|
||||||
|
scale_.set_value(brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BacklightSlider::onValueChanged() {
|
||||||
|
auto brightness = scale_.get_value();
|
||||||
|
backend.set_scaled_brightness(preferred_device_, brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules
|
|
@ -1,74 +1,12 @@
|
||||||
#include "modules/pulseaudio.hpp"
|
#include "modules/pulseaudio.hpp"
|
||||||
|
|
||||||
waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
|
waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
|
||||||
: ALabel(config, "pulseaudio", id, "{volume}%"),
|
: ALabel(config, "pulseaudio", id, "{volume}%") {
|
||||||
mainloop_(nullptr),
|
|
||||||
mainloop_api_(nullptr),
|
|
||||||
context_(nullptr),
|
|
||||||
sink_idx_(0),
|
|
||||||
volume_(0),
|
|
||||||
muted_(false),
|
|
||||||
source_idx_(0),
|
|
||||||
source_volume_(0),
|
|
||||||
source_muted_(false) {
|
|
||||||
mainloop_ = pa_threaded_mainloop_new();
|
|
||||||
if (mainloop_ == nullptr) {
|
|
||||||
throw std::runtime_error("pa_mainloop_new() failed.");
|
|
||||||
}
|
|
||||||
pa_threaded_mainloop_lock(mainloop_);
|
|
||||||
mainloop_api_ = pa_threaded_mainloop_get_api(mainloop_);
|
|
||||||
context_ = pa_context_new(mainloop_api_, "waybar");
|
|
||||||
if (context_ == nullptr) {
|
|
||||||
throw std::runtime_error("pa_context_new() failed.");
|
|
||||||
}
|
|
||||||
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) {
|
|
||||||
auto err =
|
|
||||||
fmt::format("pa_context_connect() failed: {}", pa_strerror(pa_context_errno(context_)));
|
|
||||||
throw std::runtime_error(err);
|
|
||||||
}
|
|
||||||
pa_context_set_state_callback(context_, contextStateCb, this);
|
|
||||||
if (pa_threaded_mainloop_start(mainloop_) < 0) {
|
|
||||||
throw std::runtime_error("pa_mainloop_run() failed.");
|
|
||||||
}
|
|
||||||
pa_threaded_mainloop_unlock(mainloop_);
|
|
||||||
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
||||||
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Pulseaudio::handleScroll));
|
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Pulseaudio::handleScroll));
|
||||||
}
|
|
||||||
|
|
||||||
waybar::modules::Pulseaudio::~Pulseaudio() {
|
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||||
pa_context_disconnect(context_);
|
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||||
mainloop_api_->quit(mainloop_api_, 0);
|
|
||||||
pa_threaded_mainloop_stop(mainloop_);
|
|
||||||
pa_threaded_mainloop_free(mainloop_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) {
|
|
||||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
|
||||||
switch (pa_context_get_state(c)) {
|
|
||||||
case PA_CONTEXT_TERMINATED:
|
|
||||||
pa->mainloop_api_->quit(pa->mainloop_api_, 0);
|
|
||||||
break;
|
|
||||||
case PA_CONTEXT_READY:
|
|
||||||
pa_context_get_server_info(c, serverInfoCb, data);
|
|
||||||
pa_context_set_subscribe_callback(c, subscribeCb, data);
|
|
||||||
pa_context_subscribe(c,
|
|
||||||
static_cast<enum pa_subscription_mask>(
|
|
||||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) |
|
|
||||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |
|
|
||||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) |
|
|
||||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) |
|
|
||||||
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)),
|
|
||||||
nullptr, nullptr);
|
|
||||||
break;
|
|
||||||
case PA_CONTEXT_FAILED:
|
|
||||||
pa->mainloop_api_->quit(pa->mainloop_api_, 1);
|
|
||||||
break;
|
|
||||||
case PA_CONTEXT_CONNECTING:
|
|
||||||
case PA_CONTEXT_AUTHORIZING:
|
|
||||||
case PA_CONTEXT_SETTING_NAME:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||||
|
@ -81,9 +19,6 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||||
if (dir == SCROLL_DIR::NONE) {
|
if (dir == SCROLL_DIR::NONE) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
|
||||||
pa_volume_t change = volume_tick;
|
|
||||||
pa_cvolume pa_volume = pa_volume_;
|
|
||||||
int max_volume = 100;
|
int max_volume = 100;
|
||||||
double step = 1;
|
double step = 1;
|
||||||
// isDouble returns true for integers as well, just in case
|
// isDouble returns true for integers as well, just in case
|
||||||
|
@ -91,152 +26,24 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
|
||||||
step = config_["scroll-step"].asDouble();
|
step = config_["scroll-step"].asDouble();
|
||||||
}
|
}
|
||||||
if (config_["max-volume"].isInt()) {
|
if (config_["max-volume"].isInt()) {
|
||||||
max_volume = std::min(config_["max-volume"].asInt(), static_cast<int>(PA_VOLUME_UI_MAX));
|
max_volume = config_["max-volume"].asInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir == SCROLL_DIR::UP) {
|
auto change_type = (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::RIGHT)
|
||||||
if (volume_ < max_volume) {
|
? util::ChangeType::Increase
|
||||||
if (volume_ + step > max_volume) {
|
: util::ChangeType::Decrease;
|
||||||
change = round((max_volume - volume_) * volume_tick);
|
|
||||||
} else {
|
backend->changeVolume(change_type, step, max_volume);
|
||||||
change = round(step * volume_tick);
|
|
||||||
}
|
|
||||||
pa_cvolume_inc(&pa_volume, change);
|
|
||||||
}
|
|
||||||
} else if (dir == SCROLL_DIR::DOWN) {
|
|
||||||
if (volume_ > 0) {
|
|
||||||
if (volume_ - step < 0) {
|
|
||||||
change = round(volume_ * volume_tick);
|
|
||||||
} else {
|
|
||||||
change = round(step * volume_tick);
|
|
||||||
}
|
|
||||||
pa_cvolume_dec(&pa_volume, change);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when an event we subscribed to occurs.
|
|
||||||
*/
|
|
||||||
void waybar::modules::Pulseaudio::subscribeCb(pa_context *context,
|
|
||||||
pa_subscription_event_type_t type, uint32_t idx,
|
|
||||||
void *data) {
|
|
||||||
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
|
|
||||||
unsigned operation = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
|
|
||||||
if (operation != PA_SUBSCRIPTION_EVENT_CHANGE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (facility == PA_SUBSCRIPTION_EVENT_SERVER) {
|
|
||||||
pa_context_get_server_info(context, serverInfoCb, data);
|
|
||||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
|
|
||||||
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
|
|
||||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
|
|
||||||
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
|
||||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {
|
|
||||||
pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data);
|
|
||||||
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
|
|
||||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called in response to a volume change request
|
|
||||||
*/
|
|
||||||
void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, void *data) {
|
|
||||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
|
||||||
if (success != 0) {
|
|
||||||
pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when the requested source information is ready.
|
|
||||||
*/
|
|
||||||
void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const pa_source_info *i,
|
|
||||||
int /*eol*/, void *data) {
|
|
||||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
|
||||||
if (i != nullptr && pa->default_source_name_ == i->name) {
|
|
||||||
auto source_volume = static_cast<float>(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM};
|
|
||||||
pa->source_volume_ = std::round(source_volume * 100.0F);
|
|
||||||
pa->source_idx_ = i->index;
|
|
||||||
pa->source_muted_ = i->mute != 0;
|
|
||||||
pa->source_desc_ = i->description;
|
|
||||||
pa->source_port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
|
|
||||||
pa->dp.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when the requested sink information is ready.
|
|
||||||
*/
|
|
||||||
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
|
|
||||||
int /*eol*/, void *data) {
|
|
||||||
if (i == nullptr) return;
|
|
||||||
|
|
||||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
|
||||||
|
|
||||||
if (pa->config_["ignored-sinks"].isArray()) {
|
|
||||||
for (const auto &ignored_sink : pa->config_["ignored-sinks"]) {
|
|
||||||
if (ignored_sink.asString() == i->description) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pa->current_sink_name_ == i->name) {
|
|
||||||
if (i->state != PA_SINK_RUNNING) {
|
|
||||||
pa->current_sink_running_ = false;
|
|
||||||
} else {
|
|
||||||
pa->current_sink_running_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) {
|
|
||||||
pa->current_sink_name_ = i->name;
|
|
||||||
pa->current_sink_running_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pa->current_sink_name_ == i->name) {
|
|
||||||
pa->pa_volume_ = i->volume;
|
|
||||||
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
|
|
||||||
pa->sink_idx_ = i->index;
|
|
||||||
pa->volume_ = std::round(volume * 100.0F);
|
|
||||||
pa->muted_ = i->mute != 0;
|
|
||||||
pa->desc_ = i->description;
|
|
||||||
pa->monitor_ = i->monitor_source_name;
|
|
||||||
pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
|
|
||||||
if (auto ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) {
|
|
||||||
pa->form_factor_ = ff;
|
|
||||||
} else {
|
|
||||||
pa->form_factor_ = "";
|
|
||||||
}
|
|
||||||
pa->dp.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when the requested information on the server is ready. This is
|
|
||||||
* used to find the default PulseAudio sink.
|
|
||||||
*/
|
|
||||||
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
|
|
||||||
void *data) {
|
|
||||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
|
||||||
pa->current_sink_name_ = i->default_sink_name;
|
|
||||||
pa->default_source_name_ = i->default_source_name;
|
|
||||||
|
|
||||||
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
|
||||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const std::array<std::string, 9> ports = {
|
static const std::array<std::string, 9> ports = {
|
||||||
"headphone", "speaker", "hdmi", "headset", "hands-free", "portable", "car", "hifi", "phone",
|
"headphone", "speaker", "hdmi", "headset", "hands-free", "portable", "car", "hifi", "phone",
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
||||||
std::vector<std::string> res = {current_sink_name_, default_source_name_};
|
std::vector<std::string> res = {backend->getCurrentSinkName(), backend->getDefaultSourceName()};
|
||||||
std::string nameLC = port_name_ + form_factor_;
|
std::string nameLC = backend->getSinkPortName() + backend->getFormFactor();
|
||||||
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
||||||
for (auto const &port : ports) {
|
for (auto const &port : ports) {
|
||||||
if (nameLC.find(port) != std::string::npos) {
|
if (nameLC.find(port) != std::string::npos) {
|
||||||
|
@ -250,17 +57,16 @@ const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const
|
||||||
auto waybar::modules::Pulseaudio::update() -> void {
|
auto waybar::modules::Pulseaudio::update() -> void {
|
||||||
auto format = format_;
|
auto format = format_;
|
||||||
std::string tooltip_format;
|
std::string tooltip_format;
|
||||||
|
auto sink_volume = backend->getSinkVolume();
|
||||||
if (!alt_) {
|
if (!alt_) {
|
||||||
std::string format_name = "format";
|
std::string format_name = "format";
|
||||||
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
|
if (backend->isBluetooth()) {
|
||||||
monitor_.find("a2dp-sink") != std::string::npos || // PipeWire
|
|
||||||
monitor_.find("bluez") != std::string::npos) {
|
|
||||||
format_name = format_name + "-bluetooth";
|
format_name = format_name + "-bluetooth";
|
||||||
label_.get_style_context()->add_class("bluetooth");
|
label_.get_style_context()->add_class("bluetooth");
|
||||||
} else {
|
} else {
|
||||||
label_.get_style_context()->remove_class("bluetooth");
|
label_.get_style_context()->remove_class("bluetooth");
|
||||||
}
|
}
|
||||||
if (muted_) {
|
if (backend->getSinkMuted()) {
|
||||||
// Check muted bluetooth format exist, otherwise fallback to default muted format
|
// Check muted bluetooth format exist, otherwise fallback to default muted format
|
||||||
if (format_name != "format" && !config_[format_name + "-muted"].isString()) {
|
if (format_name != "format" && !config_[format_name + "-muted"].isString()) {
|
||||||
format_name = "format";
|
format_name = "format";
|
||||||
|
@ -272,7 +78,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||||
label_.get_style_context()->remove_class("muted");
|
label_.get_style_context()->remove_class("muted");
|
||||||
label_.get_style_context()->remove_class("sink-muted");
|
label_.get_style_context()->remove_class("sink-muted");
|
||||||
}
|
}
|
||||||
auto state = getState(volume_, true);
|
auto state = getState(sink_volume, true);
|
||||||
if (!state.empty() && config_[format_name + "-" + state].isString()) {
|
if (!state.empty() && config_[format_name + "-" + state].isString()) {
|
||||||
format = config_[format_name + "-" + state].asString();
|
format = config_[format_name + "-" + state].asString();
|
||||||
} else if (config_[format_name].isString()) {
|
} else if (config_[format_name].isString()) {
|
||||||
|
@ -281,7 +87,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||||
}
|
}
|
||||||
// TODO: find a better way to split source/sink
|
// TODO: find a better way to split source/sink
|
||||||
std::string format_source = "{volume}%";
|
std::string format_source = "{volume}%";
|
||||||
if (source_muted_) {
|
if (backend->getSourceMuted()) {
|
||||||
label_.get_style_context()->add_class("source-muted");
|
label_.get_style_context()->add_class("source-muted");
|
||||||
if (config_["format-source-muted"].isString()) {
|
if (config_["format-source-muted"].isString()) {
|
||||||
format_source = config_["format-source-muted"].asString();
|
format_source = config_["format-source-muted"].asString();
|
||||||
|
@ -292,11 +98,16 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||||
format_source = config_["format-source"].asString();
|
format_source = config_["format-source"].asString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume_));
|
|
||||||
|
auto source_volume = backend->getSourceVolume();
|
||||||
|
auto sink_desc = backend->getSinkDesc();
|
||||||
|
auto source_desc = backend->getSourceDesc();
|
||||||
|
|
||||||
|
format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume));
|
||||||
auto text = fmt::format(
|
auto text = fmt::format(
|
||||||
fmt::runtime(format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
|
fmt::runtime(format), fmt::arg("desc", sink_desc), fmt::arg("volume", sink_volume),
|
||||||
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
|
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume),
|
||||||
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
|
fmt::arg("source_desc", source_desc), fmt::arg("icon", getIcon(sink_volume, getPulseIcon())));
|
||||||
if (text.empty()) {
|
if (text.empty()) {
|
||||||
label_.hide();
|
label_.hide();
|
||||||
} else {
|
} else {
|
||||||
|
@ -310,12 +121,12 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||||
}
|
}
|
||||||
if (!tooltip_format.empty()) {
|
if (!tooltip_format.empty()) {
|
||||||
label_.set_tooltip_text(fmt::format(
|
label_.set_tooltip_text(fmt::format(
|
||||||
fmt::runtime(tooltip_format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
|
fmt::runtime(tooltip_format), fmt::arg("desc", sink_desc),
|
||||||
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
|
fmt::arg("volume", sink_volume), fmt::arg("format_source", format_source),
|
||||||
fmt::arg("source_desc", source_desc_),
|
fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc),
|
||||||
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
|
fmt::arg("icon", getIcon(sink_volume, getPulseIcon()))));
|
||||||
} else {
|
} else {
|
||||||
label_.set_tooltip_text(desc_);
|
label_.set_tooltip_text(sink_desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
#include "modules/pulseaudio_slider.hpp"
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& config)
|
||||||
|
: ASlider(config, "pulseaudio-slider", id) {
|
||||||
|
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
|
||||||
|
backend->setIgnoredSinks(config_["ignored-sinks"]);
|
||||||
|
|
||||||
|
if (config_["target"].isString()) {
|
||||||
|
std::string target = config_["target"].asString();
|
||||||
|
if (target == "sink") {
|
||||||
|
this->target = PulseaudioSliderTarget::Sink;
|
||||||
|
} else if (target == "source") {
|
||||||
|
this->target = PulseaudioSliderTarget::Source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseaudioSlider::update() {
|
||||||
|
switch (target) {
|
||||||
|
case PulseaudioSliderTarget::Sink:
|
||||||
|
if (backend->getSinkMuted()) {
|
||||||
|
scale_.set_value(min_);
|
||||||
|
} else {
|
||||||
|
scale_.set_value(backend->getSinkVolume());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PulseaudioSliderTarget::Source:
|
||||||
|
if (backend->getSourceMuted()) {
|
||||||
|
scale_.set_value(min_);
|
||||||
|
} else {
|
||||||
|
scale_.set_value(backend->getSourceVolume());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseaudioSlider::onValueChanged() {
|
||||||
|
bool is_mute = false;
|
||||||
|
|
||||||
|
switch (target) {
|
||||||
|
case PulseaudioSliderTarget::Sink:
|
||||||
|
if (backend->getSinkMuted()) {
|
||||||
|
is_mute = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PulseaudioSliderTarget::Source:
|
||||||
|
if (backend->getSourceMuted()) {
|
||||||
|
is_mute = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t volume = scale_.get_value();
|
||||||
|
|
||||||
|
if (is_mute) {
|
||||||
|
// Avoid setting sink/source to volume 0 if the user muted if via another mean.
|
||||||
|
if (volume == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the sink/source is mute, but the user clicked the slider, unmute it!
|
||||||
|
else {
|
||||||
|
switch (target) {
|
||||||
|
case PulseaudioSliderTarget::Sink:
|
||||||
|
backend->toggleSinkMute(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PulseaudioSliderTarget::Source:
|
||||||
|
backend->toggleSourceMute(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backend->changeVolume(volume, min_, max_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::modules
|
|
@ -0,0 +1,280 @@
|
||||||
|
#include "util/audio_backend.hpp"
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <pulse/error.h>
|
||||||
|
#include <pulse/subscribe.h>
|
||||||
|
#include <pulse/volume.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace waybar::util {
|
||||||
|
|
||||||
|
AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag)
|
||||||
|
: mainloop_(nullptr),
|
||||||
|
mainloop_api_(nullptr),
|
||||||
|
context_(nullptr),
|
||||||
|
sink_idx_(0),
|
||||||
|
volume_(0),
|
||||||
|
muted_(false),
|
||||||
|
source_idx_(0),
|
||||||
|
source_volume_(0),
|
||||||
|
source_muted_(false),
|
||||||
|
on_updated_cb_(on_updated_cb) {
|
||||||
|
mainloop_ = pa_threaded_mainloop_new();
|
||||||
|
if (mainloop_ == nullptr) {
|
||||||
|
throw std::runtime_error("pa_mainloop_new() failed.");
|
||||||
|
}
|
||||||
|
pa_threaded_mainloop_lock(mainloop_);
|
||||||
|
mainloop_api_ = pa_threaded_mainloop_get_api(mainloop_);
|
||||||
|
context_ = pa_context_new(mainloop_api_, "waybar");
|
||||||
|
if (context_ == nullptr) {
|
||||||
|
throw std::runtime_error("pa_context_new() failed.");
|
||||||
|
}
|
||||||
|
if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) {
|
||||||
|
auto err =
|
||||||
|
fmt::format("pa_context_connect() failed: {}", pa_strerror(pa_context_errno(context_)));
|
||||||
|
throw std::runtime_error(err);
|
||||||
|
}
|
||||||
|
pa_context_set_state_callback(context_, contextStateCb, this);
|
||||||
|
if (pa_threaded_mainloop_start(mainloop_) < 0) {
|
||||||
|
throw std::runtime_error("pa_mainloop_run() failed.");
|
||||||
|
}
|
||||||
|
pa_threaded_mainloop_unlock(mainloop_);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioBackend::~AudioBackend() {
|
||||||
|
if (context_ != nullptr) {
|
||||||
|
pa_context_disconnect(context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainloop_ != nullptr) {
|
||||||
|
mainloop_api_->quit(mainloop_api_, 0);
|
||||||
|
pa_threaded_mainloop_stop(mainloop_);
|
||||||
|
pa_threaded_mainloop_free(mainloop_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AudioBackend> AudioBackend::getInstance(std::function<void()> on_updated_cb) {
|
||||||
|
private_constructor_tag tag;
|
||||||
|
return std::make_shared<AudioBackend>(on_updated_cb, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBackend::contextStateCb(pa_context *c, void *data) {
|
||||||
|
auto backend = static_cast<AudioBackend *>(data);
|
||||||
|
switch (pa_context_get_state(c)) {
|
||||||
|
case PA_CONTEXT_TERMINATED:
|
||||||
|
backend->mainloop_api_->quit(backend->mainloop_api_, 0);
|
||||||
|
break;
|
||||||
|
case PA_CONTEXT_READY:
|
||||||
|
pa_context_get_server_info(c, serverInfoCb, data);
|
||||||
|
pa_context_set_subscribe_callback(c, subscribeCb, data);
|
||||||
|
pa_context_subscribe(c,
|
||||||
|
static_cast<enum pa_subscription_mask>(
|
||||||
|
static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) |
|
||||||
|
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |
|
||||||
|
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) |
|
||||||
|
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) |
|
||||||
|
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)),
|
||||||
|
nullptr, nullptr);
|
||||||
|
break;
|
||||||
|
case PA_CONTEXT_FAILED:
|
||||||
|
backend->mainloop_api_->quit(backend->mainloop_api_, 1);
|
||||||
|
break;
|
||||||
|
case PA_CONTEXT_CONNECTING:
|
||||||
|
case PA_CONTEXT_AUTHORIZING:
|
||||||
|
case PA_CONTEXT_SETTING_NAME:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when an event we subscribed to occurs.
|
||||||
|
*/
|
||||||
|
void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t type, uint32_t idx,
|
||||||
|
void *data) {
|
||||||
|
unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
|
||||||
|
unsigned operation = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
|
||||||
|
if (operation != PA_SUBSCRIPTION_EVENT_CHANGE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (facility == PA_SUBSCRIPTION_EVENT_SERVER) {
|
||||||
|
pa_context_get_server_info(context, serverInfoCb, data);
|
||||||
|
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
|
||||||
|
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
|
||||||
|
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
|
||||||
|
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
||||||
|
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {
|
||||||
|
pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data);
|
||||||
|
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
|
||||||
|
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called in response to a volume change request
|
||||||
|
*/
|
||||||
|
void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) {
|
||||||
|
auto backend = static_cast<AudioBackend *>(data);
|
||||||
|
if (success != 0) {
|
||||||
|
pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when the requested sink information is ready.
|
||||||
|
*/
|
||||||
|
void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, int /*eol*/,
|
||||||
|
void *data) {
|
||||||
|
if (i == nullptr) return;
|
||||||
|
|
||||||
|
auto backend = static_cast<AudioBackend *>(data);
|
||||||
|
|
||||||
|
if (!backend->ignored_sinks_.empty()) {
|
||||||
|
for (const auto &ignored_sink : backend->ignored_sinks_) {
|
||||||
|
if (ignored_sink == i->description) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backend->current_sink_name_ == i->name) {
|
||||||
|
if (i->state != PA_SINK_RUNNING) {
|
||||||
|
backend->current_sink_running_ = false;
|
||||||
|
} else {
|
||||||
|
backend->current_sink_running_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!backend->current_sink_running_ && i->state == PA_SINK_RUNNING) {
|
||||||
|
backend->current_sink_name_ = i->name;
|
||||||
|
backend->current_sink_running_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backend->current_sink_name_ == i->name) {
|
||||||
|
backend->pa_volume_ = i->volume;
|
||||||
|
float volume =
|
||||||
|
static_cast<float>(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM};
|
||||||
|
backend->sink_idx_ = i->index;
|
||||||
|
backend->volume_ = std::round(volume * 100.0F);
|
||||||
|
backend->muted_ = i->mute != 0;
|
||||||
|
backend->desc_ = i->description;
|
||||||
|
backend->monitor_ = i->monitor_source_name;
|
||||||
|
backend->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
|
||||||
|
if (auto ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) {
|
||||||
|
backend->form_factor_ = ff;
|
||||||
|
} else {
|
||||||
|
backend->form_factor_ = "";
|
||||||
|
}
|
||||||
|
backend->on_updated_cb_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when the requested source information is ready.
|
||||||
|
*/
|
||||||
|
void AudioBackend::sourceInfoCb(pa_context * /*context*/, const pa_source_info *i, int /*eol*/,
|
||||||
|
void *data) {
|
||||||
|
auto backend = static_cast<AudioBackend *>(data);
|
||||||
|
if (i != nullptr && backend->default_source_name_ == i->name) {
|
||||||
|
auto source_volume = static_cast<float>(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM};
|
||||||
|
backend->source_volume_ = std::round(source_volume * 100.0F);
|
||||||
|
backend->source_idx_ = i->index;
|
||||||
|
backend->source_muted_ = i->mute != 0;
|
||||||
|
backend->source_desc_ = i->description;
|
||||||
|
backend->source_port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
|
||||||
|
backend->on_updated_cb_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called when the requested information on the server is ready. This is
|
||||||
|
* used to find the default PulseAudio sink.
|
||||||
|
*/
|
||||||
|
void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, void *data) {
|
||||||
|
auto backend = static_cast<AudioBackend *>(data);
|
||||||
|
backend->current_sink_name_ = i->default_sink_name;
|
||||||
|
backend->default_source_name_ = i->default_source_name;
|
||||||
|
|
||||||
|
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
||||||
|
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {
|
||||||
|
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||||
|
pa_cvolume pa_volume = pa_volume_;
|
||||||
|
|
||||||
|
volume = std::clamp(volume, min_volume, max_volume);
|
||||||
|
pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick);
|
||||||
|
|
||||||
|
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) {
|
||||||
|
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
|
||||||
|
pa_volume_t change = volume_tick;
|
||||||
|
pa_cvolume pa_volume = pa_volume_;
|
||||||
|
|
||||||
|
max_volume = std::min(max_volume, static_cast<uint16_t>(PA_VOLUME_UI_MAX));
|
||||||
|
|
||||||
|
if (change_type == ChangeType::Increase) {
|
||||||
|
if (volume_ < max_volume) {
|
||||||
|
if (volume_ + step > max_volume) {
|
||||||
|
change = round((max_volume - volume_) * volume_tick);
|
||||||
|
} else {
|
||||||
|
change = round(step * volume_tick);
|
||||||
|
}
|
||||||
|
pa_cvolume_inc(&pa_volume, change);
|
||||||
|
}
|
||||||
|
} else if (change_type == ChangeType::Decrease) {
|
||||||
|
if (volume_ > 0) {
|
||||||
|
if (volume_ - step < 0) {
|
||||||
|
change = round(volume_ * volume_tick);
|
||||||
|
} else {
|
||||||
|
change = round(step * volume_tick);
|
||||||
|
}
|
||||||
|
pa_cvolume_dec(&pa_volume, change);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBackend::toggleSinkMute() {
|
||||||
|
muted_ = !muted_;
|
||||||
|
pa_context_set_sink_mute_by_index(context_, sink_idx_, muted_, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBackend::toggleSinkMute(bool mute) {
|
||||||
|
muted_ = mute;
|
||||||
|
pa_context_set_sink_mute_by_index(context_, sink_idx_, muted_, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBackend::toggleSourceMute() {
|
||||||
|
source_muted_ = !muted_;
|
||||||
|
pa_context_set_source_mute_by_index(context_, source_idx_, source_muted_, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBackend::toggleSourceMute(bool mute) {
|
||||||
|
source_muted_ = mute;
|
||||||
|
pa_context_set_source_mute_by_index(context_, source_idx_, source_muted_, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioBackend::isBluetooth() {
|
||||||
|
return monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
|
||||||
|
monitor_.find("a2dp-sink") != std::string::npos || // PipeWire
|
||||||
|
monitor_.find("bluez") != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioBackend::setIgnoredSinks(const Json::Value &config) {
|
||||||
|
if (config.isArray()) {
|
||||||
|
for (const auto &ignored_sink : config) {
|
||||||
|
if (ignored_sink.isString()) {
|
||||||
|
ignored_sinks_.push_back(ignored_sink.asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::util
|
|
@ -0,0 +1,288 @@
|
||||||
|
#include "util/backlight_backend.hpp"
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class FileDescriptor {
|
||||||
|
public:
|
||||||
|
explicit FileDescriptor(int fd) : fd_(fd) {}
|
||||||
|
FileDescriptor(const FileDescriptor &other) = delete;
|
||||||
|
FileDescriptor(FileDescriptor &&other) noexcept = delete;
|
||||||
|
FileDescriptor &operator=(const FileDescriptor &other) = delete;
|
||||||
|
FileDescriptor &operator=(FileDescriptor &&other) noexcept = delete;
|
||||||
|
~FileDescriptor() {
|
||||||
|
if (fd_ != -1) {
|
||||||
|
if (close(fd_) != 0) {
|
||||||
|
fmt::print(stderr, "Failed to close fd: {}\n", errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int get() const { return fd_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fd_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UdevDeleter {
|
||||||
|
void operator()(udev *ptr) { udev_unref(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UdevDeviceDeleter {
|
||||||
|
void operator()(udev_device *ptr) { udev_device_unref(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UdevEnumerateDeleter {
|
||||||
|
void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UdevMonitorDeleter {
|
||||||
|
void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
|
||||||
|
if (rc != expected) {
|
||||||
|
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
|
||||||
|
if (rc == bad_rc) {
|
||||||
|
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, message); }
|
||||||
|
|
||||||
|
void check_gte(int rc, int gte, const char *message = "rc was: ") {
|
||||||
|
if (rc < gte) {
|
||||||
|
throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_nn(const void *ptr, const char *message = "ptr was null") {
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
throw std::runtime_error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace waybar::util {
|
||||||
|
|
||||||
|
BacklightDevice::BacklightDevice(std::string name, int actual, int max, bool powered)
|
||||||
|
: name_(name), actual_(actual), max_(max), powered_(powered) {}
|
||||||
|
|
||||||
|
std::string BacklightDevice::name() const { return name_; }
|
||||||
|
|
||||||
|
int BacklightDevice::get_actual() const { return actual_; }
|
||||||
|
|
||||||
|
void BacklightDevice::set_actual(int actual) { actual_ = actual; }
|
||||||
|
|
||||||
|
int BacklightDevice::get_max() const { return max_; }
|
||||||
|
|
||||||
|
void BacklightDevice::set_max(int max) { max_ = max; }
|
||||||
|
|
||||||
|
bool BacklightDevice::get_powered() const { return powered_; }
|
||||||
|
|
||||||
|
void BacklightDevice::set_powered(bool powered) { powered_ = powered; }
|
||||||
|
|
||||||
|
BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
|
||||||
|
std::function<void()> on_updated_cb)
|
||||||
|
: on_updated_cb_(on_updated_cb), polling_interval_(interval), previous_best_({}) {
|
||||||
|
std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};
|
||||||
|
check_nn(udev_check.get(), "Udev check new failed");
|
||||||
|
enumerate_devices(devices_.begin(), devices_.end(), std::back_inserter(devices_),
|
||||||
|
udev_check.get());
|
||||||
|
if (devices_.empty()) {
|
||||||
|
throw std::runtime_error("No backlight found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the login interface
|
||||||
|
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
|
||||||
|
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
|
||||||
|
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
|
||||||
|
|
||||||
|
udev_thread_ = [this] {
|
||||||
|
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
|
||||||
|
check_nn(udev.get(), "Udev new failed");
|
||||||
|
|
||||||
|
std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{
|
||||||
|
udev_monitor_new_from_netlink(udev.get(), "udev")};
|
||||||
|
check_nn(mon.get(), "udev monitor new failed");
|
||||||
|
check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "backlight", nullptr), 0,
|
||||||
|
"udev failed to add monitor filter: ");
|
||||||
|
udev_monitor_enable_receiving(mon.get());
|
||||||
|
|
||||||
|
auto udev_fd = udev_monitor_get_fd(mon.get());
|
||||||
|
|
||||||
|
auto epoll_fd = FileDescriptor{epoll_create1(EPOLL_CLOEXEC)};
|
||||||
|
check_neq(epoll_fd.get(), -1, "epoll init failed: ");
|
||||||
|
epoll_event ctl_event{};
|
||||||
|
ctl_event.events = EPOLLIN;
|
||||||
|
ctl_event.data.fd = udev_fd;
|
||||||
|
|
||||||
|
check0(epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event),
|
||||||
|
"epoll_ctl failed: {}");
|
||||||
|
epoll_event events[EPOLL_MAX_EVENTS];
|
||||||
|
|
||||||
|
while (udev_thread_.isRunning()) {
|
||||||
|
const int event_count =
|
||||||
|
epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, this->polling_interval_.count());
|
||||||
|
if (!udev_thread_.isRunning()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
decltype(devices_) devices;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||||
|
devices = devices_;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < event_count; ++i) {
|
||||||
|
const auto &event = events[i];
|
||||||
|
check_eq(event.data.fd, udev_fd, "unexpected udev fd");
|
||||||
|
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_monitor_receive_device(mon.get())};
|
||||||
|
check_nn(dev.get(), "epoll dev was null");
|
||||||
|
upsert_device(devices.begin(), devices.end(), std::back_inserter(devices), dev.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh state if timed out
|
||||||
|
if (event_count == 0) {
|
||||||
|
enumerate_devices(devices.begin(), devices.end(), std::back_inserter(devices), udev.get());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||||
|
devices_ = devices;
|
||||||
|
}
|
||||||
|
this->on_updated_cb_();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class ForwardIt>
|
||||||
|
const BacklightDevice *BacklightBackend::best_device(ForwardIt first, ForwardIt last,
|
||||||
|
std::string_view preferred_device) {
|
||||||
|
const auto found = std::find_if(
|
||||||
|
first, last, [preferred_device](const auto &dev) { return dev.name() == preferred_device; });
|
||||||
|
if (found != last) {
|
||||||
|
return &(*found);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto max = std::max_element(
|
||||||
|
first, last, [](const auto &l, const auto &r) { return l.get_max() < r.get_max(); });
|
||||||
|
|
||||||
|
return max == last ? nullptr : &(*max);
|
||||||
|
}
|
||||||
|
|
||||||
|
const BacklightDevice *BacklightBackend::get_previous_best_device() {
|
||||||
|
return previous_best_.has_value() ? &(*previous_best_) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BacklightBackend::set_previous_best_device(const BacklightDevice *device) {
|
||||||
|
if (device == nullptr) {
|
||||||
|
previous_best_ = std::nullopt;
|
||||||
|
} else {
|
||||||
|
previous_best_ = std::optional{*device};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BacklightBackend::set_scaled_brightness(std::string preferred_device, int brightness) {
|
||||||
|
GET_BEST_DEVICE(best, (*this), preferred_device);
|
||||||
|
|
||||||
|
if (best != nullptr) {
|
||||||
|
const auto max = best->get_max();
|
||||||
|
const auto abs_val = static_cast<int>(round(brightness * max / 100.0f));
|
||||||
|
set_brightness_internal(best->name(), abs_val, best->get_max());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BacklightBackend::set_brightness(std::string preferred_device, ChangeType change_type,
|
||||||
|
double step) {
|
||||||
|
GET_BEST_DEVICE(best, (*this), preferred_device);
|
||||||
|
|
||||||
|
if (best != nullptr) {
|
||||||
|
const auto max = best->get_max();
|
||||||
|
|
||||||
|
const auto abs_step = static_cast<int>(round(step * max / 100.0f));
|
||||||
|
|
||||||
|
const int new_brightness = change_type == ChangeType::Increase ? best->get_actual() + abs_step
|
||||||
|
: best->get_actual() - abs_step;
|
||||||
|
set_brightness_internal(best->name(), new_brightness, max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BacklightBackend::set_brightness_internal(std::string device_name, int brightness,
|
||||||
|
int max_brightness) {
|
||||||
|
brightness = std::clamp(brightness, 0, max_brightness);
|
||||||
|
|
||||||
|
auto call_args = Glib::VariantContainerBase(
|
||||||
|
g_variant_new("(ssu)", "backlight", device_name.c_str(), brightness));
|
||||||
|
|
||||||
|
login_proxy_->call_sync("SetBrightness", call_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
int BacklightBackend::get_scaled_brightness(std::string preferred_device) {
|
||||||
|
GET_BEST_DEVICE(best, (*this), preferred_device);
|
||||||
|
|
||||||
|
if (best != nullptr) {
|
||||||
|
return best->get_actual() * 100 / best->get_max();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class ForwardIt, class Inserter>
|
||||||
|
void BacklightBackend::upsert_device(ForwardIt first, ForwardIt last, Inserter inserter,
|
||||||
|
udev_device *dev) {
|
||||||
|
const char *name = udev_device_get_sysname(dev);
|
||||||
|
check_nn(name);
|
||||||
|
|
||||||
|
const char *actual_brightness_attr =
|
||||||
|
strncmp(name, "amdgpu_bl", 9) == 0 || strcmp(name, "apple-panel-bl") == 0
|
||||||
|
? "brightness"
|
||||||
|
: "actual_brightness";
|
||||||
|
|
||||||
|
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
|
||||||
|
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
|
||||||
|
const char *power = udev_device_get_sysattr_value(dev, "bl_power");
|
||||||
|
|
||||||
|
auto found =
|
||||||
|
std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
|
||||||
|
if (found != last) {
|
||||||
|
if (actual != nullptr) {
|
||||||
|
found->set_actual(std::stoi(actual));
|
||||||
|
}
|
||||||
|
if (max != nullptr) {
|
||||||
|
found->set_max(std::stoi(max));
|
||||||
|
}
|
||||||
|
if (power != nullptr) {
|
||||||
|
found->set_powered(std::stoi(power) == 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
|
||||||
|
const int max_int = max == nullptr ? 0 : std::stoi(max);
|
||||||
|
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0;
|
||||||
|
*inserter = BacklightDevice{name, actual_int, max_int, power_bool};
|
||||||
|
++inserter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class ForwardIt, class Inserter>
|
||||||
|
void BacklightBackend::enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter,
|
||||||
|
udev *udev) {
|
||||||
|
std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{udev_enumerate_new(udev)};
|
||||||
|
udev_enumerate_add_match_subsystem(enumerate.get(), "backlight");
|
||||||
|
udev_enumerate_scan_devices(enumerate.get());
|
||||||
|
udev_list_entry *enum_devices = udev_enumerate_get_list_entry(enumerate.get());
|
||||||
|
udev_list_entry *dev_list_entry;
|
||||||
|
udev_list_entry_foreach(dev_list_entry, enum_devices) {
|
||||||
|
const char *path = udev_list_entry_get_name(dev_list_entry);
|
||||||
|
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{udev_device_new_from_syspath(udev, path)};
|
||||||
|
check_nn(dev.get(), "dev new failed");
|
||||||
|
upsert_device(first, last, inserter, dev.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace waybar::util
|
Loading…
Reference in New Issue