我希望了解 Linux 桌面环境如何与 Xserver 一起使用。我在读到大多数窗口管理器不直接打开套接字,而是使用 Xlib 绑定来编写 WM 的任何语言,或者您可以使用更高级别的绑定 XCB;但我想知道直接向 Xserver 打开套接字有什么好处?
3 回答
我不会告诉你优点,但我会告诉你如何去做。
前段时间我发现了一个人的例子,我再也找不到了,但这里或多或少是他的代码。了解 XCB 等客户端库的幕后情况非常有趣。
为了向 X 服务器发送请求,您需要:
- 到 X 服务器的套接字连接
- 请求的操作码
- 与请求相关的数据
打开套接字、创建图形上下文、创建窗口和映射窗口的 API 可能类似于:
x11_connection_t connection = {0};
x11_init_connection(&connection);
x11_state_t state;
state.socket_fd = connection.socket_fd;
state.id_base = connection.setup->id_base;
state.root_window = connection.root[0].window_id;
state.root_visual = connection.root[0].visual_id;
state.root_depth = connection.root[0].depth;
state.gcontext = x11_init_gc(&state, X11_GC_GRAPHICS_EXPOSURES, (u32[]){X11_EXPOSURES_NOT_ALLOWED});
state.window = x11_init_window(&state, 0,0, WIDTH,HEIGHT, state.root_window, state.root_visual,
X11_CW_BACK_PIXEL | X11_CW_EVENT_MASK,
(u32[]){XCB_RGB_BLUE, X11_EVENT_MASK_KEY_PRESS | X11_EVENT_MASK_POINTER_MOTION}); // X11_EVENT_MASK_KEY_PRESS | X11_EVENT_MASK_POINTER_MOTION | X11_EVENT_MASK_STRUCTURE_NOTIFY | X11_EVENT_MASK_EXPOSURE
x11_map_window(&state, state.window);
但首先我们需要实现它。
为了说明,这里是前 13 个 X 协议操作码:
#define X11_OPCODE_CREATE_WINDOW 1
#define X11_OPCODE_CHANGE_WINDOW_ATTRIBUTES 2
#define X11_OPCODE_GET_WINDOW_ATTRIBUTES 3
#define X11_OPCODE_DESTROY_WINDOW 4
#define X11_OPCODE_DESTROY_SUBWINDOWS 5
#define X11_OPCODE_CHANGE_SAVE_SET 6
#define X11_OPCODE_REPARENT_WINDOW 7
#define X11_OPCODE_MAP_WINDOW 8
#define X11_OPCODE_MAP_SUBWINDOWS 9
#define X11_OPCODE_UNMAP_WINDOW 10
#define X11_OPCODE_UNMAP_SUBWINDOWS 11
#define X11_OPCODE_CONFIGURE_WINDOW 12
#define X11_OPCODE_CIRCULATE_WINDOW 13
您可以使用如下函数打开套接字,
int x11_init_socket(){
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); // Create the socket!
struct sockaddr_un serv_addr = {0};
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, "/tmp/.X11-unix/X0");
int srv_len = sizeof(struct sockaddr_un);
connect(socket_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
return socket_fd;
}
然后你需要握手,
int x11_init_connection(x11_connection_t* connection){
connection->socket_fd = x11_init_socket();
x11_connection_request_t request = {0};
request.order = 'l'; // Little endian!
request.major = 11;
request.minor = 0; // Version 11.0
write(connection->socket_fd, &request, sizeof(x11_connection_request_t)); // Send request
read(connection->socket_fd, &connection->header, sizeof(x11_connection_reply_t)); // Read reply header
if(connection->header.success == 0) return connection->header.success; // Error handling!
connection->setup = sbrk(connection->header.length * 4); // Allocate memory for remainder of data
read(connection->socket_fd, connection->setup, connection->header.length * 4); // Read remainder of data
void* p = ((void*)connection->setup);
p += sizeof(x11_connection_setup_t) + connection->setup->vendor_length; // Ignore the vendor
connection->format = p; p += sizeof(x11_pixmap_format_t) * connection->setup->formats; // Align struct with format sections. Move pointer to end of section
connection->root = p; p += sizeof(x11_root_t) * connection->setup->roots; // Align struct with root section(s). Move pointer to end of section
connection->depth = p; p += sizeof(x11_depth_t); // Align depth struct with first depth section. Move pointer to end of section
connection->visual = p; // Align visual with first visual for first depth
return connection->header.success;
}
和一堆 X 服务器喜欢的数据结构:
typedef struct{
u8 order;
u8 pad1;
u16 major, minor;
u16 auth_proto, auth_data;
u16 pad2;
}x11_connection_request_t;
typedef struct{
u8 success;
u8 pad1;
u16 major, minor;
u16 length;
}x11_connection_reply_t;
typedef struct{
u32 release;
u32 id_base, id_mask;
u32 motion_buffer_size;
u16 vendor_length;
u16 request_max;
u8 roots;
u8 formats;
u8 image_order;
u8 bitmap_order;
u8 scanline_unit, scanline_pad;
u8 keycode_min, keycode_max;
u32 pad;
}x11_connection_setup_t;
typedef struct{
u8 depth;
u8 bpp;
u8 scanline_pad;
u8 pad1;
u32 pad2;
}x11_pixmap_format_t;
typedef struct{
u32 window_id;
u32 colormap;
u32 white, black;
u32 input_mask;
u16 width, height;
u16 width_mm, height_mm;
u16 maps_min, maps_max;
u32 visual_id;
u8 backing_store;
u8 save_unders;
u8 depth;
u8 depths;
}x11_root_t;
typedef struct{
u8 depth;
u8 pad1;
u16 visuals;
u32 pad2;
}x11_depth_t;
typedef struct{
u8 group;
u8 bits;
u16 colormap_entries;
u32 mask_red, mask_green, mask_blue;
u32 pad;
}x11_visual_t;
typedef struct{
u8 response_type; /**< Type of the response */
u8 pad0; /**< Padding */
u16 sequence; /**< Sequence number */
u32 pad[7]; /**< Padding */
u32 full_sequence; /**< full sequence */
}x11_generic_event_t;
typedef struct{
u8 success;
u8 code;
u16 seq;
u32 id;
u16 op_major;
u8 op_minor;
u8 pad[21];
}x11_error_t;
typedef struct{
int socket_fd;
x11_connection_reply_t header;
x11_connection_setup_t* setup;
x11_pixmap_format_t* format;
x11_root_t* root;
x11_depth_t* depth;
x11_visual_t* visual;
}x11_connection_t;
typedef struct{
int socket_fd;
u32 id_base; // We'll use this to generate 32-bit identifiers!
u32 root_window;
u32 root_visual;
u8 root_depth;
u32 window;
u32 gcontext;
}x11_state_t;
您还需要一个函数来生成 32 位标识符:
u32 x11_generate_id(x11_connection_t* connection){
static u32 id = 0;
return (id++ | connection->setup->id_base);
}
最后,您需要将数据包与请求组合在一起的实际函数。以下是发送X11_OPCODE_CREATE_GC
、X11_OPCODE_CREATE_WINDOW
、X11_OPCODE_MAP_WINDOW
和的辅助函数X11_OPCODE_PUT_IMAGE
。
#define X11_NONE 0x00000000L // X11_NONE is the universal null resource or null atom parameter value for many core X requests
#define X11_COPY_FROM_PARENT 0x00000000L // X11_COPY_FROM_PARENT can be used for many xcb_create_window parameters
#define X11_CURRENT_TIME 0x00000000L // X11_CURRENT_TIME can be used in most requests that take an xcb_timestamp_t
#define X11_NO_SYMBOL 0x00000000L // X11_NO_SYMBOL fills in unused entries in xcb_keysym_t tables
enum x11_exposures_t{
X11_EXPOSURES_NOT_ALLOWED = 0,
X11_EXPOSURES_ALLOWED = 1,
X11_EXPOSURES_DEFAULT = 2
};
enum x11_gc_t{
X11_GC_FUNCTION = 1<<0,
X11_GC_PLANE_MASK = 1<<1,
X11_GC_FOREGROUND = 1<<2,
X11_GC_BACKGROUND = 1<<3,
X11_GC_LINE_WIDTH = 1<<4,
X11_GC_LINE_STYLE = 1<<5,
X11_GC_CAP_STYLE = 1<<6,
X11_GC_JOIN_STYLE = 1<<7,
X11_GC_FILL_STYLE = 1<<8,
X11_GC_FILL_RULE = 1<<9,
X11_GC_TILE = 1<<10,
X11_GC_STIPPLE = 1<<11,
X11_GC_TILE_STIPPLE_ORIGIN_X = 1<<12,
X11_GC_TILE_STIPPLE_ORIGIN_Y = 1<<13,
X11_GC_FONT = 1<<14,
X11_GC_SUBWINDOW_MODE = 1<<15,
X11_GC_GRAPHICS_EXPOSURES = 1<<16,
X11_GC_CLIP_ORIGIN_X = 1<<17,
X11_GC_CLIP_ORIGIN_Y = 1<<18,
X11_GC_CLIP_MASK = 1<<19,
X11_GC_DASH_OFFSET = 1<<20,
X11_GC_DASH_LIST = 1<<21,
X11_GC_ARC_MODE = 1<<22,
};
u32 x11_init_gc(x11_state_t* state, u32 value_mask, u32* value_list){
u32 gcontext_id = x11_generate_id(state);
u16 flag_count = popcnt(value_mask);
u16 length = 4 + flag_count;
u32* packet = sbrk(length * 4);
packet[0] = X11_OPCODE_CREATE_GC | (length<<16); // The first `u32` in the packet is always the opcode and the length of the packet!
packet[1] = gcontext_id;
packet[2] = state->root_window;
packet[3] = value_mask;
for(int i=0; i < flag_count; ++i)
packet[4 + i] = value_list[i];
write(state->socket_fd, packet, length * 4);
sbrk(-(length * 4));
return gcontext_id;
}
enum x11_cw_t{
X11_CW_BACK_PIXMAP = 1<<0,
X11_CW_BACK_PIXEL = 1<<1,
X11_CW_BORDER_PIXMAP = 1<<2,
X11_CW_BORDER_PIXEL = 1<<3,
X11_CW_BIT_GRAVITY = 1<<4,
X11_CW_WIN_GRAVITY = 1<<5,
X11_CW_BACKING_STORE = 1<<6,
X11_CW_BACKING_PLANES = 1<<7,
X11_CW_BACKING_PIXEL = 1<<8,
X11_CW_OVERRIDE_REDIRECT = 1<<9,
X11_CW_SAVE_UNDER = 1<<10,
X11_CW_EVENT_MASK = 1<<11,
X11_CW_DONT_PROPAGATE = 1<<12,
X11_CW_COLORMAP = 1<<13,
X11_CW_CURSOR = 1<<14
};
enum x11_event_mask_t{
X11_EVENT_MASK_NO_EVENT = 0,
X11_EVENT_MASK_KEY_PRESS = 1<<0, // 0x00000001
X11_EVENT_MASK_KEY_RELEASE = 1<<1, // 0x00000002
X11_EVENT_MASK_BUTTON_PRESS = 1<<2, // 0x00000004
X11_EVENT_MASK_BUTTON_RELEASE = 1<<3, // 0x00000008
X11_EVENT_MASK_ENTER_WINDOW = 1<<4, // 0x00000010
X11_EVENT_MASK_LEAVE_WINDOW = 1<<5, // 0x00000020
X11_EVENT_MASK_POINTER_MOTION = 1<<6, // 0x00000040
X11_EVENT_MASK_POINTER_MOTION_HINT = 1<<7, // 0x00000080
X11_EVENT_MASK_BUTTON_1_MOTION = 1<<8, // 0x00000100
X11_EVENT_MASK_BUTTON_2_MOTION = 1<<9, // 0x00000200
X11_EVENT_MASK_BUTTON_3_MOTION = 1<<10, // 0x00000400
X11_EVENT_MASK_BUTTON_4_MOTION = 1<<11, // 0x00000800
X11_EVENT_MASK_BUTTON_5_MOTION = 1<<12, // 0x00001000
X11_EVENT_MASK_BUTTON_MOTION = 1<<13, // 0x00002000
X11_EVENT_MASK_KEYMAP_STATE = 1<<14, // 0x00004000
X11_EVENT_MASK_EXPOSURE = 1<<15, // 0x00008000
X11_EVENT_MASK_VISIBILITY_CHANGE = 1<<16, // 0x00010000
X11_EVENT_MASK_STRUCTURE_NOTIFY = 1<<17, // 0x00020000
X11_EVENT_MASK_RESIZE_REDIRECT = 1<<18, // 0x00040000
X11_EVENT_MASK_SUBSTRUCTURE_NOTIFY = 1<<19, // 0x00080000
X11_EVENT_MASK_SUBSTRUCTURE_REDIRECT = 1<<20, // 0x00100000
X11_EVENT_MASK_FOCUS_CHANGE = 1<<21, // 0x00200000
X11_EVENT_MASK_PROPERTY_CHANGE = 1<<22, // 0x00400000
X11_EVENT_MASK_COLOR_MAP_CHANGE = 1<<23, // 0x00800000
X11_EVENT_MASK_OWNER_GRAB_BUTTON = 1<<24 // 0x01000000
};
#define X11_DEFAULT_BORDER 0
#define X11_DEFAULT_GROUP 0
u32 x11_init_window(x11_state_t* state, u16 x, u16 y, u16 w, u16 h, u32 window_parent, u32 visual, u32 value_mask, u32* value_list){
u32 window_id = x11_generate_id(state);
u16 flag_count = popcnt(value_mask);
u16 length = 8 + flag_count;
u32* packet = sbrk(length * 4);
packet[0] = X11_OPCODE_CREATE_WINDOW | length<<16; // The first `u32` in the packet is always the opcode and the length of the packet!
packet[1] = window_id;
packet[2] = window_parent;
packet[3] = x | (y<<16);
packet[4] = w | (h<<16);
packet[5] = (X11_DEFAULT_BORDER<<16) | X11_DEFAULT_GROUP;
packet[6] = visual;
packet[7] = value_mask;
for(int i=0;i < flag_count; ++i)
packet[8 + i] = value_list[i];
write(state->socket_fd, packet, length * 4);
sbrk(-(length * 4));
return window_id;
}
void x11_map_window(x11_state_t* state, u32 window_id){
int const length = 2;
u32 packet[length];
packet[0] = X11_OPCODE_MAP_WINDOW | (length<<16); // The first `u32` in the packet is always the opcode and the length of the packet!
packet[1] = window_id;
write(state->socket_fd, packet, 8);
}
enum x11_image_format_t{
X11_IMAGE_FORMAT_XY_BITMAP = 0,
X11_IMAGE_FORMAT_XY_PIXMAP = 1,
X11_IMAGE_FORMAT_Z_PIXMAP = 2
};
void x11_put_img(x11_state_t* state, u16 x, u16 y, u32* data){
u32 packet[6];
u16 const length = ((state->tile_width * state->tile_height)) + 6;
packet[0] = X11_OPCODE_PUT_IMAGE | (X11_IMAGE_FORMAT_Z_PIXMAP<<8) | (length<<16); // The first `u32` in the packet is always the opcode and the length of the packet!
packet[1] = state->window;
packet[2] = state->gcontext;
packet[3] = state->tile_width | (state->tile_height<<16);
packet[4] = x | (y<<16);
packet[5] = state->root_depth<<8;
write(state->socket_fd, packet, 24);
write(state->socket_fd, data, state->tile_width * state->tile_height * 4);
}
我已经用 node/javascript ( https://github.com/sidorares/node-x11/ ) 完成了它并不像看起来那么可怕,并且对于某些特定任务可能比 xlib 工作得更好(非平凡的并行性或资源依赖或间接 glx ),但其中最有价值的可能是教育。如果您只想完成一些工作,可能有可用的解决方案,您无需重新发明轮子
起点是 x11 协议文档:https ://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html
问题应该是“使用 Xlib 而不是像 gtk 这样的图形工具包有什么优势”?
即使要掌握 Xlib,你也得花费数月或数年的时间!它非常复杂,几乎每个要显示图形内容的应用程序都使用工具包。
使用普通套接字意味着直接使用 X11 协议,这意味着您最终将重新创建 Xlib(不,您不会完成!)
打个比方,普通套接字~机器码,xlib~汇编代码,工具包~高级语言。(尽管 xlib 为您做了一些维护,但我想这可以忽略不计)