Compare commits

...

2 Commits

Author SHA1 Message Date
9e53010ff7 检测屏幕信息 2025-11-13 19:28:40 +08:00
79425dc890 窗口管理器状态机设计 2025-11-13 16:48:08 +08:00
18 changed files with 526 additions and 134 deletions

View File

@@ -19,7 +19,7 @@ project(${APP_NAME}
LANGUAGES C
)
add_executable(${APP_NAME} main.c utils.c buffer.c backtrace.c)
add_executable(${APP_NAME} wm.c utils.c buffer.c backtrace.c monitor.c client.c)
# use C23
set_property(TARGET ${APP_NAME} PROPERTY C_STANDARD 23)
@@ -31,6 +31,9 @@ pkg_check_modules(deps REQUIRED
xcb
xcb-aux
xcb-randr
xcb-xfixes
xcb-xinerama
)
target_include_directories(${APP_NAME} SYSTEM
@@ -63,8 +66,8 @@ else()
message(STATUS "checking for execinfo -- not found")
endif()
configure_file("${SOURCE_DIR}/config.h.in"
"${BUILD_DIR}/config.h"
configure_file("${SOURCE_DIR}/app.h.in"
"${BUILD_DIR}/app.h"
ESCAPE_QUOTES
@ONLY
)

View File

@@ -1,7 +1,7 @@
#include "backtrace.h"
#include "app.h"
#include "buffer.h"
#include "config.h"
#ifdef HAS_EXECINFO
#include <execinfo.h>
@@ -9,7 +9,6 @@
#define MAX_STACK_SIZE 32
/**
* Get a backtrace.
* @param buffer The buffer to fill with backtrace.

2
src/bar.h Normal file
View File

@@ -0,0 +1,2 @@
#pragma once

13
src/base.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
typedef struct area_t {
int16_t x, y;
uint16_t width, height;
} area_t;
typedef struct extent_in_bar_t {
int16_t start;
int16_t end;
} extent_in_bar_t;

1
src/client.c Normal file
View File

@@ -0,0 +1 @@
#include "client.h"

45
src/client.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include <stdint.h>
#include "color.h"
#include "types.h"
void client_manage(xcb_window_t window);
void client_send_to_tag(client_t *client, uint32_t tag_mask);
void client_send_to_monitor(client_t *client, monitor_t *monitor);
void client_move_to(client_t *client, int16_t x, int16_t y);
void client_resize(client_t *client, uint16_t width, uint16_t height);
void client_change_border_color(client_t *client, color_t *color);
void client_change_border_width(client_t *client, uint16_t border_width);
void client_set_floating(client_t *client, bool floating);
void client_set_fullscreen(client_t *client, bool fullscreen);
void client_set_maximize(client_t *client, bool maximize);
void client_set_minimize(client_t *client, bool minimize);
void client_set_sticky(client_t *client, bool sticky);
void client_set_urgent(client_t *client, bool urgent);
void client_set_class_instance(client_t *client, const char *class,
const char *instance);
void client_set_name(client_t *client, char *name);
void client_set_icon_name(client_t *client, char *icon_name);
void client_set_net_name(client_t *client, char *name);
void client_set_net_icon_name(client_t *client, char *icon_name);
void client_set_role(client_t *client, char *role);
void client_set_transient_for_window(client_t *client,
xcb_window_t transient_for_window);
void client_set_leader_window(client_t *client, xcb_window_t leader_window);
void client_kill(client_t *client);
void client_unmanage(client_t *client);
void client_focus(client_t *client);
void client_stack_raise(client_t *client);
bool client_is_visible(client_t *client);

22
src/color.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
typedef struct color_t {
uint32_t rgba; /* use for human reading */
uint32_t argb; /* use for xcb */
/* channels of color, use for cairo */
double red; /* red channel */
double green; /* green channel */
double blue; /* blue channel */
double alpha; /* alpha channel */
} color_t;
/**
* @brief parsing color represented in hexadecimal format
* @param hex color represented in hexadecimal format, the supported format
have: RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA
* @param color return the parsed color
*/
void parse_color(const char *hex, color_t *const color);

3
src/draw.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once

View File

@@ -1,119 +0,0 @@
#include <glib-unix.h>
#include <glib.h>
#include <glibconfig.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xproto.h>
#include "backtrace.h"
#include "buffer.h"
#include "types.h"
#include "utils.h"
global_state_t global_state;
/** argv used to run wm */
static char **cmd_argv;
/** A pipe that is used to asynchronously handle SIGCHLD */
static int sigchld_pipe[2];
static void wm_atexit(bool restart) { global_state.need_restart = restart; }
static void wm_restart(void) {
wm_atexit(true);
execvp(cmd_argv[0], cmd_argv);
fatal("execv() failed: %s", strerror(errno));
}
static gboolean restart_on_signal(gpointer data) {
wm_restart();
return TRUE;
}
static gboolean exit_on_signal(gpointer data) {
g_main_loop_quit(global_state.loop);
return TRUE;
}
static void signal_fatal(int signal_number) {
buffer_t buffer;
backtrace_get(&buffer);
fatal("signal %d, dumping backtrace\n%s", signal_number, buffer.s);
}
/* Signal handler for SIGCHLD. Causes reap_children() to be called. */
static void signal_child(int signum) {
assert(signum == SIGCHLD);
int res = write(sigchld_pipe[1], " ", 1);
(void)res;
assert(res == 1);
}
static void check_other_wm(void) {
/* connect X server */
int default_screen;
xcb_connection_t *conn = xcb_connect(NULL, &default_screen);
int xcb_conn_error = xcb_connection_has_error(conn);
if (xcb_conn_error) fatal("cannot open display (error %d)", xcb_conn_error);
xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen);
xcb_window_t root = screen->root;
/* check other window manager running */
uint32_t mask = XCB_CW_EVENT_MASK;
const xcb_params_cw_t params = {
.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
};
xcb_void_cookie_t cookie =
xcb_aux_change_window_attributes_checked(conn, root, mask, &params);
if (xcb_request_check(conn, cookie)) {
fatal(
"another window manager is already running (cannot select "
"SubstructureRedirect)");
}
global_state.xcb_conn = conn;
global_state.default_screen = default_screen;
global_state.screen = screen;
}
int main(int argc, char *argv[]) {
p_clear(&global_state, 1);
cmd_argv = argv;
g_unix_signal_add(SIGINT, exit_on_signal, NULL);
g_unix_signal_add(SIGTERM, exit_on_signal, NULL);
g_unix_signal_add(SIGHUP, restart_on_signal, NULL);
struct sigaction sa = {.sa_handler = signal_fatal, .sa_flags = SA_RESETHAND};
sigemptyset(&sa.sa_mask);
sigaction(SIGABRT, &sa, 0);
sigaction(SIGBUS, &sa, 0);
sigaction(SIGFPE, &sa, 0);
sigaction(SIGILL, &sa, 0);
sigaction(SIGSEGV, &sa, 0);
signal(SIGPIPE, SIG_IGN);
sa.sa_handler = signal_child;
sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
sigaction(SIGCHLD, &sa, 0);
check_other_wm();
if (global_state.loop == NULL) {
global_state.loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(global_state.loop);
}
g_main_loop_unref(global_state.loop);
global_state.loop = NULL;
wm_atexit(false);
return EXIT_SUCCESS;
}

