Skip to content
Snippets Groups Projects
juce_linux_Windowing.cpp 152 KiB
Newer Older
Josh Siegle's avatar
Josh Siegle committed
/*
  ==============================================================================

   This file is part of the JUCE library.
Septen's avatar
Septen committed
   Copyright (c) 2015 - ROLI Ltd.
Josh Siegle's avatar
Josh Siegle committed

   Permission is granted to use this software under the terms of either:
   a) the GPL v2 (or any later version)
   b) the Affero GPL v3

   Details of these licenses can be found at: www.gnu.org/licenses

   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

   ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.juce.com for more information.

  ==============================================================================
*/

Septen's avatar
Septen committed
extern ::Display* display;
Josh Siegle's avatar
Josh Siegle committed
extern XContext windowHandleXContext;
typedef void (*WindowMessageReceiveCallback) (XEvent&);
extern WindowMessageReceiveCallback dispatchWindowMessage;


//==============================================================================
struct Atoms
{
    Atoms()
    {
Septen's avatar
Septen committed
        protocols                       = getIfExists ("WM_PROTOCOLS");
        protocolList [TAKE_FOCUS]       = getIfExists ("WM_TAKE_FOCUS");
        protocolList [DELETE_WINDOW]    = getIfExists ("WM_DELETE_WINDOW");
        protocolList [PING]             = getIfExists ("_NET_WM_PING");
        changeState                     = getIfExists ("WM_CHANGE_STATE");
        state                           = getIfExists ("WM_STATE");
        userTime                        = getCreating ("_NET_WM_USER_TIME");
        activeWin                       = getCreating ("_NET_ACTIVE_WINDOW");
        pid                             = getCreating ("_NET_WM_PID");
        windowType                      = getIfExists ("_NET_WM_WINDOW_TYPE");
        windowState                     = getIfExists ("_NET_WM_STATE");
Josh Siegle's avatar
Josh Siegle committed

        XdndAware                       = getCreating ("XdndAware");
        XdndEnter                       = getCreating ("XdndEnter");
        XdndLeave                       = getCreating ("XdndLeave");
        XdndPosition                    = getCreating ("XdndPosition");
        XdndStatus                      = getCreating ("XdndStatus");
        XdndDrop                        = getCreating ("XdndDrop");
        XdndFinished                    = getCreating ("XdndFinished");
        XdndSelection                   = getCreating ("XdndSelection");

        XdndTypeList                    = getCreating ("XdndTypeList");
        XdndActionList                  = getCreating ("XdndActionList");
        XdndActionCopy                  = getCreating ("XdndActionCopy");
        XdndActionPrivate               = getCreating ("XdndActionPrivate");
        XdndActionDescription           = getCreating ("XdndActionDescription");

        allowedMimeTypes[0]             = getCreating ("UTF8_STRING");
        allowedMimeTypes[1]             = getCreating ("text/plain;charset=utf-8");
        allowedMimeTypes[2]             = getCreating ("text/plain");
        allowedMimeTypes[3]             = getCreating ("text/uri-list");

        allowedActions[0]               = getCreating ("XdndActionMove");
        allowedActions[1]               = XdndActionCopy;
        allowedActions[2]               = getCreating ("XdndActionLink");
        allowedActions[3]               = getCreating ("XdndActionAsk");
        allowedActions[4]               = XdndActionPrivate;
    }

    enum ProtocolItems
    {
        TAKE_FOCUS = 0,
        DELETE_WINDOW = 1,
        PING = 2
    };

Septen's avatar
Septen committed
    Atom protocols, protocolList[3], changeState, state, userTime,
         activeWin, pid, windowType, windowState,
Josh Siegle's avatar
Josh Siegle committed
         XdndAware, XdndEnter, XdndLeave, XdndPosition, XdndStatus,
         XdndDrop, XdndFinished, XdndSelection, XdndTypeList, XdndActionList,
         XdndActionDescription, XdndActionCopy, XdndActionPrivate,
         allowedActions[5],
Septen's avatar
Septen committed
         allowedMimeTypes[4];
Josh Siegle's avatar
Josh Siegle committed

    static const unsigned long DndVersion;

    static Atom getIfExists (const char* name)    { return XInternAtom (display, name, True); }
    static Atom getCreating (const char* name)    { return XInternAtom (display, name, False); }

    static String getName (const Atom atom)
    {
        if (atom == None)
            return "None";

        return String (XGetAtomName (display, atom));
    }

    static bool isMimeTypeFile (const Atom atom)  { return getName (atom).equalsIgnoreCase ("text/uri-list"); }
};

