mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
feat(upower): added support for auxiliary batteries
This commit is contained in:
@@ -953,7 +953,7 @@
|
||||
},
|
||||
"device": {
|
||||
"label": "Device",
|
||||
"description": "Audio stream to control"
|
||||
"description": "Device to monitor or control"
|
||||
},
|
||||
"display": {
|
||||
"label": "Display",
|
||||
|
||||
@@ -525,6 +525,7 @@ void Application::initServices() {
|
||||
m_upowerService->setChangeCallback([this]() {
|
||||
onUpowerStateChangedForHooks();
|
||||
m_bar.refresh();
|
||||
m_settingsWindow.onExternalOptionsChanged();
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
kLog.warn("upower disabled: {}", e.what());
|
||||
@@ -754,7 +755,8 @@ void Application::initUi() {
|
||||
m_renderContext.setTextFontFamily(m_configService.config().shell.fontFamily);
|
||||
m_wallpaper.initialize(m_wayland, &m_configService, &m_renderContext, &m_sharedTextureCache);
|
||||
m_backdrop.initialize(m_wayland, &m_configService, &m_sharedTextureCache, &m_glShared);
|
||||
m_settingsWindow.initialize(m_wayland, &m_configService, &m_renderContext, &m_dependencyService);
|
||||
m_settingsWindow.initialize(m_wayland, &m_configService, &m_renderContext, &m_dependencyService,
|
||||
m_upowerService.get());
|
||||
m_settingsWindow.setOpenDesktopWidgetEditor([this]() { m_desktopWidgetsController.toggleEdit(); });
|
||||
m_lockScreen.initialize(m_wayland, &m_renderContext, &m_configService, &m_sharedTextureCache);
|
||||
m_lockScreen.setSessionHooks([this]() { m_hookManager.fire(HookKind::SessionLocked); },
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
#include "core/log.h"
|
||||
#include "dbus/system_bus.h"
|
||||
#include "i18n/i18n.h"
|
||||
#include "util/string_utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
@@ -19,6 +22,8 @@ namespace {
|
||||
static constexpr auto k_propertiesInterface = "org.freedesktop.DBus.Properties";
|
||||
|
||||
// UPower device types
|
||||
constexpr std::uint32_t k_deviceTypeUnknown = 0;
|
||||
constexpr std::uint32_t k_deviceTypeLinePower = 1;
|
||||
constexpr std::uint32_t k_deviceTypeBattery = 2;
|
||||
|
||||
// UPower battery states
|
||||
@@ -63,6 +68,40 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
bool isBatteryCapableDeviceType(std::uint32_t type) {
|
||||
return type != k_deviceTypeUnknown && type != k_deviceTypeLinePower;
|
||||
}
|
||||
|
||||
bool isAutoSelector(std::string_view selector) {
|
||||
const std::string normalized = StringUtils::toLower(StringUtils::trim(selector));
|
||||
return normalized.empty() || normalized == "auto";
|
||||
}
|
||||
|
||||
bool hasSelectorSuffix(std::string_view value, std::string_view selector) {
|
||||
if (value.empty() || selector.empty() || value.size() < selector.size()) {
|
||||
return false;
|
||||
}
|
||||
const std::size_t start = value.size() - selector.size();
|
||||
if (value.substr(start) != selector) {
|
||||
return false;
|
||||
}
|
||||
if (start == 0) {
|
||||
return true;
|
||||
}
|
||||
const char before = value[start - 1];
|
||||
return before == '/' || before == '_' || before == '-' || before == ':' || before == '.';
|
||||
}
|
||||
|
||||
bool selectorMatchesField(const std::string& value, std::string_view selector) {
|
||||
return std::string_view(value) == selector || hasSelectorSuffix(value, selector);
|
||||
}
|
||||
|
||||
bool selectorMatchesDevice(const UPowerDeviceInfo& info, std::string_view selector) {
|
||||
return selectorMatchesField(info.path, selector) || selectorMatchesField(info.nativePath, selector) ||
|
||||
selectorMatchesField(info.model, selector) || selectorMatchesField(info.serial, selector) ||
|
||||
selectorMatchesField(info.vendor, selector);
|
||||
}
|
||||
|
||||
BatteryState decodeBatteryState(std::uint32_t raw) {
|
||||
switch (raw) {
|
||||
case k_stateCharging:
|
||||
@@ -89,107 +128,197 @@ namespace {
|
||||
UPowerService::UPowerService(SystemBus& bus) : m_bus(bus) {
|
||||
m_upowerProxy = sdbus::createProxy(m_bus.connection(), k_upowerBusName, k_upowerObjectPath);
|
||||
|
||||
// Listen for devices being added/removed so we can rebind the battery proxy
|
||||
m_upowerProxy->uponSignal("DeviceAdded").onInterface(k_upowerInterface).call([this](const sdbus::ObjectPath&) {
|
||||
scanDevices();
|
||||
refresh();
|
||||
});
|
||||
|
||||
m_upowerProxy->uponSignal("DeviceRemoved").onInterface(k_upowerInterface).call([this](const sdbus::ObjectPath& path) {
|
||||
if (m_deviceProxy != nullptr) {
|
||||
// If our tracked device was removed, clear it and re-scan
|
||||
try {
|
||||
if (m_deviceProxy->getObjectPath() == path) {
|
||||
m_deviceProxy.reset();
|
||||
scanDevices();
|
||||
m_upowerProxy->uponSignal("PropertiesChanged")
|
||||
.onInterface(k_propertiesInterface)
|
||||
.call([this](const std::string& interfaceName, const std::map<std::string, sdbus::Variant>& /*changed*/,
|
||||
const std::vector<std::string>& /*invalidated*/) {
|
||||
if (interfaceName == k_upowerInterface) {
|
||||
refresh();
|
||||
}
|
||||
} catch (const sdbus::Error&) {
|
||||
m_deviceProxy.reset();
|
||||
scanDevices();
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
|
||||
m_upowerProxy->uponSignal("DeviceAdded").onInterface(k_upowerInterface).call([this](const sdbus::ObjectPath&) {
|
||||
rescanDevices();
|
||||
});
|
||||
|
||||
scanDevices();
|
||||
refresh();
|
||||
m_upowerProxy->uponSignal("DeviceRemoved").onInterface(k_upowerInterface).call([this](const sdbus::ObjectPath&) {
|
||||
rescanDevices();
|
||||
});
|
||||
|
||||
rescanDevices();
|
||||
|
||||
if (m_state.isPresent) {
|
||||
kLog.info("battery {:.0f}% state={} ({})", m_state.percentage, static_cast<int>(m_state.state),
|
||||
m_state.onBattery ? "on battery" : "on AC");
|
||||
} else {
|
||||
kLog.info("connected (no battery present)");
|
||||
kLog.info("connected (no system battery present)");
|
||||
}
|
||||
}
|
||||
|
||||
void UPowerService::setChangeCallback(ChangeCallback callback) { m_changeCallback = std::move(callback); }
|
||||
|
||||
void UPowerService::refresh() { emitChangedIfNeeded(readState()); }
|
||||
void UPowerService::refresh() { refreshDeviceStates(); }
|
||||
|
||||
void UPowerService::scanDevices() {
|
||||
if (m_deviceProxy != nullptr) {
|
||||
return; // already have a battery bound
|
||||
std::vector<UPowerDeviceInfo> UPowerService::batteryDevices() const {
|
||||
std::vector<UPowerDeviceInfo> devices;
|
||||
devices.reserve(m_devices.size());
|
||||
for (const auto& device : m_devices) {
|
||||
if (device.info.isPresent && isBatteryCapableDeviceType(device.info.type)) {
|
||||
devices.push_back(device.info);
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
UPowerState UPowerService::stateForDevice(std::string_view selector) const {
|
||||
if (isAutoSelector(selector)) {
|
||||
return m_state;
|
||||
}
|
||||
|
||||
if (const auto* device = findDevice(selector); device != nullptr) {
|
||||
return device->state;
|
||||
}
|
||||
|
||||
UPowerState missing;
|
||||
missing.onBattery = getPropertyOr<bool>(*m_upowerProxy, k_upowerInterface, "OnBattery", false);
|
||||
return missing;
|
||||
}
|
||||
|
||||
void UPowerService::rescanDevices() {
|
||||
std::vector<sdbus::ObjectPath> paths;
|
||||
try {
|
||||
m_upowerProxy->callMethod("EnumerateDevices").onInterface(k_upowerInterface).storeResultsTo(paths);
|
||||
} catch (const sdbus::Error& e) {
|
||||
kLog.warn("EnumerateDevices failed: {}", e.what());
|
||||
emitChangedIfNeeded(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<TrackedDevice> nextDevices;
|
||||
nextDevices.reserve(paths.size());
|
||||
for (const auto& path : paths) {
|
||||
try {
|
||||
auto probe = sdbus::createProxy(m_bus.connection(), k_upowerBusName, path);
|
||||
const auto type = getPropertyOr<std::uint32_t>(*probe, k_deviceInterface, "Type", 0);
|
||||
const auto present = getPropertyOr<bool>(*probe, k_deviceInterface, "IsPresent", false);
|
||||
if (type == k_deviceTypeBattery && present) {
|
||||
bindBatteryDevice(path);
|
||||
return;
|
||||
auto proxy = sdbus::createProxy(m_bus.connection(), k_upowerBusName, path);
|
||||
auto info = readDeviceInfo(std::string(path), *proxy);
|
||||
if (!isBatteryCapableDeviceType(info.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
proxy->uponSignal("PropertiesChanged")
|
||||
.onInterface(k_propertiesInterface)
|
||||
.call([this](const std::string& interfaceName, const std::map<std::string, sdbus::Variant>& /*changed*/,
|
||||
const std::vector<std::string>& /*invalidated*/) {
|
||||
if (interfaceName == k_deviceInterface) {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
nextDevices.push_back(TrackedDevice{std::move(info), std::move(proxy)});
|
||||
} catch (const sdbus::Error&) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(nextDevices.begin(), nextDevices.end(),
|
||||
[](const TrackedDevice& lhs, const TrackedDevice& rhs) { return lhs.info.path < rhs.info.path; });
|
||||
|
||||
bool devicesChanged = m_devices.size() != nextDevices.size();
|
||||
if (!devicesChanged) {
|
||||
for (std::size_t i = 0; i < m_devices.size(); ++i) {
|
||||
if (m_devices[i].info != nextDevices[i].info) {
|
||||
devicesChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_devices = std::move(nextDevices);
|
||||
if (devicesChanged) {
|
||||
kLog.debug("tracking {} UPower battery-capable device(s)", m_devices.size());
|
||||
}
|
||||
emitChangedIfNeeded(devicesChanged);
|
||||
}
|
||||
|
||||
void UPowerService::bindBatteryDevice(const sdbus::ObjectPath& path) {
|
||||
m_deviceProxy = sdbus::createProxy(m_bus.connection(), k_upowerBusName, path);
|
||||
|
||||
m_deviceProxy->uponSignal("PropertiesChanged")
|
||||
.onInterface(k_propertiesInterface)
|
||||
.call([this](const std::string& interfaceName, const std::map<std::string, sdbus::Variant>& /*changed*/,
|
||||
const std::vector<std::string>& /*invalidated*/) {
|
||||
if (interfaceName == k_deviceInterface) {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
kLog.debug("bound battery device {}", std::string(path));
|
||||
}
|
||||
|
||||
UPowerState UPowerService::readState() const {
|
||||
UPowerState UPowerService::readDefaultState() const {
|
||||
UPowerState next;
|
||||
|
||||
next.onBattery = getPropertyOr<bool>(*m_upowerProxy, k_upowerInterface, "OnBattery", false);
|
||||
|
||||
if (m_deviceProxy == nullptr) {
|
||||
const auto* device = defaultSystemBattery();
|
||||
if (device == nullptr) {
|
||||
return next;
|
||||
}
|
||||
|
||||
next.percentage = getPropertyOr<double>(*m_deviceProxy, k_deviceInterface, "Percentage", 0.0);
|
||||
next.isPresent = getPropertyOr<bool>(*m_deviceProxy, k_deviceInterface, "IsPresent", false);
|
||||
const auto rawState = getPropertyOr<std::uint32_t>(*m_deviceProxy, k_deviceInterface, "State", 0);
|
||||
next = device->state;
|
||||
next.onBattery = getPropertyOr<bool>(*m_upowerProxy, k_upowerInterface, "OnBattery", false);
|
||||
return next;
|
||||
}
|
||||
|
||||
UPowerState UPowerService::readDeviceState(sdbus::IProxy& proxy) const {
|
||||
UPowerState next;
|
||||
|
||||
next.onBattery = getPropertyOr<bool>(*m_upowerProxy, k_upowerInterface, "OnBattery", false);
|
||||
next.percentage = getPropertyOr<double>(proxy, k_deviceInterface, "Percentage", 0.0);
|
||||
next.isPresent = getPropertyOr<bool>(proxy, k_deviceInterface, "IsPresent", false);
|
||||
const auto rawState = getPropertyOr<std::uint32_t>(proxy, k_deviceInterface, "State", 0);
|
||||
next.state = decodeBatteryState(rawState);
|
||||
next.timeToEmpty = getPropertyOr<std::int64_t>(*m_deviceProxy, k_deviceInterface, "TimeToEmpty", 0);
|
||||
next.timeToFull = getPropertyOr<std::int64_t>(*m_deviceProxy, k_deviceInterface, "TimeToFull", 0);
|
||||
next.timeToEmpty = getPropertyOr<std::int64_t>(proxy, k_deviceInterface, "TimeToEmpty", 0);
|
||||
next.timeToFull = getPropertyOr<std::int64_t>(proxy, k_deviceInterface, "TimeToFull", 0);
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
void UPowerService::emitChangedIfNeeded(const UPowerState& next) {
|
||||
if (next == m_state) {
|
||||
UPowerDeviceInfo UPowerService::readDeviceInfo(std::string path, sdbus::IProxy& proxy) const {
|
||||
UPowerDeviceInfo info;
|
||||
info.path = std::move(path);
|
||||
info.nativePath = getPropertyOr<std::string>(proxy, k_deviceInterface, "NativePath", "");
|
||||
info.vendor = getPropertyOr<std::string>(proxy, k_deviceInterface, "Vendor", "");
|
||||
info.model = getPropertyOr<std::string>(proxy, k_deviceInterface, "Model", "");
|
||||
info.serial = getPropertyOr<std::string>(proxy, k_deviceInterface, "Serial", "");
|
||||
info.type = getPropertyOr<std::uint32_t>(proxy, k_deviceInterface, "Type", 0);
|
||||
info.powerSupply = getPropertyOr<bool>(proxy, k_deviceInterface, "PowerSupply", false);
|
||||
info.state = readDeviceState(proxy);
|
||||
info.isPresent = info.state.isPresent;
|
||||
return info;
|
||||
}
|
||||
|
||||
const UPowerDeviceInfo* UPowerService::defaultSystemBattery() const noexcept {
|
||||
for (const auto& device : m_devices) {
|
||||
if (device.info.type == k_deviceTypeBattery && device.info.powerSupply && device.info.isPresent) {
|
||||
return &device.info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const UPowerDeviceInfo* UPowerService::findDevice(std::string_view selector) const {
|
||||
const std::string trimmed = StringUtils::trim(selector);
|
||||
if (trimmed.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& device : m_devices) {
|
||||
if (isBatteryCapableDeviceType(device.info.type) && selectorMatchesDevice(device.info, trimmed)) {
|
||||
return &device.info;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UPowerService::refreshDeviceStates() {
|
||||
bool devicesChanged = false;
|
||||
for (auto& device : m_devices) {
|
||||
auto next = readDeviceInfo(device.info.path, *device.proxy);
|
||||
if (next != device.info) {
|
||||
device.info = std::move(next);
|
||||
devicesChanged = true;
|
||||
}
|
||||
}
|
||||
emitChangedIfNeeded(devicesChanged);
|
||||
}
|
||||
|
||||
void UPowerService::emitChangedIfNeeded(bool devicesChanged) {
|
||||
const UPowerState next = readDefaultState();
|
||||
if (!devicesChanged && next == m_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
class SystemBus;
|
||||
|
||||
namespace sdbus {
|
||||
class IProxy;
|
||||
class ObjectPath;
|
||||
} // namespace sdbus
|
||||
|
||||
enum class BatteryState : std::uint8_t {
|
||||
@@ -35,6 +36,20 @@ struct UPowerState {
|
||||
bool operator==(const UPowerState&) const = default;
|
||||
};
|
||||
|
||||
struct UPowerDeviceInfo {
|
||||
std::string path;
|
||||
std::string nativePath;
|
||||
std::string vendor;
|
||||
std::string model;
|
||||
std::string serial;
|
||||
std::uint32_t type = 0;
|
||||
bool powerSupply = false;
|
||||
bool isPresent = false;
|
||||
UPowerState state;
|
||||
|
||||
bool operator==(const UPowerDeviceInfo&) const = default;
|
||||
};
|
||||
|
||||
class UPowerService {
|
||||
public:
|
||||
using ChangeCallback = std::function<void()>;
|
||||
@@ -45,16 +60,27 @@ public:
|
||||
void refresh();
|
||||
|
||||
[[nodiscard]] const UPowerState& state() const noexcept { return m_state; }
|
||||
[[nodiscard]] UPowerState stateForDevice(std::string_view selector) const;
|
||||
[[nodiscard]] std::vector<UPowerDeviceInfo> batteryDevices() const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] UPowerState readState() const;
|
||||
void emitChangedIfNeeded(const UPowerState& next);
|
||||
void bindBatteryDevice(const sdbus::ObjectPath& path);
|
||||
void scanDevices();
|
||||
struct TrackedDevice {
|
||||
UPowerDeviceInfo info;
|
||||
std::unique_ptr<sdbus::IProxy> proxy;
|
||||
};
|
||||
|
||||
[[nodiscard]] UPowerState readDefaultState() const;
|
||||
[[nodiscard]] UPowerState readDeviceState(sdbus::IProxy& proxy) const;
|
||||
[[nodiscard]] UPowerDeviceInfo readDeviceInfo(std::string path, sdbus::IProxy& proxy) const;
|
||||
[[nodiscard]] const UPowerDeviceInfo* defaultSystemBattery() const noexcept;
|
||||
[[nodiscard]] const UPowerDeviceInfo* findDevice(std::string_view selector) const;
|
||||
void emitChangedIfNeeded(bool devicesChanged);
|
||||
void rescanDevices();
|
||||
void refreshDeviceStates();
|
||||
|
||||
SystemBus& m_bus;
|
||||
std::unique_ptr<sdbus::IProxy> m_upowerProxy;
|
||||
std::unique_ptr<sdbus::IProxy> m_deviceProxy;
|
||||
std::vector<TrackedDevice> m_devices;
|
||||
UPowerState m_state;
|
||||
ChangeCallback m_changeCallback;
|
||||
};
|
||||
|
||||
@@ -131,7 +131,8 @@ std::unique_ptr<Widget> WidgetFactory::create(const std::string& name, wl_output
|
||||
}
|
||||
|
||||
if (type == "battery") {
|
||||
auto widget = std::make_unique<BatteryWidget>(m_upower);
|
||||
const std::string deviceSelector = wc != nullptr ? wc->getString("device", "auto") : std::string("auto");
|
||||
auto widget = std::make_unique<BatteryWidget>(m_upower, deviceSelector);
|
||||
widget->setContentScale(contentScale);
|
||||
return widget;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -38,7 +39,8 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
BatteryWidget::BatteryWidget(UPowerService* upower) : m_upower(upower) {}
|
||||
BatteryWidget::BatteryWidget(UPowerService* upower, std::string deviceSelector)
|
||||
: m_upower(upower), m_deviceSelector(std::move(deviceSelector)) {}
|
||||
|
||||
void BatteryWidget::create() {
|
||||
auto container = std::make_unique<Node>();
|
||||
@@ -90,7 +92,7 @@ void BatteryWidget::syncState(Renderer& renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& s = m_upower->state();
|
||||
const auto s = m_upower->stateForDevice(m_deviceSelector);
|
||||
|
||||
if (s.percentage == m_lastPct && s.state == m_lastState && s.isPresent == m_lastPresent &&
|
||||
m_isVertical == m_lastVertical) {
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
#include "dbus/upower/upower_service.h"
|
||||
#include "shell/bar/widget.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class Glyph;
|
||||
class Label;
|
||||
|
||||
class BatteryWidget : public Widget {
|
||||
public:
|
||||
explicit BatteryWidget(UPowerService* upower);
|
||||
BatteryWidget(UPowerService* upower, std::string deviceSelector = "auto");
|
||||
|
||||
void create() override;
|
||||
|
||||
@@ -18,6 +20,7 @@ private:
|
||||
void syncState(Renderer& renderer);
|
||||
|
||||
UPowerService* m_upower = nullptr;
|
||||
std::string m_deviceSelector = "auto";
|
||||
Glyph* m_glyph = nullptr;
|
||||
Label* m_label = nullptr;
|
||||
double m_lastPct = -1.0;
|
||||
|
||||
@@ -664,6 +664,29 @@ namespace settings {
|
||||
return options;
|
||||
}
|
||||
|
||||
SelectSetting batteryDeviceSelectSetting(const BarWidgetEditorContext& ctx, std::string selectedValue) {
|
||||
if (selectedValue.empty()) {
|
||||
selectedValue = "auto";
|
||||
}
|
||||
|
||||
std::vector<SelectOption> options = ctx.batteryDeviceOptions;
|
||||
if (options.empty()) {
|
||||
options.push_back(SelectOption{.value = "auto", .label = i18n::tr("common.states.auto")});
|
||||
}
|
||||
|
||||
const auto hasSelected = std::any_of(options.begin(), options.end(), [&selectedValue](const SelectOption& opt) {
|
||||
return opt.value == selectedValue;
|
||||
});
|
||||
if (!selectedValue.empty() && !hasSelected) {
|
||||
options.push_back(SelectOption{
|
||||
.value = selectedValue,
|
||||
.label = i18n::tr("settings.controls.select.unknown-value", "value", selectedValue),
|
||||
});
|
||||
}
|
||||
|
||||
return SelectSetting{std::move(options), std::move(selectedValue)};
|
||||
}
|
||||
|
||||
void addRawWidgetSettings(Flex& panel, std::string_view widgetName, const std::vector<WidgetSettingSpec>& specs,
|
||||
std::size_t& visibleSpecs, const BarWidgetEditorContext& ctx) {
|
||||
if (!ctx.showAdvanced) {
|
||||
@@ -987,12 +1010,18 @@ namespace settings {
|
||||
ctx.makeListBlock(*panel, entry, ListSetting{.items = settingValueAsStringList(value)});
|
||||
break;
|
||||
case WidgetSettingValueType::Select: {
|
||||
std::vector<SelectOption> options;
|
||||
options.reserve(spec.options.size());
|
||||
for (const auto& option : spec.options) {
|
||||
options.push_back(SelectOption{std::string(option.value), i18n::tr(option.labelKey)});
|
||||
SelectSetting selectSetting;
|
||||
const std::string selectedValue = settingValueAsString(value);
|
||||
if (widgetType == "battery" && spec.key == "device") {
|
||||
selectSetting = batteryDeviceSelectSetting(ctx, selectedValue);
|
||||
} else {
|
||||
std::vector<SelectOption> options;
|
||||
options.reserve(spec.options.size());
|
||||
for (const auto& option : spec.options) {
|
||||
options.push_back(SelectOption{std::string(option.value), i18n::tr(option.labelKey)});
|
||||
}
|
||||
selectSetting = SelectSetting{std::move(options), selectedValue};
|
||||
}
|
||||
SelectSetting selectSetting{std::move(options), settingValueAsString(value)};
|
||||
selectSetting.segmented = spec.segmented;
|
||||
ctx.makeRow(*panel, entry, ctx.makeSelect(std::move(selectSetting), path));
|
||||
break;
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace settings {
|
||||
float scale = 1.0f;
|
||||
bool showAdvanced = false;
|
||||
bool showOverriddenOnly = false;
|
||||
std::vector<SelectOption> batteryDeviceOptions;
|
||||
std::string& openWidgetPickerPath;
|
||||
std::string& editingWidgetName;
|
||||
std::string& pendingDeleteWidgetName;
|
||||
|
||||
@@ -1223,6 +1223,7 @@ namespace settings {
|
||||
.scale = scale,
|
||||
.showAdvanced = ctx.showAdvanced,
|
||||
.showOverriddenOnly = ctx.showOverriddenOnly,
|
||||
.batteryDeviceOptions = ctx.batteryDeviceOptions,
|
||||
.openWidgetPickerPath = ctx.openWidgetPickerPath,
|
||||
.editingWidgetName = ctx.editingWidgetName,
|
||||
.pendingDeleteWidgetName = ctx.pendingDeleteWidgetName,
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace settings {
|
||||
const BarMonitorOverride* selectedMonitorOverride = nullptr;
|
||||
bool showAdvanced = false;
|
||||
bool showOverriddenOnly = false;
|
||||
std::vector<SelectOption> batteryDeviceOptions;
|
||||
|
||||
std::string& openWidgetPickerPath;
|
||||
std::string& openSearchPickerPath;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "core/deferred_call.h"
|
||||
#include "core/log.h"
|
||||
#include "core/ui_phase.h"
|
||||
#include "dbus/upower/upower_service.h"
|
||||
#include "i18n/i18n.h"
|
||||
#include "render/render_context.h"
|
||||
#include "shell/settings/settings_content.h"
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "ui/dialogs/file_dialog.h"
|
||||
#include "ui/palette.h"
|
||||
#include "ui/style.h"
|
||||
#include "util/string_utils.h"
|
||||
#include "wayland/toplevel_surface.h"
|
||||
#include "wayland/wayland_connection.h"
|
||||
|
||||
@@ -157,16 +159,61 @@ namespace {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string upowerDeviceLabel(const UPowerDeviceInfo& device) {
|
||||
const std::string nativeName =
|
||||
!device.nativePath.empty() ? StringUtils::pathTail(device.nativePath) : StringUtils::pathTail(device.path);
|
||||
|
||||
std::string label;
|
||||
if (!device.vendor.empty() && !device.model.empty()) {
|
||||
label = device.vendor + " " + device.model;
|
||||
} else if (!device.model.empty()) {
|
||||
label = device.model;
|
||||
} else if (!device.vendor.empty()) {
|
||||
label = device.vendor;
|
||||
} else {
|
||||
label = nativeName;
|
||||
}
|
||||
|
||||
if (!nativeName.empty() && label != nativeName) {
|
||||
label += " (" + nativeName + ")";
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
std::vector<settings::SelectOption> upowerBatteryDeviceOptions(UPowerService* upower) {
|
||||
std::vector<settings::SelectOption> options;
|
||||
options.push_back(settings::SelectOption{.value = "auto", .label = i18n::tr("common.states.auto")});
|
||||
if (upower == nullptr) {
|
||||
return options;
|
||||
}
|
||||
|
||||
const auto devices = upower->batteryDevices();
|
||||
options.reserve(devices.size() + 1);
|
||||
for (const auto& device : devices) {
|
||||
std::string description = device.path;
|
||||
if (!device.nativePath.empty() && device.nativePath != device.path) {
|
||||
description = device.nativePath + " - " + device.path;
|
||||
}
|
||||
options.push_back(settings::SelectOption{
|
||||
.value = device.path,
|
||||
.label = upowerDeviceLabel(device),
|
||||
.description = std::move(description),
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SettingsWindow::~SettingsWindow() = default;
|
||||
|
||||
void SettingsWindow::initialize(WaylandConnection& wayland, ConfigService* config, RenderContext* renderContext,
|
||||
DependencyService* dependencies) {
|
||||
DependencyService* dependencies, UPowerService* upower) {
|
||||
m_wayland = &wayland;
|
||||
m_config = config;
|
||||
m_renderContext = renderContext;
|
||||
m_dependencies = dependencies;
|
||||
m_upower = upower;
|
||||
m_showAdvanced = m_config != nullptr ? m_config->config().shell.settingsShowAdvanced : false;
|
||||
}
|
||||
|
||||
@@ -1084,6 +1131,7 @@ void SettingsWindow::rebuildSettingsContent() {
|
||||
},
|
||||
});
|
||||
|
||||
const auto batteryDeviceOptions = upowerBatteryDeviceOptions(m_upower);
|
||||
settings::addSettingsContentSections(
|
||||
*m_contentContainer, m_settingsRegistry,
|
||||
settings::SettingsContentContext{
|
||||
@@ -1096,6 +1144,7 @@ void SettingsWindow::rebuildSettingsContent() {
|
||||
.selectedMonitorOverride = selectedMonitorOverride,
|
||||
.showAdvanced = m_showAdvanced,
|
||||
.showOverriddenOnly = m_showOverriddenOnly,
|
||||
.batteryDeviceOptions = batteryDeviceOptions,
|
||||
.openWidgetPickerPath = m_openWidgetPickerPath,
|
||||
.openSearchPickerPath = m_openSearchPickerPath,
|
||||
.editingWidgetName = m_editingWidgetName,
|
||||
|
||||
@@ -21,6 +21,7 @@ class ConfigService;
|
||||
class DependencyService;
|
||||
class Flex;
|
||||
class RenderContext;
|
||||
class UPowerService;
|
||||
class WaylandConnection;
|
||||
struct KeyboardEvent;
|
||||
struct PointerEvent;
|
||||
@@ -33,7 +34,7 @@ public:
|
||||
~SettingsWindow();
|
||||
|
||||
void initialize(WaylandConnection& wayland, ConfigService* config, RenderContext* renderContext,
|
||||
DependencyService* dependencies);
|
||||
DependencyService* dependencies, UPowerService* upower);
|
||||
|
||||
void open();
|
||||
void openToBarWidget(std::string barName, std::string widgetName);
|
||||
@@ -84,6 +85,7 @@ private:
|
||||
ConfigService* m_config = nullptr;
|
||||
RenderContext* m_renderContext = nullptr;
|
||||
DependencyService* m_dependencies = nullptr;
|
||||
UPowerService* m_upower = nullptr;
|
||||
|
||||
std::unique_ptr<ToplevelSurface> m_surface;
|
||||
std::unique_ptr<Node> m_sceneRoot;
|
||||
|
||||
@@ -337,6 +337,8 @@ namespace settings {
|
||||
add(boolSpec("show_when_idle", false));
|
||||
add(colorRoleSpec("low_color", "primary"));
|
||||
add(colorRoleSpec("high_color", "primary"));
|
||||
} else if (type == "battery") {
|
||||
add(selectSpec("device", "auto", {{"auto", "common.states.auto"}}));
|
||||
} else if (type == "bluetooth") {
|
||||
add(boolSpec("show_label", false));
|
||||
} else if (type == "brightness") {
|
||||
|
||||
@@ -26,6 +26,14 @@ namespace StringUtils {
|
||||
return out;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string pathTail(std::string_view path) {
|
||||
const auto slash = path.find_last_of('/');
|
||||
if (slash == std::string_view::npos || slash + 1 >= path.size()) {
|
||||
return std::string(path);
|
||||
}
|
||||
return std::string(path.substr(slash + 1));
|
||||
}
|
||||
|
||||
inline void toLowerInPlace(std::string& s) {
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user