3
src/monitor.c Normal file
View File

@@ -0,0 +1,3 @@
#include "monitor.h"

7
src/monitor.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#include <stdint.h>
#include "types.h"
void monitor_select_tag(monitor_t *monitor, uint32_t tag_mask);

View File

@@ -1,16 +1,76 @@
#pragma once
#include <glib.h>
#include <stdint.h>
#include <xcb/xcb.h>
#include <xcb/xproto.h>
typedef struct global_state_t {
bool need_restart;
GMainLoop *loop;
#include "base.h"
#include "color.h"
xcb_connection_t *xcb_conn;
xcb_screen_t *screen;
int default_screen;
} global_state_t;
typedef struct client_t client_t;
typedef struct monitor_t monitor_t;
typedef struct tag_t tag_t;
typedef struct task_in_tag_t task_in_tag_t;
extern global_state_t global_state;
typedef struct layout_t {
char *symbol;
void (*arrange)(tag_t *tag);
} layout_t;
struct client_t {
client_t *stack_next;
client_t *next;
monitor_t *monitor;
uint32_t tags;
area_t geometry;
color_t *border_color;
uint16_t border_width;
bool floating;
bool fullscreen;
bool maximize;
bool minimize;
bool sticky;
bool urgent;
char *class, *instance;
char *name, *icon_name, *net_name, *net_icon_name;
char *role;
xcb_window_t transient_for_window;
xcb_window_t leader_window;
xcb_window_t frame_window;
xcb_window_t window;
};
struct monitor_t {
monitor_t *next;
area_t geometry;
area_t workarea;
tag_t *tag_list;
tag_t *selected_tag;
char *name;
xcb_window_t bar_window;
};
struct tag_t {
uint32_t index; /* index in all tags */
uint32_t mask;
extent_in_bar_t bar_extent;
tag_t *next;
char *name;
layout_t *layout;
task_in_tag_t *task_list;
};
struct task_in_tag_t {
client_t *client;
task_in_tag_t *next;
area_t geometry;
extent_in_bar_t bar_extent;
};