const unsigned long Atoms::DndVersion = 3;

//==============================================================================
struct GetXProperty
{
    GetXProperty (Window window, Atom atom, long offset, long length, bool shouldDelete, Atom requestedType)
        : data (nullptr)
    {
        success = (XGetWindowProperty (display, window, atom, offset, length,
                                       (Bool) shouldDelete, requestedType, &actualType,
                                       &actualFormat, &numItems, &bytesLeft, &data) == Success)
                    && data != nullptr;
    }

    ~GetXProperty()
    {
        if (data != nullptr)
            XFree (data);
    }

    bool success;
    unsigned char* data;
    unsigned long numItems, bytesLeft;
    Atom actualType;
    int actualFormat;
};

//==============================================================================
namespace Keys
{
    enum MouseButtons
    {
        NoButton = 0,
        LeftButton = 1,
        MiddleButton = 2,
        RightButton = 3,
        WheelUp = 4,
        WheelDown = 5
    };

    static int AltMask = 0;
    static int NumLockMask = 0;
    static bool numLock = false;
    static bool capsLock = false;
    static char keyStates [32];
    static const int extendedKeyModifier = 0x10000000;
}

bool KeyPress::isKeyCurrentlyDown (const int keyCode)
{
Septen's avatar
Septen committed
    if (display == nullptr)
        return false;

Josh Siegle's avatar
Josh Siegle committed
    int keysym;

    if (keyCode & Keys::extendedKeyModifier)
    {
        keysym = 0xff00 | (keyCode & 0xff);
    }
    else
    {
        keysym = keyCode;

        if (keysym == (XK_Tab & 0xff)
            || keysym == (XK_Return & 0xff)
            || keysym == (XK_Escape & 0xff)
            || keysym == (XK_BackSpace & 0xff))
        {
            keysym |= 0xff00;
        }
    }

    ScopedXLock xlock;

Septen's avatar
Septen committed
    const int keycode = XKeysymToKeycode (display, (KeySym) keysym);
Josh Siegle's avatar
Josh Siegle committed

    const int keybyte = keycode >> 3;
    const int keybit = (1 << (keycode & 7));
    return (Keys::keyStates [keybyte] & keybit) != 0;
}

//==============================================================================
#if JUCE_USE_XSHM
namespace XSHMHelpers
{
    static int trappedErrorCode = 0;
    extern "C" int errorTrapHandler (Display*, XErrorEvent* err)
    {
        trappedErrorCode = err->error_code;
        return 0;
    }

    static bool isShmAvailable() noexcept
    {
        static bool isChecked = false;
        static bool isAvailable = false;

        if (! isChecked)
        {
            isChecked = true;

Septen's avatar
Septen committed
            if (display != nullptr)
Josh Siegle's avatar
Josh Siegle committed
            {
Septen's avatar
Septen committed
                int major, minor;
                Bool pixmaps;
Septen's avatar
Septen committed
                ScopedXLock xlock;
Septen's avatar
Septen committed
                if (XShmQueryVersion (display, &major, &minor, &pixmaps))
Josh Siegle's avatar
Josh Siegle committed
                {
Septen's avatar
Septen committed
                    trappedErrorCode = 0;
                    XErrorHandler oldHandler = XSetErrorHandler (errorTrapHandler);
Septen's avatar
Septen committed
                    XShmSegmentInfo segmentInfo;
                    zerostruct (segmentInfo);
Septen's avatar
Septen committed
                    if (XImage* xImage = XShmCreateImage (display, DefaultVisual (display, DefaultScreen (display)),
                                                          24, ZPixmap, 0, &segmentInfo, 50, 50))
                    {
                        if ((segmentInfo.shmid = shmget (IPC_PRIVATE,
                                                         (size_t) (xImage->bytes_per_line * xImage->height),
                                                         IPC_CREAT | 0777)) >= 0)
Josh Siegle's avatar
Josh Siegle committed
                        {
Septen's avatar
Septen committed
                            segmentInfo.shmaddr = (char*) shmat (segmentInfo.shmid, 0, 0);
Septen's avatar
Septen committed
                            if (segmentInfo.shmaddr != (void*) -1)
                            {
                                segmentInfo.readOnly = False;
                                xImage->data = segmentInfo.shmaddr;
                                XSync (display, False);
Septen's avatar
Septen committed
                                if (XShmAttach (display, &segmentInfo) != 0)
                                {
                                    XSync (display, False);
                                    XShmDetach (display, &segmentInfo);
Septen's avatar
Septen committed
                                    isAvailable = true;
                                }
                            }
Septen's avatar
Septen committed
                            XFlush (display);
                            XDestroyImage (xImage);

                            shmdt (segmentInfo.shmaddr);
                        }
Septen's avatar
Septen committed
                        shmctl (segmentInfo.shmid, IPC_RMID, 0);

                        XSetErrorHandler (oldHandler);
                        if (trappedErrorCode != 0)
                            isAvailable = false;
                    }
                }
Josh Siegle's avatar
Josh Siegle committed
            }
        }

        return isAvailable;
    }
}
#endif

