添加状态栏和调整音量的快捷键

This commit is contained in:
2025-08-27 06:51:07 +08:00
parent 96629c3045
commit c5e2b4c081
10 changed files with 552 additions and 9 deletions

View File

@@ -18,7 +18,7 @@ project(${APP_NAME}
LANGUAGES C
)
add_executable(${APP_NAME} main.c utils.c layout.c)
add_executable(${APP_NAME} main.c utils.c layout.c status.c)
# use C17
set_property(TARGET ${APP_NAME} PROPERTY C_STANDARD 17)
@@ -40,6 +40,7 @@ pkg_check_modules(XCB REQUIRED
)
pkg_check_modules(RENDERER REQUIRED cairo pango pangocairo imlib2)
pkg_check_modules(CAIRO_XCB REQUIRED cairo-xcb)
pkg_check_modules(PULSE REQUIRED libpulse-mainloop-glib)
add_subdirectory(xcb)
add_subdirectory(renderer)
@@ -53,6 +54,7 @@ configure_file(
target_include_directories(${APP_NAME} SYSTEM
PRIVATE ${GLIB_INCLUDE_DIRS}
PRIVATE ${CAIRO_XCB_INCLUDE_DIRS}
PRIVATE ${PULSE_INCLUDE_DIRS}
)
target_include_directories(${APP_NAME}
PRIVATE "${SOURCE_DIR}/include"
@@ -64,6 +66,7 @@ target_link_libraries(${APP_NAME}
PRIVATE m
PRIVATE ${GLIB_LIBRARIES}
PRIVATE ${CAIRO_XCB_LIBRARIES}
PRIVATE ${PULSE_LIBRARIES}
PRIVATE ${LIB_XCB_NAME}
PRIVATE ${LIB_RENDERER_NAME}
)

View File

@@ -9,3 +9,5 @@ void kill_client(const user_action_arg_t *arg);
void focus_stack(const user_action_arg_t *arg);
void focus_monitor(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);

View File

@@ -1,5 +1,6 @@
#pragma once
#include <X11/XF86keysym.h>
#include <X11/keysym.h>
#include <stdbool.h>
#include <stdint.h>
@@ -95,6 +96,7 @@ const keybinding_t keys[] = {
{ALT, XK_k, focus_stack, {.b = false}},
{SUPER | SHIFT, XK_j, focus_monitor, {.b = true}},
{SUPER | SHIFT, XK_k, focus_monitor, {.b = false}},
{
SUPER,
XK_a,
@@ -109,6 +111,14 @@ const keybinding_t keys[] = {
},
{ALT | CTRL, XK_r, raise_or_run, {.ptr = (char *[]){"St", "st"}}},
{SUPER, XK_q, raise_or_run, {.ptr = (char *[]){"firefox", "firefox-bin"}}},
{SUPER, XK_Up, change_volume, {.i = 1}},
{SUPER, XK_Down, change_volume, {.i = -1}},
{SUPER | SHIFT, XK_m, toggle_mute, {0}},
{0, XF86XK_AudioRaiseVolume, change_volume, {.i = 1}},
{0, XF86XK_AudioLowerVolume, change_volume, {.i = -1}},
{0, XF86XK_AudioMute, toggle_mute, {0}},
{SUPER, XK_1, select_tag, {.ui = 1 << 0}},
{SUPER, XK_2, select_tag, {.ui = 1 << 1}},
{SUPER, XK_3, select_tag, {.ui = 1 << 2}},
@@ -142,4 +152,5 @@ const uint32_t bar_y_padding = 1;
const uint32_t tag_x_padding = 10;
const uint32_t layout_symbol_x_padding = 10;
const uint32_t client_name_x_padding = 10;
const uint32_t status_x_padding = 6;
const uint32_t border_width = 1;

View File

@@ -12,7 +12,8 @@ uint32_t *generate_wallpaper(const wallpaper_config_t *wallpaper_config,
uint32_t get_font_height(const char *font_family, const uint32_t font_size,
const uint32_t dpi);
void draw_bar(monitor_t *monitor, bool is_current_monitor,
void draw_bar(monitor_t *monitor, bool is_current_monitor, status_t *status,
color_set_t *color_set, uint32_t height, uint32_t tag_x_padding,
uint32_t layout_x_padding, uint32_t client_name_x_padding);
uint32_t layout_x_padding, uint32_t client_name_x_padding,
uint32_t status_x_padding);
void clean_pango_context(void);

View File

@@ -119,3 +119,44 @@ typedef struct color_set_t {
color_t border_color;
color_t active_border_color;
} color_set_t;
typedef struct {
char mem_used_text[32];
char swap_used_text[32];
float mem_percent;
float swap_percent;
uint64_t mem_used;
uint64_t swap_used;
uint64_t mem_total;
uint64_t mem_free;
uint64_t buffers;
uint64_t cached;
uint64_t swap_total;
uint64_t swap_free;
uint64_t s_reclaimable;
} memory_usage_t;
typedef struct {
uint64_t rx_bytes;
uint64_t tx_bytes;
char up[128];
char down[128];
} net_speed_t;
typedef struct {
uint32_t index;
int mute;
int avg_volume; /* 各通道平均音量(百分比) */
int volumes[5]; /* 各通道音量(百分比) */
char device[512]; /* 音频设备名称 */
} pulse_t;
typedef struct {
pulse_t pulse;
net_speed_t net_speed;
memory_usage_t mem_usage;
double cpu_usage_percent;
char time[256];
} status_t;

View File

@@ -10,8 +10,13 @@
#define LENGTH(X) (sizeof(X) / sizeof(X)[0])
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
double get_time();
void die(const char *format, ...);

View File

@@ -17,6 +17,7 @@
#include "event-adapter.h"
#include "layout.h"
#include "renderer.h"
#include "status.h"
#include "types.h"
#include "utils.h"
#include "xcb.h"
@@ -33,6 +34,7 @@ static void init_color_set(void);
static void parse_color(const char *hex, color_t *color);
static void init_monitor_bar(void);
static void get_xresource_config(void);
static void update_status(status_t *status);
static void draw_all_monitor_bars(void);
static void draw_monitor_bar(monitor_t *monitor);
static void init_xcb_event_loop(void);
@@ -77,6 +79,7 @@ static monitor_t *current_monitor = NULL;
static cairo_t *wallpaper_cr = NULL;
static wallpaper_config_t *wallpaper_config = NULL;
static color_set_t *color_set = NULL;
static status_t *status = NULL;
static GMainLoop *loop = NULL;
static event_adapter_t *adapter = NULL;
static char *font_family = NULL;
@@ -86,6 +89,7 @@ static uint32_t bar_y_pad = bar_y_padding;
static uint32_t tag_x_pad = tag_x_padding;
static uint32_t layout_symbol_x_pad = layout_symbol_x_padding;
static uint32_t client_name_x_pad = client_name_x_padding;
static uint32_t status_x_pad = status_x_padding;
static uint32_t bar_height = 0;
static uint32_t border_px = border_width;
@@ -184,6 +188,12 @@ void raise_or_run(const user_action_arg_t *arg) {
spawn(&argument);
}
void toggle_mute(const user_action_arg_t *arg) { toggle_pulse_mute(); }
void change_volume(const user_action_arg_t *arg) {
change_pulse_volume(arg->i);
}
/* 调试函数,开发完成后删除 */
static void print_monitor_list_info(monitor_t *monitor_list) {
logger("========================= monitor info =========================\n");
@@ -559,6 +569,7 @@ void get_xresource_config(void) {
get_xresource_uint32("zswm.tag_x_padding", &tag_x_pad);
get_xresource_uint32("zswm.layout_symbol_x_padding", &layout_symbol_x_pad);
get_xresource_uint32("zswm.client_name_x_padding", &client_name_x_pad);
get_xresource_uint32("zswm.status_x_padding", &status_x_pad);
get_xresource_uint32("zswm.border_width", &border_px);
logger("dpi: %u, font family: %s, font size: %u\n", dpi, font_family,
@@ -566,13 +577,18 @@ void get_xresource_config(void) {
clean_xresource();
}
static void update_status(status_t *s) {
status = s;
draw_all_monitor_bars();
}
void draw_all_monitor_bars(void) {
for (monitor_t *m = monitors; m; m = m->next) draw_monitor_bar(m);
}
void draw_monitor_bar(monitor_t *m) {
draw_bar(m, m == current_monitor, color_set, bar_height, tag_x_pad,
layout_symbol_x_pad, client_name_x_pad);
draw_bar(m, m == current_monitor, status, color_set, bar_height, tag_x_pad,
layout_symbol_x_pad, client_name_x_pad, status_x_pad);
flush_xcb_connection();
}
@@ -1117,6 +1133,8 @@ gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
}
void cleanup(void) {
clean_status();
g_main_loop_unref(loop);
loop = NULL;
@@ -1161,12 +1179,15 @@ int main(int argc, char *argv[]) {
die("another window manager is already running");
}
loop = g_main_loop_new(NULL, FALSE);
setup_xcb();
init_monitors();
init_color_set();
init_monitor_bar();
change_window_cursor(get_root_window(), cursor_normal);
init_wallpaper();
init_status(g_main_loop_get_context(loop), update_status);
draw_all_monitor_bars();
@@ -1192,7 +1213,6 @@ int main(int argc, char *argv[]) {
init_xcb_event_loop();
loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(loop);
cleanup();

View File

@@ -11,9 +11,11 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "renderer.h"
#include "types.h"
#include "utils.h"
static PangoContext *context = NULL;
static PangoAttrList *attr_list = NULL;
@@ -135,9 +137,43 @@ static void draw_rect(cairo_t *cr, int32_t x, int32_t y, uint32_t width,
}
}
void draw_bar(monitor_t *monitor, bool is_current_monitor,
static int32_t draw_status(monitor_t *monitor, status_t *status,
color_set_t *color_set, uint32_t height,
uint32_t x_padding, int32_t end) {
char cpu[16], mem[64], volume[600], net_down[140], net_up[140];
sprintf(cpu, ":%.1f", status->cpu_usage_percent);
sprintf(mem, ":%s(%.1f%%)", status->mem_usage.mem_used_text,
status->mem_usage.mem_percent);
sprintf(volume, ":%d%%%s%s%s", status->pulse.avg_volume,
status->pulse.mute ? "M" : "",
strlen(status->pulse.device) ? "|" : "", status->pulse.device);
sprintf(net_down, ":%s", status->net_speed.down);
sprintf(net_up, ":%s", status->net_speed.up);
char *info[] = {net_down, net_up, volume, mem, cpu, status->time};
int32_t x = monitor->monitor_width;
color_t *color = &color_set->status_color;
for (int i = LENGTH(info) - 1; i >= 0; i--) {
char *text = info[i];
if (!strlen(text)) continue;
uint32_t width = get_text_width(text);
x -= width + x_padding;
if (x < end) return x + (width + x_padding);
draw_text(monitor->cr, text, color, x, 0, width, height, false);
x -= x_padding;
if (x < end) return x + x_padding;
}
return x;
};
void draw_bar(monitor_t *monitor, bool is_current_monitor, status_t *status,
color_set_t *color_set, uint32_t height, uint32_t tag_x_padding,
uint32_t layout_x_padding, uint32_t client_name_x_padding) {
uint32_t layout_x_padding, uint32_t client_name_x_padding,
uint32_t status_x_padding) {
cairo_t *cr = monitor->cr;
int32_t x = 0;
uint32_t width = 0;
@@ -179,9 +215,14 @@ void draw_bar(monitor_t *monitor, bool is_current_monitor,
draw_text(cr, text, color, x, 0, width, height, true);
x += width;
}
/* 绘制状态栏信息 */
int32_t status_start =
draw_status(monitor, status, color_set, height, status_x_padding, x);
/* 绘制 client name 列表 */
if (visible_client_count) {
uint32_t box_width = (monitor->monitor_width - x) / visible_client_count;
uint32_t box_width = (status_start - x) / visible_client_count;
width = box_width - 2 * client_name_x_padding;
for (client_t *c = monitor->clients; c; c = c->next) {
if (!CLIENT_VISIBLE(c)) continue;

407
src/status.c Normal file
View File

@@ -0,0 +1,407 @@
#include "status.h"
#include <glib.h>
#include <math.h>
#include <pulse/context.h>
#include <pulse/glib-mainloop.h>
#include <pulse/introspect.h>
#include <pulse/subscribe.h>
#include <pulse/volume.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "types.h"
#include "utils.h"
#define CPU_FILE "/proc/stat"
#define MEM_FILE "/proc/meminfo"
#define NET_FILE "/proc/net/dev"
#define INTERVAL 1
#define TIME_FORMAT "%A %m-%d %H:%M:%S"
typedef struct cpu_stat_t {
uint64_t user;
uint64_t nice;
uint64_t system;
uint64_t idle;
uint64_t iowait;
uint64_t irq;
uint64_t softirq;
uint64_t steal;
uint64_t guest;
uint64_t guest_nice;
} cpu_stat_t;
typedef struct net_stat_t {
uint64_t rx_bytes;
uint64_t tx_bytes;
} net_stat_t;
typedef void (*pulse_notify)(pulse_t pulse);
static status_t status;
static guint timer = 0;
static status_changed_notify listener = NULL;
static bool pulse_inited = false;
static pa_cvolume current_volume;
static pa_context *context = NULL;
static double calc_cpu_usage(cpu_stat_t curr, cpu_stat_t prev) {
uint64_t curr_idle = curr.idle + curr.iowait;
uint64_t curr_total = curr.user + curr.nice + curr.system + curr.idle +
curr.iowait + curr.irq + curr.softirq + curr.steal +
curr.guest + curr.guest_nice;
uint64_t prev_idle = prev.idle + prev.iowait;
uint64_t prev_total = prev.user + prev.nice + prev.system + prev.idle +
prev.iowait + prev.irq + prev.softirq + prev.steal +
prev.guest + prev.guest_nice;
uint64_t total = curr_total - prev_total;
uint64_t idle = curr_idle - prev_idle;
uint64_t active = total - idle;
if (total == 0) {
return 0.0;
}
double usage = ((double)active) / total * 100;
return usage;
}
static bool get_cpu_usage(double *usage, GError *error) {
static cpu_stat_t prev_cpu_stat = {0};
static gboolean inited = false;
gchar *contents = NULL;
gsize length = 0;
if (!g_file_get_contents(CPU_FILE, &contents, &length, &error)) {
return false;
}
gchar **lines = g_strsplit(contents, "\n", 0);
g_free(contents);
char cpu_line[256];
for (gchar **line = lines; *line != NULL; ++line) {
if (strncmp(*line, "cpu ", 4) == 0) {
strncpy(cpu_line, *line, sizeof(cpu_line));
break;
}
}
cpu_stat_t stat = {0};
sscanf(cpu_line, "cpu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", &stat.user,
&stat.nice, &stat.system, &stat.idle, &stat.iowait, &stat.irq,
&stat.softirq, &stat.steal, &stat.guest, &stat.guest_nice);
if (!inited) {
prev_cpu_stat = stat;
inited = false;
}
double percent = calc_cpu_usage(stat, prev_cpu_stat);
prev_cpu_stat = stat;
*usage = percent;
g_strfreev(lines);
return true;
}
static void bytes_to_readable_size(uint64_t bytes, char *str) {
static char *units[] = {"B", "K", "M", "G"};
double size = (double)bytes;
int i = 0;
while (size > 1024) {
size /= 1024;
++i;
}
sprintf(str, "%.1lf%s", size, units[i]);
}
static void calc_mem_usage(memory_usage_t *usage) {
usage->mem_used = usage->mem_total - usage->mem_free - usage->buffers -
usage->cached - usage->s_reclaimable;
usage->swap_used = usage->swap_total - usage->swap_free;
usage->mem_percent =
((float)usage->mem_used) / ((float)usage->mem_total) * 100;
if (usage->swap_total == 0) {
usage->swap_percent = 0;
}
bytes_to_readable_size(usage->mem_used * 1024, usage->mem_used_text);
bytes_to_readable_size(usage->swap_used * 1024, usage->swap_used_text);
}
static bool get_mem_usage(memory_usage_t *usage, GError *error) {
gchar *contents = NULL;
gsize length = 0;
if (!g_file_get_contents(MEM_FILE, &contents, &length, &error)) {
return false;
}
gchar **lines = g_strsplit(contents, "\n", 0);
g_free(contents);
for (gchar **line = lines; *line != NULL; ++line) {
char name[32];
uint64_t kb = 0;
sscanf(*line, "%[^:]: %lu", name, &kb);
if (g_str_equal(name, "MemTotal")) {
usage->mem_total = kb;
} else if (g_str_equal(name, "MemFree")) {
usage->mem_free = kb;
} else if (g_str_equal(name, "Buffers")) {
usage->buffers = kb;
} else if (g_str_equal(name, "Cached")) {
usage->cached = kb;
} else if (g_str_equal(name, "SwapTotal")) {
usage->swap_total = kb;
} else if (g_str_equal(name, "SwapFree")) {
usage->swap_free = kb;
} else if (g_str_equal(name, "SReclaimable")) {
usage->s_reclaimable = kb;
}
}
calc_mem_usage(usage);
g_strfreev(lines);
return true;
}
static void calc_speed(const uint32_t interval, const net_stat_t *current,
const net_stat_t *previous, net_speed_t *speed) {
speed->rx_bytes = current->rx_bytes - previous->rx_bytes;
speed->tx_bytes = current->tx_bytes - previous->tx_bytes;
bytes_to_readable_size(speed->rx_bytes / interval, speed->down);
bytes_to_readable_size(speed->tx_bytes / interval, speed->up);
}
static bool get_net_speed(const uint32_t interval, net_speed_t *speed,
GError *error) {
static net_stat_t prev = {0};
static bool inited = false;
gchar *contents = NULL;
gsize length = 0;
if (!g_file_get_contents(NET_FILE, &contents, &length, &error)) {
return false;
}
gchar **lines = g_strsplit(contents, "\n", 0);
g_free(contents);
net_stat_t stat = {.rx_bytes = 0, .tx_bytes = 0};
for (gchar **line = lines; *line != NULL; ++line) {
if (strchr(*line, ':') == NULL) {
continue;
}
char name[16];
uint64_t rx_bytes, tx_bytes;
sscanf(*line, " %[^:]: %lu %*u %*u %*u %*u %*u %*u %*u %lu", name,
&rx_bytes, &tx_bytes);
if (g_str_equal(name, "lo")) {
continue;
}
stat.rx_bytes += rx_bytes;
stat.tx_bytes += tx_bytes;
}
if (!inited) {
inited = true;
prev = stat;
}
calc_speed(interval, &stat, &prev, speed);
prev = stat;
g_strfreev(lines);
return false;
}
static void get_date_time(char *time_text, size_t length) {
time_t current_time = time(NULL);
struct tm *time_info = localtime(&current_time);
strftime(time_text, length, TIME_FORMAT, time_info);
}
#define volume_to_percent(v) ((int)round((double)(v) * 100 / PA_VOLUME_NORM))
static void parse_sink(const pa_sink_info *i, pulse_t *pulse) {
pulse->index = i->index;
pulse->mute = i->mute;
memset(pulse->volumes, -1, sizeof(pulse->volumes));
pulse->avg_volume = volume_to_percent(pa_cvolume_avg(&i->volume));
strncpy(pulse->device, i->description, sizeof(pulse->device));
int len = 0;
for (uint8_t ch = 0; ch < LENGTH(pulse->volumes); ++ch) {
if (!pa_channel_map_has_position(&i->channel_map, ch)) {
continue;
}
pa_volume_t v = pa_cvolume_get_position(&i->volume, &i->channel_map, ch);
int volume = volume_to_percent(v);
pulse->volumes[len++] = volume;
}
for (uint8_t i = len; i < LENGTH(pulse->volumes); ++i) {
pulse->volumes[i] = -1;
}
const char *key = NULL;
void *state = NULL;
while ((key = pa_proplist_iterate(i->proplist, &state)) != NULL) {
if (strcmp(key, "api.alsa.path") != 0 &&
strcmp(key, "device.description") != 0) {
continue;
}
const char *value = pa_proplist_gets(i->proplist, key);
unsigned long len = strlen(value);
if (len && (len < strlen(pulse->device))) {
strncpy(pulse->device, value, sizeof(pulse->device));
}
}
}
static void notify_status_change() {
if (listener != NULL) listener(&status);
}
static void sink_info_callback(pa_context *c, const pa_sink_info *info, int eol,
void *userdata) {
if (eol < 0) {
return;
}
if (eol > 0) {
return;
}
if (info) {
context = c;
parse_sink(info, &status.pulse);
current_volume = info->volume;
pulse_inited = true;
notify_status_change();
}
}
static void server_info_callback(pa_context *c, const pa_server_info *info,
void *userdata) {
pa_context_get_sink_info_by_name(c, info->default_sink_name,
sink_info_callback, NULL);
}
static void subscribe_callback(pa_context *c, pa_subscription_event_type_t t,
uint32_t idx, void *userdata) {
pa_subscription_event_type_t event = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
uint32_t facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
if (facility != PA_SUBSCRIPTION_EVENT_SINK) {
return;
}
if (event == PA_SUBSCRIPTION_EVENT_CHANGE) {
pa_context_get_server_info(c, server_info_callback, NULL);
}
}
static void clean_pulse() {
memset(&status.pulse, 0, sizeof(status.pulse));
notify_status_change();
if (context) {
pa_context_disconnect(context);
pa_context_unref(context);
context = NULL;
}
pulse_inited = false;
}
static void init_pulse(GMainContext *g_context);
static void state_callback(pa_context *c, void *userdata) {
pa_context_state_t state = pa_context_get_state(c);
if (PA_CONTEXT_IS_GOOD(state)) {
pa_context_set_subscribe_callback(c, subscribe_callback, NULL);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK, NULL, NULL);
pa_context_get_server_info(c, server_info_callback, userdata);
} else if (state == PA_CONTEXT_FAILED) {
clean_pulse();
init_pulse((GMainContext *)userdata);
}
}
static void init_pulse(GMainContext *g_context) {
pa_glib_mainloop *loop = pa_glib_mainloop_new(g_context);
pa_mainloop_api *api = pa_glib_mainloop_get_api(loop);
pa_context *ctx = pa_context_new(api, "ZSWM-Status-Volume");
pa_context_connect(ctx, NULL, PA_CONTEXT_NOFAIL, NULL);
pa_context_set_state_callback(ctx, state_callback, g_context);
}
static gboolean update_status(gpointer data) {
GError *error = NULL;
get_net_speed(INTERVAL, &status.net_speed, error);
get_mem_usage(&status.mem_usage, error);
get_cpu_usage(&status.cpu_usage_percent, error);
get_date_time(status.time, sizeof(status.time));
notify_status_change();
return true;
}
void init_status(GMainContext *context, status_changed_notify callback) {
listener = callback;
init_pulse(context);
update_status(NULL);
timer = g_timeout_add_seconds(INTERVAL, update_status, NULL);
}
void clean_status() {
clean_pulse();
g_source_remove(timer);
listener = NULL;
}
void change_pulse_volume(int step) {
if (!pulse_inited || !step || step > 100 || step < -100) {
return;
}
pa_cvolume vol = current_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);
}
}
uint32_t index = status.pulse.index;
pa_context_set_sink_volume_by_index(context, index, &vol, NULL, NULL);
}
void toggle_pulse_mute() {
if (!pulse_inited) {
return;
}
uint32_t index = status.pulse.index;
int mute = !status.pulse.mute;
pa_context_set_sink_mute_by_index(context, index, mute, NULL, NULL);
}

12
src/status.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <glib.h>
#include "types.h"
typedef void (*status_changed_notify)(status_t *status);
void init_status(GMainContext *context, status_changed_notify callback);
void clean_status(void);
void change_pulse_volume(int step);
void toggle_pulse_mute(void);