View File

@@ -5,7 +5,7 @@
#include <stdlib.h>
#include <time.h>
#include "config.h"
#include "app.h"
const char *current_time_str(void) {
static char buffer[25];
@@ -19,7 +19,8 @@ const char *current_time_str(void) {
return buffer;
}
/** Print error and exit with EXIT_FAILURE code.
/**
* Print error and exit with EXIT_FAILURE code.
*/
void _fatal(int line, const char *fct, const char *file, const char *fmt, ...) {
va_list ap;
@@ -32,3 +33,17 @@ void _fatal(int line, const char *fct, const char *file, const char *fmt, ...) {
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
/**
* Print error message on stderr
*/
void _warn(int line, const char *fct, const char *file, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
const char *format = "(WW) [%s] %s\n%s:%d:%s:\n";
fprintf(stderr, format, APP_NAME, current_time_str(), file, line, fct);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}

View File

@@ -7,8 +7,10 @@
#define countof(foo) (ssizeof(foo) / ssizeof(foo[0]))
#define p_alloc_nr(x) (((x) + 16) * 3 / 2)
#define p_new(type, count) ((type *)xmalloc(sizeof(type) * (count)))
#define p_clear(p, count) ((void)memset((p), 0, sizeof(*(p)) * (count)))
#define p_realloc(pp, count) xrealloc((void *)(pp), sizeof(**(pp)) * (count))
#define p_dup(p, count) xmemdup((p), sizeof(*(p)) * (count))
#define p_delete(mem_p) \
do { \
@@ -46,6 +48,16 @@ static inline void xrealloc(void **ptr, ssize_t newsize) {
}
}
/**
* Duplicate a memory zone.
* @param src The source.
* @param size The source size.
* @return The memory address of the copy.
*/
static inline void *xmemdup(const void *src, ssize_t size) {
return memcpy(xmalloc(size), src, size);
}
/**
* @brief NULL resistant strlen.
*
@@ -57,10 +69,30 @@ static inline void xrealloc(void **ptr, ssize_t newsize) {
*/
static inline ssize_t a_strlen(const char *s) { return s ? strlen(s) : 0; }
/** \brief \c NULL resistant strdup.
*
* The a_strdup() function returns a pointer to a new string, which is a
* duplicate of \c s. Memory should be freed using p_delete().
*
* \warning when s is \c "", it returns NULL !
*
* \param[in] s the string to duplicate.
* \return a pointer to the duplicated string.
*/
static inline char *a_strdup(const char *s) {
ssize_t len = a_strlen(s);
return len ? p_dup(s, len + 1) : NULL;
}
#define fatal(format, ...) \
_fatal(__LINE__, __FUNCTION__, __FILE__, format, ##__VA_ARGS__)
void _fatal(int line, const char *function, const char *file,
const char *format, ...) __attribute__((noreturn))
__attribute__((format(printf, 4, 5)));
#define warn(format, ...) \
_fatal(__LINE__, __FUNCTION__, __FILE__, format, ##__VA_ARGS__)
void _warn(int line, const char *function, const char *file, const char *format,
...) __attribute__((format(printf, 4, 5)));
const char *current_time_str(void);

279
src/wm.c Normal file
View File

@@ -0,0 +1,279 @@
#include "wm.h"
#include <glib-unix.h>
#include <glib.h>
#include <glibconfig.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xcb/randr.h>
#include <xcb/xcb.h>
#include <xcb/xcb_aux.h>
#include <xcb/xfixes.h>
#include <xcb/xinerama.h>
#include <xcb/xproto.h>
#include "backtrace.h"
#include "buffer.h"
#include "types.h"
#include "utils.h"
static void wm_setup_signal(void);
static void wm_check_other_wm(void);
static void wm_check_xcb_extensions(void);
static void wm_detect_monitor(void);
static void wm_scan_clients(void);
wm_t wm;
/** argv used to run wm */
static char **cmd_argv;
/** A pipe that is used to asynchronously handle SIGCHLD */
static int sigchld_pipe[2];
static void wm_atexit(bool restart) { wm.need_restart = restart; }
static gboolean restart_on_signal(gpointer data) {
wm_restart();
return TRUE;
}
static gboolean exit_on_signal(gpointer data) {
g_main_loop_quit(wm.loop);
return TRUE;
}
static void signal_fatal(int signal_number) {
buffer_t buffer;
backtrace_get(&buffer);
fatal("signal %d, dumping backtrace\n%s", signal_number, buffer.s);
}
/* Signal handler for SIGCHLD. Causes reap_children() to be called. */
static void signal_child(int signum) {
assert(signum == SIGCHLD);
int res = write(sigchld_pipe[1], " ", 1);
(void)res;
assert(res == 1);
}
void wm_setup_signal(void) {
g_unix_signal_add(SIGINT, exit_on_signal, NULL);
g_unix_signal_add(SIGTERM, exit_on_signal, NULL);
g_unix_signal_add(SIGHUP, restart_on_signal, NULL);
struct sigaction sa = {.sa_handler = signal_fatal, .sa_flags = SA_RESETHAND};
sigemptyset(&sa.sa_mask);
sigaction(SIGABRT, &sa, 0);
sigaction(SIGBUS, &sa, 0);
sigaction(SIGFPE, &sa, 0);
sigaction(SIGILL, &sa, 0);
sigaction(SIGSEGV, &sa, 0);
signal(SIGPIPE, SIG_IGN);
sa.sa_handler = signal_child;
sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
sigaction(SIGCHLD, &sa, 0);
}
void wm_check_other_wm(void) {
/* connect X server */
int default_screen;
xcb_connection_t *conn = xcb_connect(NULL, &default_screen);
int xcb_conn_error = xcb_connection_has_error(conn);
if (xcb_conn_error) fatal("cannot open display (error %d)", xcb_conn_error);
xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen);
xcb_window_t root = screen->root;
/* check other window manager running */
uint32_t mask = XCB_CW_EVENT_MASK;
const xcb_params_cw_t params = {
.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
};
xcb_void_cookie_t cookie =
xcb_aux_change_window_attributes_checked(conn, root, mask, &params);
if (xcb_request_check(conn, cookie)) {
fatal(
"another window manager is already running (cannot select "
"SubstructureRedirect)");
}
wm.xcb_conn = conn;
wm.default_screen = default_screen;
wm.screen = screen;
}
void wm_check_xcb_extensions(void) {
xcb_prefetch_extension_data(wm.xcb_conn, &xcb_xfixes_id);
xcb_prefetch_extension_data(wm.xcb_conn, &xcb_xinerama_id);
xcb_prefetch_extension_data(wm.xcb_conn, &xcb_randr_id);
const xcb_query_extension_reply_t *query;
query = xcb_get_extension_data(wm.xcb_conn, &xcb_xfixes_id);
wm.have_xfixes = query && query->present;
query = xcb_get_extension_data(wm.xcb_conn, &xcb_xinerama_id);
wm.have_xinerama = query && query->present;
query = xcb_get_extension_data(wm.xcb_conn, &xcb_randr_id);
wm.have_randr = query && query->present;
}
static bool wm_detect_monitor_by_randr(void) {
xcb_randr_get_monitors_cookie_t monitors_cookie =
xcb_randr_get_monitors(wm.xcb_conn, wm.screen->root, 1);
xcb_randr_get_monitors_reply_t *monitors_reply =
xcb_randr_get_monitors_reply(wm.xcb_conn, monitors_cookie, NULL);
if (monitors_reply == NULL) {
warn("RandR get monitor failed");
return false;
}
monitor_t *prev_monitor = NULL;
xcb_randr_monitor_info_iterator_t monitor_iter;
for (monitor_iter = xcb_randr_get_monitors_monitors_iterator(monitors_reply);
monitor_iter.rem; xcb_randr_monitor_info_next(&monitor_iter)) {
monitor_t *monitor = p_new(monitor_t, 1);
monitor->geometry.x = monitor_iter.data->x;
monitor->geometry.y = monitor_iter.data->y;
monitor->geometry.width = monitor_iter.data->width;
monitor->geometry.height = monitor_iter.data->height;
xcb_get_atom_name_cookie_t name_cookie =
xcb_get_atom_name_unchecked(wm.xcb_conn, monitor_iter.data->name);
xcb_get_atom_name_reply_t *name_reply =
xcb_get_atom_name_reply(wm.xcb_conn, name_cookie, NULL);
if (name_reply) {
char *name = xcb_get_atom_name_name(name_reply);
int length = xcb_get_atom_name_name_length(name_reply);
monitor->name = memcpy(p_new(char, length + 1), name, length);
monitor->name[length] = '\0';
p_delete(&name_reply);
} else {
monitor->name = a_strdup("unknown");
}
if (prev_monitor) {
prev_monitor->next = monitor;
} else {
wm.monitor_list = monitor;
prev_monitor = monitor;
}
}
return true;
}
static bool wm_detect_monitor_by_xinerama(void) {
xcb_xinerama_is_active_cookie_t active_cookie =
xcb_xinerama_is_active(wm.xcb_conn);
xcb_xinerama_is_active_reply_t *active_reply =
xcb_xinerama_is_active_reply(wm.xcb_conn, active_cookie, NULL);
if (!active_reply || !active_reply->state) {
p_delete(&active_reply);
return false;
}
xcb_xinerama_query_screens_cookie_t screens_cookie =
xcb_xinerama_query_screens(wm.xcb_conn);
xcb_xinerama_query_screens_reply_t *screens_reply =
xcb_xinerama_query_screens_reply(wm.xcb_conn, screens_cookie, NULL);
if (screens_reply == NULL) return false;
int count = xcb_xinerama_query_screens_screen_info_length(screens_reply);
if (count <= 0) {
p_delete(&screens_reply);
return false;
}
xcb_xinerama_screen_info_t *screen_info =
xcb_xinerama_query_screens_screen_info(screens_reply);
monitor_t *prev_monitor = NULL;
for (int i = 0; i < count; i++) {
monitor_t *monitor = p_new(monitor_t, 1);
monitor->geometry.x = screen_info[i].x_org;
monitor->geometry.y = screen_info[i].y_org;
monitor->geometry.width = screen_info[i].width;
monitor->geometry.height = screen_info[i].height;
if (prev_monitor) {
prev_monitor->next = monitor;
} else {
wm.monitor_list = monitor;
prev_monitor = monitor;
}
}
p_delete(&screens_reply);
return true;
}
void wm_detect_monitor(void) {
if (wm.have_randr && wm_detect_monitor_by_randr()) return;
if (wm.have_xinerama && wm_detect_monitor_by_xinerama()) return;
if (wm.screen == NULL) fatal("cannot detect monitor info");
monitor_t *monitor = p_new(monitor_t, 1);
monitor->geometry.x = 0;
monitor->geometry.y = 0;
monitor->geometry.width = wm.screen->width_in_pixels;
monitor->geometry.height = wm.screen->height_in_pixels;
wm.monitor_list = monitor;
}
void wm_scan_clients(void) {}
void wm_restart(void) {
wm_atexit(true);
execvp(cmd_argv[0], cmd_argv);
fatal("execv() failed: %s", strerror(errno));
}
void wm_quit(void) {
if (g_main_loop_is_running(wm.loop)) {
g_main_loop_quit(wm.loop);
}
}
static void debug_show_monitor_list(void) {
printf("\n========================== monitors ==========================\n");
for (monitor_t *m = wm.monitor_list; m; m = m->next) {
printf("x: %4d, y: %4d, width: %4u, height: %4u -> monitor[%s]\n",
m->geometry.x, m->geometry.y, m->geometry.width, m->geometry.height,
m->name);
}
}
int main(int argc, char *argv[]) {
p_clear(&wm, 1);
cmd_argv = argv;
wm_setup_signal();
wm_check_other_wm();
wm_check_xcb_extensions();
wm_detect_monitor();
debug_show_monitor_list();
if (wm.loop == NULL) {
wm.loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(wm.loop);
}
g_main_loop_unref(wm.loop);
wm.loop = NULL;
wm_atexit(false);
return EXIT_SUCCESS;
}

26
src/wm.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include "types.h"
typedef struct wm_t {
bool need_restart;
GMainLoop *loop;
client_t *client_list;
client_t *client_stack_list;
client_t *client_focused;
monitor_t *monitor_list;
monitor_t *current_monitor;
xcb_connection_t *xcb_conn;
xcb_screen_t *screen;
int default_screen;
bool have_xfixes;
bool have_xinerama;
bool have_randr;
} wm_t;
extern wm_t wm;
void wm_restart(void);
void wm_quit(void);

1
src/xwindow.h Normal file
View File

@@ -0,0 +1 @@
#pragma once