状态栏添加音量显示并添加音量调节功能

This commit is contained in:
2026-02-11 02:22:49 +08:00
parent 194c2b000d
commit a69f3bbc64
9 changed files with 246 additions and 2 deletions

View File

@@ -42,6 +42,7 @@ add_executable(${APP_NAME}
${SOURCE_DIR}/layout.c ${SOURCE_DIR}/layout.c
${SOURCE_DIR}/xres.c ${SOURCE_DIR}/xres.c
${SOURCE_DIR}/status.c ${SOURCE_DIR}/status.c
${SOURCE_DIR}/audio.c
) )
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
@@ -66,6 +67,8 @@ pkg_check_modules(deps REQUIRED
cairo-xcb cairo-xcb
pango pango
pangocairo pangocairo
libpulse-mainloop-glib
) )
target_include_directories(${APP_NAME} SYSTEM target_include_directories(${APP_NAME} SYSTEM

View File

@@ -3,6 +3,7 @@
#include <glib.h> #include <glib.h>
#include <string.h> #include <string.h>
#include "audio.h"
#include "client.h" #include "client.h"
#include "monitor.h" #include "monitor.h"
#include "types.h" #include "types.h"
@@ -76,6 +77,16 @@ void raise_or_run(const user_action_arg_t *arg) {
spawn(&arguments); spawn(&arguments);
} }
void toggle_mute(const user_action_arg_t *arg) {
if (wm.status->pulse_context) toggle_pulse_mute(wm.status->pulse_context);
}
void change_volume(const user_action_arg_t *arg) {
if (wm.status->pulse_context) {
change_pulse_volume(wm.status->pulse_context, arg->i);
}
}
void toggle_client_floating(const user_action_arg_t *arg) { void toggle_client_floating(const user_action_arg_t *arg) {
if (!wm.client_focused) return; if (!wm.client_focused) return;

View File

@@ -55,6 +55,8 @@ void send_client_to_next_monitor(const user_action_arg_t *arg);
void spawn(const user_action_arg_t *arg); void spawn(const user_action_arg_t *arg);
void quit(const user_action_arg_t *arg); void quit(const user_action_arg_t *arg);
void raise_or_run(const user_action_arg_t *arg); void raise_or_run(const user_action_arg_t *arg);
void toggle_mute(const user_action_arg_t *arg);
void change_volume(const user_action_arg_t *arg);
void toggle_client_floating(const user_action_arg_t *arg); void toggle_client_floating(const user_action_arg_t *arg);
void toggle_client_fullscreen(const user_action_arg_t *arg); void toggle_client_fullscreen(const user_action_arg_t *arg);
void toggle_client_maximize(const user_action_arg_t *arg); void toggle_client_maximize(const user_action_arg_t *arg);

175
src/audio.c Normal file
View File

@@ -0,0 +1,175 @@
#include "audio.h"
#include <glib.h>
#include <math.h>
#include <pulse/context.h>
#include <pulse/def.h>
#include <pulse/glib-mainloop.h>
#include <pulse/introspect.h>
#include <pulse/mainloop-api.h>
#include <pulse/proplist.h>
#include <pulse/subscribe.h>
#include <pulse/volume.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "app.h"
#include "utils.h"
struct pulse_context_t {
GMainContext *g_context;
pulse_notify_t callback;
pa_glib_mainloop *loop;
pa_context *context;
pa_cvolume volume;
pulse_t pulse;
uint32_t index;
};
static void clean_pulse_context(pulse_context_t *context);
static void init_pulse_context(pulse_context_t *context);
static void state_callback(pa_context *context, void *userdata);
static void subscribe_callback(pa_context *context,
pa_subscription_event_type_t t, uint32_t index,
void *userdata);
static void server_info_callback(pa_context *context,
const pa_server_info *info, void *userdata);
static void sink_info_callback(pa_context *context, const pa_sink_info *info,
int eol, void *userdata);
static void parse_sink(const pa_sink_info *info, pulse_t *pulse);
pulse_context_t *init_pulse(GMainContext *context, pulse_notify_t callback) {
pulse_context_t *ctx = p_new(pulse_context_t, 1);
ctx->g_context = context;
ctx->callback = callback;
init_pulse_context(ctx);
return ctx;
}
void change_pulse_volume(pulse_context_t *context, int step) {
if (!context->pulse.inited || !step || step > 100 || step < -100) return;
pa_cvolume vol = context->volume;
if (step > 0) {
pa_cvolume_inc(&vol, PA_VOLUME_NORM * step / 100);
if (pa_cvolume_max(&vol) > PA_VOLUME_NORM) {
pa_cvolume_set(&vol, vol.channels, PA_VOLUME_NORM);
}
} else {
pa_cvolume_dec(&vol, PA_VOLUME_NORM * (-step) / 100);
if (pa_cvolume_min(&vol) < PA_VOLUME_MUTED) {
pa_cvolume_set(&vol, vol.channels, PA_VOLUME_MUTED);
}
}
pa_context *ctx = context->context;
uint32_t index = context->index;
pa_context_set_sink_volume_by_index(ctx, index, &vol, nullptr, nullptr);
}
void toggle_pulse_mute(pulse_context_t *context) {
if (!context->pulse.inited) return;
pa_context *ctx = context->context;
uint32_t index = context->index;
bool mute = !context->pulse.mute;
pa_context_set_sink_mute_by_index(ctx, index, mute, nullptr, nullptr);
}
void clean_pulse(pulse_context_t *context) {
clean_pulse_context(context);
p_delete(&context);
}
void clean_pulse_context(pulse_context_t *context) {
memset(&context->pulse, 0, sizeof(context->pulse));
if (context->callback) context->callback(&context->pulse);
if (context->context) {
pa_context_disconnect(context->context);
pa_context_unref(context->context);
context->context = nullptr;
}
if (context->loop) pa_glib_mainloop_free(context->loop);
context->pulse.inited = false;
context->loop = nullptr;
}
void init_pulse_context(pulse_context_t *context) {
if (context->loop) pa_glib_mainloop_free(context->loop);
context->loop = pa_glib_mainloop_new(context->g_context);
pa_mainloop_api *api = pa_glib_mainloop_get_api(context->loop);
context->context = pa_context_new(api, APP_NAME "-audio-status");
pa_context_connect(context->context, nullptr, PA_CONTEXT_NOFAIL, nullptr);
pa_context_set_state_callback(context->context, state_callback, context);
}
void state_callback(pa_context *context, void *userdata) {
pulse_context_t *ctx = userdata;
pa_context_state_t state = pa_context_get_state(context);
if (PA_CONTEXT_IS_GOOD(state)) {
pa_context_set_subscribe_callback(context, subscribe_callback, ctx);
pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr);
pa_context_get_server_info(context, server_info_callback, ctx);
} else if (state == PA_CONTEXT_FAILED) {
clean_pulse_context(ctx);
init_pulse_context(ctx);
}
}
void subscribe_callback(pa_context *context, pa_subscription_event_type_t t,
uint32_t index, void *userdata) {
auto facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
if (facility != PA_SUBSCRIPTION_EVENT_SINK) return;
pa_subscription_event_type_t event = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
if (event == PA_SUBSCRIPTION_EVENT_CHANGE) {
pa_context_get_server_info(context, server_info_callback, userdata);
}
}
void server_info_callback(pa_context *context, const pa_server_info *info,
void *userdata) {
pa_context_get_sink_info_by_name(context, info->default_sink_name,
sink_info_callback, userdata);
}
void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol,
void *userdata) {
if (eol != 0 || !info) return;
pulse_context_t *ctx = userdata;
ctx->pulse.inited = true;
ctx->volume = info->volume;
ctx->index = info->index;
parse_sink(info, &ctx->pulse);
if (ctx->callback) ctx->callback(&ctx->pulse);
}
static inline int volume_to_percent(pa_volume_t volume) {
return (int)round(((double)volume) * 100 / PA_VOLUME_NORM);
}
void parse_sink(const pa_sink_info *info, pulse_t *pulse) {
size_t size = sizeof(pulse->device_name);
pulse->mute = info->mute;
pulse->volume_percent = volume_to_percent(pa_cvolume_avg(&info->volume));
strncpy(pulse->device_name, info->description, size);
const char *key = nullptr;
void *state = nullptr;
while ((key = pa_proplist_iterate(info->proplist, &state)) != nullptr) {
if (strcmp(key, "api.alsa.path") != 0 &&
strcmp(key, "device.description") != 0) {
continue;
}
const char *value = pa_proplist_gets(info->proplist, key);
size_t len = strnlen(value, size);
if (len && (len < strnlen(pulse->device_name, size))) {
strncpy(pulse->device_name, value, size);
pulse->device_name[size - 1] = '\0';
}
}
}

20
src/audio.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <glib.h>
#include <stdint.h>
typedef struct pulse_context_t pulse_context_t;
typedef struct pulse_t {
int volume_percent;
bool mute;
bool inited;
char device_name[254];
} pulse_t;
typedef void (*pulse_notify_t)(pulse_t *pulse);
pulse_context_t *init_pulse(GMainContext *context, pulse_notify_t callback);
void change_pulse_volume(pulse_context_t *context, int step);
void toggle_pulse_mute(pulse_context_t *context);
void clean_pulse(pulse_context_t *context);

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <X11/XF86keysym.h>
#include <X11/keysym.h> #include <X11/keysym.h>
#include <stdint.h> #include <stdint.h>
@@ -98,6 +99,14 @@ static const keyboard_t key_list[] = {
{modifier_super | modifier_control, XK_space, toggle_client_floating, {0}}, {modifier_super | modifier_control, XK_space, toggle_client_floating, {0}},
{modifier_super, XK_f, toggle_client_fullscreen, {0}}, {modifier_super, XK_f, toggle_client_fullscreen, {0}},
{modifier_super, XK_m, toggle_client_maximize, {0}}, {modifier_super, XK_m, toggle_client_maximize, {0}},
{modifier_super, XK_Up, change_volume, {.i = 1}},
{modifier_super, XK_Down, change_volume, {.i = -1}},
{modifier_super | modifier_shift, XK_m, toggle_mute, {0}},
{modifier_none, XF86XK_AudioRaiseVolume, change_volume, {.i = 1}},
{modifier_none, XF86XK_AudioLowerVolume, change_volume, {.i = -1}},
{modifier_none, XF86XK_AudioMute, toggle_mute, {0}},
}; };
static const layout_t layout_list[] = { static const layout_t layout_list[] = {

View File

@@ -243,12 +243,22 @@ static void monitor_draw_status(monitor_t *monitor, status_t *status) {
if (status == nullptr) return; if (status == nullptr) return;
char cpu[16], mem[64]; char cpu[16], mem[64], volume[300];
snprintf(cpu, sizeof(cpu), "%.1lf", status->cpu_usage_percent); snprintf(cpu, sizeof(cpu), "%.1lf", status->cpu_usage_percent);
snprintf(mem, sizeof(mem), "%s(%.1f%%)", status->mem_usage.mem_used_text, snprintf(mem, sizeof(mem), "%s(%.1f%%)", status->mem_usage.mem_used_text,
status->mem_usage.mem_percent); status->mem_usage.mem_percent);
if (status->pulse) {
snprintf(volume, sizeof(volume), "%d%%%s%s%s",
status->pulse->volume_percent, status->pulse->mute ? "M" : "",
strlen(status->pulse->device_name) ? "|" : "",
status->pulse->device_name);
} else {
snprintf(volume, sizeof(volume), "0%%");
}
char *status_items[] = { char *status_items[] = {
status->net_speed.down, status->net_speed.up, mem, cpu, status->time, status->net_speed.down, status->net_speed.up, volume, mem, cpu,
status->time,
}; };
int16_t start = monitor->status_extent.start; int16_t start = monitor->status_extent.start;
for (int i = countof(status_items) - 1; i >= 0; i--) { for (int i = countof(status_items) - 1; i >= 0; i--) {

View File

@@ -8,6 +8,8 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "audio.h"
static constexpr char CPU_FILE[] = "/proc/stat"; static constexpr char CPU_FILE[] = "/proc/stat";
static constexpr char MEM_FILE[] = "/proc/meminfo"; static constexpr char MEM_FILE[] = "/proc/meminfo";
static constexpr char NET_FILE[] = "/proc/net/dev"; static constexpr char NET_FILE[] = "/proc/net/dev";
@@ -228,13 +230,21 @@ static gboolean update_status(gpointer data) {
return true; return true;
} }
void notify_pulse_change(pulse_t *pulse) {
status.pulse = pulse;
notify_status_change();
}
void init_status(GMainContext *context, status_changed_notify callback) { void init_status(GMainContext *context, status_changed_notify callback) {
listener = callback; listener = callback;
status.pulse_context = init_pulse(context, notify_pulse_change);
update_status(nullptr); update_status(nullptr);
timer = g_timeout_add_seconds(INTERVAL, update_status, nullptr); timer = g_timeout_add_seconds(INTERVAL, update_status, nullptr);
} }
void clean_status(void) { void clean_status(void) {
clean_pulse(status.pulse_context);
g_source_remove(timer); g_source_remove(timer);
status.pulse_context = nullptr;
listener = nullptr; listener = nullptr;
} }

View File

@@ -3,6 +3,8 @@
#include <glib.h> #include <glib.h>
#include <stdint.h> #include <stdint.h>
#include "audio.h"
typedef struct memory_usage_t { typedef struct memory_usage_t {
char mem_used_text[32]; char mem_used_text[32];
char swap_used_text[32]; char swap_used_text[32];
@@ -33,6 +35,8 @@ typedef struct status_t {
memory_usage_t mem_usage; memory_usage_t mem_usage;
double cpu_usage_percent; double cpu_usage_percent;
char time[64]; char time[64];
pulse_t *pulse;
pulse_context_t *pulse_context;
} status_t; } status_t;
typedef void (*status_changed_notify)(status_t *status); typedef void (*status_changed_notify)(status_t *status);