1474 lines
46 KiB
C
1474 lines
46 KiB
C
#include <cairo-xcb.h>
|
||
#include <cairo.h>
|
||
#include <glib.h>
|
||
#include <glibconfig.h>
|
||
#include <stdbool.h>
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <wordexp.h>
|
||
#include <xcb/xproto.h>
|
||
|
||
#include "action.h"
|
||
#include "app.h"
|
||
#include "config.h"
|
||
#include "event-adapter.h"
|
||
#include "layout.h"
|
||
#include "renderer.h"
|
||
#include "status.h"
|
||
#include "types.h"
|
||
#include "utils.h"
|
||
#include "xcb.h"
|
||
|
||
static void init_monitors(void);
|
||
static char **get_monitor_tags(uint32_t monitor_count, uint32_t monitor_index);
|
||
static void init_wallpaper(void);
|
||
static gboolean update_wallpaper(gpointer user_data);
|
||
static void set_wallpaper_by_index(uint32_t index, wallpaper_config_t *config,
|
||
cairo_t *cr);
|
||
static wallpaper_config_t *parse_wallpaper_config(void);
|
||
static void free_wallpaper_config(wallpaper_config_t *wallpaper_config);
|
||
static void init_icons(void);
|
||
static void clean_icons(void);
|
||
static void init_color_set(void);
|
||
static void parse_color(const char *hex, color_t *color);
|
||
static void init_monitor_bar(void);
|
||
static void get_xresource_config(void);
|
||
static void update_status(status_t *status);
|
||
static void draw_all_monitor_bars(void);
|
||
static void draw_monitor_bar(monitor_t *monitor);
|
||
static void init_xcb_event_loop(void);
|
||
static void xcb_event_handler(generic_event_t *event, void *user_data);
|
||
static void key_press(key_press_event_t *event);
|
||
static void configure_request(configure_request_event_t *event);
|
||
static void map_request(map_request_event_t *event);
|
||
static void unmap_notify(unmap_notify_event_t *event);
|
||
static void destroy_notify(destroy_notify_event_t *event);
|
||
static void expose(expose_event_t *event);
|
||
static void motion_notify(motion_notify_event_t *event);
|
||
static void enter_notify(enter_notify_event_t *event);
|
||
static void focus_in(focus_in_event_t *event);
|
||
static void property_notify(property_notify_event_t *event);
|
||
static void client_message(client_message_event_t *event);
|
||
static void manage_window(xcb_window_t window);
|
||
static void unmanage_client(client_t *client, bool destroyed);
|
||
static void update_client_list(void);
|
||
static void update_client_name(client_t *client);
|
||
static void apply_rules(client_t *client);
|
||
static uint32_t get_tags_mask_of_monitor(monitor_t *monitor);
|
||
static void attach_client(client_t *client);
|
||
static void detach_client(client_t *client);
|
||
static void attach_stack_client(client_t *client);
|
||
static void detach_stack_client(client_t *client);
|
||
static void arrange(monitor_t *monitor);
|
||
static void show_hide_client(client_t *client);
|
||
static void restack(monitor_t *monitor);
|
||
static void focus(client_t *client);
|
||
static void unfocus(client_t *client, bool set_focus);
|
||
static void set_client_fullscreen(client_t *client, bool fullscreen);
|
||
static void apply_monitor_layout(monitor_t *monitor);
|
||
static client_t *get_client_of_window(xcb_window_t window);
|
||
static monitor_t *get_monitor_of_window(xcb_window_t window);
|
||
static monitor_t *get_monitor_by_direction(bool forward);
|
||
static void send_client_to_monitor(client_t *client, monitor_t *monitor);
|
||
static void set_client_tag_prop(client_t *client);
|
||
static bool restore_client_tag(client_t *client);
|
||
static client_t *get_next_client_with_class(const char *class);
|
||
static gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
|
||
gpointer userdata);
|
||
static void cleanup(bool will_restart);
|
||
|
||
static monitor_t *monitors = NULL;
|
||
static monitor_t *current_monitor = NULL;
|
||
static cairo_t *wallpaper_cr = NULL;
|
||
static wallpaper_config_t *wallpaper_config = NULL;
|
||
static icons_t *icons = NULL;
|
||
static color_set_t *color_set = NULL;
|
||
static status_t *status = NULL;
|
||
static GMainLoop *loop = NULL;
|
||
static event_adapter_t *adapter = NULL;
|
||
static char *font_family = NULL;
|
||
static uint32_t font_size = default_font_size;
|
||
static uint32_t dpi = dpi_fallback;
|
||
static uint32_t bar_y_pad = bar_y_padding;
|
||
static uint32_t tag_x_pad = tag_x_padding;
|
||
static uint32_t layout_symbol_x_pad = layout_symbol_x_padding;
|
||
static uint32_t client_name_x_pad = client_name_x_padding;
|
||
static uint32_t status_x_pad = status_x_padding;
|
||
static uint32_t status_icon_pad = status_icon_padding;
|
||
static uint32_t bar_height = 0;
|
||
static uint32_t border_px = border_width;
|
||
|
||
static bool need_restart = false;
|
||
|
||
void spawn(const user_action_arg_t *arg) {
|
||
g_spawn_command_line_async(arg->ptr, NULL);
|
||
}
|
||
void quit(const user_action_arg_t *arg) {
|
||
need_restart = arg->b;
|
||
g_main_loop_quit(loop);
|
||
}
|
||
|
||
void select_tag(const user_action_arg_t *arg) {
|
||
uint32_t tags_mask = get_tags_mask_of_monitor(current_monitor);
|
||
uint32_t target_tag = arg->ui & tags_mask;
|
||
if (!target_tag || current_monitor->selected_tags == target_tag) return;
|
||
|
||
current_monitor->selected_tags = target_tag;
|
||
focus(NULL);
|
||
arrange(current_monitor);
|
||
}
|
||
|
||
void kill_client(const user_action_arg_t *arg) {
|
||
client_t *client = current_monitor->selected_client;
|
||
if (!client) return;
|
||
|
||
if (!send_event(client->window, get_xcb_atom(atom_wm_delete))) {
|
||
kill_window(client->window);
|
||
}
|
||
}
|
||
|
||
void focus_stack(const user_action_arg_t *arg) {
|
||
if (!current_monitor->selected_client) return;
|
||
|
||
bool forward = arg->b;
|
||
client_t *client = NULL;
|
||
if (forward) {
|
||
for (client = current_monitor->selected_client->next;
|
||
client && !CLIENT_VISIBLE(client); client = client->next);
|
||
if (!client) {
|
||
for (client = current_monitor->clients; client && !CLIENT_VISIBLE(client);
|
||
client = client->next);
|
||
}
|
||
} else {
|
||
client_t *c = NULL;
|
||
for (c = current_monitor->clients; c != current_monitor->selected_client;
|
||
c = c->next) {
|
||
if (CLIENT_VISIBLE(c)) client = c;
|
||
}
|
||
if (!client) {
|
||
for (; c; c = c->next) {
|
||
if (CLIENT_VISIBLE(c)) client = c;
|
||
}
|
||
}
|
||
}
|
||
if (client) {
|
||
focus(client);
|
||
restack(current_monitor);
|
||
}
|
||
}
|
||
|
||
void focus_monitor(const user_action_arg_t *arg) {
|
||
if (!monitors->next) return;
|
||
|
||
bool forward = arg->b;
|
||
monitor_t *monitor = get_monitor_by_direction(forward);
|
||
if (monitor == current_monitor) return;
|
||
|
||
unfocus(current_monitor->selected_client, false);
|
||
current_monitor = monitor;
|
||
focus(NULL);
|
||
/* TODO: 调整鼠标光标 */
|
||
}
|
||
|
||
void raise_or_run(const user_action_arg_t *arg) {
|
||
const char *class = ((const char **)arg->ptr)[0];
|
||
client_t *client = get_next_client_with_class(class);
|
||
if (client && client == current_monitor->selected_client) return;
|
||
|
||
user_action_arg_t argument;
|
||
if (client) {
|
||
logger("==== raise client: %s, window: 0x%x\n", client->name,
|
||
client->window);
|
||
current_monitor = client->monitor;
|
||
argument.ui = client->tags;
|
||
select_tag(&argument);
|
||
focus(client);
|
||
raise_window(client->window);
|
||
return;
|
||
}
|
||
|
||
const char *command = ((const char **)arg->ptr)[1];
|
||
argument.ptr = command;
|
||
spawn(&argument);
|
||
return;
|
||
}
|
||
|
||
void toggle_mute(const user_action_arg_t *arg) { toggle_pulse_mute(); }
|
||
|
||
void change_volume(const user_action_arg_t *arg) {
|
||
change_pulse_volume(arg->i);
|
||
}
|
||
|
||
void move_to_monitor(const user_action_arg_t *arg) {
|
||
if (!current_monitor->selected_client || !monitors->next) return;
|
||
|
||
bool forward = arg->b;
|
||
monitor_t *monitor = get_monitor_by_direction(forward);
|
||
send_client_to_monitor(current_monitor->selected_client, monitor);
|
||
}
|
||
|
||
void move_to_tag(const user_action_arg_t *arg) {
|
||
if (!current_monitor || !current_monitor->selected_client) return;
|
||
|
||
uint32_t tag_mask = 0;
|
||
for (uint32_t i = 0; i < g_strv_length(current_monitor->tags); i++) {
|
||
tag_mask |= 1 << i;
|
||
}
|
||
uint32_t target_tag = arg->ui & tag_mask;
|
||
if (!target_tag) return;
|
||
|
||
client_t *client = current_monitor->selected_client;
|
||
client->tags = target_tag;
|
||
set_client_tag_prop(client);
|
||
focus(NULL);
|
||
arrange(current_monitor);
|
||
}
|
||
|
||
/* 调试函数,开发完成后删除 */
|
||
static void print_monitor_list_info(monitor_t *monitor_list) {
|
||
logger("========================= monitor info =========================\n");
|
||
for (monitor_t *m = monitor_list; m; m = m->next) {
|
||
logger("========== mon[%u]:\n", m->index);
|
||
logger("x: %d, y: %d, width: %u, height: %u\n", m->monitor_x, m->monitor_y,
|
||
m->monitor_width, m->monitor_height);
|
||
logger("mon[%u] tags: ", m->index);
|
||
for (int i = 0; m->tags[i]; i++) {
|
||
logger("%s%s", i > 0 ? ", " : "", m->tags[i]);
|
||
}
|
||
logger("\n");
|
||
}
|
||
logger("======================= monitor info end =======================\n");
|
||
}
|
||
static void print_color(const char *prompt, const color_t *color) {
|
||
const char *p = strlen(prompt) ? " " : "";
|
||
logger("%s%srgba: #%08x, argb: #%08x\n", prompt, p, color->rgba, color->argb);
|
||
}
|
||
static void print_color_set(color_set_t *set) {
|
||
print_color("== bar bg:", &set->bar_bg);
|
||
print_color("== tag bg:", &set->tag_bg);
|
||
print_color("== active tag bg:", &set->active_tag_bg);
|
||
print_color("== tag color:", &set->tag_color);
|
||
print_color("== active tag color:", &set->active_tag_color);
|
||
print_color("== layout bg:", &set->layout_color);
|
||
print_color("== client name bg:", &set->client_name_bg);
|
||
print_color("== client name color:", &set->client_name_color);
|
||
print_color("== active client name bg:", &set->active_client_name_bg);
|
||
print_color("== active client name color:", &set->active_client_name_color);
|
||
print_color("== status net recv color:", &set->status_net_recv_color);
|
||
print_color("== status net send color:", &set->status_net_send_color);
|
||
print_color("== status volume color:", &set->status_volume_color);
|
||
print_color("== status memory color:", &set->status_memory_color);
|
||
print_color("== status cpu color:", &set->status_cpu_color);
|
||
print_color("== status datetime color:", &set->status_datetime_color);
|
||
print_color("== border color:", &set->border_color);
|
||
print_color("== active border color:", &set->active_border_color);
|
||
}
|
||
static void print_wallpaper_config(wallpaper_config_t *config) {
|
||
if (!config) {
|
||
logger("no wallpaper\n");
|
||
return;
|
||
}
|
||
|
||
logger("wallpaper count: %u\n", config->count);
|
||
for (uint32_t i = 0; i < config->count; i++) {
|
||
logger("wallpaper[%02u]: %s\n", i, config->path_list[i]);
|
||
}
|
||
}
|
||
static void print_window_list(window_list_t *window_list) {
|
||
if (!window_list) {
|
||
logger("no window\n");
|
||
return;
|
||
}
|
||
|
||
for (uint32_t i = 0; i < window_list->normal_count; i++) {
|
||
logger("normal window[%u]: 0x%x\n", i, window_list->normal_list[i]);
|
||
}
|
||
for (uint32_t i = 0; i < window_list->transient_count; i++) {
|
||
logger("transient window[%u]: 0x%x\n", i, window_list->transient_list[i]);
|
||
}
|
||
}
|
||
static char *bool2string(bool b) { return b ? "true" : "false"; }
|
||
static void print_client(client_t *c) {
|
||
if (!c) return;
|
||
|
||
logger("======= client info ========\n");
|
||
logger("window: 0x%x, x: %d, y: %d, w: %u, h: %u, bw: %u\n", c->window, c->x,
|
||
c->y, c->width, c->height, c->border_width);
|
||
logger("floating: %s, fullscreen: %s, maximize: %s\n",
|
||
bool2string(c->floating), bool2string(c->fullscreen),
|
||
bool2string(c->maximize));
|
||
logger("tags: %b, monitor selected tags: %b\n", c->tags,
|
||
c->monitor->selected_tags);
|
||
logger("name: %s\n", c->name);
|
||
logger("===== client info end ======\n");
|
||
}
|
||
static void print_rule(const rule_t *r) {
|
||
logger("==================== rule ====================\n");
|
||
logger("class: %s, instance: %s, title: %s\n", r->class, r->instance,
|
||
r->title);
|
||
logger("floating: %s, maximize: %s, fullscreen: %s, switch to tag: %s\n",
|
||
bool2string(r->action.floating), bool2string(r->action.maximize),
|
||
bool2string(r->action.fullscreen),
|
||
bool2string(r->action.switch_to_tag));
|
||
for (uint32_t i = 0; r->tag && r->tag[i].tags; i++) {
|
||
const rule_tag_t *t = &r->tag[i];
|
||
logger("monitor amount: %u, monitor index: %u, tags: %b\n",
|
||
t->monitor_amount, t->monitor_index, t->tags);
|
||
}
|
||
logger("================== rule end ==================\n");
|
||
}
|
||
/* 调试函数结束 */
|
||
|
||
void init_monitors(void) {
|
||
monitor_rect_t *mr = detect_monitor_rect();
|
||
uint32_t i = 0;
|
||
monitor_t *prev_monitor = NULL;
|
||
while (mr) {
|
||
monitor_t *m = ecalloc(1, sizeof(monitor_t));
|
||
m->monitor_x = mr->x;
|
||
m->monitor_y = mr->y;
|
||
m->monitor_width = mr->width;
|
||
m->monitor_height = mr->height;
|
||
m->index = i;
|
||
m->layout = &layouts[0];
|
||
|
||
if (prev_monitor) {
|
||
prev_monitor->next = m;
|
||
} else {
|
||
monitors = m;
|
||
current_monitor = m;
|
||
}
|
||
prev_monitor = m;
|
||
|
||
i++;
|
||
monitor_rect_t *next_mr = mr->next;
|
||
free(mr);
|
||
mr = next_mr;
|
||
}
|
||
for (monitor_t *m = monitors; m; m = m->next) {
|
||
m->tags = get_monitor_tags(i, m->index);
|
||
m->selected_tags = 0x1;
|
||
}
|
||
}
|
||
|
||
char **get_monitor_tags(uint32_t monitor_count, uint32_t monitor_index) {
|
||
GStrvBuilder *builder = g_strv_builder_new();
|
||
const char *const *tags = NULL;
|
||
|
||
/* 倒序遍历,达到后面的配置覆盖前面相同配置的效果 */
|
||
for (int i = LENGTH(monitor_tags) - 1; i >= 0; i--) {
|
||
const char *const *const *config = monitor_tags[i];
|
||
|
||
uint32_t monitor_count_in_tags_config = 0;
|
||
while (config[monitor_count_in_tags_config]) monitor_count_in_tags_config++;
|
||
|
||
if (monitor_count_in_tags_config != monitor_count) continue;
|
||
|
||
tags = config[monitor_index];
|
||
break;
|
||
const char *const *const tags = config[monitor_index];
|
||
for (int j = 0; tags[j]; j++) g_strv_builder_add(builder, tags[j]);
|
||
|
||
return g_strv_builder_unref_to_strv(builder);
|
||
}
|
||
|
||
if (tags == NULL) tags = default_monitor_tags;
|
||
|
||
for (int i = 0; tags[i]; i++) g_strv_builder_add(builder, tags[i]);
|
||
return g_strv_builder_unref_to_strv(builder);
|
||
}
|
||
|
||
void init_wallpaper(void) {
|
||
bar_cairo_params_t *p = prepare_wallpaper_surface_params();
|
||
rect_size_t *screen_size = get_screen_size();
|
||
if (wallpaper_cr) cairo_destroy(wallpaper_cr);
|
||
|
||
cairo_surface_t *surface = cairo_xcb_surface_create(
|
||
p->conn, p->window, p->visual, screen_size->width, screen_size->height);
|
||
free(p);
|
||
free(screen_size);
|
||
|
||
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
|
||
cairo_surface_destroy(surface);
|
||
return;
|
||
}
|
||
|
||
cairo_t *cr = cairo_create(surface);
|
||
if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
|
||
cairo_destroy(cr);
|
||
return;
|
||
}
|
||
wallpaper_cr = cr;
|
||
cairo_surface_destroy(surface);
|
||
cairo_set_source_rgba(cr, 0, 0, 0, 1);
|
||
cairo_paint(cr);
|
||
refresh_root_window();
|
||
|
||
wallpaper_config = parse_wallpaper_config();
|
||
if (!wallpaper_config) return;
|
||
|
||
update_wallpaper(wallpaper_config);
|
||
if (!wallpaper_interval) return;
|
||
|
||
g_timeout_add_seconds(wallpaper_interval, update_wallpaper, wallpaper_config);
|
||
}
|
||
|
||
gboolean update_wallpaper(gpointer user_data) {
|
||
wallpaper_config_t *wc = user_data;
|
||
if (!wc || !wallpaper_cr) return false;
|
||
|
||
static uint32_t index = 0;
|
||
set_wallpaper_by_index(index, wc, wallpaper_cr);
|
||
uint32_t monitor_count = 0;
|
||
for (monitor_t *m = monitors; m; m = m->next) monitor_count++;
|
||
|
||
/* 屏幕比壁纸多则不动态切换 */
|
||
if (!monitor_count || monitor_count >= wc->count) return false;
|
||
|
||
if (monitor_count) {
|
||
index += monitor_count;
|
||
index %= wc->count;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void set_wallpaper_by_index(uint32_t index, wallpaper_config_t *config,
|
||
cairo_t *cr) {
|
||
rect_size_t *screen_size = get_screen_size();
|
||
uint32_t width = screen_size->width;
|
||
uint32_t height = screen_size->height;
|
||
free(screen_size);
|
||
|
||
uint32_t *wallpaper =
|
||
generate_wallpaper(config, monitors, index, width, height);
|
||
|
||
if (!wallpaper) return;
|
||
|
||
double start = get_time();
|
||
uint8_t *data = (uint8_t *)wallpaper;
|
||
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
|
||
cairo_surface_t *s = cairo_image_surface_create_for_data(
|
||
data, CAIRO_FORMAT_ARGB32, width, height, stride);
|
||
cairo_set_source_surface(cr, s, 0, 0);
|
||
cairo_paint(cr);
|
||
refresh_root_window();
|
||
free(wallpaper);
|
||
double end = get_time();
|
||
logger("set wallpaper cost: %fs\n", end - start);
|
||
}
|
||
|
||
wallpaper_config_t *parse_wallpaper_config(void) {
|
||
if (!LENGTH(wallpapers)) return NULL;
|
||
|
||
wallpaper_config_t *wc = ecalloc(1, sizeof(wallpaper_config_t));
|
||
|
||
GStrvBuilder *builder = g_strv_builder_new();
|
||
for (int i = 0; i < LENGTH(wallpapers); i++) {
|
||
wordexp_t p;
|
||
if (wordexp(wallpapers[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 = ecalloc(len, sizeof(char));
|
||
strncpy(realpath, p.we_wordv[i], len);
|
||
g_strstrip(realpath);
|
||
if (file_exist(realpath)) g_strv_builder_add(builder, realpath);
|
||
|
||
free(realpath);
|
||
}
|
||
wordfree(&p);
|
||
}
|
||
|
||
wc->path_list = g_strv_builder_unref_to_strv(builder);
|
||
wc->count = g_strv_length(wc->path_list);
|
||
if (wc->count) return wc;
|
||
|
||
free_wallpaper_config(wc);
|
||
return NULL;
|
||
}
|
||
|
||
void free_wallpaper_config(wallpaper_config_t *wallpaper_config) {
|
||
if (!wallpaper_config) return;
|
||
|
||
g_strfreev(wallpaper_config->path_list);
|
||
free(wallpaper_config);
|
||
}
|
||
|
||
void init_icons(void) {
|
||
typedef struct {
|
||
const char *img_path;
|
||
icon_res_t *icon_res;
|
||
} icon_path_surface_map_t;
|
||
|
||
if (icons) return;
|
||
|
||
icons = ecalloc(1, sizeof(icons_t));
|
||
icon_path_surface_map_t map[] = {
|
||
{NET_DOWN_ICON, &icons->net_down}, {NET_UP_ICON, &icons->net_up},
|
||
{SPEAKER_ICON, &icons->volume}, {MEM_ICON, &icons->memory},
|
||
{CPU_ICON, &icons->cpu}, {CLOCK_ICON, &icons->clock},
|
||
};
|
||
for (int i = 0; i < LENGTH(map); i++) {
|
||
const char *path = map[i].img_path;
|
||
icon_res_t *res = map[i].icon_res;
|
||
if (path && strlen(path)) {
|
||
uint32_t *pixels =
|
||
generate_icon_pixels(path, bar_height, bar_height, status_icon_pad);
|
||
cairo_format_t format = CAIRO_FORMAT_ARGB32;
|
||
int stride = cairo_format_stride_for_width(format, bar_height);
|
||
cairo_surface_t *surface = cairo_image_surface_create_for_data(
|
||
(uint8_t *)pixels, format, bar_height, bar_height, stride);
|
||
if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
|
||
res->pixels = pixels;
|
||
res->surface = surface;
|
||
} else {
|
||
cairo_surface_destroy(surface);
|
||
free(pixels);
|
||
res->pixels = NULL;
|
||
res->surface = NULL;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void clean_icons(void) {
|
||
if (!icons) return;
|
||
|
||
icon_res_t icon_res_list[] = {
|
||
icons->net_down, icons->net_up, icons->volume,
|
||
icons->memory, icons->cpu, icons->clock,
|
||
};
|
||
for (int i = 0; i < LENGTH(icon_res_list); i++) {
|
||
icon_res_t res = icon_res_list[i];
|
||
if (res.surface) {
|
||
cairo_surface_finish(res.surface);
|
||
free(res.pixels);
|
||
cairo_surface_destroy(res.surface);
|
||
|
||
res.pixels = NULL;
|
||
res.surface = NULL;
|
||
}
|
||
}
|
||
|
||
free(icons);
|
||
icons = NULL;
|
||
}
|
||
|
||
void init_color_set(void) {
|
||
color_set = ecalloc(1, sizeof(color_set_t));
|
||
char *bar_background = NULL;
|
||
char *tag_background = NULL;
|
||
char *active_tag_background = NULL;
|
||
char *tag = NULL;
|
||
char *active_tag = NULL;
|
||
char *layout = NULL;
|
||
char *client_name_background = NULL;
|
||
char *client_name = NULL;
|
||
char *active_client_name_background = NULL;
|
||
char *active_client_name = NULL;
|
||
char *status_net_recv = NULL;
|
||
char *status_net_send = NULL;
|
||
char *status_volume = NULL;
|
||
char *status_memory = NULL;
|
||
char *status_cpu = NULL;
|
||
char *status_datetime = NULL;
|
||
char *border = NULL;
|
||
char *active_border = NULL;
|
||
|
||
get_xresource_string("zswm.color.bar_bg", &bar_background, bar_bg);
|
||
get_xresource_string("zswm.color.tag_bg", &tag_background, tag_bg);
|
||
get_xresource_string("zswm.color.active_tag_bg", &active_tag_background,
|
||
active_tag_bg);
|
||
get_xresource_string("zswm.color.tag", &tag, tag_color);
|
||
get_xresource_string("zswm.color.active_tag", &active_tag, active_tag_color);
|
||
get_xresource_string("zswm.color.layout", &layout, layout_color);
|
||
get_xresource_string("zswm.color.client_name_bg", &client_name_background,
|
||
client_name_bg);
|
||
get_xresource_string("zswm.color.client_name_color", &client_name,
|
||
client_name_color);
|
||
get_xresource_string("zswm.color.active_client_name_bg",
|
||
&active_client_name_background, active_client_name_bg);
|
||
get_xresource_string("zswm.color.active_client_name_color",
|
||
&active_client_name, active_client_name_color);
|
||
get_xresource_string("zswm.color.status_net_recv", &status_net_recv,
|
||
status_net_recv_color);
|
||
get_xresource_string("zswm.color.status_net_send", &status_net_send,
|
||
status_net_send_color);
|
||
get_xresource_string("zswm.color.status_volume", &status_volume,
|
||
status_volume_color);
|
||
get_xresource_string("zswm.color.status_memory", &status_memory,
|
||
status_memory_color);
|
||
get_xresource_string("zswm.color.status_cpu", &status_cpu, status_cpu_color);
|
||
get_xresource_string("zswm.color.status_datetime", &status_datetime,
|
||
status_datetime_color);
|
||
get_xresource_string("zswm.color.border", &border, border_color);
|
||
get_xresource_string("zswm.color.active_border", &active_border,
|
||
active_border_color);
|
||
|
||
parse_color(bar_background, &color_set->bar_bg);
|
||
parse_color(tag_background, &color_set->tag_bg);
|
||
parse_color(active_tag_background, &color_set->active_tag_bg);
|
||
parse_color(tag, &color_set->tag_color);
|
||
parse_color(active_tag, &color_set->active_tag_color);
|
||
parse_color(layout, &color_set->layout_color);
|
||
parse_color(client_name_background, &color_set->client_name_bg);
|
||
parse_color(client_name, &color_set->client_name_color);
|
||
parse_color(active_client_name_background, &color_set->active_client_name_bg);
|
||
parse_color(active_client_name, &color_set->active_client_name_color);
|
||
parse_color(status_net_recv, &color_set->status_net_recv_color);
|
||
parse_color(status_net_send, &color_set->status_net_send_color);
|
||
parse_color(status_volume, &color_set->status_volume_color);
|
||
parse_color(status_memory, &color_set->status_memory_color);
|
||
parse_color(status_cpu, &color_set->status_cpu_color);
|
||
parse_color(status_datetime, &color_set->status_datetime_color);
|
||
parse_color(border, &color_set->border_color);
|
||
parse_color(active_border, &color_set->active_border_color);
|
||
|
||
free(bar_background);
|
||
free(tag_background);
|
||
free(active_tag_background);
|
||
free(tag);
|
||
free(active_tag);
|
||
free(layout);
|
||
free(client_name_background);
|
||
free(client_name);
|
||
free(active_client_name_background);
|
||
free(active_client_name);
|
||
free(status_net_recv);
|
||
free(status_net_send);
|
||
free(status_volume);
|
||
free(status_memory);
|
||
free(status_cpu);
|
||
free(status_datetime);
|
||
free(border);
|
||
free(active_border);
|
||
}
|
||
|
||
void parse_color(const char *hex, color_t *color) {
|
||
bool success = hex_color_to_rgba(hex, &color->rgba);
|
||
if (!success) die("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);
|
||
}
|
||
|
||
void init_monitor_bar(void) {
|
||
get_xresource_config();
|
||
uint32_t font_height = get_font_height(font_family, font_size, dpi);
|
||
bar_height = font_height + bar_y_pad * 2;
|
||
for (monitor_t *m = monitors; m; m = m->next) {
|
||
bar_cairo_params_t *p =
|
||
create_bar_window(m->monitor_x, m->monitor_y, m->monitor_width,
|
||
bar_height, color_set->bar_bg.argb, cursor_normal);
|
||
m->window_x = m->monitor_x;
|
||
m->window_width = m->monitor_width;
|
||
m->window_y = m->monitor_y + bar_height;
|
||
m->window_height = m->monitor_height - bar_height;
|
||
m->bar_window = p->window;
|
||
|
||
cairo_surface_t *surface = cairo_xcb_surface_create(
|
||
p->conn, p->window, p->visual, m->monitor_width, bar_height);
|
||
cairo_t *cr = cairo_create(surface);
|
||
cairo_surface_destroy(surface);
|
||
free(p);
|
||
|
||
cairo_status_t status = cairo_status(cr);
|
||
if (status != CAIRO_STATUS_SUCCESS) {
|
||
die("cannot create cairo context: %s\n", cairo_status_to_string(status));
|
||
}
|
||
|
||
m->cr = cr;
|
||
}
|
||
logger("font height: %u, bar height: %u\n", font_height, bar_height);
|
||
}
|
||
|
||
void get_xresource_config(void) {
|
||
get_xresource_uint32("Xft.dpi", &dpi);
|
||
get_xresource_string("zswm.font_family", &font_family, default_font_family);
|
||
get_xresource_uint32("zswm.font_size", &font_size);
|
||
get_xresource_uint32("zswm.bar_y_padding", &bar_y_pad);
|
||
get_xresource_uint32("zswm.tag_x_padding", &tag_x_pad);
|
||
get_xresource_uint32("zswm.layout_symbol_x_padding", &layout_symbol_x_pad);
|
||
get_xresource_uint32("zswm.client_name_x_padding", &client_name_x_pad);
|
||
get_xresource_uint32("zswm.status_x_padding", &status_x_pad);
|
||
get_xresource_uint32("zswm.status_icon_padding", &status_icon_pad);
|
||
get_xresource_uint32("zswm.border_width", &border_px);
|
||
|
||
logger("dpi: %u, font family: %s, font size: %u\n", dpi, font_family,
|
||
font_size);
|
||
clean_xresource();
|
||
}
|
||
|
||
static void update_status(status_t *s) {
|
||
status = s;
|
||
draw_all_monitor_bars();
|
||
}
|
||
|
||
void draw_all_monitor_bars(void) {
|
||
for (monitor_t *m = monitors; m; m = m->next) draw_monitor_bar(m);
|
||
}
|
||
|
||
void draw_monitor_bar(monitor_t *m) {
|
||
draw_bar(m, m == current_monitor, status, icons, color_set, bar_height,
|
||
tag_x_pad, layout_symbol_x_pad, client_name_x_pad, status_x_pad);
|
||
flush_xcb_connection();
|
||
}
|
||
|
||
void init_xcb_event_loop(void) {
|
||
adapter = create_event_adapter();
|
||
event_adapter_set_handler(adapter, xcb_event_handler, NULL);
|
||
GIOChannel *channel = g_io_channel_unix_new(get_xcb_connection_fd());
|
||
g_io_channel_set_encoding(channel, NULL, NULL);
|
||
GIOCondition cond = G_IO_IN | G_IO_HUP | G_IO_ERR;
|
||
g_io_add_watch(channel, cond, handle_xcb_event, NULL);
|
||
g_io_channel_unref(channel);
|
||
}
|
||
|
||
void xcb_event_handler(generic_event_t *event, void *user_data) {
|
||
switch (event->type) {
|
||
#define EVENT(type, callback) \
|
||
case type: \
|
||
callback((void *)event); \
|
||
break
|
||
|
||
EVENT(EVENT_TYPE_KEY_PRESS, key_press);
|
||
EVENT(EVENT_TYPE_CONFIGURE_REQUEST, configure_request);
|
||
EVENT(EVENT_TYPE_MAP_REQUEST, map_request);
|
||
EVENT(EVENT_TYPE_UNMAP_NOTIFY, unmap_notify);
|
||
EVENT(EVENT_TYPE_DESTROY_NOTIFY, destroy_notify);
|
||
EVENT(EVENT_TYPE_EXPOSE, expose);
|
||
EVENT(EVENT_TYPE_MOTION_NOTIFY, motion_notify);
|
||
EVENT(EVENT_TYPE_ENTER_NOTIFY, enter_notify);
|
||
EVENT(EVENT_TYPE_FOCUS_IN, focus_in);
|
||
EVENT(EVENT_TYPE_PROPERTY_NOTIFY, property_notify);
|
||
EVENT(EVENT_TYPE_CLIENT_MESSAGE, client_message);
|
||
|
||
#undef EVENT
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void key_press(key_press_event_t *event) {
|
||
const keybinding_t *key = NULL;
|
||
for (int i = 0; i < LENGTH(keys); i++) {
|
||
key = &keys[i];
|
||
if (key->key_symbol == event->key_symbol &&
|
||
key->modifiers == event->modifiers && key->func) {
|
||
key->func(&key->arg);
|
||
}
|
||
}
|
||
}
|
||
|
||
void configure_request(configure_request_event_t *event) {
|
||
client_t *client = get_client_of_window(event->window);
|
||
if (!client) {
|
||
configure_window(event->window, event->x, event->y, event->width,
|
||
event->height, event->border_width);
|
||
} else {
|
||
configure_window(client->window, client->x, client->y, client->width,
|
||
client->height, client->border_width);
|
||
logger("configure request: %s\n", client->name);
|
||
}
|
||
|
||
flush_xcb_connection();
|
||
}
|
||
|
||
void map_request(map_request_event_t *event) {
|
||
if (!get_window_visible(event->window)) return;
|
||
if (get_client_of_window(event->window)) return;
|
||
if (event->parent != get_root_window()) return;
|
||
|
||
manage_window(event->window);
|
||
}
|
||
|
||
void unmap_notify(unmap_notify_event_t *event) {
|
||
client_t *c = get_client_of_window(event->window);
|
||
logger("unmap window: 0x%x, send event: %s\n", event->window,
|
||
event->send_event ? "true" : "false");
|
||
if (c) {
|
||
print_client(c);
|
||
if (event->send_event) {
|
||
set_window_state(event->window, wm_state_withdrawn);
|
||
} else {
|
||
unmanage_client(c, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
void destroy_notify(destroy_notify_event_t *event) {
|
||
client_t *c = get_client_of_window(event->window);
|
||
if (c) unmanage_client(c, true);
|
||
}
|
||
|
||
void expose(expose_event_t *event) {
|
||
monitor_t *m = NULL;
|
||
if (event->count == 0 && (m = get_monitor_of_window(event->window))) {
|
||
draw_monitor_bar(m);
|
||
}
|
||
}
|
||
|
||
void motion_notify(motion_notify_event_t *event) {
|
||
if (event->window != get_root_window()) return;
|
||
|
||
static monitor_t *mon = NULL;
|
||
monitor_t *m = rect_to_monitor(monitors, event->root_x, event->root_y, 1, 1);
|
||
if (mon && m != mon) {
|
||
unfocus(current_monitor->selected_client, true);
|
||
current_monitor = m;
|
||
focus(NULL);
|
||
}
|
||
mon = m;
|
||
}
|
||
|
||
void enter_notify(enter_notify_event_t *event) {
|
||
if ((event->mode != notify_mode_normal ||
|
||
event->detail == notify_detail_inferior) &&
|
||
event->window != get_root_window()) {
|
||
return;
|
||
}
|
||
client_t *c = get_client_of_window(event->window);
|
||
monitor_t *m = c ? c->monitor : get_monitor_of_window(event->window);
|
||
if (m != current_monitor) {
|
||
unfocus(current_monitor->selected_client, true);
|
||
current_monitor = m;
|
||
} else if (!c || c == current_monitor->selected_client) {
|
||
return;
|
||
}
|
||
focus(c);
|
||
}
|
||
|
||
void focus_in(focus_in_event_t *event) {
|
||
if (current_monitor->selected_client &&
|
||
event->window != current_monitor->selected_client->window) {
|
||
focus_window(event->window);
|
||
send_event(event->window, atom_wm_take_focus);
|
||
}
|
||
}
|
||
|
||
void property_notify(property_notify_event_t *event) {
|
||
logger("window: 0x%x state: %u, atom: %u\n", event->window, event->state,
|
||
event->xcb_atom);
|
||
if (event->state == property_delete) return;
|
||
|
||
client_t *c = get_client_of_window(event->window);
|
||
if (!c) return;
|
||
print_client(c);
|
||
|
||
switch (event->xcb_atom) {
|
||
case XCB_ATOM_WM_TRANSIENT_FOR: {
|
||
xcb_window_t transient_for;
|
||
if (!c->floating &&
|
||
((transient_for = get_transient_window_for(event->window)) !=
|
||
WINDOW_NONE) &&
|
||
(c->floating = (get_client_of_window(transient_for) != NULL))) {
|
||
arrange(c->monitor);
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
if (event->xcb_atom == XCB_ATOM_WM_NAME ||
|
||
event->xcb_atom == get_xcb_atom(atom_net_wm_name)) {
|
||
update_client_name(c);
|
||
if (c->tags & c->monitor->selected_tags) draw_monitor_bar(c->monitor);
|
||
}
|
||
}
|
||
|
||
void client_message(client_message_event_t *event) {
|
||
client_t *c = get_client_of_window(event->window);
|
||
if (!c) return;
|
||
|
||
/**
|
||
* _NET_WM_STATE_FULLSCREEN 协议数据格式为
|
||
* 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):切换全屏状态(若当前全屏则退出,反之进入)
|
||
*/
|
||
if (event->message_type == get_xcb_atom(atom_net_wm_state)) {
|
||
uint32_t fullscreen_atom = get_xcb_atom(atom_net_wm_fullscreen);
|
||
if (event->data.data32[1] == fullscreen_atom ||
|
||
event->data.data32[2] == fullscreen_atom) {
|
||
bool set_fullscreen = event->data.data32[0] == 1;
|
||
bool toggle_to_fullscreen = event->data.data32[0] == 2 && !c->fullscreen;
|
||
set_client_fullscreen(c, set_fullscreen || toggle_to_fullscreen);
|
||
}
|
||
} else if (event->message_type == get_xcb_atom(atom_net_active_window)) {
|
||
client_t *client = get_client_of_window(event->window);
|
||
if (client) {
|
||
logger("==== _NET_ACTIVE_WINDOW\n");
|
||
print_client(client);
|
||
|
||
uint32_t tag_mask = 0;
|
||
for (uint32_t i = 0; i < g_strv_length(client->monitor->tags); i++) {
|
||
tag_mask |= 1 << i;
|
||
}
|
||
uint32_t target_tag = tag_mask & client->tags;
|
||
const user_action_arg_t arg = {.ui = target_tag};
|
||
|
||
current_monitor = client->monitor;
|
||
select_tag(&arg);
|
||
focus(client);
|
||
restack(current_monitor);
|
||
}
|
||
}
|
||
}
|
||
|
||
void manage_window(xcb_window_t window) {
|
||
window_geometry_t *geometry = get_window_geometry(window);
|
||
client_t *c = ecalloc(1, sizeof(client_t));
|
||
c->window = window;
|
||
c->x = c->old_x = geometry->x;
|
||
c->y = c->old_y = geometry->y;
|
||
c->width = c->old_width = geometry->width;
|
||
c->height = c->old_height = geometry->height;
|
||
c->old_border_width = geometry->border_width;
|
||
c->border_width = border_px;
|
||
update_client_name(c);
|
||
|
||
logger("== manage window: 0x%x\n", window);
|
||
logger("== x: %d, y: %d, w: %u, h: %u, bw: %u\n", c->x, c->y, c->width,
|
||
c->height, c->border_width);
|
||
|
||
xcb_window_t tw = get_transient_window_for(window);
|
||
client_t *tc = NULL;
|
||
if (tw != WINDOW_NONE && (tc = get_client_of_window(tw))) {
|
||
c->monitor = tc->monitor;
|
||
c->tags = tc->tags;
|
||
} else {
|
||
c->monitor = current_monitor;
|
||
apply_rules(c);
|
||
}
|
||
|
||
set_client_tag_prop(c);
|
||
|
||
set_window_event_mask(window, false);
|
||
change_window_border_color(window, color_set->border_color.argb);
|
||
|
||
attach_client(c);
|
||
attach_stack_client(c);
|
||
update_client_list();
|
||
set_window_state(window, wm_state_normal);
|
||
|
||
if (c->monitor == current_monitor) {
|
||
unfocus(current_monitor->selected_client, false);
|
||
}
|
||
c->monitor->selected_client = c;
|
||
arrange(c->monitor);
|
||
map_window(window, false);
|
||
focus(NULL);
|
||
}
|
||
|
||
void unmanage_client(client_t *client, bool destroyed) {
|
||
monitor_t *monitor = client->monitor;
|
||
|
||
detach_client(client);
|
||
detach_stack_client(client);
|
||
if (!destroyed) {
|
||
set_window_event_mask(client->window, true);
|
||
change_window_border_width(client->window, client->old_border_width);
|
||
set_window_state(client->window, wm_state_withdrawn);
|
||
flush_xcb_connection();
|
||
}
|
||
free(client);
|
||
focus(NULL);
|
||
update_client_list();
|
||
arrange(monitor);
|
||
}
|
||
|
||
void update_client_list(void) {
|
||
uint32_t length = 0;
|
||
for (monitor_t *m = monitors; m; m = m->next) {
|
||
for (client_t *c = m->clients; c; c = c->next) length++;
|
||
}
|
||
if (!length) {
|
||
set_window_list(NULL, length);
|
||
return;
|
||
}
|
||
|
||
xcb_window_t *list = ecalloc(length, sizeof(xcb_window_t));
|
||
uint32_t i = 0;
|
||
for (monitor_t *m = monitors; m; m = m->next) {
|
||
for (client_t *c = m->clients; c; c = c->next) list[i++] = c->window;
|
||
}
|
||
set_window_list(list, length);
|
||
free(list);
|
||
}
|
||
|
||
void update_client_name(client_t *client) {
|
||
char *name = get_window_name(client->window);
|
||
strncpy(client->name, name, sizeof(client->name) - 1);
|
||
free(name);
|
||
}
|
||
|
||
void apply_rules(client_t *client) {
|
||
uint32_t monitor_amount = 0;
|
||
for (monitor_t *m = monitors; m; m = m->next) monitor_amount++;
|
||
|
||
window_class_instance_t class_instance;
|
||
get_window_class_instance(client->window, &class_instance);
|
||
bool switch_to_tag = 0;
|
||
for (int i = 0; i < LENGTH(rules); i++) {
|
||
const rule_t *r = &rules[i];
|
||
if (!((!r->title || strstr(client->name, r->title)) &&
|
||
(!r->class || strstr(class_instance.class, r->class)) &&
|
||
(!r->instance || strstr(class_instance.instance, r->instance)))) {
|
||
continue;
|
||
}
|
||
|
||
print_rule(r);
|
||
|
||
switch_to_tag = r->action.switch_to_tag;
|
||
client->fullscreen = r->action.fullscreen;
|
||
client->maximize = r->action.maximize;
|
||
client->floating = r->action.floating;
|
||
|
||
if (!r->tag) continue;
|
||
|
||
const rule_tag_t *tag = NULL;
|
||
for (uint32_t i = 0;; i++) {
|
||
const rule_tag_t *t = &r->tag[i];
|
||
if (!t->tags) break;
|
||
|
||
if (!t->monitor_amount || t->monitor_amount == monitor_amount) {
|
||
tag = t;
|
||
}
|
||
}
|
||
if (tag) {
|
||
monitor_t *m = monitors;
|
||
for (uint32_t i = 0; i < tag->monitor_index; i++) m = m ? m->next : NULL;
|
||
if (!m) continue;
|
||
|
||
uint32_t tags_mask = get_tags_mask_of_monitor(m);
|
||
client->tags = tag->tags & tags_mask;
|
||
client->monitor = m;
|
||
}
|
||
}
|
||
|
||
if (!client->tags && !restore_client_tag(client)) {
|
||
client->monitor = current_monitor;
|
||
client->tags = current_monitor->selected_tags;
|
||
}
|
||
if (switch_to_tag) {
|
||
current_monitor = client->monitor;
|
||
current_monitor->selected_tags = client->tags;
|
||
}
|
||
|
||
print_client(client);
|
||
logger("apply rules done\n");
|
||
}
|
||
|
||
uint32_t get_tags_mask_of_monitor(monitor_t *monitor) {
|
||
uint32_t tags_mask = 0;
|
||
for (uint32_t i = 0; monitor->tags[i]; i++) tags_mask |= (1 << i);
|
||
return tags_mask;
|
||
}
|
||
|
||
void attach_client(client_t *client) {
|
||
client->next = client->monitor->clients;
|
||
client->monitor->clients = client;
|
||
}
|
||
|
||
void detach_client(client_t *client) {
|
||
client_t **c = &client->monitor->clients;
|
||
for (; *c && *c != client; c = &(*c)->next);
|
||
*c = client->next;
|
||
}
|
||
|
||
void attach_stack_client(client_t *client) {
|
||
client->stack_next = client->monitor->clients_stack;
|
||
client->monitor->clients_stack = client;
|
||
}
|
||
|
||
void detach_stack_client(client_t *client) {
|
||
client_t **tc = &client->monitor->clients_stack;
|
||
for (; *tc && *tc != client; tc = &(*tc)->stack_next);
|
||
*tc = client->stack_next;
|
||
|
||
if (client == client->monitor->selected_client) {
|
||
client_t *t = client->monitor->clients_stack;
|
||
for (; t && !CLIENT_VISIBLE(t); t = t->stack_next);
|
||
client->monitor->selected_client = t;
|
||
}
|
||
}
|
||
|
||
void arrange(monitor_t *monitor) {
|
||
if (monitor) {
|
||
show_hide_client(monitor->clients_stack);
|
||
apply_monitor_layout(monitor);
|
||
restack(monitor);
|
||
} else {
|
||
for (monitor = monitors; monitor; monitor = monitor->next) {
|
||
show_hide_client(monitor->clients_stack);
|
||
apply_monitor_layout(monitor);
|
||
}
|
||
}
|
||
flush_xcb_connection();
|
||
}
|
||
|
||
void restack(monitor_t *monitor) {
|
||
draw_monitor_bar(monitor);
|
||
if (!monitor->selected_client) return;
|
||
if (monitor->selected_client->floating || !monitor->layout->arrange) {
|
||
raise_window(monitor->selected_client->window);
|
||
}
|
||
if (monitor->layout->arrange) {
|
||
xcb_window_t sibling = monitor->bar_window;
|
||
for (client_t *c = monitor->clients_stack; c; c = c->stack_next) {
|
||
if (!CLIENT_VISIBLE(c)) continue;
|
||
if (c->fullscreen) {
|
||
place_window_above_sibling(c->window, monitor->bar_window);
|
||
} else if (!c->floating) {
|
||
place_window_below_sibling(c->window, sibling);
|
||
sibling = c->window;
|
||
}
|
||
}
|
||
}
|
||
flush_xcb_connection();
|
||
while (xcb_check_mask_event(XCB_EVENT_MASK_ENTER_WINDOW));
|
||
}
|
||
void focus(client_t *client) {
|
||
if (!client || !CLIENT_VISIBLE(client)) {
|
||
for (client = current_monitor->clients_stack;
|
||
client && !CLIENT_VISIBLE(client); client = client->stack_next);
|
||
}
|
||
if (current_monitor->selected_client &&
|
||
current_monitor->selected_client != client) {
|
||
unfocus(current_monitor->selected_client, false);
|
||
}
|
||
if (client) {
|
||
if (client->monitor != current_monitor) current_monitor = client->monitor;
|
||
detach_stack_client(client);
|
||
attach_stack_client(client);
|
||
change_window_border_color(client->window,
|
||
color_set->active_border_color.argb);
|
||
focus_window(client->window);
|
||
} else {
|
||
focus_window(WINDOW_NONE);
|
||
}
|
||
|
||
current_monitor->selected_client = client;
|
||
draw_all_monitor_bars();
|
||
}
|
||
void unfocus(client_t *client, bool set_focus) {
|
||
if (!client) return;
|
||
|
||
change_window_border_color(client->window, color_set->border_color.argb);
|
||
if (set_focus) focus_window(WINDOW_NONE);
|
||
}
|
||
|
||
void set_client_fullscreen(client_t *client, bool fullscreen) {
|
||
set_window_fullscreen_state(client->window, true);
|
||
client->fullscreen = fullscreen;
|
||
if (fullscreen) {
|
||
fullscreen_client(client);
|
||
configure_window(client->window, client->x, client->y, client->width,
|
||
client->height, client->border_width);
|
||
raise_window(client->window);
|
||
} else {
|
||
client->x = client->old_x;
|
||
client->y = client->old_y;
|
||
client->width = client->old_width;
|
||
client->height = client->old_height;
|
||
client->border_width = client->old_border_width;
|
||
configure_window(client->window, client->x, client->y, client->width,
|
||
client->height, client->border_width);
|
||
arrange(client->monitor);
|
||
}
|
||
}
|
||
|
||
void show_hide_client(client_t *client) {
|
||
if (!client) return;
|
||
|
||
if (CLIENT_VISIBLE(client)) {
|
||
if (client->fullscreen) {
|
||
fullscreen_client(client);
|
||
resize_window(client->window, client->width, client->height);
|
||
} else if (client->maximize) {
|
||
maximize_client(client);
|
||
resize_window(client->window, client->width, client->height);
|
||
} else if (!client->monitor->layout->arrange || client->floating) {
|
||
client->x = client->old_x;
|
||
client->y = client->old_y;
|
||
}
|
||
move_window(client->window, client->x, client->y);
|
||
show_hide_client(client->stack_next);
|
||
} else {
|
||
if (!client->monitor->layout->arrange || client->floating) {
|
||
client->old_x = client->x;
|
||
client->old_y = client->y;
|
||
}
|
||
client->x = client->monitor->monitor_x;
|
||
client->y = client->monitor->monitor_y + client->monitor->monitor_height;
|
||
show_hide_client(client->stack_next);
|
||
move_window(client->window, client->x, client->y);
|
||
}
|
||
}
|
||
|
||
void apply_monitor_layout(monitor_t *monitor) {
|
||
if (!monitor || !monitor->layout || !monitor->layout->arrange) return;
|
||
|
||
monitor->layout->arrange(monitor);
|
||
for (client_t *c = next_visible_client(monitor->clients); c;
|
||
c = next_visible_client(c->next)) {
|
||
configure_window(c->window, c->x, c->y, c->width, c->height,
|
||
c->border_width);
|
||
}
|
||
}
|
||
|
||
client_t *get_client_of_window(xcb_window_t window) {
|
||
for (monitor_t *m = monitors; m; m = m->next) {
|
||
for (client_t *c = m->clients; c; c = c->next) {
|
||
if (c->window == window) return c;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
monitor_t *get_monitor_of_window(xcb_window_t window) {
|
||
int32_t x, y;
|
||
if (window == get_root_window() && get_pointer_position(&x, &y)) {
|
||
return rect_to_monitor(monitors, x, y, 1, 1);
|
||
}
|
||
|
||
for (monitor_t *m = monitors; m; m = m->next) {
|
||
if (window == m->bar_window) return m;
|
||
}
|
||
|
||
client_t *c = get_client_of_window(window);
|
||
if (c) return c->monitor;
|
||
|
||
return current_monitor;
|
||
}
|
||
|
||
monitor_t *get_monitor_by_direction(bool forward) {
|
||
monitor_t *m = NULL;
|
||
if (forward) {
|
||
if (!(m = current_monitor->next)) m = monitors;
|
||
} else if (current_monitor == monitors) {
|
||
for (m = monitors; m->next; m = m->next);
|
||
} else {
|
||
for (m = monitors; m->next != current_monitor; m = m->next);
|
||
}
|
||
return m;
|
||
}
|
||
|
||
void send_client_to_monitor(client_t *client, monitor_t *monitor) {
|
||
if (client->monitor == monitor) return;
|
||
|
||
unfocus(client, true);
|
||
detach_client(client);
|
||
detach_stack_client(client);
|
||
client->monitor = monitor;
|
||
client->tags = monitor->selected_tags;
|
||
attach_client(client);
|
||
attach_stack_client(client);
|
||
set_client_tag_prop(client);
|
||
focus(client);
|
||
arrange(NULL);
|
||
}
|
||
|
||
void set_client_tag_prop(client_t *client) {
|
||
client_tag_info_t info = {
|
||
.tags = client->tags,
|
||
.monitor_index = client->monitor->index,
|
||
};
|
||
set_window_tag(client->window, &info);
|
||
}
|
||
|
||
/**
|
||
* @brief 恢复 client 的 tag 信息
|
||
* @returns 成功返回 true 否则返回 false
|
||
*/
|
||
bool restore_client_tag(client_t *client) {
|
||
client_tag_info_t client_tag_info;
|
||
if (!get_window_tag(client->window, &client_tag_info)) return false;
|
||
|
||
for (monitor_t *m = monitors; m; m = m->next) {
|
||
if (client_tag_info.monitor_index == m->index) {
|
||
uint32_t tag_mask = 0;
|
||
for (uint32_t i = 0; i < g_strv_length(m->tags); i++) tag_mask |= 1 << i;
|
||
uint32_t target_tag = tag_mask & client_tag_info.tags;
|
||
if (target_tag) {
|
||
client->monitor = m;
|
||
client->tags = target_tag;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
client_t *get_next_client_with_class(const char *class) {
|
||
monitor_t *m = NULL;
|
||
client_t *c = NULL;
|
||
window_class_instance_t class_instance;
|
||
|
||
for (m = current_monitor; m; m = m->next) {
|
||
if (m == current_monitor && m->selected_client) {
|
||
c = m->selected_client->next;
|
||
} else {
|
||
c = m->clients;
|
||
}
|
||
for (; c; c = c->next) {
|
||
get_window_class_instance(c->window, &class_instance);
|
||
if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) {
|
||
return c;
|
||
}
|
||
}
|
||
}
|
||
|
||
for (m = monitors; m && m != current_monitor; m = m->next) {
|
||
for (c = m->clients; c; c = c->next) {
|
||
get_window_class_instance(c->window, &class_instance);
|
||
if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) {
|
||
return c;
|
||
}
|
||
}
|
||
}
|
||
|
||
m = current_monitor;
|
||
for (c = m->clients; c && m->selected_client && c != m->selected_client;
|
||
c = c->next) {
|
||
get_window_class_instance(c->window, &class_instance);
|
||
if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) {
|
||
return c;
|
||
}
|
||
}
|
||
|
||
if (current_monitor->selected_client) {
|
||
get_window_class_instance(c->window, &class_instance);
|
||
if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) {
|
||
return c;
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
|
||
gpointer userdata) {
|
||
if (condition & (G_IO_HUP | G_IO_ERR)) {
|
||
g_main_loop_quit(loop);
|
||
return false;
|
||
}
|
||
|
||
event_adapter_poll(adapter);
|
||
return true;
|
||
}
|
||
|
||
void cleanup(bool will_restart) {
|
||
clean_status();
|
||
|
||
g_main_loop_unref(loop);
|
||
loop = NULL;
|
||
|
||
monitor_t *m = monitors;
|
||
client_t *c = m->clients;
|
||
while (m) {
|
||
while (c) {
|
||
if (!will_restart) remove_window_tag(c->window);
|
||
client_t *tc = c->next;
|
||
unmanage_client(c, false);
|
||
c = tc;
|
||
}
|
||
monitor_t *next_monitor = m->next;
|
||
if (m->cr) cairo_destroy(m->cr);
|
||
destroy_window(m->bar_window);
|
||
g_strfreev(m->tags);
|
||
free(m);
|
||
m = next_monitor;
|
||
}
|
||
monitors = NULL;
|
||
|
||
clean_pango_context();
|
||
|
||
free_wallpaper_config(wallpaper_config);
|
||
cairo_destroy(wallpaper_cr);
|
||
wallpaper_config = NULL;
|
||
wallpaper_cr = NULL;
|
||
|
||
free(color_set);
|
||
color_set = NULL;
|
||
|
||
clean_icons();
|
||
|
||
free(font_family);
|
||
font_family = NULL;
|
||
|
||
destroy_event_adapter(adapter);
|
||
adapter = NULL;
|
||
|
||
clean_xcb();
|
||
}
|
||
|
||
int main(int argc, char *argv[]) {
|
||
if (has_other_wm_running()) {
|
||
die("another window manager is already running");
|
||
}
|
||
|
||
loop = g_main_loop_new(NULL, FALSE);
|
||
|
||
logger("========================== ZSWM START ==========================\n");
|
||
setup_xcb();
|
||
init_monitors();
|
||
init_color_set();
|
||
init_monitor_bar();
|
||
change_window_cursor(get_root_window(), cursor_normal);
|
||
init_wallpaper();
|
||
init_icons();
|
||
init_status(g_main_loop_get_context(loop), update_status);
|
||
|
||
draw_all_monitor_bars();
|
||
|
||
window_list_t *window_list = scan_window_list();
|
||
|
||
print_monitor_list_info(monitors);
|
||
print_color_set(color_set);
|
||
print_wallpaper_config(wallpaper_config);
|
||
print_window_list(window_list);
|
||
|
||
if (window_list) {
|
||
for (uint32_t i = 0; i < window_list->normal_count; i++) {
|
||
manage_window(window_list->normal_list[i]);
|
||
}
|
||
for (uint32_t i = 0; i < window_list->transient_count; i++) {
|
||
manage_window(window_list->transient_list[i]);
|
||
}
|
||
}
|
||
|
||
free_window_list(window_list);
|
||
|
||
grab_keybindings(keys, LENGTH(keys));
|
||
|
||
init_xcb_event_loop();
|
||
|
||
g_main_loop_run(loop);
|
||
|
||
cleanup(need_restart);
|
||
|
||
if (need_restart) {
|
||
need_restart = false;
|
||
execvp(argv[0], argv);
|
||
}
|
||
|
||
return 0;
|
||
}
|