处理焦点相关事件
This commit is contained in:
@@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.24)
|
|||||||
set(APP_NAME "zswm")
|
set(APP_NAME "zswm")
|
||||||
set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
add_compile_definitions(LOG_FILE="$ENV{HOME}/zswm-log.txt")
|
||||||
|
# add_compile_definitions(RELEASE)
|
||||||
|
|
||||||
# generate compile_commands.json for clangd
|
# generate compile_commands.json for clangd
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
@@ -21,6 +22,24 @@ typedef enum event_type_t {
|
|||||||
EVENT_TYPE_CLIENT_MESSAGE,
|
EVENT_TYPE_CLIENT_MESSAGE,
|
||||||
} event_type_t;
|
} event_type_t;
|
||||||
|
|
||||||
|
typedef enum notify_mode_t {
|
||||||
|
notify_mode_normal = 0,
|
||||||
|
notify_mode_grab = 1,
|
||||||
|
notify_mode_ungrab = 2,
|
||||||
|
notify_mode_while_grabbed = 3
|
||||||
|
} notify_mode_t;
|
||||||
|
|
||||||
|
typedef enum notify_detail_t {
|
||||||
|
notify_detail_ancestor = 0,
|
||||||
|
notify_detail_virtual = 1,
|
||||||
|
notify_detail_inferior = 2,
|
||||||
|
notify_detail_nonlinear = 3,
|
||||||
|
notify_detail_nonlinear_virtual = 4,
|
||||||
|
notify_detail_pointer = 5,
|
||||||
|
notify_detail_pointer_root = 6,
|
||||||
|
notify_detail_none = 7,
|
||||||
|
} notify_detail_t;
|
||||||
|
|
||||||
typedef struct mapping_notify_event_t {
|
typedef struct mapping_notify_event_t {
|
||||||
event_type_t type;
|
event_type_t type;
|
||||||
} mapping_notify_event_t;
|
} mapping_notify_event_t;
|
||||||
@@ -81,10 +100,14 @@ typedef struct motion_notify_event_t {
|
|||||||
|
|
||||||
typedef struct enter_notify_event_t {
|
typedef struct enter_notify_event_t {
|
||||||
event_type_t type;
|
event_type_t type;
|
||||||
|
xcb_window_t window;
|
||||||
|
notify_mode_t mode;
|
||||||
|
notify_detail_t detail;
|
||||||
} enter_notify_event_t;
|
} enter_notify_event_t;
|
||||||
|
|
||||||
typedef struct focus_in_event_t {
|
typedef struct focus_in_event_t {
|
||||||
event_type_t type;
|
event_type_t type;
|
||||||
|
xcb_window_t window;
|
||||||
} focus_in_event_t;
|
} focus_in_event_t;
|
||||||
|
|
||||||
typedef struct property_notify_event_t {
|
typedef struct property_notify_event_t {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
@@ -18,6 +19,14 @@ void *ecalloc(size_t number_of_member, size_t member_size);
|
|||||||
|
|
||||||
bool file_exist(const char *path);
|
bool file_exist(const char *path);
|
||||||
|
|
||||||
|
#if defined(RELEASE)
|
||||||
|
#define logger(fmt, ...)
|
||||||
|
#elif defined(LOG_FILE)
|
||||||
|
void logger(const char *fmt, ...);
|
||||||
|
#else
|
||||||
|
#define logger(fmt, ...) printf(fmt, ##__VA_ARGS__)
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 将 16 进制的颜色配置转换成 uint32 格式的 rgba数据
|
* @brief 将 16 进制的颜色配置转换成 uint32 格式的 rgba数据
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ void map_window(xcb_window_t window);
|
|||||||
char *get_window_name(xcb_window_t window);
|
char *get_window_name(xcb_window_t window);
|
||||||
void init_window_event_mask(xcb_window_t window);
|
void init_window_event_mask(xcb_window_t window);
|
||||||
void change_window_border_color(xcb_window_t window, uint32_t argb);
|
void change_window_border_color(xcb_window_t window, uint32_t argb);
|
||||||
|
void focus_window(xcb_window_t window);
|
||||||
|
|
||||||
typedef struct bar_cairo_params_t {
|
typedef struct bar_cairo_params_t {
|
||||||
xcb_connection_t *conn;
|
xcb_connection_t *conn;
|
||||||
@@ -95,3 +96,12 @@ void grab_keybindings(const keybinding_t *keys, const size_t length);
|
|||||||
bool get_xresource_uint32(const char *name, uint32_t *value);
|
bool get_xresource_uint32(const char *name, uint32_t *value);
|
||||||
void get_xresource_string(const char *name, char **value, const char *fallback);
|
void get_xresource_string(const char *name, char **value, const char *fallback);
|
||||||
void clean_xresource(void);
|
void clean_xresource(void);
|
||||||
|
|
||||||
|
typedef enum atom_t {
|
||||||
|
atom_wm_protocols,
|
||||||
|
atom_wm_delete,
|
||||||
|
atom_wm_take_focus,
|
||||||
|
} atom_t;
|
||||||
|
bool send_event(xcb_window_t window, atom_t atom);
|
||||||
|
|
||||||
|
bool get_pointer_position(int32_t *x, int32_t *y);
|
||||||
|
|||||||
142
src/main.c
142
src/main.c
@@ -40,6 +40,8 @@ static void configure_request(configure_request_event_t *event);
|
|||||||
static void map_request(map_request_event_t *event);
|
static void map_request(map_request_event_t *event);
|
||||||
static void unmap_notify(unmap_notify_event_t *event);
|
static void unmap_notify(unmap_notify_event_t *event);
|
||||||
static void motion_notify(motion_notify_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 manage_window(xcb_window_t window);
|
static void manage_window(xcb_window_t window);
|
||||||
static void unmanage_client(client_t *client);
|
static void unmanage_client(client_t *client);
|
||||||
static void update_client_name(client_t *client);
|
static void update_client_name(client_t *client);
|
||||||
@@ -56,6 +58,7 @@ static void focus(client_t *client);
|
|||||||
static void unfocus(client_t *client, bool set_focus);
|
static void unfocus(client_t *client, bool set_focus);
|
||||||
static void apply_monitor_layout(monitor_t *monitor);
|
static void apply_monitor_layout(monitor_t *monitor);
|
||||||
static client_t *get_client_of_window(xcb_window_t window);
|
static client_t *get_client_of_window(xcb_window_t window);
|
||||||
|
static monitor_t *get_monitor_of_window(xcb_window_t window);
|
||||||
static gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
|
static gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
|
||||||
gpointer userdata);
|
gpointer userdata);
|
||||||
static void cleanup(void);
|
static void cleanup(void);
|
||||||
@@ -96,22 +99,22 @@ void select_tag(const user_action_arg_t *arg) {
|
|||||||
|
|
||||||
/* 调试函数,开发完成后删除 */
|
/* 调试函数,开发完成后删除 */
|
||||||
static void print_monitor_list_info(monitor_t *monitor_list) {
|
static void print_monitor_list_info(monitor_t *monitor_list) {
|
||||||
printf("========================= monitor info =========================\n");
|
logger("========================= monitor info =========================\n");
|
||||||
for (monitor_t *m = monitor_list; m; m = m->next) {
|
for (monitor_t *m = monitor_list; m; m = m->next) {
|
||||||
printf("========== mon[%u]:\n", m->index);
|
logger("========== mon[%u]:\n", m->index);
|
||||||
printf("x: %d, y: %d, width: %u, height: %u\n", m->monitor_x, m->monitor_y,
|
logger("x: %d, y: %d, width: %u, height: %u\n", m->monitor_x, m->monitor_y,
|
||||||
m->monitor_width, m->monitor_height);
|
m->monitor_width, m->monitor_height);
|
||||||
printf("mon[%u] tags: ", m->index);
|
logger("mon[%u] tags: ", m->index);
|
||||||
for (int i = 0; m->tags[i]; i++) {
|
for (int i = 0; m->tags[i]; i++) {
|
||||||
printf("%s%s", i > 0 ? ", " : "", m->tags[i]);
|
logger("%s%s", i > 0 ? ", " : "", m->tags[i]);
|
||||||
}
|
}
|
||||||
printf("\n");
|
logger("\n");
|
||||||
}
|
}
|
||||||
printf("======================= monitor info end =======================\n");
|
logger("======================= monitor info end =======================\n");
|
||||||
}
|
}
|
||||||
static void print_color(const char *prompt, const color_t *color) {
|
static void print_color(const char *prompt, const color_t *color) {
|
||||||
const char *p = strlen(prompt) ? " " : "";
|
const char *p = strlen(prompt) ? " " : "";
|
||||||
printf("%s%srgba: #%08x, argb: #%08x\n", prompt, p, color->rgba, color->argb);
|
logger("%s%srgba: #%08x, argb: #%08x\n", prompt, p, color->rgba, color->argb);
|
||||||
}
|
}
|
||||||
static void print_color_set(color_set_t *set) {
|
static void print_color_set(color_set_t *set) {
|
||||||
print_color("== bar bg:", &set->bar_bg);
|
print_color("== bar bg:", &set->bar_bg);
|
||||||
@@ -128,54 +131,54 @@ static void print_color_set(color_set_t *set) {
|
|||||||
}
|
}
|
||||||
static void print_wallpaper_config(wallpaper_config_t *config) {
|
static void print_wallpaper_config(wallpaper_config_t *config) {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
printf("no wallpaper\n");
|
logger("no wallpaper\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("wallpaper count: %u\n", config->count);
|
logger("wallpaper count: %u\n", config->count);
|
||||||
for (uint32_t i = 0; i < config->count; i++) {
|
for (uint32_t i = 0; i < config->count; i++) {
|
||||||
printf("wallpaper[%02u]: %s\n", i, config->path_list[i]);
|
logger("wallpaper[%02u]: %s\n", i, config->path_list[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static void print_window_list(window_list_t *window_list) {
|
static void print_window_list(window_list_t *window_list) {
|
||||||
if (!window_list) {
|
if (!window_list) {
|
||||||
printf("no window\n");
|
logger("no window\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < window_list->count; i++) {
|
for (uint32_t i = 0; i < window_list->count; i++) {
|
||||||
printf("window[%u]: 0x%x\n", i, window_list->list[i]);
|
logger("window[%u]: 0x%x\n", i, window_list->list[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static char *bool2string(bool b) { return b ? "true" : "false"; }
|
static char *bool2string(bool b) { return b ? "true" : "false"; }
|
||||||
static void print_client(client_t *c) {
|
static void print_client(client_t *c) {
|
||||||
if (!c) return;
|
if (!c) return;
|
||||||
|
|
||||||
printf("======= client info ========\n");
|
logger("======= client info ========\n");
|
||||||
printf("window: 0x%x, x: %d, y: %d, w: %u, h: %u, bw: %u\n", c->window, c->x,
|
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);
|
c->y, c->width, c->height, c->border_width);
|
||||||
printf("floating: %s, fullscreen: %s, maximize: %s\n",
|
logger("floating: %s, fullscreen: %s, maximize: %s\n",
|
||||||
bool2string(c->floating), bool2string(c->fullscreen),
|
bool2string(c->floating), bool2string(c->fullscreen),
|
||||||
bool2string(c->maximize));
|
bool2string(c->maximize));
|
||||||
printf("tags: %b, monitor selected tags: %b\n", c->tags,
|
logger("tags: %b, monitor selected tags: %b\n", c->tags,
|
||||||
c->monitor->selected_tags);
|
c->monitor->selected_tags);
|
||||||
printf("name: %s\n", c->name);
|
logger("name: %s\n", c->name);
|
||||||
printf("===== client info end ======\n");
|
logger("===== client info end ======\n");
|
||||||
}
|
}
|
||||||
static void print_rule(const rule_t *r) {
|
static void print_rule(const rule_t *r) {
|
||||||
printf("==================== rule ====================\n");
|
logger("==================== rule ====================\n");
|
||||||
printf("class: %s, instance: %s, title: %s\n", r->class, r->instance,
|
logger("class: %s, instance: %s, title: %s\n", r->class, r->instance,
|
||||||
r->title);
|
r->title);
|
||||||
printf("floating: %s, maximize: %s, fullscreen: %s, switch to tag: %s\n",
|
logger("floating: %s, maximize: %s, fullscreen: %s, switch to tag: %s\n",
|
||||||
bool2string(r->action.floating), bool2string(r->action.maximize),
|
bool2string(r->action.floating), bool2string(r->action.maximize),
|
||||||
bool2string(r->action.fullscreen),
|
bool2string(r->action.fullscreen),
|
||||||
bool2string(r->action.switch_to_tag));
|
bool2string(r->action.switch_to_tag));
|
||||||
for (uint32_t i = 0; r->tag && r->tag[i].tags; i++) {
|
for (uint32_t i = 0; r->tag && r->tag[i].tags; i++) {
|
||||||
const rule_tag_t *t = &r->tag[i];
|
const rule_tag_t *t = &r->tag[i];
|
||||||
printf("monitor amount: %u, monitor index: %u, tags: %b\n",
|
logger("monitor amount: %u, monitor index: %u, tags: %b\n",
|
||||||
t->monitor_amount, t->monitor_index, t->tags);
|
t->monitor_amount, t->monitor_index, t->tags);
|
||||||
}
|
}
|
||||||
printf("================== rule end ==================\n");
|
logger("================== rule end ==================\n");
|
||||||
}
|
}
|
||||||
/* 调试函数结束 */
|
/* 调试函数结束 */
|
||||||
|
|
||||||
@@ -279,7 +282,7 @@ void set_wallpaper_by_index(uint32_t index, wallpaper_config_t *config) {
|
|||||||
set_wallpaper(wallpaper);
|
set_wallpaper(wallpaper);
|
||||||
double end = get_time();
|
double end = get_time();
|
||||||
free(wallpaper);
|
free(wallpaper);
|
||||||
printf("set wallpaper cost: %fs\n", end - start);
|
logger("set wallpaper cost: %fs\n", end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(screen_size);
|
free(screen_size);
|
||||||
@@ -413,7 +416,7 @@ void init_monitor_bar(void) {
|
|||||||
|
|
||||||
m->cr = cr;
|
m->cr = cr;
|
||||||
}
|
}
|
||||||
printf("font height: %u, bar height: %u\n", font_height, bar_height);
|
logger("font height: %u, bar height: %u\n", font_height, bar_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void get_xresource_config(void) {
|
void get_xresource_config(void) {
|
||||||
@@ -424,7 +427,7 @@ void get_xresource_config(void) {
|
|||||||
get_xresource_uint32("zswm.tag_x_padding", &tag_x_pad);
|
get_xresource_uint32("zswm.tag_x_padding", &tag_x_pad);
|
||||||
get_xresource_uint32("zswm.border_width", &border_px);
|
get_xresource_uint32("zswm.border_width", &border_px);
|
||||||
|
|
||||||
printf("dpi: %u, font family: %s, font size: %u\n", dpi, font_family,
|
logger("dpi: %u, font family: %s, font size: %u\n", dpi, font_family,
|
||||||
font_size);
|
font_size);
|
||||||
clean_xresource();
|
clean_xresource();
|
||||||
}
|
}
|
||||||
@@ -460,6 +463,8 @@ void xcb_event_handler(generic_event_t *event, void *user_data) {
|
|||||||
EVENT(EVENT_TYPE_MAP_REQUEST, map_request);
|
EVENT(EVENT_TYPE_MAP_REQUEST, map_request);
|
||||||
EVENT(EVENT_TYPE_UNMAP_NOTIFY, unmap_notify);
|
EVENT(EVENT_TYPE_UNMAP_NOTIFY, unmap_notify);
|
||||||
EVENT(EVENT_TYPE_MOTION_NOTIFY, motion_notify);
|
EVENT(EVENT_TYPE_MOTION_NOTIFY, motion_notify);
|
||||||
|
EVENT(EVENT_TYPE_ENTER_NOTIFY, enter_notify);
|
||||||
|
EVENT(EVENT_TYPE_FOCUS_IN, focus_in);
|
||||||
|
|
||||||
#undef EVENT
|
#undef EVENT
|
||||||
|
|
||||||
@@ -487,7 +492,7 @@ void configure_request(configure_request_event_t *event) {
|
|||||||
} else {
|
} else {
|
||||||
configure_window(client->window, client->x, client->y, client->width,
|
configure_window(client->window, client->x, client->y, client->width,
|
||||||
client->height, client->border_width);
|
client->height, client->border_width);
|
||||||
printf("configure request: %s\n", client->name);
|
logger("configure request: %s\n", client->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
flush_xcb_connection();
|
flush_xcb_connection();
|
||||||
@@ -509,6 +514,10 @@ void unmap_notify(unmap_notify_event_t *event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void motion_notify(motion_notify_event_t *event) {
|
void motion_notify(motion_notify_event_t *event) {
|
||||||
|
logger("motion: window: 0x%x, root: 0x%x\n", event->window, event->root);
|
||||||
|
logger("x: %d, y: %d, root_x: %d, root_y: %d\n", event->x, event->y,
|
||||||
|
event->root_x, event->root_y);
|
||||||
|
logger("root window: 0x%x\n", get_root_window());
|
||||||
if (event->window != get_root_window()) return;
|
if (event->window != get_root_window()) return;
|
||||||
|
|
||||||
static monitor_t *mon = NULL;
|
static monitor_t *mon = NULL;
|
||||||
@@ -521,6 +530,31 @@ void motion_notify(motion_notify_event_t *event) {
|
|||||||
mon = m;
|
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 manage_window(xcb_window_t window) {
|
void manage_window(xcb_window_t window) {
|
||||||
window_geometry_t *geometry = get_window_geometry(window);
|
window_geometry_t *geometry = get_window_geometry(window);
|
||||||
client_t *c = ecalloc(1, sizeof(client_t));
|
client_t *c = ecalloc(1, sizeof(client_t));
|
||||||
@@ -533,8 +567,8 @@ void manage_window(xcb_window_t window) {
|
|||||||
c->border_width = border_px;
|
c->border_width = border_px;
|
||||||
update_client_name(c);
|
update_client_name(c);
|
||||||
|
|
||||||
printf("== manage window: %u\n", window);
|
logger("== manage window: %u\n", window);
|
||||||
printf("== x: %d, y: %d, w: %u, h: %u, bw: %u\n", c->x, c->y, c->width,
|
logger("== x: %d, y: %d, w: %u, h: %u, bw: %u\n", c->x, c->y, c->width,
|
||||||
c->height, c->border_width);
|
c->height, c->border_width);
|
||||||
|
|
||||||
xcb_window_t tw = get_transient_window_for(window);
|
xcb_window_t tw = get_transient_window_for(window);
|
||||||
@@ -551,9 +585,12 @@ void manage_window(xcb_window_t window) {
|
|||||||
attach_stack_client(c);
|
attach_stack_client(c);
|
||||||
init_window_event_mask(window);
|
init_window_event_mask(window);
|
||||||
change_window_border_color(window, color_set->border_color.argb);
|
change_window_border_color(window, color_set->border_color.argb);
|
||||||
|
if (c->monitor == current_monitor) {
|
||||||
|
unfocus(current_monitor->selected_client, false);
|
||||||
|
}
|
||||||
arrange(c->monitor);
|
arrange(c->monitor);
|
||||||
map_window(window);
|
map_window(window);
|
||||||
draw_all_monitor_bars();
|
focus(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void unmanage_client(client_t *client) {
|
void unmanage_client(client_t *client) {
|
||||||
@@ -626,7 +663,7 @@ void apply_rules(client_t *client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print_client(client);
|
print_client(client);
|
||||||
printf("apply rules done\n");
|
logger("apply rules done\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t get_tags_mask_of_monitor(monitor_t *monitor) {
|
uint32_t get_tags_mask_of_monitor(monitor_t *monitor) {
|
||||||
@@ -681,16 +718,33 @@ void restack(monitor_t *monitor) {
|
|||||||
/* TODO: */
|
/* TODO: */
|
||||||
}
|
}
|
||||||
void focus(client_t *client) {
|
void focus(client_t *client) {
|
||||||
/* TODO: */
|
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;
|
current_monitor->selected_client = client;
|
||||||
draw_all_monitor_bars();
|
draw_all_monitor_bars();
|
||||||
}
|
}
|
||||||
void unfocus(client_t *client, bool set_focus) {
|
void unfocus(client_t *client, bool set_focus) {
|
||||||
if (!client) return;
|
if (!client) return;
|
||||||
|
|
||||||
change_window_border_color(client->window, color_set->border_color.argb);
|
change_window_border_color(client->window, color_set->border_color.argb);
|
||||||
if (set_focus) {
|
if (set_focus) focus_window(WINDOW_NONE);
|
||||||
/* TODO: */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void show_hide_client(client_t *client) {
|
void show_hide_client(client_t *client) {
|
||||||
@@ -741,6 +795,22 @@ client_t *get_client_of_window(xcb_window_t window) {
|
|||||||
return NULL;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
|
gboolean handle_xcb_event(GIOChannel *channel, GIOCondition condition,
|
||||||
gpointer userdata) {
|
gpointer userdata) {
|
||||||
if (condition & (G_IO_HUP | G_IO_ERR)) {
|
if (condition & (G_IO_HUP | G_IO_ERR)) {
|
||||||
|
|||||||
11
src/utils.c
11
src/utils.c
@@ -44,6 +44,17 @@ bool file_exist(const char *path) {
|
|||||||
return access(path, R_OK) == 0;
|
return access(path, R_OK) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if (!defined(RELEASE)) && defined(LOG_FILE)
|
||||||
|
void logger(const char *fmt, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
FILE *log_file = fopen(LOG_FILE, "a+t");
|
||||||
|
vfprintf(log_file, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
fclose(log_file);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool hex_color_to_rgba(const char *hex, uint32_t *rgba) {
|
bool hex_color_to_rgba(const char *hex, uint32_t *rgba) {
|
||||||
if (hex[0] == '#') hex++;
|
if (hex[0] == '#') hex++;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
set(LIB_XCB "zswm-xcb")
|
set(LIB_XCB "zswm-xcb")
|
||||||
set(LIB_XCB_NAME ${LIB_XCB} PARENT_SCOPE)
|
set(LIB_XCB_NAME ${LIB_XCB} PARENT_SCOPE)
|
||||||
|
|
||||||
add_library(${LIB_XCB} STATIC xcb.c window.c res.c cursor.c event.c)
|
add_library(${LIB_XCB} STATIC xcb.c window.c res.c cursor.c event.c wm-extension.c)
|
||||||
|
|
||||||
target_include_directories(${LIB_XCB} SYSTEM
|
target_include_directories(${LIB_XCB} SYSTEM
|
||||||
PRIVATE ${XCB_INCLUDE_DIRS}
|
PRIVATE ${XCB_INCLUDE_DIRS}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include <xcb/xcb_event.h>
|
#include <xcb/xcb_event.h>
|
||||||
@@ -19,7 +18,7 @@ static void convert_event(xcb_generic_event_t *xcb_event,
|
|||||||
generic_event_t *event) {
|
generic_event_t *event) {
|
||||||
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(xcb_event);
|
uint8_t event_type = XCB_EVENT_RESPONSE_TYPE(xcb_event);
|
||||||
const char *label = xcb_event_get_label(event_type);
|
const char *label = xcb_event_get_label(event_type);
|
||||||
printf("=========================== %s ===========================\n", label);
|
logger("=========================== %s ===========================\n", label);
|
||||||
switch (event_type) {
|
switch (event_type) {
|
||||||
case XCB_MAPPING_NOTIFY:
|
case XCB_MAPPING_NOTIFY:
|
||||||
event->type = EVENT_TYPE_MAPPING_NOTIFY;
|
event->type = EVENT_TYPE_MAPPING_NOTIFY;
|
||||||
@@ -84,12 +83,19 @@ static void convert_event(xcb_generic_event_t *xcb_event,
|
|||||||
event->motiion_notify.y = ev->event_y;
|
event->motiion_notify.y = ev->event_y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case XCB_ENTER_NOTIFY:
|
case XCB_ENTER_NOTIFY: {
|
||||||
event->type = EVENT_TYPE_ENTER_NOTIFY;
|
xcb_enter_notify_event_t *ev = (xcb_enter_notify_event_t *)xcb_event;
|
||||||
break;
|
event->enter_notify.type = EVENT_TYPE_ENTER_NOTIFY;
|
||||||
case XCB_FOCUS_IN:
|
event->enter_notify.window = ev->event;
|
||||||
event->type = EVENT_TYPE_FOCUS_IN;
|
event->enter_notify.mode = ev->mode;
|
||||||
|
event->enter_notify.detail = ev->detail;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case XCB_FOCUS_IN: {
|
||||||
|
xcb_focus_in_event_t *ev = (xcb_focus_in_event_t *)xcb_event;
|
||||||
|
event->focus_in.type = EVENT_TYPE_FOCUS_IN;
|
||||||
|
event->focus_in.window = ev->event;
|
||||||
|
} break;
|
||||||
case XCB_PROPERTY_NOTIFY:
|
case XCB_PROPERTY_NOTIFY:
|
||||||
event->type = EVENT_TYPE_PROPERTY_NOTIFY;
|
event->type = EVENT_TYPE_PROPERTY_NOTIFY;
|
||||||
break;
|
break;
|
||||||
@@ -97,6 +103,7 @@ static void convert_event(xcb_generic_event_t *xcb_event,
|
|||||||
event->type = EVENT_TYPE_CLIENT_MESSAGE;
|
event->type = EVENT_TYPE_CLIENT_MESSAGE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
event->type = -1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,3 +285,19 @@ void change_window_border_color(xcb_window_t window, uint32_t argb) {
|
|||||||
xcb_params_cw_t params = {.border_pixel = argb};
|
xcb_params_cw_t params = {.border_pixel = argb};
|
||||||
xcb_aux_change_window_attributes(conn, window, XCB_CW_BORDER_PIXEL, ¶ms);
|
xcb_aux_change_window_attributes(conn, window, XCB_CW_BORDER_PIXEL, ¶ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void focus_window(xcb_window_t window) {
|
||||||
|
xcb_window_t root = screen->root;
|
||||||
|
if (window == WINDOW_NONE) {
|
||||||
|
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root,
|
||||||
|
XCB_CURRENT_TIME);
|
||||||
|
xcb_delete_property(conn, root, ewmh->_NET_ACTIVE_WINDOW);
|
||||||
|
} else {
|
||||||
|
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, window,
|
||||||
|
XCB_CURRENT_TIME);
|
||||||
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
|
||||||
|
ewmh->_NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1,
|
||||||
|
&window);
|
||||||
|
send_event(window, atom_wm_take_focus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
127
src/xcb/wm-extension.c
Normal file
127
src/xcb/wm-extension.c
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_icccm.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
|
#include "app.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "window.h"
|
||||||
|
#include "xcb-private.h"
|
||||||
|
#include "xcb.h"
|
||||||
|
|
||||||
|
xcb_ewmh_connection_t *ewmh = NULL;
|
||||||
|
static bool ewmh_init_failed = false;
|
||||||
|
static xcb_window_t wm_check_window = XCB_NONE;
|
||||||
|
|
||||||
|
typedef struct atom_name_map_t {
|
||||||
|
const atom_t atom;
|
||||||
|
const char *name;
|
||||||
|
xcb_atom_t xcb_atom;
|
||||||
|
} atom_map_t;
|
||||||
|
static atom_map_t atom_map[] = {
|
||||||
|
{.atom = atom_wm_protocols, .name = "WM_PROTOCOLS"},
|
||||||
|
{.atom = atom_wm_delete, .name = "WM_DELETE_WINDOW"},
|
||||||
|
{.atom = atom_wm_take_focus, .name = "WM_TAKE_FOCUS"},
|
||||||
|
};
|
||||||
|
|
||||||
|
void init_icccm_extension(void) {
|
||||||
|
xcb_intern_atom_cookie_t cookie;
|
||||||
|
for (int i = 0; i < LENGTH(atom_map); i++) {
|
||||||
|
const char *name = atom_map[i].name;
|
||||||
|
cookie = xcb_intern_atom(conn, false, strlen(name), name);
|
||||||
|
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, cookie, NULL);
|
||||||
|
atom_map[i].xcb_atom = reply->atom;
|
||||||
|
free(reply);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_ewmh_extension(void) {
|
||||||
|
if (ewmh || ewmh_init_failed) return;
|
||||||
|
|
||||||
|
ewmh = ecalloc(1, sizeof(xcb_ewmh_connection_t));
|
||||||
|
xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(conn, ewmh);
|
||||||
|
if (!xcb_ewmh_init_atoms_replies(ewmh, cookie, NULL)) {
|
||||||
|
free(ewmh);
|
||||||
|
ewmh = NULL;
|
||||||
|
ewmh_init_failed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wm_check_window = xcb_generate_id(conn);
|
||||||
|
xcb_create_window(conn, XCB_COPY_FROM_PARENT, wm_check_window, screen->root,
|
||||||
|
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
|
||||||
|
screen->root_visual, XCB_NONE, NULL);
|
||||||
|
const char *class = APP_NAME "_wm_check";
|
||||||
|
set_window_class_instance(wm_check_window, class, class);
|
||||||
|
xcb_ewmh_set_wm_name(ewmh, wm_check_window, strlen(APP_NAME), APP_NAME);
|
||||||
|
xcb_ewmh_set_supporting_wm_check(ewmh, screen->root, wm_check_window);
|
||||||
|
xcb_ewmh_set_supporting_wm_check(ewmh, wm_check_window, wm_check_window);
|
||||||
|
|
||||||
|
uint8_t mode = XCB_PROP_MODE_REPLACE;
|
||||||
|
xcb_atom_t atom = XCB_ATOM_ATOM;
|
||||||
|
const xcb_atom_t state[] = {ewmh->_NET_WM_STATE_STICKY};
|
||||||
|
const xcb_atom_t supported[] = {
|
||||||
|
ewmh->_NET_ACTIVE_WINDOW,
|
||||||
|
ewmh->_NET_SUPPORTED,
|
||||||
|
ewmh->_NET_WM_NAME,
|
||||||
|
ewmh->_NET_WM_STATE,
|
||||||
|
ewmh->_NET_SUPPORTING_WM_CHECK,
|
||||||
|
ewmh->_NET_WM_STATE_FULLSCREEN,
|
||||||
|
ewmh->_NET_WM_WINDOW_TYPE,
|
||||||
|
ewmh->_NET_WM_WINDOW_TYPE_DIALOG,
|
||||||
|
ewmh->_NET_CLIENT_LIST,
|
||||||
|
};
|
||||||
|
xcb_change_property(conn, mode, wm_check_window, ewmh->_NET_WM_STATE, atom,
|
||||||
|
32, LENGTH(state), state);
|
||||||
|
xcb_change_property(conn, mode, screen->root, ewmh->_NET_SUPPORTED, atom, 32,
|
||||||
|
LENGTH(supported), supported);
|
||||||
|
xcb_delete_property(conn, screen->root, ewmh->_NET_CLIENT_LIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clean_ewmh_extension(void) {
|
||||||
|
if (wm_check_window != XCB_NONE) {
|
||||||
|
xcb_destroy_window(conn, wm_check_window);
|
||||||
|
wm_check_window = XCB_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ewmh) {
|
||||||
|
if (!ewmh_init_failed) {
|
||||||
|
xcb_ewmh_connection_wipe(ewmh);
|
||||||
|
}
|
||||||
|
free(ewmh);
|
||||||
|
ewmh_init_failed = false;
|
||||||
|
ewmh = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send_event(xcb_window_t window, atom_t atom) {
|
||||||
|
if (atom < 0 || atom >= LENGTH(atom_map)) return false;
|
||||||
|
|
||||||
|
bool exist = false;
|
||||||
|
|
||||||
|
xcb_atom_t xcb_atom = atom_map[atom].xcb_atom;
|
||||||
|
xcb_get_property_cookie_t cookie =
|
||||||
|
xcb_icccm_get_wm_protocols(conn, window, xcb_atom);
|
||||||
|
xcb_icccm_get_wm_protocols_reply_t reply;
|
||||||
|
if (xcb_icccm_get_wm_protocols_reply(conn, cookie, &reply, NULL)) {
|
||||||
|
for (uint32_t i = 0; !exist && i < reply.atoms_len; i++) {
|
||||||
|
exist = reply.atoms[i] == xcb_atom;
|
||||||
|
}
|
||||||
|
xcb_icccm_get_wm_protocols_reply_wipe(&reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exist) {
|
||||||
|
xcb_client_message_event_t ev = {
|
||||||
|
.response_type = XCB_CLIENT_MESSAGE,
|
||||||
|
.format = 32,
|
||||||
|
.window = window,
|
||||||
|
.type = atom_map[atom_wm_protocols].xcb_atom,
|
||||||
|
.data = {.data32 = {xcb_atom, XCB_CURRENT_TIME, 0, 0, 0}},
|
||||||
|
};
|
||||||
|
xcb_send_event(conn, false, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
return exist;
|
||||||
|
}
|
||||||
@@ -12,3 +12,7 @@ extern xcb_key_symbols_t *key_symbols;
|
|||||||
|
|
||||||
xcb_cursor_t get_xcb_cursor(cursor_t cursor);
|
xcb_cursor_t get_xcb_cursor(cursor_t cursor);
|
||||||
void clean_xcb_cursor(void);
|
void clean_xcb_cursor(void);
|
||||||
|
|
||||||
|
void init_icccm_extension(void);
|
||||||
|
void init_ewmh_extension(void);
|
||||||
|
void clean_ewmh_extension(void);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "window.h"
|
|
||||||
#include "xcb-private.h"
|
#include "xcb-private.h"
|
||||||
|
|
||||||
xcb_connection_t *conn = NULL;
|
xcb_connection_t *conn = NULL;
|
||||||
@@ -170,58 +169,13 @@ void set_wallpaper(uint32_t *wallpaper_data) {
|
|||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_ewmh_connection_t *ewmh = NULL;
|
|
||||||
static bool ewmh_init_failed = false;
|
|
||||||
static xcb_window_t wm_check_window = XCB_NONE;
|
|
||||||
static void init_ewmh_extension(void) {
|
|
||||||
if (ewmh || ewmh_init_failed) return;
|
|
||||||
|
|
||||||
ewmh = ecalloc(1, sizeof(xcb_ewmh_connection_t));
|
|
||||||
xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(conn, ewmh);
|
|
||||||
if (!xcb_ewmh_init_atoms_replies(ewmh, cookie, NULL)) {
|
|
||||||
free(ewmh);
|
|
||||||
ewmh = NULL;
|
|
||||||
ewmh_init_failed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
wm_check_window = xcb_generate_id(conn);
|
|
||||||
xcb_create_window(conn, XCB_COPY_FROM_PARENT, wm_check_window, screen->root,
|
|
||||||
0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY,
|
|
||||||
screen->root_visual, XCB_NONE, NULL);
|
|
||||||
const char *class = APP_NAME "_wm_check";
|
|
||||||
set_window_class_instance(wm_check_window, class, class);
|
|
||||||
xcb_ewmh_set_wm_name(ewmh, wm_check_window, strlen(APP_NAME), APP_NAME);
|
|
||||||
xcb_ewmh_set_supporting_wm_check(ewmh, screen->root, wm_check_window);
|
|
||||||
xcb_ewmh_set_supporting_wm_check(ewmh, wm_check_window, wm_check_window);
|
|
||||||
|
|
||||||
uint8_t mode = XCB_PROP_MODE_REPLACE;
|
|
||||||
xcb_atom_t atom = XCB_ATOM_ATOM;
|
|
||||||
const xcb_atom_t state[] = {ewmh->_NET_WM_STATE_STICKY};
|
|
||||||
const xcb_atom_t supported[] = {
|
|
||||||
ewmh->_NET_ACTIVE_WINDOW,
|
|
||||||
ewmh->_NET_SUPPORTED,
|
|
||||||
ewmh->_NET_WM_NAME,
|
|
||||||
ewmh->_NET_WM_STATE,
|
|
||||||
ewmh->_NET_SUPPORTING_WM_CHECK,
|
|
||||||
ewmh->_NET_WM_STATE_FULLSCREEN,
|
|
||||||
ewmh->_NET_WM_WINDOW_TYPE,
|
|
||||||
ewmh->_NET_WM_WINDOW_TYPE_DIALOG,
|
|
||||||
ewmh->_NET_CLIENT_LIST,
|
|
||||||
};
|
|
||||||
xcb_change_property(conn, mode, wm_check_window, ewmh->_NET_WM_STATE, atom,
|
|
||||||
32, LENGTH(state), state);
|
|
||||||
xcb_change_property(conn, mode, screen->root, ewmh->_NET_SUPPORTED, atom, 32,
|
|
||||||
LENGTH(supported), supported);
|
|
||||||
xcb_delete_property(conn, screen->root, ewmh->_NET_CLIENT_LIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫描窗口列表
|
* 扫描窗口列表
|
||||||
* @returns 若无窗口,返回 NULL,否则返回窗口列表
|
* @returns 若无窗口,返回 NULL,否则返回窗口列表
|
||||||
* @note: 返回的窗口列表数据使用完后使用 free_window_list 释放
|
* @note: 返回的窗口列表数据使用完后使用 free_window_list 释放
|
||||||
*/
|
*/
|
||||||
window_list_t *scan_window_list(void) {
|
window_list_t *scan_window_list(void) {
|
||||||
|
init_icccm_extension();
|
||||||
init_ewmh_extension();
|
init_ewmh_extension();
|
||||||
|
|
||||||
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, screen->root);
|
xcb_query_tree_cookie_t cookie = xcb_query_tree(conn, screen->root);
|
||||||
@@ -278,21 +232,7 @@ void free_window_list(window_list_t *window_list) {
|
|||||||
|
|
||||||
void clean_xcb(void) {
|
void clean_xcb(void) {
|
||||||
clean_xcb_cursor();
|
clean_xcb_cursor();
|
||||||
|
clean_ewmh_extension();
|
||||||
if (wm_check_window != XCB_NONE) {
|
|
||||||
xcb_destroy_window(conn, wm_check_window);
|
|
||||||
wm_check_window = XCB_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ewmh) {
|
|
||||||
if (!ewmh_init_failed) {
|
|
||||||
xcb_ewmh_connection_wipe(ewmh);
|
|
||||||
}
|
|
||||||
free(ewmh);
|
|
||||||
ewmh_init_failed = false;
|
|
||||||
ewmh = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_disconnect(conn);
|
xcb_disconnect(conn);
|
||||||
conn = NULL;
|
conn = NULL;
|
||||||
screen = NULL;
|
screen = NULL;
|
||||||
@@ -322,3 +262,18 @@ void grab_keybindings(const keybinding_t *keys, const size_t length) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get_pointer_position(int32_t *x, int32_t *y) {
|
||||||
|
xcb_query_pointer_cookie_t cookie = xcb_query_pointer(conn, screen->root);
|
||||||
|
xcb_query_pointer_reply_t *reply =
|
||||||
|
xcb_query_pointer_reply(conn, cookie, NULL);
|
||||||
|
|
||||||
|
if (reply) {
|
||||||
|
*x = reply->root_x;
|
||||||
|
*y = reply->root_y;
|
||||||
|
free(reply);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user