状态栏添加音量显示并添加音量调节功能
This commit is contained in:
@@ -42,6 +42,7 @@ add_executable(${APP_NAME}
|
||||
${SOURCE_DIR}/layout.c
|
||||
${SOURCE_DIR}/xres.c
|
||||
${SOURCE_DIR}/status.c
|
||||
${SOURCE_DIR}/audio.c
|
||||
)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
@@ -66,6 +67,8 @@ pkg_check_modules(deps REQUIRED
|
||||
cairo-xcb
|
||||
pango
|
||||
pangocairo
|
||||
|
||||
libpulse-mainloop-glib
|
||||
)
|
||||
|
||||
target_include_directories(${APP_NAME} SYSTEM
|
||||
|
||||
11
src/action.c
11
src/action.c
@@ -3,6 +3,7 @@
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "audio.h"
|
||||
#include "client.h"
|
||||
#include "monitor.h"
|
||||
#include "types.h"
|
||||
@@ -76,6 +77,16 @@ void raise_or_run(const user_action_arg_t *arg) {
|
||||
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) {
|
||||
if (!wm.client_focused) return;
|
||||
|
||||
|
||||
@@ -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 quit(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_fullscreen(const user_action_arg_t *arg);
|
||||
void toggle_client_maximize(const user_action_arg_t *arg);
|
||||
|
||||
175
src/audio.c
Normal file
175
src/audio.c
Normal 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
20
src/audio.h
Normal 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);
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <X11/XF86keysym.h>
|
||||
#include <X11/keysym.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, XK_f, toggle_client_fullscreen, {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[] = {
|
||||
|
||||
@@ -243,12 +243,22 @@ static void monitor_draw_status(monitor_t *monitor, status_t *status) {
|
||||
|
||||
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(mem, sizeof(mem), "%s(%.1f%%)", status->mem_usage.mem_used_text,
|
||||
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[] = {
|
||||
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;
|
||||
for (int i = countof(status_items) - 1; i >= 0; i--) {
|
||||
|
||||
10
src/status.c
10
src/status.c
@@ -8,6 +8,8 @@
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
static constexpr char CPU_FILE[] = "/proc/stat";
|
||||
static constexpr char MEM_FILE[] = "/proc/meminfo";
|
||||
static constexpr char NET_FILE[] = "/proc/net/dev";
|
||||
@@ -228,13 +230,21 @@ static gboolean update_status(gpointer data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void notify_pulse_change(pulse_t *pulse) {
|
||||
status.pulse = pulse;
|
||||
notify_status_change();
|
||||
}
|
||||
|
||||
void init_status(GMainContext *context, status_changed_notify callback) {
|
||||
listener = callback;
|
||||
status.pulse_context = init_pulse(context, notify_pulse_change);
|
||||
update_status(nullptr);
|
||||
timer = g_timeout_add_seconds(INTERVAL, update_status, nullptr);
|
||||
}
|
||||
|
||||
void clean_status(void) {
|
||||
clean_pulse(status.pulse_context);
|
||||
g_source_remove(timer);
|
||||
status.pulse_context = nullptr;
|
||||
listener = nullptr;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
typedef struct memory_usage_t {
|
||||
char mem_used_text[32];
|
||||
char swap_used_text[32];
|
||||
@@ -33,6 +35,8 @@ typedef struct status_t {
|
||||
memory_usage_t mem_usage;
|
||||
double cpu_usage_percent;
|
||||
char time[64];
|
||||
pulse_t *pulse;
|
||||
pulse_context_t *pulse_context;
|
||||
} status_t;
|
||||
|
||||
typedef void (*status_changed_notify)(status_t *status);
|
||||
|
||||
Reference in New Issue
Block a user