添加状态栏和调整音量的快捷键
This commit is contained in:
@@ -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}
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
11
src/config.h
11
src/config.h
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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, ...);
|
||||
|
||||
26
src/main.c
26
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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
404
src/status.c
Normal file
404
src/status.c
Normal file
@@ -0,0 +1,404 @@
|
||||
#include "status.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(¤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 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();
|
||||
}
|
||||
}
|
||||
|
||||
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, NULL);
|
||||
}
|
||||
|
||||
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
12
src/status.h
Normal 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);
|
||||
Reference in New Issue
Block a user