refactor: 移除新架构用不到的老模块
This commit is contained in:
@@ -1,16 +0,0 @@
|
|||||||
# -*- mode: yaml; -*-
|
|
||||||
|
|
||||||
---
|
|
||||||
BasedOnStyle: Google
|
|
||||||
IndentWidth: 2
|
|
||||||
ConstructorInitializerIndentWidth: 2
|
|
||||||
ContinuationIndentWidth: 2
|
|
||||||
---
|
|
||||||
Language: C
|
|
||||||
DerivePointerAlignment: false
|
|
||||||
PointerAlignment: Right
|
|
||||||
---
|
|
||||||
Language: Cpp
|
|
||||||
DerivePointerAlignment: false
|
|
||||||
PointerAlignment: Right
|
|
||||||
---
|
|
||||||
158
src/action.c
158
src/action.c
@@ -1,158 +0,0 @@
|
|||||||
#include "action.h"
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "audio.h"
|
|
||||||
#include "client.h"
|
|
||||||
#include "monitor.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "wm.h"
|
|
||||||
#include "xcursor.h"
|
|
||||||
|
|
||||||
void focus_client_in_same_tag(const user_action_arg_t *arg) {
|
|
||||||
bool next = arg->b;
|
|
||||||
tag_t *tag = wm.current_monitor->selected_tag;
|
|
||||||
|
|
||||||
task_in_tag_t *task = nullptr;
|
|
||||||
if (next) {
|
|
||||||
task = client_get_next_task_in_tag(wm.client_focused, tag);
|
|
||||||
} else {
|
|
||||||
task = client_get_previous_task_in_tag(wm.client_focused, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!task) return;
|
|
||||||
|
|
||||||
client_stack_raise(task->client);
|
|
||||||
client_focus(task->client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void select_tag_of_current_monitor(const user_action_arg_t *arg) {
|
|
||||||
monitor_select_tag(wm.current_monitor, arg->ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_client_to_tag(const user_action_arg_t *arg) {
|
|
||||||
if (wm.client_focused) client_send_to_tag(wm.client_focused, arg->ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_client_to_next_monitor(const user_action_arg_t *arg) {
|
|
||||||
if (!wm.client_focused) return;
|
|
||||||
|
|
||||||
monitor_t *next_monitor = wm_get_next_monitor(wm.client_focused->monitor);
|
|
||||||
if (wm.client_focused->monitor == next_monitor) return;
|
|
||||||
|
|
||||||
client_send_to_monitor(wm.client_focused, next_monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void focus_next_monitor(const user_action_arg_t *arg) {
|
|
||||||
monitor_t *next_monitor = wm_get_next_monitor(wm.current_monitor);
|
|
||||||
if (next_monitor == wm.current_monitor) return;
|
|
||||||
|
|
||||||
wm_set_current_monitor(next_monitor, true);
|
|
||||||
if (wm.current_monitor->selected_tag->task_list) {
|
|
||||||
monitor_deal_focus(wm.current_monitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void spawn(const user_action_arg_t *arg) {
|
|
||||||
const char *cmd = (const char *)arg->ptr;
|
|
||||||
if (cmd == nullptr || strlen(cmd) == 0) return;
|
|
||||||
|
|
||||||
g_spawn_command_line_async((const char *)arg->ptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void quit(const user_action_arg_t *arg) {
|
|
||||||
const bool restart = arg->b;
|
|
||||||
if (restart) {
|
|
||||||
wm_restart();
|
|
||||||
} else {
|
|
||||||
wm_quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void raise_or_run(const user_action_arg_t *arg) {
|
|
||||||
const char *class = ((const char **)arg->ptr)[0];
|
|
||||||
client_t *client = client_get_next_by_class(wm.client_focused, class);
|
|
||||||
|
|
||||||
if (client) {
|
|
||||||
bool monitor_changed = client->monitor != wm.current_monitor;
|
|
||||||
bool tag_changed = client->tags != client->monitor->selected_tag->mask;
|
|
||||||
if (monitor_changed || tag_changed) {
|
|
||||||
point_t point = monitor_changed
|
|
||||||
? monitor_get_restore_cursor_point(client->monitor)
|
|
||||||
: xcursor_query_pointer_position();
|
|
||||||
wm_ignore_enter_notify_at_point(point);
|
|
||||||
}
|
|
||||||
wm_set_current_monitor(client->monitor, true);
|
|
||||||
monitor_select_tag(client->monitor, client->tags);
|
|
||||||
client_stack_raise(client);
|
|
||||||
client_focus(client);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *command = ((const char **)arg->ptr)[1];
|
|
||||||
user_action_arg_t arguments = {.ptr = command};
|
|
||||||
spawn(&arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggle_mute(const user_action_arg_t *arg) {
|
|
||||||
if (wm.status->pulse_context) toggle_pulse_mute(wm.status->pulse_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void change_volume(const user_action_arg_t *arg) {
|
|
||||||
if (wm.status->pulse_context) {
|
|
||||||
change_pulse_volume(wm.status->pulse_context, arg->i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggle_client_floating(const user_action_arg_t *arg) {
|
|
||||||
if (!wm.client_focused) return;
|
|
||||||
|
|
||||||
client_set_floating(wm.client_focused, !wm.client_focused->floating);
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggle_client_fullscreen(const user_action_arg_t *arg) {
|
|
||||||
if (!wm.client_focused) return;
|
|
||||||
|
|
||||||
client_set_fullscreen(wm.client_focused, !wm.client_focused->fullscreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggle_client_maximize(const user_action_arg_t *arg) {
|
|
||||||
if (!wm.client_focused) return;
|
|
||||||
|
|
||||||
client_set_maximize(wm.client_focused, !wm.client_focused->maximize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggle_client_minimize(const user_action_arg_t *arg) {
|
|
||||||
client_t *client = wm.client_focused;
|
|
||||||
if (arg->ptr) client = (client_t *)arg->ptr;
|
|
||||||
if (!client) return;
|
|
||||||
|
|
||||||
client_set_minimize(client, !client->minimize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void kill_client(const user_action_arg_t *arg) {
|
|
||||||
if (wm.client_focused) {
|
|
||||||
client_kill(wm.client_focused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据 client 的当前状态切换 client 下一状态
|
|
||||||
* @description 如果当前 client 被遮挡了或者最小化了,则显示出来,否则最小化
|
|
||||||
*/
|
|
||||||
void change_client_state_dwim(const user_action_arg_t *arg) {
|
|
||||||
client_t *client = (client_t *)arg->ptr;
|
|
||||||
if (!client) return;
|
|
||||||
|
|
||||||
if (client != wm.client_stack_list &&
|
|
||||||
client_get_obscured_state(client, wm.client_stack_list) !=
|
|
||||||
obscured_none) {
|
|
||||||
client_stack_raise(client);
|
|
||||||
client_focus(client);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_set_minimize(client, !client->minimize);
|
|
||||||
if (!client->minimize) client_stack_raise(client);
|
|
||||||
}
|
|
||||||
67
src/action.h
67
src/action.h
@@ -1,67 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
typedef union user_action_arg_t {
|
|
||||||
bool b;
|
|
||||||
int32_t i;
|
|
||||||
uint32_t ui;
|
|
||||||
const void *ptr;
|
|
||||||
} user_action_arg_t;
|
|
||||||
|
|
||||||
typedef enum click_area_t {
|
|
||||||
click_none,
|
|
||||||
click_tag,
|
|
||||||
click_client_name,
|
|
||||||
} click_area_t;
|
|
||||||
|
|
||||||
typedef enum modifier_t {
|
|
||||||
modifier_none = 0,
|
|
||||||
modifier_shift = XCB_MOD_MASK_SHIFT,
|
|
||||||
modifier_control = XCB_MOD_MASK_CONTROL,
|
|
||||||
modifier_alt = XCB_MOD_MASK_1,
|
|
||||||
modifier_super = XCB_MOD_MASK_4,
|
|
||||||
modifier_any = XCB_MOD_MASK_ANY,
|
|
||||||
} modifier_t;
|
|
||||||
|
|
||||||
typedef uint16_t modifier_value_t;
|
|
||||||
|
|
||||||
typedef enum button_index_t {
|
|
||||||
button_any = XCB_BUTTON_INDEX_ANY,
|
|
||||||
button_left = XCB_BUTTON_INDEX_1,
|
|
||||||
button_right = XCB_BUTTON_INDEX_2,
|
|
||||||
button_middle = XCB_BUTTON_INDEX_3,
|
|
||||||
} button_index_t;
|
|
||||||
|
|
||||||
typedef struct button_t {
|
|
||||||
click_area_t click_area : 16;
|
|
||||||
modifier_value_t modifiers;
|
|
||||||
button_index_t button;
|
|
||||||
void (*func)(const user_action_arg_t *arg);
|
|
||||||
const user_action_arg_t arg;
|
|
||||||
} button_t;
|
|
||||||
|
|
||||||
typedef struct keyboard_t {
|
|
||||||
modifier_value_t modifiers;
|
|
||||||
xcb_keysym_t keysym;
|
|
||||||
void (*func)(const user_action_arg_t *arg);
|
|
||||||
const user_action_arg_t arg;
|
|
||||||
} keyboard_t;
|
|
||||||
|
|
||||||
void focus_client_in_same_tag(const user_action_arg_t *arg);
|
|
||||||
void select_tag_of_current_monitor(const user_action_arg_t *arg);
|
|
||||||
void send_client_to_tag(const user_action_arg_t *arg);
|
|
||||||
void send_client_to_next_monitor(const user_action_arg_t *arg);
|
|
||||||
void focus_next_monitor(const user_action_arg_t *arg);
|
|
||||||
void spawn(const user_action_arg_t *arg);
|
|
||||||
void quit(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);
|
|
||||||
void toggle_client_floating(const user_action_arg_t *arg);
|
|
||||||
void toggle_client_fullscreen(const user_action_arg_t *arg);
|
|
||||||
void toggle_client_maximize(const user_action_arg_t *arg);
|
|
||||||
void toggle_client_minimize(const user_action_arg_t *arg);
|
|
||||||
void kill_client(const user_action_arg_t *arg);
|
|
||||||
void change_client_state_dwim(const user_action_arg_t *arg);
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
_XKB_RULES_NAMES
|
|
||||||
|
|
||||||
_XROOTPMAP_ID
|
|
||||||
ESETROOT_PMAP_ID
|
|
||||||
|
|
||||||
WM_CHANGE_STATE
|
|
||||||
WM_DELETE_WINDOW
|
|
||||||
WM_PROTOCOLS
|
|
||||||
WM_TAKE_FOCUS
|
|
||||||
WM_NAME
|
|
||||||
WM_WINDOW_ROLE
|
|
||||||
|
|
||||||
UTF8_STRING
|
|
||||||
COMPOUND_TEXT
|
|
||||||
MANAGER
|
|
||||||
_XEMBED
|
|
||||||
_XEMBED_INFO
|
|
||||||
|
|
||||||
_NET_ACTIVE_WINDOW
|
|
||||||
_NET_CLIENT_LIST
|
|
||||||
_NET_SYSTEM_TRAY_COLORS
|
|
||||||
_NET_SYSTEM_TRAY_OPCODE
|
|
||||||
_NET_SYSTEM_TRAY_ORIENTATION
|
|
||||||
_NET_WM_DESKTOP
|
|
||||||
_NET_WM_NAME
|
|
||||||
_NET_WM_STATE
|
|
||||||
_NET_WM_STATE_FULLSCREEN
|
|
||||||
_NET_WM_STATE_MAXIMIZED_HORZ
|
|
||||||
_NET_WM_STATE_MAXIMIZED_VERT
|
|
||||||
_NET_WM_STATE_SKIP_TASKBAR
|
|
||||||
_NET_WM_WINDOW_TYPE
|
|
||||||
_NET_WM_WINDOW_TYPE_DIALOG
|
|
||||||
_NET_SUPPORTED
|
|
||||||
_NET_SUPPORTING_WM_CHECK
|
|
||||||
39
src/atoms.c
39
src/atoms.c
@@ -1,39 +0,0 @@
|
|||||||
#include "atoms.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "atoms-intern.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wm.h"
|
|
||||||
|
|
||||||
void atoms_init(xcb_connection_t *conn) {
|
|
||||||
xcb_intern_atom_cookie_t cookies[countof(ATOM_LIST)];
|
|
||||||
for (int i = 0; i < countof(ATOM_LIST); i++) {
|
|
||||||
atom_item_t atom = ATOM_LIST[i];
|
|
||||||
cookies[i] = xcb_intern_atom_unchecked(conn, false, atom.len, atom.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
atom_item_t *atom = nullptr;
|
|
||||||
uint32_t supported_length = 0;
|
|
||||||
xcb_atom_t supported_list[countof(ATOM_LIST)] = {XCB_ATOM_NONE};
|
|
||||||
|
|
||||||
xcb_intern_atom_reply_t *reply = nullptr;
|
|
||||||
for (int i = 0; i < countof(ATOM_LIST); i++) {
|
|
||||||
reply = xcb_intern_atom_reply(conn, cookies[i], nullptr);
|
|
||||||
if (reply) {
|
|
||||||
atom = &ATOM_LIST[i];
|
|
||||||
*atom->atom = reply->atom;
|
|
||||||
p_delete(&reply);
|
|
||||||
|
|
||||||
if (strstr(atom->name, "_NET_") == atom->name) {
|
|
||||||
supported_list[supported_length++] = *atom->atom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, wm.screen->root,
|
|
||||||
_NET_SUPPORTED, XCB_ATOM_ATOM, 32, supported_length,
|
|
||||||
supported_list);
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
|
|
||||||
void atoms_init(xcb_connection_t *conn);
|
|
||||||
182
src/audio.c
182
src/audio.c
@@ -1,182 +0,0 @@
|
|||||||
#include "audio.h"
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <pulse/context.h>
|
|
||||||
#include <pulse/def.h>
|
|
||||||
#include <pulse/glib-mainloop.h>
|
|
||||||
#include <pulse/introspect.h>
|
|
||||||
#include <pulse/mainloop-api.h>
|
|
||||||
#include <pulse/proplist.h>
|
|
||||||
#include <pulse/subscribe.h>
|
|
||||||
#include <pulse/volume.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "app.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
struct pulse_context_t {
|
|
||||||
GMainContext *g_context;
|
|
||||||
pulse_notify_t callback;
|
|
||||||
pa_glib_mainloop *loop;
|
|
||||||
pa_context *context;
|
|
||||||
pa_cvolume volume;
|
|
||||||
pulse_t pulse;
|
|
||||||
uint32_t index;
|
|
||||||
bool inited;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void clean_pulse_context(pulse_context_t *context, bool full_cleanup);
|
|
||||||
static void init_pulse_context(pulse_context_t *context, bool create_loop);
|
|
||||||
static void state_callback(pa_context *context, void *userdata);
|
|
||||||
static void subscribe_callback(pa_context *context,
|
|
||||||
pa_subscription_event_type_t t, uint32_t index,
|
|
||||||
void *userdata);
|
|
||||||
static void server_info_callback(pa_context *context,
|
|
||||||
const pa_server_info *info, void *userdata);
|
|
||||||
static void sink_info_callback(pa_context *context, const pa_sink_info *info,
|
|
||||||
int eol, void *userdata);
|
|
||||||
static void parse_sink(const pa_sink_info *info, pulse_t *pulse);
|
|
||||||
|
|
||||||
pulse_context_t *init_pulse(GMainContext *context, pulse_notify_t callback) {
|
|
||||||
pulse_context_t *ctx = p_new(pulse_context_t, 1);
|
|
||||||
ctx->g_context = context;
|
|
||||||
ctx->callback = callback;
|
|
||||||
init_pulse_context(ctx, true);
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void change_pulse_volume(pulse_context_t *context, int step) {
|
|
||||||
if (!context->inited || !step || step > 100 || step < -100) return;
|
|
||||||
|
|
||||||
pa_cvolume vol = context->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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pa_context *ctx = context->context;
|
|
||||||
uint32_t index = context->index;
|
|
||||||
pa_context_set_sink_volume_by_index(ctx, index, &vol, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void toggle_pulse_mute(pulse_context_t *context) {
|
|
||||||
if (!context->inited) return;
|
|
||||||
|
|
||||||
pa_context *ctx = context->context;
|
|
||||||
uint32_t index = context->index;
|
|
||||||
bool mute = !context->pulse.mute;
|
|
||||||
pa_context_set_sink_mute_by_index(ctx, index, mute, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clean_pulse(pulse_context_t *context) {
|
|
||||||
clean_pulse_context(context, true);
|
|
||||||
p_delete(&context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clean_pulse_context(pulse_context_t *context, bool full_cleanup) {
|
|
||||||
memset(&context->pulse, 0, sizeof(context->pulse));
|
|
||||||
if (context->callback) context->callback(&context->pulse);
|
|
||||||
if (context->context) {
|
|
||||||
pa_context_set_state_callback(context->context, nullptr, nullptr);
|
|
||||||
pa_context_set_subscribe_callback(context->context, nullptr, nullptr);
|
|
||||||
pa_context_disconnect(context->context);
|
|
||||||
pa_context_unref(context->context);
|
|
||||||
context->context = nullptr;
|
|
||||||
}
|
|
||||||
context->inited = false;
|
|
||||||
if (full_cleanup && context->loop) {
|
|
||||||
pa_glib_mainloop_free(context->loop);
|
|
||||||
context->loop = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_pulse_context(pulse_context_t *context, bool create_loop) {
|
|
||||||
if (create_loop) {
|
|
||||||
if (context->loop) pa_glib_mainloop_free(context->loop);
|
|
||||||
context->loop = pa_glib_mainloop_new(context->g_context);
|
|
||||||
}
|
|
||||||
pa_mainloop_api *api = pa_glib_mainloop_get_api(context->loop);
|
|
||||||
context->context = pa_context_new(api, APP_NAME "-audio-status");
|
|
||||||
pa_context_connect(context->context, nullptr, PA_CONTEXT_NOFAIL, nullptr);
|
|
||||||
pa_context_set_state_callback(context->context, state_callback, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
void state_callback(pa_context *context, void *userdata) {
|
|
||||||
pulse_context_t *ctx = userdata;
|
|
||||||
|
|
||||||
pa_context_state_t state = pa_context_get_state(context);
|
|
||||||
if (PA_CONTEXT_IS_GOOD(state)) {
|
|
||||||
pa_context_set_subscribe_callback(context, subscribe_callback, ctx);
|
|
||||||
pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK, nullptr, nullptr);
|
|
||||||
pa_context_get_server_info(context, server_info_callback, ctx);
|
|
||||||
} else if (state == PA_CONTEXT_FAILED) {
|
|
||||||
clean_pulse_context(ctx, false);
|
|
||||||
init_pulse_context(ctx, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void subscribe_callback(pa_context *context, pa_subscription_event_type_t t,
|
|
||||||
uint32_t index, void *userdata) {
|
|
||||||
auto facility = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
|
|
||||||
if (facility != PA_SUBSCRIPTION_EVENT_SINK) return;
|
|
||||||
|
|
||||||
pa_subscription_event_type_t event = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
|
|
||||||
if (event == PA_SUBSCRIPTION_EVENT_CHANGE) {
|
|
||||||
pa_context_get_server_info(context, server_info_callback, userdata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void server_info_callback(pa_context *context, const pa_server_info *info,
|
|
||||||
void *userdata) {
|
|
||||||
pa_context_get_sink_info_by_name(context, info->default_sink_name,
|
|
||||||
sink_info_callback, userdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol,
|
|
||||||
void *userdata) {
|
|
||||||
if (eol != 0 || !info) return;
|
|
||||||
|
|
||||||
pulse_context_t *ctx = userdata;
|
|
||||||
ctx->inited = true;
|
|
||||||
ctx->volume = info->volume;
|
|
||||||
ctx->index = info->index;
|
|
||||||
parse_sink(info, &ctx->pulse);
|
|
||||||
if (ctx->callback) ctx->callback(&ctx->pulse);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int volume_to_percent(pa_volume_t volume) {
|
|
||||||
return (int)round(((double)volume) * 100 / PA_VOLUME_NORM);
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_sink(const pa_sink_info *info, pulse_t *pulse) {
|
|
||||||
size_t size = sizeof(pulse->device_name);
|
|
||||||
pulse->mute = info->mute;
|
|
||||||
pulse->volume_percent = volume_to_percent(pa_cvolume_avg(&info->volume));
|
|
||||||
strncpy(pulse->device_name, info->description, size);
|
|
||||||
|
|
||||||
const char *key = nullptr;
|
|
||||||
void *state = nullptr;
|
|
||||||
while ((key = pa_proplist_iterate(info->proplist, &state)) != nullptr) {
|
|
||||||
if (strcmp(key, "api.alsa.path") != 0 &&
|
|
||||||
strcmp(key, "device.description") != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *value = pa_proplist_gets(info->proplist, key);
|
|
||||||
size_t len = strnlen(value, size);
|
|
||||||
if (len && (len < strnlen(pulse->device_name, size))) {
|
|
||||||
strncpy(pulse->device_name, value, size);
|
|
||||||
pulse->device_name[size - 1] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
19
src/audio.h
19
src/audio.h
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef struct pulse_context_t pulse_context_t;
|
|
||||||
|
|
||||||
typedef struct pulse_t {
|
|
||||||
int volume_percent;
|
|
||||||
bool mute;
|
|
||||||
char device_name[255];
|
|
||||||
} pulse_t;
|
|
||||||
|
|
||||||
typedef void (*pulse_notify_t)(pulse_t *pulse);
|
|
||||||
|
|
||||||
pulse_context_t *init_pulse(GMainContext *context, pulse_notify_t callback);
|
|
||||||
void change_pulse_volume(pulse_context_t *context, int step);
|
|
||||||
void toggle_pulse_mute(pulse_context_t *context);
|
|
||||||
void clean_pulse(pulse_context_t *context);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.clang-format
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#include "backtrace.h"
|
|
||||||
|
|
||||||
#include "app.h"
|
|
||||||
#include "buffer.h"
|
|
||||||
|
|
||||||
#ifdef HAS_EXECINFO
|
|
||||||
#include <execinfo.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define MAX_STACK_SIZE 32
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a backtrace.
|
|
||||||
* @param buffer The buffer to fill with backtrace.
|
|
||||||
*/
|
|
||||||
void backtrace_get(buffer_t *buffer) {
|
|
||||||
buffer_init(buffer);
|
|
||||||
|
|
||||||
#ifdef HAS_EXECINFO
|
|
||||||
void *stack[MAX_STACK_SIZE];
|
|
||||||
char **bt;
|
|
||||||
int stack_size;
|
|
||||||
|
|
||||||
stack_size = backtrace(stack, countof(stack));
|
|
||||||
bt = backtrace_symbols(stack, stack_size);
|
|
||||||
|
|
||||||
if (bt) {
|
|
||||||
for (int i = 0; i < stack_size; i++) {
|
|
||||||
if (i > 0) buffer_addsl(buffer, "\n");
|
|
||||||
buffer_adds(buffer, bt[i]);
|
|
||||||
}
|
|
||||||
p_delete(&bt);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
|
|
||||||
buffer_addsl(buffer, "Cannot get backtrace symbols.");
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "buffer.h"
|
|
||||||
|
|
||||||
void backtrace_get(buffer_t *buffer);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.clang-format
|
|
||||||
17
src/base.h
17
src/base.h
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef struct area_t {
|
|
||||||
int16_t x, y;
|
|
||||||
uint16_t width, height;
|
|
||||||
} area_t;
|
|
||||||
|
|
||||||
typedef struct extent_in_bar_t {
|
|
||||||
int16_t start;
|
|
||||||
int16_t end;
|
|
||||||
} extent_in_bar_t;
|
|
||||||
|
|
||||||
typedef struct point_t {
|
|
||||||
int16_t x, y;
|
|
||||||
} point_t;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.clang-format
|
|
||||||
37
src/buffer.c
37
src/buffer.c
@@ -1,37 +0,0 @@
|
|||||||
#include "buffer.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sysexits.h>
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
char buffer_slop[1];
|
|
||||||
|
|
||||||
void buffer_ensure(buffer_t *buf, int newlen) {
|
|
||||||
if (newlen < 0) exit(EX_SOFTWARE);
|
|
||||||
|
|
||||||
if (newlen < buf->size) return;
|
|
||||||
|
|
||||||
if (newlen < buf->offs + buf->size && buf->offs > buf->size / 4) {
|
|
||||||
/* Data fits in the current area, shift it left */
|
|
||||||
memmove(buf->s - buf->offs, buf->s, buf->len + 1);
|
|
||||||
buf->s -= buf->offs;
|
|
||||||
buf->size += buf->offs;
|
|
||||||
buf->offs = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->size = p_alloc_nr(buf->size + buf->offs);
|
|
||||||
if (buf->size < newlen + 1) buf->size = newlen + 1;
|
|
||||||
if (buf->alloced && !buf->offs)
|
|
||||||
p_realloc(&buf->s, buf->size);
|
|
||||||
else {
|
|
||||||
char *new_area = xmalloc(buf->size);
|
|
||||||
memcpy(new_area, buf->s, buf->len + 1);
|
|
||||||
if (buf->alloced) free(buf->s - buf->offs);
|
|
||||||
buf->alloced = true;
|
|
||||||
buf->s = new_area;
|
|
||||||
buf->offs = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
78
src/buffer.h
78
src/buffer.h
@@ -1,78 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
typedef struct buffer_t {
|
|
||||||
char *s;
|
|
||||||
int len, size;
|
|
||||||
unsigned alloced : 1;
|
|
||||||
unsigned offs : 31;
|
|
||||||
} buffer_t;
|
|
||||||
|
|
||||||
extern char buffer_slop[1];
|
|
||||||
|
|
||||||
#define BUFFER_INIT (buffer_t){.s = buffer_slop, .size = 1}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a buffer.
|
|
||||||
* @param buf A buffer pointer.
|
|
||||||
* @return The same buffer pointer.
|
|
||||||
*/
|
|
||||||
static inline buffer_t *buffer_init(buffer_t *buf) {
|
|
||||||
*buf = BUFFER_INIT;
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
void buffer_ensure(buffer_t *buf, int len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add data in the buffer.
|
|
||||||
* @param buf Buffer where to add.
|
|
||||||
* @param pos Position where to add.
|
|
||||||
* @param len Length.
|
|
||||||
* @param data Data to add.
|
|
||||||
* @param dlen Data length.
|
|
||||||
*/
|
|
||||||
static inline void buffer_splice(buffer_t *buf, int pos, int len,
|
|
||||||
const void *data, int dlen) {
|
|
||||||
assert(pos >= 0 && len >= 0 && dlen >= 0);
|
|
||||||
|
|
||||||
if (unlikely(pos > buf->len)) pos = buf->len;
|
|
||||||
if (unlikely(len > buf->len - pos)) len = buf->len - pos;
|
|
||||||
if (pos == 0 && len + buf->offs >= dlen) {
|
|
||||||
buf->offs += len - dlen;
|
|
||||||
buf->s += len - dlen;
|
|
||||||
buf->size -= len - dlen;
|
|
||||||
buf->len -= len - dlen;
|
|
||||||
} else if (len != dlen) {
|
|
||||||
buffer_ensure(buf, buf->len + dlen - len);
|
|
||||||
memmove(buf->s + pos + dlen, buf->s + pos + len, buf->len - pos - len);
|
|
||||||
buf->len += dlen - len;
|
|
||||||
buf->s[buf->len] = '\0';
|
|
||||||
}
|
|
||||||
memcpy(buf->s + pos, data, dlen);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add data at the end of buffer.
|
|
||||||
* @param buf Buffer where to add.
|
|
||||||
* @param data Data to add.
|
|
||||||
* @param len Data length.
|
|
||||||
*/
|
|
||||||
static inline void buffer_add(buffer_t *buf, const void *data, int len) {
|
|
||||||
buffer_splice(buf, buf->len, 0, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define buffer_addsl(buf, data) buffer_add(buf, data, sizeof(data) - 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a string to the and of a buffer.
|
|
||||||
* @param buf The buffer where to add.
|
|
||||||
* @param s The string to add.
|
|
||||||
*/
|
|
||||||
static inline void buffer_adds(buffer_t *buf, const char *s) {
|
|
||||||
buffer_splice(buf, buf->len, 0, s, a_strlen(s));
|
|
||||||
}
|
|
||||||
706
src/client.c
706
src/client.c
@@ -1,706 +0,0 @@
|
|||||||
#include "client.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_aux.h>
|
|
||||||
#include <xcb/xcb_icccm.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "atoms-extern.h"
|
|
||||||
#include "base.h"
|
|
||||||
#include "monitor.h"
|
|
||||||
#include "rect.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wm.h"
|
|
||||||
#include "xwindow.h"
|
|
||||||
|
|
||||||
client_t *client_get_by_window(xcb_window_t window) {
|
|
||||||
for (client_t *c = wm.client_list; c; c = c->next) {
|
|
||||||
if (c->window == window) return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_t *client_get_next_by_class(client_t *current, const char *class) {
|
|
||||||
client_t *start = current ? current->next : wm.client_list;
|
|
||||||
for (client_t *c = start; c; c = c->next) {
|
|
||||||
if (strcmp(class, c->class) == 0) return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (client_t *c = wm.client_list; c && c != start; c = c->next) {
|
|
||||||
if (strcmp(class, c->class) == 0) return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_attach_list(client_t *client) {
|
|
||||||
client->next = wm.client_list;
|
|
||||||
wm.client_list = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_detach_list(client_t *client) {
|
|
||||||
client_t **tc = nullptr;
|
|
||||||
for (tc = &wm.client_list; *tc && *tc != client; tc = &(*tc)->next);
|
|
||||||
*tc = client->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_attach_stack(client_t *client) {
|
|
||||||
client->stack_next = wm.client_stack_list;
|
|
||||||
wm.client_stack_list = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_detach_stack(client_t *client) {
|
|
||||||
client_t **tc = &wm.client_stack_list;
|
|
||||||
for (; *tc && *tc != client; tc = &(*tc)->stack_next);
|
|
||||||
*tc = client->stack_next;
|
|
||||||
|
|
||||||
if (client == wm.client_focused) {
|
|
||||||
client_t *t = wm.client_stack_list;
|
|
||||||
for (; t && !client_is_visible(t); t = t->stack_next);
|
|
||||||
wm.client_focused = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task_in_tag_t *client_get_task_in_tag(client_t *client, tag_t *tag) {
|
|
||||||
for (task_in_tag_t *task = tag->task_list; task; task = task->next) {
|
|
||||||
if (task->client == client) return task;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
task_in_tag_t *client_get_next_task_in_tag(client_t *client, tag_t *tag) {
|
|
||||||
if (!tag->task_list) return nullptr;
|
|
||||||
|
|
||||||
task_in_tag_t *task = tag->task_list;
|
|
||||||
for (; task && task->client != client; task = task->next);
|
|
||||||
return task && task->next ? task->next : tag->task_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
task_in_tag_t *client_get_previous_task_in_tag(client_t *client, tag_t *tag) {
|
|
||||||
if (!tag->task_list) return nullptr;
|
|
||||||
|
|
||||||
task_in_tag_t *task = tag->task_list;
|
|
||||||
for (; task && task->next && task->next->client != client; task = task->next);
|
|
||||||
return task ? task : tag->task_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_add_to_tag(client_t *client, tag_t *tag) {
|
|
||||||
if (client_get_task_in_tag(client, tag)) return;
|
|
||||||
|
|
||||||
xwindow_set_wm_desktop(client->window, tag->index);
|
|
||||||
|
|
||||||
task_in_tag_t *task = p_new(task_in_tag_t, 1);
|
|
||||||
task->client = client;
|
|
||||||
task->next = tag->task_list;
|
|
||||||
tag->task_list = task;
|
|
||||||
task->geometry = client->geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_remove_from_tag(client_t *client, tag_t *tag) {
|
|
||||||
task_in_tag_t **task = &tag->task_list;
|
|
||||||
while (*task) {
|
|
||||||
if ((*task)->client == client) {
|
|
||||||
task_in_tag_t *t = *task;
|
|
||||||
*task = (*task)->next;
|
|
||||||
p_delete(&t);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
task = &(*task)->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void client_wipe(client_t *client) {
|
|
||||||
p_delete(&client->class);
|
|
||||||
p_delete(&client->instance);
|
|
||||||
p_delete(&client->name);
|
|
||||||
p_delete(&client->net_name);
|
|
||||||
p_delete(&client->role);
|
|
||||||
p_delete(&client);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void client_tags_apply(client_t *client) {
|
|
||||||
for (tag_t *tag = client->monitor->tag_list; tag; tag = tag->next) {
|
|
||||||
if (client->tags & tag->mask) {
|
|
||||||
client_add_to_tag(client, tag);
|
|
||||||
} else {
|
|
||||||
client_remove_from_tag(client, tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->tags == 0) client_wipe(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void client_update_names(client_t *c) {
|
|
||||||
xwindow_get_text_property(c->window, WM_NAME, &c->name);
|
|
||||||
xwindow_get_text_property(c->window, _NET_WM_NAME, &c->net_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void client_init_geometry(client_t *c) {
|
|
||||||
if (c->maximize || c->fullscreen || c->minimize) return;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_init_tag_by_wm_desktop(client_t *client, uint32_t tag_mask) {
|
|
||||||
uint32_t tag_index = 0;
|
|
||||||
if (!xwindow_get_wm_desktop(client->window, &tag_index)) {
|
|
||||||
goto tag_fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
|
||||||
for (tag_t *tag = m->tag_list; tag; tag = tag->next) {
|
|
||||||
if (tag->index == tag_index) {
|
|
||||||
client->monitor = m;
|
|
||||||
client->tags = tag->mask;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tag_fallback:
|
|
||||||
client->tags = tag_mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void set_window_event_mask(xcb_window_t window, bool clean) {
|
|
||||||
xcb_cw_t change_mask = XCB_CW_EVENT_MASK;
|
|
||||||
uint32_t init_event_mask =
|
|
||||||
XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_FOCUS_CHANGE |
|
|
||||||
XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
|
|
||||||
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
|
|
||||||
uint32_t event_mask = clean ? XCB_EVENT_MASK_NO_EVENT : init_event_mask;
|
|
||||||
xcb_params_cw_t params = {.event_mask = event_mask};
|
|
||||||
xcb_aux_change_window_attributes(wm.xcb_conn, window, change_mask, ¶ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_manage(xcb_window_t window,
|
|
||||||
xcb_get_geometry_reply_t *geometry_reply) {
|
|
||||||
client_t *c = p_new(client_t, 1);
|
|
||||||
c->window = window;
|
|
||||||
c->old_geometry.x = geometry_reply->x;
|
|
||||||
c->old_geometry.y = geometry_reply->y;
|
|
||||||
c->old_geometry.width = geometry_reply->width;
|
|
||||||
c->old_geometry.height = geometry_reply->height;
|
|
||||||
c->geometry = c->old_geometry;
|
|
||||||
c->old_border_width = geometry_reply->border_width;
|
|
||||||
|
|
||||||
c->minimize = xwindow_get_state(window) == XCB_ICCCM_WM_STATE_ICONIC;
|
|
||||||
|
|
||||||
set_window_event_mask(window, false);
|
|
||||||
|
|
||||||
client_update_names(c);
|
|
||||||
xwindow_get_text_property(c->window, WM_WINDOW_ROLE, &c->role);
|
|
||||||
client_update_wm_hints(c);
|
|
||||||
|
|
||||||
{
|
|
||||||
xcb_icccm_get_wm_class_reply_t prop;
|
|
||||||
xcb_get_property_cookie_t cookie =
|
|
||||||
xcb_icccm_get_wm_class_unchecked(wm.xcb_conn, c->window);
|
|
||||||
if (xcb_icccm_get_wm_class_reply(wm.xcb_conn, cookie, &prop, nullptr)) {
|
|
||||||
c->class = strdup(prop.class_name);
|
|
||||||
c->instance = strdup(prop.instance_name);
|
|
||||||
xcb_icccm_get_wm_class_reply_wipe(&prop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
xcb_get_property_cookie_t cookie =
|
|
||||||
xcb_icccm_get_wm_transient_for_unchecked(wm.xcb_conn, window);
|
|
||||||
xcb_icccm_get_wm_transient_for_reply(wm.xcb_conn, cookie,
|
|
||||||
&c->transient_for_window, nullptr);
|
|
||||||
|
|
||||||
client_t *transient_for_client = nullptr;
|
|
||||||
if (c->transient_for_window && (transient_for_client = client_get_by_window(
|
|
||||||
c->transient_for_window))) {
|
|
||||||
c->monitor = transient_for_client->monitor;
|
|
||||||
client_init_tag_by_wm_desktop(c, transient_for_client->tags);
|
|
||||||
} else {
|
|
||||||
c->monitor = wm.current_monitor;
|
|
||||||
client_init_tag_by_wm_desktop(c, c->monitor->selected_tag->mask);
|
|
||||||
client_apply_rules(c, wm.rules, wm.rules_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
client_init_geometry(c);
|
|
||||||
client_tags_apply(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
client_attach_list(c);
|
|
||||||
client_attach_stack(c);
|
|
||||||
client_update_window_type(c);
|
|
||||||
client_update_size_hints(c);
|
|
||||||
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_APPEND, wm.screen->root,
|
|
||||||
_NET_CLIENT_LIST, XCB_ATOM_WINDOW, 32, 1, &window);
|
|
||||||
|
|
||||||
if (c->minimize) {
|
|
||||||
xwindow_set_state(window, XCB_ICCCM_WM_STATE_ICONIC);
|
|
||||||
} else {
|
|
||||||
xwindow_set_state(window, XCB_ICCCM_WM_STATE_NORMAL);
|
|
||||||
xcb_map_window(wm.xcb_conn, window);
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_arrange(c->monitor);
|
|
||||||
monitor_draw_bar(c->monitor);
|
|
||||||
|
|
||||||
wm_restack_clients();
|
|
||||||
if (c->tags & c->monitor->selected_tag->mask) client_focus(c);
|
|
||||||
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
char **client_get_task_title(client_t *client) {
|
|
||||||
if (client->net_name) return &client->net_name;
|
|
||||||
if (client->name) return &client->name;
|
|
||||||
if (client->instance) return &client->instance;
|
|
||||||
if (client->class) return &client->class;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool client_need_layout(client_t *client) {
|
|
||||||
if (client->floating || client->fullscreen || client->maximize ||
|
|
||||||
client->minimize || client->size_freeze) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_send_to_tag(client_t *client, uint32_t tag_mask) {
|
|
||||||
if (client->tags == tag_mask) return;
|
|
||||||
client->tags = tag_mask;
|
|
||||||
client_tags_apply(client);
|
|
||||||
monitor_arrange(client->monitor);
|
|
||||||
monitor_draw_bar(client->monitor);
|
|
||||||
monitor_deal_focus(client->monitor);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_send_to_monitor(client_t *client, monitor_t *monitor) {
|
|
||||||
if (client->monitor == monitor) return;
|
|
||||||
|
|
||||||
for (tag_t *tag = client->monitor->tag_list; tag; tag = tag->next) {
|
|
||||||
client_remove_from_tag(client, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_t *m = client->monitor;
|
|
||||||
|
|
||||||
client->monitor = monitor;
|
|
||||||
client->tags = monitor->selected_tag->mask;
|
|
||||||
client_add_to_tag(client, monitor->selected_tag);
|
|
||||||
|
|
||||||
wm_set_current_monitor(monitor, true);
|
|
||||||
monitor_arrange(m);
|
|
||||||
monitor_arrange(monitor);
|
|
||||||
monitor_draw_bar(m);
|
|
||||||
monitor_draw_bar(monitor);
|
|
||||||
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_move_to(client_t *client, int16_t x, int16_t y) {
|
|
||||||
client->geometry.x = x;
|
|
||||||
client->geometry.y = y;
|
|
||||||
uint16_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
|
|
||||||
const xcb_params_configure_window_t params = {.x = x, .y = y};
|
|
||||||
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, ¶ms);
|
|
||||||
logger("== client 0x%x move to x: %d, y: %d\n", client->window, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_resize(client_t *client, uint16_t width, uint16_t height) {
|
|
||||||
client->geometry.width = width;
|
|
||||||
client->geometry.height = height;
|
|
||||||
uint16_t mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
|
|
||||||
const xcb_params_configure_window_t params = {
|
|
||||||
.width = width,
|
|
||||||
.height = height,
|
|
||||||
};
|
|
||||||
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, ¶ms);
|
|
||||||
logger("== client 0x%x resize to width: %d, height: %d\n", client->window,
|
|
||||||
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) {
|
|
||||||
if (client->border_width == border_width) return;
|
|
||||||
uint16_t mask = XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
|
||||||
const xcb_params_configure_window_t params = {.border_width = border_width};
|
|
||||||
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, ¶ms);
|
|
||||||
client->border_width = border_width;
|
|
||||||
logger("== client 0x%x border width: %u\n", client->window, border_width);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_update_window_type(client_t *client) {
|
|
||||||
xcb_window_t window = client->window;
|
|
||||||
xcb_get_property_cookie_t state_cookie = xcb_get_property_unchecked(
|
|
||||||
wm.xcb_conn, false, window, _NET_WM_STATE, XCB_ATOM_ATOM, 0, 1);
|
|
||||||
xcb_get_property_cookie_t window_type_cookie = xcb_get_property_unchecked(
|
|
||||||
wm.xcb_conn, false, window, _NET_WM_WINDOW_TYPE, XCB_ATOM_ATOM, 0, 1);
|
|
||||||
xcb_get_property_reply_t *state_reply =
|
|
||||||
xcb_get_property_reply(wm.xcb_conn, state_cookie, nullptr);
|
|
||||||
xcb_get_property_reply_t *window_type_reply =
|
|
||||||
xcb_get_property_reply(wm.xcb_conn, window_type_cookie, nullptr);
|
|
||||||
|
|
||||||
if (state_reply) {
|
|
||||||
xcb_atom_t state = *(xcb_atom_t *)xcb_get_property_value(state_reply);
|
|
||||||
client->skip_taskbar = state == _NET_WM_STATE_SKIP_TASKBAR;
|
|
||||||
if (state == _NET_WM_STATE_FULLSCREEN) {
|
|
||||||
client_set_fullscreen(client, true);
|
|
||||||
}
|
|
||||||
p_delete(&state_reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window_type_reply) {
|
|
||||||
xcb_atom_t window_type =
|
|
||||||
*(xcb_atom_t *)xcb_get_property_value(window_type_reply);
|
|
||||||
if (window_type == _NET_WM_WINDOW_TYPE_DIALOG) {
|
|
||||||
client_set_floating(client, true);
|
|
||||||
}
|
|
||||||
p_delete(&window_type_reply);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_update_wm_hints(client_t *client) {
|
|
||||||
xcb_get_property_cookie_t cookie =
|
|
||||||
xcb_icccm_get_wm_hints(wm.xcb_conn, client->window);
|
|
||||||
xcb_icccm_wm_hints_t hints;
|
|
||||||
if (xcb_icccm_get_wm_hints_reply(wm.xcb_conn, cookie, &hints, nullptr)) {
|
|
||||||
if (client == wm.client_focused &&
|
|
||||||
(hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY)) {
|
|
||||||
hints.flags &= ~XCB_ICCCM_WM_HINT_X_URGENCY;
|
|
||||||
xcb_icccm_set_wm_hints(wm.xcb_conn, client->window, &hints);
|
|
||||||
} else {
|
|
||||||
client->urgent = hints.flags & XCB_ICCCM_WM_HINT_X_URGENCY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_update_size_hints(client_t *client) {
|
|
||||||
xcb_connection_t *conn = wm.xcb_conn;
|
|
||||||
xcb_size_hints_t hints;
|
|
||||||
xcb_get_property_cookie_t cookie =
|
|
||||||
xcb_icccm_get_wm_normal_hints_unchecked(conn, client->window);
|
|
||||||
if (!xcb_icccm_get_wm_normal_hints_reply(conn, cookie, &hints, nullptr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t min_width = 0, min_height = 0, max_width = 0, max_height = 0;
|
|
||||||
if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
|
|
||||||
min_width = hints.min_width;
|
|
||||||
min_height = hints.min_height;
|
|
||||||
}
|
|
||||||
if (hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
|
|
||||||
max_width = hints.max_width;
|
|
||||||
max_height = hints.max_height;
|
|
||||||
}
|
|
||||||
if (min_width == max_width && min_height == max_height) {
|
|
||||||
client->size_freeze = true;
|
|
||||||
client_set_floating(client, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_set_floating(client_t *client, bool floating) {
|
|
||||||
if (client->floating == floating || client->fullscreen) return;
|
|
||||||
|
|
||||||
logger("== client set floating: %s\n", floating ? "true" : "false");
|
|
||||||
logger("== old geometry: x -> %d, y -> %d, width -> %u, height -> %u\n",
|
|
||||||
client->old_geometry.x, client->old_geometry.y,
|
|
||||||
client->old_geometry.width, client->old_geometry.height);
|
|
||||||
client->floating = floating;
|
|
||||||
if (floating) {
|
|
||||||
client_apply_workarea_geometry(client, client->old_geometry);
|
|
||||||
client->old_geometry = client->geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_arrange(client->monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_set_fullscreen(client_t *client, bool fullscreen) {
|
|
||||||
if (client->fullscreen == fullscreen) return;
|
|
||||||
|
|
||||||
client->fullscreen = fullscreen;
|
|
||||||
if (fullscreen) {
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
|
|
||||||
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 1,
|
|
||||||
&_NET_WM_STATE_FULLSCREEN);
|
|
||||||
client->old_border_width = client->border_width;
|
|
||||||
if (client->floating) client->old_geometry = client->geometry;
|
|
||||||
client_change_border_width(client, 0);
|
|
||||||
client_apply_geometry(client, client->monitor->geometry);
|
|
||||||
client_stack_raise(client);
|
|
||||||
} else {
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
|
|
||||||
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 0, 0);
|
|
||||||
client_change_border_width(client, client->old_border_width);
|
|
||||||
client_apply_geometry(client, client->old_geometry);
|
|
||||||
}
|
|
||||||
monitor_arrange(client->monitor);
|
|
||||||
client_focus(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_set_maximize(client_t *client, bool maximize) {
|
|
||||||
if (client->maximize == maximize) return;
|
|
||||||
|
|
||||||
client->maximize = maximize;
|
|
||||||
if (maximize) {
|
|
||||||
xcb_atom_t max_atoms[] = {
|
|
||||||
_NET_WM_STATE_MAXIMIZED_HORZ,
|
|
||||||
_NET_WM_STATE_MAXIMIZED_VERT,
|
|
||||||
};
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
|
|
||||||
_NET_WM_STATE, XCB_ATOM_ATOM, 32, countof(max_atoms),
|
|
||||||
max_atoms);
|
|
||||||
client->old_border_width = client->border_width;
|
|
||||||
if (client->floating) client->old_geometry = client->geometry;
|
|
||||||
client_change_border_width(client, 0);
|
|
||||||
client_apply_geometry(client, client->monitor->workarea);
|
|
||||||
client_stack_raise(client);
|
|
||||||
} else {
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
|
|
||||||
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 0, 0);
|
|
||||||
client_change_border_width(client, client->old_border_width);
|
|
||||||
client_apply_geometry(client, client->old_geometry);
|
|
||||||
}
|
|
||||||
monitor_arrange(client->monitor);
|
|
||||||
client_focus(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_set_minimize(client_t *client, bool minimize) {
|
|
||||||
if (client->minimize == minimize) return;
|
|
||||||
|
|
||||||
client->minimize = minimize;
|
|
||||||
if (minimize) {
|
|
||||||
xwindow_set_state(client->window, XCB_ICCCM_WM_STATE_ICONIC);
|
|
||||||
|
|
||||||
xcb_grab_server(wm.xcb_conn);
|
|
||||||
set_window_event_mask(client->window, true);
|
|
||||||
xcb_unmap_window(wm.xcb_conn, client->window);
|
|
||||||
set_window_event_mask(client->window, false);
|
|
||||||
xcb_ungrab_server(wm.xcb_conn);
|
|
||||||
} else {
|
|
||||||
xwindow_set_state(client->window, XCB_ICCCM_WM_STATE_NORMAL);
|
|
||||||
xcb_map_window(wm.xcb_conn, client->window);
|
|
||||||
}
|
|
||||||
monitor_arrange(client->monitor);
|
|
||||||
client_focus(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void update_client_list(void) {
|
|
||||||
xcb_connection_t *conn = wm.xcb_conn;
|
|
||||||
xcb_window_t root = wm.screen->root;
|
|
||||||
xcb_atom_t atom = _NET_CLIENT_LIST;
|
|
||||||
uint8_t mode = XCB_PROP_MODE_PREPEND;
|
|
||||||
xcb_atom_t type = XCB_ATOM_WINDOW;
|
|
||||||
|
|
||||||
xcb_delete_property(conn, root, atom);
|
|
||||||
for (client_t *c = wm.client_list; c; c = c->next) {
|
|
||||||
xcb_change_property(conn, mode, root, atom, type, 32, 1, &c->window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_kill(client_t *client) {
|
|
||||||
if (!xwindow_send_event(client->window, WM_DELETE_WINDOW)) {
|
|
||||||
xwindow_kill_window(client->window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_unmanage(client_t *client, bool destroyed) {
|
|
||||||
monitor_t *m = client->monitor;
|
|
||||||
|
|
||||||
client_detach_list(client);
|
|
||||||
client_detach_stack(client);
|
|
||||||
|
|
||||||
for (tag_t *tag = m->tag_list; tag; tag = tag->next) {
|
|
||||||
if (tag->mask & client->tags) client_remove_from_tag(client, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!destroyed) {
|
|
||||||
set_window_event_mask(client->window, true);
|
|
||||||
client_change_border_width(client, client->old_border_width);
|
|
||||||
xwindow_set_state(client->window, XCB_ICCCM_WM_STATE_WITHDRAWN);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
client_wipe(client);
|
|
||||||
|
|
||||||
update_client_list();
|
|
||||||
|
|
||||||
wm_restack_clients();
|
|
||||||
monitor_deal_focus(m);
|
|
||||||
monitor_arrange(m);
|
|
||||||
monitor_draw_bar(m);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_apply_geometry(client_t *client, area_t geometry) {
|
|
||||||
uint16_t mask = 0;
|
|
||||||
const xcb_params_configure_window_t params = {
|
|
||||||
.x = geometry.x,
|
|
||||||
.y = geometry.y,
|
|
||||||
.width = geometry.width,
|
|
||||||
.height = geometry.height,
|
|
||||||
};
|
|
||||||
if (client->geometry.x != geometry.x) mask |= XCB_CONFIG_WINDOW_X;
|
|
||||||
if (client->geometry.y != geometry.y) mask |= XCB_CONFIG_WINDOW_Y;
|
|
||||||
if (client->geometry.width != geometry.width) mask |= XCB_CONFIG_WINDOW_WIDTH;
|
|
||||||
if (client->geometry.height != geometry.height) {
|
|
||||||
mask |= XCB_CONFIG_WINDOW_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
client->geometry = geometry;
|
|
||||||
xcb_aux_configure_window(wm.xcb_conn, client->window, mask, ¶ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 调整 client 的几何信息但需让 client 任在其 monitor 的 workarea 中
|
|
||||||
* @param client 要调整几何信息的 client
|
|
||||||
* @param geometry 目标几何信息
|
|
||||||
*/
|
|
||||||
void client_apply_workarea_geometry(client_t *client, area_t geometry) {
|
|
||||||
area_t workarea = client->monitor->workarea;
|
|
||||||
uint16_t width = MIN(geometry.width, workarea.width);
|
|
||||||
uint16_t height = MIN(geometry.height, workarea.height);
|
|
||||||
int16_t x = MAX(geometry.x, workarea.x);
|
|
||||||
int16_t y = MAX(geometry.y, workarea.y);
|
|
||||||
if (x + width + client->border_width * 2 > workarea.x + workarea.width) {
|
|
||||||
x = workarea.x + workarea.width - width - client->border_width * 2;
|
|
||||||
}
|
|
||||||
if (y + height + client->border_width * 2 > workarea.y + workarea.height) {
|
|
||||||
y = workarea.y + workarea.height - height - client->border_width * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
area_t rect = {.x = x, .y = y, .width = width, .height = height};
|
|
||||||
client_apply_geometry(client, rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_apply_rules(client_t *client, const rule_t rules[],
|
|
||||||
size_t rules_count) {
|
|
||||||
for (size_t i = 0; i < rules_count; i++) {
|
|
||||||
const rule_t *r = &rules[i];
|
|
||||||
if (!((!r->role || (client->role && strstr(client->role, r->role))) &&
|
|
||||||
(!r->class || (client->class && strstr(client->class, r->class))))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
client->fullscreen = r->fullscreen;
|
|
||||||
client->maximize = r->maximize;
|
|
||||||
client->floating = r->floating;
|
|
||||||
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
|
||||||
for (tag_t *t = m->tag_list; t; t = t->next) {
|
|
||||||
if (t->index != r->tag_index) continue;
|
|
||||||
|
|
||||||
client->monitor = m;
|
|
||||||
client->tags = t->mask;
|
|
||||||
|
|
||||||
if (r->switch_to_tag) {
|
|
||||||
wm_set_current_monitor(m, true);
|
|
||||||
m->selected_tag = t;
|
|
||||||
|
|
||||||
logger("++ switch to tag: %u\n", t->index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->fullscreen) {
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
|
|
||||||
_NET_WM_STATE, XCB_ATOM_ATOM, 32, 1,
|
|
||||||
&_NET_WM_STATE_FULLSCREEN);
|
|
||||||
} else if (client->maximize) {
|
|
||||||
xcb_atom_t max_atoms[] = {
|
|
||||||
_NET_WM_STATE_MAXIMIZED_HORZ,
|
|
||||||
_NET_WM_STATE_MAXIMIZED_VERT,
|
|
||||||
};
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, client->window,
|
|
||||||
_NET_WM_STATE, XCB_ATOM_ATOM, 32, countof(max_atoms),
|
|
||||||
max_atoms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_focus(client_t *client) {
|
|
||||||
wm.client_focused = client;
|
|
||||||
xwindow_focus(client ? client->window : XCB_WINDOW_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_stack_raise(client_t *client) {
|
|
||||||
if (wm.client_stack_list == client) return;
|
|
||||||
|
|
||||||
client_detach_stack(client);
|
|
||||||
client_attach_stack(client);
|
|
||||||
wm_restack_clients();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool client_is_visible(client_t *client) {
|
|
||||||
return client->tags & client->monitor->selected_tag->mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline rect_t client_to_rect(client_t *client) {
|
|
||||||
return (rect_t){
|
|
||||||
.x1 = client->geometry.x,
|
|
||||||
.y1 = client->geometry.y,
|
|
||||||
.x2 = client->geometry.x + (int32_t)client_width(client),
|
|
||||||
.y2 = client->geometry.y + (int32_t)client_height(client),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
obscured_t client_get_obscured_state(client_t *client, client_t *client_stack) {
|
|
||||||
if (!client || !client_stack) return obscured_none;
|
|
||||||
|
|
||||||
rect_t source = client_to_rect(client);
|
|
||||||
size_t clip_count = 0;
|
|
||||||
size_t clip_capacity = 0;
|
|
||||||
rect_t *clips = nullptr;
|
|
||||||
|
|
||||||
bool has_overlap = false;
|
|
||||||
for (client_t *c = client_stack; c; c = c->stack_next) {
|
|
||||||
if (c == client) break;
|
|
||||||
if (c->minimize || !client_is_visible(c)) continue;
|
|
||||||
|
|
||||||
rect_t clip = client_to_rect(c);
|
|
||||||
if (!rect_intersection(source, clip, nullptr)) continue;
|
|
||||||
|
|
||||||
has_overlap = true;
|
|
||||||
if (clip_count == clip_capacity) {
|
|
||||||
clip_capacity = clip_capacity ? clip_capacity * 2 : 4;
|
|
||||||
p_realloc(&clips, clip_capacity);
|
|
||||||
}
|
|
||||||
clips[clip_count++] = clip;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!has_overlap) {
|
|
||||||
p_delete(&clips);
|
|
||||||
return obscured_none;
|
|
||||||
}
|
|
||||||
|
|
||||||
rect_t *remaining = nullptr;
|
|
||||||
size_t remaining_count =
|
|
||||||
rect_subtract_many(source, clips, clip_count, &remaining);
|
|
||||||
|
|
||||||
p_delete(&clips);
|
|
||||||
p_delete(&remaining);
|
|
||||||
return remaining_count == 0 ? obscured_fully : obscured_partially;
|
|
||||||
}
|
|
||||||
76
src/client.h
76
src/client.h
@@ -1,76 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "color.h"
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
client_t *client_get_by_window(xcb_window_t window);
|
|
||||||
client_t *client_get_next_by_class(client_t *current, const char *class);
|
|
||||||
task_in_tag_t *client_get_task_in_tag(client_t *client, tag_t *tag);
|
|
||||||
task_in_tag_t *client_get_next_task_in_tag(client_t *client, tag_t *tag);
|
|
||||||
task_in_tag_t *client_get_previous_task_in_tag(client_t *client, tag_t *tag);
|
|
||||||
void client_manage(xcb_window_t window,
|
|
||||||
xcb_get_geometry_reply_t *geometry_reply);
|
|
||||||
char **client_get_task_title(client_t *client);
|
|
||||||
bool client_need_layout(client_t *client);
|
|
||||||
|
|
||||||
void client_send_to_tag(client_t *client, uint32_t tag_mask);
|
|
||||||
void client_send_to_monitor(client_t *client, monitor_t *monitor);
|
|
||||||
|
|
||||||
void client_move_to(client_t *client, int16_t x, int16_t y);
|
|
||||||
void client_resize(client_t *client, uint16_t width, uint16_t height);
|
|
||||||
void client_change_border_color(client_t *client, color_t *color);
|
|
||||||
void client_change_border_width(client_t *client, uint16_t border_width);
|
|
||||||
|
|
||||||
void client_update_window_type(client_t *client);
|
|
||||||
void client_update_wm_hints(client_t *client);
|
|
||||||
void client_update_size_hints(client_t *client);
|
|
||||||
|
|
||||||
void client_set_floating(client_t *client, bool floating);
|
|
||||||
void client_set_fullscreen(client_t *client, bool fullscreen);
|
|
||||||
void client_set_maximize(client_t *client, bool maximize);
|
|
||||||
void client_set_minimize(client_t *client, bool minimize);
|
|
||||||
void client_set_sticky(client_t *client, bool sticky);
|
|
||||||
void client_set_urgent(client_t *client, bool urgent);
|
|
||||||
|
|
||||||
void client_set_class_instance(client_t *client, const char *class,
|
|
||||||
const char *instance);
|
|
||||||
|
|
||||||
void client_set_name(client_t *client, char *name);
|
|
||||||
void client_set_icon_name(client_t *client, char *icon_name);
|
|
||||||
void client_set_net_name(client_t *client, char *name);
|
|
||||||
void client_set_net_icon_name(client_t *client, char *icon_name);
|
|
||||||
|
|
||||||
void client_set_role(client_t *client, char *role);
|
|
||||||
|
|
||||||
void client_set_transient_for_window(client_t *client,
|
|
||||||
xcb_window_t transient_for_window);
|
|
||||||
void client_set_leader_window(client_t *client, xcb_window_t leader_window);
|
|
||||||
|
|
||||||
void client_kill(client_t *client);
|
|
||||||
void client_unmanage(client_t *client, bool destroyed);
|
|
||||||
void client_apply_geometry(client_t *client, area_t geometry);
|
|
||||||
void client_apply_workarea_geometry(client_t *client, area_t geometry);
|
|
||||||
void client_apply_rules(client_t *client, const rule_t rules[],
|
|
||||||
size_t rules_count);
|
|
||||||
|
|
||||||
void client_focus(client_t *client);
|
|
||||||
void client_stack_raise(client_t *client);
|
|
||||||
|
|
||||||
bool client_is_visible(client_t *client);
|
|
||||||
|
|
||||||
typedef enum obscured_t {
|
|
||||||
obscured_none,
|
|
||||||
obscured_partially,
|
|
||||||
obscured_fully,
|
|
||||||
} obscured_t;
|
|
||||||
obscured_t client_get_obscured_state(client_t *client, client_t *client_stack);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
87
src/color.c
87
src/color.c
@@ -1,87 +0,0 @@
|
|||||||
#include "color.h"
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将 16 进制的颜色配置转换成 uint32 格式的 rgba数据
|
|
||||||
*
|
|
||||||
* @param hex 16 进制的颜色表示,支持的格式有
|
|
||||||
* RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA
|
|
||||||
* @param rgba 返回数据的指针,解析结果会放到这个指针指向的内存中
|
|
||||||
* @return 如果 hex 是一个能正常解析的颜色,返回 true ,否则返回 false
|
|
||||||
*/
|
|
||||||
static bool hex_color_to_rgba(const char *hex, uint32_t *rgba);
|
|
||||||
/**
|
|
||||||
* @brief 将 rgba 通道顺序的颜色转换成 argb 顺序
|
|
||||||
* @description xcb 库用到的颜色需要是 argb 通道顺序
|
|
||||||
*/
|
|
||||||
static uint32_t rgba_to_argb(uint32_t rgba);
|
|
||||||
/**
|
|
||||||
* @brief 提取颜色中的各个颜色通道数值
|
|
||||||
* @param rgba uint32 表示的颜色
|
|
||||||
* @param red red channel, range 0~1
|
|
||||||
* @param green green channel, range 0~1
|
|
||||||
* @param blue blue channel, range 0~1
|
|
||||||
* @param alpha alpha channel, range 0~1
|
|
||||||
*/
|
|
||||||
static void extract_color_channel(const uint32_t rgba, double *red,
|
|
||||||
double *green, double *blue, double *alpha);
|
|
||||||
|
|
||||||
void color_parse(const char *hex, color_t *const color) {
|
|
||||||
bool success = hex_color_to_rgba(hex, &color->rgba);
|
|
||||||
if (!success) fatal("invalid hex color: %s", hex);
|
|
||||||
|
|
||||||
color->argb = rgba_to_argb(color->rgba);
|
|
||||||
extract_color_channel(color->rgba, &color->red, &color->green, &color->blue,
|
|
||||||
&color->alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hex_color_to_rgba(const char *hex, uint32_t *rgba) {
|
|
||||||
if (hex[0] == '#') hex++;
|
|
||||||
|
|
||||||
size_t len = strlen(hex);
|
|
||||||
char extended[9] = {0};
|
|
||||||
uint32_t color = 0x00000000;
|
|
||||||
|
|
||||||
if (len == 3 || len == 4) {
|
|
||||||
extended[0] = extended[1] = hex[0];
|
|
||||||
extended[2] = extended[3] = hex[1];
|
|
||||||
extended[4] = extended[5] = hex[2];
|
|
||||||
if (len == 3) {
|
|
||||||
extended[6] = extended[7] = 'F';
|
|
||||||
} else {
|
|
||||||
extended[6] = extended[7] = hex[3];
|
|
||||||
}
|
|
||||||
} else if (len == 6) {
|
|
||||||
strncpy(extended, hex, len);
|
|
||||||
extended[6] = extended[7] = 'F';
|
|
||||||
} else if (len == 8) {
|
|
||||||
strncpy(extended, hex, len);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
color = strtoul(extended, nullptr, 16) & 0xffffffff;
|
|
||||||
*rgba = color;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define EXTRACE_COLOR_BIT(C, R) (((C) >> (R)) & 0xff)
|
|
||||||
uint32_t rgba_to_argb(uint32_t rgba) {
|
|
||||||
uint32_t r = EXTRACE_COLOR_BIT(rgba, 24);
|
|
||||||
uint32_t g = EXTRACE_COLOR_BIT(rgba, 16);
|
|
||||||
uint32_t b = EXTRACE_COLOR_BIT(rgba, 8);
|
|
||||||
uint32_t a = EXTRACE_COLOR_BIT(rgba, 0);
|
|
||||||
|
|
||||||
uint32_t argb = (a << 24) | (r << 16) | (g << 8) | b;
|
|
||||||
return argb;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define COLOR_SPLIT(C, R) (EXTRACE_COLOR_BIT((C), (R)) / (double)0xff)
|
|
||||||
void extract_color_channel(const uint32_t rgba, double *red, double *green,
|
|
||||||
double *blue, double *alpha) {
|
|
||||||
*red = COLOR_SPLIT(rgba, 24);
|
|
||||||
*green = COLOR_SPLIT(rgba, 16);
|
|
||||||
*blue = COLOR_SPLIT(rgba, 8);
|
|
||||||
*alpha = COLOR_SPLIT(rgba, 0);
|
|
||||||
}
|
|
||||||
22
src/color.h
22
src/color.h
@@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef struct color_t {
|
|
||||||
uint32_t rgba; /* use for human reading */
|
|
||||||
uint32_t argb; /* use for xcb */
|
|
||||||
|
|
||||||
/* channels of color, use for cairo */
|
|
||||||
double red; /* red channel */
|
|
||||||
double green; /* green channel */
|
|
||||||
double blue; /* blue channel */
|
|
||||||
double alpha; /* alpha channel */
|
|
||||||
} color_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief parsing color represented in hexadecimal format
|
|
||||||
* @param hex color represented in hexadecimal format, the supported format
|
|
||||||
have: RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA
|
|
||||||
* @param color return the parsed color
|
|
||||||
*/
|
|
||||||
void color_parse(const char *hex, color_t *const color);
|
|
||||||
81
src/config.c
81
src/config.c
@@ -1,81 +0,0 @@
|
|||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "default_config.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
void config_status_set_gap(config_status_t *status_config, uint32_t gap,
|
|
||||||
uint32_t item_gap) {
|
|
||||||
if (!status_config) fatal("no valid status config");
|
|
||||||
status_config->status_gap = gap;
|
|
||||||
status_config->status_item_gap = item_gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
void config_status_add_item(config_status_t **status_config,
|
|
||||||
config_status_item_t status_item) {
|
|
||||||
const auto item_count = (*status_config)->status_count + 1;
|
|
||||||
const auto new_size =
|
|
||||||
sizeof(config_status_t) + item_count * sizeof(config_status_item_t);
|
|
||||||
xrealloc((void **)status_config, (ssize_t)new_size);
|
|
||||||
(*status_config)->list[item_count - 1] = status_item;
|
|
||||||
(*status_config)->status_count = item_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void config_status_filter_item(config_status_t **status_config,
|
|
||||||
status_item_filter filter) {
|
|
||||||
if (!status_config || !*status_config) fatal("no valid status config");
|
|
||||||
if (!filter) return;
|
|
||||||
|
|
||||||
config_status_t *config = *status_config;
|
|
||||||
size_t write_idx = 0;
|
|
||||||
size_t item_count = config->status_count;
|
|
||||||
|
|
||||||
for (size_t read_idx = 0; read_idx < item_count; read_idx++) {
|
|
||||||
config_status_item_t *item = &config->list[read_idx];
|
|
||||||
if (!filter(item)) continue;
|
|
||||||
if (write_idx != read_idx) {
|
|
||||||
config->list[write_idx] = *item;
|
|
||||||
}
|
|
||||||
write_idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write_idx == item_count) return;
|
|
||||||
|
|
||||||
config->status_count = write_idx;
|
|
||||||
size_t new_size =
|
|
||||||
sizeof(config_status_t) + write_idx * sizeof(config_status_item_t);
|
|
||||||
xrealloc((void **)status_config, (ssize_t)new_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static config_status_t *config_create_default_status(void) {
|
|
||||||
size_t item_count = countof(status_list);
|
|
||||||
size_t size = sizeof(config_status_t) + item_count * sizeof(status_list[0]);
|
|
||||||
config_status_t *status = xmalloc((ssize_t)size);
|
|
||||||
|
|
||||||
status->status_gap = status_config.status_gap;
|
|
||||||
status->status_item_gap = status_config.status_item_gap;
|
|
||||||
status->status_count = item_count;
|
|
||||||
memcpy(status->list, status_list, item_count * sizeof(status_list[0]));
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static config_t runtime_config = {};
|
|
||||||
|
|
||||||
const config_t *init_config(void) {
|
|
||||||
if (!runtime_config.status) {
|
|
||||||
runtime_config.status = config_create_default_status();
|
|
||||||
}
|
|
||||||
if (!runtime_config.status_renderer) {
|
|
||||||
runtime_config.status_renderer = renderer_render_status;
|
|
||||||
}
|
|
||||||
return &runtime_config;
|
|
||||||
}
|
|
||||||
|
|
||||||
void config_set_custom_status_renderer(config_t *config,
|
|
||||||
status_renderer_t status_renderer) {
|
|
||||||
if (!config) fatal("no valid config");
|
|
||||||
config->status_renderer =
|
|
||||||
status_renderer ? status_renderer : renderer_render_status;
|
|
||||||
}
|
|
||||||
57
src/config.h
57
src/config.h
@@ -1,57 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "renderer.h"
|
|
||||||
#include "status.h"
|
|
||||||
|
|
||||||
typedef struct config_status_item_t {
|
|
||||||
status_type_t type;
|
|
||||||
icon_type_t icon_type;
|
|
||||||
const char *icon;
|
|
||||||
const char *color;
|
|
||||||
} config_status_item_t;
|
|
||||||
|
|
||||||
typedef struct config_status_t {
|
|
||||||
uint32_t status_gap;
|
|
||||||
uint32_t status_item_gap;
|
|
||||||
size_t status_count;
|
|
||||||
config_status_item_t list[];
|
|
||||||
} config_status_t;
|
|
||||||
|
|
||||||
void config_status_set_gap(config_status_t *status_config, uint32_t gap,
|
|
||||||
uint32_t item_gap);
|
|
||||||
|
|
||||||
void config_status_add_item(config_status_t **status_config,
|
|
||||||
config_status_item_t status_item);
|
|
||||||
|
|
||||||
typedef bool (*status_item_filter)(config_status_item_t *item);
|
|
||||||
/**
|
|
||||||
* @brief 按过滤条件原地筛选 status 项并保持原有顺序
|
|
||||||
*
|
|
||||||
* 当 filter(item) 返回 true 时保留该项,返回 false 时删除该项。
|
|
||||||
* 筛选后会压缩 list、更新 status_count,并收缩配置对象内存。
|
|
||||||
*
|
|
||||||
* @param status_config 配置指针地址(函数内部会重分配内存)
|
|
||||||
* @param filter 过滤函数
|
|
||||||
*/
|
|
||||||
void config_status_filter_item(config_status_t **status_config,
|
|
||||||
status_item_filter filter);
|
|
||||||
|
|
||||||
typedef void (*status_renderer_t)(config_status_t *config, size_t config_count,
|
|
||||||
status_t *status, renderer_status_t *output);
|
|
||||||
|
|
||||||
typedef struct config_t {
|
|
||||||
config_status_t *status;
|
|
||||||
status_renderer_t status_renderer;
|
|
||||||
} config_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化配置对象
|
|
||||||
* @return 返回静态变量指针,不需要释放
|
|
||||||
*/
|
|
||||||
const config_t *init_config(void);
|
|
||||||
|
|
||||||
void config_set_custom_status_renderer(config_t *config,
|
|
||||||
status_renderer_t status_renderer);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.clang-format
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.clang-format
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <X11/XF86keysym.h>
|
|
||||||
#include <X11/keysym.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "action.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "layout.h"
|
|
||||||
#include "renderer.h"
|
|
||||||
#include "status.h"
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
static const char *const font_family = "monospace";
|
|
||||||
static const int font_size = 10;
|
|
||||||
static const int default_dpi = 144;
|
|
||||||
|
|
||||||
static const char *const tags[] = {
|
|
||||||
"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 tag_x_padding = 10;
|
|
||||||
|
|
||||||
static const char *const bar_bg = "#222222";
|
|
||||||
static const char *const tag_bg = "#222222";
|
|
||||||
static const char *const active_tag_bg = "#005577";
|
|
||||||
static const char *const tag_color = "#bbbbbb";
|
|
||||||
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 config_status_item_t status_list[] = {
|
|
||||||
{
|
|
||||||
.type = status_net_down,
|
|
||||||
.icon_type = icon_type_image,
|
|
||||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/"
|
|
||||||
"corner-left-down-line.png",
|
|
||||||
.color = "#87af5f",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.type = status_net_up,
|
|
||||||
.icon_type = icon_type_image,
|
|
||||||
.icon =
|
|
||||||
"/home/zedhugh/develop/zswm/src/resources/icons/corner-right-up-line.png",
|
|
||||||
.color = "#e54c62",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.type = status_audio,
|
|
||||||
.icon_type = icon_type_image,
|
|
||||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/volume-up-fill.png",
|
|
||||||
.color = "#7493d2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.type = status_memory,
|
|
||||||
.icon_type = icon_type_image,
|
|
||||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/ram-line.png",
|
|
||||||
.color = "#e0da37",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.type = status_cpu,
|
|
||||||
.icon_type = icon_type_image,
|
|
||||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/cpu-line.png",
|
|
||||||
.color = "#e33a6e",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.type = status_time,
|
|
||||||
.icon_type = icon_type_image,
|
|
||||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/time-line.png",
|
|
||||||
.color = "#7788af",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
static const config_status_t status_config = {
|
|
||||||
.status_gap = 10,
|
|
||||||
.status_item_gap = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const config_t config = {
|
|
||||||
.status = (config_status_t *)&status_config,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const button_t button_list[] = {
|
|
||||||
{click_tag, modifier_none, button_left, select_tag_of_current_monitor, {0}},
|
|
||||||
{
|
|
||||||
click_client_name,
|
|
||||||
modifier_none,
|
|
||||||
button_left,
|
|
||||||
change_client_state_dwim,
|
|
||||||
{0},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#define TAGKEYS(KEY, TAG) \
|
|
||||||
{modifier_super, KEY, select_tag_of_current_monitor, {.ui = TAG}}, { \
|
|
||||||
modifier_super | modifier_shift, KEY, send_client_to_tag, {.ui = TAG}, \
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char launcher[] =
|
|
||||||
"rofi -show combi -modes combi -combi-modes window,drun,run,ssh,windowcd";
|
|
||||||
static const char terminal[] = "xterm";
|
|
||||||
static const char terminal_class[] = "XTerm";
|
|
||||||
static const char editor[] = "emacsclient -a '' -r -n";
|
|
||||||
static const char editor_class[] = "Emacs";
|
|
||||||
static const char browser[] = "firefox-bin";
|
|
||||||
static const char browser_class[] = "firefox";
|
|
||||||
static const char chrome[] = "google-chrome-stable";
|
|
||||||
static const char chrome_class[] = "Google-chrome";
|
|
||||||
static const keyboard_t key_list[] = {
|
|
||||||
{modifier_super, XK_r, spawn, {.ptr = launcher}},
|
|
||||||
{modifier_super, XK_Return, spawn, {.ptr = terminal}},
|
|
||||||
{
|
|
||||||
modifier_control | modifier_alt,
|
|
||||||
XK_r,
|
|
||||||
raise_or_run,
|
|
||||||
{.ptr = (const char *[]){terminal_class, terminal}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modifier_super,
|
|
||||||
XK_e,
|
|
||||||
raise_or_run,
|
|
||||||
{.ptr = (const char *[]){editor_class, editor}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modifier_super,
|
|
||||||
XK_q,
|
|
||||||
raise_or_run,
|
|
||||||
{.ptr = (const char *[]){browser_class, browser}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
modifier_super,
|
|
||||||
XK_a,
|
|
||||||
raise_or_run,
|
|
||||||
{.ptr = (const char *[]){chrome_class, chrome}},
|
|
||||||
},
|
|
||||||
{modifier_super | modifier_shift, XK_q, quit, {.b = false}},
|
|
||||||
{modifier_super | modifier_control, XK_r, quit, {.b = true}},
|
|
||||||
{modifier_super | modifier_shift,
|
|
||||||
XK_j,
|
|
||||||
focus_client_in_same_tag,
|
|
||||||
{.b = true}},
|
|
||||||
{modifier_super | modifier_shift,
|
|
||||||
XK_k,
|
|
||||||
focus_client_in_same_tag,
|
|
||||||
{.b = false}},
|
|
||||||
|
|
||||||
TAGKEYS(XK_1, 1 << 0),
|
|
||||||
TAGKEYS(XK_2, 1 << 1),
|
|
||||||
TAGKEYS(XK_3, 1 << 2),
|
|
||||||
TAGKEYS(XK_4, 1 << 3),
|
|
||||||
TAGKEYS(XK_5, 1 << 4),
|
|
||||||
TAGKEYS(XK_6, 1 << 5),
|
|
||||||
TAGKEYS(XK_7, 1 << 6),
|
|
||||||
TAGKEYS(XK_8, 1 << 7),
|
|
||||||
TAGKEYS(XK_9, 1 << 8),
|
|
||||||
|
|
||||||
{modifier_super, XK_o, send_client_to_next_monitor, {0}},
|
|
||||||
|
|
||||||
{modifier_super | modifier_control, XK_j, focus_next_monitor, {0}},
|
|
||||||
{modifier_super | modifier_control, XK_space, toggle_client_floating, {0}},
|
|
||||||
{modifier_super, XK_f, toggle_client_fullscreen, {0}},
|
|
||||||
{modifier_super, XK_m, toggle_client_maximize, {0}},
|
|
||||||
{modifier_super | modifier_shift, XK_c, kill_client, {0}},
|
|
||||||
|
|
||||||
{modifier_super, XK_Up, change_volume, {.i = 1}},
|
|
||||||
{modifier_super, XK_Down, change_volume, {.i = -1}},
|
|
||||||
{modifier_super | modifier_shift, XK_m, toggle_mute, {0}},
|
|
||||||
{modifier_none, XF86XK_AudioRaiseVolume, change_volume, {.i = 1}},
|
|
||||||
{modifier_none, XF86XK_AudioLowerVolume, change_volume, {.i = -1}},
|
|
||||||
{modifier_none, XF86XK_AudioMute, toggle_mute, {0}},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
static const layout_t layout_list[] = {
|
|
||||||
{.symbol = "[]=", .arrange = tile},
|
|
||||||
{.symbol = "[M]", .arrange = monocle},
|
|
||||||
{.symbol = "><=", .arrange = nullptr},
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char *const autostart_list[] = {
|
|
||||||
"gentoo-pipewire-launcher restart",
|
|
||||||
"picom -b --backend xrender",
|
|
||||||
"fcitx5 -d",
|
|
||||||
nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const rule_t rules[] = {
|
|
||||||
{.role = "dialog", .tag_index = -1, .floating = true},
|
|
||||||
{.class = "firefox", .role = "About", .tag_index = 1, .floating = true},
|
|
||||||
{.class = "firefox", .role = "browser", .tag_index = 1, .maximize = true},
|
|
||||||
{.class = "Google-chrome", .tag_index = 1, .maximize = true},
|
|
||||||
{.class = "mpv", .tag_index = 11, .switch_to_tag = true, .fullscreen = true},
|
|
||||||
{.class = "Emacs", .tag_index = 10, .switch_to_tag = true, .maximize = true},
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char *const wallpapers[] = {"~/bg/*", "~/Downloads/bg/*"};
|
|
||||||
/* static const char *const wallpapers[] = {}; */
|
|
||||||
static const uint32_t wallpaper_interval = 0;
|
|
||||||
387
src/event.c
387
src/event.c
@@ -1,387 +0,0 @@
|
|||||||
#include "event.h"
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_event.h>
|
|
||||||
#include <xcb/xcb_icccm.h>
|
|
||||||
#include <xcb/xcb_keysyms.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
|
|
||||||
#include "action.h"
|
|
||||||
#include "atoms-extern.h"
|
|
||||||
#include "base.h"
|
|
||||||
#include "client.h"
|
|
||||||
#include "default_config.h"
|
|
||||||
#include "monitor.h"
|
|
||||||
#include "tray.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wm.h"
|
|
||||||
#include "xkb.h"
|
|
||||||
#include "xwindow.h"
|
|
||||||
|
|
||||||
static void button_press(xcb_button_press_event_t *ev) {
|
|
||||||
click_area_t click_area = click_none;
|
|
||||||
user_action_arg_t arg = {0};
|
|
||||||
|
|
||||||
monitor_t *monitor = nullptr;
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
|
||||||
if (ev->event == m->bar_window) {
|
|
||||||
monitor = m;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!monitor) return;
|
|
||||||
|
|
||||||
wm_set_current_monitor(monitor, false);
|
|
||||||
if (ev->event_x >= monitor->tag_extent.start &&
|
|
||||||
ev->event_x <= monitor->tag_extent.end) {
|
|
||||||
click_area = click_tag;
|
|
||||||
|
|
||||||
for (tag_t *tag = monitor->tag_list; tag; tag = tag->next) {
|
|
||||||
if (ev->event_x >= tag->bar_extent.start &&
|
|
||||||
ev->event_x <= tag->bar_extent.end) {
|
|
||||||
arg.ui = tag->mask;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (click_area == click_none) {
|
|
||||||
for (task_in_tag_t *task = monitor->selected_tag->task_list; task;
|
|
||||||
task = task->next) {
|
|
||||||
if (ev->event_x >= task->bar_extent.start &&
|
|
||||||
ev->event_x <= task->bar_extent.end) {
|
|
||||||
click_area = click_client_name;
|
|
||||||
arg.ptr = task->client;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (click_area == click_none) return;
|
|
||||||
|
|
||||||
for (int i = 0; i < countof(button_list); i++) {
|
|
||||||
if (click_area == button_list[i].click_area &&
|
|
||||||
button_list[i].button == ev->detail &&
|
|
||||||
button_list[i].modifiers == ev->state) {
|
|
||||||
button_list[i].func(&arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void configure_request(xcb_configure_request_event_t *ev) {
|
|
||||||
if (tray_handle_configure_request(ev)) return;
|
|
||||||
|
|
||||||
client_t *client = client_get_by_window(ev->window);
|
|
||||||
if (client) {
|
|
||||||
if (ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
|
|
||||||
client_change_border_width(client, ev->border_width);
|
|
||||||
} else {
|
|
||||||
tag_t *tag = client->monitor->selected_tag;
|
|
||||||
task_in_tag_t *task = client_get_task_in_tag(client, tag);
|
|
||||||
if (!task || !(client->floating || !tag->layout->arrange)) return;
|
|
||||||
|
|
||||||
if (ev->value_mask & XCB_CONFIG_WINDOW_X) task->geometry.x = ev->x;
|
|
||||||
if (ev->value_mask & XCB_CONFIG_WINDOW_Y) task->geometry.y = ev->y;
|
|
||||||
if (ev->value_mask & XCB_CONFIG_WINDOW_WIDTH)
|
|
||||||
task->geometry.width = ev->width;
|
|
||||||
if (ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT)
|
|
||||||
task->geometry.height = ev->height;
|
|
||||||
client_apply_geometry(client, task->geometry);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uint16_t value_mask = ev->value_mask;
|
|
||||||
xcb_configure_window_value_list_t value_list = {
|
|
||||||
.x = ev->x,
|
|
||||||
.y = ev->y,
|
|
||||||
.width = ev->width,
|
|
||||||
.height = ev->height,
|
|
||||||
.border_width = ev->border_width,
|
|
||||||
.sibling = ev->sibling,
|
|
||||||
.stack_mode = ev->stack_mode,
|
|
||||||
};
|
|
||||||
xcb_configure_window_aux(wm.xcb_conn, ev->window, value_mask, &value_list);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void key_press(xcb_key_press_event_t *ev) {
|
|
||||||
bool is_press = XCB_EVENT_RESPONSE_TYPE(ev) == XCB_KEY_PRESS;
|
|
||||||
xcb_keysym_t keysym = xcb_key_press_lookup_keysym(wm.key_symbols, ev, 0);
|
|
||||||
char key_name[16];
|
|
||||||
if (xkb_keysym_get_name(keysym, key_name, sizeof(key_name)) != -1) {
|
|
||||||
logger("key %s: %s\n", is_press ? "press" : "release", key_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_press) return;
|
|
||||||
|
|
||||||
for (int i = 0; i < countof(key_list); i++) {
|
|
||||||
keyboard_t key = key_list[i];
|
|
||||||
if (keysym == key.keysym && ev->state == key.modifiers && key.func) {
|
|
||||||
/* 查询光标所在屏幕并将其设置为当前屏幕 */
|
|
||||||
point_t point = {.x = ev->root_x, .y = ev->root_y};
|
|
||||||
monitor_t *monitor = wm_get_monitor_by_point(point);
|
|
||||||
wm_set_current_monitor(monitor, false);
|
|
||||||
|
|
||||||
key.func(&key.arg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void map_request(xcb_map_request_event_t *ev) {
|
|
||||||
if (tray_handle_map_request(ev)) return;
|
|
||||||
|
|
||||||
xcb_get_window_attributes_reply_t *wa_reply =
|
|
||||||
xwindow_get_attributes_reply(ev->window);
|
|
||||||
|
|
||||||
if (!wa_reply) return;
|
|
||||||
if (wa_reply->override_redirect) {
|
|
||||||
p_delete(&wa_reply);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_t *c = client_get_by_window(ev->window);
|
|
||||||
if (!c) {
|
|
||||||
xcb_get_geometry_reply_t *geo_reply =
|
|
||||||
xwindow_get_geometry_reply(ev->window);
|
|
||||||
if (!geo_reply) {
|
|
||||||
p_delete(&wa_reply);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_manage(ev->window, geo_reply);
|
|
||||||
p_delete(&geo_reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
p_delete(&wa_reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef enum _NET_WM_STATE_ACTION : uint32_t {
|
|
||||||
_NET_WM_STATE_ADD = 1,
|
|
||||||
_NET_WM_STATE_REMOVE = 0,
|
|
||||||
_NET_WM_STATE_TOGGLE = 2,
|
|
||||||
} _NET_WM_STATE_ACTION;
|
|
||||||
|
|
||||||
static inline bool get_bool_property(bool init_property,
|
|
||||||
_NET_WM_STATE_ACTION action) {
|
|
||||||
switch (action) {
|
|
||||||
case _NET_WM_STATE_ADD:
|
|
||||||
return true;
|
|
||||||
case _NET_WM_STATE_REMOVE:
|
|
||||||
return false;
|
|
||||||
case _NET_WM_STATE_TOGGLE:
|
|
||||||
return !init_property;
|
|
||||||
default:
|
|
||||||
fatal(
|
|
||||||
"Invalid _NET_WM_STATE_ACTION: %u\n"
|
|
||||||
"Only supported:"
|
|
||||||
"_NET_WM_STATE_ADD: %u"
|
|
||||||
"_NET_WM_STATE_REMOVE: %u"
|
|
||||||
"_NET_WM_STATE_TOGGLE: %u",
|
|
||||||
action, _NET_WM_STATE_ADD, _NET_WM_STATE_REMOVE, _NET_WM_STATE_TOGGLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_message(xcb_client_message_event_t *ev) {
|
|
||||||
if (tray_handle_client_message(ev)) return;
|
|
||||||
|
|
||||||
client_t *c = client_get_by_window(ev->window);
|
|
||||||
if (c == nullptr) return;
|
|
||||||
|
|
||||||
if (ev->type == _NET_ACTIVE_WINDOW) {
|
|
||||||
for (tag_t *t = c->monitor->tag_list; t; t = t->next) {
|
|
||||||
if ((t->mask & c->tags) == 0) continue;
|
|
||||||
|
|
||||||
logger("== _NET_ACTIVE_WINDOW: %s[%u]\n", c->class, ev->data.data32[0]);
|
|
||||||
|
|
||||||
switch (ev->data.data32[0]) {
|
|
||||||
case 2: /* 来自 pager */
|
|
||||||
wm_set_current_monitor(c->monitor, true);
|
|
||||||
monitor_select_tag(c->monitor, t->mask);
|
|
||||||
client_focus(c);
|
|
||||||
break;
|
|
||||||
case 1: /* 来自应用程序 */
|
|
||||||
c->urgent = true;
|
|
||||||
monitor_draw_bar(c->monitor);
|
|
||||||
break;
|
|
||||||
case 0: /* 来自老版本标志 */
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (ev->type == _NET_WM_STATE) {
|
|
||||||
/**
|
|
||||||
* _NET_WM_STATE 协议数据格式为
|
|
||||||
* data.data32 = {
|
|
||||||
* action, // 操作类型(添加/移除/切换)
|
|
||||||
* property1, // 第一个状态原子(如_NET_WM_STATE_FULLSCREEN)
|
|
||||||
* property2, // 第二个状态原子(可选,通常为0)
|
|
||||||
* 0, // 预留字段(固定为0)
|
|
||||||
* 0 // 预留字段(固定为0)
|
|
||||||
* };
|
|
||||||
* action 值可能为
|
|
||||||
* _NET_WM_STATE_ADD(1):添加全屏状态,窗口进入全屏模式
|
|
||||||
* _NET_WM_STATE_REMOVE(0):移除全屏状态,窗口退出全屏模式
|
|
||||||
* _NET_WM_STATE_TOGGLE(2):切换全屏状态(若当前全屏则退出,反之进入)
|
|
||||||
*/
|
|
||||||
_NET_WM_STATE_ACTION action = ev->data.data32[0];
|
|
||||||
|
|
||||||
if (ev->data.data32[1] == _NET_WM_STATE_FULLSCREEN ||
|
|
||||||
ev->data.data32[2] == _NET_WM_STATE_FULLSCREEN) {
|
|
||||||
client_set_fullscreen(c, get_bool_property(c->fullscreen, action));
|
|
||||||
} else if (ev->data.data32[1] == _NET_WM_STATE_MAXIMIZED_HORZ ||
|
|
||||||
ev->data.data32[1] == _NET_WM_STATE_MAXIMIZED_VERT ||
|
|
||||||
ev->data.data32[2] == _NET_WM_STATE_MAXIMIZED_HORZ ||
|
|
||||||
ev->data.data32[2] == _NET_WM_STATE_MAXIMIZED_VERT) {
|
|
||||||
client_set_maximize(c, get_bool_property(c->maximize, action));
|
|
||||||
}
|
|
||||||
} else if (ev->type == WM_CHANGE_STATE) {
|
|
||||||
switch (ev->data.data32[0]) {
|
|
||||||
case XCB_ICCCM_WM_STATE_ICONIC:
|
|
||||||
client_set_minimize(c, true);
|
|
||||||
break;
|
|
||||||
case XCB_ICCCM_WM_STATE_NORMAL:
|
|
||||||
client_set_minimize(c, false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void property_notify(xcb_property_notify_event_t *ev) {
|
|
||||||
if (tray_handle_property_notify(ev)) return;
|
|
||||||
|
|
||||||
client_t *client = client_get_by_window(ev->window);
|
|
||||||
if (client == nullptr) return;
|
|
||||||
|
|
||||||
if (ev->atom == WM_NAME) {
|
|
||||||
xwindow_get_text_property(ev->window, ev->atom, &client->name);
|
|
||||||
monitor_draw_bar(client->monitor);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
} else if (ev->atom == _NET_WM_NAME) {
|
|
||||||
xwindow_get_text_property(ev->window, ev->atom, &client->net_name);
|
|
||||||
monitor_draw_bar(client->monitor);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
} else if (ev->atom == XCB_ATOM_WM_HINTS) {
|
|
||||||
client_update_wm_hints(client);
|
|
||||||
monitor_draw_bar(client->monitor);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void destroy_notify(xcb_destroy_notify_event_t *ev) {
|
|
||||||
if (tray_handle_destroy_notify(ev)) return;
|
|
||||||
|
|
||||||
client_t *client = client_get_by_window(ev->window);
|
|
||||||
if (client) client_unmanage(client, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unmap_notify(xcb_unmap_notify_event_t *ev) {
|
|
||||||
if (tray_handle_unmap_notify(ev)) return;
|
|
||||||
|
|
||||||
client_t *client = client_get_by_window(ev->window);
|
|
||||||
if (!client || client->minimize) return;
|
|
||||||
|
|
||||||
if (XCB_EVENT_SENT(ev)) {
|
|
||||||
xwindow_set_state(ev->window, XCB_ICCCM_WM_STATE_WITHDRAWN);
|
|
||||||
} else {
|
|
||||||
client_unmanage(client, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void map_notify(xcb_map_notify_event_t *ev) {
|
|
||||||
/* 锁屏重新进入桌面后绘制 bar 内容以免 bar 不显示内容 */
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
|
||||||
if (ev->window == m->bar_window) {
|
|
||||||
monitor_draw_bar(m);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void expose(xcb_expose_event_t *ev) {
|
|
||||||
if (ev->count > 0) return;
|
|
||||||
|
|
||||||
monitor_t *monitor = wm_get_monitor_by_window(ev->window);
|
|
||||||
monitor_draw_bar(monitor);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void enter_notify(xcb_enter_notify_event_t *ev) {
|
|
||||||
if (wm_should_ignore_enter_notify(ev)) return;
|
|
||||||
|
|
||||||
client_t *client = client_get_by_window(ev->event);
|
|
||||||
if (!client || wm.client_focused == client) return;
|
|
||||||
|
|
||||||
client_focus(client);
|
|
||||||
wm_set_current_monitor(client->monitor, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void selection_clear(xcb_selection_clear_event_t *ev) {
|
|
||||||
tray_handle_selection_clear(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle_xcb_event(xcb_generic_event_t *event) {
|
|
||||||
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(event);
|
|
||||||
const char *label = xcb_event_get_label(event_type);
|
|
||||||
logger("event type: %u[%s], xkb_event: %u\n", event_type, label,
|
|
||||||
wm.event_base_xkb);
|
|
||||||
|
|
||||||
switch (event_type) {
|
|
||||||
#define EVENT(type, callback) \
|
|
||||||
case type: \
|
|
||||||
callback((void *)event); \
|
|
||||||
return
|
|
||||||
|
|
||||||
EVENT(XCB_BUTTON_PRESS, button_press);
|
|
||||||
EVENT(XCB_KEY_PRESS, key_press);
|
|
||||||
EVENT(XCB_KEY_RELEASE, key_press);
|
|
||||||
EVENT(XCB_CONFIGURE_REQUEST, configure_request);
|
|
||||||
EVENT(XCB_MAP_REQUEST, map_request);
|
|
||||||
EVENT(XCB_MAP_NOTIFY, map_notify);
|
|
||||||
EVENT(XCB_EXPOSE, expose);
|
|
||||||
EVENT(XCB_CLIENT_MESSAGE, client_message);
|
|
||||||
EVENT(XCB_PROPERTY_NOTIFY, property_notify);
|
|
||||||
EVENT(XCB_UNMAP_NOTIFY, unmap_notify);
|
|
||||||
EVENT(XCB_DESTROY_NOTIFY, destroy_notify);
|
|
||||||
EVENT(XCB_ENTER_NOTIFY, enter_notify);
|
|
||||||
EVENT(XCB_SELECTION_CLEAR, selection_clear);
|
|
||||||
|
|
||||||
#undef EVENT
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 处理 XKB 事件 */
|
|
||||||
if (wm.event_base_xkb != 0 && event_type == wm.event_base_xkb) {
|
|
||||||
xkb_handle_event(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean xcb_event_loop(GIOChannel *channel, GIOCondition condition,
|
|
||||||
gpointer user_data) {
|
|
||||||
if (condition & (G_IO_HUP | G_IO_ERR)) {
|
|
||||||
wm_quit();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_generic_event_t *event = nullptr;
|
|
||||||
while ((event = xcb_poll_for_event(wm.xcb_conn))) {
|
|
||||||
handle_xcb_event(event);
|
|
||||||
p_delete(&event);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup_event_loop(void) {
|
|
||||||
int fd = xcb_get_file_descriptor(wm.xcb_conn);
|
|
||||||
GIOChannel *channel = g_io_channel_unix_new(fd);
|
|
||||||
g_io_channel_set_encoding(channel, nullptr, nullptr);
|
|
||||||
GIOCondition cond = G_IO_IN | G_IO_HUP | G_IO_ERR;
|
|
||||||
g_io_add_watch(channel, cond, xcb_event_loop, nullptr);
|
|
||||||
g_io_channel_unref(channel);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
void setup_event_loop(void);
|
|
||||||
171
src/image.c
171
src/image.c
@@ -1,171 +0,0 @@
|
|||||||
#include "image.h"
|
|
||||||
|
|
||||||
#include <Imlib2.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
typedef struct image_cache_entry_t {
|
|
||||||
char *path;
|
|
||||||
cairo_surface_t *surface;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
} image_cache_entry_t;
|
|
||||||
|
|
||||||
static image_cache_entry_t *image_cache = nullptr;
|
|
||||||
static size_t image_cache_count = 0;
|
|
||||||
static cairo_user_data_key_t image_surface_data_key;
|
|
||||||
|
|
||||||
static inline uint32_t premultiply_argb(uint32_t pixel) {
|
|
||||||
uint8_t a = (pixel >> 24) & 0xff;
|
|
||||||
uint8_t r = (pixel >> 16) & 0xff;
|
|
||||||
uint8_t g = (pixel >> 8) & 0xff;
|
|
||||||
uint8_t b = pixel & 0xff;
|
|
||||||
r = (uint8_t)((r * a + 127) / 255);
|
|
||||||
g = (uint8_t)((g * a + 127) / 255);
|
|
||||||
b = (uint8_t)((b * a + 127) / 255);
|
|
||||||
return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) |
|
|
||||||
(uint32_t)b;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool image_load_surface(const char *path, cairo_surface_t **surface,
|
|
||||||
int *width, int *height) {
|
|
||||||
if (!path || !*path || !surface || !width || !height) return false;
|
|
||||||
|
|
||||||
Imlib_Image image = imlib_load_image(path);
|
|
||||||
if (!image) return false;
|
|
||||||
|
|
||||||
imlib_context_set_image(image);
|
|
||||||
int img_width = imlib_image_get_width();
|
|
||||||
int img_height = imlib_image_get_height();
|
|
||||||
if (img_width <= 0 || img_height <= 0) {
|
|
||||||
imlib_free_image();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DATA32 *src = imlib_image_get_data_for_reading_only();
|
|
||||||
if (!src) {
|
|
||||||
imlib_free_image();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t pixel_count = (size_t)img_width * (size_t)img_height;
|
|
||||||
uint32_t *dst = xmalloc((ssize_t)(pixel_count * sizeof(uint32_t)));
|
|
||||||
for (size_t i = 0; i < pixel_count; i++) {
|
|
||||||
dst[i] = premultiply_argb(src[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
cairo_surface_t *img_surface = cairo_image_surface_create_for_data(
|
|
||||||
(unsigned char *)dst, CAIRO_FORMAT_ARGB32, img_width, img_height,
|
|
||||||
img_width * (int)sizeof(uint32_t));
|
|
||||||
cairo_status_t status = cairo_surface_status(img_surface);
|
|
||||||
if (status != CAIRO_STATUS_SUCCESS) {
|
|
||||||
cairo_surface_destroy(img_surface);
|
|
||||||
p_delete(&dst);
|
|
||||||
imlib_free_image();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cairo_surface_set_user_data(img_surface, &image_surface_data_key, dst, free);
|
|
||||||
*surface = img_surface;
|
|
||||||
*width = img_width;
|
|
||||||
*height = img_height;
|
|
||||||
|
|
||||||
imlib_free_image();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static image_cache_entry_t *image_cache_find(const char *path) {
|
|
||||||
if (!path || !*path) return nullptr;
|
|
||||||
for (size_t i = 0; i < image_cache_count; i++) {
|
|
||||||
image_cache_entry_t *entry = &image_cache[i];
|
|
||||||
if (!entry->path) continue;
|
|
||||||
if (strcmp(entry->path, path) == 0) return entry;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static image_cache_entry_t *image_cache_add(const char *path) {
|
|
||||||
if (!path || !*path) return nullptr;
|
|
||||||
|
|
||||||
size_t new_count = image_cache_count + 1;
|
|
||||||
xrealloc((void **)&image_cache,
|
|
||||||
(ssize_t)(new_count * sizeof(image_cache_entry_t)));
|
|
||||||
image_cache_entry_t *entry = &image_cache[image_cache_count];
|
|
||||||
entry->path = strdup(path);
|
|
||||||
entry->surface = nullptr;
|
|
||||||
entry->width = 0;
|
|
||||||
entry->height = 0;
|
|
||||||
image_cache_count = new_count;
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
static image_cache_entry_t *image_cache_get(const char *path) {
|
|
||||||
image_cache_entry_t *entry = image_cache_find(path);
|
|
||||||
if (entry && entry->surface) return entry;
|
|
||||||
if (!entry) entry = image_cache_add(path);
|
|
||||||
if (!entry) return nullptr;
|
|
||||||
|
|
||||||
if (entry->surface) {
|
|
||||||
cairo_surface_destroy(entry->surface);
|
|
||||||
entry->surface = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!image_load_surface(path, &entry->surface, &entry->width,
|
|
||||||
&entry->height)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool image_get_scaled_size(const char *path, int32_t target_height, int *width,
|
|
||||||
int *height) {
|
|
||||||
if (!width || !height || target_height <= 0) return false;
|
|
||||||
image_cache_entry_t *entry = image_cache_get(path);
|
|
||||||
if (!entry || !entry->surface || entry->width <= 0 || entry->height <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*height = target_height;
|
|
||||||
*width = (entry->width * target_height + entry->height / 2) / entry->height;
|
|
||||||
if (*width <= 0) *width = 1;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool image_draw(cairo_t *cr, const char *path, int32_t x, int32_t y,
|
|
||||||
int32_t width, int32_t height) {
|
|
||||||
if (!cr || width <= 0 || height <= 0) return false;
|
|
||||||
image_cache_entry_t *entry = image_cache_get(path);
|
|
||||||
if (!entry || !entry->surface || entry->width <= 0 || entry->height <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cairo_save(cr);
|
|
||||||
cairo_translate(cr, (double)x, (double)y);
|
|
||||||
cairo_scale(cr, (double)width / (double)entry->width,
|
|
||||||
(double)height / (double)entry->height);
|
|
||||||
cairo_set_source_surface(cr, entry->surface, 0, 0);
|
|
||||||
cairo_paint(cr);
|
|
||||||
cairo_restore(cr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void image_cache_clean(void) {
|
|
||||||
if (!image_cache) return;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < image_cache_count; i++) {
|
|
||||||
image_cache_entry_t *entry = &image_cache[i];
|
|
||||||
if (entry->surface) {
|
|
||||||
cairo_surface_destroy(entry->surface);
|
|
||||||
entry->surface = nullptr;
|
|
||||||
}
|
|
||||||
p_delete(&entry->path);
|
|
||||||
entry->width = 0;
|
|
||||||
entry->height = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p_delete(&image_cache);
|
|
||||||
image_cache_count = 0;
|
|
||||||
}
|
|
||||||
36
src/image.h
36
src/image.h
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cairo.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取图片按目标高度缩放后的尺寸
|
|
||||||
*
|
|
||||||
* @param path 图片路径
|
|
||||||
* @param target_height 目标高度(像素)
|
|
||||||
* @param width 返回缩放后的宽度
|
|
||||||
* @param height 返回缩放后的高度
|
|
||||||
* @return 加载并计算成功返回 true
|
|
||||||
*/
|
|
||||||
bool image_get_scaled_size(const char *path, int32_t target_height, int *width,
|
|
||||||
int *height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 在 cairo 上下文中绘制图片
|
|
||||||
*
|
|
||||||
* @param cr cairo 上下文
|
|
||||||
* @param path 图片路径
|
|
||||||
* @param x 绘制起始 x
|
|
||||||
* @param y 绘制起始 y
|
|
||||||
* @param width 绘制宽度
|
|
||||||
* @param height 绘制高度
|
|
||||||
* @return 绘制成功返回 true
|
|
||||||
*/
|
|
||||||
bool image_draw(cairo_t *cr, const char *path, int32_t x, int32_t y,
|
|
||||||
int32_t width, int32_t height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 释放图片缓存
|
|
||||||
*/
|
|
||||||
void image_cache_clean(void);
|
|
||||||
93
src/layout.c
93
src/layout.c
@@ -1,93 +0,0 @@
|
|||||||
#include "layout.h"
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
#include "client.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
void monocle(tag_t *tag) {
|
|
||||||
logger("== monocle tag: %s start ==\n", tag->name);
|
|
||||||
for (task_in_tag_t *task = tag->task_list; task; task = task->next) {
|
|
||||||
if (!client_need_layout(task->client)) continue;
|
|
||||||
|
|
||||||
uint16_t border_width = task->client->border_width;
|
|
||||||
area_t workarea = task->client->monitor->workarea;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tile(tag_t *tag) {
|
|
||||||
logger("== tile tag: %s start ==\n", tag->name);
|
|
||||||
uint16_t amount = 0;
|
|
||||||
for (task_in_tag_t *task = tag->task_list; task; task = task->next) {
|
|
||||||
if (client_need_layout(task->client)) ++amount;
|
|
||||||
}
|
|
||||||
if (amount == 0) goto end;
|
|
||||||
|
|
||||||
uint16_t columns = (uint32_t)floor(sqrt(amount));
|
|
||||||
if (columns * (columns + 1) <= amount) ++columns;
|
|
||||||
|
|
||||||
uint32_t rows_in_other_cols = amount / columns;
|
|
||||||
uint32_t rows_in_main_col;
|
|
||||||
while ((rows_in_main_col = amount - (columns - 1) * rows_in_other_cols) >
|
|
||||||
rows_in_other_cols) {
|
|
||||||
++rows_in_other_cols;
|
|
||||||
}
|
|
||||||
|
|
||||||
area_t *workarea = &tag->task_list->client->monitor->workarea;
|
|
||||||
|
|
||||||
uint16_t width_avg = workarea->width / columns;
|
|
||||||
uint16_t width_for_main_col = workarea->width - (columns - 1) * width_avg;
|
|
||||||
uint16_t width_for_other_cols = width_avg;
|
|
||||||
|
|
||||||
uint16_t i = 0, row = 0, col = 0, row_count, width, height;
|
|
||||||
int16_t x = workarea->x, y = workarea->y;
|
|
||||||
for (task_in_tag_t *task = tag->task_list; task; task = task->next) {
|
|
||||||
if (!client_need_layout(task->client)) continue;
|
|
||||||
|
|
||||||
if (i < rows_in_main_col) {
|
|
||||||
row = i;
|
|
||||||
width = width_for_main_col;
|
|
||||||
row_count = rows_in_main_col;
|
|
||||||
} else {
|
|
||||||
width = width_for_other_cols;
|
|
||||||
row_count = rows_in_other_cols;
|
|
||||||
if (((i - rows_in_main_col) % row_count) == 0) {
|
|
||||||
col++;
|
|
||||||
row = 0;
|
|
||||||
x += col == 1 ? width_for_main_col : width_for_other_cols;
|
|
||||||
y = workarea->y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t h_avg = workarea->height / row_count;
|
|
||||||
uint16_t h_main = workarea->height - h_avg * (row_count - 1);
|
|
||||||
height = row == 0 ? h_main : h_avg;
|
|
||||||
task->geometry.x = x;
|
|
||||||
task->geometry.y = y;
|
|
||||||
task->geometry.width = width - task->client->border_width * 2;
|
|
||||||
task->geometry.height = height - task->client->border_width * 2;
|
|
||||||
y += height;
|
|
||||||
|
|
||||||
++i;
|
|
||||||
|
|
||||||
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, task->client->border_width);
|
|
||||||
}
|
|
||||||
|
|
||||||
end:
|
|
||||||
logger("== tile tag: %s end\n", tag->name);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
void monocle(tag_t *tag);
|
|
||||||
void tile(tag_t *tag);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
../../.clang-format
|
|
||||||
454
src/monitor.c
454
src/monitor.c
@@ -1,454 +0,0 @@
|
|||||||
#include "monitor.h"
|
|
||||||
|
|
||||||
#include <cairo-xcb.h>
|
|
||||||
#include <cairo.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <xcb/xcb_aux.h>
|
|
||||||
#include <xcb/xcb_icccm.h>
|
|
||||||
|
|
||||||
#include "app.h"
|
|
||||||
#include "base.h"
|
|
||||||
#include "client.h"
|
|
||||||
#include "color.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "image.h"
|
|
||||||
#include "renderer.h"
|
|
||||||
#include "status.h"
|
|
||||||
#include "text.h"
|
|
||||||
#include "tray.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wm.h"
|
|
||||||
#include "xcursor.h"
|
|
||||||
#include "xwindow.h"
|
|
||||||
|
|
||||||
uint32_t monitor_initialize_tag(monitor_t *monitor, const char **tags,
|
|
||||||
uint32_t tag_index_start_at) {
|
|
||||||
uint32_t tag_count = 0;
|
|
||||||
int i = 0;
|
|
||||||
const char *tag_name = nullptr;
|
|
||||||
tag_t *prev_tag = nullptr;
|
|
||||||
for (tag_name = tags[i]; tag_name; tag_name = tags[++i]) {
|
|
||||||
tag_t *tag = p_new(tag_t, 1);
|
|
||||||
tag->name = strdup(tag_name);
|
|
||||||
tag->mask = 1u << i;
|
|
||||||
tag->index = tag_index_start_at + tag_count;
|
|
||||||
|
|
||||||
if (wm.layout_count > 0 && wm.layout_list) {
|
|
||||||
tag->layout = &wm.layout_list[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev_tag) {
|
|
||||||
prev_tag->next = tag;
|
|
||||||
} else {
|
|
||||||
monitor->tag_list = tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_tag = tag;
|
|
||||||
tag_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor->selected_tag = monitor->tag_list;
|
|
||||||
return tag_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void monitor_deal_focus(monitor_t *monitor) {
|
|
||||||
if (!monitor->selected_tag->task_list) {
|
|
||||||
client_focus(nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_t *client = nullptr;
|
|
||||||
for (client_t *c = wm.client_stack_list; c; c = c->stack_next) {
|
|
||||||
if (client_get_task_in_tag(c, monitor->selected_tag)) {
|
|
||||||
client = c;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client_focus(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void monitor_select_tag(monitor_t *monitor, uint32_t tag_mask) {
|
|
||||||
if (monitor->selected_tag->mask == tag_mask) return;
|
|
||||||
|
|
||||||
for (task_in_tag_t *task = monitor->selected_tag->task_list; task;
|
|
||||||
task = task->next) {
|
|
||||||
if (task->client->floating || task->client->size_freeze ||
|
|
||||||
!monitor->selected_tag->layout->arrange) {
|
|
||||||
task->geometry = task->client->geometry;
|
|
||||||
task->client->old_geometry = task->client->geometry;
|
|
||||||
}
|
|
||||||
if (task->client->fullscreen || task->client->maximize ||
|
|
||||||
task->client->minimize) {
|
|
||||||
task->geometry = task->client->geometry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (tag_t *tag = monitor->tag_list; tag; tag = tag->next) {
|
|
||||||
if (tag->mask == tag_mask) {
|
|
||||||
monitor->selected_tag = tag;
|
|
||||||
monitor_arrange(monitor);
|
|
||||||
monitor_draw_bar(monitor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_deal_focus(monitor);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tag_clean(tag_t *tag) {
|
|
||||||
tag_t *next_tag = nullptr;
|
|
||||||
for (tag_t *t = tag; t; t = next_tag) {
|
|
||||||
p_delete(&t->name);
|
|
||||||
|
|
||||||
next_tag = t->next;
|
|
||||||
p_delete(&t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void monitor_clean(monitor_t *monitor) {
|
|
||||||
monitor_t *next_monitor = nullptr;
|
|
||||||
for (monitor_t *m = monitor; m; m = next_monitor) {
|
|
||||||
p_delete(&m->name);
|
|
||||||
tag_clean(m->tag_list);
|
|
||||||
|
|
||||||
m->selected_tag = nullptr;
|
|
||||||
m->tag_list = nullptr;
|
|
||||||
|
|
||||||
cairo_destroy(m->bar_cr);
|
|
||||||
m->bar_cr = nullptr;
|
|
||||||
|
|
||||||
next_monitor = m->next;
|
|
||||||
p_delete(&m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void monitor_init_bar(monitor_t *monitor) {
|
|
||||||
static xcb_colormap_t colormap = XCB_NONE;
|
|
||||||
visual_t *visual = xwindow_get_xcb_visual(true);
|
|
||||||
xcb_visualid_t visual_id = visual->visual->visual_id;
|
|
||||||
xcb_connection_t *conn = wm.xcb_conn;
|
|
||||||
if (colormap == XCB_NONE) {
|
|
||||||
colormap = xcb_generate_id(conn);
|
|
||||||
uint8_t alloc = XCB_COLORMAP_ALLOC_NONE;
|
|
||||||
xcb_create_colormap(conn, alloc, colormap, wm.screen->root, visual_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_window_t window = xcb_generate_id(conn);
|
|
||||||
uint32_t value_mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_BACK_PIXEL |
|
|
||||||
XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK |
|
|
||||||
XCB_CW_CURSOR | XCB_CW_COLORMAP;
|
|
||||||
const xcb_create_window_value_list_t value_list = {
|
|
||||||
.override_redirect = true,
|
|
||||||
.background_pixel = wm.color_set.bar_bg.argb,
|
|
||||||
.border_pixel = 0,
|
|
||||||
.event_mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_EXPOSURE,
|
|
||||||
.cursor = xcursor_get_xcb_cursor(cursor_normal),
|
|
||||||
.colormap = colormap,
|
|
||||||
};
|
|
||||||
xcb_void_cookie_t cookie;
|
|
||||||
cookie = xcb_create_window_aux_checked(
|
|
||||||
conn, visual->depth, window, wm.screen->root, monitor->geometry.x,
|
|
||||||
monitor->geometry.y, monitor->geometry.width, wm.bar_height, 0,
|
|
||||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, value_mask, &value_list);
|
|
||||||
if (xcb_request_check(conn, cookie)) fatal("cannot create bar window");
|
|
||||||
|
|
||||||
cookie = xcb_map_window(conn, window);
|
|
||||||
if (xcb_request_check(conn, cookie)) fatal("cannot map bar window:");
|
|
||||||
|
|
||||||
xwindow_set_class_instance(window);
|
|
||||||
xwindow_set_name_static(window, APP_NAME "_bar");
|
|
||||||
|
|
||||||
xcb_aux_sync(conn);
|
|
||||||
|
|
||||||
uint16_t width = monitor->geometry.width;
|
|
||||||
monitor->workarea.x = monitor->geometry.x;
|
|
||||||
monitor->workarea.y = monitor->geometry.y + wm.bar_height;
|
|
||||||
monitor->workarea.width = width;
|
|
||||||
monitor->workarea.height = monitor->geometry.height - wm.bar_height;
|
|
||||||
monitor->bar_window = window;
|
|
||||||
cairo_surface_t *surface = cairo_xcb_surface_create(
|
|
||||||
wm.xcb_conn, window, visual->visual, width, wm.bar_height);
|
|
||||||
cairo_t *cr = cairo_create(surface);
|
|
||||||
cairo_surface_destroy(surface);
|
|
||||||
p_delete(&visual);
|
|
||||||
|
|
||||||
cairo_status_t status = cairo_status(cr);
|
|
||||||
if (status != CAIRO_STATUS_SUCCESS) {
|
|
||||||
fatal("cannot create cairo context: %s", cairo_status_to_string(status));
|
|
||||||
}
|
|
||||||
monitor->bar_cr = cr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void monitor_draw_tags(monitor_t *monitor) {
|
|
||||||
int16_t x = 0;
|
|
||||||
for (tag_t *tag = monitor->tag_list; tag; tag = tag->next) {
|
|
||||||
bool selected = monitor->selected_tag == tag;
|
|
||||||
bool has_client = tag->task_list;
|
|
||||||
color_t *bg = nullptr;
|
|
||||||
color_t *color = nullptr;
|
|
||||||
if (selected) {
|
|
||||||
bg = &wm.color_set.active_tag_bg;
|
|
||||||
color = &wm.color_set.active_tag_color;
|
|
||||||
} else {
|
|
||||||
bg = &wm.color_set.tag_bg;
|
|
||||||
color = &wm.color_set.tag_color;
|
|
||||||
}
|
|
||||||
int width = 0, height = 0;
|
|
||||||
text_get_size(tag->name, &width, &height);
|
|
||||||
width += 2 * wm.padding.tag_x;
|
|
||||||
tag->bar_extent.start = x;
|
|
||||||
tag->bar_extent.end = x + width;
|
|
||||||
|
|
||||||
area_t tag_rect = {.x = x, .y = 0, .width = width, .height = wm.bar_height};
|
|
||||||
if (bg->rgba != wm.color_set.bar_bg.rgba) {
|
|
||||||
draw_background(monitor->bar_cr, bg, tag_rect);
|
|
||||||
}
|
|
||||||
if (has_client) {
|
|
||||||
area_t rect = {.x = x, .y = 0, .width = 4, .height = 4};
|
|
||||||
draw_rect(monitor->bar_cr, rect, selected, color, 1);
|
|
||||||
}
|
|
||||||
area_t text_rect = tag_rect;
|
|
||||||
tag_rect.y = (int16_t)((int)wm.bar_height - height) / 2;
|
|
||||||
tag_rect.height = (int16_t)height;
|
|
||||||
draw_text(monitor->bar_cr, tag->name, color, text_rect, true);
|
|
||||||
x += width;
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor->tag_extent.start = 0;
|
|
||||||
monitor->tag_extent.end = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void monitor_draw_layout_symbol(monitor_t *monitor) {
|
|
||||||
const layout_t *layout = monitor->selected_tag->layout;
|
|
||||||
if (layout && layout->symbol) {
|
|
||||||
color_t *color = &wm.color_set.tag_color;
|
|
||||||
int width = 0;
|
|
||||||
text_get_size(layout->symbol, &width, nullptr);
|
|
||||||
area_t area = {
|
|
||||||
.x = monitor->tag_extent.end,
|
|
||||||
.y = monitor->geometry.y,
|
|
||||||
.width = width + 2 * wm.padding.tag_x,
|
|
||||||
.height = wm.bar_height,
|
|
||||||
};
|
|
||||||
monitor->layout_symbol_extent.start = area.x;
|
|
||||||
monitor->layout_symbol_extent.end = area.x + area.width;
|
|
||||||
draw_text(monitor->bar_cr, layout->symbol, color, area, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void monitor_draw_status(monitor_t *monitor, status_t *status,
|
|
||||||
int16_t right_edge, int16_t left_edge) {
|
|
||||||
static renderer_status_t *rendered_status = nullptr;
|
|
||||||
static size_t rendered_status_capacity = 0;
|
|
||||||
|
|
||||||
monitor->status_extent.end = right_edge;
|
|
||||||
monitor->status_extent.start = right_edge;
|
|
||||||
int32_t end = left_edge;
|
|
||||||
|
|
||||||
if (status == nullptr || !wm.config || !wm.config->status) return;
|
|
||||||
|
|
||||||
config_status_t *status_config = wm.config->status;
|
|
||||||
size_t item_count = status_config->status_count;
|
|
||||||
if (item_count == 0) return;
|
|
||||||
|
|
||||||
if (rendered_status_capacity < item_count) {
|
|
||||||
size_t size =
|
|
||||||
sizeof(renderer_status_t) + item_count * sizeof(renderer_status_item_t);
|
|
||||||
xrealloc((void **)&rendered_status, (ssize_t)size);
|
|
||||||
rendered_status_capacity = item_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
status_renderer_t renderer = wm.config->status_renderer;
|
|
||||||
if (!renderer) renderer = renderer_render_status;
|
|
||||||
|
|
||||||
renderer(status_config, item_count, status, rendered_status);
|
|
||||||
item_count = MIN(item_count, rendered_status->item_count);
|
|
||||||
|
|
||||||
int32_t start = monitor->status_extent.start;
|
|
||||||
int32_t icon_target_height =
|
|
||||||
(int32_t)wm.bar_height - (int32_t)wm.padding.bar_y * 2;
|
|
||||||
if (icon_target_height <= 0) icon_target_height = wm.bar_height;
|
|
||||||
|
|
||||||
for (size_t ri = item_count; ri > 0; ri--) {
|
|
||||||
size_t i = ri - 1;
|
|
||||||
renderer_status_item_t *item = &rendered_status->item_list[i];
|
|
||||||
bool has_text = item->text[0] != '\0';
|
|
||||||
int text_width = 0;
|
|
||||||
if (has_text) text_get_size(item->text, &text_width, nullptr);
|
|
||||||
|
|
||||||
bool has_text_icon = false;
|
|
||||||
int icon_text_width = 0;
|
|
||||||
if (item->icon_type == icon_type_text && item->icon_text &&
|
|
||||||
item->icon_text[0]) {
|
|
||||||
has_text_icon = true;
|
|
||||||
text_get_size(item->icon_text, &icon_text_width, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_image_icon = false;
|
|
||||||
int icon_width = 0;
|
|
||||||
int icon_height = 0;
|
|
||||||
if (item->icon_type == icon_type_image && item->icon_path &&
|
|
||||||
item->icon_path[0]) {
|
|
||||||
has_image_icon = image_get_scaled_size(
|
|
||||||
item->icon_path, icon_target_height, &icon_width, &icon_height);
|
|
||||||
}
|
|
||||||
bool has_icon = has_text_icon || has_image_icon;
|
|
||||||
|
|
||||||
int width = 0;
|
|
||||||
if (has_text_icon) width += icon_text_width;
|
|
||||||
if (has_image_icon) width += icon_width;
|
|
||||||
if (has_text) width += text_width;
|
|
||||||
if (has_icon && has_text) width += (int)rendered_status->item_gap;
|
|
||||||
if (!width) continue;
|
|
||||||
|
|
||||||
start -= width;
|
|
||||||
if (start <= end) return;
|
|
||||||
|
|
||||||
color_t *color = item->color ? item->color : &wm.color_set.tag_color;
|
|
||||||
int32_t draw_x = start;
|
|
||||||
if (has_text_icon && icon_text_width > 0) {
|
|
||||||
area_t icon_rect = {
|
|
||||||
.x = (int16_t)draw_x,
|
|
||||||
.y = 0,
|
|
||||||
.width = (uint16_t)icon_text_width,
|
|
||||||
.height = wm.bar_height,
|
|
||||||
};
|
|
||||||
draw_text(monitor->bar_cr, item->icon_text, color, icon_rect, true);
|
|
||||||
draw_x += icon_text_width;
|
|
||||||
} else if (has_image_icon) {
|
|
||||||
int32_t icon_y = ((int32_t)wm.bar_height - icon_height) / 2;
|
|
||||||
image_draw(monitor->bar_cr, item->icon_path, draw_x, icon_y, icon_width,
|
|
||||||
icon_height);
|
|
||||||
draw_x += icon_width;
|
|
||||||
}
|
|
||||||
if (has_icon && has_text) draw_x += (int32_t)rendered_status->item_gap;
|
|
||||||
|
|
||||||
if (has_text && text_width > 0) {
|
|
||||||
area_t rect = {
|
|
||||||
.x = (int16_t)draw_x,
|
|
||||||
.y = 0,
|
|
||||||
.width = (uint16_t)text_width,
|
|
||||||
.height = wm.bar_height,
|
|
||||||
};
|
|
||||||
draw_text(monitor->bar_cr, item->text, color, rect, true);
|
|
||||||
}
|
|
||||||
monitor->status_extent.start = (int16_t)start;
|
|
||||||
|
|
||||||
if (ri > 1) {
|
|
||||||
start -= (int32_t)rendered_status->gap;
|
|
||||||
if (start < end) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void monitor_draw_tasks(monitor_t *monitor, int16_t right_edge) {
|
|
||||||
task_in_tag_t *task_list = monitor->selected_tag->task_list;
|
|
||||||
if (!task_list) return;
|
|
||||||
|
|
||||||
uint16_t task_count = 0;
|
|
||||||
for (task_in_tag_t *task = task_list; task; task = task->next) {
|
|
||||||
if (task->client->skip_taskbar) continue;
|
|
||||||
++task_count;
|
|
||||||
};
|
|
||||||
if (task_count == 0) return;
|
|
||||||
|
|
||||||
int16_t x = monitor->layout_symbol_extent.end;
|
|
||||||
int32_t available_width = (int32_t)right_edge - x;
|
|
||||||
if (available_width <= 0) return;
|
|
||||||
|
|
||||||
uint16_t task_width = (uint16_t)(available_width / task_count);
|
|
||||||
if (task_width < wm.font_size) return;
|
|
||||||
|
|
||||||
for (task_in_tag_t *task = task_list; task; task = task->next) {
|
|
||||||
if (task->client->skip_taskbar) continue;
|
|
||||||
|
|
||||||
task->bar_extent.start = x;
|
|
||||||
task->bar_extent.end = x + task_width;
|
|
||||||
x += task_width;
|
|
||||||
|
|
||||||
char **title = client_get_task_title(task->client);
|
|
||||||
if (title == nullptr || *title == nullptr) continue;
|
|
||||||
color_t *color = &wm.color_set.active_tag_color;
|
|
||||||
area_t area = {
|
|
||||||
.x = task->bar_extent.start,
|
|
||||||
.y = monitor->geometry.y,
|
|
||||||
.width = task_width,
|
|
||||||
.height = wm.bar_height,
|
|
||||||
};
|
|
||||||
draw_text(monitor->bar_cr, *title, color, area, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void monitor_draw_bar(monitor_t *monitor) {
|
|
||||||
cairo_t *cr = monitor->bar_cr;
|
|
||||||
uint16_t height = wm.bar_height;
|
|
||||||
uint16_t tray_width = tray_get_width(monitor);
|
|
||||||
int16_t status_right = (int16_t)monitor->geometry.width - tray_width;
|
|
||||||
if (status_right < 0) status_right = 0;
|
|
||||||
area_t bar_area = {
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.width = monitor->geometry.width,
|
|
||||||
.height = height,
|
|
||||||
};
|
|
||||||
draw_background(cr, &wm.color_set.bar_bg, bar_area);
|
|
||||||
|
|
||||||
monitor_draw_tags(monitor);
|
|
||||||
monitor_draw_layout_symbol(monitor);
|
|
||||||
monitor_draw_status(monitor, wm.status, status_right,
|
|
||||||
monitor->layout_symbol_extent.end);
|
|
||||||
monitor_draw_tasks(monitor, monitor->status_extent.start);
|
|
||||||
tray_place(monitor, monitor->geometry.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
void monitor_arrange(monitor_t *monitor) {
|
|
||||||
tag_t *tag = monitor->selected_tag;
|
|
||||||
if (tag->layout && tag->layout->arrange) tag->layout->arrange(tag);
|
|
||||||
|
|
||||||
for (client_t *c = wm.client_stack_list; c; c = c->stack_next) {
|
|
||||||
if (c->monitor != monitor || c->minimize) continue;
|
|
||||||
|
|
||||||
task_in_tag_t *task = client_get_task_in_tag(c, tag);
|
|
||||||
if (task) {
|
|
||||||
if (client_need_layout(c)) {
|
|
||||||
client_apply_geometry(c, task->geometry);
|
|
||||||
} else if (c->fullscreen) {
|
|
||||||
client_apply_geometry(c, c->monitor->geometry);
|
|
||||||
} else if (c->maximize) {
|
|
||||||
client_apply_geometry(c, c->monitor->workarea);
|
|
||||||
} else {
|
|
||||||
client_move_to(c, task->geometry.x, task->geometry.y);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int16_t x = -client_width(c);
|
|
||||||
int16_t y = -client_height(c);
|
|
||||||
client_move_to(c, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void monitor_save_cursor_point(monitor_t *monitor) {
|
|
||||||
monitor->cursor_position = xcursor_query_pointer_position();
|
|
||||||
monitor->position_inited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
point_t monitor_get_restore_cursor_point(monitor_t *monitor) {
|
|
||||||
if (!monitor->position_inited) {
|
|
||||||
point_t point = xcursor_query_pointer_position();
|
|
||||||
monitor_t *m = wm_get_monitor_by_point(point);
|
|
||||||
monitor->cursor_position.x = point.x - m->geometry.x + monitor->geometry.x;
|
|
||||||
monitor->cursor_position.y = point.y - m->geometry.y + monitor->geometry.y;
|
|
||||||
monitor->position_inited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return monitor->cursor_position;
|
|
||||||
}
|
|
||||||
|
|
||||||
void monitor_restore_cursor_point(monitor_t *monitor) {
|
|
||||||
xcursor_set_pointer_position(monitor_get_restore_cursor_point(monitor));
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
uint32_t monitor_initialize_tag(monitor_t *monitor, const char **tags,
|
|
||||||
uint32_t tag_index_start_at);
|
|
||||||
void monitor_deal_focus(monitor_t *monitor);
|
|
||||||
void monitor_select_tag(monitor_t *monitor, uint32_t tag_mask);
|
|
||||||
void monitor_clean(monitor_t *monitor);
|
|
||||||
void monitor_init_bar(monitor_t *monitor);
|
|
||||||
void monitor_draw_bar(monitor_t *monitor);
|
|
||||||
void monitor_arrange(monitor_t *monitor);
|
|
||||||
void monitor_save_cursor_point(monitor_t *monitor);
|
|
||||||
point_t monitor_get_restore_cursor_point(monitor_t *monitor);
|
|
||||||
void monitor_restore_cursor_point(monitor_t *monitor);
|
|
||||||
156
src/rect.c
156
src/rect.c
@@ -1,156 +0,0 @@
|
|||||||
#include "rect.h"
|
|
||||||
|
|
||||||
#include "base/macros.h"
|
|
||||||
#include "base/memory.h"
|
|
||||||
|
|
||||||
static inline bool rect_valid(rect_t rect) {
|
|
||||||
return rect.x1 < rect.x2 && rect.y1 < rect.y2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 将一个合法矩形追加到动态矩形数组中。 */
|
|
||||||
static inline void rect_list_append(rect_t **list, size_t *length,
|
|
||||||
size_t *capacity, rect_t rect) {
|
|
||||||
if (!rect_valid(rect)) return;
|
|
||||||
|
|
||||||
if (*length == *capacity) {
|
|
||||||
*capacity = *capacity ? *capacity * 2 : 4;
|
|
||||||
p_realloc(list, *capacity);
|
|
||||||
}
|
|
||||||
(*list)[(*length)++] = rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool rect_intersection(rect_t a, rect_t b, rect_t *intersection) {
|
|
||||||
if (!rect_valid(a) || !rect_valid(b)) return false;
|
|
||||||
|
|
||||||
rect_t overlap = {
|
|
||||||
.x1 = MAX(a.x1, b.x1),
|
|
||||||
.y1 = MAX(a.y1, b.y1),
|
|
||||||
.x2 = MIN(a.x2, b.x2),
|
|
||||||
.y2 = MIN(a.y2, b.y2),
|
|
||||||
};
|
|
||||||
if (!rect_valid(overlap)) return false;
|
|
||||||
|
|
||||||
if (intersection) *intersection = overlap;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t rect_subtract(rect_t source, rect_t clip, rect_t remaining[4]) {
|
|
||||||
if (!rect_valid(source)) return 0;
|
|
||||||
|
|
||||||
rect_t overlap;
|
|
||||||
if (!rect_intersection(source, clip, &overlap)) {
|
|
||||||
if (remaining) remaining[0] = source;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t count = 0;
|
|
||||||
/*
|
|
||||||
* source: [sx1,sx2) x [sy1,sy2)
|
|
||||||
* overlap: [ox1,ox2) x [oy1,oy2) = source 与 clip 的交集
|
|
||||||
*
|
|
||||||
* y=sy1 ┌───────────────────────────────────────────┐
|
|
||||||
* │ TOP │
|
|
||||||
* │ UL UR │
|
|
||||||
* y=oy1 ├───────────────┬───────────────┬───────────┤
|
|
||||||
* │ LEFT │ OVERLAP │ RIGHT │
|
|
||||||
* y=oy2 ├───────────────┴───────────────┴───────────┤
|
|
||||||
* │ LL LR │
|
|
||||||
* │ BOTTOM │
|
|
||||||
* y=sy2 └───────────────────────────────────────────┘
|
|
||||||
* x=sx1 x=ox1 x=ox2 x=sx2
|
|
||||||
*
|
|
||||||
* 角块归属:
|
|
||||||
* UL、UR 属于 TOP
|
|
||||||
* LL、LR 属于 BOTTOM
|
|
||||||
* LEFT/RIGHT 只覆盖 y ∈ [oy1, oy2),因此不会包含四个角块。
|
|
||||||
*/
|
|
||||||
rect_t parts[] = {
|
|
||||||
/* top */
|
|
||||||
{
|
|
||||||
.x1 = source.x1,
|
|
||||||
.y1 = source.y1,
|
|
||||||
.x2 = source.x2,
|
|
||||||
.y2 = overlap.y1,
|
|
||||||
},
|
|
||||||
/* bottom */
|
|
||||||
{
|
|
||||||
.x1 = source.x1,
|
|
||||||
.y1 = overlap.y2,
|
|
||||||
.x2 = source.x2,
|
|
||||||
.y2 = source.y2,
|
|
||||||
},
|
|
||||||
/* left */
|
|
||||||
{
|
|
||||||
.x1 = source.x1,
|
|
||||||
.y1 = overlap.y1,
|
|
||||||
.x2 = overlap.x1,
|
|
||||||
.y2 = overlap.y2,
|
|
||||||
},
|
|
||||||
/* right */
|
|
||||||
{
|
|
||||||
.x1 = overlap.x2,
|
|
||||||
.y1 = overlap.y1,
|
|
||||||
.x2 = source.x2,
|
|
||||||
.y2 = overlap.y2,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (size_t i = 0; i < countof(parts); i++) {
|
|
||||||
if (!rect_valid(parts[i])) continue;
|
|
||||||
if (remaining) remaining[count] = parts[i];
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t rect_subtract_many(rect_t source, const rect_t clips[],
|
|
||||||
size_t clip_count, rect_t **remaining) {
|
|
||||||
/*
|
|
||||||
* 实现说明:
|
|
||||||
* 初始剩余集合为 {source},然后依次处理每个 clip:
|
|
||||||
* 1) 用 clip 去减当前集合中的每个碎片
|
|
||||||
* 2) 将产生的新碎片合并到 next 集合
|
|
||||||
* 3) 用 next 替换 current,进入下一轮
|
|
||||||
* 最终 current 即 source - clips[0] - clips[1] - ... 的结果。
|
|
||||||
*/
|
|
||||||
if (remaining) *remaining = nullptr;
|
|
||||||
if (!rect_valid(source)) return 0;
|
|
||||||
|
|
||||||
size_t current_length = 1;
|
|
||||||
size_t current_capacity = 1;
|
|
||||||
rect_t *current = p_new(rect_t, current_capacity);
|
|
||||||
current[0] = source;
|
|
||||||
|
|
||||||
if (clips) {
|
|
||||||
for (size_t i = 0; i < clip_count && current_length > 0; i++) {
|
|
||||||
rect_t clip = clips[i];
|
|
||||||
if (!rect_valid(clip)) continue;
|
|
||||||
|
|
||||||
size_t next_length = 0;
|
|
||||||
size_t next_capacity = current_length ? current_length : 1;
|
|
||||||
rect_t *next = p_new(rect_t, next_capacity);
|
|
||||||
|
|
||||||
for (size_t j = 0; j < current_length; j++) {
|
|
||||||
rect_t fragments[4];
|
|
||||||
size_t fragment_count = rect_subtract(current[j], clip, fragments);
|
|
||||||
for (size_t k = 0; k < fragment_count; k++) {
|
|
||||||
rect_list_append(&next, &next_length, &next_capacity, fragments[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p_delete(¤t);
|
|
||||||
current = next;
|
|
||||||
current_length = next_length;
|
|
||||||
current_capacity = next_capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remaining) {
|
|
||||||
*remaining = current;
|
|
||||||
} else {
|
|
||||||
p_delete(¤t);
|
|
||||||
}
|
|
||||||
|
|
||||||
return current_length;
|
|
||||||
}
|
|
||||||
42
src/rect.h
42
src/rect.h
@@ -1,42 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
typedef struct rect_t {
|
|
||||||
int32_t x1;
|
|
||||||
int32_t y1;
|
|
||||||
int32_t x2;
|
|
||||||
int32_t y2;
|
|
||||||
} rect_t;
|
|
||||||
|
|
||||||
bool rect_intersection(rect_t a, rect_t b, rect_t *intersection);
|
|
||||||
size_t rect_subtract(rect_t source, rect_t clip, rect_t remaining[4]);
|
|
||||||
/**
|
|
||||||
* @brief 从 source 中按顺序减去 clips 中的多个矩形
|
|
||||||
* @param source 源矩形
|
|
||||||
* @param clips 要减去的矩形数组,可为 nullptr
|
|
||||||
* @param clip_count clips 的长度
|
|
||||||
* @param remaining 输出剩余矩形数组;若不为
|
|
||||||
* nullptr,则由函数分配内存并由调用方释放
|
|
||||||
* @return 剩余矩形数量
|
|
||||||
*
|
|
||||||
* @code{.c}
|
|
||||||
* rect_t source = {.x1 = 0, .y1 = 0, .x2 = 100, .y2 = 80};
|
|
||||||
* rect_t clips[] = {
|
|
||||||
* {.x1 = 10, .y1 = 10, .x2 = 40, .y2 = 40},
|
|
||||||
* {.x1 = 30, .y1 = 20, .x2 = 70, .y2 = 60},
|
|
||||||
* };
|
|
||||||
*
|
|
||||||
* rect_t *remaining = nullptr;
|
|
||||||
* size_t count = rect_subtract_many(source, clips, 2, &remaining);
|
|
||||||
*
|
|
||||||
* for (size_t i = 0; i < count; i++) {
|
|
||||||
* // use remaining[i]
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* free(remaining);
|
|
||||||
* @endcode
|
|
||||||
*/
|
|
||||||
size_t rect_subtract_many(rect_t source, const rect_t clips[],
|
|
||||||
size_t clip_count, rect_t **remaining);
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#include "renderer.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "color.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "status.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
static void status_text_from_data(status_type_t type, const status_t *status,
|
|
||||||
char *text, size_t text_size) {
|
|
||||||
if (!text || text_size == 0) return;
|
|
||||||
text[0] = '\0';
|
|
||||||
if (!status) return;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case status_net_down:
|
|
||||||
snprintf(text, text_size, "%s", status->net_speed.down);
|
|
||||||
break;
|
|
||||||
case status_net_up:
|
|
||||||
snprintf(text, text_size, "%s", status->net_speed.up);
|
|
||||||
break;
|
|
||||||
case status_audio:
|
|
||||||
if (status->pulse) {
|
|
||||||
snprintf(text, text_size, "%d%%%s%s%s", status->pulse->volume_percent,
|
|
||||||
status->pulse->mute ? "M" : "",
|
|
||||||
strlen(status->pulse->device_name) ? "|" : "",
|
|
||||||
status->pulse->device_name);
|
|
||||||
} else {
|
|
||||||
snprintf(text, text_size, "0%%");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case status_memory:
|
|
||||||
snprintf(text, text_size, "%s(%.1f%%)", status->mem_usage.mem_used_text,
|
|
||||||
status->mem_usage.mem_percent);
|
|
||||||
break;
|
|
||||||
case status_cpu:
|
|
||||||
snprintf(text, text_size, "%.1lf", status->cpu_usage_percent);
|
|
||||||
break;
|
|
||||||
case status_time:
|
|
||||||
snprintf(text, text_size, "%s", status->time);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void renderer_render_status(config_status_t *config, size_t config_count,
|
|
||||||
status_t *status, renderer_status_t *output) {
|
|
||||||
static color_t *color_cache = nullptr;
|
|
||||||
static size_t color_cache_count = 0;
|
|
||||||
|
|
||||||
if (!config || !output) return;
|
|
||||||
|
|
||||||
size_t item_count = config->status_count;
|
|
||||||
if (config_count && config_count < item_count) item_count = config_count;
|
|
||||||
|
|
||||||
output->gap = config->status_gap;
|
|
||||||
output->item_gap = config->status_item_gap;
|
|
||||||
output->item_count = item_count;
|
|
||||||
|
|
||||||
if (item_count == 0) return;
|
|
||||||
|
|
||||||
if (color_cache_count < item_count) {
|
|
||||||
xrealloc((void **)&color_cache, (ssize_t)(sizeof(color_t) * item_count));
|
|
||||||
color_cache_count = item_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < item_count; i++) {
|
|
||||||
const config_status_item_t *config_item = &config->list[i];
|
|
||||||
renderer_status_item_t *output_item = &output->item_list[i];
|
|
||||||
memset(output_item, 0, sizeof(*output_item));
|
|
||||||
|
|
||||||
output_item->icon_type = config_item->icon_type;
|
|
||||||
if (config_item->icon_type == icon_type_text) {
|
|
||||||
output_item->icon_text = config_item->icon;
|
|
||||||
} else if (config_item->icon_type == icon_type_image) {
|
|
||||||
output_item->icon_path = config_item->icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
status_text_from_data(config_item->type, status, output_item->text,
|
|
||||||
sizeof(output_item->text));
|
|
||||||
|
|
||||||
if (config_item->color && *config_item->color) {
|
|
||||||
color_parse(config_item->color, &color_cache[i]);
|
|
||||||
output_item->color = &color_cache[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "color.h"
|
|
||||||
|
|
||||||
typedef struct config_status_t config_status_t;
|
|
||||||
typedef struct status_t status_t;
|
|
||||||
|
|
||||||
typedef enum icon_type_t {
|
|
||||||
icon_type_none,
|
|
||||||
icon_type_text,
|
|
||||||
icon_type_image,
|
|
||||||
} icon_type_t;
|
|
||||||
|
|
||||||
typedef struct renderer_status_item_t {
|
|
||||||
icon_type_t icon_type;
|
|
||||||
char text[124];
|
|
||||||
const char *icon_text;
|
|
||||||
const char *icon_path;
|
|
||||||
color_t *color;
|
|
||||||
} renderer_status_item_t;
|
|
||||||
|
|
||||||
typedef struct renderer_status_t {
|
|
||||||
uint32_t gap;
|
|
||||||
uint32_t item_gap;
|
|
||||||
size_t item_count;
|
|
||||||
renderer_status_item_t item_list[];
|
|
||||||
} renderer_status_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据 status 配置和实时状态生成可绘制的 status 数据
|
|
||||||
*
|
|
||||||
* @param config status 配置
|
|
||||||
* @param config_count 最大输出项数,传 0 表示使用 config->status_count
|
|
||||||
* @param status 实时状态数据
|
|
||||||
* @param output 渲染输出缓冲
|
|
||||||
*/
|
|
||||||
void renderer_render_status(config_status_t *config, size_t config_count,
|
|
||||||
status_t *status, renderer_status_t *output);
|
|
||||||
253
src/status.c
253
src/status.c
@@ -1,253 +0,0 @@
|
|||||||
#include "status.h"
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <glibconfig.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include "audio.h"
|
|
||||||
|
|
||||||
static constexpr char CPU_FILE[] = "/proc/stat";
|
|
||||||
static constexpr char MEM_FILE[] = "/proc/meminfo";
|
|
||||||
static constexpr char NET_FILE[] = "/proc/net/dev";
|
|
||||||
|
|
||||||
static constexpr uint32_t INTERVAL = 1;
|
|
||||||
static constexpr char TIME_FORMAT[] = "%A %m-%d %H:%M:%S";
|
|
||||||
|
|
||||||
static status_t status;
|
|
||||||
static guint timer = 0;
|
|
||||||
static status_changed_notify listener = nullptr;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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 bool inited = false;
|
|
||||||
|
|
||||||
gchar *contents = nullptr;
|
|
||||||
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 != nullptr; ++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 = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 *uints[] = {"B", "K", "M", "G", "T"};
|
|
||||||
|
|
||||||
double size = (double)bytes;
|
|
||||||
int i = 0;
|
|
||||||
while (size > 1024.0) {
|
|
||||||
size /= 1024.0;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
if (i == 0) {
|
|
||||||
sprintf(str, "%ld%s", bytes, uints[i]);
|
|
||||||
} else {
|
|
||||||
sprintf(str, "%.1lf%s", size, uints[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;
|
|
||||||
} else {
|
|
||||||
usage->swap_percent =
|
|
||||||
((float)usage->swap_used) / ((float)usage->swap_total) * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = nullptr;
|
|
||||||
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 != nullptr; ++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;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct net_stat_t {
|
|
||||||
uint64_t rx_bytes;
|
|
||||||
uint64_t tx_bytes;
|
|
||||||
} net_stat_t;
|
|
||||||
|
|
||||||
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 = nullptr;
|
|
||||||
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 = {0};
|
|
||||||
for (gchar **line = lines; *line != nullptr; ++line) {
|
|
||||||
if (strchr(*line, ':') == nullptr) 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 true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void get_date_time(char *time_text, size_t length) {
|
|
||||||
time_t current_time = time(nullptr);
|
|
||||||
struct tm *time_info = localtime(¤t_time);
|
|
||||||
strftime(time_text, length, TIME_FORMAT, time_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void notify_status_change(void) {
|
|
||||||
if (listener) listener(&status);
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean update_status(gpointer data) {
|
|
||||||
GError *error = nullptr;
|
|
||||||
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 notify_pulse_change(pulse_t *pulse) {
|
|
||||||
status.pulse = pulse;
|
|
||||||
notify_status_change();
|
|
||||||
}
|
|
||||||
|
|
||||||
void init_status(GMainContext *context, status_changed_notify callback) {
|
|
||||||
listener = callback;
|
|
||||||
status.pulse_context = init_pulse(context, notify_pulse_change);
|
|
||||||
update_status(nullptr);
|
|
||||||
timer = g_timeout_add_seconds(INTERVAL, update_status, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clean_status(void) {
|
|
||||||
clean_pulse(status.pulse_context);
|
|
||||||
g_source_remove(timer);
|
|
||||||
status.pulse_context = nullptr;
|
|
||||||
listener = nullptr;
|
|
||||||
}
|
|
||||||
53
src/status.h
53
src/status.h
@@ -1,53 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "audio.h"
|
|
||||||
|
|
||||||
typedef struct memory_usage_t {
|
|
||||||
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 net_speed_t {
|
|
||||||
uint64_t rx_bytes;
|
|
||||||
uint64_t tx_bytes;
|
|
||||||
char up[64];
|
|
||||||
char down[64];
|
|
||||||
} net_speed_t;
|
|
||||||
|
|
||||||
typedef enum status_type_t {
|
|
||||||
status_net_down,
|
|
||||||
status_net_up,
|
|
||||||
status_audio,
|
|
||||||
status_memory,
|
|
||||||
status_cpu,
|
|
||||||
status_time,
|
|
||||||
} status_type_t;
|
|
||||||
|
|
||||||
typedef struct status_t {
|
|
||||||
net_speed_t net_speed;
|
|
||||||
memory_usage_t mem_usage;
|
|
||||||
double cpu_usage_percent;
|
|
||||||
char time[64];
|
|
||||||
pulse_t *pulse;
|
|
||||||
pulse_context_t *pulse_context;
|
|
||||||
} status_t;
|
|
||||||
|
|
||||||
typedef void (*status_changed_notify)(status_t *status);
|
|
||||||
void init_status(GMainContext *context, status_changed_notify callback);
|
|
||||||
void clean_status(void);
|
|
||||||
183
src/text.c
183
src/text.c
@@ -1,183 +0,0 @@
|
|||||||
#include "text.h"
|
|
||||||
|
|
||||||
#include <cairo.h>
|
|
||||||
#include <glib-object.h>
|
|
||||||
#include <glibconfig.h>
|
|
||||||
#include <pango/pango-attributes.h>
|
|
||||||
#include <pango/pango-context.h>
|
|
||||||
#include <pango/pango-enum-types.h>
|
|
||||||
#include <pango/pango-font.h>
|
|
||||||
#include <pango/pango-fontmap.h>
|
|
||||||
#include <pango/pango-layout.h>
|
|
||||||
#include <pango/pango-types.h>
|
|
||||||
#include <pango/pangocairo.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
#include "color.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
static PangoContext *context = nullptr;
|
|
||||||
static PangoLayout *layout = nullptr;
|
|
||||||
static PangoAttrList *attr_list = nullptr;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化绘制文字的 pango 环境
|
|
||||||
* @param family 多个字体用逗号分割
|
|
||||||
* @param size 字体大小
|
|
||||||
* @param dpi
|
|
||||||
* @return layout 高度,可用于确定 bar 高度
|
|
||||||
*/
|
|
||||||
int text_init_pango_layout(const char *family, uint32_t size, uint32_t dpi) {
|
|
||||||
logger("family: %s, size: %u, dpi: %u\n", family, size, dpi);
|
|
||||||
|
|
||||||
static const char *layout_family = nullptr;
|
|
||||||
static uint32_t layout_size = 0;
|
|
||||||
static uint32_t layout_dpi = 0;
|
|
||||||
static int text_height = 0;
|
|
||||||
|
|
||||||
if (layout_family != nullptr && strcmp(layout_family, family) == 0 &&
|
|
||||||
layout_size == size && layout_dpi == dpi) {
|
|
||||||
logger("text pango layout config cached\n");
|
|
||||||
return text_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
text_clean_pango_layout();
|
|
||||||
|
|
||||||
layout_family = family;
|
|
||||||
layout_size = size;
|
|
||||||
layout_dpi = dpi;
|
|
||||||
|
|
||||||
PangoFontMap *fontmap = pango_cairo_font_map_new();
|
|
||||||
context = pango_font_map_create_context(fontmap);
|
|
||||||
pango_cairo_context_set_resolution(context, (double)dpi);
|
|
||||||
|
|
||||||
layout = pango_layout_new(context);
|
|
||||||
|
|
||||||
PangoFontDescription *desc = pango_font_description_from_string(family);
|
|
||||||
pango_font_description_set_size(desc, size * PANGO_SCALE);
|
|
||||||
PangoLanguage *lang = pango_context_get_language(context);
|
|
||||||
PangoFontMetrics *metrics = pango_context_get_metrics(context, desc, lang);
|
|
||||||
|
|
||||||
PangoAttribute *attr = pango_attr_font_desc_new(desc);
|
|
||||||
attr_list = pango_attr_list_new();
|
|
||||||
pango_attr_list_insert(attr_list, attr);
|
|
||||||
pango_layout_set_attributes(layout, attr_list);
|
|
||||||
|
|
||||||
pango_layout_set_wrap(layout, PANGO_WRAP_NONE);
|
|
||||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
|
||||||
|
|
||||||
int height = pango_font_metrics_get_height(metrics);
|
|
||||||
int ascent = pango_font_metrics_get_ascent(metrics);
|
|
||||||
int descent = pango_font_metrics_get_descent(metrics);
|
|
||||||
text_height =
|
|
||||||
MAX(PANGO_PIXELS_CEIL(height), PANGO_PIXELS_CEIL(ascent + descent));
|
|
||||||
|
|
||||||
pango_font_metrics_unref(metrics);
|
|
||||||
pango_font_description_free(desc);
|
|
||||||
g_object_unref(fontmap);
|
|
||||||
|
|
||||||
PangoRectangle ink_rect, logical_rect;
|
|
||||||
pango_layout_get_pixel_extents(layout, &ink_rect, &logical_rect);
|
|
||||||
|
|
||||||
text_height = MAX(text_height, MAX(ink_rect.height, logical_rect.height));
|
|
||||||
|
|
||||||
return text_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
void text_clean_pango_layout(void) {
|
|
||||||
if (attr_list) {
|
|
||||||
pango_attr_list_unref(attr_list);
|
|
||||||
attr_list = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout) {
|
|
||||||
g_object_unref(layout);
|
|
||||||
layout = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context) {
|
|
||||||
g_object_unref(context);
|
|
||||||
context = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void get_layout_size(int *width, int *height) {
|
|
||||||
PangoRectangle logical_rect;
|
|
||||||
pango_layout_get_pixel_extents(layout, nullptr, &logical_rect);
|
|
||||||
if (width) *width = logical_rect.width;
|
|
||||||
if (height) *height = logical_rect.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
void text_get_size(const char *text, int *width, int *height) {
|
|
||||||
pango_layout_set_width(layout, -1);
|
|
||||||
pango_layout_set_text(layout, text, -1);
|
|
||||||
get_layout_size(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void text_layout_prepare(bool align_center) {
|
|
||||||
if (layout == nullptr) {
|
|
||||||
layout = pango_layout_new(context);
|
|
||||||
pango_layout_set_attributes(layout, attr_list);
|
|
||||||
pango_layout_set_wrap(layout, PANGO_WRAP_NONE);
|
|
||||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
|
||||||
}
|
|
||||||
|
|
||||||
PangoAlignment align = align_center ? PANGO_ALIGN_CENTER : PANGO_ALIGN_LEFT;
|
|
||||||
pango_layout_set_alignment(layout, align);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_text(cairo_t *cr, const char *text, color_t *color, area_t area,
|
|
||||||
bool align_center) {
|
|
||||||
text_layout_prepare(align_center);
|
|
||||||
pango_layout_set_width(layout, (int)area.width * PANGO_SCALE);
|
|
||||||
pango_layout_set_text(layout, text, -1);
|
|
||||||
cairo_set_source_rgba(cr, (double)color->red, (double)color->green,
|
|
||||||
(double)color->blue, (double)color->alpha);
|
|
||||||
int height = 0;
|
|
||||||
get_layout_size(nullptr, &height);
|
|
||||||
|
|
||||||
double offset_y = ((double)area.height - (double)height) / 2;
|
|
||||||
pango_cairo_update_layout(cr, layout);
|
|
||||||
cairo_move_to(cr, (double)area.x, offset_y + (double)area.y);
|
|
||||||
pango_cairo_show_layout(cr, layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_rect(cairo_t *cr, area_t area, bool fill, color_t *color,
|
|
||||||
uint16_t line_width) {
|
|
||||||
cairo_set_line_width(cr, (double)line_width);
|
|
||||||
double x = (double)area.x;
|
|
||||||
double y = (double)area.y;
|
|
||||||
double width = (double)area.width;
|
|
||||||
double height = (double)area.height;
|
|
||||||
if (fill) {
|
|
||||||
cairo_rectangle(cr, x, y, width, height);
|
|
||||||
cairo_set_source_rgba(cr, (double)color->red, (double)color->green,
|
|
||||||
(double)color->blue, (double)color->alpha);
|
|
||||||
cairo_fill(cr);
|
|
||||||
} else {
|
|
||||||
double half_line_width = (double)line_width / 2;
|
|
||||||
x += half_line_width;
|
|
||||||
y += half_line_width;
|
|
||||||
width -= half_line_width;
|
|
||||||
height -= half_line_width;
|
|
||||||
|
|
||||||
cairo_rectangle(cr, x, y, width, height);
|
|
||||||
cairo_set_source_rgba(cr, (double)color->red, (double)color->green,
|
|
||||||
(double)color->blue, (double)color->alpha);
|
|
||||||
cairo_stroke(cr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_background(cairo_t *cr, color_t *color, area_t area) {
|
|
||||||
double x = (double)area.x;
|
|
||||||
double y = (double)area.y;
|
|
||||||
double width = (double)area.width;
|
|
||||||
double height = (double)area.height;
|
|
||||||
cairo_move_to(cr, x, y);
|
|
||||||
cairo_set_source_rgba(cr, (double)color->red, (double)color->green,
|
|
||||||
(double)color->blue, (double)color->alpha);
|
|
||||||
cairo_rectangle(cr, x, y, width, height);
|
|
||||||
cairo_fill(cr);
|
|
||||||
}
|
|
||||||
16
src/text.h
16
src/text.h
@@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cairo.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
#include "color.h"
|
|
||||||
|
|
||||||
int text_init_pango_layout(const char *family, uint32_t size, uint32_t dpi);
|
|
||||||
void text_clean_pango_layout(void);
|
|
||||||
void text_get_size(const char *text, int *width, int *height);
|
|
||||||
void draw_text(cairo_t *cr, const char *text, color_t *color, area_t area,
|
|
||||||
bool align_center);
|
|
||||||
void draw_rect(cairo_t *cr, area_t area, bool fill, color_t *color,
|
|
||||||
uint16_t line_width);
|
|
||||||
void draw_background(cairo_t *cr, color_t *color, area_t area);
|
|
||||||
598
src/tray.c
598
src/tray.c
@@ -1,598 +0,0 @@
|
|||||||
#include "tray.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_aux.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "atoms-extern.h"
|
|
||||||
#include "base.h"
|
|
||||||
#include "monitor.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wm.h"
|
|
||||||
#include "xwindow.h"
|
|
||||||
|
|
||||||
typedef enum tray_opcode_t {
|
|
||||||
TRAY_REQUEST_DOCK = 0,
|
|
||||||
TRAY_BEGIN_MESSAGE = 1,
|
|
||||||
TRAY_CANCEL_MESSAGE = 2,
|
|
||||||
} tray_opcode_t;
|
|
||||||
|
|
||||||
typedef enum tray_orientation_t {
|
|
||||||
TRAY_ORIENTATION_HORIZONTAL = 0,
|
|
||||||
} tray_orientation_t;
|
|
||||||
|
|
||||||
typedef enum xembed_message_t {
|
|
||||||
XEMBED_EMBEDDED_NOTIFY = 0,
|
|
||||||
} xembed_message_t;
|
|
||||||
|
|
||||||
enum {
|
|
||||||
XEMBED_VERSION = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct tray_icon_t {
|
|
||||||
xcb_window_t window;
|
|
||||||
uint16_t natural_width;
|
|
||||||
uint16_t natural_height;
|
|
||||||
uint8_t ignore_unmaps;
|
|
||||||
bool mapped;
|
|
||||||
} tray_icon_t;
|
|
||||||
|
|
||||||
typedef struct tray_t {
|
|
||||||
bool initialized;
|
|
||||||
xcb_atom_t selection_atom;
|
|
||||||
xcb_window_t window;
|
|
||||||
monitor_t *host_monitor;
|
|
||||||
|
|
||||||
tray_icon_t *icons;
|
|
||||||
size_t icon_count;
|
|
||||||
size_t icon_capacity;
|
|
||||||
|
|
||||||
uint16_t spacing;
|
|
||||||
uint16_t side_padding;
|
|
||||||
} tray_t;
|
|
||||||
|
|
||||||
static tray_t tray = {
|
|
||||||
.selection_atom = XCB_ATOM_NONE,
|
|
||||||
.window = XCB_WINDOW_NONE,
|
|
||||||
};
|
|
||||||
|
|
||||||
static xcb_atom_t tray_intern_atom(const char *name) {
|
|
||||||
if (!name || !name[0]) return XCB_ATOM_NONE;
|
|
||||||
|
|
||||||
xcb_intern_atom_cookie_t cookie =
|
|
||||||
xcb_intern_atom(wm.xcb_conn, false, (uint16_t)strlen(name), name);
|
|
||||||
xcb_intern_atom_reply_t *reply =
|
|
||||||
xcb_intern_atom_reply(wm.xcb_conn, cookie, nullptr);
|
|
||||||
if (!reply) return XCB_ATOM_NONE;
|
|
||||||
|
|
||||||
xcb_atom_t atom = reply->atom;
|
|
||||||
p_delete(&reply);
|
|
||||||
return atom;
|
|
||||||
}
|
|
||||||
|
|
||||||
static tray_icon_t *tray_get_icon(xcb_window_t window) {
|
|
||||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
|
||||||
tray_icon_t *icon = &tray.icons[i];
|
|
||||||
if (icon->window == window) return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t tray_find_icon_index(xcb_window_t window) {
|
|
||||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
|
||||||
if (tray.icons[i].window == window) return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SIZE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint16_t tray_target_icon_height(void) {
|
|
||||||
return MAX((uint16_t)1, wm.bar_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t tray_color_component_u16(double channel) {
|
|
||||||
if (channel < 0.0) channel = 0.0;
|
|
||||||
if (channel > 1.0) channel = 1.0;
|
|
||||||
|
|
||||||
uint32_t value = (uint32_t)(channel * 65535.0 + 0.5);
|
|
||||||
return MIN(value, 65535u);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_get_icon_layout(const tray_icon_t *icon, uint16_t *width,
|
|
||||||
uint16_t *height) {
|
|
||||||
uint32_t target_height = tray_target_icon_height();
|
|
||||||
uint32_t natural_width = icon->natural_width;
|
|
||||||
uint32_t natural_height = icon->natural_height;
|
|
||||||
|
|
||||||
if (natural_width == 0) natural_width = target_height;
|
|
||||||
if (natural_height == 0) natural_height = target_height;
|
|
||||||
if (natural_height == 0) natural_height = 1;
|
|
||||||
|
|
||||||
uint32_t scaled_width =
|
|
||||||
(natural_width * target_height + natural_height / 2) / natural_height;
|
|
||||||
if (scaled_width == 0) scaled_width = 1;
|
|
||||||
|
|
||||||
if (width) *width = (uint16_t)MIN((uint32_t)UINT16_MAX, scaled_width);
|
|
||||||
if (height) *height = (uint16_t)MIN((uint32_t)UINT16_MAX, target_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint16_t tray_compute_width(void) {
|
|
||||||
if (!tray.initialized || tray.host_monitor == nullptr ||
|
|
||||||
tray.icon_count == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t width = (uint32_t)tray.side_padding * 2;
|
|
||||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
|
||||||
uint16_t icon_width = 0;
|
|
||||||
tray_get_icon_layout(&tray.icons[i], &icon_width, nullptr);
|
|
||||||
width += icon_width;
|
|
||||||
}
|
|
||||||
if (tray.icon_count > 1) {
|
|
||||||
width += (uint32_t)(tray.icon_count - 1) * tray.spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (uint16_t)MIN((uint32_t)UINT16_MAX, width);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_send_xembed_message(xcb_window_t window, uint32_t message,
|
|
||||||
uint32_t detail, uint32_t data1,
|
|
||||||
uint32_t data2) {
|
|
||||||
xcb_client_message_event_t ev;
|
|
||||||
p_clear(&ev, 1);
|
|
||||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
|
||||||
ev.format = 32;
|
|
||||||
ev.window = window;
|
|
||||||
ev.type = _XEMBED;
|
|
||||||
ev.data.data32[0] = XCB_CURRENT_TIME;
|
|
||||||
ev.data.data32[1] = message;
|
|
||||||
ev.data.data32[2] = detail;
|
|
||||||
ev.data.data32[3] = data1;
|
|
||||||
ev.data.data32[4] = data2;
|
|
||||||
|
|
||||||
xcb_send_event(wm.xcb_conn, false, window, XCB_EVENT_MASK_NO_EVENT,
|
|
||||||
(char *)&ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_select_icon_events(xcb_window_t window) {
|
|
||||||
xcb_params_cw_t params = {
|
|
||||||
.back_pixel = wm.color_set.bar_bg.argb,
|
|
||||||
.border_pixel = 0,
|
|
||||||
.event_mask =
|
|
||||||
XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY,
|
|
||||||
};
|
|
||||||
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK;
|
|
||||||
xcb_aux_change_window_attributes(wm.xcb_conn, window, mask, ¶ms);
|
|
||||||
xcb_clear_area(wm.xcb_conn, false, window, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_release_icon_window(tray_icon_t *icon) {
|
|
||||||
if (!icon || icon->window == XCB_WINDOW_NONE) return;
|
|
||||||
|
|
||||||
xcb_change_save_set(wm.xcb_conn, XCB_SET_MODE_DELETE, icon->window);
|
|
||||||
xcb_params_cw_t params = {
|
|
||||||
.event_mask = XCB_EVENT_MASK_NO_EVENT,
|
|
||||||
.border_pixel = 0,
|
|
||||||
};
|
|
||||||
xcb_aux_change_window_attributes(wm.xcb_conn, icon->window,
|
|
||||||
XCB_CW_EVENT_MASK | XCB_CW_BORDER_PIXEL,
|
|
||||||
¶ms);
|
|
||||||
xcb_reparent_window(wm.xcb_conn, icon->window, wm.screen->root, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_layout_icons(int16_t right_edge) {
|
|
||||||
if (!tray.initialized || tray.window == XCB_WINDOW_NONE ||
|
|
||||||
tray.host_monitor == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t tray_width = tray_compute_width();
|
|
||||||
if (tray_width == 0) {
|
|
||||||
xcb_unmap_window(wm.xcb_conn, tray.window);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t tray_x = (int32_t)right_edge - tray_width;
|
|
||||||
if (tray_x < 0) tray_x = 0;
|
|
||||||
|
|
||||||
xcb_configure_window_value_list_t tray_values = {
|
|
||||||
.x = (int16_t)tray_x,
|
|
||||||
.y = 0,
|
|
||||||
.width = tray_width,
|
|
||||||
.height = wm.bar_height,
|
|
||||||
};
|
|
||||||
uint16_t tray_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
|
||||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
|
|
||||||
xcb_configure_window_aux(wm.xcb_conn, tray.window, tray_mask, &tray_values);
|
|
||||||
xcb_map_window(wm.xcb_conn, tray.window);
|
|
||||||
|
|
||||||
int32_t x = tray.side_padding;
|
|
||||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
|
||||||
tray_icon_t *icon = &tray.icons[i];
|
|
||||||
uint16_t icon_width = 0;
|
|
||||||
uint16_t icon_height = 0;
|
|
||||||
tray_get_icon_layout(icon, &icon_width, &icon_height);
|
|
||||||
|
|
||||||
int32_t y = ((int32_t)wm.bar_height - icon_height) / 2;
|
|
||||||
if (y < 0) y = 0;
|
|
||||||
|
|
||||||
xcb_configure_window_value_list_t icon_values = {
|
|
||||||
.x = (int16_t)x,
|
|
||||||
.y = (int16_t)y,
|
|
||||||
.width = icon_width,
|
|
||||||
.height = icon_height,
|
|
||||||
.border_width = 0,
|
|
||||||
};
|
|
||||||
uint16_t icon_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
|
||||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
|
||||||
XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
|
||||||
xcb_configure_window_aux(wm.xcb_conn, icon->window, icon_mask,
|
|
||||||
&icon_values);
|
|
||||||
xcb_map_window(wm.xcb_conn, icon->window);
|
|
||||||
icon->mapped = true;
|
|
||||||
|
|
||||||
x += icon_width + tray.spacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_request_redraw(void) {
|
|
||||||
if (!tray.initialized || tray.host_monitor == nullptr) return;
|
|
||||||
|
|
||||||
monitor_draw_bar(tray.host_monitor);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_remove_icon_by_index(size_t index, bool release_window) {
|
|
||||||
if (index >= tray.icon_count) return;
|
|
||||||
|
|
||||||
tray_icon_t icon = tray.icons[index];
|
|
||||||
if (release_window) tray_release_icon_window(&icon);
|
|
||||||
|
|
||||||
if (index + 1 < tray.icon_count) {
|
|
||||||
memmove(&tray.icons[index], &tray.icons[index + 1],
|
|
||||||
sizeof(*tray.icons) * (tray.icon_count - index - 1));
|
|
||||||
}
|
|
||||||
tray.icon_count--;
|
|
||||||
|
|
||||||
tray_request_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_add_icon(xcb_window_t window) {
|
|
||||||
if (!tray.initialized || window == XCB_WINDOW_NONE ||
|
|
||||||
tray_get_icon(window) != nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tray.icon_count == tray.icon_capacity) {
|
|
||||||
tray.icon_capacity = (size_t)p_alloc_nr(tray.icon_capacity);
|
|
||||||
p_realloc(&tray.icons, tray.icon_capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_get_geometry_reply_t *geometry = xwindow_get_geometry_reply(window);
|
|
||||||
if (!geometry) return;
|
|
||||||
|
|
||||||
tray_icon_t *icon = &tray.icons[tray.icon_count++];
|
|
||||||
*icon = (tray_icon_t){
|
|
||||||
.window = window,
|
|
||||||
.natural_width = geometry->width,
|
|
||||||
.natural_height = geometry->height,
|
|
||||||
.ignore_unmaps = 1,
|
|
||||||
.mapped = true,
|
|
||||||
};
|
|
||||||
p_delete(&geometry);
|
|
||||||
|
|
||||||
xcb_change_save_set(wm.xcb_conn, XCB_SET_MODE_INSERT, window);
|
|
||||||
xcb_reparent_window(wm.xcb_conn, window, tray.window, 0, 0);
|
|
||||||
tray_select_icon_events(window);
|
|
||||||
tray_send_xembed_message(window, XEMBED_EMBEDDED_NOTIFY, 0, tray.window,
|
|
||||||
XEMBED_VERSION);
|
|
||||||
xcb_map_window(wm.xcb_conn, window);
|
|
||||||
|
|
||||||
tray_request_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool tray_create_window(void) {
|
|
||||||
static xcb_colormap_t colormap = XCB_NONE;
|
|
||||||
if (tray.host_monitor == nullptr ||
|
|
||||||
tray.host_monitor->bar_window == XCB_NONE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
visual_t *visual = xwindow_get_xcb_visual(false);
|
|
||||||
if (visual == nullptr) return false;
|
|
||||||
|
|
||||||
xcb_visualid_t visual_id = visual->visual->visual_id;
|
|
||||||
if (colormap == XCB_NONE) {
|
|
||||||
colormap = xcb_generate_id(wm.xcb_conn);
|
|
||||||
xcb_create_colormap(wm.xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap,
|
|
||||||
wm.screen->root, visual_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_window_t window = xcb_generate_id(wm.xcb_conn);
|
|
||||||
uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
|
||||||
XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
|
|
||||||
const xcb_create_window_value_list_t value_list = {
|
|
||||||
.background_pixel = wm.color_set.bar_bg.argb,
|
|
||||||
.border_pixel = 0,
|
|
||||||
.event_mask =
|
|
||||||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
|
|
||||||
.colormap = colormap,
|
|
||||||
};
|
|
||||||
|
|
||||||
xcb_void_cookie_t cookie = xcb_create_window_aux_checked(
|
|
||||||
wm.xcb_conn, visual->depth, window, tray.host_monitor->bar_window, 0, 0, 1,
|
|
||||||
wm.bar_height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, value_mask,
|
|
||||||
&value_list);
|
|
||||||
p_delete(&visual);
|
|
||||||
if (xcb_request_check(wm.xcb_conn, cookie)) {
|
|
||||||
logger("cannot create system tray window\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
tray.window = window;
|
|
||||||
xwindow_set_class_instance(window);
|
|
||||||
#define NAME APP_NAME "_tray"
|
|
||||||
xwindow_set_name_static(window, NAME);
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, window, _NET_WM_NAME,
|
|
||||||
UTF8_STRING, 8, sizeof(NAME) - 1, NAME);
|
|
||||||
#undef NAME
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool tray_take_selection_owner(void) {
|
|
||||||
xcb_get_selection_owner_cookie_t owner_cookie =
|
|
||||||
xcb_get_selection_owner(wm.xcb_conn, tray.selection_atom);
|
|
||||||
xcb_get_selection_owner_reply_t *owner_reply =
|
|
||||||
xcb_get_selection_owner_reply(wm.xcb_conn, owner_cookie, nullptr);
|
|
||||||
if (owner_reply && owner_reply->owner != XCB_WINDOW_NONE) {
|
|
||||||
logger("system tray selection is already owned by window %u\n",
|
|
||||||
owner_reply->owner);
|
|
||||||
p_delete(&owner_reply);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
p_delete(&owner_reply);
|
|
||||||
|
|
||||||
xcb_set_selection_owner(wm.xcb_conn, tray.window, tray.selection_atom,
|
|
||||||
XCB_CURRENT_TIME);
|
|
||||||
|
|
||||||
owner_cookie = xcb_get_selection_owner(wm.xcb_conn, tray.selection_atom);
|
|
||||||
owner_reply =
|
|
||||||
xcb_get_selection_owner_reply(wm.xcb_conn, owner_cookie, nullptr);
|
|
||||||
bool success = owner_reply && owner_reply->owner == tray.window;
|
|
||||||
p_delete(&owner_reply);
|
|
||||||
|
|
||||||
if (!success) logger("cannot acquire system tray selection\n");
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_set_properties(void) {
|
|
||||||
uint32_t orientation = TRAY_ORIENTATION_HORIZONTAL;
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, tray.window,
|
|
||||||
_NET_SYSTEM_TRAY_ORIENTATION, XCB_ATOM_CARDINAL, 32, 1,
|
|
||||||
&orientation);
|
|
||||||
|
|
||||||
const color_t *fg = &wm.color_set.tag_color;
|
|
||||||
uint32_t colors[12] = {
|
|
||||||
tray_color_component_u16(fg->red), tray_color_component_u16(fg->green),
|
|
||||||
tray_color_component_u16(fg->blue), tray_color_component_u16(fg->red),
|
|
||||||
tray_color_component_u16(fg->green), tray_color_component_u16(fg->blue),
|
|
||||||
tray_color_component_u16(fg->red), tray_color_component_u16(fg->green),
|
|
||||||
tray_color_component_u16(fg->blue), tray_color_component_u16(fg->red),
|
|
||||||
tray_color_component_u16(fg->green), tray_color_component_u16(fg->blue),
|
|
||||||
};
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, tray.window,
|
|
||||||
_NET_SYSTEM_TRAY_COLORS, XCB_ATOM_CARDINAL, 32,
|
|
||||||
countof(colors), colors);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void tray_announce_manager(void) {
|
|
||||||
xcb_client_message_event_t ev;
|
|
||||||
p_clear(&ev, 1);
|
|
||||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
|
||||||
ev.window = wm.screen->root;
|
|
||||||
ev.format = 32;
|
|
||||||
ev.type = MANAGER;
|
|
||||||
ev.data.data32[0] = XCB_CURRENT_TIME;
|
|
||||||
ev.data.data32[1] = tray.selection_atom;
|
|
||||||
ev.data.data32[2] = tray.window;
|
|
||||||
|
|
||||||
xcb_send_event(wm.xcb_conn, false, wm.screen->root,
|
|
||||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *)&ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tray_init(void) {
|
|
||||||
if (tray.initialized) return;
|
|
||||||
|
|
||||||
tray.host_monitor = wm.monitor_list;
|
|
||||||
if (tray.host_monitor == nullptr) return;
|
|
||||||
|
|
||||||
tray.side_padding = 0;
|
|
||||||
tray.spacing = 0;
|
|
||||||
|
|
||||||
char selection_name[64];
|
|
||||||
snprintf(selection_name, sizeof(selection_name), "_NET_SYSTEM_TRAY_S%d",
|
|
||||||
wm.default_screen);
|
|
||||||
tray.selection_atom = tray_intern_atom(selection_name);
|
|
||||||
if (tray.selection_atom == XCB_ATOM_NONE) {
|
|
||||||
logger("cannot intern tray selection atom %s\n", selection_name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tray_create_window()) return;
|
|
||||||
if (!tray_take_selection_owner()) {
|
|
||||||
tray_cleanup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tray.initialized = true;
|
|
||||||
tray_set_properties();
|
|
||||||
tray_announce_manager();
|
|
||||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tray_cleanup(void) {
|
|
||||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
|
||||||
tray_release_icon_window(&tray.icons[i]);
|
|
||||||
}
|
|
||||||
p_delete(&tray.icons);
|
|
||||||
tray.icon_count = 0;
|
|
||||||
tray.icon_capacity = 0;
|
|
||||||
|
|
||||||
if (tray.selection_atom != XCB_ATOM_NONE) {
|
|
||||||
xcb_set_selection_owner(wm.xcb_conn, XCB_WINDOW_NONE, tray.selection_atom,
|
|
||||||
XCB_CURRENT_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tray.window != XCB_WINDOW_NONE) {
|
|
||||||
xcb_destroy_window(wm.xcb_conn, tray.window);
|
|
||||||
}
|
|
||||||
|
|
||||||
tray = (tray_t){
|
|
||||||
.selection_atom = XCB_ATOM_NONE,
|
|
||||||
.window = XCB_WINDOW_NONE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tray_handle_client_message(xcb_client_message_event_t *ev) {
|
|
||||||
if (!tray.initialized) return false;
|
|
||||||
|
|
||||||
if (ev->type != _NET_SYSTEM_TRAY_OPCODE) return false;
|
|
||||||
if (ev->window != tray.window && ev->window != wm.screen->root) return false;
|
|
||||||
|
|
||||||
switch (ev->data.data32[1]) {
|
|
||||||
case TRAY_REQUEST_DOCK:
|
|
||||||
tray_add_icon((xcb_window_t)ev->data.data32[2]);
|
|
||||||
break;
|
|
||||||
case TRAY_BEGIN_MESSAGE:
|
|
||||||
case TRAY_CANCEL_MESSAGE:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tray_handle_configure_request(xcb_configure_request_event_t *ev) {
|
|
||||||
if (!tray.initialized) return false;
|
|
||||||
|
|
||||||
if (ev->window == tray.window) {
|
|
||||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
|
||||||
if (!icon) return false;
|
|
||||||
|
|
||||||
if ((ev->value_mask & XCB_CONFIG_WINDOW_WIDTH) && ev->width > 0) {
|
|
||||||
icon->natural_width = ev->width;
|
|
||||||
}
|
|
||||||
if ((ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT) && ev->height > 0) {
|
|
||||||
icon->natural_height = ev->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
tray_request_redraw();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tray_handle_map_request(xcb_map_request_event_t *ev) {
|
|
||||||
if (!tray.initialized) return false;
|
|
||||||
|
|
||||||
if (ev->window == tray.window) {
|
|
||||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
|
||||||
if (!icon && ev->parent != tray.window) return false;
|
|
||||||
|
|
||||||
if (icon) icon->mapped = true;
|
|
||||||
xcb_map_window(wm.xcb_conn, ev->window);
|
|
||||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tray_handle_destroy_notify(xcb_destroy_notify_event_t *ev) {
|
|
||||||
if (!tray.initialized) return false;
|
|
||||||
|
|
||||||
if (ev->window == tray.window) {
|
|
||||||
tray.window = XCB_WINDOW_NONE;
|
|
||||||
tray.initialized = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t index = tray_find_icon_index(ev->window);
|
|
||||||
if (index == SIZE_MAX) return false;
|
|
||||||
|
|
||||||
tray_remove_icon_by_index(index, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tray_handle_unmap_notify(xcb_unmap_notify_event_t *ev) {
|
|
||||||
if (!tray.initialized) return false;
|
|
||||||
|
|
||||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
|
||||||
if (!icon) return ev->window == tray.window;
|
|
||||||
|
|
||||||
if (icon->ignore_unmaps > 0) {
|
|
||||||
icon->ignore_unmaps--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t index = tray_find_icon_index(ev->window);
|
|
||||||
if (index != SIZE_MAX) tray_remove_icon_by_index(index, true);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tray_handle_property_notify(xcb_property_notify_event_t *ev) {
|
|
||||||
if (!tray.initialized) return false;
|
|
||||||
return tray_is_window(ev->window);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tray_handle_selection_clear(xcb_selection_clear_event_t *ev) {
|
|
||||||
if (!tray.initialized) return false;
|
|
||||||
if (ev->owner != tray.window) return false;
|
|
||||||
if (ev->selection != tray.selection_atom) return false;
|
|
||||||
|
|
||||||
monitor_t *host_monitor = tray.host_monitor;
|
|
||||||
logger("system tray selection lost\n");
|
|
||||||
tray_cleanup();
|
|
||||||
|
|
||||||
if (host_monitor != nullptr) monitor_draw_bar(host_monitor);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t tray_get_width(monitor_t *monitor) {
|
|
||||||
if (!tray.initialized || monitor == nullptr || monitor != tray.host_monitor) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tray_compute_width();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tray_place(monitor_t *monitor, int16_t right_edge) {
|
|
||||||
if (!tray.initialized || monitor == nullptr || monitor != tray.host_monitor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tray_layout_icons(right_edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tray_is_window(xcb_window_t window) {
|
|
||||||
if (window == XCB_WINDOW_NONE) return false;
|
|
||||||
if (tray.window == window) return true;
|
|
||||||
return tray_get_icon(window) != nullptr;
|
|
||||||
}
|
|
||||||
21
src/tray.h
21
src/tray.h
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
void tray_init(void);
|
|
||||||
void tray_cleanup(void);
|
|
||||||
|
|
||||||
bool tray_handle_client_message(xcb_client_message_event_t *ev);
|
|
||||||
bool tray_handle_configure_request(xcb_configure_request_event_t *ev);
|
|
||||||
bool tray_handle_map_request(xcb_map_request_event_t *ev);
|
|
||||||
bool tray_handle_destroy_notify(xcb_destroy_notify_event_t *ev);
|
|
||||||
bool tray_handle_unmap_notify(xcb_unmap_notify_event_t *ev);
|
|
||||||
bool tray_handle_property_notify(xcb_property_notify_event_t *ev);
|
|
||||||
bool tray_handle_selection_clear(xcb_selection_clear_event_t *ev);
|
|
||||||
|
|
||||||
uint16_t tray_get_width(monitor_t *monitor);
|
|
||||||
void tray_place(monitor_t *monitor, int16_t right_edge);
|
|
||||||
bool tray_is_window(xcb_window_t window);
|
|
||||||
114
src/types.h
114
src/types.h
@@ -1,114 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cairo.h>
|
|
||||||
#include <glib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
#include "color.h"
|
|
||||||
|
|
||||||
typedef struct client_t client_t;
|
|
||||||
typedef struct monitor_t monitor_t;
|
|
||||||
typedef struct tag_t tag_t;
|
|
||||||
typedef struct task_in_tag_t task_in_tag_t;
|
|
||||||
|
|
||||||
typedef struct layout_t {
|
|
||||||
char *symbol;
|
|
||||||
void (*arrange)(tag_t *tag);
|
|
||||||
} layout_t;
|
|
||||||
|
|
||||||
struct client_t {
|
|
||||||
client_t *stack_next;
|
|
||||||
client_t *next;
|
|
||||||
|
|
||||||
monitor_t *monitor;
|
|
||||||
uint32_t tags;
|
|
||||||
|
|
||||||
area_t old_geometry;
|
|
||||||
area_t geometry;
|
|
||||||
uint16_t border_width;
|
|
||||||
uint16_t old_border_width;
|
|
||||||
color_t *border_color;
|
|
||||||
|
|
||||||
bool floating;
|
|
||||||
bool fullscreen;
|
|
||||||
bool maximize;
|
|
||||||
bool minimize;
|
|
||||||
bool sticky;
|
|
||||||
bool urgent;
|
|
||||||
bool skip_taskbar;
|
|
||||||
bool size_freeze; /* 尺寸冻结窗口一定是浮动窗口 */
|
|
||||||
|
|
||||||
char *class, *instance;
|
|
||||||
char *name, *net_name;
|
|
||||||
char *role;
|
|
||||||
|
|
||||||
xcb_window_t transient_for_window;
|
|
||||||
xcb_window_t leader_window;
|
|
||||||
xcb_window_t frame_window;
|
|
||||||
xcb_window_t window;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct monitor_t {
|
|
||||||
monitor_t *next;
|
|
||||||
area_t geometry;
|
|
||||||
area_t workarea;
|
|
||||||
tag_t *tag_list;
|
|
||||||
tag_t *selected_tag;
|
|
||||||
|
|
||||||
char *name;
|
|
||||||
cairo_t *bar_cr;
|
|
||||||
|
|
||||||
extent_in_bar_t tag_extent;
|
|
||||||
extent_in_bar_t layout_symbol_extent;
|
|
||||||
extent_in_bar_t status_extent;
|
|
||||||
|
|
||||||
xcb_window_t bar_window;
|
|
||||||
|
|
||||||
point_t cursor_position;
|
|
||||||
bool position_inited;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct tag_t {
|
|
||||||
uint32_t index; /* index in all tags */
|
|
||||||
uint32_t mask;
|
|
||||||
extent_in_bar_t bar_extent;
|
|
||||||
tag_t *next;
|
|
||||||
char *name;
|
|
||||||
const layout_t *layout;
|
|
||||||
task_in_tag_t *task_list;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct task_in_tag_t {
|
|
||||||
client_t *client;
|
|
||||||
task_in_tag_t *next;
|
|
||||||
area_t geometry;
|
|
||||||
extent_in_bar_t bar_extent;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct color_set_t {
|
|
||||||
color_t bar_bg;
|
|
||||||
color_t tag_bg;
|
|
||||||
color_t active_tag_bg;
|
|
||||||
color_t tag_color;
|
|
||||||
color_t active_tag_color;
|
|
||||||
color_t border_color;
|
|
||||||
color_t active_border_color;
|
|
||||||
} color_set_t;
|
|
||||||
|
|
||||||
typedef struct padding_t {
|
|
||||||
uint16_t bar_y;
|
|
||||||
uint16_t tag_x;
|
|
||||||
} padding_t;
|
|
||||||
|
|
||||||
typedef struct rule_t {
|
|
||||||
const char *role;
|
|
||||||
const char *class;
|
|
||||||
uint32_t tag_index;
|
|
||||||
bool switch_to_tag;
|
|
||||||
bool fullscreen;
|
|
||||||
bool maximize;
|
|
||||||
bool floating;
|
|
||||||
} rule_t;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
/* Compatibility umbrella header. Prefer including headers from src/base in new
|
|
||||||
* code. */
|
|
||||||
|
|
||||||
#include "base/log.h"
|
|
||||||
#include "base/macros.h"
|
|
||||||
#include "base/memory.h"
|
|
||||||
313
src/wallpaper.c
313
src/wallpaper.c
@@ -1,313 +0,0 @@
|
|||||||
#include "wallpaper.h"
|
|
||||||
|
|
||||||
#include <Imlib2.h>
|
|
||||||
#include <cairo-xcb.h>
|
|
||||||
#include <cairo.h>
|
|
||||||
#include <glib.h>
|
|
||||||
#include <glibconfig.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <wordexp.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "atoms-extern.h"
|
|
||||||
#include "base.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "xwindow.h"
|
|
||||||
|
|
||||||
struct wallpaper_context_t {
|
|
||||||
uint32_t interval;
|
|
||||||
uint32_t image_count;
|
|
||||||
char **image_path_list;
|
|
||||||
|
|
||||||
xcb_screen_t *screen;
|
|
||||||
xcb_connection_t *conn;
|
|
||||||
|
|
||||||
area_t *monitor_geometries;
|
|
||||||
uint32_t monitor_count;
|
|
||||||
|
|
||||||
xcb_pixmap_t pixmap;
|
|
||||||
cairo_t *cr;
|
|
||||||
|
|
||||||
uint32_t index;
|
|
||||||
|
|
||||||
guint timer;
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline bool file_exist(const char *path) {
|
|
||||||
if (!path || !*path) return false;
|
|
||||||
return access(path, R_OK) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char **parse_wallpaper_paths(char **images, uint32_t image_count) {
|
|
||||||
GStrvBuilder *builder = g_strv_builder_new();
|
|
||||||
for (uint32_t i = 0; i < image_count; i++) {
|
|
||||||
wordexp_t p;
|
|
||||||
if (wordexp(images[i], &p, 0)) continue;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < p.we_wordc; i++) {
|
|
||||||
size_t len = strlen(p.we_wordv[i]) + 1;
|
|
||||||
char *realpath = p_new(char, len);
|
|
||||||
strncpy(realpath, p.we_wordv[i], len);
|
|
||||||
g_strstrip(realpath);
|
|
||||||
if (file_exist(realpath)) g_strv_builder_add(builder, realpath);
|
|
||||||
|
|
||||||
p_delete(&realpath);
|
|
||||||
}
|
|
||||||
wordfree(&p);
|
|
||||||
}
|
|
||||||
|
|
||||||
return g_strv_builder_unref_to_strv(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t *generate_wallpaper(wallpaper_context_t *context) {
|
|
||||||
int width = context->screen->width_in_pixels;
|
|
||||||
int height = context->screen->height_in_pixels;
|
|
||||||
|
|
||||||
Imlib_Image final_image = imlib_create_image(width, height);
|
|
||||||
if (!final_image) return nullptr;
|
|
||||||
|
|
||||||
imlib_context_set_image(final_image);
|
|
||||||
imlib_image_set_has_alpha(true);
|
|
||||||
|
|
||||||
/* 重置像素数据 */
|
|
||||||
imlib_image_clear();
|
|
||||||
for (uint32_t i = 0; i < context->monitor_count; i++) {
|
|
||||||
uint32_t img_index = (context->index + i) % context->image_count;
|
|
||||||
const char *path = context->image_path_list[img_index];
|
|
||||||
logger("== wallpaper[%u]: %s\n", img_index, path);
|
|
||||||
Imlib_Image wallpaper_img = imlib_load_image(path);
|
|
||||||
if (!wallpaper_img) continue;
|
|
||||||
|
|
||||||
uint32_t dst_width = context->monitor_geometries[i].width;
|
|
||||||
uint32_t dst_height = context->monitor_geometries[i].height;
|
|
||||||
|
|
||||||
/* 获取图片原始尺寸 */
|
|
||||||
imlib_context_set_image(wallpaper_img);
|
|
||||||
uint32_t img_width = imlib_image_get_width();
|
|
||||||
uint32_t img_height = imlib_image_get_height();
|
|
||||||
|
|
||||||
/* 计算缩放比例,保持原始宽高比 */
|
|
||||||
float width_ratio = (float)dst_width / img_width;
|
|
||||||
float height_ratio = (float)dst_height / img_height;
|
|
||||||
float scale = MAX(width_ratio, height_ratio);
|
|
||||||
|
|
||||||
/* 图片居中显示,计算原图绘制区域 */
|
|
||||||
int src_width = dst_width / scale;
|
|
||||||
int src_height = dst_height / scale;
|
|
||||||
int src_x = (img_width - src_width) / 2;
|
|
||||||
int src_y = (img_height - src_height) / 2;
|
|
||||||
|
|
||||||
/* 绘制图片到最终图像上 */
|
|
||||||
imlib_context_set_image(final_image);
|
|
||||||
imlib_blend_image_onto_image(wallpaper_img, true, src_x, src_y, src_width,
|
|
||||||
src_height, context->monitor_geometries[i].x,
|
|
||||||
context->monitor_geometries[i].y, dst_width,
|
|
||||||
dst_height);
|
|
||||||
|
|
||||||
/* 释放图片资源 */
|
|
||||||
imlib_context_set_image(wallpaper_img);
|
|
||||||
imlib_free_image();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 获取最终图像数据 */
|
|
||||||
imlib_context_set_image(final_image);
|
|
||||||
uint32_t *data = imlib_image_get_data_for_reading_only();
|
|
||||||
|
|
||||||
/* 复制数据到自己分配的内存中(原数据会在图像释放后无效) */
|
|
||||||
int count = width * height;
|
|
||||||
uint32_t *result = p_new(uint32_t, count);
|
|
||||||
if (result) memcpy(result, data, count * sizeof(uint32_t));
|
|
||||||
|
|
||||||
/* 释放图像 */
|
|
||||||
imlib_context_set_image(final_image);
|
|
||||||
imlib_free_image();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
double get_time() {
|
|
||||||
struct timeval tv;
|
|
||||||
gettimeofday(&tv, nullptr);
|
|
||||||
return tv.tv_sec + tv.tv_usec / 1000000.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cairo_set_wallpaper(wallpaper_context_t *context,
|
|
||||||
uint8_t *wallpaper_data) {
|
|
||||||
xcb_connection_t *conn = context->conn;
|
|
||||||
xcb_window_t root = context->screen->root;
|
|
||||||
xcb_pixmap_t pixmap = context->pixmap;
|
|
||||||
uint32_t width = context->screen->width_in_pixels;
|
|
||||||
uint32_t height = context->screen->height_in_pixels;
|
|
||||||
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
|
|
||||||
cairo_surface_t *s = cairo_image_surface_create_for_data(
|
|
||||||
wallpaper_data, CAIRO_FORMAT_ARGB32, width, height, stride);
|
|
||||||
cairo_set_source_surface(context->cr, s, 0, 0);
|
|
||||||
cairo_paint(context->cr);
|
|
||||||
|
|
||||||
xcb_change_window_attributes_value_list_t value_list = {
|
|
||||||
.background_pixmap = pixmap,
|
|
||||||
};
|
|
||||||
xcb_change_window_attributes_aux(conn, root, XCB_CW_BACK_PIXMAP, &value_list);
|
|
||||||
|
|
||||||
/* 即使壁纸对应的 pixmap 未变更,也强制通知合成器壁纸已更改 */
|
|
||||||
uint8_t mode = XCB_PROP_MODE_REPLACE;
|
|
||||||
xcb_pixmap_t none = XCB_PIXMAP_NONE;
|
|
||||||
xcb_atom_t atom = _XROOTPMAP_ID;
|
|
||||||
xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &none);
|
|
||||||
xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap);
|
|
||||||
atom = ESETROOT_PMAP_ID;
|
|
||||||
xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &none);
|
|
||||||
xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap);
|
|
||||||
|
|
||||||
xcb_clear_area(conn, 0, root, 0, 0, 0, 0);
|
|
||||||
xcb_flush(conn);
|
|
||||||
cairo_surface_destroy(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void set_wallpaper(wallpaper_context_t *context) {
|
|
||||||
double start = get_time();
|
|
||||||
uint32_t *wallpaper = generate_wallpaper(context);
|
|
||||||
logger("== generate wallpaper cost: %lf, %p\n", get_time() - start,
|
|
||||||
wallpaper);
|
|
||||||
if (!wallpaper) return;
|
|
||||||
|
|
||||||
uint8_t *data = (uint8_t *)wallpaper;
|
|
||||||
|
|
||||||
cairo_set_wallpaper(context, data);
|
|
||||||
|
|
||||||
p_delete(&wallpaper);
|
|
||||||
|
|
||||||
context->index += context->monitor_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean update_wallpaper(gpointer user_data) {
|
|
||||||
if (!user_data) return FALSE;
|
|
||||||
|
|
||||||
wallpaper_context_t *wc = user_data;
|
|
||||||
set_wallpaper(wc);
|
|
||||||
|
|
||||||
/* 屏幕比壁纸多则不动态切换 */
|
|
||||||
if (wc->monitor_count >= wc->image_count) return FALSE;
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool prepare_wallpaper_surface(wallpaper_context_t *context) {
|
|
||||||
uint16_t width = context->screen->width_in_pixels;
|
|
||||||
uint16_t height = context->screen->height_in_pixels;
|
|
||||||
xcb_window_t root = context->screen->root;
|
|
||||||
xcb_connection_t *conn = context->conn;
|
|
||||||
xcb_pixmap_t pixmap = context->pixmap;
|
|
||||||
|
|
||||||
visual_t *visual = xwindow_get_xcb_visual(false);
|
|
||||||
|
|
||||||
if (pixmap != XCB_PIXMAP_NONE) {
|
|
||||||
xcb_kill_client(conn, pixmap);
|
|
||||||
xcb_free_pixmap(conn, pixmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
pixmap = xcb_generate_id(conn);
|
|
||||||
xcb_create_pixmap(conn, visual->depth, pixmap, root, width, height);
|
|
||||||
xcb_gcontext_t gc = xcb_generate_id(conn);
|
|
||||||
xcb_create_gc(conn, gc, pixmap, 0, nullptr);
|
|
||||||
xcb_change_window_attributes_value_list_t value_list = {
|
|
||||||
.background_pixmap = pixmap,
|
|
||||||
};
|
|
||||||
xcb_change_window_attributes_aux(conn, root, XCB_CW_BACK_PIXMAP, &value_list);
|
|
||||||
|
|
||||||
/* 兼容合成器,不设置若开启合成器壁纸会不显示 */
|
|
||||||
uint8_t mode = XCB_PROP_MODE_REPLACE;
|
|
||||||
xcb_atom_t atom = _XROOTPMAP_ID;
|
|
||||||
xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap);
|
|
||||||
atom = ESETROOT_PMAP_ID;
|
|
||||||
xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap);
|
|
||||||
|
|
||||||
xcb_clear_area(conn, 0, root, 0, 0, width, height);
|
|
||||||
xcb_free_gc(conn, gc);
|
|
||||||
xcb_flush(conn);
|
|
||||||
|
|
||||||
context->pixmap = pixmap;
|
|
||||||
|
|
||||||
if (context->cr) cairo_destroy(context->cr);
|
|
||||||
cairo_surface_t *surface =
|
|
||||||
cairo_xcb_surface_create(conn, pixmap, visual->visual, width, height);
|
|
||||||
p_delete(&visual);
|
|
||||||
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
|
|
||||||
cairo_surface_destroy(surface);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cairo_t *cr = cairo_create(surface);
|
|
||||||
if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
|
|
||||||
cairo_destroy(cr);
|
|
||||||
cairo_surface_destroy(surface);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
context->cr = cr;
|
|
||||||
|
|
||||||
cairo_surface_destroy(surface);
|
|
||||||
cairo_set_source_rgba(cr, 0, 0, 0, 1);
|
|
||||||
cairo_paint(cr);
|
|
||||||
xcb_clear_area(conn, 0, root, 0, 0, 0, 0);
|
|
||||||
xcb_flush(context->conn);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
wallpaper_context_t *wallpaper_init(wallpaper_config_t config) {
|
|
||||||
if (!config.path_count || !config.paths) return nullptr;
|
|
||||||
if (!config.monitors) return nullptr;
|
|
||||||
|
|
||||||
char **images = parse_wallpaper_paths(config.paths, config.path_count);
|
|
||||||
if (!images || !g_strv_length(images)) return nullptr;
|
|
||||||
|
|
||||||
wallpaper_context_t *ctx = p_new(wallpaper_context_t, 1);
|
|
||||||
ctx->interval = config.interval;
|
|
||||||
ctx->image_path_list = images;
|
|
||||||
ctx->image_count = g_strv_length(images);
|
|
||||||
|
|
||||||
ctx->conn = config.conn;
|
|
||||||
ctx->screen = config.screen;
|
|
||||||
|
|
||||||
{
|
|
||||||
uint32_t i = 0;
|
|
||||||
monitor_t *m = nullptr;
|
|
||||||
for (m = config.monitors; m; m = m->next) ctx->monitor_count++;
|
|
||||||
|
|
||||||
ctx->monitor_geometries = p_new(area_t, ctx->monitor_count);
|
|
||||||
for (m = config.monitors; m; m = m->next) {
|
|
||||||
ctx->monitor_geometries[i++] = m->geometry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prepare_wallpaper_surface(ctx)) {
|
|
||||||
wallpaper_clean(ctx);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!update_wallpaper(ctx) || !ctx->interval) {
|
|
||||||
wallpaper_clean(ctx);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->timer = g_timeout_add_seconds(ctx->interval, update_wallpaper, ctx);
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wallpaper_clean(wallpaper_context_t *context) {
|
|
||||||
if (!context) return;
|
|
||||||
|
|
||||||
if (context->timer) g_source_remove(context->timer);
|
|
||||||
if (context->cr) cairo_destroy(context->cr);
|
|
||||||
p_delete(&context->monitor_geometries);
|
|
||||||
if (context->image_path_list) g_strfreev(context->image_path_list);
|
|
||||||
|
|
||||||
p_delete(&context);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
typedef struct wallpaper_context_t wallpaper_context_t;
|
|
||||||
|
|
||||||
typedef struct wallpaper_config_t {
|
|
||||||
uint32_t interval;
|
|
||||||
uint32_t path_count;
|
|
||||||
char **paths;
|
|
||||||
xcb_screen_t *screen;
|
|
||||||
xcb_connection_t *conn;
|
|
||||||
monitor_t *monitors;
|
|
||||||
} wallpaper_config_t;
|
|
||||||
|
|
||||||
wallpaper_context_t *wallpaper_init(wallpaper_config_t config);
|
|
||||||
void wallpaper_clean(wallpaper_context_t *context);
|
|
||||||
775
src/wm.c
775
src/wm.c
@@ -1,775 +0,0 @@
|
|||||||
#include "wm.h"
|
|
||||||
|
|
||||||
#include <glib-unix.h>
|
|
||||||
#include <glib.h>
|
|
||||||
#include <glibconfig.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <xcb/randr.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_aux.h>
|
|
||||||
#include <xcb/xcb_icccm.h>
|
|
||||||
#include <xcb/xcb_keysyms.h>
|
|
||||||
#include <xcb/xfixes.h>
|
|
||||||
#include <xcb/xinerama.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "app.h"
|
|
||||||
#include "atoms-extern.h"
|
|
||||||
#include "atoms.h"
|
|
||||||
#include "backtrace.h"
|
|
||||||
#include "base.h"
|
|
||||||
#include "buffer.h"
|
|
||||||
#include "client.h"
|
|
||||||
#include "color.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "default_config.h"
|
|
||||||
#include "event.h"
|
|
||||||
#include "image.h"
|
|
||||||
#include "monitor.h"
|
|
||||||
#include "status.h"
|
|
||||||
#include "text.h"
|
|
||||||
#include "tray.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wallpaper.h"
|
|
||||||
#include "xcursor.h"
|
|
||||||
#include "xkb.h"
|
|
||||||
#include "xres.h"
|
|
||||||
#include "xwindow.h"
|
|
||||||
|
|
||||||
static void wm_setup_signal(void);
|
|
||||||
static void wm_check_other_wm(void);
|
|
||||||
static void wm_check_xcb_extensions(void);
|
|
||||||
static void wm_detect_monitor(void);
|
|
||||||
static void wm_scan_clients(void);
|
|
||||||
static void wm_clean(void);
|
|
||||||
static void wm_setup(void);
|
|
||||||
static void wm_init_wallpaper(void);
|
|
||||||
static void wm_get_xres_config(void);
|
|
||||||
static void wm_init_color_set(void);
|
|
||||||
static void wm_setup_keybindings(void);
|
|
||||||
static void wm_update_status(status_t *status);
|
|
||||||
static void wm_run_autostart(const char *const commands[]);
|
|
||||||
static void run_once(const char *command);
|
|
||||||
static bool command_already_running(const char *command);
|
|
||||||
static gboolean clear_ignore_enter_notify(gpointer data);
|
|
||||||
|
|
||||||
wm_t wm;
|
|
||||||
|
|
||||||
/** argv used to run wm */
|
|
||||||
static char **cmd_argv;
|
|
||||||
|
|
||||||
static gboolean restart_on_signal(gpointer data) {
|
|
||||||
wm_restart();
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean exit_on_signal(gpointer data) {
|
|
||||||
g_main_loop_quit(wm.loop);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void signal_fatal(int signal_number) {
|
|
||||||
buffer_t buffer;
|
|
||||||
backtrace_get(&buffer);
|
|
||||||
fatal("signal %d, dumping backtrace\n%s", signal_number, buffer.s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static guint sources[3] = {0};
|
|
||||||
|
|
||||||
static gboolean clear_ignore_enter_notify(gpointer data) {
|
|
||||||
wm.ignore_enter_notify = false;
|
|
||||||
wm.ignored_enter_notify_point = (point_t){0};
|
|
||||||
wm.clear_ignore_enter_notify_source = 0;
|
|
||||||
return G_SOURCE_REMOVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_ignore_enter_notify_at_point(point_t point) {
|
|
||||||
wm.ignore_enter_notify = true;
|
|
||||||
wm.ignored_enter_notify_point = point;
|
|
||||||
if (wm.clear_ignore_enter_notify_source) {
|
|
||||||
g_source_remove(wm.clear_ignore_enter_notify_source);
|
|
||||||
}
|
|
||||||
wm.clear_ignore_enter_notify_source =
|
|
||||||
g_idle_add(clear_ignore_enter_notify, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool wm_should_ignore_enter_notify(const xcb_enter_notify_event_t *ev) {
|
|
||||||
if (!wm.ignore_enter_notify) return false;
|
|
||||||
if (ev->root_x != wm.ignored_enter_notify_point.x ||
|
|
||||||
ev->root_y != wm.ignored_enter_notify_point.y) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wm.clear_ignore_enter_notify_source) {
|
|
||||||
g_source_remove(wm.clear_ignore_enter_notify_source);
|
|
||||||
wm.clear_ignore_enter_notify_source = 0;
|
|
||||||
}
|
|
||||||
wm.ignore_enter_notify = false;
|
|
||||||
wm.ignored_enter_notify_point = (point_t){0};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_setup_signal(void) {
|
|
||||||
sources[0] = g_unix_signal_add(SIGINT, exit_on_signal, nullptr);
|
|
||||||
sources[1] = g_unix_signal_add(SIGTERM, exit_on_signal, nullptr);
|
|
||||||
sources[2] = g_unix_signal_add(SIGHUP, restart_on_signal, nullptr);
|
|
||||||
|
|
||||||
struct sigaction sa = {.sa_handler = signal_fatal, .sa_flags = SA_RESETHAND};
|
|
||||||
sigemptyset(&sa.sa_mask);
|
|
||||||
sigaction(SIGABRT, &sa, 0);
|
|
||||||
sigaction(SIGBUS, &sa, 0);
|
|
||||||
sigaction(SIGFPE, &sa, 0);
|
|
||||||
sigaction(SIGILL, &sa, 0);
|
|
||||||
sigaction(SIGSEGV, &sa, 0);
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_check_other_wm(void) {
|
|
||||||
/* connect X server */
|
|
||||||
int default_screen;
|
|
||||||
xcb_connection_t *conn = xcb_connect(nullptr, &default_screen);
|
|
||||||
int xcb_conn_error = xcb_connection_has_error(conn);
|
|
||||||
if (xcb_conn_error) fatal("cannot open display (error %d)", xcb_conn_error);
|
|
||||||
|
|
||||||
xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen);
|
|
||||||
xcb_window_t root = screen->root;
|
|
||||||
|
|
||||||
/* check other window manager running */
|
|
||||||
uint32_t mask = XCB_CW_EVENT_MASK;
|
|
||||||
const xcb_params_cw_t params = {
|
|
||||||
.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
|
||||||
};
|
|
||||||
xcb_void_cookie_t cookie =
|
|
||||||
xcb_aux_change_window_attributes_checked(conn, root, mask, ¶ms);
|
|
||||||
if (xcb_request_check(conn, cookie)) {
|
|
||||||
fatal(
|
|
||||||
"another window manager is already running (cannot select "
|
|
||||||
"SubstructureRedirect)");
|
|
||||||
}
|
|
||||||
|
|
||||||
wm.xcb_conn = conn;
|
|
||||||
wm.default_screen = default_screen;
|
|
||||||
wm.screen = screen;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_check_xcb_extensions(void) {
|
|
||||||
xcb_prefetch_extension_data(wm.xcb_conn, &xcb_xfixes_id);
|
|
||||||
xcb_prefetch_extension_data(wm.xcb_conn, &xcb_xinerama_id);
|
|
||||||
xcb_prefetch_extension_data(wm.xcb_conn, &xcb_randr_id);
|
|
||||||
|
|
||||||
const xcb_query_extension_reply_t *query;
|
|
||||||
query = xcb_get_extension_data(wm.xcb_conn, &xcb_xfixes_id);
|
|
||||||
wm.have_xfixes = query && query->present;
|
|
||||||
|
|
||||||
query = xcb_get_extension_data(wm.xcb_conn, &xcb_xinerama_id);
|
|
||||||
wm.have_xinerama = query && query->present;
|
|
||||||
|
|
||||||
query = xcb_get_extension_data(wm.xcb_conn, &xcb_randr_id);
|
|
||||||
wm.have_randr = query && query->present;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int32_t right(area_t a) {
|
|
||||||
return (int32_t)a.x + (int32_t)a.width;
|
|
||||||
}
|
|
||||||
static inline int32_t bottom(area_t a) {
|
|
||||||
return (int32_t)a.y + (int32_t)a.height;
|
|
||||||
}
|
|
||||||
static inline bool fully_contains(area_t outer, area_t inner) {
|
|
||||||
return inner.x >= outer.x && inner.y >= outer.y &&
|
|
||||||
right(inner) <= right(outer) && bottom(inner) <= bottom(outer);
|
|
||||||
}
|
|
||||||
static inline void monitor_remove_duplication(monitor_t **monitor_list) {
|
|
||||||
if (!monitor_list || !*monitor_list) return;
|
|
||||||
|
|
||||||
int32_t count = 0;
|
|
||||||
for (monitor_t *m = *monitor_list; m; m = m->next) count++;
|
|
||||||
if (count <= 1) return;
|
|
||||||
|
|
||||||
monitor_t **nodes = p_new(monitor_t *, count);
|
|
||||||
bool *remove = p_new(bool, count);
|
|
||||||
|
|
||||||
size_t index = 0;
|
|
||||||
for (monitor_t *m = *monitor_list; m; m = m->next) nodes[index++] = m;
|
|
||||||
|
|
||||||
for (int32_t i = count - 1; i >= 0; i--) {
|
|
||||||
if (remove[i]) continue;
|
|
||||||
for (int32_t j = i - 1; j >= 0; j--) {
|
|
||||||
if (remove[j]) continue;
|
|
||||||
if (fully_contains(nodes[j]->geometry, nodes[i]->geometry)) {
|
|
||||||
remove[i] = true;
|
|
||||||
} else if (fully_contains(nodes[i]->geometry, nodes[j]->geometry)) {
|
|
||||||
remove[j] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_t *head = nullptr;
|
|
||||||
monitor_t *tail = nullptr;
|
|
||||||
for (int32_t i = 0; i < count; i++) {
|
|
||||||
monitor_t *node = nodes[i];
|
|
||||||
if (remove[i]) {
|
|
||||||
p_delete(&node->name);
|
|
||||||
p_delete(&node);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tail) {
|
|
||||||
tail->next = node;
|
|
||||||
} else {
|
|
||||||
head = node;
|
|
||||||
}
|
|
||||||
node->next = nullptr;
|
|
||||||
tail = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tail) tail->next = nullptr;
|
|
||||||
*monitor_list = head;
|
|
||||||
|
|
||||||
p_delete(&nodes);
|
|
||||||
p_delete(&remove);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool wm_detect_monitor_by_randr(void) {
|
|
||||||
xcb_randr_get_monitors_cookie_t monitors_cookie =
|
|
||||||
xcb_randr_get_monitors(wm.xcb_conn, wm.screen->root, 1);
|
|
||||||
xcb_randr_get_monitors_reply_t *monitors_reply =
|
|
||||||
xcb_randr_get_monitors_reply(wm.xcb_conn, monitors_cookie, nullptr);
|
|
||||||
if (monitors_reply == nullptr) {
|
|
||||||
warn("RandR get monitor failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_t *prev_monitor = nullptr;
|
|
||||||
xcb_randr_monitor_info_iterator_t monitor_iter =
|
|
||||||
xcb_randr_get_monitors_monitors_iterator(monitors_reply);
|
|
||||||
for (; monitor_iter.rem; xcb_randr_monitor_info_next(&monitor_iter)) {
|
|
||||||
monitor_t *monitor = p_new(monitor_t, 1);
|
|
||||||
monitor->geometry.x = monitor_iter.data->x;
|
|
||||||
monitor->geometry.y = monitor_iter.data->y;
|
|
||||||
monitor->geometry.width = monitor_iter.data->width;
|
|
||||||
monitor->geometry.height = monitor_iter.data->height;
|
|
||||||
|
|
||||||
xcb_get_atom_name_cookie_t name_cookie =
|
|
||||||
xcb_get_atom_name_unchecked(wm.xcb_conn, monitor_iter.data->name);
|
|
||||||
xcb_get_atom_name_reply_t *name_reply =
|
|
||||||
xcb_get_atom_name_reply(wm.xcb_conn, name_cookie, nullptr);
|
|
||||||
|
|
||||||
if (name_reply) {
|
|
||||||
char *name = xcb_get_atom_name_name(name_reply);
|
|
||||||
int length = xcb_get_atom_name_name_length(name_reply);
|
|
||||||
monitor->name = memcpy(p_new(char, length + 1), name, length);
|
|
||||||
monitor->name[length] = '\0';
|
|
||||||
p_delete(&name_reply);
|
|
||||||
} else {
|
|
||||||
monitor->name = strdup("unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev_monitor) {
|
|
||||||
prev_monitor->next = monitor;
|
|
||||||
} else {
|
|
||||||
wm.monitor_list = monitor;
|
|
||||||
wm.current_monitor = monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_monitor = monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
p_delete(&monitors_reply);
|
|
||||||
|
|
||||||
monitor_remove_duplication(&wm.monitor_list);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool wm_detect_monitor_by_xinerama(void) {
|
|
||||||
xcb_xinerama_is_active_cookie_t active_cookie =
|
|
||||||
xcb_xinerama_is_active(wm.xcb_conn);
|
|
||||||
xcb_xinerama_is_active_reply_t *active_reply =
|
|
||||||
xcb_xinerama_is_active_reply(wm.xcb_conn, active_cookie, nullptr);
|
|
||||||
if (!active_reply || !active_reply->state) {
|
|
||||||
p_delete(&active_reply);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_xinerama_query_screens_cookie_t screens_cookie =
|
|
||||||
xcb_xinerama_query_screens(wm.xcb_conn);
|
|
||||||
xcb_xinerama_query_screens_reply_t *screens_reply =
|
|
||||||
xcb_xinerama_query_screens_reply(wm.xcb_conn, screens_cookie, nullptr);
|
|
||||||
|
|
||||||
if (screens_reply == nullptr) return false;
|
|
||||||
|
|
||||||
int count = xcb_xinerama_query_screens_screen_info_length(screens_reply);
|
|
||||||
if (count <= 0) {
|
|
||||||
p_delete(&screens_reply);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_xinerama_screen_info_t *screen_info =
|
|
||||||
xcb_xinerama_query_screens_screen_info(screens_reply);
|
|
||||||
|
|
||||||
monitor_t *prev_monitor = nullptr;
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
monitor_t *monitor = p_new(monitor_t, 1);
|
|
||||||
monitor->geometry.x = screen_info[i].x_org;
|
|
||||||
monitor->geometry.y = screen_info[i].y_org;
|
|
||||||
monitor->geometry.width = screen_info[i].width;
|
|
||||||
monitor->geometry.height = screen_info[i].height;
|
|
||||||
|
|
||||||
if (prev_monitor) {
|
|
||||||
prev_monitor->next = monitor;
|
|
||||||
} else {
|
|
||||||
wm.monitor_list = monitor;
|
|
||||||
wm.current_monitor = monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_monitor = monitor;
|
|
||||||
}
|
|
||||||
p_delete(&screens_reply);
|
|
||||||
|
|
||||||
monitor_remove_duplication(&wm.monitor_list);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_detect_monitor(void) {
|
|
||||||
if (wm.have_randr && wm_detect_monitor_by_randr()) return;
|
|
||||||
if (wm.have_xinerama && wm_detect_monitor_by_xinerama()) return;
|
|
||||||
|
|
||||||
if (wm.screen == nullptr) fatal("cannot detect monitor info");
|
|
||||||
|
|
||||||
monitor_t *monitor = p_new(monitor_t, 1);
|
|
||||||
monitor->geometry.x = 0;
|
|
||||||
monitor->geometry.y = 0;
|
|
||||||
monitor->geometry.width = wm.screen->width_in_pixels;
|
|
||||||
monitor->geometry.height = wm.screen->height_in_pixels;
|
|
||||||
|
|
||||||
wm.monitor_list = monitor;
|
|
||||||
wm.current_monitor = monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct window_list_t {
|
|
||||||
xcb_window_t *normal_list;
|
|
||||||
xcb_window_t *transient_list;
|
|
||||||
uint32_t normal_count;
|
|
||||||
uint32_t transient_count;
|
|
||||||
} window_list_t;
|
|
||||||
void wm_scan_clients(void) {
|
|
||||||
xcb_query_tree_cookie_t cookie = xcb_query_tree(wm.xcb_conn, wm.screen->root);
|
|
||||||
xcb_query_tree_reply_t *tree_reply =
|
|
||||||
xcb_query_tree_reply(wm.xcb_conn, cookie, nullptr);
|
|
||||||
if (!tree_reply) return;
|
|
||||||
|
|
||||||
xcb_window_t *list = xcb_query_tree_children(tree_reply);
|
|
||||||
int len = xcb_query_tree_children_length(tree_reply);
|
|
||||||
if (!len) {
|
|
||||||
p_delete(&tree_reply);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_get_window_attributes_reply_t *wa_reply = nullptr;
|
|
||||||
xcb_get_geometry_reply_t *geo_reply = nullptr;
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
xcb_window_t window = list[i];
|
|
||||||
wa_reply = xwindow_get_attributes_reply(window);
|
|
||||||
if (!wa_reply) continue;
|
|
||||||
|
|
||||||
uint8_t override_redirect = wa_reply->override_redirect;
|
|
||||||
uint8_t map_state = wa_reply->map_state;
|
|
||||||
p_delete(&wa_reply);
|
|
||||||
|
|
||||||
if (override_redirect || xwindow_get_transient_for(window)) continue;
|
|
||||||
|
|
||||||
geo_reply = xwindow_get_geometry_reply(window);
|
|
||||||
if (!geo_reply) continue;
|
|
||||||
|
|
||||||
if (map_state == XCB_MAP_STATE_VIEWABLE ||
|
|
||||||
xwindow_get_state(window) == XCB_ICCCM_WM_STATE_ICONIC) {
|
|
||||||
client_manage(window, geo_reply);
|
|
||||||
}
|
|
||||||
p_delete(&geo_reply);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
xcb_window_t window = list[i];
|
|
||||||
wa_reply = xwindow_get_attributes_reply(window);
|
|
||||||
if (!wa_reply) continue;
|
|
||||||
|
|
||||||
uint8_t map_state = wa_reply->map_state;
|
|
||||||
p_delete(&wa_reply);
|
|
||||||
|
|
||||||
geo_reply = xwindow_get_geometry_reply(window);
|
|
||||||
if (!geo_reply) continue;
|
|
||||||
|
|
||||||
if (xwindow_get_transient_for(window) &&
|
|
||||||
(map_state == XCB_MAP_STATE_VIEWABLE ||
|
|
||||||
xwindow_get_state(window) == XCB_ICCCM_WM_STATE_ICONIC)) {
|
|
||||||
client_manage(window, geo_reply);
|
|
||||||
}
|
|
||||||
p_delete(&geo_reply);
|
|
||||||
}
|
|
||||||
p_delete(&tree_reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_clean(void) {
|
|
||||||
wallpaper_clean(wm.wallpaper);
|
|
||||||
wm.wallpaper = nullptr;
|
|
||||||
|
|
||||||
clean_status();
|
|
||||||
text_clean_pango_layout();
|
|
||||||
image_cache_clean();
|
|
||||||
tray_cleanup();
|
|
||||||
|
|
||||||
{
|
|
||||||
client_t *c = wm.client_stack_list;
|
|
||||||
while (c) {
|
|
||||||
client_t *next_client = c->stack_next;
|
|
||||||
client_unmanage(c, false);
|
|
||||||
c = next_client;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_clean(wm.monitor_list);
|
|
||||||
|
|
||||||
for (int i = 0; i < countof(sources); i++) {
|
|
||||||
guint source_id = sources[i];
|
|
||||||
if (source_id) g_source_remove(source_id);
|
|
||||||
}
|
|
||||||
if (wm.clear_ignore_enter_notify_source) {
|
|
||||||
g_source_remove(wm.clear_ignore_enter_notify_source);
|
|
||||||
wm.clear_ignore_enter_notify_source = 0;
|
|
||||||
}
|
|
||||||
wm.ignore_enter_notify = false;
|
|
||||||
wm.ignored_enter_notify_point = (point_t){0};
|
|
||||||
|
|
||||||
xcb_delete_property(wm.xcb_conn, wm.screen->root, _NET_ACTIVE_WINDOW);
|
|
||||||
xcb_delete_property(wm.xcb_conn, wm.screen->root, _NET_SUPPORTING_WM_CHECK);
|
|
||||||
xcb_aux_sync(wm.xcb_conn);
|
|
||||||
|
|
||||||
xcb_destroy_window(wm.xcb_conn, wm.wm_check_window);
|
|
||||||
wm.wm_check_window = XCB_WINDOW_NONE;
|
|
||||||
|
|
||||||
xcursor_clean();
|
|
||||||
xkb_free();
|
|
||||||
xcb_ungrab_key(wm.xcb_conn, XCB_GRAB_ANY, wm.screen->root, modifier_any);
|
|
||||||
xcb_key_symbols_free(wm.key_symbols);
|
|
||||||
xcb_aux_sync(wm.xcb_conn);
|
|
||||||
xcb_disconnect(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wm_setup(void) {
|
|
||||||
wm.config = init_config();
|
|
||||||
if (!wm.config) fatal("cannot init config");
|
|
||||||
|
|
||||||
wm.layout_list = layout_list;
|
|
||||||
wm.layout_count = (uint16_t)countof(layout_list);
|
|
||||||
|
|
||||||
wm.rules = rules;
|
|
||||||
wm.rules_count = countof(rules);
|
|
||||||
|
|
||||||
wm_get_xres_config();
|
|
||||||
wm_init_color_set();
|
|
||||||
|
|
||||||
{
|
|
||||||
int font_height =
|
|
||||||
text_init_pango_layout(wm.font_family, wm.font_size, wm.dpi);
|
|
||||||
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;
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
|
||||||
tag_count += monitor_initialize_tag(m, (const char **)tags, tag_count);
|
|
||||||
monitor_init_bar(m);
|
|
||||||
monitor_draw_bar(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t mask = XCB_CW_EVENT_MASK | XCB_CW_CURSOR | XCB_CW_BACK_PIXEL;
|
|
||||||
xcb_params_cw_t params = {
|
|
||||||
.back_pixel = wm.screen->black_pixel,
|
|
||||||
.cursor = xcursor_get_xcb_cursor(cursor_normal),
|
|
||||||
.event_mask =
|
|
||||||
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_KEY_PRESS,
|
|
||||||
};
|
|
||||||
xcb_aux_change_window_attributes(wm.xcb_conn, wm.screen->root, mask, ¶ms);
|
|
||||||
|
|
||||||
wm_setup_keybindings();
|
|
||||||
atoms_init(wm.xcb_conn);
|
|
||||||
tray_init();
|
|
||||||
|
|
||||||
{
|
|
||||||
wm.wm_check_window = xcb_generate_id(wm.xcb_conn);
|
|
||||||
xcb_create_window(wm.xcb_conn, wm.screen->root_depth, wm.wm_check_window,
|
|
||||||
wm.screen->root, -1, -1, 1, 1, 0, XCB_COPY_FROM_PARENT,
|
|
||||||
wm.screen->root_visual, XCB_NONE, nullptr);
|
|
||||||
xwindow_set_class_instance(wm.wm_check_window);
|
|
||||||
#define NAME APP_NAME "_wm_check"
|
|
||||||
xwindow_set_name_static(wm.wm_check_window, NAME);
|
|
||||||
const void *data = &wm.wm_check_window;
|
|
||||||
xcb_atom_t type = XCB_ATOM_WINDOW;
|
|
||||||
uint8_t mode = XCB_PROP_MODE_REPLACE;
|
|
||||||
xcb_change_property(wm.xcb_conn, mode, wm.wm_check_window, _NET_WM_NAME,
|
|
||||||
UTF8_STRING, 8, sizeof(NAME) - 1, NAME);
|
|
||||||
xcb_change_property(wm.xcb_conn, mode, wm.wm_check_window,
|
|
||||||
_NET_SUPPORTING_WM_CHECK, type, 32, 1, data);
|
|
||||||
xcb_change_property(wm.xcb_conn, mode, wm.screen->root,
|
|
||||||
_NET_SUPPORTING_WM_CHECK, type, 32, 1, data);
|
|
||||||
#undef NAME
|
|
||||||
}
|
|
||||||
|
|
||||||
xkb_init();
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wm_init_wallpaper(void) {
|
|
||||||
wallpaper_config_t config = {
|
|
||||||
.interval = wallpaper_interval,
|
|
||||||
.path_count = countof(wallpapers),
|
|
||||||
.paths = (char **)wallpapers,
|
|
||||||
.screen = wm.screen,
|
|
||||||
.conn = wm.xcb_conn,
|
|
||||||
.monitors = wm.monitor_list,
|
|
||||||
};
|
|
||||||
wm.wallpaper = wallpaper_init(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
color_parse(bar_bg, &wm.color_set.bar_bg);
|
|
||||||
color_parse(tag_bg, &wm.color_set.tag_bg);
|
|
||||||
color_parse(active_tag_bg, &wm.color_set.active_tag_bg);
|
|
||||||
color_parse(tag_color, &wm.color_set.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) {
|
|
||||||
if (wm.key_symbols) {
|
|
||||||
xcb_key_symbols_free(wm.key_symbols);
|
|
||||||
p_delete(&wm.key_symbols);
|
|
||||||
}
|
|
||||||
|
|
||||||
wm.key_symbols = xcb_key_symbols_alloc(wm.xcb_conn);
|
|
||||||
xwindow_grab_keys(wm.screen->root, key_list, countof(key_list));
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_update_status(status_t *status) {
|
|
||||||
wm.status = status;
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) monitor_draw_bar(m);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 允许自启动程序
|
|
||||||
* @param commands 命令字符串数组(以 nullptr 结尾)
|
|
||||||
*/
|
|
||||||
void wm_run_autostart(const char *const commands[]) {
|
|
||||||
for (int i = 0; commands[i]; i++) run_once(commands[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run_once(const char *command) {
|
|
||||||
if (command_already_running(command)) return;
|
|
||||||
g_spawn_command_line_async(command, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool command_already_running(const char *command) {
|
|
||||||
char shell_cmd[1024];
|
|
||||||
const char *user = getenv("USER");
|
|
||||||
snprintf(shell_cmd, sizeof(shell_cmd), "pgrep -u %s -fx '%s'", user, command);
|
|
||||||
|
|
||||||
char *output = nullptr;
|
|
||||||
char *error = nullptr;
|
|
||||||
g_spawn_command_line_sync(shell_cmd, &output, &error, nullptr, nullptr);
|
|
||||||
|
|
||||||
bool result =
|
|
||||||
(error == nullptr || !strlen(error)) && (output && strlen(output));
|
|
||||||
p_delete(&output);
|
|
||||||
p_delete(&error);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_restart(void) {
|
|
||||||
wm.need_restart = true;
|
|
||||||
if (g_main_loop_is_running(wm.loop)) {
|
|
||||||
g_main_loop_quit(wm.loop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_quit(void) {
|
|
||||||
wm.need_restart = false;
|
|
||||||
if (g_main_loop_is_running(wm.loop)) {
|
|
||||||
g_main_loop_quit(wm.loop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_restack_clients(void) {
|
|
||||||
xcb_window_t sibling_window = XCB_WINDOW_NONE;
|
|
||||||
for (client_t *c = wm.client_stack_list; c; c = c->stack_next) {
|
|
||||||
uint16_t mask = XCB_CONFIG_WINDOW_STACK_MODE;
|
|
||||||
xcb_params_configure_window_t params = {};
|
|
||||||
if (sibling_window == XCB_WINDOW_NONE) {
|
|
||||||
mask = XCB_CONFIG_WINDOW_STACK_MODE;
|
|
||||||
params.stack_mode = XCB_STACK_MODE_ABOVE;
|
|
||||||
} else {
|
|
||||||
mask = XCB_CONFIG_WINDOW_STACK_MODE | XCB_CONFIG_WINDOW_SIBLING;
|
|
||||||
params.stack_mode = XCB_STACK_MODE_BELOW;
|
|
||||||
params.sibling = sibling_window;
|
|
||||||
}
|
|
||||||
sibling_window = c->window;
|
|
||||||
xcb_aux_configure_window(wm.xcb_conn, c->window, mask, ¶ms);
|
|
||||||
}
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wm_set_current_monitor(monitor_t *monitor, bool restore_cursor) {
|
|
||||||
if (!monitor || wm.current_monitor == monitor) return;
|
|
||||||
|
|
||||||
point_t point = xcursor_query_pointer_position();
|
|
||||||
monitor_t *point_monitor = wm_get_monitor_by_point(point);
|
|
||||||
if (point_monitor == wm.current_monitor) {
|
|
||||||
point_monitor->cursor_position = point;
|
|
||||||
point_monitor->position_inited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
wm.current_monitor = monitor;
|
|
||||||
if (restore_cursor) monitor_restore_cursor_point(monitor);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int intersect(area_t area, monitor_t *m) {
|
|
||||||
return MAX(0, MIN(area.x + area.width, m->geometry.x + m->geometry.width) -
|
|
||||||
MAX(area.x, m->geometry.x)) *
|
|
||||||
MAX(0, MIN(area.y + area.height, m->geometry.y + m->geometry.height) -
|
|
||||||
MAX(area.y, m->geometry.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_t *__attribute__((returns_nonnull)) wm_get_monitor_by_area(
|
|
||||||
area_t area) {
|
|
||||||
if (wm.current_monitor == nullptr) {
|
|
||||||
fatal("wm_get_monitor_by_area: current monitor is nullptr");
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_t *monitor = wm.current_monitor;
|
|
||||||
int temp_area, max_area = 0;
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
|
||||||
if ((temp_area = intersect(area, m)) > max_area) {
|
|
||||||
max_area = temp_area;
|
|
||||||
monitor = m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_t *__attribute__((returns_nonnull)) wm_get_monitor_by_point(
|
|
||||||
point_t point) {
|
|
||||||
area_t area = {.x = point.x, .y = point.y, .width = 1, .height = 1};
|
|
||||||
return wm_get_monitor_by_area(area);
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_t *wm_get_monitor_by_window(xcb_window_t window) {
|
|
||||||
if (window == wm.screen->root) {
|
|
||||||
xcb_query_pointer_cookie_t cookie = xcb_query_pointer(wm.xcb_conn, window);
|
|
||||||
xcb_query_pointer_reply_t *reply =
|
|
||||||
xcb_query_pointer_reply(wm.xcb_conn, cookie, nullptr);
|
|
||||||
if (reply) {
|
|
||||||
area_t area = {reply->root_x, reply->root_y, 1, 1};
|
|
||||||
p_delete(&reply);
|
|
||||||
return wm_get_monitor_by_area(area);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
|
||||||
if (window == m->bar_window) return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_t *c = client_get_by_window(window);
|
|
||||||
if (c) return c->monitor;
|
|
||||||
|
|
||||||
return wm.current_monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor_t *wm_get_next_monitor(monitor_t *monitor) {
|
|
||||||
monitor_t *next_monitor = monitor->next;
|
|
||||||
if (next_monitor == nullptr) next_monitor = wm.monitor_list;
|
|
||||||
return next_monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void debug_show_monitor_list(void) {
|
|
||||||
logger("\n========================== monitors ==========================\n");
|
|
||||||
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
|
|
||||||
logger("x: %4d, y: %4d, width: %4u, height: %4u -> monitor[%s]\n",
|
|
||||||
m->geometry.x, m->geometry.y, m->geometry.width, m->geometry.height,
|
|
||||||
m->name);
|
|
||||||
logger("x: %4d, y: %4d, width: %4u, height: %4u -> workarea[%s]: %u\n",
|
|
||||||
m->workarea.x, m->workarea.y, m->workarea.width, m->workarea.height,
|
|
||||||
m->name, wm.bar_height);
|
|
||||||
logger("tag extend: [%d, %d]\n", m->tag_extent.start, m->tag_extent.end);
|
|
||||||
for (const tag_t *tag = m->tag_list; tag; tag = tag->next) {
|
|
||||||
logger("tag[%u]: %4u, \"%s\", [%d, %d]\n", tag->index, tag->mask,
|
|
||||||
tag->name, tag->bar_extent.start, tag->bar_extent.end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
p_clear(&wm, 1);
|
|
||||||
cmd_argv = argv;
|
|
||||||
wm_setup_signal();
|
|
||||||
|
|
||||||
wm_check_other_wm();
|
|
||||||
wm_check_xcb_extensions();
|
|
||||||
|
|
||||||
wm_detect_monitor();
|
|
||||||
wm_setup();
|
|
||||||
wm_init_wallpaper();
|
|
||||||
setup_event_loop();
|
|
||||||
wm_scan_clients();
|
|
||||||
|
|
||||||
debug_show_monitor_list();
|
|
||||||
|
|
||||||
wm_run_autostart(autostart_list);
|
|
||||||
|
|
||||||
if (wm.loop == nullptr) {
|
|
||||||
wm.loop = g_main_loop_new(nullptr, FALSE);
|
|
||||||
init_status(g_main_loop_get_context(wm.loop), wm_update_status);
|
|
||||||
g_main_loop_run(wm.loop);
|
|
||||||
}
|
|
||||||
g_main_loop_unref(wm.loop);
|
|
||||||
wm.loop = nullptr;
|
|
||||||
|
|
||||||
wm_clean();
|
|
||||||
|
|
||||||
if (wm.need_restart) {
|
|
||||||
execvp(cmd_argv[0], cmd_argv);
|
|
||||||
fatal("execv() failed: %s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
76
src/wm.h
76
src/wm.h
@@ -1,76 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <xcb/xcb_keysyms.h>
|
|
||||||
#include <xcb/xcb_xrm.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
#include "status.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "wallpaper.h"
|
|
||||||
|
|
||||||
typedef struct config_t config_t;
|
|
||||||
|
|
||||||
typedef struct wm_t {
|
|
||||||
padding_t padding;
|
|
||||||
uint16_t border_width;
|
|
||||||
uint16_t bar_height;
|
|
||||||
uint16_t layout_count;
|
|
||||||
|
|
||||||
bool need_restart;
|
|
||||||
GMainLoop *loop;
|
|
||||||
status_t *status;
|
|
||||||
const config_t *config;
|
|
||||||
|
|
||||||
client_t *client_list;
|
|
||||||
client_t *client_stack_list;
|
|
||||||
client_t *client_focused;
|
|
||||||
monitor_t *monitor_list;
|
|
||||||
monitor_t *current_monitor;
|
|
||||||
bool ignore_enter_notify;
|
|
||||||
point_t ignored_enter_notify_point;
|
|
||||||
guint clear_ignore_enter_notify_source;
|
|
||||||
const layout_t *layout_list;
|
|
||||||
|
|
||||||
char *font_family;
|
|
||||||
uint32_t font_size;
|
|
||||||
uint32_t dpi;
|
|
||||||
const rule_t *rules;
|
|
||||||
uint32_t rules_count;
|
|
||||||
|
|
||||||
xcb_connection_t *xcb_conn;
|
|
||||||
xcb_screen_t *screen;
|
|
||||||
xcb_xrm_database_t *xrm;
|
|
||||||
xcb_key_symbols_t *key_symbols;
|
|
||||||
xcb_window_t wm_check_window;
|
|
||||||
int default_screen;
|
|
||||||
bool have_xfixes;
|
|
||||||
bool have_xinerama;
|
|
||||||
bool have_randr;
|
|
||||||
bool have_xkb;
|
|
||||||
|
|
||||||
uint8_t event_base_xkb;
|
|
||||||
bool xkb_reload_keymap;
|
|
||||||
bool xkb_update_pending;
|
|
||||||
struct xkb_context *xkb_ctx;
|
|
||||||
struct xkb_state *xkb_state;
|
|
||||||
|
|
||||||
wallpaper_context_t *wallpaper;
|
|
||||||
|
|
||||||
color_set_t color_set;
|
|
||||||
} wm_t;
|
|
||||||
|
|
||||||
extern wm_t wm;
|
|
||||||
|
|
||||||
void wm_restart(void);
|
|
||||||
void wm_quit(void);
|
|
||||||
void wm_restack_clients(void);
|
|
||||||
void wm_ignore_enter_notify_at_point(point_t point);
|
|
||||||
bool wm_should_ignore_enter_notify(const xcb_enter_notify_event_t *ev);
|
|
||||||
void wm_set_current_monitor(monitor_t *monitor, bool restore_cursor);
|
|
||||||
monitor_t *__attribute__((returns_nonnull)) wm_get_monitor_by_area(area_t area);
|
|
||||||
monitor_t *__attribute__((returns_nonnull)) wm_get_monitor_by_point(
|
|
||||||
point_t point);
|
|
||||||
monitor_t *wm_get_monitor_by_window(xcb_window_t window);
|
|
||||||
monitor_t *wm_get_next_monitor(monitor_t *monitor);
|
|
||||||
148
src/xcursor.c
148
src/xcursor.c
@@ -1,148 +0,0 @@
|
|||||||
#include "xcursor.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_cursor.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wm.h"
|
|
||||||
|
|
||||||
static const char *const xcursor_font[] = {
|
|
||||||
[XC_X_cursor] = "X_cursor",
|
|
||||||
[XC_arrow] = "arrow",
|
|
||||||
[XC_based_arrow_down] = "based_arrow_down",
|
|
||||||
[XC_based_arrow_up] = "based_arrow_up",
|
|
||||||
[XC_boat] = "boat",
|
|
||||||
[XC_bogosity] = "bogosity",
|
|
||||||
[XC_bottom_left_corner] = "bottom_left_corner",
|
|
||||||
[XC_bottom_right_corner] = "bottom_right_corner",
|
|
||||||
[XC_bottom_side] = "bottom_side",
|
|
||||||
[XC_bottom_tee] = "bottom_tee",
|
|
||||||
[XC_box_spiral] = "box_spiral",
|
|
||||||
[XC_center_ptr] = "center_ptr",
|
|
||||||
[XC_circle] = "circle",
|
|
||||||
[XC_clock] = "clock",
|
|
||||||
[XC_coffee_mug] = "coffee_mug",
|
|
||||||
[XC_cross] = "cross",
|
|
||||||
[XC_cross_reverse] = "cross_reverse",
|
|
||||||
[XC_crosshair] = "crosshair",
|
|
||||||
[XC_diamond_cross] = "diamond_cross",
|
|
||||||
[XC_dot] = "dot",
|
|
||||||
[XC_dotbox] = "dotbox",
|
|
||||||
[XC_double_arrow] = "double_arrow",
|
|
||||||
[XC_draft_large] = "draft_large",
|
|
||||||
[XC_draft_small] = "draft_small",
|
|
||||||
[XC_draped_box] = "draped_box",
|
|
||||||
[XC_exchange] = "exchange",
|
|
||||||
[XC_fleur] = "fleur",
|
|
||||||
[XC_gobbler] = "gobbler",
|
|
||||||
[XC_gumby] = "gumby",
|
|
||||||
[XC_hand1] = "hand1",
|
|
||||||
[XC_hand2] = "hand2",
|
|
||||||
[XC_heart] = "heart",
|
|
||||||
[XC_icon] = "icon",
|
|
||||||
[XC_iron_cross] = "iron_cross",
|
|
||||||
[XC_left_ptr] = "left_ptr",
|
|
||||||
[XC_left_side] = "left_side",
|
|
||||||
[XC_left_tee] = "left_tee",
|
|
||||||
[XC_leftbutton] = "leftbutton",
|
|
||||||
[XC_ll_angle] = "ll_angle",
|
|
||||||
[XC_lr_angle] = "lr_angle",
|
|
||||||
[XC_man] = "man",
|
|
||||||
[XC_middlebutton] = "middlebutton",
|
|
||||||
[XC_mouse] = "mouse",
|
|
||||||
[XC_pencil] = "pencil",
|
|
||||||
[XC_pirate] = "pirate",
|
|
||||||
[XC_plus] = "plus",
|
|
||||||
[XC_question_arrow] = "question_arrow",
|
|
||||||
[XC_right_ptr] = "right_ptr",
|
|
||||||
[XC_right_side] = "right_side",
|
|
||||||
[XC_right_tee] = "right_tee",
|
|
||||||
[XC_rightbutton] = "rightbutton",
|
|
||||||
[XC_rtl_logo] = "rtl_logo",
|
|
||||||
[XC_sailboat] = "sailboat",
|
|
||||||
[XC_sb_down_arrow] = "sb_down_arrow",
|
|
||||||
[XC_sb_h_double_arrow] = "sb_h_double_arrow",
|
|
||||||
[XC_sb_left_arrow] = "sb_left_arrow",
|
|
||||||
[XC_sb_right_arrow] = "sb_right_arrow",
|
|
||||||
[XC_sb_up_arrow] = "sb_up_arrow",
|
|
||||||
[XC_sb_v_double_arrow] = "sb_v_double_arrow",
|
|
||||||
[XC_shuttle] = "shuttle",
|
|
||||||
[XC_sizing] = "sizing",
|
|
||||||
[XC_spider] = "spider",
|
|
||||||
[XC_spraycan] = "spraycan",
|
|
||||||
[XC_star] = "star",
|
|
||||||
[XC_target] = "target",
|
|
||||||
[XC_tcross] = "tcross",
|
|
||||||
[XC_top_left_arrow] = "top_left_arrow",
|
|
||||||
[XC_top_left_corner] = "top_left_corner",
|
|
||||||
[XC_top_right_corner] = "top_right_corner",
|
|
||||||
[XC_top_side] = "top_side",
|
|
||||||
[XC_top_tee] = "top_tee",
|
|
||||||
[XC_trek] = "trek",
|
|
||||||
[XC_ul_angle] = "ul_angle",
|
|
||||||
[XC_umbrella] = "umbrella",
|
|
||||||
[XC_ur_angle] = "ur_angle",
|
|
||||||
[XC_watch] = "watch",
|
|
||||||
[XC_xterm] = "xterm",
|
|
||||||
};
|
|
||||||
|
|
||||||
static xcb_cursor_context_t *get_xcb_cursor_context(void) {
|
|
||||||
static xcb_cursor_context_t *ctx = nullptr;
|
|
||||||
if (ctx) return ctx;
|
|
||||||
|
|
||||||
if (xcb_cursor_context_new(wm.xcb_conn, wm.screen, &ctx) < 0) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *xcursor_font_tostr(uint16_t c) {
|
|
||||||
if (c < (uint16_t)countof(xcursor_font)) {
|
|
||||||
return xcursor_font[c];
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static xcb_cursor_t cursor_list[countof(xcursor_font)];
|
|
||||||
static xcb_cursor_context_t *cursor_ctx = nullptr;
|
|
||||||
|
|
||||||
xcb_cursor_t xcursor_get_xcb_cursor(cursor_t cursor) {
|
|
||||||
if (!cursor_ctx) cursor_ctx = get_xcb_cursor_context();
|
|
||||||
if (!cursor_ctx) fatal("cannot get cursor");
|
|
||||||
|
|
||||||
if (!cursor_list[cursor]) {
|
|
||||||
const char *name = xcursor_font_tostr(cursor);
|
|
||||||
cursor_list[cursor] = xcb_cursor_load_cursor(cursor_ctx, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cursor_list[cursor];
|
|
||||||
}
|
|
||||||
|
|
||||||
void xcursor_clean(void) {
|
|
||||||
for (int i = 0; i < countof(xcursor_font); i++) {
|
|
||||||
xcb_cursor_t cursor = cursor_list[i];
|
|
||||||
if (cursor) xcb_free_cursor(wm.xcb_conn, cursor);
|
|
||||||
}
|
|
||||||
if (cursor_ctx) xcb_cursor_context_free(cursor_ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
point_t xcursor_query_pointer_position(void) {
|
|
||||||
xcb_query_pointer_cookie_t cookie =
|
|
||||||
xcb_query_pointer(wm.xcb_conn, wm.screen->root);
|
|
||||||
xcb_query_pointer_reply_t *reply =
|
|
||||||
xcb_query_pointer_reply(wm.xcb_conn, cookie, nullptr);
|
|
||||||
point_t pos = {.x = reply->root_x, .y = reply->root_y};
|
|
||||||
p_delete(&reply);
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
void xcursor_set_pointer_position(point_t point) {
|
|
||||||
xcb_window_t window = XCB_WINDOW_NONE;
|
|
||||||
xcb_window_t root = wm.screen->root;
|
|
||||||
xcb_warp_pointer(wm.xcb_conn, window, root, 0, 0, 1, 1, point.x, point.y);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <X11/cursorfont.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
|
|
||||||
typedef enum cursor_t {
|
|
||||||
cursor_normal = XC_left_ptr,
|
|
||||||
cursor_resize = XC_bottom_right_corner,
|
|
||||||
cursor_move = XC_fleur,
|
|
||||||
} cursor_t;
|
|
||||||
|
|
||||||
xcb_cursor_t xcursor_get_xcb_cursor(cursor_t cursor);
|
|
||||||
void xcursor_clean(void);
|
|
||||||
point_t xcursor_query_pointer_position(void);
|
|
||||||
void xcursor_set_pointer_position(point_t point);
|
|
||||||
216
src/xkb.c
216
src/xkb.c
@@ -1,216 +0,0 @@
|
|||||||
#include "xkb.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <glib.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_keysyms.h>
|
|
||||||
#include <xcb/xkb.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
#include <xkbcommon/xkbcommon-x11.h>
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
|
||||||
|
|
||||||
#include "atoms-extern.h"
|
|
||||||
#include "default_config.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wm.h"
|
|
||||||
#include "xwindow.h"
|
|
||||||
|
|
||||||
static bool xkb_fill_rule_names_from_root(struct xkb_rule_names *xkb_names) {
|
|
||||||
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(
|
|
||||||
wm.xcb_conn, false, wm.screen->root, _XKB_RULES_NAMES,
|
|
||||||
XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX);
|
|
||||||
xcb_get_property_reply_t *reply =
|
|
||||||
xcb_get_property_reply(wm.xcb_conn, cookie, nullptr);
|
|
||||||
if (!reply) return false;
|
|
||||||
|
|
||||||
if (reply->value_len == 0) {
|
|
||||||
p_delete(&reply);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *walk = xcb_get_property_value(reply);
|
|
||||||
size_t remaining = xcb_get_property_value_length(reply);
|
|
||||||
for (int i = 0; i < 5 && remaining > 0; i++) {
|
|
||||||
size_t len = strnlen(walk, remaining);
|
|
||||||
switch (i) {
|
|
||||||
case 0:
|
|
||||||
xkb_names->rules = strndup(walk, len);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
xkb_names->model = strndup(walk, len);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
xkb_names->layout = strndup(walk, len);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
xkb_names->variant = strndup(walk, len);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
xkb_names->options = strndup(walk, len);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
remaining -= len + 1;
|
|
||||||
walk = &walk[len + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
p_delete(&reply);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void xkb_fill_state(void) {
|
|
||||||
int32_t device_id = -1;
|
|
||||||
if (wm.have_xkb) {
|
|
||||||
device_id = xkb_x11_get_core_keyboard_device_id(wm.xcb_conn);
|
|
||||||
if (device_id == -1) warn("Failed get XKB device id");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (device_id == -1) {
|
|
||||||
struct xkb_rule_names names = {nullptr, nullptr, nullptr, nullptr, nullptr};
|
|
||||||
if (!xkb_fill_rule_names_from_root(&names)) {
|
|
||||||
warn(
|
|
||||||
"Could not get _XKB_RULES_NAMES from root window, falling back to "
|
|
||||||
"defaults.");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names(
|
|
||||||
wm.xkb_ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
||||||
wm.xkb_state = xkb_state_new(xkb_keymap);
|
|
||||||
|
|
||||||
if (!wm.xkb_state) fatal("Failed create XKB state");
|
|
||||||
|
|
||||||
xkb_keymap_unref(xkb_keymap);
|
|
||||||
p_delete(&names.rules);
|
|
||||||
p_delete(&names.model);
|
|
||||||
p_delete(&names.layout);
|
|
||||||
p_delete(&names.variant);
|
|
||||||
p_delete(&names.options);
|
|
||||||
} else {
|
|
||||||
struct xkb_keymap *xkb_keymap = xkb_x11_keymap_new_from_device(
|
|
||||||
wm.xkb_ctx, wm.xcb_conn, device_id, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
||||||
if (!xkb_keymap) fatal("Failed get XKB keymap from device");
|
|
||||||
wm.xkb_state =
|
|
||||||
xkb_x11_state_new_from_device(xkb_keymap, wm.xcb_conn, device_id);
|
|
||||||
if (!wm.xkb_state) fatal("Failed get XKB state from device");
|
|
||||||
xkb_keymap_unref(xkb_keymap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void xkb_init_keymap(void) {
|
|
||||||
wm.xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
||||||
if (!wm.xkb_ctx) fatal("Cannot get XKB context");
|
|
||||||
|
|
||||||
xkb_fill_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
#define DEVICE_SPEC XCB_XKB_ID_USE_CORE_KBD
|
|
||||||
|
|
||||||
void xkb_init(void) {
|
|
||||||
wm.xkb_update_pending = false;
|
|
||||||
wm.xkb_reload_keymap = false;
|
|
||||||
|
|
||||||
wm.have_xkb = xkb_x11_setup_xkb_extension(
|
|
||||||
wm.xcb_conn, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
|
|
||||||
XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, nullptr, nullptr, &wm.event_base_xkb,
|
|
||||||
nullptr);
|
|
||||||
logger("xkb base event: %u\n", wm.event_base_xkb);
|
|
||||||
if (!wm.have_xkb) {
|
|
||||||
warn("XKB not found or not supported");
|
|
||||||
xkb_init_keymap();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_xkb_per_client_flags_cookie_t cookie = xcb_xkb_per_client_flags(
|
|
||||||
wm.xcb_conn, DEVICE_SPEC, XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT,
|
|
||||||
XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, 0, 0, 0);
|
|
||||||
xcb_discard_reply(wm.xcb_conn, cookie.sequence);
|
|
||||||
uint16_t xkb_event = XCB_XKB_EVENT_TYPE_STATE_NOTIFY |
|
|
||||||
XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
|
|
||||||
XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY;
|
|
||||||
uint16_t map_parts =
|
|
||||||
XCB_XKB_MAP_PART_KEY_TYPES | XCB_XKB_MAP_PART_KEY_SYMS |
|
|
||||||
XCB_XKB_MAP_PART_MODIFIER_MAP | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
|
|
||||||
XCB_XKB_MAP_PART_KEY_ACTIONS | XCB_XKB_MAP_PART_KEY_BEHAVIORS |
|
|
||||||
XCB_XKB_MAP_PART_VIRTUAL_MODS | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP;
|
|
||||||
xcb_xkb_select_events(wm.xcb_conn, DEVICE_SPEC, xkb_event, 0, xkb_event,
|
|
||||||
map_parts, map_parts, nullptr);
|
|
||||||
|
|
||||||
xkb_init_keymap();
|
|
||||||
}
|
|
||||||
|
|
||||||
void xkb_free(void) {
|
|
||||||
if (wm.have_xkb) {
|
|
||||||
xcb_xkb_select_events(wm.xcb_conn, DEVICE_SPEC, 0, 0, 0, 0, 0, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
xkb_state_unref(wm.xkb_state);
|
|
||||||
xkb_context_unref(wm.xkb_ctx);
|
|
||||||
wm.xkb_state = nullptr;
|
|
||||||
wm.xkb_ctx = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void xkb_reload_keymap(void) {
|
|
||||||
assert(wm.have_xkb);
|
|
||||||
|
|
||||||
xcb_key_symbols_free(wm.key_symbols);
|
|
||||||
wm.key_symbols = xcb_key_symbols_alloc(wm.xcb_conn);
|
|
||||||
xwindow_grab_keys(wm.screen->root, key_list, countof(key_list));
|
|
||||||
|
|
||||||
xkb_state_unref(wm.xkb_state);
|
|
||||||
xkb_fill_state();
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean xkb_refresh(gpointer ignore) {
|
|
||||||
wm.xkb_update_pending = false;
|
|
||||||
if (wm.xkb_reload_keymap) xkb_reload_keymap();
|
|
||||||
|
|
||||||
wm.xkb_reload_keymap = false;
|
|
||||||
|
|
||||||
return G_SOURCE_REMOVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void xkb_schedule_refresh(void) {
|
|
||||||
if (wm.xkb_update_pending) return;
|
|
||||||
|
|
||||||
wm.xkb_update_pending = true;
|
|
||||||
g_idle_add_full(G_PRIORITY_LOW, xkb_refresh, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void xkb_handle_event(xcb_generic_event_t *event) {
|
|
||||||
assert(wm.have_xkb);
|
|
||||||
|
|
||||||
logger("xkb event type: %u\n", event->pad0);
|
|
||||||
|
|
||||||
/* XKB 中没有对应所有事件的泛型类型,xcb_generic_event_t 类型中的 pad0
|
|
||||||
* 字段对应 XKB 事件中的 xkbType 字段,故用该字段判断 XKB 事件类型 */
|
|
||||||
switch (event->pad0) {
|
|
||||||
case XCB_XKB_NEW_KEYBOARD_NOTIFY: {
|
|
||||||
wm.xkb_reload_keymap = true;
|
|
||||||
xkb_schedule_refresh();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case XCB_XKB_MAP_NOTIFY: {
|
|
||||||
wm.xkb_reload_keymap = true;
|
|
||||||
xkb_schedule_refresh();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case XCB_XKB_STATE_NOTIFY: {
|
|
||||||
xcb_xkb_state_notify_event_t *state_notify_event = (void *)event;
|
|
||||||
xkb_state_update_mask(
|
|
||||||
wm.xkb_state, state_notify_event->baseMods,
|
|
||||||
state_notify_event->latchedMods, state_notify_event->lockedMods,
|
|
||||||
state_notify_event->baseGroup, state_notify_event->latchedGroup,
|
|
||||||
state_notify_event->lockedGroup);
|
|
||||||
|
|
||||||
logger("changed: %u\n",
|
|
||||||
state_notify_event->changed & XCB_XKB_STATE_PART_GROUP_STATE);
|
|
||||||
if (state_notify_event->changed & XCB_XKB_STATE_PART_GROUP_STATE) {
|
|
||||||
xkb_schedule_refresh();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
|
|
||||||
void xkb_init(void);
|
|
||||||
void xkb_free(void);
|
|
||||||
void xkb_handle_event(xcb_generic_event_t *event);
|
|
||||||
52
src/xres.c
52
src/xres.c
@@ -1,52 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#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);
|
|
||||||
288
src/xwindow.c
288
src/xwindow.c
@@ -1,288 +0,0 @@
|
|||||||
#include "xwindow.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_aux.h>
|
|
||||||
#include <xcb/xcb_icccm.h>
|
|
||||||
#include <xcb/xcb_keysyms.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "action.h"
|
|
||||||
#include "atoms-extern.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "wm.h"
|
|
||||||
#include "xcursor.h"
|
|
||||||
|
|
||||||
xcb_window_t xwindow_create_bar_window(area_t area, uint32_t background_argb,
|
|
||||||
cursor_t cursor) {
|
|
||||||
static xcb_colormap_t colormap = XCB_NONE;
|
|
||||||
visual_t *visual = xwindow_get_xcb_visual(true);
|
|
||||||
xcb_visualid_t visual_id = visual->visual->visual_id;
|
|
||||||
xcb_connection_t *conn = wm.xcb_conn;
|
|
||||||
if (colormap == XCB_NONE) {
|
|
||||||
colormap = xcb_generate_id(conn);
|
|
||||||
uint8_t alloc = XCB_COLORMAP_ALLOC_NONE;
|
|
||||||
xcb_create_colormap(conn, alloc, colormap, wm.screen->root, visual_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_window_t window = xcb_generate_id(conn);
|
|
||||||
uint32_t value_mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_BACK_PIXEL |
|
|
||||||
XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK |
|
|
||||||
XCB_CW_CURSOR | XCB_CW_COLORMAP;
|
|
||||||
const xcb_create_window_value_list_t value_list = {
|
|
||||||
.override_redirect = true,
|
|
||||||
.background_pixel = background_argb,
|
|
||||||
.border_pixel = 0,
|
|
||||||
.event_mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_EXPOSURE,
|
|
||||||
.cursor = xcursor_get_xcb_cursor(cursor),
|
|
||||||
.colormap = colormap,
|
|
||||||
};
|
|
||||||
xcb_void_cookie_t cookie;
|
|
||||||
cookie = xcb_create_window_aux_checked(
|
|
||||||
conn, visual->depth, window, wm.screen->root, area.x, area.y, area.width,
|
|
||||||
area.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, value_mask,
|
|
||||||
&value_list);
|
|
||||||
p_delete(&visual);
|
|
||||||
if (xcb_request_check(conn, cookie)) fatal("cannot create bar window");
|
|
||||||
|
|
||||||
cookie = xcb_map_window(conn, window);
|
|
||||||
if (xcb_request_check(conn, cookie)) fatal("cannot map bar window:");
|
|
||||||
|
|
||||||
xcb_aux_sync(conn);
|
|
||||||
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
static visual_t *find_alpha_visual() {
|
|
||||||
xcb_depth_iterator_t depth_iter;
|
|
||||||
depth_iter = xcb_screen_allowed_depths_iterator(wm.screen);
|
|
||||||
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
|
|
||||||
if (depth_iter.data->depth != 32) continue;
|
|
||||||
|
|
||||||
xcb_visualtype_iterator_t visual_iter;
|
|
||||||
visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
|
|
||||||
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
|
|
||||||
if (visual_iter.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR) {
|
|
||||||
visual_t *r = p_new(visual_t, 1);
|
|
||||||
r->visual = visual_iter.data;
|
|
||||||
r->depth = depth_iter.data->depth;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static visual_t *find_root_visual() {
|
|
||||||
xcb_depth_iterator_t depth_iter;
|
|
||||||
depth_iter = xcb_screen_allowed_depths_iterator(wm.screen);
|
|
||||||
xcb_visualid_t vid = wm.screen->root_visual;
|
|
||||||
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
|
|
||||||
xcb_visualtype_iterator_t visual_iter;
|
|
||||||
visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
|
|
||||||
for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
|
|
||||||
if (visual_iter.data->visual_id == vid) {
|
|
||||||
visual_t *r = p_new(visual_t, 1);
|
|
||||||
r->visual = visual_iter.data;
|
|
||||||
r->depth = depth_iter.data->depth;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 xcb 的 visual 及其对应的 depth
|
|
||||||
* @param prefer_alpha 支持 alpha 的 visual 优先
|
|
||||||
* @returns 返回的信息使用后记得使用 free 释放
|
|
||||||
*/
|
|
||||||
visual_t *xwindow_get_xcb_visual(bool prefer_alpha) {
|
|
||||||
if (!prefer_alpha) return find_root_visual();
|
|
||||||
|
|
||||||
visual_t *visual = find_alpha_visual();
|
|
||||||
if (visual == nullptr) visual = find_root_visual();
|
|
||||||
|
|
||||||
return visual;
|
|
||||||
}
|
|
||||||
|
|
||||||
void xwindow_change_cursor(xcb_window_t window, cursor_t cursor) {
|
|
||||||
xcb_params_cw_t params = {.cursor = xcursor_get_xcb_cursor(cursor)};
|
|
||||||
xcb_aux_change_window_attributes(wm.xcb_conn, window, XCB_CW_CURSOR, ¶ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
void xwindow_grab_keys(xcb_window_t window, const keyboard_t *keys,
|
|
||||||
int keys_length) {
|
|
||||||
xcb_connection_t *conn = wm.xcb_conn;
|
|
||||||
xcb_ungrab_key(conn, XCB_GRAB_ANY, window, modifier_any);
|
|
||||||
|
|
||||||
xcb_grab_mode_t mode = XCB_GRAB_MODE_ASYNC;
|
|
||||||
for (int i = 0; i < keys_length; i++) {
|
|
||||||
xcb_keycode_t *keycodes =
|
|
||||||
xcb_key_symbols_get_keycode(wm.key_symbols, keys[i].keysym);
|
|
||||||
if (keycodes) {
|
|
||||||
for (xcb_keycode_t *kc = keycodes; *kc != XCB_NO_SYMBOL; kc++) {
|
|
||||||
xcb_grab_key(conn, true, window, keys[i].modifiers, *kc, mode, mode);
|
|
||||||
}
|
|
||||||
p_delete(&keycodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool xwindow_send_event(xcb_window_t window, xcb_atom_t atom) {
|
|
||||||
bool exist = false;
|
|
||||||
|
|
||||||
xcb_get_property_cookie_t cookie =
|
|
||||||
xcb_icccm_get_wm_protocols(wm.xcb_conn, window, WM_PROTOCOLS);
|
|
||||||
xcb_icccm_get_wm_protocols_reply_t reply;
|
|
||||||
if (xcb_icccm_get_wm_protocols_reply(wm.xcb_conn, cookie, &reply, nullptr)) {
|
|
||||||
for (uint32_t i = 0; !exist && i < reply.atoms_len; i++) {
|
|
||||||
exist = reply.atoms[i] == atom;
|
|
||||||
}
|
|
||||||
xcb_icccm_get_wm_protocols_reply_wipe(&reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exist) {
|
|
||||||
xcb_client_message_event_t ev;
|
|
||||||
p_clear(&ev, 1);
|
|
||||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
|
||||||
ev.format = 32;
|
|
||||||
ev.window = window;
|
|
||||||
ev.type = WM_PROTOCOLS;
|
|
||||||
ev.data.data32[0] = atom;
|
|
||||||
ev.data.data32[1] = XCB_CURRENT_TIME;
|
|
||||||
|
|
||||||
xcb_connection_t *conn = wm.xcb_conn;
|
|
||||||
xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
return exist;
|
|
||||||
}
|
|
||||||
|
|
||||||
void xwindow_focus(xcb_window_t window) {
|
|
||||||
if (window == XCB_WINDOW_NONE || window == wm.screen->root) {
|
|
||||||
xcb_set_input_focus(wm.xcb_conn, XCB_INPUT_FOCUS_POINTER_ROOT,
|
|
||||||
wm.screen->root, XCB_CURRENT_TIME);
|
|
||||||
xcb_delete_property(wm.xcb_conn, wm.screen->root, _NET_ACTIVE_WINDOW);
|
|
||||||
} else {
|
|
||||||
xcb_set_input_focus(wm.xcb_conn, XCB_INPUT_FOCUS_POINTER_ROOT, window,
|
|
||||||
XCB_CURRENT_TIME);
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, wm.screen->root,
|
|
||||||
_NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1, &window);
|
|
||||||
xwindow_send_event(window, WM_TAKE_FOCUS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取目标窗口的文本属性
|
|
||||||
* @param window 目标窗口
|
|
||||||
* @param property 属性名
|
|
||||||
* @param out 返回的信息使用后记得使用 free 释放
|
|
||||||
*/
|
|
||||||
void xwindow_get_text_property(xcb_window_t window, xcb_atom_t property,
|
|
||||||
char **out) {
|
|
||||||
if (out == nullptr) return;
|
|
||||||
|
|
||||||
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(
|
|
||||||
wm.xcb_conn, false, window, property, XCB_ATOM_ANY, 0, UINT32_MAX);
|
|
||||||
xcb_get_property_reply_t *reply =
|
|
||||||
xcb_get_property_reply(wm.xcb_conn, cookie, nullptr);
|
|
||||||
if (!reply) return;
|
|
||||||
|
|
||||||
int length = xcb_get_property_value_length(reply);
|
|
||||||
char *value = xcb_get_property_value(reply);
|
|
||||||
|
|
||||||
if (reply &&
|
|
||||||
(reply->type == XCB_ATOM_STRING || reply->type == UTF8_STRING ||
|
|
||||||
reply->type == COMPOUND_TEXT) &&
|
|
||||||
reply->format == 8 && length &&
|
|
||||||
(*out == nullptr || strncmp(*out, value, length) != 0)) {
|
|
||||||
if (*out) p_delete(out);
|
|
||||||
*out = p_new(char, length + 1);
|
|
||||||
memcpy(*out, value, length);
|
|
||||||
(*out)[length] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
p_delete(&reply);
|
|
||||||
}
|
|
||||||
|
|
||||||
void xwindow_set_wm_desktop(xcb_window_t window, uint32_t desktop) {
|
|
||||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, window,
|
|
||||||
_NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &desktop);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool xwindow_get_wm_desktop(xcb_window_t window, uint32_t *desktop) {
|
|
||||||
xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(
|
|
||||||
wm.xcb_conn, false, window, _NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 0, 1);
|
|
||||||
xcb_get_property_reply_t *reply =
|
|
||||||
xcb_get_property_reply(wm.xcb_conn, cookie, nullptr);
|
|
||||||
if (!reply) return false;
|
|
||||||
|
|
||||||
bool success = reply->type == XCB_ATOM_CARDINAL && reply->format == 32;
|
|
||||||
if (success) *desktop = *(uint32_t *)xcb_get_property_value(reply);
|
|
||||||
|
|
||||||
p_delete(&reply);
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void xwindow_kill_window(xcb_window_t window) {
|
|
||||||
xcb_grab_server(wm.xcb_conn);
|
|
||||||
xcb_set_close_down_mode(wm.xcb_conn, XCB_CLOSE_DOWN_DESTROY_ALL);
|
|
||||||
xcb_kill_client(wm.xcb_conn, window);
|
|
||||||
xcb_flush(wm.xcb_conn);
|
|
||||||
xcb_ungrab_server(wm.xcb_conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_window_t xwindow_get_transient_for(xcb_window_t window) {
|
|
||||||
xcb_connection_t *conn = wm.xcb_conn;
|
|
||||||
xcb_window_t t_window = XCB_WINDOW_NONE;
|
|
||||||
xcb_get_property_cookie_t cookie =
|
|
||||||
xcb_icccm_get_wm_transient_for(conn, window);
|
|
||||||
xcb_icccm_get_wm_transient_for_reply(conn, cookie, &t_window, nullptr);
|
|
||||||
return t_window;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取窗口的状态
|
|
||||||
* @param window 目标窗口
|
|
||||||
* @returns 成功返回 xcb_icccm_wm_state_t 枚举类型,失败返回 -1
|
|
||||||
*/
|
|
||||||
int32_t xwindow_get_state(xcb_window_t window) {
|
|
||||||
xcb_connection_t *conn = wm.xcb_conn;
|
|
||||||
xcb_icccm_wm_hints_t hints;
|
|
||||||
xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints(conn, window);
|
|
||||||
if (!xcb_icccm_get_wm_hints_reply(conn, cookie, &hints, nullptr)) return -1;
|
|
||||||
return hints.initial_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void xwindow_set_state(xcb_window_t window, xcb_icccm_wm_state_t state) {
|
|
||||||
xcb_icccm_wm_hints_t hints;
|
|
||||||
xcb_icccm_wm_hints_set_none(&hints);
|
|
||||||
switch (state) {
|
|
||||||
case XCB_ICCCM_WM_STATE_WITHDRAWN:
|
|
||||||
xcb_icccm_wm_hints_set_withdrawn(&hints);
|
|
||||||
break;
|
|
||||||
case XCB_ICCCM_WM_STATE_NORMAL:
|
|
||||||
xcb_icccm_wm_hints_set_normal(&hints);
|
|
||||||
break;
|
|
||||||
case XCB_ICCCM_WM_STATE_ICONIC:
|
|
||||||
xcb_icccm_wm_hints_set_iconic(&hints);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
xcb_icccm_set_wm_hints(wm.xcb_conn, window, &hints);
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_get_geometry_reply_t *xwindow_get_geometry_reply(xcb_window_t window) {
|
|
||||||
xcb_get_geometry_cookie_t cookie = xcb_get_geometry(wm.xcb_conn, window);
|
|
||||||
return xcb_get_geometry_reply(wm.xcb_conn, cookie, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_get_window_attributes_reply_t *xwindow_get_attributes_reply(
|
|
||||||
xcb_window_t window) {
|
|
||||||
xcb_get_window_attributes_cookie_t wa_cookie =
|
|
||||||
xcb_get_window_attributes(wm.xcb_conn, window);
|
|
||||||
return xcb_get_window_attributes_reply(wm.xcb_conn, wa_cookie, nullptr);
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <xcb/xcb_icccm.h>
|
|
||||||
#include <xcb/xproto.h>
|
|
||||||
|
|
||||||
#include "action.h"
|
|
||||||
#include "app.h"
|
|
||||||
#include "xcursor.h"
|
|
||||||
|
|
||||||
typedef struct visual_t {
|
|
||||||
uint8_t depth;
|
|
||||||
xcb_visualtype_t *visual;
|
|
||||||
} visual_t;
|
|
||||||
|
|
||||||
visual_t *xwindow_get_xcb_visual(bool prefer_alpha);
|
|
||||||
void xwindow_change_cursor(xcb_window_t window, cursor_t cursor);
|
|
||||||
void xwindow_grab_keys(xcb_window_t window, const keyboard_t *keys,
|
|
||||||
int keys_length);
|
|
||||||
bool xwindow_send_event(xcb_window_t window, xcb_atom_t atom);
|
|
||||||
void xwindow_focus(xcb_window_t window);
|
|
||||||
void xwindow_get_text_property(xcb_window_t window, xcb_atom_t property,
|
|
||||||
char **out);
|
|
||||||
void xwindow_set_wm_desktop(xcb_window_t window, uint32_t desktop);
|
|
||||||
bool xwindow_get_wm_desktop(xcb_window_t window, uint32_t *desktop);
|
|
||||||
void xwindow_kill_window(xcb_window_t window);
|
|
||||||
|
|
||||||
xcb_window_t xwindow_get_transient_for(xcb_window_t window);
|
|
||||||
int32_t xwindow_get_state(xcb_window_t window);
|
|
||||||
void xwindow_set_state(xcb_window_t window, xcb_icccm_wm_state_t state);
|
|
||||||
xcb_get_geometry_reply_t *xwindow_get_geometry_reply(xcb_window_t window);
|
|
||||||
xcb_get_window_attributes_reply_t *xwindow_get_attributes_reply(
|
|
||||||
xcb_window_t window);
|
|
||||||
|
|
||||||
#define xwindow_set_name_static(window, name) \
|
|
||||||
xcb_icccm_set_wm_name(wm.xcb_conn, window, XCB_ATOM_STRING, 8, \
|
|
||||||
sizeof(name) - 1, name)
|
|
||||||
#define xwindow_set_class_instance(window) \
|
|
||||||
xwindow_set_class_instance_static(window, APP_NAME, APP_NAME)
|
|
||||||
#define xwindow_set_class_instance_static(window, class, instance) \
|
|
||||||
_xwindow_set_class_instance_static(window, instance "\0" class)
|
|
||||||
#define _xwindow_set_class_instance_static(window, instance_class) \
|
|
||||||
xcb_icccm_set_wm_class(wm.xcb_conn, window, sizeof(instance_class), \
|
|
||||||
instance_class)
|
|
||||||
Reference in New Issue
Block a user