flat assembler
Message board for the users of flat assembler.
Index
> Linux > [fasm2]XCB full window example (with button and framebuffer) |
| Author |
|
|
Jessé 13 Aug 2026, 17:36
So far, quite inspired by this excelent post, I've created a full window example, but using the (supposedly) more efficient 'libxcb' instead of 'libX11', which showcases:
- Advanced window properties, like, 32-bit RGBA color handling, fixed size, title name, and more; - A shared memory "framebuffer" that can be drawn pixel-by-pixel by software, which actually will be copied to window real framebuffer on paint; - A custom background, generated by software, using a quite cool stub idea that occured to me when trying to deal with 'OK son, now you've got a 32-bit RGBA window framebuffer, so, whats next?' question; - A custom icon which, as of the custom background, is uniquely drawn at every single run, because its result is based on random data from RNG unit, as well as TSC values; - "Widgets", done in the Microsoft way (everything is a window), and handling quite the same. A fully functional and animated button, and also a label; - Using Cairo to render text, so it will be more modern looking; - Filtering and handling events, as well as an easy "click event" example, emulated by control over BUTTON_PRESS and BUTTON_RELEASE events. Here, the complete source for rapid reference: Code: format ELF64 ; Dynamic address ELF include 'fastcall3.inc' ; All available at: https://github.com/Jesse-6/fastcall include 'stdio.inc' ; include 'xcb.inc' ; include 'xcb-shm.inc' ; include 'xcb-icccm.inc' ; include 'cairo.inc' ; ; Short offset names for those structures define_offset art XCB_INTERN_ATOM_REPLY_T define_offset bpe XCB_BUTTON_PRESS_EVENT_T define_offset bre XCB_BUTTON_PRESS_EVENT_T define_offset cme XCB_CLIENT_MESSAGE_EVENT_T define_offset eet XCB_EXPOSE_EVENT_T define_offset ene XCB_ENTER_NOTIFY_EVENT_T define_offset get XCB_GENERIC_EVENT_T define_offset lne XCB_ENTER_NOTIFY_EVENT_T define_offset mne XCB_MOTION_NOTIFY_EVENT_T define_offset qer XCB_QUERY_EXTENSION_REPLY_T define_offset scr XCB_SCREEN_T define_offset xdi XCB_DEPTH_ITERATOR_T define_offset xdt XCB_DEPTH_T define_offset xge XCB_GENERIC_ERROR_T define_offset xvi XCB_VISUALTYPE_ITERATOR_T define_offset xvt XCB_VISUALTYPE_T ; Window parameters WIDTH = 400 HEIGHT = 240 ; Button parameters B_WIDTH = (WIDTH / 3) - 10 B_HEIGHT = (HEIGHT / 6) ; Label parameters L_WIDTH = WIDTH - 40 L_HEIGHT = 20 ; Start by parsing command line _code Start entry: cmp [rsp], dword 2 jb @1f ja .err mov r11, [rsp+16] cmp [r11], dword '-B1' ; use algorithm 1 to draw background jne @f or [bg_mode], 1 jmp @1f @@ cmp [r11], dword '-B2' ; use algorithm 2 to draw background (default) jne @f and [bg_mode], 0 jmp @1f @@ cmp [r11], dword '-B3' ; use algorithm 1 and remove random dots jne @f or [bg_mode], 11b jmp @1f @@ cmp [r11], dword '-B4' ; use algorithm 2 and remove random dots jne .err or [bg_mode], 10b @1 libc.StartMain(Main); ; Start main() using libc (this is a macro!) .err: fprintf(stderr, "Invalid argument."\n \ "Accepted arguments: -B1 | -B2 | -B3 | -B4."\n); exit(-1); ; main() routine Main: push 0 push r13 push r12 push r15 push rbx push r14 push rbp ; Create a connection xcb_connect(NULL, NULL); test rax, rax jnz @f fprintf(stderr, "Could not connect to X server."\n); mov [rsp+48], byte 1 jmp .end @@ mov r15, rax ; Render 5 x 5 icon (with a centered, random colors 3 x 3 square) lea rdi, [dyn_icon] ; Generate random icon mov edx, 0_FF_000000h mov rax, 00000005_00000005h stosq xor eax, eax stosq stosq stosq mov rsi, rdi add rdi, 12 stosq add rdi, 12 stosq add rdi, 12 stosq stosq stosq mov rdi, rsi mov ecx, 3 @@ pause rdrand rax ; Avoiding AMD "no zero at size" problem jnc @b or eax, edx ; by only using 32 bit of 64-bit generated value stosd loop @b mov ecx, 3 add rdi, 8 @@ pause rdrand rax jnc @b or eax, edx stosd loop @b mov ecx, 3 add rdi, 8 @@ pause rdrand rax jnc @b or eax, edx stosd loop @b ; Iterate once to obtain current screen xcb_get_setup(r15); xcb_setup_roots_iterator(rax); mov r14, rax ; Find 32-bit, RGBA visual ID (if supported) @bss p.visual xq ? xcb_screen_allowed_depths_iterator(rax); push rdx push rax @@ mov r10, [rsp+xdi.data] cmp [r10+xdt.depth], 32 je @1f test [rsp+xdi.rem], -1 jz @f xcb_depth_next(rsp); jmp @b @@ fprintf(stderr, "Couldn't find 32-bit color depth support."\n); xcb_disconnect(r15); add rsp, 16 mov [rsp+48], byte 1 jmp Main.end @1 xcb_depth_visuals_iterator(r10); mov ebx, [rax+xvt.visual_id] ; visual_id; rax = *xcb_visualtype_t mov [p.visual], rax ; save this for Cairo surface add rsp, 16 ; Check support for MIT-SHM (copy to framebuffer rendering) xcb_get_extension_data(r15, &xcb_shm_id); test rax, rax jnz @f @2 fprintf(stderr, "Couldn't find MIT-SHM support."\n); xcb_disconnect(r15); jmp Main.end @@ test [rax+qer.present], -1 jz @2b @bss window xd ? @bss colormap xd ? xcb_generate_id(r15); mov [window], eax xcb_generate_id(r15); mov [colormap], eax ; Create atoms for window icon and window close request xcb_intern_atom(r15, FALSE, 12, "WM_PROTOCOLS"); mov ebp, eax xcb_intern_atom(r15, FALSE, 16, "WM_DELETE_WINDOW"); push rax push rbp xcb_intern_atom(r15, FALSE, 12, "_NET_WM_ICON"); mov ebp, eax ; Needed: create a new colormap for 32-bit mode colors xcb_create_colormap(r15, XCB_COLORMAP_ALLOC_NONE, colormap, [r14+scr.root], ebx); ; Configured window attributes mov edx, XCB_EVENT_MASK_EXPOSURE or XCB_EVENT_MASK_POINTER_MOTION mov eax, [colormap] mov rcx, 90200000_90200000h shl rax, 32 or rdx, rax push rdx push rcx mov r9, rsp ; Create main window xcb_create_window(r15, 32, window, [r14+scr.root], 0, 0, WIDTH, HEIGHT, 0, \ XCB_WINDOW_CLASS_INPUT_OUTPUT, ebx, XCB_CW_BACK_PIXEL or \ XCB_CW_BORDER_PIXEL or XCB_CW_COLORMAP or XCB_CW_EVENT_MASK, \ r9); ; Create child window, which will be a button xcb_generate_id(r15); mov r10d, [colormap] mov [button], eax mov [button.cmap], r10d xcb_create_window_checked(r15, XCB_COPY_FROM_PARENT, button, window, (WIDTH / 3) + 5, \ HEIGHT - (HEIGHT / 3), B_WIDTH, B_HEIGHT, 0, \ XCB_WINDOW_CLASS_INPUT_OUTPUT, ebx, XCB_CW_BACK_PIXEL or \ XCB_CW_COLORMAP or XCB_CW_OVERRIDE_REDIRECT or XCB_CW_EVENT_MASK, \ button.attr); xcb_request_check(r15, eax); test rax, rax jz @f movzx edx, [rax+xge.error_code] fprintf(stderr, "Fail to create button: %u"\n, edx); xcb_disconnect(r15); add rsp, 32 jmp Main.end ; Create child window, which will be a label @@ xcb_generate_id(r15); mov r11d, [colormap] mov [label], eax mov [label.cmap], r11d xcb_create_window_checked(r15, XCB_COPY_FROM_PARENT, label, window, 20, \ 20, L_WIDTH, L_HEIGHT, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, \ ebx, XCB_CW_BACK_PIXEL or XCB_CW_COLORMAP or \ XCB_CW_OVERRIDE_REDIRECT or XCB_CW_EVENT_MASK, \ label.attr); xcb_request_check(r15, eax); test rax, rax jz @f movzx edx, [rax+xge.error_code] fprintf(stderr, "Fail to create label: %u"\n, edx); xcb_disconnect(r15); add rsp, 32 jmp Main.end @@ add rsp, 16 ; Set minimum and maximum window sizes to be the same, so resizing window ; and maximize button are disabled (window with fixed size) xcb_icccm_size_hints_set_min_size(win_attr, WIDTH, HEIGHT); xcb_icccm_size_hints_set_max_size(win_attr, WIDTH, HEIGHT); mov [win_attr.flags], \ XCB_ICCCM_SIZE_HINT_P_MIN_SIZE or XCB_ICCCM_SIZE_HINT_P_MAX_SIZE xcb_icccm_set_wm_size_hints(r15, window, XCB_ATOM_WM_NORMAL_HINTS, win_attr); ; Gather replies from previously requested atoms xcb_intern_atom_reply(r15, ebp, NULL); mov r13, rax pop rbp pop rax xcb_intern_atom_reply(r15, eax, NULL); xchg rax, rbp xcb_intern_atom_reply(r15, eax, NULL); mov r14, rax ; Apply close event, icon, window title and iconified title to main window xcb_change_property(r15, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_NAME, \ XCB_ATOM_STRING, 8, 27, "XCB Direct Framebuffer Demo"); xcb_change_property_checked(r15, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_ICON_NAME, \ XCB_ATOM_STRING, 8, 23, "XCB FB Demo - minimized"); xcb_request_check(r15, eax); test rax, rax jz @f movzx edx, [rax+xge.error_code] fprintf(stderr, "Error while applying iconified name: %u"\n, edx); @@ xcb_change_property(r15, XCB_PROP_MODE_REPLACE, window, [r13+art.atom], \ XCB_ATOM_CARDINAL, 32, dyn_icon.n, dyn_icon); xcb_change_property(r15, XCB_PROP_MODE_REPLACE, window, [r14+art.atom], \ XCB_ATOM_ATOM, 32, 1, rbp+art.atom); mov ebp, [rbp+art.atom] ; Create XCB graphic context, so we can render (copy) to framebuffer @bss gc xd ? xcb_generate_id(r15); mov [gc], eax xcb_create_gc(r15, eax, window, 0, NULL); ; Create and attach shared memory to be the manual render area @bss shmid xd ? shmget(IPC_PRIVATE, WIDTH * HEIGHT * 4, IPC_CREAT or 0666o); mov [shmid], eax shmat(eax, NULL, 0); mov r12, rax ; r12 = *framebuffer fprintf(stderr, "Copy to framebuffer at address: 0x%lX. Size: %u bytes."\n, \ rax, WIDTH * HEIGHT * 4); ; Attach it to XCB SHM, so it can be acccessed to the server-side (I guess) @bss shmsegid xd ? xcb_generate_id(r15); mov [shmsegid], eax xcb_shm_attach(r15, eax, shmid, 0); shmctl(shmid, IPC_RMID, NULL); ; Set remove on detach ; Render background algorithm 1 test [bg_mode], 1b jz @2f mov ecx, (WIDTH * HEIGHT) / 2 mov rdx, 0FF_000500_FF_000500h mov r10, 0FF_161514_FF_161514h mov rdi, r12 @@ pause rdrand rax or rax, rdx and rax, r10 stosq loop @b jmp @3f ; Render background algorithm 2 @2 mov rdi, r12 mov r10, 0_C6_17171C_C6_17171Ch mov r11, 0_FF_000916_FF_000916h mov r8, 0_1357_9BDF_2468_ACE0h rdtsc mov cl, ah shl rax, 32 or rdx, rax rol rax, cl xor r8, rdx mov ecx, WIDTH * HEIGHT / 2 @@ pause rdtsc shl rdx, 32 or rax, rdx xor r8, rax ror rax, cl xchg r8, rax and rax, r10 or rax, r11 stosq sfence loop @b ; Random dots (symmetric) blend mask @3 test [bg_mode], 10b jnz @4f mov ecx, WIDTH * HEIGHT @@ pause rdrand esi jnc @b shr esi, 10 cmp esi, WIDTH * HEIGHT jae @3f mov edx, 000_840210h mov r9d, [rsi*4+r12] shl r9, 2 add r9, rdx shr r9, 3 or r9d, 0FF_000000h and r9d, 0FF_77643Eh mov [rsi*4+r12], r9d neg rsi mov [r12-4+rsi*4+WIDTH*HEIGHT*4], r9d @3 loop @b @4 nop @bss button.surface xq ? @bss button.cr xq ? @bss ?label.surface xq ? @bss ?label.cr xq ? ; Setup cairo to render texts at our "widgets" cairo_xcb_surface_create(r15, button, p.visual, B_WIDTH, B_HEIGHT); mov [button.surface], rax cairo_create(rax); mov [button.cr], rax cairo_xcb_surface_create(r15, label, p.visual, L_WIDTH, L_HEIGHT); mov [label.surface], rax cairo_create(rax); mov [label.cr], rax cairo_select_font_face(rax, "Mono", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_select_font_face(button.cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(label.cr, 11.0); cairo_set_font_size(button.cr, 12.0); nop nop ; Map all windows, so they can be seen xcb_map_window(r15, window); xcb_map_window(r15, button); xcb_map_window(r15, label); xcb_flush(r15); ; Label window will be updated on every event loop iteration @1 xcb_clear_area(r15, FALSE, label, 0, 0, L_WIDTH, L_HEIGHT); cairo_move_to(label.cr, 3.0, 14.0); snprintf(buff1, 127, "Events: %u | Clicks: %u | Cursor x:%u y:%u", \ events, clicks, pointer.x, pointer.y); cairo_set_source_rgb(label.cr, 1.0, 1.0, 1.0); cairo_show_text(label.cr, buff1); xcb_flush(r15); ; Event loop idle catcher xcb_wait_for_event(r15); test rax, rax jz @1f inc [events] mov r14, rax mov ch, [rax+get.response_type] and ch, not 80h cmp ch, XCB_CLIENT_MESSAGE ; Close request will arrive as a client message jne @f cmp [r14+cme.data32+0*4], ebp ; WM_DELETE_WINDOW atom je @2f jmp @9f @@ cmp ch, XCB_EXPOSE ; Expose event (where to draw windows) jne @f mov r11d, [r14+eet.window] cmp r11d, [label] je @9f ; Do nothing for label, as we already update it every loop ; "Retext" button, because we are animating background using XCB native method @3 cmp r11d, [button] jne @3f cairo_set_source_rgb(button.cr, 1.0, 1.0, 1.0); cairo_move_to(button.cr, (B_WIDTH / 2.0) - 30.0, (B_HEIGHT / 2.0) + 6.0); cairo_show_text(button.cr, "Click me!"); xcb_flush(r15); jmp @9f ; Default expose: redraw window background by copying SHM memory @3 xcb_shm_put_image(r15, window, gc, WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT, 0, 0, 32, \ XCB_IMAGE_FORMAT_Z_PIXMAP, 0, shmsegid, 0); xcb_flush(r15); jmp @9f @@ cmp ch, XCB_ENTER_NOTIFY ; Enter window event jne @f mov r11d, [r14+ene.event] ; Window ID cmp r11d, [button] jne @9f ; Animate button by making it clear whenever pointer enters its area xcb_change_window_attributes(r15, r11d, XCB_CW_BACK_PIXEL, button.bg_enter); xcb_clear_area(r15, TRUE, button, 0, 0, B_WIDTH, B_HEIGHT); xcb_flush(r15); jmp @9f @@ cmp ch, XCB_LEAVE_NOTIFY ; Leave window event jne @f mov r10d, [r14+lne.event] cmp r10d, [button] jne @9f ; Restore button background to previous whenever point leaves its area and [clicks.flag], not 11b ; Clear both flags xcb_change_window_attributes(r15, r10d, XCB_CW_BACK_PIXEL, button.attr); xcb_clear_area(r15, TRUE, button, 0, 0, B_WIDTH, B_HEIGHT); xcb_flush(r15); fprintf(stderr, <13,27,"[2K",0>); ; Reset information string at CLI jmp @9f @@ cmp ch, XCB_BUTTON_PRESS ; Mouse button down event jne @f cmp [r14+bpe.detail], 1 ; Selecting only main button jne @4f or [clicks.flag], 11b ; Map click event by: down followed by up ; Animate button (on down) by darkening its background xcb_change_window_attributes(r15, button, XCB_CW_BACK_PIXEL, button.bg_click); xcb_clear_area(r15, TRUE, button, 0, 0, B_WIDTH, B_HEIGHT); xcb_flush(r15); @rdata evtfmt: xb 13, '%s event: flags: %08B | X: %u | Y: %u ', 0 @4 movzx ecx, [r14+bpe.detail] movzx r8d, [r14+bpe.event_x] movzx r9d, [r14+bpe.event_y] fprintf(stderr, evtfmt, "Press ", ecx, r8d, r9d); jmp @9f @@ cmp ch, XCB_BUTTON_RELEASE ; Mouse button up event jne @f cmp [r14+bpe.detail], 1 ; Selecting only main button jne @4f btr [clicks+4], 0 ; if previously pressed, then 'click' jnc @5f inc [clicks] ; clicked handling here! ; Restore button background, so it seems to be released @5 xcb_change_window_attributes(r15, button, XCB_CW_BACK_PIXEL, button.bg_enter); xcb_clear_area(r15, TRUE, button, 0, 0, B_WIDTH, B_HEIGHT); xcb_flush(r15); @4 movzx ecx, [r14+bpe.detail] movzx r8d, [r14+bpe.event_x] movzx r9d, [r14+bpe.event_y] fprintf(stderr, evtfmt, "Release", ecx, r8d, r9d); or [clicks.flag], 10b ; Use bit 1 to sync line at terminal jmp @9f @@ cmp ch, XCB_MOTION_NOTIFY jne @f mov r9d, [window] cmp [r14+mne.event], r9d jne @f movzx ecx, [r14+mne.event_x] movzx edx, [r14+mne.event_y] mov [pointer.x], ecx mov [pointer.y], edx jmp @9f @@ @9 free(r14); ; Event structure must always be freed after processing it! jmp @1b @rdata farewell: xb 10, 'Graceful close request accepted, cleaning up and finishing...', \ 10, 0 ; Click window close button will bring it here, instead of abort @2 test [clicks.flag], 10b ; Check if it needs a newline char lea rdx, [farewell] lea rsi, [rdx+1] cmovnz rsi, rdx fprintf(stderr, rsi); ; Release all allocated and created stuff @1 cairo_destroy(button.cr); cairo_destroy(label.cr); cairo_surface_destroy(button.surface); cairo_surface_destroy(label.surface); xcb_destroy_window(r15, label); xcb_destroy_window(r15, button); xcb_destroy_window(r15, window); xcb_free_colormap(r15, colormap); xcb_shm_detach(r15, shmsegid); shmdt(r12); xcb_disconnect(r15); Main.end: pop rbp pop r14 pop rbx pop r15 pop r12 pop r13 pop rax ; Return previously pushed 0 value at beginning ret _bss align 16 dyn_icon: xd ?, ? .bitmap: xd *(5 * 5) .n = ($ - dyn_icon) / 4 win_attr XCB_SIZE_HINTS_T buff1: xb *128 _rdata align 16 _data align 16 events xd 0 clicks xd 0 .flag xb 0 bg_mode xb 0 button xd 0 .attr: .bgd xd 0FF_323232h .ovr xd 0 .evt xd XCB_EVENT_MASK_EXPOSURE or \ XCB_EVENT_MASK_ENTER_WINDOW or \ XCB_EVENT_MASK_LEAVE_WINDOW or \ XCB_EVENT_MASK_BUTTON_PRESS or \ XCB_EVENT_MASK_BUTTON_RELEASE .cmap xd 0 .bg_enter: xd 0FF_4C4C4Ch .bg_click: xd 0FF_232620h ?label xd 0 ; Using ?label to not conflict with fasmg 'label' keyword .attr: .bgd xd 0FF_323232h .ovr xd 0 .evt xd XCB_EVENT_MASK_EXPOSURE .cmap xd 0 pointer: .x xd 0 .y xd 0 Written at my current comfort zone syntax, but I know you guys can easily convert it (if you want), because you are amazing assembly code writers, and probably (like me) amazing C code interpreters... An important point I need to share is: for this to work with Wayland, you need to define XCB_CW_BORDER_PIXEL to the main window attributes even when not using a border at all: at XLibre (I suppose Xorg too), it isn't needed to define a border color for a border you don't have control (the compositor controls it, at least with plasma desktop). Tested on XLibre and on Wayland, as I am running it on Artix Linux with Plasma desktop. Any suggestions are welcome, as well as a Wayland-based code doing the same stuff (if one can really do it in assembly; by the way I'm currently researching do stuff with Wayland, too), so we can actually compare X with Wayland, regarding raw window management and performance, based on measurable facts, not only emotions about one favorite window system. So, to simplify: code snippets (that actually work) are warmly welcome, speculation about 'you should use this or that', not. By the way, I'll let for you guys to uncover that cool (or not) software-generated background I've talked above, without spoiling it with prints (again, if you want). Cheers,
|
|||||||||||
|
|
Jessé 15 Aug 2026, 10:29
I did another example, this one less "graphical", but with an important concept, which is one of the key points on using 'libxcb' according to their own statement: its multithreading capabilities.
What you'll see at this, can never, even in one wildest dreams, be done with GTK, for example. Every label you see at the window, is created, rendered, updated and destroyed by its own thread: everything in parallel, and completely async (as one can see by a random value to be the sleep time at the loop): Code: format ELF64 include 'fastcall3.inc' include 'stdio.inc' include 'xcb.inc' include 'xcb-icccm.inc' include 'cairo.inc' define_offset ar XCB_INTERN_ATOM_REPLY_T define_offset cm XCB_CLIENT_MESSAGE_EVENT_T define_offset s XCB_SCREEN_T define_offset ee XCB_EXPOSE_EVENT_T define_offset e XCB_GENERIC_ERROR_T define_offset ge XCB_GENERIC_EVENT_T _code Start entry: libc.StartMain(Main); Thread_label: push 0 ; ¹ push rbx push r14 push r12 push r15 push rbp push r13 mov ebx, edi ; ² received parameter from creator ; Every thread creates, updates and destroys its own label window! xcb_generate_id(connection); lea r13, [label] mov [r13+rbx*4], eax imul r10d, ebx, 30 add r10d, 10 mov r8, [screen] xcb_create_window_checked(connection, XCB_COPY_FROM_PARENT, eax, window, 10, \ r10w, 380, 20, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, \ [r8+s.root_visual], label_n.mask, label_n); xcb_request_check(connection, eax); test rax, rax jz @f movzx ecx, [rax+e.error_code] fprintf(stderr, "Thread %u failed to create label with error code: %u"\n, ebx+1, ecx); mov [rsp+48], byte 1 jmp .end @@ xcb_map_window(connection, [r13+rbx*4]); xcb_flush(connection); cairo_xcb_surface_create(connection, [r13+rbx*4], ptr_visual, 380, 20); mov r12, rax cairo_create(rax); mov r14, rax cairo_select_font_face(rax, "Mono", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(r14, 11.0); cairo_set_source_rgb(r14, 1.0, 1.0, 1.0); sub rsp, 128 call .draw_ACK xor ebp, ebp ; Generate a unique value to be the wait time @@ pause rdrand r15d jnc @b and r15d, 07_FFFFh inc r15d ; Counter update loop @1 lea r9d, [ebx+4] ; Check thread update flag based on lock btr [p.flags], r9d ; received parameter at start (²) jnc @f call .draw_ACK fprintf(stderr, "Label %u fully redrawn."\n, ebx+1); @@ xcb_clear_area(connection, FALSE, [r13+rbx*4], 224, 0, 380 - 224, 20); snprintf(rsp, 127, "Counter %u: %010u", ebx, ebp); cairo_move_to(r14, 224.0, 14.0); cairo_show_text(r14, rsp); xcb_flush(connection); inc ebp usleep(r15d); test [flags], 1 jz @1b .finish: add rsp, 128 fprintf(stderr, "Thread %u is about to exit now..."\n, ebx+1); cairo_destroy(r14); cairo_surface_destroy(r12); xcb_destroy_window(connection, [r13+rbx*4]); xcb_flush(connection); .end: pop r13 pop rbp pop r15 pop r12 pop r14 pop rbx pop rax ; ¹ pthread return value is a qword! ret ; Redraw acknowledge message whenever needed, so it shall not get lost ; by repaint events .draw_ACK: sub rsp, 8 xcb_clear_area(connection, FALSE, [r13+rbx*4], 0, 0, 180, 20); cairo_move_to(r14, 8.0, 14.0); snprintf(rsp+16, 127, "Thread %u here!", ebx+1); cairo_show_text(r14, rsp+16); xcb_flush(connection); add rsp, 8 ret Main: push 0 push rbx push rbp sub rsp, 32 xcb_connect(NULL, NULL); test rax, rax jnz @f fprintf(stderr, "Failed to connect to X server."\n); mov [rsp+48], byte 1 jmp .end ; Working with standard, 24-bit RGB mode this time @@ mov rbp, rax ; connection mov [connection], rax xcb_get_setup(rax); xcb_setup_roots_iterator(rax); mov rbx, rax ; screen mov [screen], rax xcb_screen_allowed_depths_iterator(rax); xcb_depth_visuals_iterator(rax); mov [ptr_visual], rax ; pointer to structure for Cairo parameter xcb_generate_id(rbp); mov [window], eax @rdata .wm_proto: xb 'WM_PROTOCOLS' @rdata .wmp_size = $ - .wm_proto @rdata .wm_delete: xb 'WM_DELETE_WINDOW' @rdata .wmd_size = $ - .wm_delete xcb_intern_atom(rbp, FALSE, .wmp_size, .wm_proto); mov [rsp], eax xcb_intern_atom(rbp, FALSE, .wmd_size, .wm_delete); mov [rsp+8], rax xcb_create_window_checked(rbp, XCB_COPY_FROM_PARENT, window, [rbx+s.root], 0, 0, \ 400, 130, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, \ [rbx+s.root_visual], XCB_CW_BACK_PIXEL, &window.bg); xcb_request_check(rbp, eax); test rax, rax jz @f movzx edx, [rax+e.error_code] fprintf(stderr, "Couldn't create window: %u"\n, edx); xcb_disconnect(rbp); mov [rsp+48], byte 2 jmp .end @@ xcb_change_property(rbp, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_NAME, \ XCB_ATOM_STRING, 8, 28, "Multithread with XCB example"); mov [window.prop.flags], \ XCB_ICCCM_SIZE_HINT_P_MIN_SIZE or XCB_ICCCM_SIZE_HINT_P_MAX_SIZE xcb_icccm_size_hints_set_min_size(window.prop, 400, 130); xcb_icccm_size_hints_set_max_size(window.prop, 400, 130); xcb_icccm_set_wm_size_hints(rbp, window, XCB_ATOM_WM_NORMAL_HINTS, window.prop); xcb_intern_atom_reply(rbp, [rsp], NULL); mov [rsp], rax xcb_intern_atom_reply(rbp, [rsp+8], NULL); mov [rsp+8], rax mov rcx, [rsp] mov edx, [rax+ar.atom] mov [rsp+16], edx xcb_change_property(rbp, XCB_PROP_MODE_REPLACE, window, [rcx+ar.atom], \ XCB_ATOM_ATOM, 32, 1, rax+ar.atom); free([rsp]); ; Requested atom structures need to be freed after usage! free([rsp+8]); ; xcb_map_window(rbp, window); xcb_flush(rbp); prefetcht2 [flags] prefetcht2 byte [window] prefetcht2 [label - 16] prefetcht2 [label_n - 4] pthread_create(&thread.1, NULL, Thread_label, 0); pthread_create(&thread.2, NULL, Thread_label, 1); pthread_create(&thread.3, NULL, Thread_label, 2); pthread_create(&thread.4, NULL, Thread_label, 3); ; Events loop (all windows' events are gathered here) .poll: xcb_wait_for_event(rbp); test rax, rax jz .exit mov rbx, rax mov dl, [rax+ge.response_type] and dl, 7Fh cmp dl, XCB_EXPOSE ; Expose event jne @f mov r10d, [rax+ee.window] mov cl, [flags] mov r9b, 1000_0000b mov al, 0100_0000b mov dl, 0010_0000b mov r8b, 0001_0000b or r9d, ecx or eax, ecx or edx, ecx or r8d, ecx cmp r10d, [label.1] ; Do nothing about the label windows: cmove ecx, r8d cmp r10d, [label.2] ; only sign a flag for their respective cmove ecx, edx cmp r10d, [label.3] ; thread to do something, like draw cmove ecx, eax cmp r10d, [label.4] ; the acknowledge message again cmove ecx, r9d xchg cl, [flags] jmp .default @@ cmp dl, XCB_CLIENT_MESSAGE jne .default mov eax, [rax+cm.data32+0*4] cmp eax, [rsp+16] je .graceful .default: free(rbx); jmp .poll .graceful: free(rbx); .exit: lock or [flags], 1 pthread_join(thread.1, NULL); pthread_join(thread.2, NULL); pthread_join(thread.3, NULL); pthread_join(thread.4, NULL); xcb_destroy_window(rbp, window); xcb_flush(rbp); xcb_disconnect(rbp); .end: add rsp, 32 pop rbp pop rbx pop rax ret _rdata window.bg dd 00_11_11_11h label_n: .bg dd 00_18_18_18h .ev dd XCB_EVENT_MASK_EXPOSURE .mask = XCB_CW_BACK_PIXEL or XCB_CW_EVENT_MASK ; constant property, not data _data p.flags: flags db 0 _bss window dd ? .prop XCB_SIZE_HINTS_T align 64 connection dq ? screen dq ? ptr_visual dq ? ?label: .1 dd ? .2 dd ? .3 dd ? .4 dd ? align 8 thread: .1 dq ? .2 dq ? .3 dq ? .4 dq ? One can easily state the value of handling GUI with multithreading, instead of overloading a single CPU with all graphical tasks (GTK, I'm looking at you!). So, sometimes (always, tbh), lowering the API/ABI level can be much more advantageous, regarding performance. Apparently, this also can't be done with Xlib, but I not even try to test if it works. Let me know if I'm wrong on this statement.
|
|||||||||||
|
|
Jessé 23 Aug 2026, 01:53
And here, another cool approach to get/manage your own "widgets" inside a XCB window: this example looks quite similar to the first one, except for the framebuffer. But it has only one window, which is the main window. Zero child windows this time. The button and the labels inside it, are only "special handling" given to specific areas of the main window, which make them behave as buttons and labels. In addition, I make it look much more modern, because this application draws and handles 100% of its widgets, also because, by not being child windows, they don't exist to X or to the system, so, all control here is gathered with the main application window:
Code: format ELF64 executable 3 at (65535 shl 16) + 0_D000h include 'fastcall.inc' include 'cairo.inc' include 'xcb.inc' include 'xcb-icccm.inc' include 'stdio.inc' define_offset atom_rt XCB_INTERN_ATOM_REPLY_T define_offset bpevt_t XCB_BUTTON_PRESS_EVENT_T define_offset cmevt_t XCB_CLIENT_MESSAGE_EVENT_T define_offset cnevt_t XCB_CREATE_NOTIFY_EVENT_T define_offset dnevt_t XCB_DESTROY_NOTIFY_EVENT_T define_offset eevt_t XCB_EXPOSE_EVENT_T define_offset gevt_t XCB_GENERIC_EVENT_T define_offset mnevt_t XCB_MOTION_NOTIFY_EVENT_T define_offset screen XCB_SCREEN_T define_offset size_ht XCB_SIZE_HINTS_T define_offset ctext_t CAIRO_TEXT_EXTENTS_T proto draw_interface, double WIDTH = 300 HEIGHT = 120 B.WIDTH = 100 B.HEIGHT = 35 _code Start entry: mov r11, [stderr] push [r11] pop [stderr] libc.StartMain(Main); Main: push rbp push rbx push 0 xcb_connect(NULL, NULL); test rax, rax jnz @f fprintf(stderr, "Could not connect to X."\n); mov [rsp], byte 1 jmp .end @@ mov rbp, rax xcb_get_setup(rax); xcb_setup_roots_iterator(rax); mov rbx, rax xcb_screen_allowed_depths_iterator(rax); xcb_depth_visuals_iterator(rax); mov [p.visual], rax prefetcht0 byte [p.visual] xcb_intern_atom(rbp, FALSE, wm_proto.len, wm_proto); push rax push XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY xcb_change_window_attributes(rbp, [rbx+screen.root], XCB_CW_EVENT_MASK, rsp); xcb_intern_atom(rbp, FALSE, wm_del.len, wm_del); mov [rsp], eax xcb_generate_id(rbp); mov [window], eax xcb_create_window_checked(rbp, XCB_COPY_FROM_PARENT, window, [rbx+screen.root], \ 0, 0, WIDTH, HEIGHT, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, \ [rbx+screen.root_visual], XCB_CW_BACK_PIXEL or \ XCB_CW_EVENT_MASK, window.attr); xcb_request_check(rbp, eax); test rax, rax jz @f fprintf(stderr, "An error has occured when try to create window."\n); add rsp, 16 mov [rsp], byte 1 jmp .end2 @@ sub rsp, (sizeof(size_ht) + 16) and 0FFFF_FFF0h mov [rsp+size_ht.flags], XCB_ICCCM_SIZE_HINT_P_MIN_SIZE or \ XCB_ICCCM_SIZE_HINT_P_MAX_SIZE xcb_icccm_size_hints_set_min_size(rsp, WIDTH, HEIGHT); xcb_icccm_size_hints_set_max_size(rsp, WIDTH, HEIGHT); xcb_icccm_set_wm_size_hints(rbp, window, XCB_ATOM_WM_NORMAL_HINTS, rsp); add rsp, (sizeof(size_ht) + 16) and 0FFFF_FFF0h xcb_intern_atom_reply(rbp, [rsp], NULL); mov [rsp], rax xcb_intern_atom_reply(rbp, [rsp+8], NULL); mov [rsp+8], rax mov r9, [rsp] mov r10d, [r9+atom_rt.atom] mov [wmquit], r10d xcb_change_property(rbp, XCB_PROP_MODE_REPLACE, window, [rax+atom_rt.atom], \ XCB_ATOM_ATOM, 32, 1, r9+atom_rt.atom); xcb_change_property(rbp, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_NAME, \ XCB_ATOM_STRING, 8, title.len, title); free([rsp]); free([rsp+8]); add rsp, 16 xor ecx, ecx mov [z_flag], ecx ; Initialize window zone flags, mov [clicks], rcx ; clicks counter mov [events], rcx ; and events counter to 0 xcb_map_window(rbp, window); xcb_flush(rbp); .evt_poll: xcb_wait_for_event(rbp); test rax, rax jz .end2 inc [events] mov dl, [rax+gevt_t.response_type] and dl, 7Fh mov rbx, rax cmp dl, XCB_CREATE_NOTIFY jne @f mov r11d, [window] cmp [rax+cnevt_t.window], r11d jne .default ; WINDOW CREATE movzx ecx, [rax+cnevt_t.width] movzx r8d, [rax+cnevt_t.height] fprintf(stderr, "Create window %08X successful. Width: %u; Height: %u."\n, \ [rax+cnevt_t.window], ecx, r8d); push r12 push r15 cairo_xcb_surface_create(rbp, window, p.visual, WIDTH, HEIGHT); mov r15, rax cairo_create(rax); mov r12, rax jmp .default @@ cmp dl, XCB_EXPOSE jne @f mov r10d, [rax+eevt_t.window] cmp r10d, [window] jne .default ; WINDOW EXPOSE mov rdx, 0.21 mov rax, 0.15 test [z_flag], 1 cmovnz rax, rdx movq xmm0, rax draw_interface(xmm0); ; xcb_flush(rbp); jmp .default @@ cmp dl, XCB_CLIENT_MESSAGE jne @f ; CLIENT MESSAGE lea rdi, [wmquit] lea rsi, [rax+cmevt_t.data32+0*4] cmpsd jne @f fprintf(stderr, "Graceful termination request accepted..."\n); xcb_destroy_window(rbp, window); ; xcb_flush(rbp); jmp .default @@ cmp dl, XCB_DESTROY_NOTIFY jne @f mov r10d, [window] cmp r10d, [rax+dnevt_t.event] jne .default ; WINDOW DESTROY fprintf(stderr, "Window has been requested to be destroyed."\n); cairo_destroy(r12); cairo_surface_destroy(r15); pop r15 pop r12 fprintf(stderr, "Total events: %lu."\n, events); jmp .end2 @@ cmp dl, XCB_MOTION_NOTIFY jne @f mov r11d, [window] cmp [rax+mnevt_t.event], r11d jne .default ; WINDOW MOUSE MOVE B.X.ZONE_MIN = trunc B.X B.X.ZONE_MAX = (trunc B.X) + B.WIDTH B.Y.ZONE_MIN = trunc B.Y B.Y.ZONE_MAX = (trunc B.Y) + B.HEIGHT cmp [rax+mnevt_t.event_x], B.X.ZONE_MIN ; Check if inside button rectangle jb .outside cmp [rax+mnevt_t.event_x], B.X.ZONE_MAX ; ja .outside cmp [rax+mnevt_t.event_y], B.Y.ZONE_MIN ; jb .outside cmp [rax+mnevt_t.event_y], B.Y.ZONE_MAX ; ja .outside .inside: bts [z_flag], 0 ; BIT0: button area flag jc .default ; fprintf(stderr, <13,"Pointer enter button area.",0>); draw_interface(0.21); ; xcb_flush(rbp); jmp .default .outside: btr [z_flag], 0 jnc .default ; fprintf(stderr, <13,"Pointer leave button area.",0>); draw_interface(0.15); ; xcb_flush(rbp); jmp .default @@ cmp dl, XCB_BUTTON_PRESS jne @f cmp [rax+bpevt_t.detail], 1 ; Only react to "left" mouse button jne .default mov r11d, [rax+bpevt_t.event] cmp r11d, [window] jne .default ; WINDOW MOUSE L_BUTTON DOWN cmp [rax+bpevt_t.event_x], B.X.ZONE_MIN ; Check if inside button rectangle jb .default cmp [rax+bpevt_t.event_x], B.X.ZONE_MAX ; ja .default cmp [rax+bpevt_t.event_y], B.Y.ZONE_MIN ; jb .default cmp [rax+bpevt_t.event_y], B.Y.ZONE_MAX ; ja .default bts [z_flag], 1 ; BIT1: button click state flag jc .default draw_interface(0.096); ; xcb_flush(rbp); jmp .default @@ cmp dl, XCB_BUTTON_RELEASE jne @f cmp [rax+bpevt_t.detail], 1 ; jne .default mov r10d, [rax+bpevt_t.event] cmp r10d, [window] jne .default ; WINDOW MOUSE L_BUTTON UP cmp [rax+bpevt_t.event_x], B.X.ZONE_MIN ; Check if inside button rectangle jb @1f cmp [rax+bpevt_t.event_x], B.X.ZONE_MAX ; ja @1f cmp [rax+bpevt_t.event_y], B.Y.ZONE_MIN ; jb @1f cmp [rax+bpevt_t.event_y], B.Y.ZONE_MAX ; ja @1f btr [z_flag], 1 jnc .default ; SIMULATED BUTTON LEFT CLICK EVENT inc [clicks] draw_interface(0.21); ; xcb_flush(rbp); jmp .default @1 btr [z_flag], 1 ; Release L_BUTTON outside button area jnc .default draw_interface(0.15); ; xcb_flush(rbp); jmp .default @@ nop ; WINDOW DEFAULT .default: free(rbx); sub rsp, 64 + (sizeof(ctext_t) + 16) and 0_FFFFFFF0h snprintf(rsp+16, 47, "Events: %lu", events); cairo_text_extents(r12, rsp+16, rsp+64); mov edx, 10 cvtsi2sd xmm1, edx cvtsi2sd xmm0, edx addsd xmm1, xmm1 addsd xmm1, [rsp+64+ctext_t.height] addsd xmm0, [rsp+64+ctext_t.x_bearing] addsd xmm1, [rsp+64+ctext_t.y_bearing] cairo_move_to(r12, xmm0, xmm1); cvttsd2si r9d, [rsp+64+ctext_t.width] cvttsd2si r10d, [rsp+64+ctext_t.height] add r9d, 4 add r10d, 4 xcb_clear_area(rbp, FALSE, window, 8, 8, r9w, r10w); cairo_show_text(r12, rsp+16); xcb_flush(rbp); add rsp, 64 + (sizeof(ctext_t) + 16) and 0_FFFFFFF0h jmp .evt_poll .end2: xcb_disconnect(rbp); .end: pop rax pop rbx pop rbp ret draw_interface: sub rsp, 8 ; Did you know that one can use compile-time variables with assembly? B.X = (WIDTH / 2.0) - (B.WIDTH / 2.0) B.Y = HEIGHT - B.HEIGHT - (B.HEIGHT - (B.HEIGHT / 7.0)) DEGREES = 3.141592653589793238462643383279502884197169 / 180 RADIUS = B.HEIGHT / 10.0 cairo_set_source_rgb(r12, xmm0, xmm0, xmm0); xcb_clear_area(rbp, FALSE, window, \ trunc B.X - 2, trunc B.Y - 2, B.WIDTH + 3, B.HEIGHT + 3); cairo_new_sub_path(r12); cairo_arc(r12, B.X + B.WIDTH - RADIUS, B.Y + RADIUS, RADIUS, -90 * DEGREES, 0.0); cairo_arc(r12, B.X + B.WIDTH - RADIUS, B.Y + B.HEIGHT - RADIUS, RADIUS, 0.0, 90 * DEGREES); cairo_arc(r12, B.X + RADIUS, B.Y + B.HEIGHT - RADIUS, RADIUS, 90 * DEGREES, 180 * DEGREES); cairo_arc(r12, B.X + RADIUS, B.Y + RADIUS, RADIUS, 180 * DEGREES, 270 * DEGREES); cairo_close_path(r12); cairo_fill_preserve(r12); mov r8, 0.227 mov r9, 0.446 mov r10, 0.604 mov r11, r8 test [z_flag], 1 cmovnz r8, r9 cmovnz r11, r10 movq xmm1, r8 movq xmm2, r11 cairo_set_source_rgb(r12, 0.227, xmm1, xmm2); cairo_set_line_width(r12, 0.8); cairo_stroke(r12); @rdata .bt_caption: db 'Click me!', 0 sub rsp, (sizeof(ctext_t) + 16) and 0_FFFFFFF0h prefetcht0 [rsp] cairo_select_font_face(r12, .font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(r12, 12.0); cairo_text_extents(r12, .bt_caption, rsp); mov ecx, B.WIDTH mov edx, B.HEIGHT mov eax, 2 mov r10, B.X mov r11, B.Y cvtsi2sd xmm0, ecx cvtsi2sd xmm1, edx cvtsi2sd xmm15, eax movq xmm13, r10 movq xmm14, r11 movsd xmm8, [rsp+ctext_t.x_bearing] movsd xmm9, [rsp+ctext_t.y_bearing] movsd xmm10, [rsp+ctext_t.width] movsd xmm11, [rsp+ctext_t.height] divsd xmm0, xmm15 divsd xmm1, xmm15 divsd xmm10, xmm15 divsd xmm11, xmm15 addsd xmm10, xmm8 addsd xmm11, xmm9 subsd xmm0, xmm10 subsd xmm1, xmm11 addsd xmm0, xmm13 addsd xmm1, xmm14 cairo_move_to(r12, xmm0, xmm1); cairo_set_source_rgb(r12, 0.8, 0.8, 0.8); cairo_show_text(r12, .bt_caption); @rdata .font: db 'ComicShannsMono Nerd Font', 0 ; From package: 'otf-comicshanns-nerd' cairo_select_font_face(r12, .font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(r12, 12.0); xcb_clear_area(rbp, FALSE, window, 0, HEIGHT - 24, WIDTH, 24); test [z_flag], 1 jz @f cairo_set_source_rgb(r12, 0.8, 0.8, 0.8); cairo_move_to(r12, 9.5, HEIGHT - 14.0 + 7.0); cairo_show_text(r12, "Ready when you are..."); @@ xcb_clear_area(rbp, FALSE, window, 0, 0, WIDTH, 40); test [clicks], -1 jz @f sub rsp, 64 snprintf(rsp, 63, "Clicks: %lu", clicks); cairo_text_extents(r12, rsp, rsp+64); mov ecx, WIDTH mov edx, 10 cvtsi2sd xmm0, ecx cvtsi2sd xmm1, edx cvtsi2sd xmm7, edx subsd xmm0, xmm7 addsd xmm1, xmm7 subsd xmm0, [rsp+64+ctext_t.width] addsd xmm1, [rsp+64+ctext_t.y_bearing] subsd xmm0, [rsp+64+ctext_t.x_bearing] addsd xmm1, [rsp+64+ctext_t.height] cairo_move_to(r12, xmm0, xmm1); cairo_show_text(r12, rsp); add rsp, 64 @@ add rsp, ((sizeof(ctext_t) + 16) and 0_FFFFFFF0h) + 8 ret _bss align 64 p.visual dq ? window dd ? wmquit dd ? clicks dq ? events dq ? z_flag dd ? _rdata align 1 wm_proto: db 'WM_PROTOCOLS' .len = $ - wm_proto wm_del: db 'WM_DELETE_WINDOW' .len = $ - wm_del title: db 'Drawn Button demo' .len = $ - title namespace window align 4 attr: bgcolor dd 0FF_131313h events dd XCB_EVENT_MASK_EXPOSURE or \ XCB_EVENT_MASK_STRUCTURE_NOTIFY or \ XCB_EVENT_MASK_BUTTON_PRESS or \ XCB_EVENT_MASK_BUTTON_RELEASE or \ XCB_EVENT_MASK_POINTER_MOTION end namespace One needs to install 'otf-comicshanns-nerd' package to notice that I also made this example look more casual, which adds to its modern appearance. Otherwise, a default 'Mono' font will be placed instead. Well, as one can see, when you have only a window (and probably the highest performance "possible" scenario), author creativity is the limit to what can really be done. Same goal, two distinct approaches, as shown here. Cheers!
|
|||||||||||
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2026, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.