Compare commits
3 Commits
59a6177770
...
58d1b09203
| Author | SHA1 | Date | |
|---|---|---|---|
|
58d1b09203
|
|||
|
6ec4d8422f
|
|||
|
57c0250c44
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
/build/
|
||||
/zdwm
|
||||
/.cache/
|
||||
/Testing/
|
||||
|
||||
@@ -6,6 +6,7 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS ON)
|
||||
|
||||
set(APP_NAME "zdwm")
|
||||
set(TEST_APP_NAME "zdwm-tests")
|
||||
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
set(BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
@@ -24,6 +25,8 @@ project(${APP_NAME}
|
||||
LANGUAGES C
|
||||
)
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(${APP_NAME}
|
||||
${SOURCE_DIR}/wm.c
|
||||
${SOURCE_DIR}/utils.c
|
||||
@@ -51,6 +54,15 @@ add_executable(${APP_NAME}
|
||||
${SOURCE_DIR}/tray.c
|
||||
)
|
||||
|
||||
add_executable(${TEST_APP_NAME}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/main.c
|
||||
${SOURCE_DIR}/utils.c
|
||||
${SOURCE_DIR}/backend/x11.c
|
||||
${SOURCE_DIR}/core/layout.c
|
||||
${SOURCE_DIR}/core/runtime.c
|
||||
${SOURCE_DIR}/core/state.c
|
||||
)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
pkg_check_modules(deps REQUIRED
|
||||
@@ -86,11 +98,28 @@ target_include_directories(${APP_NAME}
|
||||
PRIVATE ${BUILD_DIR}
|
||||
)
|
||||
|
||||
target_include_directories(${TEST_APP_NAME} SYSTEM
|
||||
PRIVATE ${deps_INCLUDE_DIRS}
|
||||
)
|
||||
target_include_directories(${TEST_APP_NAME}
|
||||
PRIVATE ${SOURCE_DIR}
|
||||
PRIVATE ${BUILD_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(${APP_NAME}
|
||||
PRIVATE m
|
||||
PRIVATE ${deps_LIBRARIES}
|
||||
)
|
||||
|
||||
target_link_libraries(${TEST_APP_NAME}
|
||||
PRIVATE m
|
||||
PRIVATE ${deps_LIBRARIES}
|
||||
)
|
||||
|
||||
add_test(NAME ${TEST_APP_NAME} COMMAND $<TARGET_FILE:${TEST_APP_NAME}>)
|
||||
set_tests_properties(${TEST_APP_NAME} PROPERTIES
|
||||
ENVIRONMENT "DISPLAY=:3"
|
||||
)
|
||||
|
||||
## 配置项
|
||||
# Check for backtrace_symbols()
|
||||
@@ -101,6 +130,7 @@ if(NOT HAS_EXECINFO)
|
||||
if(LIB_EXECINFO)
|
||||
set(HAS_EXECINFO TRUE)
|
||||
target_link_libraries(${APP_NAME} ${LIB_EXECINFO})
|
||||
target_link_libraries(${TEST_APP_NAME} ${LIB_EXECINFO})
|
||||
endif()
|
||||
endif()
|
||||
if(HAS_EXECINFO)
|
||||
|
||||
5
Makefile
5
Makefile
@@ -39,4 +39,7 @@ build: prepare
|
||||
@cmake --build $(BUILD_DIR)
|
||||
@cp $(BUILD_DIR)/$(TARGET_NAME) $(TARGET)
|
||||
|
||||
.PHONY: clean run wm prepare build install uninstall reinstall
|
||||
test: build
|
||||
@ctest --test-dir $(BUILD_DIR)
|
||||
|
||||
.PHONY: clean run wm prepare build install uninstall reinstall test
|
||||
|
||||
252
src/backend/x11.c
Normal file
252
src/backend/x11.c
Normal file
@@ -0,0 +1,252 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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 "core/backend.h"
|
||||
#include "utils.h"
|
||||
#include "wm_desc.h"
|
||||
|
||||
struct wm_backend_t {
|
||||
xcb_connection_t *conn;
|
||||
xcb_screen_t *screen;
|
||||
int screenp;
|
||||
bool have_xfixes;
|
||||
};
|
||||
|
||||
wm_backend_t *wm_backend_create(const char *display_name) {
|
||||
int screen_num = 0;
|
||||
xcb_connection_t *conn = xcb_connect(display_name, &screen_num);
|
||||
int xcb_conn_error = xcb_connection_has_error(conn);
|
||||
if (xcb_conn_error) {
|
||||
fatal("cannot open display %s, error %d", display_name, xcb_conn_error);
|
||||
}
|
||||
|
||||
xcb_screen_t *screen = xcb_aux_get_screen(conn, screen_num);
|
||||
if (!screen) fatal("cannot get screen info");
|
||||
xcb_window_t root = screen->root;
|
||||
|
||||
uint32_t mask = XCB_CW_EVENT_MASK;
|
||||
const xcb_params_cw_t params = {
|
||||
.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||||
};
|
||||
xcb_void_cookie_t cookie =
|
||||
xcb_aux_change_window_attributes_checked(conn, root, mask, ¶ms);
|
||||
if (xcb_request_check(conn, cookie)) {
|
||||
fatal(
|
||||
"another window manager is already running (cannot select "
|
||||
"SubstructureRedirect)");
|
||||
}
|
||||
|
||||
wm_backend_t *backend = p_new(wm_backend_t, 1);
|
||||
backend->conn = conn;
|
||||
backend->screen = screen;
|
||||
backend->screenp = screen_num;
|
||||
|
||||
xcb_prefetch_extension_data(conn, &xcb_xfixes_id);
|
||||
const xcb_query_extension_reply_t *query =
|
||||
xcb_get_extension_data(conn, &xcb_xfixes_id);
|
||||
if (query && query->present) {
|
||||
xcb_xfixes_query_version_cookie_t cookie = xcb_xfixes_query_version(
|
||||
conn, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION);
|
||||
xcb_xfixes_query_version_reply_t *reply =
|
||||
xcb_xfixes_query_version_reply(conn, cookie, nullptr);
|
||||
|
||||
if (reply) {
|
||||
backend->have_xfixes = true;
|
||||
p_delete(&reply);
|
||||
}
|
||||
} else {
|
||||
backend->have_xfixes = false;
|
||||
}
|
||||
|
||||
return backend;
|
||||
}
|
||||
|
||||
void wm_backend_destroy(wm_backend_t *backend) {
|
||||
if (!backend) return;
|
||||
|
||||
xcb_disconnect(backend->conn);
|
||||
backend->conn = nullptr;
|
||||
free(backend);
|
||||
}
|
||||
|
||||
static bool detect_monitor_by_randr(const wm_backend_t *backend,
|
||||
wm_output_info_t **outputs, size_t *count) {
|
||||
xcb_prefetch_extension_data(backend->conn, &xcb_randr_id);
|
||||
const xcb_query_extension_reply_t *query =
|
||||
xcb_get_extension_data(backend->conn, &xcb_randr_id);
|
||||
if (!query || !query->present) return false;
|
||||
|
||||
xcb_randr_query_version_cookie_t cookie = xcb_randr_query_version(
|
||||
backend->conn, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
|
||||
xcb_randr_query_version_reply_t *reply =
|
||||
xcb_randr_query_version_reply(backend->conn, cookie, nullptr);
|
||||
if (!reply) return false;
|
||||
|
||||
/* xcb_randr_get_monitors 接口是 1.5 引入的 */
|
||||
uint32_t major_version = reply->major_version;
|
||||
uint32_t minor_version = reply->minor_version;
|
||||
if (major_version < 1 || (major_version == 1 && minor_version < 5)) {
|
||||
p_delete(&reply);
|
||||
return false;
|
||||
}
|
||||
p_delete(&reply);
|
||||
|
||||
xcb_randr_get_monitors_cookie_t monitors_cookie =
|
||||
xcb_randr_get_monitors(backend->conn, backend->screen->root, 1);
|
||||
xcb_randr_get_monitors_reply_t *monitors_reply =
|
||||
xcb_randr_get_monitors_reply(backend->conn, monitors_cookie, nullptr);
|
||||
if (!monitors_reply) {
|
||||
warn("RandR get monitor failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
int len = xcb_randr_get_monitors_monitors_length(monitors_reply);
|
||||
if (len <= 0) {
|
||||
p_delete(&monitors_reply);
|
||||
return false;
|
||||
}
|
||||
|
||||
wm_output_info_t *output_list = p_new(wm_output_info_t, len);
|
||||
xcb_randr_monitor_info_iterator_t iter =
|
||||
xcb_randr_get_monitors_monitors_iterator(monitors_reply);
|
||||
for (int i = 0; iter.rem; xcb_randr_monitor_info_next(&iter), i++) {
|
||||
wm_output_info_t *output = &output_list[i];
|
||||
output->geometry.x = iter.data->x;
|
||||
output->geometry.y = iter.data->y;
|
||||
output->geometry.width = iter.data->width;
|
||||
output->geometry.height = iter.data->height;
|
||||
|
||||
xcb_get_atom_name_cookie_t name_cookie =
|
||||
xcb_get_atom_name_unchecked(backend->conn, iter.data->name);
|
||||
xcb_get_atom_name_reply_t *name_reply =
|
||||
xcb_get_atom_name_reply(backend->conn, name_cookie, nullptr);
|
||||
|
||||
if (name_reply) {
|
||||
char *name = xcb_get_atom_name_name(name_reply);
|
||||
int length = xcb_get_atom_name_name_length(name_reply);
|
||||
output->name = strndup(name, length);
|
||||
p_delete(&name_reply);
|
||||
}
|
||||
}
|
||||
|
||||
p_delete(&monitors_reply);
|
||||
|
||||
if (count) *count = (size_t)len;
|
||||
if (outputs) {
|
||||
*outputs = output_list;
|
||||
} else {
|
||||
for (size_t i = 0; i < (size_t)len; i++) p_delete(&output_list[i].name);
|
||||
p_delete(&output_list);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool detect_monitor_by_xinerama(const wm_backend_t *backend,
|
||||
wm_output_info_t **outputs,
|
||||
size_t *count) {
|
||||
xcb_prefetch_extension_data(backend->conn, &xcb_xinerama_id);
|
||||
const xcb_query_extension_reply_t *query =
|
||||
xcb_get_extension_data(backend->conn, &xcb_xinerama_id);
|
||||
if (!query || !query->present) return false;
|
||||
|
||||
xcb_xinerama_query_version_cookie_t cookie = xcb_xinerama_query_version(
|
||||
backend->conn, XCB_XINERAMA_MAJOR_VERSION, XCB_XINERAMA_MINOR_VERSION);
|
||||
xcb_xinerama_query_version_reply_t *version_reply =
|
||||
xcb_xinerama_query_version_reply(backend->conn, cookie, nullptr);
|
||||
if (!version_reply) return false;
|
||||
p_delete(&version_reply);
|
||||
|
||||
xcb_xinerama_is_active_cookie_t active_cookie =
|
||||
xcb_xinerama_is_active(backend->conn);
|
||||
xcb_xinerama_is_active_reply_t *active_reply =
|
||||
xcb_xinerama_is_active_reply(backend->conn, active_cookie, nullptr);
|
||||
bool active = active_reply && active_reply->state;
|
||||
if (active_reply) p_delete(&active_reply);
|
||||
if (!active) return false;
|
||||
|
||||
xcb_xinerama_query_screens_cookie_t screens_cookie =
|
||||
xcb_xinerama_query_screens(backend->conn);
|
||||
xcb_xinerama_query_screens_reply_t *screens_reply =
|
||||
xcb_xinerama_query_screens_reply(backend->conn, screens_cookie, nullptr);
|
||||
|
||||
if (!screens_reply) return false;
|
||||
|
||||
int len = xcb_xinerama_query_screens_screen_info_length(screens_reply);
|
||||
if (len <= 0) {
|
||||
p_delete(&screens_reply);
|
||||
return false;
|
||||
}
|
||||
|
||||
xcb_xinerama_screen_info_t *screen_info =
|
||||
xcb_xinerama_query_screens_screen_info(screens_reply);
|
||||
if (!screen_info) {
|
||||
p_delete(&screens_reply);
|
||||
return false;
|
||||
}
|
||||
|
||||
wm_output_info_t *output_list = p_new(wm_output_info_t, len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
wm_output_info_t *output = &output_list[i];
|
||||
output->geometry.x = screen_info[i].x_org;
|
||||
output->geometry.y = screen_info[i].y_org;
|
||||
output->geometry.width = screen_info[i].width;
|
||||
output->geometry.height = screen_info[i].height;
|
||||
}
|
||||
p_delete(&screens_reply);
|
||||
|
||||
if (count) *count = (size_t)len;
|
||||
if (outputs) {
|
||||
*outputs = output_list;
|
||||
} else {
|
||||
for (size_t i = 0; i < (size_t)len; i++) p_delete(&output_list[i].name);
|
||||
p_delete(&output_list);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wm_backend_detect_t *wm_backend_detect(wm_backend_t *backend) {
|
||||
wm_backend_detect_t *detect = p_new(wm_backend_detect_t, 1);
|
||||
|
||||
wm_output_info_t *outputs = nullptr;
|
||||
size_t count = 0;
|
||||
|
||||
if (detect_monitor_by_randr(backend, &outputs, &count)) {
|
||||
detect->output_count = count;
|
||||
detect->outputs = outputs;
|
||||
} else if (detect_monitor_by_xinerama(backend, &outputs, &count)) {
|
||||
detect->output_count = count;
|
||||
detect->outputs = outputs;
|
||||
} else {
|
||||
wm_output_info_t *output = p_new(wm_output_info_t, 1);
|
||||
output->geometry.x = 0;
|
||||
output->geometry.y = 0;
|
||||
output->geometry.width = backend->screen->width_in_pixels;
|
||||
output->geometry.height = backend->screen->height_in_pixels;
|
||||
|
||||
detect->outputs = output;
|
||||
detect->output_count = 1;
|
||||
}
|
||||
|
||||
return detect;
|
||||
}
|
||||
|
||||
void wm_backend_detect_destroy(wm_backend_detect_t *detect) {
|
||||
if (!detect) return;
|
||||
|
||||
for (size_t i = 0; i < detect->output_count; i++) {
|
||||
p_delete(&detect->outputs[i].name);
|
||||
}
|
||||
|
||||
p_delete(&detect->outputs);
|
||||
free(detect);
|
||||
}
|
||||
18
src/core/backend.h
Normal file
18
src/core/backend.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "wm_desc.h"
|
||||
|
||||
typedef struct wm_backend_t wm_backend_t;
|
||||
|
||||
typedef struct wm_backend_detect_t {
|
||||
wm_output_info_t *outputs;
|
||||
size_t output_count;
|
||||
} wm_backend_detect_t;
|
||||
|
||||
wm_backend_t *wm_backend_create(const char *display_name);
|
||||
void wm_backend_destroy(wm_backend_t *backend);
|
||||
|
||||
wm_backend_detect_t *wm_backend_detect(wm_backend_t *backend);
|
||||
void wm_backend_detect_destroy(wm_backend_detect_t *detect);
|
||||
51
src/core/runtime.c
Normal file
51
src/core/runtime.c
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "core/runtime.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "core/backend.h"
|
||||
#include "core/state.h"
|
||||
#include "core/types.h"
|
||||
#include "utils.h"
|
||||
#include "wm_desc.h"
|
||||
|
||||
bool wm_runtime_init(wm_runtime_t *runtime) {
|
||||
p_clear(runtime, 1);
|
||||
|
||||
wm_backend_t *backend = wm_backend_create(nullptr);
|
||||
if (!backend) return false;
|
||||
|
||||
runtime->backend = backend;
|
||||
|
||||
wm_backend_detect_t *detect = wm_backend_detect(backend);
|
||||
if (!detect || detect->output_count <= 0) {
|
||||
wm_backend_destroy(backend);
|
||||
return false;
|
||||
}
|
||||
|
||||
wm_workspace_desc_t *workspaces =
|
||||
p_new(wm_workspace_desc_t, detect->output_count);
|
||||
static wm_layout_id_t ids[] = {0, 1, 2};
|
||||
for (size_t i = 0; i < detect->output_count; i++) {
|
||||
wm_workspace_desc_t *desc = &workspaces[i];
|
||||
desc->output_index = i;
|
||||
desc->name = "web";
|
||||
desc->layout_ids = ids;
|
||||
desc->layout_count = countof(ids);
|
||||
desc->initial_layout_id = 1;
|
||||
}
|
||||
|
||||
wm_state_init(&runtime->state, detect->outputs, detect->output_count,
|
||||
workspaces, detect->output_count);
|
||||
p_delete(&workspaces);
|
||||
wm_backend_detect_destroy(detect);
|
||||
detect = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wm_runtime_shutdown(wm_runtime_t *runtime) {
|
||||
runtime->running = false;
|
||||
wm_state_cleanup(&runtime->state);
|
||||
wm_backend_destroy(runtime->backend);
|
||||
runtime->backend = nullptr;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/backend.h"
|
||||
#include "core/state.h"
|
||||
|
||||
typedef struct wm_runtime_t {
|
||||
@@ -7,6 +8,7 @@ typedef struct wm_runtime_t {
|
||||
bool will_restart;
|
||||
|
||||
wm_state_t state;
|
||||
wm_backend_t *backend;
|
||||
} wm_runtime_t;
|
||||
|
||||
bool wm_runtime_init(wm_runtime_t *runtime);
|
||||
|
||||
@@ -84,7 +84,7 @@ void _fatal(int line, const char *function, const char *file,
|
||||
__attribute__((format(printf, 4, 5)));
|
||||
|
||||
#define warn(format, ...) \
|
||||
_fatal(__LINE__, __FUNCTION__, __FILE__, format, ##__VA_ARGS__)
|
||||
_warn(__LINE__, __FUNCTION__, __FILE__, format, ##__VA_ARGS__)
|
||||
void _warn(int line, const char *function, const char *file, const char *format,
|
||||
...) __attribute__((format(printf, 4, 5)));
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "core/types.h"
|
||||
|
||||
typedef struct wm_output_info_t {
|
||||
const char *name;
|
||||
char *name;
|
||||
wm_rect_t geometry;
|
||||
} wm_output_info_t;
|
||||
|
||||
@@ -42,8 +42,8 @@ typedef struct wm_workspace_desc_t {
|
||||
*
|
||||
* 这组接口只校验描述表自身的一致性,不访问 state。
|
||||
*/
|
||||
static inline bool
|
||||
wm_workspace_desc_layouts_valid(const wm_workspace_desc_t *workspace) {
|
||||
static inline bool wm_workspace_desc_layouts_valid(
|
||||
const wm_workspace_desc_t *workspace) {
|
||||
if (!workspace || !workspace->layout_count || !workspace->layout_ids) {
|
||||
return false;
|
||||
}
|
||||
|
||||
10
tests/main.c
Normal file
10
tests/main.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include "core/runtime.h"
|
||||
|
||||
int main(void) {
|
||||
wm_runtime_t runtime = {0};
|
||||
wm_runtime_init(&runtime);
|
||||
|
||||
wm_runtime_shutdown(&runtime);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user