From c5e2b4c081335e4ac3a82db453cc99ce31be83c7 Mon Sep 17 00:00:00 2001 From: Zedhugh Chen Date: Wed, 27 Aug 2025 06:51:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=8A=B6=E6=80=81=E6=A0=8F?= =?UTF-8?q?=E5=92=8C=E8=B0=83=E6=95=B4=E9=9F=B3=E9=87=8F=E7=9A=84=E5=BF=AB?= =?UTF-8?q?=E6=8D=B7=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CMakeLists.txt | 5 +- src/action.h | 2 + src/config.h | 11 ++ src/include/renderer.h | 5 +- src/include/types.h | 41 +++++ src/include/utils.h | 5 + src/main.c | 26 ++- src/renderer/text.c | 47 ++++- src/status.c | 407 +++++++++++++++++++++++++++++++++++++++++ src/status.h | 12 ++ 10 files changed, 552 insertions(+), 9 deletions(-) create mode 100644 src/status.c create mode 100644 src/status.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 75f37f2..abee543 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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} ) diff --git a/src/action.h b/src/action.h index b031014..e67de41 100644 --- a/src/action.h +++ b/src/action.h @@ -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); diff --git a/src/config.h b/src/config.h index 3d6f0a0..220f818 100644 --- a/src/config.h +++ b/src/config.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -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; diff --git a/src/include/renderer.h b/src/include/renderer.h index 3ba9d3e..ddc01a9 100644 --- a/src/include/renderer.h +++ b/src/include/renderer.h @@ -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); diff --git a/src/include/types.h b/src/include/types.h index c1cc1d2..0193481 100644 --- a/src/include/types.h +++ b/src/include/types.h @@ -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; diff --git a/src/include/utils.h b/src/include/utils.h index b9bb90d..1973214 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -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, ...); diff --git a/src/main.c b/src/main.c index 0df873c..60f092f 100644 --- a/src/main.c +++ b/src/main.c @@ -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(); diff --git a/src/renderer/text.c b/src/renderer/text.c index b4d0697..f862e96 100644 --- a/src/renderer/text.c +++ b/src/renderer/text.c @@ -11,9 +11,11 @@ #include #include #include +#include #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; diff --git a/src/status.c b/src/status.c new file mode 100644 index 0000000..2380570 --- /dev/null +++ b/src/status.c @@ -0,0 +1,407 @@ +#include "status.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(¤t_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); +} diff --git a/src/status.h b/src/status.h new file mode 100644 index 0000000..e6fe81a --- /dev/null +++ b/src/status.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#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);