//==============================================================================
#if JUCE_USE_XRENDER
namespace XRender
{
    typedef Status (*tXRenderQueryVersion) (Display*, int*, int*);
    typedef XRenderPictFormat* (*tXRenderFindStandardFormat) (Display*, int);
    typedef XRenderPictFormat* (*tXRenderFindFormat) (Display*, unsigned long, XRenderPictFormat*, int);
    typedef XRenderPictFormat* (*tXRenderFindVisualFormat) (Display*, Visual*);

    static tXRenderQueryVersion xRenderQueryVersion = nullptr;
    static tXRenderFindStandardFormat xRenderFindStandardFormat = nullptr;
    static tXRenderFindFormat xRenderFindFormat = nullptr;
    static tXRenderFindVisualFormat xRenderFindVisualFormat = nullptr;

    static bool isAvailable()
    {
        static bool hasLoaded = false;

        if (! hasLoaded)
        {
Septen's avatar
Septen committed
            if (display != nullptr)
Josh Siegle's avatar
Josh Siegle committed
            {
Septen's avatar
Septen committed
                hasLoaded = true;
Septen's avatar
Septen committed
                ScopedXLock xlock;

                if (void* h = dlopen ("libXrender.so", RTLD_GLOBAL | RTLD_NOW))
                {
                    xRenderQueryVersion         = (tXRenderQueryVersion)        dlsym (h, "XRenderQueryVersion");
                    xRenderFindStandardFormat   = (tXRenderFindStandardFormat)  dlsym (h, "XRenderFindStandardFormat");
                    xRenderFindFormat           = (tXRenderFindFormat)          dlsym (h, "XRenderFindFormat");
                    xRenderFindVisualFormat     = (tXRenderFindVisualFormat)    dlsym (h, "XRenderFindVisualFormat");
                }

                if (xRenderQueryVersion != nullptr
                     && xRenderFindStandardFormat != nullptr
                     && xRenderFindFormat != nullptr
                     && xRenderFindVisualFormat != nullptr)
                {
                    int major, minor;
                    if (xRenderQueryVersion (display, &major, &minor))
                        return true;
                }
Josh Siegle's avatar
Josh Siegle committed
            }

            xRenderQueryVersion = nullptr;
        }

        return xRenderQueryVersion != nullptr;
    }

Septen's avatar
Septen committed
    static bool hasCompositingWindowManager() noexcept
    {
        return display != nullptr
                && XGetSelectionOwner (display, Atoms::getCreating ("_NET_WM_CM_S0")) != 0;
    }

Josh Siegle's avatar
Josh Siegle committed
    static XRenderPictFormat* findPictureFormat()
    {
        ScopedXLock xlock;
        XRenderPictFormat* pictFormat = nullptr;

        if (isAvailable())
        {
            pictFormat = xRenderFindStandardFormat (display, PictStandardARGB32);

            if (pictFormat == nullptr)
            {
                XRenderPictFormat desiredFormat;
                desiredFormat.type = PictTypeDirect;
                desiredFormat.depth = 32;

                desiredFormat.direct.alphaMask = 0xff;
                desiredFormat.direct.redMask   = 0xff;
                desiredFormat.direct.greenMask = 0xff;
                desiredFormat.direct.blueMask  = 0xff;

                desiredFormat.direct.alpha = 24;
                desiredFormat.direct.red   = 16;
                desiredFormat.direct.green = 8;
                desiredFormat.direct.blue  = 0;

                pictFormat = xRenderFindFormat (display,
                                                PictFormatType | PictFormatDepth
                                                 | PictFormatRedMask | PictFormatRed
                                                 | PictFormatGreenMask | PictFormatGreen
                                                 | PictFormatBlueMask | PictFormatBlue
                                                 | PictFormatAlphaMask | PictFormatAlpha,
                                                &desiredFormat,
                                                0);
            }
        }

        return pictFormat;
    }
}
#endif

