Compare commits
4 Commits
194816ec2d
...
c5e79661d1
| Author | SHA1 | Date | |
|---|---|---|---|
|
c5e79661d1
|
|||
|
8eeeff5f3b
|
|||
|
364a00fb7c
|
|||
|
abc1979c04
|
@@ -40,6 +40,7 @@ add_executable(${APP_NAME}
|
|||||||
${SOURCE_DIR}/xkb.c
|
${SOURCE_DIR}/xkb.c
|
||||||
${SOURCE_DIR}/atoms.c
|
${SOURCE_DIR}/atoms.c
|
||||||
${SOURCE_DIR}/layout.c
|
${SOURCE_DIR}/layout.c
|
||||||
|
${SOURCE_DIR}/xres.c
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
@@ -57,6 +58,7 @@ pkg_check_modules(deps REQUIRED
|
|||||||
xcb-xfixes
|
xcb-xfixes
|
||||||
xcb-xinerama
|
xcb-xinerama
|
||||||
xcb-xkb
|
xcb-xkb
|
||||||
|
xcb-xrm
|
||||||
xkbcommon-x11
|
xkbcommon-x11
|
||||||
|
|
||||||
cairo
|
cairo
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -19,10 +19,10 @@ clean:
|
|||||||
${RM} -r $(BUILD_DIR)
|
${RM} -r $(BUILD_DIR)
|
||||||
|
|
||||||
run:
|
run:
|
||||||
-$(shell Xephyr :3 -screen 1920x1080 &)
|
-$(shell Xephyr :3 -screen 1920x1080 -dpi `xrdb -get Xft.dpi` &)
|
||||||
|
|
||||||
run_multiple_screen:
|
run_multiple_screen:
|
||||||
-$(shell Xephyr :3 -screen 1920x1080 -screen 1920x1080 +xinerama &)
|
-$(shell Xephyr :3 -screen 1920x1080 -screen 1920x1080 +xinerama -dpi `xrdb -get Xft.dpi` &)
|
||||||
|
|
||||||
wm: $(TARGET_NAME)
|
wm: $(TARGET_NAME)
|
||||||
DISPLAY=:3 $(TARGET)
|
DISPLAY=:3 $(TARGET)
|
||||||
|
|||||||
39
src/client.c
39
src/client.c
@@ -8,6 +8,7 @@
|
|||||||
#include <xcb/xproto.h>
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
#include "atoms-extern.h"
|
#include "atoms-extern.h"
|
||||||
|
#include "base.h"
|
||||||
#include "monitor.h"
|
#include "monitor.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
@@ -64,6 +65,7 @@ static void client_add_to_tag(client_t *client, tag_t *tag) {
|
|||||||
task->client = client;
|
task->client = client;
|
||||||
task->next = tag->task_list;
|
task->next = tag->task_list;
|
||||||
tag->task_list = task;
|
tag->task_list = task;
|
||||||
|
task->geometry = client->geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void client_remove_from_tag(client_t *client, tag_t *tag) {
|
static void client_remove_from_tag(client_t *client, tag_t *tag) {
|
||||||
@@ -110,14 +112,32 @@ static inline void client_update_names(client_t *c) {
|
|||||||
xwindow_get_text_property(c->window, _NET_WM_ICON_NAME, &c->net_icon_name);
|
xwindow_get_text_property(c->window, _NET_WM_ICON_NAME, &c->net_icon_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void client_init_geometry(client_t *c) {
|
||||||
|
area_t workarea = c->monitor->workarea;
|
||||||
|
if (c->geometry.x + client_width(c) > workarea.x + workarea.width) {
|
||||||
|
c->geometry.x = workarea.x + workarea.width - client_width(c);
|
||||||
|
}
|
||||||
|
if (c->geometry.y + client_height(c) > workarea.y + workarea.height) {
|
||||||
|
c->geometry.y = workarea.y + workarea.height - client_height(c);
|
||||||
|
}
|
||||||
|
c->geometry.x = MAX(c->geometry.x, workarea.x);
|
||||||
|
c->geometry.y = MAX(c->geometry.y, workarea.y);
|
||||||
|
|
||||||
|
client_move_to(c, c->geometry.x, c->geometry.y);
|
||||||
|
client_resize(c, c->geometry.width, c->geometry.height);
|
||||||
|
client_change_border_color(c, &wm.color_set.active_border_color);
|
||||||
|
client_change_border_width(c, wm.border_width);
|
||||||
|
}
|
||||||
|
|
||||||
void client_manage(xcb_window_t window,
|
void client_manage(xcb_window_t window,
|
||||||
xcb_get_geometry_reply_t *geometry_reply) {
|
xcb_get_geometry_reply_t *geometry_reply) {
|
||||||
client_t *c = p_new(client_t, 1);
|
client_t *c = p_new(client_t, 1);
|
||||||
c->window = window;
|
c->window = window;
|
||||||
|
c->geometry.x = geometry_reply->x;
|
||||||
client_move_to(c, geometry_reply->x, geometry_reply->y);
|
c->geometry.y = geometry_reply->y;
|
||||||
client_resize(c, geometry_reply->width, geometry_reply->height);
|
c->geometry.width = geometry_reply->width;
|
||||||
client_change_border_width(c, geometry_reply->border_width);
|
c->geometry.height = geometry_reply->height;
|
||||||
|
c->border_width = geometry_reply->border_width;
|
||||||
|
|
||||||
{
|
{
|
||||||
const xcb_params_cw_t params = {
|
const xcb_params_cw_t params = {
|
||||||
@@ -161,6 +181,7 @@ void client_manage(xcb_window_t window,
|
|||||||
c->tags = c->monitor->selected_tag->mask;
|
c->tags = c->monitor->selected_tag->mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_init_geometry(c);
|
||||||
client_tags_apply(c);
|
client_tags_apply(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,12 +232,22 @@ void client_resize(client_t *client, uint16_t width, uint16_t height) {
|
|||||||
width, height);
|
width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void client_change_border_color(client_t *client, color_t *color) {
|
||||||
|
uint16_t mask = XCB_CW_BORDER_PIXEL;
|
||||||
|
const xcb_params_cw_t params = {.border_pixel = color->argb};
|
||||||
|
xcb_aux_change_window_attributes(wm.xcb_conn, client->window, mask, ¶ms);
|
||||||
|
client->border_color = color;
|
||||||
|
logger("== client 0x%x border color: RGBA(#%08x), ARGB(#%08x)\n",
|
||||||
|
client->window, color->rgba, color->argb);
|
||||||
|
}
|
||||||
|
|
||||||
void client_change_border_width(client_t *client, uint16_t border_width) {
|
void client_change_border_width(client_t *client, uint16_t border_width) {
|
||||||
if (client->border_width == border_width) return;
|
if (client->border_width == border_width) return;
|
||||||
uint16_t mask = XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
uint16_t mask = XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
||||||
const xcb_params_configure_window_t params = {.border_width = border_width};
|
const xcb_params_configure_window_t params = {.border_width = border_width};
|
||||||
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, ¶ms);
|
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, ¶ms);
|
||||||
client->border_width = border_width;
|
client->border_width = border_width;
|
||||||
|
logger("== client 0x%x border width: %u\n", client->window, border_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
void client_update_wm_hints(client_t *client) {
|
void client_update_wm_hints(client_t *client) {
|
||||||
|
|||||||
@@ -51,3 +51,10 @@ void client_focus(client_t *client);
|
|||||||
void client_stack_raise(client_t *client);
|
void client_stack_raise(client_t *client);
|
||||||
|
|
||||||
bool client_is_visible(client_t *client);
|
bool client_is_visible(client_t *client);
|
||||||
|
|
||||||
|
static inline uint16_t client_width(client_t *c) {
|
||||||
|
return c->geometry.width + c->border_width * 2;
|
||||||
|
}
|
||||||
|
static inline uint16_t client_height(client_t *c) {
|
||||||
|
return c->geometry.height + c->border_width * 2;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ static const int default_dpi = 144;
|
|||||||
static const char *const tags[] = {
|
static const char *const tags[] = {
|
||||||
"1", "2", "3", "4", "5", "6", "7", "8", "9", nullptr,
|
"1", "2", "3", "4", "5", "6", "7", "8", "9", nullptr,
|
||||||
};
|
};
|
||||||
|
static const uint16_t border_width = 1;
|
||||||
static const uint16_t bar_y_padding = 1;
|
static const uint16_t bar_y_padding = 1;
|
||||||
static const uint16_t tag_x_padding = 10;
|
static const uint16_t tag_x_padding = 10;
|
||||||
|
|
||||||
@@ -22,6 +23,8 @@ static const char *const tag_bg = "#222222";
|
|||||||
static const char *const active_tag_bg = "#005577";
|
static const char *const active_tag_bg = "#005577";
|
||||||
static const char *const tag_color = "#bbbbbb";
|
static const char *const tag_color = "#bbbbbb";
|
||||||
static const char *const active_tag_color = "#eeeeee";
|
static const char *const active_tag_color = "#eeeeee";
|
||||||
|
static const char *const border_color = "#444444";
|
||||||
|
static const char *const active_border_color = "#005577";
|
||||||
|
|
||||||
static const button_t button_list[] = {
|
static const button_t button_list[] = {
|
||||||
{click_tag, modifier_none, button_left, select_tag_of_current_monitor, {0}},
|
{click_tag, modifier_none, button_left, select_tag_of_current_monitor, {0}},
|
||||||
|
|||||||
16
src/layout.c
16
src/layout.c
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "base.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
@@ -10,10 +11,17 @@ void monocle(tag_t *tag) {
|
|||||||
logger("== monocle tag: %s start ==\n", tag->name);
|
logger("== monocle tag: %s start ==\n", tag->name);
|
||||||
for (task_in_tag_t *task = tag->task_list; task; task = task->next) {
|
for (task_in_tag_t *task = tag->task_list; task; task = task->next) {
|
||||||
if (!client_need_layout(task->client)) continue;
|
if (!client_need_layout(task->client)) continue;
|
||||||
task->geometry = task->client->monitor->workarea;
|
|
||||||
logger("== window 0x%x: x: %d, y: %d, width: %u, height: %u\n",
|
uint16_t border_width = task->client->border_width;
|
||||||
task->client->window, task->geometry.x, task->geometry.y,
|
area_t workarea = task->client->monitor->workarea;
|
||||||
task->geometry.width, task->geometry.height);
|
task->geometry.x = workarea.x;
|
||||||
|
task->geometry.y = workarea.y;
|
||||||
|
task->geometry.width = workarea.width - border_width * 2;
|
||||||
|
task->geometry.height = workarea.height - border_width * 2;
|
||||||
|
logger(
|
||||||
|
"== window 0x%x: x: %d, y: %d, width: %u, height: %u, border width: %u\n",
|
||||||
|
task->client->window, task->geometry.x, task->geometry.y,
|
||||||
|
task->geometry.width, task->geometry.height, border_width);
|
||||||
}
|
}
|
||||||
logger("== monocle tag: %s end ==\n", tag->name);
|
logger("== monocle tag: %s end ==\n", tag->name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,8 +255,8 @@ void monitor_arrange(monitor_t *monitor) {
|
|||||||
if (task) {
|
if (task) {
|
||||||
client_apply_task_geometry(c, task);
|
client_apply_task_geometry(c, task);
|
||||||
} else {
|
} else {
|
||||||
int16_t x = -c->geometry.width;
|
int16_t x = -client_width(c);
|
||||||
int16_t y = -c->geometry.height;
|
int16_t y = -client_height(c);
|
||||||
if (x != c->geometry.x || y != c->geometry.y) {
|
if (x != c->geometry.x || y != c->geometry.y) {
|
||||||
client_move_to(c, x, y);
|
client_move_to(c, x, y);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,11 @@ typedef struct color_set_t {
|
|||||||
color_t active_tag_bg;
|
color_t active_tag_bg;
|
||||||
color_t tag_color;
|
color_t tag_color;
|
||||||
color_t active_tag_color;
|
color_t active_tag_color;
|
||||||
|
color_t border_color;
|
||||||
|
color_t active_border_color;
|
||||||
} color_set_t;
|
} color_set_t;
|
||||||
|
|
||||||
typedef struct padding_t {
|
typedef struct padding_t {
|
||||||
|
uint16_t bar_y;
|
||||||
uint16_t tag_x;
|
uint16_t tag_x;
|
||||||
} padding_t;
|
} padding_t;
|
||||||
|
|||||||
@@ -87,3 +87,8 @@ static inline void logger(const char *format, ...) {
|
|||||||
#else
|
#else
|
||||||
#define logger(format, ...) printf(format, ##__VA_ARGS__)
|
#define logger(format, ...) printf(format, ##__VA_ARGS__)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef MAX
|
||||||
|
#undef MAX
|
||||||
|
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
|||||||
38
src/wm.c
38
src/wm.c
@@ -31,6 +31,7 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "xcursor.h"
|
#include "xcursor.h"
|
||||||
#include "xkb.h"
|
#include "xkb.h"
|
||||||
|
#include "xres.h"
|
||||||
#include "xwindow.h"
|
#include "xwindow.h"
|
||||||
|
|
||||||
static void wm_setup_signal(void);
|
static void wm_setup_signal(void);
|
||||||
@@ -40,6 +41,7 @@ static void wm_detect_monitor(void);
|
|||||||
static void wm_scan_clients(void);
|
static void wm_scan_clients(void);
|
||||||
static void wm_clean(void);
|
static void wm_clean(void);
|
||||||
static void wm_setup(void);
|
static void wm_setup(void);
|
||||||
|
static void wm_get_xres_config(void);
|
||||||
static void wm_init_color_set(void);
|
static void wm_init_color_set(void);
|
||||||
static void wm_setup_keybindings(void);
|
static void wm_setup_keybindings(void);
|
||||||
|
|
||||||
@@ -277,16 +279,18 @@ void wm_clean(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void wm_setup(void) {
|
static void wm_setup(void) {
|
||||||
wm.padding.tag_x = tag_x_padding;
|
|
||||||
wm.layout_list = layout_list;
|
wm.layout_list = layout_list;
|
||||||
wm.layout_count = (uint16_t)countof(layout_list);
|
wm.layout_count = (uint16_t)countof(layout_list);
|
||||||
|
|
||||||
|
wm_get_xres_config();
|
||||||
wm_init_color_set();
|
wm_init_color_set();
|
||||||
|
|
||||||
int font_height = text_init_pango_layout(font_family, font_size, default_dpi);
|
{
|
||||||
wm.bar_height = (uint16_t)font_height + 2 * bar_y_padding;
|
int font_height =
|
||||||
|
text_init_pango_layout(wm.font_family, wm.font_size, wm.dpi);
|
||||||
logger("font height: %d, bar height: %u\n", font_height, wm.bar_height);
|
wm.bar_height = (uint16_t)font_height + 2 * wm.padding.bar_y;
|
||||||
|
logger("font height: %d, bar height: %u\n", font_height, wm.bar_height);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t tag_count = 0;
|
uint32_t tag_count = 0;
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
||||||
@@ -330,12 +334,36 @@ static void wm_setup(void) {
|
|||||||
xcb_flush(wm.xcb_conn);
|
xcb_flush(wm.xcb_conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wm_get_xres_config(void) {
|
||||||
|
xres_init_xrm_db();
|
||||||
|
|
||||||
|
wm.dpi = default_dpi;
|
||||||
|
wm.font_size = font_size;
|
||||||
|
xres_get_uint32("Xft.dpi", (uint32_t *)&wm.dpi);
|
||||||
|
xres_get_uint32(APP_NAME ".font_size", (uint32_t *)&wm.font_size);
|
||||||
|
xres_get_string(APP_NAME ".font_family", &wm.font_family, font_family);
|
||||||
|
|
||||||
|
wm.border_width = border_width;
|
||||||
|
xres_get_uint32(APP_NAME ".border_width", (uint32_t *)&wm.border_width);
|
||||||
|
|
||||||
|
wm.padding.bar_y = bar_y_padding;
|
||||||
|
wm.padding.tag_x = tag_x_padding;
|
||||||
|
#define NAME APP_NAME ".padding."
|
||||||
|
xres_get_uint32(NAME "bar_y", (uint32_t *)&wm.padding.bar_y);
|
||||||
|
xres_get_uint32(NAME "tag_x", (uint32_t *)&wm.padding.tag_x);
|
||||||
|
#undef NAME
|
||||||
|
|
||||||
|
xres_clean();
|
||||||
|
}
|
||||||
|
|
||||||
void wm_init_color_set(void) {
|
void wm_init_color_set(void) {
|
||||||
color_parse(bar_bg, &wm.color_set.bar_bg);
|
color_parse(bar_bg, &wm.color_set.bar_bg);
|
||||||
color_parse(tag_bg, &wm.color_set.tag_bg);
|
color_parse(tag_bg, &wm.color_set.tag_bg);
|
||||||
color_parse(active_tag_bg, &wm.color_set.active_tag_bg);
|
color_parse(active_tag_bg, &wm.color_set.active_tag_bg);
|
||||||
color_parse(tag_color, &wm.color_set.tag_color);
|
color_parse(tag_color, &wm.color_set.tag_color);
|
||||||
color_parse(active_tag_color, &wm.color_set.active_tag_color);
|
color_parse(active_tag_color, &wm.color_set.active_tag_color);
|
||||||
|
color_parse(border_color, &wm.color_set.border_color);
|
||||||
|
color_parse(active_border_color, &wm.color_set.active_border_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wm_setup_keybindings(void) {
|
void wm_setup_keybindings(void) {
|
||||||
|
|||||||
7
src/wm.h
7
src/wm.h
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <xcb/xcb_keysyms.h>
|
#include <xcb/xcb_keysyms.h>
|
||||||
|
#include <xcb/xcb_xrm.h>
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
typedef struct wm_t {
|
typedef struct wm_t {
|
||||||
padding_t padding;
|
padding_t padding;
|
||||||
|
uint16_t border_width;
|
||||||
uint16_t bar_height;
|
uint16_t bar_height;
|
||||||
uint16_t layout_count;
|
uint16_t layout_count;
|
||||||
|
|
||||||
@@ -21,8 +23,13 @@ typedef struct wm_t {
|
|||||||
monitor_t *current_monitor;
|
monitor_t *current_monitor;
|
||||||
const layout_t *layout_list;
|
const layout_t *layout_list;
|
||||||
|
|
||||||
|
char *font_family;
|
||||||
|
uint32_t font_size;
|
||||||
|
uint32_t dpi;
|
||||||
|
|
||||||
xcb_connection_t *xcb_conn;
|
xcb_connection_t *xcb_conn;
|
||||||
xcb_screen_t *screen;
|
xcb_screen_t *screen;
|
||||||
|
xcb_xrm_database_t *xrm;
|
||||||
xcb_key_symbols_t *key_symbols;
|
xcb_key_symbols_t *key_symbols;
|
||||||
xcb_window_t wm_check_window;
|
xcb_window_t wm_check_window;
|
||||||
int default_screen;
|
int default_screen;
|
||||||
|
|||||||
52
src/xres.c
Normal file
52
src/xres.c
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
#include "xres.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <xcb/xcb_xrm.h>
|
||||||
|
|
||||||
|
#include "wm.h"
|
||||||
|
|
||||||
|
void xres_init_xrm_db(void) {
|
||||||
|
if (wm.xrm) return;
|
||||||
|
wm.xrm = xcb_xrm_database_from_default(wm.xcb_conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xres_clean(void) {
|
||||||
|
if (!wm.xrm) return;
|
||||||
|
|
||||||
|
xcb_xrm_database_free(wm.xrm);
|
||||||
|
wm.xrm = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 Xresources 文件中配置的 long 类型配置并将其转化为 uint32_t 类型
|
||||||
|
* @param name Xresources 文件中的配置名
|
||||||
|
* @param value 获取到的结果存放的指针
|
||||||
|
* @returns 配置获取是否成功,成功返回 true 失败返回 false
|
||||||
|
*/
|
||||||
|
bool xres_get_uint32(const char *name, uint32_t *value) {
|
||||||
|
if (!wm.xrm) return false;
|
||||||
|
|
||||||
|
long temp_value = 0;
|
||||||
|
bool success =
|
||||||
|
xcb_xrm_resource_get_long(wm.xrm, name, nullptr, &temp_value) == 0;
|
||||||
|
if (success) *value = (uint32_t)temp_value;
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取 Xresources 文件中的字符串配置
|
||||||
|
* @param name Xresources 文件中的配置名
|
||||||
|
* @param value 存放读取到的字符串存储的指针的指针,使用完后记得使用
|
||||||
|
* free(*value) 释放字符串对应的内存
|
||||||
|
* @param fallback 若 Xresources 解析失败,则使用这个默认值
|
||||||
|
*/
|
||||||
|
void xres_get_string(const char *name, char **value, const char *fallback) {
|
||||||
|
if (!wm.xrm) return;
|
||||||
|
|
||||||
|
if (!xcb_xrm_resource_get_string(wm.xrm, name, nullptr, value)) return;
|
||||||
|
|
||||||
|
*value = strdup(fallback);
|
||||||
|
}
|
||||||
8
src/xres.h
Normal file
8
src/xres.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void xres_init_xrm_db(void);
|
||||||
|
void xres_clean(void);
|
||||||
|
bool xres_get_uint32(const char *name, uint32_t *value);
|
||||||
|
void xres_get_string(const char *name, char **value, const char *fallback);
|
||||||
Reference in New Issue
Block a user