flat assembler
Message board for the users of flat assembler.

Index > Linux > XCB full window example (with button and framebuffer access)

Author
Thread Post new topic Reply to topic
Jessé



Joined: 03 May 2025
Posts: 148
Location: Brazil
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... Wink

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). Wink

Cheers,


Description: Source and binary, for the curious mind.
Download
Filename: xcb_fb.tar.gz
Filesize: 10.27 KB
Downloaded: 29 Time(s)


_________________
jesse6
Post 13 Aug 2026, 17:36
View user's profile Send private message Visit poster's website Reply with quote
Jessé



Joined: 03 May 2025
Posts: 148
Location: Brazil
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.

Cool


Description: Source and binary, to ease testing
Download
Filename: multithread.tar.gz
Filesize: 6.03 KB
Downloaded: 22 Time(s)


_________________
jesse6
Post 15 Aug 2026, 10:29
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2026, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.