//==============================================================================
namespace Visuals
{
    static Visual* findVisualWithDepth (const int desiredDepth) noexcept
    {
        ScopedXLock xlock;

        Visual* visual = nullptr;
        int numVisuals = 0;
        long desiredMask = VisualNoMask;
        XVisualInfo desiredVisual;

        desiredVisual.screen = DefaultScreen (display);
        desiredVisual.depth = desiredDepth;

        desiredMask = VisualScreenMask | VisualDepthMask;

        if (desiredDepth == 32)
        {
            desiredVisual.c_class    = TrueColor;
            desiredVisual.red_mask   = 0x00FF0000;
            desiredVisual.green_mask = 0x0000FF00;
            desiredVisual.blue_mask  = 0x000000FF;
            desiredVisual.bits_per_rgb = 8;

            desiredMask |= VisualClassMask;
            desiredMask |= VisualRedMaskMask;
            desiredMask |= VisualGreenMaskMask;
            desiredMask |= VisualBlueMaskMask;
            desiredMask |= VisualBitsPerRGBMask;
        }

Septen's avatar
Septen committed
        if (XVisualInfo* xvinfos = XGetVisualInfo (display,
                                                   desiredMask,
                                                   &desiredVisual,
                                                   &numVisuals))
Josh Siegle's avatar
Josh Siegle committed
        {
            for (int i = 0; i < numVisuals; i++)
            {
                if (xvinfos[i].depth == desiredDepth)
                {
                    visual = xvinfos[i].visual;
                    break;
                }
            }

            XFree (xvinfos);
        }

        return visual;
    }

    static Visual* findVisualFormat (const int desiredDepth, int& matchedDepth) noexcept
    {
        Visual* visual = nullptr;

        if (desiredDepth == 32)
        {
           #if JUCE_USE_XSHM
            if (XSHMHelpers::isShmAvailable())
            {
               #if JUCE_USE_XRENDER
                if (XRender::isAvailable())
                {
Septen's avatar
Septen committed
                    if (XRenderPictFormat* pictFormat = XRender::findPictureFormat())
Josh Siegle's avatar
Josh Siegle committed
                    {
                        int numVisuals = 0;
                        XVisualInfo desiredVisual;
                        desiredVisual.screen = DefaultScreen (display);
                        desiredVisual.depth = 32;
                        desiredVisual.bits_per_rgb = 8;

Septen's avatar
Septen committed
                        if (XVisualInfo* xvinfos = XGetVisualInfo (display,
                                                                   VisualScreenMask | VisualDepthMask | VisualBitsPerRGBMask,
                                                                   &desiredVisual, &numVisuals))
Josh Siegle's avatar
Josh Siegle committed
                        {
                            for (int i = 0; i < numVisuals; ++i)
                            {
                                XRenderPictFormat* pictVisualFormat = XRender::xRenderFindVisualFormat (display, xvinfos[i].visual);

                                if (pictVisualFormat != nullptr
                                     && pictVisualFormat->type == PictTypeDirect
                                     && pictVisualFormat->direct.alphaMask)
                                {
                                    visual = xvinfos[i].visual;
                                    matchedDepth = 32;
                                    break;
                                }
                            }

                            XFree (xvinfos);
                        }
                    }
                }
               #endif
                if (visual == nullptr)
                {
                    visual = findVisualWithDepth (32);
                    if (visual != nullptr)
                        matchedDepth = 32;
                }
            }
           #endif
        }

        if (visual == nullptr && desiredDepth >= 24)
        {
            visual = findVisualWithDepth (24);
            if (visual != nullptr)
                matchedDepth = 24;
        }

        if (visual == nullptr && desiredDepth >= 16)
        {
            visual = findVisualWithDepth (16);
            if (visual != nullptr)
                matchedDepth = 16;
        }

        return visual;
    }
}

