Search code examples
c++clinuxxlibfasm

Hanging of XShmPutImage event notification


I am using XShm extension to draw and manipulate images in Linux.

In order to not have screen flickering, I am passing send_event = TRUE to XShmPutImage and then waiting for the event with XIfEvent, immediately after the call to XScmPutImage.

This way, I am making the image drawing blocking in order to not changing the image until it is displayed on the window surface.

Usually everything works fine. But sometimes, when I have intensive image drawing, it seems that the event never comes and the drawing procedure hangs.

Where to see for the problem? Is using XIfEvent appropriate for this task? How can the event dissapear from the message queue?

Is it possible XShmPutImage to not send event (if send_event = TRUE) or to send event different than ShmCompletion on some circumstances? (for example on some internal error or something?)

EDIT:

After some more research, I found that such hangs happens only when the window manager intensively generate events to the window. For example when I resize the window by dragging its corners.

EDIT2:

I tried several ways to solve this problem, but without success. At the end I was forced to use some timeout and to cancel waiting after some time. But of course this is dirty hack and I want to fix it anyway.

So, what can be the reason XShmPutImage to not send event if send_event=TRUE or is it possible this event to disappear from the message queue?

EDIT3:

Here is the questionable code (FASM):

        cinvoke XShmPutImage, ......, TRUE

    .loop:
        lea     eax, [.event]
        cinvoke XCheckTypedEvent, [Display], [ShmCompletionEvent], eax

        test    eax, eax
        jz      .loop      ; there is no message

NB: XShmPutImage always return TRUE, regardless whether the event check hangs or not, so I didn't put error check after it.

EDIT4:

Because of request I am posting the whole code of the drawing function. The code uses some macro libraries of FASM, but at least the ideas are clear (I hope)

Notice that this code contains workaround code that limits the event waiting for only 20ms. Without this timeout the waiting loop simply hangs forever. The number of the XShm event is acquired by calling XShmGetEventBase as recommended in the Xshm documentation.

; Draws the image on a OS provided window surface.
proc DrawImageRect, .where, .pImage, .xDst, .yDst, .xSrc, .ySrc, .width, .height
.event XEvent
       rb 256
begin
        pushad

        mov     esi, [.pImage]
        test    esi, esi
        jz      .exit

        mov     ebx, [esi+TImage.ximage]

        cinvoke XCreateGC, [hApplicationDisplay], [.where], 0, 0
        mov     edi, eax


        cinvoke XShmPutImage, [hApplicationDisplay], [.where], edi, [esi+TImage.ximage], [.xSrc], [.ySrc], [.xDst], [.yDst], [.width], [.height], TRUE

        stdcall GetTimestamp
        lea     esi, [eax+20]    ; 20ms timeout

.loop:
        lea     eax, [.event]
        cinvoke XCheckTypedEvent, [hApplicationDisplay], [ShmCompletionEvent], eax
        test    eax, eax
        jnz     .finish

        stdcall GetTimestamp
        cmp     eax, esi
        jb      .loop

.finish:
        cinvoke XFreeGC, [hApplicationDisplay], edi

.exit:
        popad
        return

endp

And here is the code of the main event loop of the application.

The procedure __ProcessOneSystemEvent simply dispatches the events to the GUI objects and ignores all events it does not use. It does not process ShmCompletionEvent at all.

All the windows created in the application have events mask of: ExposureMask+FocusChangeMask+KeyPressMask+KeyReleaseMask+ButtonPressMask+ButtonReleaseMask+EnterWindowMask+LeaveWindowMask+PointerMotionMask+StructureNotifyMask

proc ProcessSystemEvents
  .event  XEvent
          rb 256
begin
        push    ebx ecx edx

.event_loop:
; check for quit

        get     eax, [pApplication], TApplication:MainWindow

        test    eax, eax
        jz      .continue    

        cmp     dword [eax], 0
        jne     .continue

        cinvoke XFlush, [hApplicationDisplay]
        xor     eax, eax
        mov     [fGlobalTerminate], 1
        stc
        pop     edx ecx ebx
        return

