Files
zswm/src/main.c

1207 lines
38 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 "config.h"
#include "event-adapter.h"
#include "layout.h"
#include "renderer.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_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 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 gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
gpointer userdata);
static void cleanup(void);
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 color_set_t *color_set = 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 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];
const char *command = ((const char **)arg->ptr)[1];
user_action_arg_t argument;
for (monitor_t *m = monitors; m; m = m->next) {
for (client_t *c = m->clients; c; c = c->next) {
window_class_instance_t class_instance;
get_window_class_instance(c->window, &class_instance);
if (!strncmp(class, class_instance.class, sizeof(class_instance.class))) {
current_monitor = m;
argument.ui = c->tags;
select_tag(&argument);
focus(c);
raise_window(c->window);
return;
}
}
}
argument.ptr = command;
spawn(&argument);
}
/* 调试函数,开发完成后删除 */
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 color:", &set->status_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);
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_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 = 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", &status, status_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, &color_set->status_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);
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.border_width", &border_px);
logger("dpi: %u, font family: %s, font size: %u\n", dpi, font_family,
font_size);
clean_xresource();
}
void draw_all_monitor_bars(void) {
for (monitor_t *m = monitors; m; m = m->next) draw_monitor_bar(m);
}
void draw_monitor_bar(monitor_t *m) {
draw_bar(m, m == current_monitor, color_set, bar_height, tag_x_pad,
layout_symbol_x_pad, client_name_x_pad);
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_ADD1添加全屏状态窗口进入全屏模式
* _NET_WM_STATE_REMOVE0移除全屏状态窗口退出全屏模式
* _NET_WM_STATE_TOGGLE2切换全屏状态若当前全屏则退出反之进入
*/
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);
}
}
}
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_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);
configure_window(client->window, client->x, client->y, client->width,
client->height, 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) {
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);
}
}
}
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);
} else if (client->maximize) {
maximize_client(client);
} else if (!client->monitor->layout->arrange || client->floating) {
client->x = client->old_x;
client->y = client->old_y;
}
configure_window(client->window, client->x, client->y, client->width,
client->height, client->border_width);
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);
configure_window(client->window, client->x, client->y, client->window,
client->height, client->border_width);
}
}
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;
}
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(void) {
g_main_loop_unref(loop);
loop = NULL;
monitor_t *m = monitors;
client_t *c = m->clients;
while (m) {
while (c) {
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;
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");
}
setup_xcb();
init_monitors();
init_color_set();
init_monitor_bar();
change_window_cursor(get_root_window(), cursor_normal);
init_wallpaper();
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();
loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(loop);
cleanup();
if (need_restart) {
need_restart = false;
execvp(argv[0], argv);
}
return 0;
}