//==============================================================================
class XBitmapImage  : public ImagePixelData
{
public:
    XBitmapImage (const Image::PixelFormat format, const int w, const int h,
Septen's avatar
Septen committed
                  const bool clearImage, const unsigned int imageDepth_, Visual* visual)
Josh Siegle's avatar
Josh Siegle committed
        : ImagePixelData (format, w, h),
          imageDepth (imageDepth_),
          gc (None)
    {
        jassert (format == Image::RGB || format == Image::ARGB);

        pixelStride = (format == Image::RGB) ? 3 : 4;
        lineStride = ((w * pixelStride + 3) & ~3);

        ScopedXLock xlock;

       #if JUCE_USE_XSHM
        usingXShm = false;

        if ((imageDepth > 16) && XSHMHelpers::isShmAvailable())
        {
            zerostruct (segmentInfo);

            segmentInfo.shmid = -1;
            segmentInfo.shmaddr = (char *) -1;
            segmentInfo.readOnly = False;

Septen's avatar
Septen committed
            xImage = XShmCreateImage (display, visual, imageDepth, ZPixmap, 0,
                                      &segmentInfo, (unsigned int) w, (unsigned int) h);
Josh Siegle's avatar
Josh Siegle committed

            if (xImage != nullptr)
            {
                if ((segmentInfo.shmid = shmget (IPC_PRIVATE,
Septen's avatar
Septen committed
                                                 (size_t) (xImage->bytes_per_line * xImage->height),
Josh Siegle's avatar
Josh Siegle committed
                                                 IPC_CREAT | 0777)) >= 0)
                {
                    if (segmentInfo.shmid != -1)
                    {
                        segmentInfo.shmaddr = (char*) shmat (segmentInfo.shmid, 0, 0);

                        if (segmentInfo.shmaddr != (void*) -1)
                        {
                            segmentInfo.readOnly = False;

                            xImage->data = segmentInfo.shmaddr;
                            imageData = (uint8*) segmentInfo.shmaddr;

                            if (XShmAttach (display, &segmentInfo) != 0)
                                usingXShm = true;
                            else
                                jassertfalse;
                        }
                        else
                        {
                            shmctl (segmentInfo.shmid, IPC_RMID, 0);
                        }
                    }
                }
            }
        }

Septen's avatar
Septen committed
        if (! isUsingXShm())
Josh Siegle's avatar
Josh Siegle committed
       #endif
        {
Septen's avatar
Septen committed
            imageDataAllocated.allocate ((size_t) (lineStride * h), format == Image::ARGB && clearImage);
Josh Siegle's avatar
Josh Siegle committed
            imageData = imageDataAllocated;

            xImage = (XImage*) ::calloc (1, sizeof (XImage));

            xImage->width = w;
            xImage->height = h;
            xImage->xoffset = 0;
            xImage->format = ZPixmap;
            xImage->data = (char*) imageData;
            xImage->byte_order = ImageByteOrder (display);
            xImage->bitmap_unit = BitmapUnit (display);
            xImage->bitmap_bit_order = BitmapBitOrder (display);
            xImage->bitmap_pad = 32;
            xImage->depth = pixelStride * 8;
            xImage->bytes_per_line = lineStride;
            xImage->bits_per_pixel = pixelStride * 8;
            xImage->red_mask   = 0x00FF0000;
            xImage->green_mask = 0x0000FF00;
            xImage->blue_mask  = 0x000000FF;

            if (imageDepth == 16)
            {
Septen's avatar
Septen committed
                const int pixStride = 2;
                const int stride = ((w * pixStride + 3) & ~3);
Septen's avatar
Septen committed
                imageData16Bit.malloc ((size_t) (stride * h));
Josh Siegle's avatar
Josh Siegle committed
                xImage->data = imageData16Bit;
                xImage->bitmap_pad = 16;
Septen's avatar
Septen committed
                xImage->depth = pixStride * 8;
                xImage->bytes_per_line = stride;
                xImage->bits_per_pixel = pixStride * 8;
Josh Siegle's avatar
Josh Siegle committed
                xImage->red_mask   = visual->red_mask;
                xImage->green_mask = visual->green_mask;
                xImage->blue_mask  = visual->blue_mask;
            }

            if (! XInitImage (xImage))
                jassertfalse;
        }
    }

    ~XBitmapImage()
    {
        ScopedXLock xlock;

        if (gc != None)
            XFreeGC (display, gc);

       #if JUCE_USE_XSHM
Septen's avatar
Septen committed
        if (isUsingXShm())
Josh Siegle's avatar
Josh Siegle committed
        {
            XShmDetach (display, &segmentInfo);

            XFlush (display);
            XDestroyImage (xImage);

            shmdt (segmentInfo.shmaddr);
            shmctl (segmentInfo.shmid, IPC_RMID, 0);
        }
        else
       #endif
        {
            xImage->data = nullptr;
            XDestroyImage (xImage);
        }
    }

    LowLevelGraphicsContext* createLowLevelContext() override
    {
        sendDataChangeMessage();
        return new LowLevelGraphicsSoftwareRenderer (Image (this));
    }

    void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override
    {
        bitmap.data = imageData + x * pixelStride + y * lineStride;
        bitmap.pixelFormat = pixelFormat;
        bitmap.lineStride = lineStride;
        bitmap.pixelStride = pixelStride;

        if (mode != Image::BitmapData::readOnly)
            sendDataChangeMessage();
    }

    ImagePixelData* clone() override
    {
        jassertfalse;
        return nullptr;
    }

