添加状态栏和调整音量的快捷键
This commit is contained in:
@@ -18,7 +18,7 @@ project(${APP_NAME}
|
|||||||
LANGUAGES C
|
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
|
# use C17
|
||||||
set_property(TARGET ${APP_NAME} PROPERTY C_STANDARD 17)
|
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(RENDERER REQUIRED cairo pango pangocairo imlib2)
|
||||||
pkg_check_modules(CAIRO_XCB REQUIRED cairo-xcb)
|
pkg_check_modules(CAIRO_XCB REQUIRED cairo-xcb)
|
||||||
|
pkg_check_modules(PULSE REQUIRED libpulse-mainloop-glib)
|
||||||
|
|
||||||
add_subdirectory(xcb)
|
add_subdirectory(xcb)
|
||||||
add_subdirectory(renderer)
|
add_subdirectory(renderer)
|
||||||
@@ -53,6 +54,7 @@ configure_file(
|
|||||||
target_include_directories(${APP_NAME} SYSTEM
|
target_include_directories(${APP_NAME} SYSTEM
|
||||||
PRIVATE ${GLIB_INCLUDE_DIRS}
|
PRIVATE ${GLIB_INCLUDE_DIRS}
|
||||||
PRIVATE ${CAIRO_XCB_INCLUDE_DIRS}
|
PRIVATE ${CAIRO_XCB_INCLUDE_DIRS}
|
||||||
|
PRIVATE ${PULSE_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
target_include_directories(${APP_NAME}
|
target_include_directories(${APP_NAME}
|
||||||
PRIVATE "${SOURCE_DIR}/include"
|
PRIVATE "${SOURCE_DIR}/include"
|
||||||
@@ -64,6 +66,7 @@ target_link_libraries(${APP_NAME}
|
|||||||
PRIVATE m
|
PRIVATE m
|
||||||
PRIVATE ${GLIB_LIBRARIES}
|
PRIVATE ${GLIB_LIBRARIES}
|
||||||
PRIVATE ${CAIRO_XCB_LIBRARIES}
|
PRIVATE ${CAIRO_XCB_LIBRARIES}
|
||||||
|
PRIVATE ${PULSE_LIBRARIES}
|
||||||
PRIVATE ${LIB_XCB_NAME}
|
PRIVATE ${LIB_XCB_NAME}
|
||||||
PRIVATE ${LIB_RENDERER_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_stack(const user_action_arg_t *arg);
|
||||||
void focus_monitor(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 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
|
#pragma once
|
||||||
|
|
||||||
|
#include <X11/XF86keysym.h>
|
||||||
#include <X11/keysym.h>
|
#include <X11/keysym.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -95,6 +96,7 @@ const keybinding_t keys[] = {
|
|||||||
{ALT, XK_k, focus_stack, {.b = false}},
|
{ALT, XK_k, focus_stack, {.b = false}},
|
||||||
{SUPER | SHIFT, XK_j, focus_monitor, {.b = true}},
|
{SUPER | SHIFT, XK_j, focus_monitor, {.b = true}},
|
||||||
{SUPER | SHIFT, XK_k, focus_monitor, {.b = false}},
|
{SUPER | SHIFT, XK_k, focus_monitor, {.b = false}},
|
||||||
|
|
||||||
{
|
{
|
||||||
SUPER,
|
SUPER,
|
||||||
XK_a,
|
XK_a,
|
||||||
@@ -109,6 +111,14 @@ const keybinding_t keys[] = {
|
|||||||
},
|
},
|
||||||
{ALT | CTRL, XK_r, raise_or_run, {.ptr = (char *[]){"St", "st"}}},
|
{ALT | CTRL, XK_r, raise_or_run, {.ptr = (char *[]){"St", "st"}}},
|
||||||
{SUPER, XK_q, raise_or_run, {.ptr = (char *[]){"firefox", "firefox-bin"}}},
|
{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_1, select_tag, {.ui = 1 << 0}},
|
||||||
{SUPER, XK_2, select_tag, {.ui = 1 << 1}},
|
{SUPER, XK_2, select_tag, {.ui = 1 << 1}},
|
||||||
{SUPER, XK_3, select_tag, {.ui = 1 << 2}},
|
{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 tag_x_padding = 10;
|
||||||
const uint32_t layout_symbol_x_padding = 10;
|
const uint32_t layout_symbol_x_padding = 10;
|
||||||
const uint32_t client_name_x_padding = 10;
|
const uint32_t client_name_x_padding = 10;
|
||||||
|
const uint32_t status_x_padding = 6;
|
||||||
const uint32_t border_width = 1;
|
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,
|
uint32_t get_font_height(const char *font_family, const uint32_t font_size,
|
||||||
const uint32_t dpi);
|
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,
|
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);
|
void clean_pango_context(void);
|
||||||
|
|||||||
@@ -119,3 +119,44 @@ typedef struct color_set_t {
|
|||||||
color_t border_color;
|
color_t border_color;
|
||||||
color_t active_border_color;
|
color_t active_border_color;
|
||||||
} color_set_t;
|
} 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])
|
#define LENGTH(X) (sizeof(X) / sizeof(X)[0])
|
||||||
|
|
||||||
|
#ifndef MIN
|
||||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX
|
||||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
|
||||||
double get_time();
|
double get_time();
|
||||||
void die(const char *format, ...);
|
void die(const char *format, ...);
|
||||||
|
|||||||
26
src/main.c
26
src/main.c
@@ -17,6 +17,7 @@
|
|||||||
#include "event-adapter.h"
|
#include "event-adapter.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
|
#include "status.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "xcb.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 parse_color(const char *hex, color_t *color);
|
||||||
static void init_monitor_bar(void);
|
static void init_monitor_bar(void);
|
||||||
static void get_xresource_config(void);
|
static void get_xresource_config(void);
|
||||||
|
static void update_status(status_t *status);
|
||||||
static void draw_all_monitor_bars(void);
|
static void draw_all_monitor_bars(void);
|
||||||
static void draw_monitor_bar(monitor_t *monitor);
|
static void draw_monitor_bar(monitor_t *monitor);
|
||||||
static void init_xcb_event_loop(void);
|
static void init_xcb_event_loop(void);
|
||||||
@@ -77,6 +79,7 @@ static monitor_t *current_monitor = NULL;
|
|||||||
static cairo_t *wallpaper_cr = NULL;
|
static cairo_t *wallpaper_cr = NULL;
|
||||||
static wallpaper_config_t *wallpaper_config = NULL;
|
static wallpaper_config_t *wallpaper_config = NULL;
|
||||||
static color_set_t *color_set = NULL;
|
static color_set_t *color_set = NULL;
|
||||||
|
static status_t *status = NULL;
|
||||||
static GMainLoop *loop = NULL;
|
static GMainLoop *loop = NULL;
|
||||||
static event_adapter_t *adapter = NULL;
|
static event_adapter_t *adapter = NULL;
|
||||||
static char *font_family = 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 tag_x_pad = tag_x_padding;
|
||||||
static uint32_t layout_symbol_x_pad = layout_symbol_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 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 bar_height = 0;
|
||||||
static uint32_t border_px = border_width;
|
static uint32_t border_px = border_width;
|
||||||
|
|
||||||
@@ -184,6 +188,12 @@ void raise_or_run(const user_action_arg_t *arg) {
|
|||||||
spawn(&argument);
|
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) {
|
static void print_monitor_list_info(monitor_t *monitor_list) {
|
||||||
logger("========================= monitor info =========================\n");
|
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.tag_x_padding", &tag_x_pad);
|
||||||
get_xresource_uint32("zswm.layout_symbol_x_padding", &layout_symbol_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.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);
|
get_xresource_uint32("zswm.border_width", &border_px);
|
||||||
|
|
||||||
logger("dpi: %u, font family: %s, font size: %u\n", dpi, font_family,
|
logger("dpi: %u, font family: %s, font size: %u\n", dpi, font_family,
|
||||||
@@ -566,13 +577,18 @@ void get_xresource_config(void) {
|
|||||||
clean_xresource();
|
clean_xresource();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void update_status(status_t *s) {
|
||||||
|
status = s;
|
||||||
|
draw_all_monitor_bars();
|
||||||
|
}
|
||||||
|
|
||||||
void draw_all_monitor_bars(void) {
|
void draw_all_monitor_bars(void) {
|
||||||
for (monitor_t *m = monitors; m; m = m->next) draw_monitor_bar(m);
|
for (monitor_t *m = monitors; m; m = m->next) draw_monitor_bar(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_monitor_bar(monitor_t *m) {
|
void draw_monitor_bar(monitor_t *m) {
|
||||||
draw_bar(m, m == current_monitor, color_set, bar_height, tag_x_pad,
|
draw_bar(m, m == current_monitor, status, color_set, bar_height, tag_x_pad,
|
||||||
layout_symbol_x_pad, client_name_x_pad);
|
layout_symbol_x_pad, client_name_x_pad, status_x_pad);
|
||||||
flush_xcb_connection();
|
flush_xcb_connection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1117,6 +1133,8 @@ gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cleanup(void) {
|
void cleanup(void) {
|
||||||
|
clean_status();
|
||||||
|
|
||||||
g_main_loop_unref(loop);
|
g_main_loop_unref(loop);
|
||||||
loop = NULL;
|
loop = NULL;
|
||||||
|
|
||||||
@@ -1161,12 +1179,15 @@ int main(int argc, char *argv[]) {
|
|||||||
die("another window manager is already running");
|
die("another window manager is already running");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loop = g_main_loop_new(NULL, FALSE);
|
||||||
|
|
||||||
setup_xcb();
|
setup_xcb();
|
||||||
init_monitors();
|
init_monitors();
|
||||||
init_color_set();
|
init_color_set();
|
||||||
init_monitor_bar();
|
init_monitor_bar();
|
||||||
change_window_cursor(get_root_window(), cursor_normal);
|
change_window_cursor(get_root_window(), cursor_normal);
|
||||||
init_wallpaper();
|
init_wallpaper();
|
||||||
|
init_status(g_main_loop_get_context(loop), update_status);
|
||||||
|
|
||||||
draw_all_monitor_bars();
|
draw_all_monitor_bars();
|
||||||
|
|
||||||
@@ -1192,7 +1213,6 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
init_xcb_event_loop();
|
init_xcb_event_loop();
|
||||||
|
|
||||||
loop = g_main_loop_new(NULL, FALSE);
|
|
||||||
g_main_loop_run(loop);
|
g_main_loop_run(loop);
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|||||||
@@ -11,9 +11,11 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
static PangoContext *context = NULL;
|
static PangoContext *context = NULL;
|
||||||
static PangoAttrList *attr_list = 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,
|
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;
|
cairo_t *cr = monitor->cr;
|
||||||
int32_t x = 0;
|
int32_t x = 0;
|
||||||
uint32_t width = 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);
|
draw_text(cr, text, color, x, 0, width, height, true);
|
||||||
x += width;
|
x += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 绘制状态栏信息 */
|
||||||
|
int32_t status_start =
|
||||||
|
draw_status(monitor, status, color_set, height, status_x_padding, x);
|
||||||
|
|
||||||
/* 绘制 client name 列表 */
|
/* 绘制 client name 列表 */
|
||||||
if (visible_client_count) {
|
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;
|
width = box_width - 2 * client_name_x_padding;
|
||||||
for (client_t *c = monitor->clients; c; c = c->next) {
|
for (client_t *c = monitor->clients; c; c = c->next) {
|
||||||
if (!CLIENT_VISIBLE(c)) continue;
|
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