.continue:
        cinvoke XPending, [hApplicationDisplay]
        test    eax, eax
        jz      .noevents

        push    edi ecx
        lea     edi, [.event]
        mov     ecx, sizeof.XEvent/4
        xor     eax, eax
        rep stosd
        pop     ecx edi

        lea     ebx, [.event]

        cinvoke  XNextEvent, [hApplicationDisplay], ebx
        stdcall  __ProcessOneSystemEvent, ebx
        jmp      .event_loop

.noevents:
        clc
        pop     edx ecx ebx
        return

endp

The full source code is available in the repository but it is a very big project not easy for navigation. The discussed source is in check-in 8453c99b1283def8.

The files: "freshlib/graphics/images.asm" and "freshlib/graphics/Linux/images.asm" are about the image drawing.

The files: "freshlib/gui/Main.asm" and "freshlib/gui/Linux/Main.asm" are about the general events handling in the application.


Solution

  • What is the X server doing?

    The X server can and will suppress a ShmCompletionEvent if the parameters passed to XShmPutImage exceed the geometry of the shared memory area attached to the XImage in the call. The server checks X/Y and width/height against the previously stored limits for the given shared area and if the call parameters are out-of-bounds, the server will return BadValue, suppress the drawing operation, and suppress the completion event.

    The above is exactly what is happening in your library. Here's how:

    1. The main event dispatcher routine is ProcessSystemEvents. It performs an XEventNext, and based on event type, using a jump table .jump_table dispatches to an event specific handler function.
    2. The event specific function for an Expose event is .expose
    3. The .expose function will, in turn, call DrawImageRect using X/Y and width/height values from the XExposeEvent struct. This is wrong and is the true source of the bug, as we shall see momentarily.
    4. DrawImageRect will pass these values along in a call to XShmPutImage
    5. The handler for XShmPutImage in the X server will examine these parameters and reject if they're out of bounds.

    The parameters are being rejected because they come from an exposure event and are related to the geometry of the window and not the geometry of the shared memory attached to the XImage used in the XShmPutImage call.

    Specifically, if the window has just be resized (e.g. by the window manager) and has been enlarged, and there has been a prior ConfigureNotify event for resize. Now, with a new Expose event, it will have larger width/height that will exceed the width/height of the shared memory area that the server knows about.

    It is the responsibility of the client to field the window resize events [etc.] and teardown/recreate the shared memory area with the enlarged size. This is not being done and is the source of the bug.

    NOTE: Just to be completely clear on this, the server can only report on the error and not do anything about it for a few reasons:

    1. The server knows about the window [and its resize].
    2. It knows about the XImage, its shared memory area and size
    3. But they're only associated during the XShmPutImage call [AFAIK]
    4. Even if the server could associate them, it couldn't adjust the shmarea
    5. That's because it has no way to relink the shmarea to the client side
    6. Only the client can do that via XShmDetach/XShmAttach

    Below are redacted versions of the relevant source files from the c5c765bc7e commit. They have been cleaned up a bit, so only the most germane parts remain. Some lines have been truncated or wrapped to eliminate horizontal scrolling.

    The files have been annotated with NOTE and NOTE/BUG which I did while analyzing them.


    gui/Main.asm The top level generic main loop. Nothing to see much to see here.

    ; FILE: gui/Main.asm
    ; _____________________________________________________________________________
    ;|                                                                             |
    ;| ..::FreshLib::.. Free, open source. Licensed under "BSD 2-clause" license." |
    ;|_____________________________________________________________________________|
    ;
    ;  Description: Main procedure of GUI application library.
    ;
    ;  Target OS: Any
    ;
    ;  Dependencies:
    ;
    ;  Notes: Organize the main message/event loop needed by every GUI engine.
    ;         This file contains only OS independent part and includes OS dependent
    ;         files.
    ;______________________________________________________________________________
    
    module "Main library"
    
    proc Run
    begin
    .mainloop:
            stdcall ProcessSystemEvents
            jc      .terminate
    
            mov     eax, [pApplication]
            test    eax, eax
            jz      .eventok
    
            get     ecx, eax, TApplication:OnIdle
            jecxz   .eventok
    
            stdcall ecx, eax
    
    .eventok:
            stdcall WaitForSystemEvent
            jmp     .mainloop
    
    .terminate:
            DebugMsg "Terminate GUI application!"
            return
    endp
    
    include '%TargetOS%/Main.asm'
    
    endmodule
    

    gui/Linux/Main.asm The event handlers

    ; FILE: gui/Linux/Main.asm
    ; _____________________________________________________________________________
    ;|                                                                             |
    ;| ..::FreshLib::.. Free, open source. Licensed under "BSD 2-clause" license." |
    ;|_____________________________________________________________________________|
    ;
    ;  Description: Main procedure of GUI application library.
    ;
    ;  Target OS: Linux
    ;
    ;  Dependencies:
    ;
    ;  Notes: Organize the main message/event loop needed by every GUI engine.
    ;______________________________________________________________________________
    
    body ProcessSystemEvents
    ; NOTE: this is the storage for the dequeued event -- all dispatch routines
    ; should use it and process it
      .event  XEvent
              rb 256
    
    begin
            push    ebx ecx edx
    
    .event_loop:
    ; check for quit
    
            get     eax, [pApplication], TApplication:MainWindow
    
            test    eax, eax
            jz      .continue     ; ???????????
    
            cmp     dword [eax], 0
            jne     .continue
    
            cinvoke XFlush, [hApplicationDisplay]
            xor     eax, eax
            mov     [fGlobalTerminate], 1
            stc
            pop     edx ecx ebx
            return
    
    ; NOTE: it is wasteful for the main loop to call WaitForSystemEvent, then call
    ; us and we do XPending on the first loop -- we already know we have at least
    ; one event waiting in the queue
    .continue:
            cinvoke XPending, [hApplicationDisplay]
            test    eax, eax
            jz      .noevents
    
            push    edi ecx
            lea     edi, [.event]
            mov     ecx, sizeof.XEvent/4
            xor     eax, eax
            rep stosd
            pop     ecx edi
    
            lea     ebx, [.event]
    
            cinvoke  XNextEvent, [hApplicationDisplay], ebx
            stdcall  __ProcessOneSystemEvent, ebx
            jmp      .event_loop
    
    .noevents:
            clc
            pop     edx ecx ebx
            return
    
    endp
    
    body WaitForSystemEvent
    .event XEvent
    begin
            push    eax ecx edx
            lea     eax, [.event]
            cinvoke XPeekEvent, [hApplicationDisplay], eax
            pop     edx ecx eax
            return
    endp
    
    proc __ProcessOneSystemEvent, .linux_event
    begin
            pushad
    
            mov     ebx, [.linux_event]
    
    ;        mov     eax, [ebx+XEvent.type]
    ;        cmp     eax, [ShmCompletionEvent]
    ;        je      .shm_completion
    
            stdcall _GetWindowStruct, [ebx+XEvent.window]
            jc      .notprocessed
    
            test    eax, eax
            jz      .notprocessed
    
            mov     esi, eax
            mov     ecx, [ebx+XEvent.type]
    
            cmp     ecx, LASTEvent
            jae     .notprocessed
    
            mov     ecx, [.jump_table+4*ecx]
            jecxz   .notprocessed
    
            jmp     ecx
    
    .notprocessed:
            popad
            stc
            return
    
    .finish:
            popad
            clc
            return
    
    ;.shm_completion:
    ;        DebugMsg "Put back completion event!"
    ;
    ;        int3
    ;        cinvoke XPutBackEvent, [hApplicationDisplay], ebx
    ;        jmp     .finish
    
    ;.........................................................................
    ; seMove and seResize events.
    ;-------------------------------------------------------------------------
    .moveresize:
    
    ; NOTE/BUG!!!!: we must not only process a resize/move request, but we must also
    ; adjust the size of the shmarea attached to the XImage -- that is _not_ being
    ; done. (e.g.) if the window is enlarged, the shmarea must be enlarged
    
            cinvoke XCheckTypedWindowEvent, [hApplicationDisplay],
                    [ebx+XConfigureEvent.window], ConfigureNotify, ebx
            test    eax, eax
            jnz     .moveresize
    
    ; resize event...
            mov     eax, [esi+TWindow._width]
            mov     edx, [esi+TWindow._height]
            cmp     eax, [ebx+XConfigureEvent.width]
            jne     .resize
            cmp     edx, [ebx+XConfigureEvent.height]
            je      .is_move
    
    .resize:
            exec    esi, TWindow:EventResize, [ebx+XConfigureEvent.width],
                    [ebx+XConfigureEvent.height]
    
    ; move event...
    .is_move:
            mov     eax, [esi+TWindow._x]
            mov     edx, [esi+TWindow._y]
            cmp     eax, [ebx+XConfigureEvent.x]
            jne     .move
            cmp     eax, [ebx+XConfigureEvent.y]
            je      .finish
    
    .move:
            exec    esi, TWindow:EventMove,
                    [ebx+XConfigureEvent.x], [ebx+XConfigureEvent.y]
    
            jmp     .finish
    
    ;.........................................................................
    ; DestroyNotify handler it invalidates the handle in TWindow structure and
    ; then destroys TWindow.
    .destroy:
            test    esi, esi
            jz      .finish
    
            mov     [esi+TWindow.handle], 0
    
            destroy esi
            jmp     .finish
    
    ;.........................................................................
    ; Window paint event
    
    .expose:
            get     edi, esi, TWindow:ImgScreen
    
    ; NOTE:BUG!!!!!
    ;
    ; if the window has been resized (e.g. enlarged), these values are wrong!
    ; they relate to the _window_ but _not_ the shmarea that is attached to the
    ; XImage
    ;
    ; however, DrawImageRect will call XShmPutImage with these values, they
    ; will exceed the geometry of what the X server knows about the shmarea and
    ; it will return BadValue and _suppress_ the completion event for XShmPutImage
    
            stdcall DrawImageRect, [esi+TWindow.handle], edi,
                    [ebx+XExposeEvent.x],[ebx+XExposeEvent.y],
                    [ebx+XExposeEvent.x], [ebx+XExposeEvent.y],
                    [ebx+XExposeEvent.width], [ebx+XExposeEvent.height]
    
            jmp     .finish
    
    ;.........................................................................
    ; Mouse event handlers
    
    .mousemove:
    
            cinvoke XCheckTypedWindowEvent, [hApplicationDisplay],
                    [ebx+XConfigureEvent.window], MotionNotify, ebx
            test    eax, eax
            jnz     .mousemove
    
            stdcall ServeMenuMouseMove, [ebx+XMotionEvent.window],
                    [ebx+XMotionEvent.x], [ebx+XMotionEvent.y],
                    [ebx+XMotionEvent.state]
            jc      .finish
    
            cinvoke XCheckTypedWindowEvent, [hApplicationDisplay],
                    [ebx+XMotionEvent.window], MotionNotify, ebx
            test    eax, eax
            jnz     .mousemove
    
            mov     edi, [__MouseTarget]
            test    edi, edi
            jz      .search_target_move
    
            stdcall __GetRelativeXY, edi, [ebx+XMotionEvent.x], [ebx+XMotionEvent.y]
            jmp     .target_move
    
    .search_target_move:
            exec    esi, TWindow:ChildByXY, [ebx+XMotionEvent.x],
                    [ebx+XMotionEvent.y], TRUE
            mov     edi, eax
    
    .target_move:
            cmp     edi, [__LastPointedWindow]
            je      .move_event
    
            cmp     [__LastPointedWindow], 0
            je      .leave_ok
    
            exec    [__LastPointedWindow], TWindow:EventMouseLeave
    
    .leave_ok:
    
            mov     [__LastPointedWindow], edi
            exec    edi, TWindow:EventMouseEnter
    
    .move_event:
            exec    edi, TWindow:EventMouseMove, ecx, edx, [ebx+XMotionEvent.state]
            jmp     .finish
    
    ;.........................................................................
    ; event jump table
    .jump_table dd  0                       ; event 0
                dd  0                       ; event 1
                dd  .key_press              ; KeyPress = 2
                dd  .key_release            ; KeyRelease = 3
                dd  .mouse_btn_press        ; ButtonPress = 4
                dd  .mouse_btn_release      ; ButtonRelease = 5
                dd  .mousemove              ; MotionNotify = 6
                dd  0                       ; EnterNotify = 7
                dd  0                       ; LeaveNotify = 8
                dd  .focusin                ; FocusIn = 9
                dd  .focusout               ; FocusOut = 10
                dd  0                       ; KeymapNotify = 11
                dd  .expose                 ; Expose = 12
                dd  0                       ; GraphicsExpose = 13
                dd  0                       ; NoExpose = 14
                dd  0                       ; VisibilityNotify = 15
                dd  0                       ; CreateNotify = 16
                dd  .destroy                ; DestroyNotify = 17
                dd  0                       ; UnmapNotify = 18
                dd  0                       ; MapNotify = 19
                dd  0                       ; MapRequest = 20
                dd  0                       ; ReparentNotify = 21
                dd  .moveresize             ; ConfigureNotify = 22
                dd  0                       ; ConfigureRequest = 23
                dd  0                       ; GravityNotify = 24
                dd  0                       ; ResizeRequest = 25
                dd  0                       ; CirculateNotify = 26
                dd  0                       ; CirculateRequest = 27
                dd  0                       ; PropertyNotify = 28
                dd  0                       ; SelectionClear = 29
                dd  0                       ; SelectionRequest = 30
                dd  0                       ; SelectionNotify = 31
                dd  0                       ; ColormapNotify = 32
                dd  .clientmessage          ; ClientMessage = 33
                dd  .mapping_notify         ; MappingNotify = 34
    

    graphics/Linux/images.asm The image drawing code [including the DrawImageRect function] and the shared memory create/destroy code.

    ; FILE: graphics/Linux/images.asm
    ; _____________________________________________________________________________
    ;|                                                                             |
    ;| ..::FreshLib::.. Free, open source. Licensed under "BSD 2-clause" license." |
    ;|_____________________________________________________________________________|
    ;
    ;  Description: Memory based images manipulation library.
    ;
    ;  Target OS: Linux
    ;
    ;  Dependencies: memory.asm
    ;
    ;  Notes:
    ;______________________________________________________________________________
    
    uses libX11, xshm
    
    struct TImage
      .width   dd ?  ; width in pixels.
      .height  dd ?  ; height in pixels.
      .pPixels dd ?  ; pointer to the pixel memory.
    
    ; os dependent data
      .ximage  dd ?
      .shminfo XShmSegmentInfo
    ends
    
    body CreateImage
    begin
            pushad
    
            stdcall GetMem, sizeof.TImage
            jc      .finish
            mov     esi, eax
    
            xor     eax, eax
            inc     eax
    
            mov     ecx, [.width]
            mov     edx, [.height]
    
            cmp     ecx, 0
            cmovle  ecx, eax
    
            cmp     edx, 0
            cmovle  edx, eax
    
            mov     [esi+TImage.width], ecx
            mov     [esi+TImage.height], edx
    
            lea     eax, [4*ecx]
            imul    eax, edx
    
            cinvoke shmget, IPC_PRIVATE, eax, IPC_CREAT or 777o
            test    eax, eax
            js      .error
    
            mov     [esi+TImage.shminfo.ShmID], eax
    
            cinvoke shmat, eax, 0, 0
            cmp     eax, -1
            je      .error_free
    
            mov     [esi+TImage.shminfo.Addr], eax
            mov     [esi+TImage.pPixels], eax
            mov     [esi+TImage.shminfo.fReadOnly], 1
    
            lea     ebx, [esi+TImage.shminfo]
            cinvoke XShmCreateImage, [hApplicationDisplay], 0, $20, ZPixmap, eax,
                    ebx, [esi+TImage.width], [esi+TImage.height]
            mov     [esi+TImage.ximage], eax
    
            cinvoke XShmAttach, [hApplicationDisplay], ebx
    
            clc
            mov     [esp+4*regEAX], esi
    
    .finish:
            popad
            return
    
    .error_free:
            cinvoke shmctl, [ebx+XShmSegmentInfo.ShmID], IPC_RMID, 0
    
    .error:
            stdcall FreeMem, esi
            stc
            jmp     .finish
    
    endp
    
    body DestroyImage
    begin
            pushad
    
            mov     esi, [.ptrImage]
            test    esi, esi
            jz      .finish
    
            lea     eax, [esi+TImage.shminfo]
            cinvoke XShmDetach, [hApplicationDisplay], eax
    
            cinvoke XDestroyImage, [esi+TImage.ximage]
    
            cinvoke shmdt, [esi+TImage.shminfo.Addr]
            cinvoke shmctl, [esi+TImage.shminfo.ShmID], IPC_RMID, 0
            stdcall FreeMem, esi
    
    .finish:
            popad
            return
    endp
    
    ;if used ___CheckCompletionEvent
    ;___CheckCompletionEvent:
    ;
    ;virtual at esp+4
    ;  .display dd ?
    ;  .pEvent  dd ?
    ;  .user    dd ?
    ;end virtual
    ;
    ;; timeout
    ;        stdcall GetTimestamp
    ;        cmp     eax, [.user]
    ;        jbe     @f
    ;
    ;        DebugMsg "Timeout!"
    ;
    ;        mov     eax, 1
    ;        retn
    ;
    ;@@:
    ;        mov     eax, [.pEvent]      ;.pEvent
    ;        mov     eax, [eax+XEvent.type]
    ;
    ;        cmp     eax, [ShmCompletionEvent]
    ;        sete    al
    ;        movzx   eax, al
    ;        retn
    ;end if
    
    body DrawImageRect
    .event XEvent
           rb 256
    begin
            pushad
    
            mov     esi, [.pImage]
            test    esi, esi
            jz      .exit
    
            mov     ebx, [esi+TImage.ximage]
    
    ; NOTE: is this necessary? it seems wasteful to create and destroy a GC
    ; repeatedly.  Dunno, does this _have_ to be done here, _every_ time?
            cinvoke XCreateGC, [hApplicationDisplay], [.where], 0, 0
            mov     edi, eax
    
    ; NOTE/BUG: The return ShmCompletionEvent will be suppressed due to a BadValue
    ; if the X/Y and width/height parameters given to us by caller exceed the
    ; geometry/range of the shmarea attached to .ximage
    ;
    ; the routine that calls us is .expose and it _is_ giving us bad values.  it is
    ; passing us X/Y width/height related to an exposure event of the .where
    ; _window_ which we put in the call.  The X server will compare these against
    ; the size of the shmarea of TImage.xmage and complain if we exceed the bounds
    
            cinvoke XShmPutImage, [hApplicationDisplay], [.where], edi,
                    [esi+TImage.ximage], [.xSrc], [.ySrc], [.xDst], [.yDst],
                    [.width], [.height], TRUE
    
    ; NOTE/BUG: this code should _not_ be looping on XCheckTypedEvent because it
    ; disrupts the normal event processing.  if we want to be "synchronous" on this
    ; we should loop on the main event dispatcher (ProcessSystemEvents) and let it
    ; dispatch to a callback we create.  we can set a "pending" flag that our [not
    ; yet existent] dispatch routine can clear
    
    ; THIS CODE SOMETIMES CAUSES HANGS!
    
            stdcall GetTimestamp
            lea     esi, [eax+20]
    
    .loop:
            lea     eax, [.event]
            cinvoke XCheckTypedEvent, [hApplicationDisplay], [ShmCompletionEvent],
                    eax
            test    eax, eax
            jnz     .finish
    
            stdcall GetTimestamp
            cmp     eax, esi
            jb      .loop
    
    .finish:
            cinvoke XFreeGC, [hApplicationDisplay], edi
    
    .exit:
            popad
            return
    
    endp
    

    Xext/shm.c The X server code that checks and processes the XShmPutImage call.

    // FILE: Xext/shm.c
    
    static int
    ProcShmPutImage(ClientPtr client)
    {
        GCPtr pGC;
        DrawablePtr pDraw;
        long length;
        ShmDescPtr shmdesc;
    
        REQUEST(xShmPutImageReq);
    
        REQUEST_SIZE_MATCH(xShmPutImageReq);
        VALIDATE_DRAWABLE_AND_GC(stuff->drawable, pDraw, DixWriteAccess);
        VERIFY_SHMPTR(stuff->shmseg, stuff->offset, FALSE, shmdesc, client);
    
        // NOTE: value must be _exactly_ 0/1
        if ((stuff->sendEvent != xTrue) && (stuff->sendEvent != xFalse))
            return BadValue;
    
        if (stuff->format == XYBitmap) {
            if (stuff->depth != 1)
                return BadMatch;
            length = PixmapBytePad(stuff->totalWidth, 1);
        }
        else if (stuff->format == XYPixmap) {
            if (pDraw->depth != stuff->depth)
                return BadMatch;
            length = PixmapBytePad(stuff->totalWidth, 1);
            length *= stuff->depth;
        }
        else if (stuff->format == ZPixmap) {
            if (pDraw->depth != stuff->depth)
                return BadMatch;
            length = PixmapBytePad(stuff->totalWidth, stuff->depth);
        }
        else {
            client->errorValue = stuff->format;
            return BadValue;
        }
    
        // NOTE/BUG: The following block is the "check parameters" code.  If the
        // given drawing parameters of the request (e.g. X, Y, width, height) [or
        // combinations thereof] exceed the geometry/size of the shmarea, the
        // BadValue error is being returned here and the code to send a return
        // event will _not_ be executed.  The bug isn't really here, it's on the
        // client side, but it's the client side bug that causes the event to be
        // suppressed
    
        /*
         * There's a potential integer overflow in this check:
         * VERIFY_SHMSIZE(shmdesc, stuff->offset, length * stuff->totalHeight,
         *                client);
         * the version below ought to avoid it
         */
        if (stuff->totalHeight != 0 &&
            length > (shmdesc->size - stuff->offset) / stuff->totalHeight) {
            client->errorValue = stuff->totalWidth;
            return BadValue;
        }
        if (stuff->srcX > stuff->totalWidth) {
            client->errorValue = stuff->srcX;
            return BadValue;
        }
        if (stuff->srcY > stuff->totalHeight) {
            client->errorValue = stuff->srcY;
            return BadValue;
        }
        if ((stuff->srcX + stuff->srcWidth) > stuff->totalWidth) {
            client->errorValue = stuff->srcWidth;
            return BadValue;
        }
        if ((stuff->srcY + stuff->srcHeight) > stuff->totalHeight) {
            client->errorValue = stuff->srcHeight;
            return BadValue;
        }
    
        // NOTE: this is where the drawing takes place
        if ((((stuff->format == ZPixmap) && (stuff->srcX == 0)) ||
             ((stuff->format != ZPixmap) &&
              (stuff->srcX < screenInfo.bitmapScanlinePad) &&
              ((stuff->format == XYBitmap) ||
               ((stuff->srcY == 0) &&
                (stuff->srcHeight == stuff->totalHeight))))) &&
            ((stuff->srcX + stuff->srcWidth) == stuff->totalWidth))
            (*pGC->ops->PutImage) (pDraw, pGC, stuff->depth,
                                   stuff->dstX, stuff->dstY,
                                   stuff->totalWidth, stuff->srcHeight,
                                   stuff->srcX, stuff->format,
                                   shmdesc->addr + stuff->offset +
                                   (stuff->srcY * length));
        else
            doShmPutImage(pDraw, pGC, stuff->depth, stuff->format,
                          stuff->totalWidth, stuff->totalHeight,
                          stuff->srcX, stuff->srcY,
                          stuff->srcWidth, stuff->srcHeight,
                          stuff->dstX, stuff->dstY, shmdesc->addr + stuff->offset);
    
        // NOTE: this is where the return event gets sent
        if (stuff->sendEvent) {
            xShmCompletionEvent ev = {
                .type = ShmCompletionCode,
                .drawable = stuff->drawable,
                .minorEvent = X_ShmPutImage,
                .majorEvent = ShmReqCode,
                .shmseg = stuff->shmseg,
                .offset = stuff->offset
            };
            WriteEventsToClient(client, 1, (xEvent *) &ev);
        }
    
        return Success;
    }