    ImageType* createType() const override     { return new NativeImageType(); }

Septen's avatar
Septen committed
    void blitToWindow (Window window, int dx, int dy, unsigned int dw, unsigned int dh, int sx, int sy)
Josh Siegle's avatar
Josh Siegle committed
    {
        ScopedXLock xlock;

        if (gc == None)
        {
            XGCValues gcvalues;
            gcvalues.foreground = None;
            gcvalues.background = None;
            gcvalues.function = GXcopy;
            gcvalues.plane_mask = AllPlanes;
            gcvalues.clip_mask = None;
            gcvalues.graphics_exposures = False;

            gc = XCreateGC (display, window,
                            GCBackground | GCForeground | GCFunction | GCPlaneMask | GCClipMask | GCGraphicsExposures,
                            &gcvalues);
        }

        if (imageDepth == 16)
        {
Septen's avatar
Septen committed
            const uint32 rMask   = (uint32) xImage->red_mask;
            const uint32 gMask   = (uint32) xImage->green_mask;
            const uint32 bMask   = (uint32) xImage->blue_mask;
            const uint32 rShiftL = (uint32) jmax (0,  getShiftNeeded (rMask));
            const uint32 rShiftR = (uint32) jmax (0, -getShiftNeeded (rMask));
            const uint32 gShiftL = (uint32) jmax (0,  getShiftNeeded (gMask));
            const uint32 gShiftR = (uint32) jmax (0, -getShiftNeeded (gMask));
            const uint32 bShiftL = (uint32) jmax (0,  getShiftNeeded (bMask));
            const uint32 bShiftR = (uint32) jmax (0, -getShiftNeeded (bMask));
Josh Siegle's avatar
Josh Siegle committed

            const Image::BitmapData srcData (Image (this), Image::BitmapData::readOnly);

Septen's avatar
Septen committed
            for (int y = sy; y < sy + (int)dh; ++y)
Josh Siegle's avatar
Josh Siegle committed
            {
                const uint8* p = srcData.getPixelPointer (sx, y);

Septen's avatar
Septen committed
                for (int x = sx; x < sx + (int)dw; ++x)
Josh Siegle's avatar
Josh Siegle committed
                {
                    const PixelRGB* const pixel = (const PixelRGB*) p;
                    p += srcData.pixelStride;

                    XPutPixel (xImage, x, y,
Septen's avatar
Septen committed
                                   (((((uint32) pixel->getRed())   << rShiftL) >> rShiftR) & rMask)
Josh Siegle's avatar
Josh Siegle committed
                                 | (((((uint32) pixel->getGreen()) << gShiftL) >> gShiftR) & gMask)
Septen's avatar
Septen committed
                                 | (((((uint32) pixel->getBlue())  << bShiftL) >> bShiftR) & bMask));
Josh Siegle's avatar
Josh Siegle committed
                }
            }
        }

        // blit results to screen.
       #if JUCE_USE_XSHM
Septen's avatar
Septen committed
        if (isUsingXShm())
Josh Siegle's avatar
Josh Siegle committed
            XShmPutImage (display, (::Drawable) window, gc, xImage, sx, sy, dx, dy, dw, dh, True);
        else
       #endif
            XPutImage (display, (::Drawable) window, gc, xImage, sx, sy, dx, dy, dw, dh);
    }

Septen's avatar
Septen committed
    #if JUCE_USE_XSHM
    bool isUsingXShm() const noexcept       { return usingXShm; }
    #endif

Josh Siegle's avatar
Josh Siegle committed
private:
    //==============================================================================
    XImage* xImage;
Septen's avatar
Septen committed
    const unsigned int imageDepth;
    HeapBlock<uint8> imageDataAllocated;
    HeapBlock<char> imageData16Bit;
Josh Siegle's avatar
Josh Siegle committed
    int pixelStride, lineStride;
    uint8* imageData;
    GC gc;

   #if JUCE_USE_XSHM
    XShmSegmentInfo segmentInfo;
    bool usingXShm;
   #endif

    static int getShiftNeeded (const uint32 mask) noexcept
    {
        for (int i = 32; --i >= 0;)
            if (((mask >> i) & 1) != 0)
                return i - 7;

        jassertfalse;
        return 0;
    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XBitmapImage)
};

Septen's avatar
Septen committed
//==============================================================================
#if JUCE_USE_XRANDR
template <>
struct ContainerDeletePolicy<XRRScreenResources>
{
    static void destroy (XRRScreenResources* object);
};

template <>
struct ContainerDeletePolicy<XRROutputInfo>
{
    static void destroy (XRROutputInfo* object);
};

template <>
struct ContainerDeletePolicy<XRRCrtcInfo>
{
    static void destroy (XRRCrtcInfo* object);
};
#endif

