From 152d0b6fad9f15dc82c46bd749e2844e79c4c0df Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Thu, 7 May 2026 03:07:38 +0200 Subject: [PATCH] feat(widgets): add input & output option to volume widget --- assets/translations/en.json | 14 +++++++++ src/config/config_service.cpp | 10 ++++++ src/shell/bar/widget_factory.cpp | 4 ++- src/shell/bar/widgets/volume_widget.cpp | 30 +++++++++++------- src/shell/bar/widgets/volume_widget.h | 8 ++++- .../settings/widget_settings_registry.cpp | 31 +++++++++++++++++-- 6 files changed, 82 insertions(+), 15 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 50a60342d..9fbbf01cc 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -831,6 +831,14 @@ } }, "widgets": { + "instances": { + "cpu": "CPU", + "date": "Date", + "input-volume": "Input Volume", + "output-volume": "Output Volume", + "ram": "RAM", + "temp": "Temperature" + }, "categories": { "custom": "Custom", "info": "Info", @@ -883,9 +891,11 @@ "gauge": "Gauge", "graph": "Graph", "id": "ID", + "input": "Input", "name": "Name", "none": "None", "on-hover": "On Hover", + "output": "Output", "ram-percent": "RAM Percent", "ram-used": "RAM Used", "short": "Short", @@ -946,6 +956,10 @@ "label": "Cycle Command", "description": "Command run when cycling keyboard layouts" }, + "device": { + "label": "Device", + "description": "Audio stream to control" + }, "display": { "label": "Display", "description": "Display mode for this widget" diff --git a/src/config/config_service.cpp b/src/config/config_service.cpp index 67c9c0979..ad5e02c3c 100644 --- a/src/config/config_service.cpp +++ b/src/config/config_service.cpp @@ -729,6 +729,16 @@ void ConfigService::seedBuiltinWidgets(Config& config) { ram.settings["stat"] = std::string("ram_used"); seed("ram", std::move(ram)); + WidgetConfig outputVolume; + outputVolume.type = "volume"; + outputVolume.settings["device"] = std::string("output"); + seed("output_volume", std::move(outputVolume)); + + WidgetConfig inputVolume; + inputVolume.type = "volume"; + inputVolume.settings["device"] = std::string("input"); + seed("input_volume", std::move(inputVolume)); + WidgetConfig date; date.type = "clock"; date.settings["format"] = std::string("{:%a %d %b}"); diff --git a/src/shell/bar/widget_factory.cpp b/src/shell/bar/widget_factory.cpp index 76f7e66d6..4188f05c8 100644 --- a/src/shell/bar/widget_factory.cpp +++ b/src/shell/bar/widget_factory.cpp @@ -356,7 +356,9 @@ std::unique_ptr WidgetFactory::create(const std::string& name, wl_output if (type == "volume") { const bool showLabel = wc != nullptr ? wc->getBool("show_label", true) : true; - auto widget = std::make_unique(m_audio, output, showLabel); + const std::string target = wc != nullptr ? wc->getString("device", "output") : std::string("output"); + const auto volumeTarget = target == "input" ? VolumeWidgetTarget::Input : VolumeWidgetTarget::Output; + auto widget = std::make_unique(m_audio, output, showLabel, volumeTarget); widget->setContentScale(contentScale); return widget; } diff --git a/src/shell/bar/widgets/volume_widget.cpp b/src/shell/bar/widgets/volume_widget.cpp index 8d76edd25..ea4c40072 100644 --- a/src/shell/bar/widgets/volume_widget.cpp +++ b/src/shell/bar/widgets/volume_widget.cpp @@ -15,7 +15,11 @@ namespace { - const char* volumeGlyphName(float volume, bool muted) { + const char* volumeGlyphName(float volume, bool muted, VolumeWidgetTarget target) { + if (target == VolumeWidgetTarget::Input) { + return muted ? "microphone-mute" : "microphone"; + } + if (muted || volume <= 0.0f) { return "volume-mute"; } @@ -29,8 +33,8 @@ namespace { } // namespace -VolumeWidget::VolumeWidget(PipeWireService* audio, wl_output* output, bool showLabel) - : m_audio(audio), m_output(output), m_showLabel(showLabel) {} +VolumeWidget::VolumeWidget(PipeWireService* audio, wl_output* output, bool showLabel, VolumeWidgetTarget target) + : m_audio(audio), m_output(output), m_showLabel(showLabel), m_target(target) {} void VolumeWidget::create() { auto area = std::make_unique(); @@ -39,13 +43,17 @@ void VolumeWidget::create() { if (m_audio == nullptr) { return; } - const auto* sink = m_audio->defaultSink(); - if (sink == nullptr) { + const auto* node = m_target == VolumeWidgetTarget::Input ? m_audio->defaultSource() : m_audio->defaultSink(); + if (node == nullptr) { return; } const float delta = data.scrollDelta(1.0f) > 0 ? -kScrollStep : kScrollStep; - const float newValue = std::clamp(sink->volume + delta, 0.0f, 1.0f); - m_audio->setSinkVolume(sink->id, newValue); + const float newValue = std::clamp(node->volume + delta, 0.0f, 1.0f); + if (m_target == VolumeWidgetTarget::Input) { + m_audio->setSourceVolume(node->id, newValue); + } else { + m_audio->setSinkVolume(node->id, newValue); + } }); auto glyph = std::make_unique(); @@ -103,9 +111,9 @@ void VolumeWidget::syncState(Renderer& renderer) { return; } - const auto* sink = m_audio->defaultSink(); - float volume = sink != nullptr ? sink->volume : 0.0f; - bool muted = sink != nullptr ? sink->muted : false; + const auto* node = m_target == VolumeWidgetTarget::Input ? m_audio->defaultSource() : m_audio->defaultSink(); + float volume = node != nullptr ? node->volume : 0.0f; + bool muted = node != nullptr ? node->muted : false; if (volume == m_lastVolume && muted == m_lastMuted && m_isVertical == m_lastVertical) { return; @@ -115,7 +123,7 @@ void VolumeWidget::syncState(Renderer& renderer) { m_lastMuted = muted; m_lastVertical = m_isVertical; - m_glyph->setGlyph(volumeGlyphName(volume, muted)); + m_glyph->setGlyph(volumeGlyphName(volume, muted, m_target)); m_glyph->setGlyphSize(Style::barGlyphSize * m_contentScale); m_glyph->setColor(muted ? colorSpecFromRole(ColorRole::OnSurfaceVariant) : widgetForegroundOr(colorSpecFromRole(ColorRole::OnSurface))); diff --git a/src/shell/bar/widgets/volume_widget.h b/src/shell/bar/widgets/volume_widget.h index 896c1b0ac..ef9b19994 100644 --- a/src/shell/bar/widgets/volume_widget.h +++ b/src/shell/bar/widgets/volume_widget.h @@ -9,9 +9,14 @@ class Label; class PipeWireService; struct wl_output; +enum class VolumeWidgetTarget { + Output, + Input, +}; + class VolumeWidget : public Widget { public: - VolumeWidget(PipeWireService* audio, wl_output* output, bool showLabel); + VolumeWidget(PipeWireService* audio, wl_output* output, bool showLabel, VolumeWidgetTarget target); void create() override; @@ -23,6 +28,7 @@ private: PipeWireService* m_audio = nullptr; wl_output* m_output = nullptr; bool m_showLabel = true; + VolumeWidgetTarget m_target = VolumeWidgetTarget::Output; Glyph* m_glyph = nullptr; Label* m_label = nullptr; float m_lastVolume = -1.0f; diff --git a/src/shell/settings/widget_settings_registry.cpp b/src/shell/settings/widget_settings_registry.cpp index d3d864445..120435569 100644 --- a/src/shell/settings/widget_settings_registry.cpp +++ b/src/shell/settings/widget_settings_registry.cpp @@ -238,6 +238,28 @@ namespace settings { return spec; } + std::string widgetInstanceDisplayLabel(std::string_view name) { + if (name == "cpu") { + return tr("settings.widgets.instances.cpu"); + } + if (name == "temp") { + return tr("settings.widgets.instances.temp"); + } + if (name == "ram") { + return tr("settings.widgets.instances.ram"); + } + if (name == "date") { + return tr("settings.widgets.instances.date"); + } + if (name == "output_volume") { + return tr("settings.widgets.instances.output-volume"); + } + if (name == "input_volume") { + return tr("settings.widgets.instances.input-volume"); + } + return std::string(name); + } + void addPickerEntry(std::vector& entries, std::unordered_set& seen, std::string value, std::string label, std::string description, std::string category, WidgetReferenceKind kind) { @@ -317,7 +339,7 @@ namespace settings { if (const auto it = cfg.widgets.find(std::string(name)); it != cfg.widgets.end()) { return WidgetReferenceInfo{ - .title = std::string(name), + .title = widgetInstanceDisplayLabel(name), .detail = it->second.type.empty() ? tr("settings.entities.widget.detail.custom") : tr("settings.entities.widget.detail.type", "type", it->second.type), .badge = tr("settings.entities.widget.kinds.named"), @@ -349,7 +371,7 @@ namespace settings { if (isBuiltInWidgetType(name)) { continue; } - addPickerEntry(entries, seen, name, name, + addPickerEntry(entries, seen, name, widgetInstanceDisplayLabel(name), widget.type.empty() ? tr("settings.entities.widget.detail.custom") : tr("settings.entities.widget.detail.type", "type", widget.type), tr("settings.entities.widget.kinds.named"), WidgetReferenceKind::Named); @@ -423,6 +445,10 @@ namespace settings { {"always", "settings.widgets.options.always"}, {"on_hover", "settings.widgets.options.on-hover"}, }; + const std::vector volumeDeviceOptions = { + {"output", "settings.widgets.options.output"}, + {"input", "settings.widgets.options.input"}, + }; const std::vector workspaceColorRoles = { {"on_surface", ""}, {"primary", ""}, {"secondary", ""}, {"tertiary", ""}, {"error", ""}, }; @@ -494,6 +520,7 @@ namespace settings { add(boolSpec("drawer", false)); add(intSpec("drawer_columns", 3, 1.0, 5.0, 1.0)); } else if (type == "volume") { + add(segmentedSpec("device", "output", volumeDeviceOptions)); add(boolSpec("show_label", true)); } else if (type == "wallpaper") { add(stringSpec("glyph", "wallpaper-selector"));