//==============================================================================
class DisplayGeometry
{
private:
    //==============================================================================
    DisplayGeometry (::Display* dpy, double masterScale)
    {
        jassert (instance == nullptr);
        instance = this;

        queryDisplayInfos (dpy, masterScale);
        updatePositions();
    }

public:
    //==============================================================================
    struct ExtendedInfo
    {
        // Unlike Desktop::Displays::Display, the following is in
        // physical pixels, i.e. the area is not scaled
        Rectangle<int> totalBounds;
        // Usable bounds is the usable area in local coordinates
        // with respect to the above totalBounds
        Rectangle<int> usableBounds;
        // top-left point of display in scaled coordinates. This
        // is different from totalBounds.getTopLeft() / scale,
        // because the neighbouring display may have a different
        // scale factor
        Point<int> topLeftScaled;
        double dpi, scale;
        bool isMain;
    };

    Array<ExtendedInfo> infos;

    //==============================================================================
    ExtendedInfo& findDisplayForRect (const Rectangle<int>& bounds, bool isScaledBounds)
    {
        int maxArea = -1;
        ExtendedInfo* retval = nullptr;

        for (int i = 0; i < infos.size(); ++i)
        {
            ExtendedInfo& dpy = infos.getReference (i);

            Rectangle<int> displayBounds = dpy.totalBounds;

            if (isScaledBounds)
                displayBounds = (displayBounds.withZeroOrigin() / dpy.scale) + dpy.topLeftScaled;

            displayBounds = displayBounds.getIntersection (bounds);
            int area = displayBounds.getWidth() * displayBounds.getHeight();

            if (area >= maxArea)
            {
                maxArea = area;
                retval = &dpy;
            }
        }

        return *retval;
    }

    ExtendedInfo& findDisplayForPoint (Point<int> pt, bool isScaledPoint)
    {
        int minDistance = (int) ((((unsigned int)(-1)) >> 1) - 1);
        ExtendedInfo* retval = nullptr;

        for (int i = 0; i < infos.size(); ++i)
        {
            ExtendedInfo& dpy = infos.getReference (i);

            Rectangle<int> displayBounds = dpy.totalBounds;

            if (isScaledPoint)
                displayBounds = (displayBounds.withZeroOrigin() / dpy.scale) + dpy.topLeftScaled;

            if (displayBounds.contains (pt))
                return dpy;

            int distance = displayBounds.getCentre().getDistanceFrom (pt);
            if (distance <= minDistance)
            {
                minDistance = distance;
                retval = &dpy;
            }
        }

        return *retval;
    }

    //==============================================================================
    static Rectangle<int> physicalToScaled (const Rectangle<int>& physicalBounds)
    {
        // first find with which display physicalBounds has the most overlap
        ExtendedInfo& dpy = getInstance().findDisplayForRect (physicalBounds, false);

        // convert to local screen bounds
        Rectangle<int> retval = physicalBounds - dpy.totalBounds.getTopLeft();

        // now we can safely scale the coordinates and convert to global again
        return (retval / dpy.scale) + dpy.topLeftScaled;
    }

    static Rectangle<int> scaledToPhysical (const Rectangle<int>& scaledBounds)
    {
        // first find with which display physicalBounds has the most overlap
        ExtendedInfo& dpy = getInstance().findDisplayForRect (scaledBounds, true);

        // convert to local screen bounds
        Rectangle<int> retval = scaledBounds - dpy.topLeftScaled;

        // now we can safely scale the coordinates and convert to global again
        return (retval * dpy.scale) + dpy.totalBounds.getTopLeft();
    }

    //==============================================================================
    template <typename ValueType>
    static Point<ValueType> physicalToScaled (const Point<ValueType>& physicalPoint)
    {
        ExtendedInfo& dpy = getInstance().findDisplayForPoint (physicalPoint.roundToInt(), false);
        Point<ValueType> scaledTopLeft =
            Point<ValueType> (dpy.topLeftScaled.getX(), dpy.topLeftScaled.getY());
        Point<ValueType> physicalTopLeft =
            Point<ValueType> (dpy.totalBounds.getX(), dpy.totalBounds.getY());

        return ((physicalPoint - physicalTopLeft) / dpy.scale) + scaledTopLeft;
    }

    template <typename ValueType>
    static Point<ValueType> scaledToPhysical (const Point<ValueType>& scaledPoint)
    {
        ExtendedInfo& dpy = getInstance().findDisplayForPoint (scaledPoint.roundToInt(), true);
        Point<ValueType> scaledTopLeft =
            Point<ValueType> (dpy.topLeftScaled.getX(), dpy.topLeftScaled.getY());
        Point<ValueType> physicalTopLeft =
            Point<ValueType> (dpy.totalBounds.getX(), dpy.totalBounds.getY());

        return ((scaledPoint - scaledTopLeft) * dpy.scale) + physicalTopLeft;
    }

    //==============================================================================
    static DisplayGeometry& getInstance()
    {
        jassert (instance != nullptr);
        return *instance;
    }

    static DisplayGeometry& getOrCreateInstance (::Display* dpy, double masterScale)
    {
        if (instance == nullptr)
            new DisplayGeometry (dpy, masterScale);

        return getInstance();
    }

private:
    //==============================================================================
    static DisplayGeometry* instance;

    //==============================================================================
   #if JUCE_USE_XINERAMA
    static Array<XineramaScreenInfo> XineramaQueryDisplays (::Display* dpy)
    {
        typedef Bool (*tXineramaIsActive) (::Display*);
        typedef XineramaScreenInfo* (*tXineramaQueryScreens) (::Display*, int*);

        int major_opcode, first_event, first_error;

        if (XQueryExtension (dpy, "XINERAMA", &major_opcode, &first_event, &first_error))
        {
            static void* libXinerama = nullptr;
            static tXineramaIsActive isActiveFuncPtr = nullptr;
            static tXineramaQueryScreens xineramaQueryScreens = nullptr;

            if (libXinerama == nullptr)
            {
                libXinerama = dlopen ("libXinerama.so", RTLD_GLOBAL | RTLD_NOW);

                if (libXinerama == nullptr)
                    libXinerama = dlopen ("libXinerama.so.1", RTLD_GLOBAL | RTLD_NOW);

                if (libXinerama != nullptr)
                {
                    isActiveFuncPtr = (tXineramaIsActive) dlsym (libXinerama, "XineramaIsActive");
                    xineramaQueryScreens = (tXineramaQueryScreens) dlsym (libXinerama, "XineramaQueryScreens");
                }
            }

            if (isActiveFuncPtr != nullptr && xineramaQueryScreens != nullptr && isActiveFuncPtr (dpy) != 0)
            {
                int numScreens;
                if (XineramaScreenInfo* xinfo = xineramaQueryScreens (dpy, &numScreens))
                {
                    Array<XineramaScreenInfo> infos (xinfo, numScreens);
                    XFree (xinfo);

                    return infos;
                }
            }
        }

        return Array<XineramaScreenInfo>();
    }
   #endif

    //==============================================================================
   #if JUCE_USE_XRANDR
    friend struct ContainerDeletePolicy<XRRScreenResources>;
    friend struct ContainerDeletePolicy<XRROutputInfo>;
    friend struct ContainerDeletePolicy<XRRCrtcInfo>;

    class XRandrWrapper
    {
    private:
        XRandrWrapper()
            : libXrandr (nullptr),
              getScreenResourcesPtr (nullptr),
              freeScreenResourcesPtr (nullptr),
              getOutputInfoPtr (nullptr),
              freeOutputInfoPtr (nullptr),
              getCrtcInfoPtr (nullptr),
              freeCrtcInfoPtr (nullptr),
              getOutputPrimaryPtr (nullptr)
        {
            if (libXrandr == nullptr)
            {
                libXrandr = dlopen ("libXrandr.so", RTLD_GLOBAL | RTLD_NOW);

                if (libXrandr == nullptr)
                    libXrandr = dlopen ("libXinerama.so.2", RTLD_GLOBAL | RTLD_NOW);

                if (libXrandr != nullptr)
                {
                    getScreenResourcesPtr  = (tXRRGetScreenResources)  dlsym (libXrandr, "XRRGetScreenResources");
                    freeScreenResourcesPtr = (tXRRFreeScreenResources) dlsym (libXrandr, "XRRFreeScreenResources");
                    getOutputInfoPtr       = (tXRRGetOutputInfo)       dlsym (libXrandr, "XRRGetOutputInfo");
                    freeOutputInfoPtr      = (tXRRFreeOutputInfo)      dlsym (libXrandr, "XRRFreeOutputInfo");
                    getCrtcInfoPtr         = (tXRRGetCrtcInfo)         dlsym (libXrandr, "XRRGetCrtcInfo");
                    freeCrtcInfoPtr        = (tXRRFreeCrtcInfo)        dlsym (libXrandr, "XRRFreeCrtcInfo");
                    getOutputPrimaryPtr    = (tXRRGetOutputPrimary)    dlsym (libXrandr, "XRRGetOutputPrimary");
                }
            }