text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

#if JUCE_MAC

//==============================================================================
namespace MouseCursorHelpers
{
    NSImage* createNSImage (const Image&, float scaleFactor = 1.0f);
    NSImage* createNSImage (const Image& image, float scaleFactor)
    {
        JUCE_AUTORELEASEPOOL
        {
            NSImage* im = [[NSImage alloc] init];
            const NSSize requiredSize = NSMakeSize (image.getWidth() / scaleFactor, image.getHeight() / scaleFactor);

            [im setSize: requiredSize];
            CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
            CGImageRef imageRef = juce_createCoreGraphicsImage (image, colourSpace, true);
            CGColorSpaceRelease (colourSpace);

            NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef];
            [imageRep setSize: requiredSize];
            [im addRepresentation: imageRep];
            [imageRep release];
            CGImageRelease (imageRef);
            return im;
        }
    }

    static NSCursor* fromNSImage (NSImage* im, NSPoint hotspot)
    {
        NSCursor* c = [[NSCursor alloc] initWithImage: im
                                              hotSpot: hotspot];
        [im release];
        return c;
    }

    static void* fromHIServices (const char* filename)
    {
        JUCE_AUTORELEASEPOOL
        {
            const String cursorPath (String ("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/"
                                             "HIServices.framework/Versions/A/Resources/cursors/")
                                       + filename);

            NSImage* originalImage = [[NSImage alloc] initByReferencingFile: juceStringToNS (cursorPath + "/cursor.pdf")];
            NSSize originalSize = [originalImage size];
            NSImage* resultImage   = [[NSImage alloc] initWithSize: originalSize];

            for (int scale = 1; scale <= 4; ++scale)
            {
                NSAffineTransform* scaleTransform = [NSAffineTransform transform];
                [scaleTransform scaleBy: (float) scale];

                if (CGImageRef rasterCGImage = [originalImage CGImageForProposedRect: nil
                                                                             context: nil
                                                                               hints: [NSDictionary dictionaryWithObjectsAndKeys:
                                                                                NSImageHintCTM, scaleTransform, nil]])
                {
                    NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: rasterCGImage];
                    [imageRep setSize: originalSize];

                    [resultImage addRepresentation: imageRep];
                    [imageRep release];
                }
                else
                {
                    return nil;
                }
            }

            [originalImage release];

            NSDictionary* info = [NSDictionary dictionaryWithContentsOfFile: juceStringToNS (cursorPath + "/info.plist")];

            const float hotspotX = (float) [[info valueForKey: nsStringLiteral ("hotx")] doubleValue];
            const float hotspotY = (float) [[info valueForKey: nsStringLiteral ("hoty")] doubleValue];

            return fromNSImage (resultImage, NSMakePoint (hotspotX, hotspotY));
        }
    }
}

void* CustomMouseCursorInfo::create() const
{
    return MouseCursorHelpers::fromNSImage (MouseCursorHelpers::createNSImage (image, scaleFactor),
                                            NSMakePoint (hotspot.x, hotspot.y));
}

void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType type)
{
    JUCE_AUTORELEASEPOOL
    {
        NSCursor* c = nil;

        switch (type)
        {
            case NormalCursor:
            case ParentCursor:          c = [NSCursor arrowCursor]; break;
            case NoCursor:              return CustomMouseCursorInfo (Image (Image::ARGB, 8, 8, true), 0, 0).create();
            case DraggingHandCursor:    c = [NSCursor openHandCursor]; break;
            case WaitCursor:            c = [NSCursor arrowCursor]; break; // avoid this on the mac, let the OS provide the beachball
            case IBeamCursor:           c = [NSCursor IBeamCursor]; break;
            case PointingHandCursor:    c = [NSCursor pointingHandCursor]; break;
            case LeftEdgeResizeCursor:  c = [NSCursor resizeLeftCursor]; break;
            case RightEdgeResizeCursor: c = [NSCursor resizeRightCursor]; break;
            case CrosshairCursor:       c = [NSCursor crosshairCursor]; break;

            case CopyingCursor:
            {
               #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_6
                if (void* m = MouseCursorHelpers::fromHIServices ("copy"))
                    return m;
               #endif

                c = [NSCursor dragCopyCursor]; // added in 10.6
                break;
            }

            case UpDownResizeCursor:
            case TopEdgeResizeCursor:
            case BottomEdgeResizeCursor:
                if (void* m = MouseCursorHelpers::fromHIServices ("resizenorthsouth"))
                    return m;

                c = [NSCursor resizeUpDownCursor];
                break;

            case LeftRightResizeCursor:
                if (void* m = MouseCursorHelpers::fromHIServices ("resizeeastwest"))
                    return m;

                c = [NSCursor resizeLeftRightCursor];
                break;

            case TopLeftCornerResizeCursor:
            case BottomRightCornerResizeCursor:
                return MouseCursorHelpers::fromHIServices ("resizenorthwestsoutheast");

            case TopRightCornerResizeCursor:
            case BottomLeftCornerResizeCursor:
                return MouseCursorHelpers::fromHIServices ("resizenortheastsouthwest");

            case UpDownLeftRightResizeCursor:
                return MouseCursorHelpers::fromHIServices ("move");

            default:
                jassertfalse;
                break;
        }

        [c retain];
        return c;
    }
}

void MouseCursor::deleteMouseCursor (void* const cursorHandle, const bool /*isStandard*/)
{
    [((NSCursor*) cursorHandle) release];
}

void MouseCursor::showInAllWindows() const
{
    showInWindow (nullptr);
}

void MouseCursor::showInWindow (ComponentPeer*) const
{
    NSCursor* c = (NSCursor*) getHandle();

    if (c == nil)
        c = [NSCursor arrowCursor];

    [c set];
}

#else

void* CustomMouseCursorInfo::create() const                                              { return nullptr; }
void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType)           { return nullptr; }
void MouseCursor::deleteMouseCursor (void*, bool)                                        {}
void MouseCursor::showInAllWindows() const                                               {}
void MouseCursor::showInWindow (ComponentPeer*) const                                    {}

#endif

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

struct _XDisplay;

namespace juce
{

typedef ::_XDisplay* XDisplay;

typedef unsigned long AtomType;
typedef unsigned long WindowType;

//==============================================================================
class XWindowSystem
{
public:
    XDisplay displayRef() noexcept;
    XDisplay displayUnref() noexcept;

    juce_DeclareSingleton (XWindowSystem, false)

private:
    XDisplay display = {};
    Atomic<int> displayCount;

    XWindowSystem() noexcept;
    ~XWindowSystem() noexcept;

    void initialiseXDisplay() noexcept;
    void destroyXDisplay() noexcept;
};

//==============================================================================
/** Creates and holds a reference to the X display. */
struct ScopedXDisplay
{
    ScopedXDisplay();
    ~ScopedXDisplay();

    const XDisplay display;
};

//==============================================================================
/** A handy class that uses XLockDisplay and XUnlockDisplay to lock the X server
    using RAII (Only available in Linux!).
*/
class ScopedXLock
{
public:
    /** Creating a ScopedXLock object locks the X display.
        This uses XLockDisplay() to grab the display that Juce is using.
    */
    ScopedXLock (XDisplay);

    /** Deleting a ScopedXLock object unlocks the X display.
        This calls XUnlockDisplay() to release the lock.
    */
    ~ScopedXLock();

private:
    // defined in juce_linux_X11.h
    XDisplay display;
};

//==============================================================================
struct Atoms
{
    Atoms (XDisplay);

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

    AtomType protocols, protocolList[3], changeState, state, userTime,
             activeWin, pid, windowType, windowState,
             XdndAware, XdndEnter, XdndLeave, XdndPosition, XdndStatus,
             XdndDrop, XdndFinished, XdndSelection, XdndTypeList, XdndActionList,
             XdndActionDescription, XdndActionCopy, XdndActionPrivate,
             XembedMsgType, XembedInfo,
             allowedActions[5],
             allowedMimeTypes[4];

    static const unsigned long DndVersion;

    static AtomType getIfExists (XDisplay, const char* name);
    static AtomType getCreating (XDisplay, const char* name);

    static String getName (XDisplay, AtomType);

    static bool isMimeTypeFile (XDisplay, AtomType);
};

//==============================================================================
struct GetXProperty
{
    GetXProperty (XDisplay, WindowType, AtomType,
                  long offset, long length, bool shouldDelete,
                  AtomType requestedType);

    ~GetXProperty();

    bool success;
    unsigned char* data = nullptr;
    unsigned long numItems, bytesLeft;
    AtomType actualType;
    int actualFormat;
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

namespace DragAndDropHelpers
{
    //==============================================================================
    struct JuceDropSource   : public ComBaseClassHelper<IDropSource>
    {
        JuceDropSource() {}

        JUCE_COMRESULT QueryContinueDrag (BOOL escapePressed, DWORD keys) override
        {
            if (escapePressed)
                return DRAGDROP_S_CANCEL;

            if ((keys & (MK_LBUTTON | MK_RBUTTON)) == 0)
                return DRAGDROP_S_DROP;

            return S_OK;
        }

        JUCE_COMRESULT GiveFeedback (DWORD) override
        {
            return DRAGDROP_S_USEDEFAULTCURSORS;
        }
    };

    //==============================================================================
    struct JuceEnumFormatEtc   : public ComBaseClassHelper<IEnumFORMATETC>
    {
        JuceEnumFormatEtc (const FORMATETC* f)  : format (f) {}

        JUCE_COMRESULT Clone (IEnumFORMATETC** result) override
        {
            if (result == 0)
                return E_POINTER;

            auto newOne = new JuceEnumFormatEtc (format);
            newOne->index = index;
            *result = newOne;
            return S_OK;
        }

        JUCE_COMRESULT Next (ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched) override
        {
            if (pceltFetched != nullptr)
                *pceltFetched = 0;
            else if (celt != 1)
                return S_FALSE;

            if (index == 0 && celt > 0 && lpFormatEtc != 0)
            {
                copyFormatEtc (lpFormatEtc [0], *format);
                ++index;

                if (pceltFetched != nullptr)
                    *pceltFetched = 1;

                return S_OK;
            }

            return S_FALSE;
        }

        JUCE_COMRESULT Skip (ULONG celt) override
        {
            if (index + (int) celt >= 1)
                return S_FALSE;

            index += celt;
            return S_OK;
        }

        JUCE_COMRESULT Reset() override
        {
            index = 0;
            return S_OK;
        }

    private:
        const FORMATETC* const format;
        int index = 0;

        static void copyFormatEtc (FORMATETC& dest, const FORMATETC& source)
        {
            dest = source;

            if (source.ptd != 0)
            {
                dest.ptd = (DVTARGETDEVICE*) CoTaskMemAlloc (sizeof (DVTARGETDEVICE));
                *(dest.ptd) = *(source.ptd);
            }
        }

        JUCE_DECLARE_NON_COPYABLE (JuceEnumFormatEtc)
    };

    //==============================================================================
    class JuceDataObject  : public ComBaseClassHelper <IDataObject>
    {
    public:
        JuceDataObject (JuceDropSource* s, const FORMATETC* f, const STGMEDIUM* m)
            : dropSource (s), format (f), medium (m)
        {
        }

        ~JuceDataObject()
        {
            jassert (refCount == 0);
        }

        JUCE_COMRESULT GetData (FORMATETC* pFormatEtc, STGMEDIUM* pMedium)
        {
            if ((pFormatEtc->tymed & format->tymed) != 0
                 && pFormatEtc->cfFormat == format->cfFormat
                 && pFormatEtc->dwAspect == format->dwAspect)
            {
                pMedium->tymed = format->tymed;
                pMedium->pUnkForRelease = 0;

                if (format->tymed == TYMED_HGLOBAL)
                {
                    auto len = GlobalSize (medium->hGlobal);
                    void* const src = GlobalLock (medium->hGlobal);
                    void* const dst = GlobalAlloc (GMEM_FIXED, len);

                    memcpy (dst, src, len);

                    GlobalUnlock (medium->hGlobal);

                    pMedium->hGlobal = dst;
                    return S_OK;
                }
            }

            return DV_E_FORMATETC;
        }

        JUCE_COMRESULT QueryGetData (FORMATETC* f)
        {
            if (f == 0)
                return E_INVALIDARG;

            if (f->tymed == format->tymed
                  && f->cfFormat == format->cfFormat
                  && f->dwAspect == format->dwAspect)
                return S_OK;

            return DV_E_FORMATETC;
        }

        JUCE_COMRESULT GetCanonicalFormatEtc (FORMATETC*, FORMATETC* pFormatEtcOut)
        {
            pFormatEtcOut->ptd = 0;
            return E_NOTIMPL;
        }

        JUCE_COMRESULT EnumFormatEtc (DWORD direction, IEnumFORMATETC** result)
        {
            if (result == 0)
                return E_POINTER;

            if (direction == DATADIR_GET)
            {
                *result = new JuceEnumFormatEtc (format);
                return S_OK;
            }

            *result = 0;
            return E_NOTIMPL;
        }

        JUCE_COMRESULT GetDataHere (FORMATETC*, STGMEDIUM*)                  { return DATA_E_FORMATETC; }
        JUCE_COMRESULT SetData (FORMATETC*, STGMEDIUM*, BOOL)                { return E_NOTIMPL; }
        JUCE_COMRESULT DAdvise (FORMATETC*, DWORD, IAdviseSink*, DWORD*)     { return OLE_E_ADVISENOTSUPPORTED; }
        JUCE_COMRESULT DUnadvise (DWORD)                                     { return E_NOTIMPL; }
        JUCE_COMRESULT EnumDAdvise (IEnumSTATDATA**)                         { return OLE_E_ADVISENOTSUPPORTED; }

    private:
        JuceDropSource* const dropSource;
        const FORMATETC* const format;
        const STGMEDIUM* const medium;

        JUCE_DECLARE_NON_COPYABLE (JuceDataObject)
    };

    //==============================================================================
    HDROP createHDrop (const StringArray& fileNames)
    {
        int totalBytes = 0;
        for (int i = fileNames.size(); --i >= 0;)
            totalBytes += (int) CharPointer_UTF16::getBytesRequiredFor (fileNames[i].getCharPointer()) + sizeof (WCHAR);

        HDROP hDrop = (HDROP) GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof (DROPFILES) + totalBytes + 4);

        if (hDrop != 0)
        {
            auto pDropFiles = (LPDROPFILES) GlobalLock (hDrop);
            pDropFiles->pFiles = sizeof (DROPFILES);
            pDropFiles->fWide = true;

            auto* fname = reinterpret_cast<WCHAR*> (addBytesToPointer (pDropFiles, sizeof (DROPFILES)));

            for (int i = 0; i < fileNames.size(); ++i)
            {
                auto bytesWritten = fileNames[i].copyToUTF16 (fname, 2048);
                fname = reinterpret_cast<WCHAR*> (addBytesToPointer (fname, bytesWritten));
            }

            *fname = 0;

            GlobalUnlock (hDrop);
        }

        return hDrop;
    }

    bool performDragDrop (FORMATETC* const format, STGMEDIUM* const medium, const DWORD whatToDo)
    {
        auto source = new JuceDropSource();
        auto data   = new JuceDataObject (source, format, medium);

        DWORD effect;
        auto res = DoDragDrop (data, source, whatToDo, &effect);

        data->Release();
        source->Release();

        return res == DRAGDROP_S_DROP;
    }
}

//==============================================================================
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove, Component*)
{
    FORMATETC format = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 };

    medium.hGlobal = DragAndDropHelpers::createHDrop (files);

    return DragAndDropHelpers::performDragDrop (&format, &medium, canMove ? (DWORD) (DROPEFFECT_COPY | DROPEFFECT_MOVE)
                                                                          : (DWORD) DROPEFFECT_COPY);
}

bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component*)
{
    FORMATETC format = { CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM medium = { TYMED_HGLOBAL, { 0 }, 0 };

    auto numBytes = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());

    medium.hGlobal = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, numBytes + 2);
    WCHAR* const data = static_cast<WCHAR*> (GlobalLock (medium.hGlobal));

    text.copyToUTF16 (data, numBytes);
    format.cfFormat = CF_UNICODETEXT;

    GlobalUnlock (medium.hGlobal);

    return DragAndDropHelpers::performDragDrop (&format, &medium, DROPEFFECT_COPY | DROPEFFECT_MOVE);
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

static bool exeIsAvailable (const char* const executable)
{
     ChildProcess child;
     const bool ok = child.start ("which " + String (executable))
                       && child.readAllProcessOutput().trim().isNotEmpty();

     child.waitForProcessToFinish (60 * 1000);
     return ok;
}

bool FileChooser::isPlatformDialogAvailable()
{
   #if JUCE_DISABLE_NATIVE_FILECHOOSERS
    return false;
   #else
    static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog");
    return canUseNativeBox;
   #endif
}

static uint64 getTopWindowID() noexcept
{
    if (TopLevelWindow* top = TopLevelWindow::getActiveTopLevelWindow())
        return (uint64) (pointer_sized_uint) top->getWindowHandle();

    return 0;
}

static bool isKdeFullSession()
{
    return SystemStats::getEnvironmentVariable ("KDE_FULL_SESSION", String())
             .equalsIgnoreCase ("true");
}

static void addKDialogArgs (StringArray& args, String& separator,
                            const String& title, const File& file, const String& filters,
                            bool isDirectory, bool isSave, bool selectMultipleFiles)
{
    args.add ("kdialog");

    if (title.isNotEmpty())
        args.add ("--title=" + title);

    if (uint64 topWindowID = getTopWindowID())
    {
        args.add ("--attach");
        args.add (String (topWindowID));
    }

    if (selectMultipleFiles)
    {
        separator = "\n";
        args.add ("--multiple");
        args.add ("--separate-output");
        args.add ("--getopenfilename");
    }
    else
    {
        if (isSave)             args.add ("--getsavefilename");
        else if (isDirectory)   args.add ("--getexistingdirectory");
        else                    args.add ("--getopenfilename");
    }

    File startPath;

    if (file.exists())
    {
        startPath = file;
    }
    else if (file.getParentDirectory().exists())
    {
        startPath = file.getParentDirectory();
    }
    else
    {
        startPath = File::getSpecialLocation (File::userHomeDirectory);

        if (isSave)
            startPath = startPath.getChildFile (file.getFileName());
    }

    args.add (startPath.getFullPathName());
    args.add (filters.replaceCharacter (';', ' '));
}

static void addZenityArgs (StringArray& args, String& separator,
                           const String& title, const File& file, const String& filters,
                           bool isDirectory, bool isSave, bool selectMultipleFiles)
{
    args.add ("zenity");
    args.add ("--file-selection");

    if (title.isNotEmpty())
        args.add ("--title=" + title);

    if (selectMultipleFiles)
    {
        separator = ":";
        args.add ("--multiple");
        args.add ("--separator=" + separator);
    }
    else
    {
        if (isDirectory)  args.add ("--directory");
        if (isSave)       args.add ("--save");
    }

    if (filters.isNotEmpty() && filters != "*" && filters != "*.*")
    {
        StringArray tokens;
        tokens.addTokens (filters, ";,|", "\"");

        for (int i = 0; i < tokens.size(); ++i)
            args.add ("--file-filter=" + tokens[i]);
    }

    if (file.isDirectory())
        file.setAsCurrentWorkingDirectory();
    else if (file.getParentDirectory().exists())
        file.getParentDirectory().setAsCurrentWorkingDirectory();
    else
        File::getSpecialLocation (File::userHomeDirectory).setAsCurrentWorkingDirectory();

    if (! file.getFileName().isEmpty())
        args.add ("--filename=" + file.getFileName());

    // supplying the window ID of the topmost window makes sure that Zenity pops up..
    if (uint64 topWindowID = getTopWindowID())
        setenv ("WINDOWID", String (topWindowID).toRawUTF8(), true);
}

void FileChooser::showPlatformDialog (Array<File>& results,
                                      const String& title, const File& file, const String& filters,
                                      bool isDirectory, bool /* selectsFiles */,
                                      bool isSave, bool /* warnAboutOverwritingExistingFiles */,
                                      bool /*treatFilePackagesAsDirs*/,
                                      bool selectMultipleFiles, FilePreviewComponent*)
{
    const File previousWorkingDirectory (File::getCurrentWorkingDirectory());

    StringArray args;
    String separator;

    // use kdialog for KDE sessions or if zenity is missing
    if (exeIsAvailable ("kdialog") && (isKdeFullSession() || ! exeIsAvailable ("zenity")))
        addKDialogArgs (args, separator, title, file, filters, isDirectory, isSave, selectMultipleFiles);
    else
        addZenityArgs (args, separator, title, file, filters, isDirectory, isSave, selectMultipleFiles);

    ChildProcess child;

    if (child.start (args, ChildProcess::wantStdOut))
    {
        if (MessageManager::getInstance()->isThisTheMessageThread())
        {
            while (child.isRunning())
            {
                if (! MessageManager::getInstance()->runDispatchLoopUntil(20))
                    break;
            }
        }

        const String result (child.readAllProcessOutput().trim());

        if (result.isNotEmpty())
        {
            StringArray tokens;

            if (selectMultipleFiles)
                tokens.addTokens (result, separator, "\"");
            else
                tokens.add (result);

            for (int i = 0; i < tokens.size(); ++i)
                results.add (File::getCurrentWorkingDirectory().getChildFile (tokens[i]));
        }

        child.waitForProcessToFinish (60 * 1000);
    }

    previousWorkingDirectory.setAsCurrentWorkingDirectory();
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

class UIViewComponentPeer;

// The way rotation works changed in iOS8..
static bool isUsingOldRotationMethod() noexcept
{
    static bool isPreV8 = ([[[UIDevice currentDevice] systemVersion] compare: @"8.0"
                                                                     options: NSNumericSearch] == NSOrderedAscending);
    return isPreV8;
}

namespace Orientations
{
    static Desktop::DisplayOrientation convertToJuce (UIInterfaceOrientation orientation)
    {
        switch (orientation)
        {
            case UIInterfaceOrientationPortrait:            return Desktop::upright;
            case UIInterfaceOrientationPortraitUpsideDown:  return Desktop::upsideDown;
            case UIInterfaceOrientationLandscapeLeft:       return Desktop::rotatedClockwise;
            case UIInterfaceOrientationLandscapeRight:      return Desktop::rotatedAntiClockwise;
            default:                                        jassertfalse; // unknown orientation!
        }

        return Desktop::upright;
    }

    static UIInterfaceOrientation convertFromJuce (Desktop::DisplayOrientation orientation)
    {
        switch (orientation)
        {
            case Desktop::upright:                          return UIInterfaceOrientationPortrait;
            case Desktop::upsideDown:                       return UIInterfaceOrientationPortraitUpsideDown;
            case Desktop::rotatedClockwise:                 return UIInterfaceOrientationLandscapeLeft;
            case Desktop::rotatedAntiClockwise:             return UIInterfaceOrientationLandscapeRight;
            default:                                        jassertfalse; // unknown orientation!
        }

        return UIInterfaceOrientationPortrait;
    }

    static CGAffineTransform getCGTransformFor (const Desktop::DisplayOrientation orientation) noexcept
    {
        if (isUsingOldRotationMethod())
        {
            switch (orientation)
            {
                case Desktop::upsideDown:             return CGAffineTransformMake (-1, 0,  0, -1, 0, 0);
                case Desktop::rotatedClockwise:       return CGAffineTransformMake (0, -1,  1,  0, 0, 0);
                case Desktop::rotatedAntiClockwise:   return CGAffineTransformMake (0,  1, -1,  0, 0, 0);
                default: break;
            }
        }

        return CGAffineTransformIdentity;
    }

    static NSUInteger getSupportedOrientations()
    {
        NSUInteger allowed = 0;
        Desktop& d = Desktop::getInstance();

        if (d.isOrientationEnabled (Desktop::upright))              allowed |= UIInterfaceOrientationMaskPortrait;
        if (d.isOrientationEnabled (Desktop::upsideDown))           allowed |= UIInterfaceOrientationMaskPortraitUpsideDown;
        if (d.isOrientationEnabled (Desktop::rotatedClockwise))     allowed |= UIInterfaceOrientationMaskLandscapeLeft;
        if (d.isOrientationEnabled (Desktop::rotatedAntiClockwise)) allowed |= UIInterfaceOrientationMaskLandscapeRight;

        return allowed;
    }
}

//==============================================================================
} // namespace juce

using namespace juce;

@interface JuceUIView : UIView <UITextViewDelegate>
{
@public
    UIViewComponentPeer* owner;
    UITextView* hiddenTextView;
}

- (JuceUIView*) initWithOwner: (UIViewComponentPeer*) owner withFrame: (CGRect) frame;
- (void) dealloc;

- (void) drawRect: (CGRect) r;

- (void) touchesBegan:     (NSSet*) touches  withEvent: (UIEvent*) event;
- (void) touchesMoved:     (NSSet*) touches  withEvent: (UIEvent*) event;
- (void) touchesEnded:     (NSSet*) touches  withEvent: (UIEvent*) event;
- (void) touchesCancelled: (NSSet*) touches  withEvent: (UIEvent*) event;

- (BOOL) becomeFirstResponder;
- (BOOL) resignFirstResponder;
- (BOOL) canBecomeFirstResponder;

- (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text;
@end

//==============================================================================
@interface JuceUIViewController : UIViewController
{
}

- (NSUInteger) supportedInterfaceOrientations;
- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation;
- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration;
- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation;
- (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>) coordinator;
- (BOOL) prefersStatusBarHidden;
- (UIStatusBarStyle) preferredStatusBarStyle;

- (void) viewDidLoad;
- (void) viewWillAppear: (BOOL) animated;
- (void) viewDidAppear: (BOOL) animated;
- (void) viewWillLayoutSubviews;
- (void) viewDidLayoutSubviews;
@end

//==============================================================================
@interface JuceUIWindow : UIWindow
{
@private
    UIViewComponentPeer* owner;
}

- (void) setOwner: (UIViewComponentPeer*) owner;
- (void) becomeKeyWindow;
@end

//==============================================================================
//==============================================================================
namespace juce
{

class UIViewComponentPeer  : public ComponentPeer,
                             public FocusChangeListener
{
public:
    UIViewComponentPeer (Component&, int windowStyleFlags, UIView* viewToAttachTo);
    ~UIViewComponentPeer();

    //==============================================================================
    void* getNativeHandle() const override                  { return view; }
    void setVisible (bool shouldBeVisible) override;
    void setTitle (const String& title) override;
    void setBounds (const Rectangle<int>&, bool isNowFullScreen) override;

    Rectangle<int> getBounds() const override               { return getBounds (! isSharedWindow); }
    Rectangle<int> getBounds (bool global) const;
    Point<float> localToGlobal (Point<float> relativePosition) override;
    Point<float> globalToLocal (Point<float> screenPosition) override;
    void setAlpha (float newAlpha) override;
    void setMinimised (bool) override                       {}
    bool isMinimised() const override                       { return false; }
    void setFullScreen (bool shouldBeFullScreen) override;
    bool isFullScreen() const override                      { return fullScreen; }
    bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override;
    BorderSize<int> getFrameSize() const override           { return BorderSize<int>(); }
    bool setAlwaysOnTop (bool alwaysOnTop) override;
    void toFront (bool makeActiveWindow) override;
    void toBehind (ComponentPeer* other) override;
    void setIcon (const Image& newIcon) override;
    StringArray getAvailableRenderingEngines() override     { return StringArray ("CoreGraphics Renderer"); }

    void drawRect (CGRect);
    bool canBecomeKeyWindow();

    //==============================================================================
    void viewFocusGain();
    void viewFocusLoss();
    bool isFocused() const override;
    void grabFocus() override;
    void textInputRequired (Point<int>, TextInputTarget&) override;

    BOOL textViewReplaceCharacters (Range<int>, const String&);
    void updateHiddenTextContent (TextInputTarget*);
    void globalFocusChanged (Component*) override;

    void updateTransformAndScreenBounds();

    void handleTouches (UIEvent*, bool isDown, bool isUp, bool isCancel);

    //==============================================================================
    void repaint (const Rectangle<int>& area) override;
    void performAnyPendingRepaintsNow() override;

    //==============================================================================
    UIWindow* window;
    JuceUIView* view;
    JuceUIViewController* controller;
    bool isSharedWindow, fullScreen, insideDrawRect, isAppex;
    static ModifierKeys currentModifiers;

    static int64 getMouseTime (UIEvent* e) noexcept
    {
        return (Time::currentTimeMillis() - Time::getMillisecondCounter())
                + (int64) ([e timestamp] * 1000.0);
    }

    static Rectangle<int> rotatedScreenPosToReal (const Rectangle<int>& r)
    {
        if (! SystemStats::isRunningInAppExtensionSandbox() && isUsingOldRotationMethod())
        {
            const Rectangle<int> screen (convertToRectInt ([UIScreen mainScreen].bounds));

            switch ([[UIApplication sharedApplication] statusBarOrientation])
            {
                case UIInterfaceOrientationPortrait:
                    return r;

                case UIInterfaceOrientationPortraitUpsideDown:
                    return Rectangle<int> (screen.getWidth() - r.getRight(), screen.getHeight() - r.getBottom(),
                                           r.getWidth(), r.getHeight());

                case UIInterfaceOrientationLandscapeLeft:
                    return Rectangle<int> (r.getY(), screen.getHeight() - r.getRight(),
                                           r.getHeight(), r.getWidth());

                case UIInterfaceOrientationLandscapeRight:
                    return Rectangle<int> (screen.getWidth() - r.getBottom(), r.getX(),
                                           r.getHeight(), r.getWidth());

                default: jassertfalse; // unknown orientation!
            }
        }

        return r;
    }

    static Rectangle<int> realScreenPosToRotated (const Rectangle<int>& r)
    {
        if (! SystemStats::isRunningInAppExtensionSandbox() && isUsingOldRotationMethod())
        {
            const Rectangle<int> screen (convertToRectInt ([UIScreen mainScreen].bounds));

            switch ([[UIApplication sharedApplication] statusBarOrientation])
            {
                case UIInterfaceOrientationPortrait:
                    return r;

                case UIInterfaceOrientationPortraitUpsideDown:
                    return Rectangle<int> (screen.getWidth() - r.getRight(), screen.getHeight() - r.getBottom(),
                                           r.getWidth(), r.getHeight());

                case UIInterfaceOrientationLandscapeLeft:
                    return Rectangle<int> (screen.getHeight() - r.getBottom(), r.getX(),
                                           r.getHeight(), r.getWidth());

                case UIInterfaceOrientationLandscapeRight:
                    return Rectangle<int> (r.getY(), screen.getWidth() - r.getRight(),
                                           r.getHeight(), r.getWidth());

                default: jassertfalse; // unknown orientation!
            }
        }

        return r;
    }

    static MultiTouchMapper<UITouch*> currentTouches;

private:
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer)

    class AsyncRepaintMessage  : public CallbackMessage
    {
    public:
        UIViewComponentPeer* const peer;
        const Rectangle<int> rect;

        AsyncRepaintMessage (UIViewComponentPeer* const p, const Rectangle<int>& r)
            : peer (p), rect (r)
        {
        }

        void messageCallback() override
        {
            if (ComponentPeer::isValidPeer (peer))
                peer->repaint (rect);
        }
    };
};

static void sendScreenBoundsUpdate (JuceUIViewController* c)
{
    JuceUIView* juceView = (JuceUIView*) [c view];

    if (juceView != nil && juceView->owner != nullptr)
        juceView->owner->updateTransformAndScreenBounds();
}

static bool isKioskModeView (JuceUIViewController* c)
{
    JuceUIView* juceView = (JuceUIView*) [c view];
    jassert (juceView != nil && juceView->owner != nullptr);

    return Desktop::getInstance().getKioskModeComponent() == &(juceView->owner->getComponent());
}

MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches;

} // namespace juce

//==============================================================================
//==============================================================================
@implementation JuceUIViewController

- (NSUInteger) supportedInterfaceOrientations
{
    return Orientations::getSupportedOrientations();
}

- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation
{
    return Desktop::getInstance().isOrientationEnabled (Orientations::convertToJuce (interfaceOrientation));
}

- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation
                                 duration: (NSTimeInterval) duration
{
    ignoreUnused (toInterfaceOrientation, duration);

    [UIView setAnimationsEnabled: NO]; // disable this because it goes the wrong way and looks like crap.
}

- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation
{
    ignoreUnused (fromInterfaceOrientation);
    sendScreenBoundsUpdate (self);
    [UIView setAnimationsEnabled: YES];
}

- (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>) coordinator
{
    [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
    sendScreenBoundsUpdate (self);

    // On some devices the screen-size isn't yet updated at this point, so also trigger another
    // async update to double-check..
    MessageManager::callAsync ([=]() { sendScreenBoundsUpdate (self); });
}

- (BOOL) prefersStatusBarHidden
{
    return isKioskModeView (self);
}

- (UIStatusBarStyle) preferredStatusBarStyle
{
    return UIStatusBarStyleDefault;
}

- (void) viewDidLoad
{
    sendScreenBoundsUpdate (self);
    [super viewDidLoad];
}

- (void) viewWillAppear: (BOOL) animated
{
    sendScreenBoundsUpdate (self);
    [super viewWillAppear:animated];
}

- (void) viewDidAppear: (BOOL) animated
{
    sendScreenBoundsUpdate (self);
    [super viewDidAppear:animated];
}

- (void) viewWillLayoutSubviews
{
    sendScreenBoundsUpdate (self);
}

- (void) viewDidLayoutSubviews
{
    sendScreenBoundsUpdate (self);
}

@end

@implementation JuceUIView

- (JuceUIView*) initWithOwner: (UIViewComponentPeer*) peer
                    withFrame: (CGRect) frame
{
    [super initWithFrame: frame];
    owner = peer;

    hiddenTextView = [[UITextView alloc] initWithFrame: CGRectZero];
    [self addSubview: hiddenTextView];
    hiddenTextView.delegate = self;

    hiddenTextView.autocapitalizationType = UITextAutocapitalizationTypeNone;
    hiddenTextView.autocorrectionType = UITextAutocorrectionTypeNo;

    return self;
}

- (void) dealloc
{
    [hiddenTextView removeFromSuperview];
    [hiddenTextView release];

    [super dealloc];
}

//==============================================================================
- (void) drawRect: (CGRect) r
{
    if (owner != nullptr)
        owner->drawRect (r);
}

//==============================================================================
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
    ignoreUnused (touches);

    if (owner != nullptr)
        owner->handleTouches (event, true, false, false);
}

- (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event
{
    ignoreUnused (touches);

    if (owner != nullptr)
        owner->handleTouches (event, false, false, false);
}

- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event
{
    ignoreUnused (touches);

    if (owner != nullptr)
        owner->handleTouches (event, false, true, false);
}

- (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event
{
    if (owner != nullptr)
        owner->handleTouches (event, false, true, true);

    [self touchesEnded: touches withEvent: event];
}

//==============================================================================
- (BOOL) becomeFirstResponder
{
    if (owner != nullptr)
        owner->viewFocusGain();

    return true;
}

- (BOOL) resignFirstResponder
{
    if (owner != nullptr)
        owner->viewFocusLoss();

    return [super resignFirstResponder];
}

- (BOOL) canBecomeFirstResponder
{
    return owner != nullptr && owner->canBecomeKeyWindow();
}

- (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text
{
    ignoreUnused (textView);
    return owner->textViewReplaceCharacters (Range<int> ((int) range.location, (int) (range.location + range.length)),
                                             nsStringToJuce (text));
}

@end

//==============================================================================
@implementation JuceUIWindow

- (void) setOwner: (UIViewComponentPeer*) peer
{
    owner = peer;
}

- (void) becomeKeyWindow
{
    [super becomeKeyWindow];

    if (owner != nullptr)
        owner->grabFocus();
}

@end

//==============================================================================
//==============================================================================
namespace juce
{

bool KeyPress::isKeyCurrentlyDown (int)
{
    return false;
}

ModifierKeys UIViewComponentPeer::currentModifiers;

ModifierKeys ModifierKeys::getCurrentModifiersRealtime() noexcept
{
    return UIViewComponentPeer::currentModifiers;
}

void ModifierKeys::updateCurrentModifiers() noexcept
{
    currentModifiers = UIViewComponentPeer::currentModifiers;
}

Point<float> juce_lastMousePos;

//==============================================================================
UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyleFlags, UIView* viewToAttachTo)
    : ComponentPeer (comp, windowStyleFlags),
      window (nil),
      view (nil),
      controller (nil),
      isSharedWindow (viewToAttachTo != nil),
      fullScreen (false),
      insideDrawRect (false),
      isAppex (SystemStats::isRunningInAppExtensionSandbox())
{
    CGRect r = convertToCGRect (component.getBounds());

    view = [[JuceUIView alloc] initWithOwner: this withFrame: r];

    view.multipleTouchEnabled = YES;
    view.hidden = true;
    view.opaque = component.isOpaque();
    view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0];
    view.transform = CGAffineTransformIdentity;

    if (isSharedWindow)
    {
        window = [viewToAttachTo window];
        [viewToAttachTo addSubview: view];
    }
    else
    {
        r = convertToCGRect (rotatedScreenPosToReal (component.getBounds()));
        r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height);

        window = [[JuceUIWindow alloc] initWithFrame: r];
        [((JuceUIWindow*) window) setOwner: this];

        controller = [[JuceUIViewController alloc] init];
        controller.view = view;
        window.rootViewController = controller;

        window.hidden = true;
        window.autoresizesSubviews = NO;
        window.transform = Orientations::getCGTransformFor (Desktop::getInstance().getCurrentOrientation());
        window.opaque = component.isOpaque();
        window.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0];

        if (component.isAlwaysOnTop())
            window.windowLevel = UIWindowLevelAlert;

        view.frame = CGRectMake (0, 0, r.size.width, r.size.height);

        [window addSubview: view];
    }

    setTitle (component.getName());
    setVisible (component.isVisible());

    Desktop::getInstance().addFocusChangeListener (this);
}

UIViewComponentPeer::~UIViewComponentPeer()
{
    currentTouches.deleteAllTouchesForPeer (this);
    Desktop::getInstance().removeFocusChangeListener (this);

    view->owner = nullptr;
    [view removeFromSuperview];
    [view release];
    [controller release];

    if (! isSharedWindow)
    {
        [((JuceUIWindow*) window) setOwner: nil];
        [window release];
    }
}

//==============================================================================
void UIViewComponentPeer::setVisible (bool shouldBeVisible)
{
    if (! isSharedWindow)
        window.hidden = ! shouldBeVisible;

    view.hidden = ! shouldBeVisible;
}

void UIViewComponentPeer::setTitle (const String&)
{
    // xxx is this possible?
}

void UIViewComponentPeer::setBounds (const Rectangle<int>& newBounds, const bool isNowFullScreen)
{
    fullScreen = isNowFullScreen;

    if (isSharedWindow)
    {
        CGRect r = convertToCGRect (newBounds);

        if (view.frame.size.width != r.size.width || view.frame.size.height != r.size.height)
            [view setNeedsDisplay];

        view.frame = r;
    }
    else
    {
        window.frame = convertToCGRect (rotatedScreenPosToReal (newBounds));
        view.frame = CGRectMake (0, 0, (CGFloat) newBounds.getWidth(), (CGFloat) newBounds.getHeight());

        handleMovedOrResized();
    }
}

Rectangle<int> UIViewComponentPeer::getBounds (const bool global) const
{
    CGRect r = view.frame;

    if (global && view.window != nil)
    {
        r = [view convertRect: r toView: view.window];
        r = [view.window convertRect: r toWindow: nil];

        return realScreenPosToRotated (convertToRectInt (r));
    }

    return convertToRectInt (r);
}

Point<float> UIViewComponentPeer::localToGlobal (Point<float> relativePosition)
{
    return relativePosition + getBounds (true).getPosition().toFloat();
}

Point<float> UIViewComponentPeer::globalToLocal (Point<float> screenPosition)
{
    return screenPosition - getBounds (true).getPosition().toFloat();
}

void UIViewComponentPeer::setAlpha (float newAlpha)
{
    [view.window setAlpha: (CGFloat) newAlpha];
}

void UIViewComponentPeer::setFullScreen (bool shouldBeFullScreen)
{
    if (! isSharedWindow)
    {
        Rectangle<int> r (shouldBeFullScreen ? Desktop::getInstance().getDisplays().getMainDisplay().userArea
                                             : lastNonFullscreenBounds);

        if ((! shouldBeFullScreen) && r.isEmpty())
            r = getBounds();

        // (can't call the component's setBounds method because that'll reset our fullscreen flag)
        if (! r.isEmpty())
            setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen);

        component.repaint();
    }
}

void UIViewComponentPeer::updateTransformAndScreenBounds()
{
    Desktop& desktop = Desktop::getInstance();
    const Rectangle<int> oldArea (component.getBounds());
    const Rectangle<int> oldDesktop (desktop.getDisplays().getMainDisplay().userArea);

    const_cast<Desktop::Displays&> (desktop.getDisplays()).refresh();

    window.transform = Orientations::getCGTransformFor (desktop.getCurrentOrientation());
    view.transform = CGAffineTransformIdentity;

    if (fullScreen)
    {
        fullScreen = false;
        setFullScreen (true);
    }
    else if (! isSharedWindow)
    {
        // this will re-centre the window, but leave its size unchanged
        const float centreRelX = oldArea.getCentreX() / (float) oldDesktop.getWidth();
        const float centreRelY = oldArea.getCentreY() / (float) oldDesktop.getHeight();

        const Rectangle<int> newDesktop (desktop.getDisplays().getMainDisplay().userArea);

        const int x = ((int) (newDesktop.getWidth()  * centreRelX)) - (oldArea.getWidth()  / 2);
        const int y = ((int) (newDesktop.getHeight() * centreRelY)) - (oldArea.getHeight() / 2);

        component.setBounds (oldArea.withPosition (x, y));
    }

    [view setNeedsDisplay];
}

bool UIViewComponentPeer::contains (Point<int> localPos, bool trueIfInAChildWindow) const
{
    {
        Rectangle<int> localBounds =
            ScalingHelpers::scaledScreenPosToUnscaled (component, component.getLocalBounds());

        if (! localBounds.contains (localPos))
            return false;
    }

    UIView* v = [view hitTest: convertToCGPoint (localPos)
                    withEvent: nil];

    if (trueIfInAChildWindow)
        return v != nil;

    return v == view;
}

bool UIViewComponentPeer::setAlwaysOnTop (bool alwaysOnTop)
{
    if (! isSharedWindow)
        window.windowLevel = alwaysOnTop ? UIWindowLevelAlert : UIWindowLevelNormal;

    return true;
}

void UIViewComponentPeer::toFront (bool makeActiveWindow)
{
    if (isSharedWindow)
        [[view superview] bringSubviewToFront: view];

    if (makeActiveWindow && window != nil && component.isVisible())
        [window makeKeyAndVisible];
}

void UIViewComponentPeer::toBehind (ComponentPeer* other)
{
    if (UIViewComponentPeer* const otherPeer = dynamic_cast<UIViewComponentPeer*> (other))
    {
        if (isSharedWindow)
        {
            [[view superview] insertSubview: view belowSubview: otherPeer->view];
        }
        else
        {
            // don't know how to do this
        }
    }
    else
    {
        jassertfalse; // wrong type of window?
    }
}

void UIViewComponentPeer::setIcon (const Image& /*newIcon*/)
{
    // to do..
}

//==============================================================================
static float getMaximumTouchForce (UITouch* touch) noexcept
{
   #if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0
    if ([touch respondsToSelector: @selector (maximumPossibleForce)])
        return (float) touch.maximumPossibleForce;
   #endif

    ignoreUnused (touch);
    return 0.0f;
}

static float getTouchForce (UITouch* touch) noexcept
{
   #if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0
    if ([touch respondsToSelector: @selector (force)])
        return (float) touch.force;
   #endif

    ignoreUnused (touch);
    return 0.0f;
}

void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, const bool isUp, bool isCancel)
{
    NSArray* touches = [[event touchesForView: view] allObjects];

    for (unsigned int i = 0; i < [touches count]; ++i)
    {
        UITouch* touch = [touches objectAtIndex: i];
        const float maximumForce = getMaximumTouchForce (touch);

        if ([touch phase] == UITouchPhaseStationary && maximumForce <= 0)
            continue;

        CGPoint p = [touch locationInView: view];
        const Point<float> pos (static_cast<float> (p.x), static_cast<float> (p.y));
        juce_lastMousePos = pos + getBounds (true).getPosition().toFloat();

        const int64 time = getMouseTime (event);
        const int touchIndex = currentTouches.getIndexOfTouch (this, touch);

        ModifierKeys modsToSend (currentModifiers);

        if (isDown)
        {
            if ([touch phase] != UITouchPhaseBegan)
                continue;

            currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
            modsToSend = currentModifiers;

            // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
            handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend.withoutMouseButtons(),
                              MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, time, {}, touchIndex);

            if (! isValidPeer (this)) // (in case this component was deleted by the event)
                return;
        }
        else if (isUp)
        {
            if (! ([touch phase] == UITouchPhaseEnded || [touch phase] == UITouchPhaseCancelled))
                continue;

            modsToSend = modsToSend.withoutMouseButtons();
            currentTouches.clearTouch (touchIndex);

            if (! currentTouches.areAnyTouchesActive())
                isCancel = true;
        }

        if (isCancel)
        {
            currentTouches.clearTouch (touchIndex);
            modsToSend = currentModifiers = currentModifiers.withoutMouseButtons();
        }

        // NB: some devices return 0 or 1.0 if pressure is unknown, so we'll clip our value to a believable range:
        float pressure = maximumForce > 0 ? jlimit (0.0001f, 0.9999f, getTouchForce (touch) / maximumForce)
                                          : MouseInputSource::invalidPressure;

        handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend, pressure,
                          MouseInputSource::invalidOrientation, time, { }, touchIndex);

        if (! isValidPeer (this)) // (in case this component was deleted by the event)
            return;

        if (isUp || isCancel)
        {
            handleMouseEvent (MouseInputSource::InputSourceType::touch, Point<float> (-1.0f, -1.0f), modsToSend,
                              MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, time, {}, touchIndex);

            if (! isValidPeer (this))
                return;
        }
    }
}

//==============================================================================
static UIViewComponentPeer* currentlyFocusedPeer = nullptr;

void UIViewComponentPeer::viewFocusGain()
{
    if (currentlyFocusedPeer != this)
    {
        if (ComponentPeer::isValidPeer (currentlyFocusedPeer))
            currentlyFocusedPeer->handleFocusLoss();

        currentlyFocusedPeer = this;

        handleFocusGain();
    }
}

void UIViewComponentPeer::viewFocusLoss()
{
    if (currentlyFocusedPeer == this)
    {
        currentlyFocusedPeer = nullptr;
        handleFocusLoss();
    }
}

bool UIViewComponentPeer::isFocused() const
{
    if (isAppex)
        return true;

    return isSharedWindow ? this == currentlyFocusedPeer
                          : (window != nil && [window isKeyWindow]);
}

void UIViewComponentPeer::grabFocus()
{
    if (window != nil)
    {
        [window makeKeyWindow];
        viewFocusGain();
    }
}

void UIViewComponentPeer::textInputRequired (Point<int>, TextInputTarget&)
{
}

static bool isIOS4_1() noexcept
{
    return [[[UIDevice currentDevice] systemVersion] doubleValue] >= 4.1;
}

static UIKeyboardType getUIKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept
{
    switch (type)
    {
        case TextInputTarget::textKeyboard:          return UIKeyboardTypeAlphabet;
        case TextInputTarget::numericKeyboard:       return isIOS4_1() ? UIKeyboardTypeNumberPad  : UIKeyboardTypeNumbersAndPunctuation;
        case TextInputTarget::decimalKeyboard:       return isIOS4_1() ? UIKeyboardTypeDecimalPad : UIKeyboardTypeNumbersAndPunctuation;
        case TextInputTarget::urlKeyboard:           return UIKeyboardTypeURL;
        case TextInputTarget::emailAddressKeyboard:  return UIKeyboardTypeEmailAddress;
        case TextInputTarget::phoneNumberKeyboard:   return UIKeyboardTypePhonePad;
        default:                                     jassertfalse; break;
    }

    return UIKeyboardTypeDefault;
}

void UIViewComponentPeer::updateHiddenTextContent (TextInputTarget* target)
{
    view->hiddenTextView.keyboardType = getUIKeyboardType (target->getKeyboardType());
    view->hiddenTextView.text = juceStringToNS (target->getTextInRange (Range<int> (0, target->getHighlightedRegion().getStart())));
    view->hiddenTextView.selectedRange = NSMakeRange ((NSUInteger) target->getHighlightedRegion().getStart(), 0);
}

BOOL UIViewComponentPeer::textViewReplaceCharacters (Range<int> range, const String& text)
{
    if (TextInputTarget* const target = findCurrentTextInputTarget())
    {
        const Range<int> currentSelection (target->getHighlightedRegion());

        if (range.getLength() == 1 && text.isEmpty()) // (detect backspace)
            if (currentSelection.isEmpty())
                target->setHighlightedRegion (currentSelection.withStart (currentSelection.getStart() - 1));

        if (text == "\r" || text == "\n" || text == "\r\n")
            handleKeyPress (KeyPress::returnKey, text[0]);
        else
            target->insertTextAtCaret (text);

        updateHiddenTextContent (target);
    }

    return NO;
}

void UIViewComponentPeer::globalFocusChanged (Component*)
{
    if (TextInputTarget* const target = findCurrentTextInputTarget())
    {
        Component* comp = dynamic_cast<Component*> (target);

        Point<int> pos (component.getLocalPoint (comp, Point<int>()));
        view->hiddenTextView.frame = CGRectMake (pos.x, pos.y, 0, 0);

        updateHiddenTextContent (target);
        [view->hiddenTextView becomeFirstResponder];
    }
    else
    {
        [view->hiddenTextView resignFirstResponder];
    }
}

//==============================================================================
void UIViewComponentPeer::drawRect (CGRect r)
{
    if (r.size.width < 1.0f || r.size.height < 1.0f)
        return;

    CGContextRef cg = UIGraphicsGetCurrentContext();

    if (! component.isOpaque())
        CGContextClearRect (cg, CGContextGetClipBoundingBox (cg));

    CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, 0, getComponent().getHeight()));

    // NB the CTM on iOS already includes a factor for the display scale, so
    // we'll tell the context that the scale is 1.0 to avoid it using it twice
    CoreGraphicsContext g (cg, getComponent().getHeight(), 1.0f);

    insideDrawRect = true;
    handlePaint (g);
    insideDrawRect = false;
}

bool UIViewComponentPeer::canBecomeKeyWindow()
{
    return (getStyleFlags() & juce::ComponentPeer::windowIgnoresKeyPresses) == 0;
}

//==============================================================================
void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, bool /*allowMenusAndBars*/)
{
    displays->refresh();

    if (ComponentPeer* peer = kioskModeComp->getPeer())
    {
        if (UIViewComponentPeer* uiViewPeer = dynamic_cast<UIViewComponentPeer*> (peer))
            [uiViewPeer->controller setNeedsStatusBarAppearanceUpdate];

        peer->setFullScreen (enableOrDisable);
    }
}

void Desktop::allowedOrientationsChanged()
{
    // if the current orientation isn't allowed anymore then switch orientations
    if (! isOrientationEnabled (getCurrentOrientation()))
    {
        DisplayOrientation orientations[] = { upright, upsideDown, rotatedClockwise, rotatedAntiClockwise };

        const int n = sizeof (orientations) / sizeof (DisplayOrientation);
        int i;

        for (i = 0; i < n; ++i)
            if (isOrientationEnabled (orientations[i]))
                break;

        // you need to support at least one orientation
        jassert (i < n);
        i = jmin (n - 1, i);

        NSNumber *value = [NSNumber numberWithInt:Orientations::convertFromJuce (orientations[i])];
        [[UIDevice currentDevice] setValue:value forKey:@"orientation"];
        [value release];
    }
}

//==============================================================================
void UIViewComponentPeer::repaint (const Rectangle<int>& area)
{
    if (insideDrawRect || ! MessageManager::getInstance()->isThisTheMessageThread())
        (new AsyncRepaintMessage (this, area))->post();
    else
        [view setNeedsDisplayInRect: convertToCGRect (area)];
}

void UIViewComponentPeer::performAnyPendingRepaintsNow()
{
}

ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo)
{
    return new UIViewComponentPeer (*this, styleFlags, (UIView*) windowToAttachTo);
}

//==============================================================================
const int KeyPress::spaceKey        = ' ';
const int KeyPress::returnKey       = 0x0d;
const int KeyPress::escapeKey       = 0x1b;
const int KeyPress::backspaceKey    = 0x7f;
const int KeyPress::leftKey         = 0x1000;
const int KeyPress::rightKey        = 0x1001;
const int KeyPress::upKey           = 0x1002;
const int KeyPress::downKey         = 0x1003;
const int KeyPress::pageUpKey       = 0x1004;
const int KeyPress::pageDownKey     = 0x1005;
const int KeyPress::endKey          = 0x1006;
const int KeyPress::homeKey         = 0x1007;
const int KeyPress::deleteKey       = 0x1008;
const int KeyPress::insertKey       = -1;
const int KeyPress::tabKey          = 9;
const int KeyPress::F1Key           = 0x2001;
const int KeyPress::F2Key           = 0x2002;
const int KeyPress::F3Key           = 0x2003;
const int KeyPress::F4Key           = 0x2004;
const int KeyPress::F5Key           = 0x2005;
const int KeyPress::F6Key           = 0x2006;
const int KeyPress::F7Key           = 0x2007;
const int KeyPress::F8Key           = 0x2008;
const int KeyPress::F9Key           = 0x2009;
const int KeyPress::F10Key          = 0x200a;
const int KeyPress::F11Key          = 0x200b;
const int KeyPress::F12Key          = 0x200c;
const int KeyPress::F13Key          = 0x200d;
const int KeyPress::F14Key          = 0x200e;
const int KeyPress::F15Key          = 0x200f;
const int KeyPress::F16Key          = 0x2010;
const int KeyPress::F17Key          = 0x2011;
const int KeyPress::F18Key          = 0x2012;
const int KeyPress::F19Key          = 0x2013;
const int KeyPress::F20Key          = 0x2014;
const int KeyPress::F21Key          = 0x2015;
const int KeyPress::F22Key          = 0x2016;
const int KeyPress::F23Key          = 0x2017;
const int KeyPress::F24Key          = 0x2018;
const int KeyPress::F25Key          = 0x2019;
const int KeyPress::F26Key          = 0x201a;
const int KeyPress::F27Key          = 0x201b;
const int KeyPress::F28Key          = 0x201c;
const int KeyPress::F29Key          = 0x201d;
const int KeyPress::F30Key          = 0x201e;
const int KeyPress::F31Key          = 0x201f;
const int KeyPress::F32Key          = 0x2020;
const int KeyPress::F33Key          = 0x2021;
const int KeyPress::F34Key          = 0x2022;
const int KeyPress::F35Key          = 0x2023;
const int KeyPress::numberPad0      = 0x30020;
const int KeyPress::numberPad1      = 0x30021;
const int KeyPress::numberPad2      = 0x30022;
const int KeyPress::numberPad3      = 0x30023;
const int KeyPress::numberPad4      = 0x30024;
const int KeyPress::numberPad5      = 0x30025;
const int KeyPress::numberPad6      = 0x30026;
const int KeyPress::numberPad7      = 0x30027;
const int KeyPress::numberPad8      = 0x30028;
const int KeyPress::numberPad9      = 0x30029;
const int KeyPress::numberPadAdd            = 0x3002a;
const int KeyPress::numberPadSubtract       = 0x3002b;
const int KeyPress::numberPadMultiply       = 0x3002c;
const int KeyPress::numberPadDivide         = 0x3002d;
const int KeyPress::numberPadSeparator      = 0x3002e;
const int KeyPress::numberPadDecimalPoint   = 0x3002f;
const int KeyPress::numberPadEquals         = 0x30030;
const int KeyPress::numberPadDelete         = 0x30031;
const int KeyPress::playKey         = 0x30000;
const int KeyPress::stopKey         = 0x30001;
const int KeyPress::fastForwardKey  = 0x30002;
const int KeyPress::rewindKey       = 0x30003;

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

extern ::Window juce_messageWindowHandle;

namespace ClipboardHelpers
{
    static String localClipboardContent;
    static Atom   atom_UTF8_STRING;
    static Atom   atom_CLIPBOARD;
    static Atom   atom_TARGETS;

    //==============================================================================
    static void initSelectionAtoms (::Display* display)
    {
        static bool isInitialised = false;

        if (! isInitialised)
        {
            isInitialised = true;

            atom_UTF8_STRING = Atoms::getCreating (display, "UTF8_STRING");
            atom_CLIPBOARD   = Atoms::getCreating (display, "CLIPBOARD");
            atom_TARGETS     = Atoms::getCreating (display, "TARGETS");
        }
    }

    //==============================================================================
    // Read the content of a window property as either a locale-dependent string or an utf8 string
    // works only for strings shorter than 1000000 bytes
    static String readWindowProperty (::Display* display, Window window, Atom prop)
    {
        String returnData;

        if (display != nullptr)
        {
            char* clipData;
            Atom actualType;
            int actualFormat;
            unsigned long numItems, bytesLeft;

            if (XGetWindowProperty (display, window, prop,
                                    0L /* offset */, 1000000 /* length (max) */, False,
                                    AnyPropertyType /* format */,
                                    &actualType, &actualFormat, &numItems, &bytesLeft,
                                    (unsigned char**) &clipData) == Success)
            {
                if (actualType == atom_UTF8_STRING && actualFormat == 8)
                    returnData = String::fromUTF8 (clipData, (int) numItems);
                else if (actualType == XA_STRING && actualFormat == 8)
                    returnData = String (clipData, numItems);

                if (clipData != nullptr)
                    XFree (clipData);

                jassert (bytesLeft == 0 || numItems == 1000000);
            }

            XDeleteProperty (display, window, prop);
        }

        return returnData;
    }

    //==============================================================================
    // Send a SelectionRequest to the window owning the selection and waits for its answer (with a timeout) */
    static bool requestSelectionContent (::Display* display, String& selectionContent,
                                         Atom selection, Atom requestedFormat)
    {
        Atom property_name = XInternAtom (display, "JUCE_SEL", false);

        // The selection owner will be asked to set the JUCE_SEL property on the
        // juce_messageWindowHandle with the selection content
        XConvertSelection (display, selection, requestedFormat, property_name,
                           juce_messageWindowHandle, CurrentTime);

        int count = 50; // will wait at most for 200 ms

        while (--count >= 0)
        {
            XEvent event;

            if (XCheckTypedWindowEvent (display, juce_messageWindowHandle, SelectionNotify, &event))
            {
                if (event.xselection.property == property_name)
                {
                    jassert (event.xselection.requestor == juce_messageWindowHandle);

                    selectionContent = readWindowProperty (display, event.xselection.requestor,
                                                           event.xselection.property);
                    return true;
                }

                return false;  // the format we asked for was denied.. (event.xselection.property == None)
            }

            // not very elegant.. we could do a select() or something like that...
            // however clipboard content requesting is inherently slow on x11, it
            // often takes 50ms or more so...
            Thread::sleep (4);
        }

        return false;
    }

    //==============================================================================
    // Called from the event loop in juce_linux_Messaging in response to SelectionRequest events
    static void handleSelection (XSelectionRequestEvent& evt)
    {
        ClipboardHelpers::initSelectionAtoms (evt.display);

        // the selection content is sent to the target window as a window property
        XSelectionEvent reply;
        reply.type = SelectionNotify;
        reply.display = evt.display;
        reply.requestor = evt.requestor;
        reply.selection = evt.selection;
        reply.target = evt.target;
        reply.property = None; // == "fail"
        reply.time = evt.time;

        HeapBlock<char> data;
        int propertyFormat = 0;
        size_t numDataItems = 0;

        if (evt.selection == XA_PRIMARY || evt.selection == ClipboardHelpers::atom_CLIPBOARD)
        {
            if (evt.target == XA_STRING || evt.target == ClipboardHelpers::atom_UTF8_STRING)
            {
                // translate to utf8
                numDataItems = ClipboardHelpers::localClipboardContent.getNumBytesAsUTF8() + 1;
                data.calloc (numDataItems + 1);
                ClipboardHelpers::localClipboardContent.copyToUTF8 (data, numDataItems);
                propertyFormat = 8; // bits/item
            }
            else if (evt.target == ClipboardHelpers::atom_TARGETS)
            {
                // another application wants to know what we are able to send
                numDataItems = 2;
                propertyFormat = 32; // atoms are 32-bit
                data.calloc (numDataItems * 4);
                Atom* atoms = reinterpret_cast<Atom*> (data.getData());
                atoms[0] = ClipboardHelpers::atom_UTF8_STRING;
                atoms[1] = XA_STRING;

                evt.target = XA_ATOM;
            }
        }
        else
        {
            DBG ("requested unsupported clipboard");
        }

        if (data != nullptr)
        {
            const size_t maxReasonableSelectionSize = 1000000;

            // for very big chunks of data, we should use the "INCR" protocol , which is a pain in the *ss
            if (evt.property != None && numDataItems < maxReasonableSelectionSize)
            {
                XChangeProperty (evt.display, evt.requestor,
                                 evt.property, evt.target,
                                 propertyFormat /* 8 or 32 */, PropModeReplace,
                                 reinterpret_cast<const unsigned char*> (data.getData()), (int) numDataItems);
                reply.property = evt.property; // " == success"
            }
        }

        XSendEvent (evt.display, evt.requestor, 0, NoEventMask, (XEvent*) &reply);
    }
}

//==============================================================================
typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&);
extern SelectionRequestCallback handleSelectionRequest;

struct ClipboardCallbackInitialiser
{
    ClipboardCallbackInitialiser()
    {
        handleSelectionRequest = ClipboardHelpers::handleSelection;
    }
};

static ClipboardCallbackInitialiser clipboardInitialiser;

//==============================================================================
void SystemClipboard::copyTextToClipboard (const String& clipText)
{
    ScopedXDisplay xDisplay;

    if (auto display = xDisplay.display)
    {
        ClipboardHelpers::initSelectionAtoms (display);
        ClipboardHelpers::localClipboardContent = clipText;

        XSetSelectionOwner (display, XA_PRIMARY, juce_messageWindowHandle, CurrentTime);
        XSetSelectionOwner (display, ClipboardHelpers::atom_CLIPBOARD, juce_messageWindowHandle, CurrentTime);
    }
}

String SystemClipboard::getTextFromClipboard()
{
    String content;
    ScopedXDisplay xDisplay;

    if (auto display = xDisplay.display)
    {
        ClipboardHelpers::initSelectionAtoms (display);

        /* 1) try to read from the "CLIPBOARD" selection first (the "high
           level" clipboard that is supposed to be filled by ctrl-C
           etc). When a clipboard manager is running, the content of this
           selection is preserved even when the original selection owner
           exits.

           2) and then try to read from "PRIMARY" selection (the "legacy" selection
           filled by good old x11 apps such as xterm)
        */
        Atom selection = XA_PRIMARY;
        Window selectionOwner = None;

        if ((selectionOwner = XGetSelectionOwner (display, selection)) == None)
        {
            selection = ClipboardHelpers::atom_CLIPBOARD;
            selectionOwner = XGetSelectionOwner (display, selection);
        }

        if (selectionOwner != None)
        {
            if (selectionOwner == juce_messageWindowHandle)
            {
                content = ClipboardHelpers::localClipboardContent;
            }
            else
            {
                // first try: we want an utf8 string
                bool ok = ClipboardHelpers::requestSelectionContent (display, content,
                                                                     selection, ClipboardHelpers::atom_UTF8_STRING);

                if (! ok)
                {
                    // second chance, ask for a good old locale-dependent string ..
                    ok = ClipboardHelpers::requestSelectionContent (display, content,
                                                                    selection, XA_STRING);
                }
            }
        }
    }

    return content;
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

typedef void (*WindowMessageReceiveCallback) (XEvent&);
WindowMessageReceiveCallback dispatchWindowMessage = nullptr;

typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&);
SelectionRequestCallback handleSelectionRequest = nullptr;

::Window juce_messageWindowHandle;
XContext windowHandleXContext;

//==============================================================================
namespace X11ErrorHandling
{
    static XErrorHandler   oldErrorHandler = {};
    static XIOErrorHandler oldIOErrorHandler = {};

    //==============================================================================
    // Usually happens when client-server connection is broken
    int ioErrorHandler (::Display*)
    {
        DBG ("ERROR: connection to X server broken.. terminating.");

        if (JUCEApplicationBase::isStandaloneApp())
            MessageManager::getInstance()->stopDispatchLoop();

        return 0;
    }

    int errorHandler (::Display* display, XErrorEvent* event)
    {
        ignoreUnused (display, event);

       #if JUCE_DEBUG_XERRORS
        char errorStr[64] = { 0 };
        char requestStr[64] = { 0 };

        XGetErrorText (display, event->error_code, errorStr, 64);
        XGetErrorDatabaseText (display, "XRequest", String (event->request_code).toUTF8(), "Unknown", requestStr, 64);
        DBG ("ERROR: X returned " << errorStr << " for operation " << requestStr);
       #endif

        return 0;
    }

    void installXErrorHandlers()
    {
        oldIOErrorHandler = XSetIOErrorHandler (ioErrorHandler);
        oldErrorHandler = XSetErrorHandler (errorHandler);
    }

    void removeXErrorHandlers()
    {
        XSetIOErrorHandler (oldIOErrorHandler);
        oldIOErrorHandler = {};

        XSetErrorHandler (oldErrorHandler);
        oldErrorHandler = {};
    }
}

//==============================================================================
XWindowSystem::XWindowSystem() noexcept
{
    if (JUCEApplicationBase::isStandaloneApp())
    {
        // Initialise xlib for multiple thread support
        static bool initThreadCalled = false;

        if (! initThreadCalled)
        {
            if (! XInitThreads())
            {
                // This is fatal!  Print error and closedown
                Logger::outputDebugString ("Failed to initialise xlib thread support.");
                Process::terminate();
                return;
            }

            initThreadCalled = true;
        }

        X11ErrorHandling::installXErrorHandlers();
    }
}

XWindowSystem::~XWindowSystem() noexcept
{
    if (JUCEApplicationBase::isStandaloneApp())
        X11ErrorHandling::removeXErrorHandlers();

    clearSingletonInstance();
}

::Display* XWindowSystem::displayRef() noexcept
{
    if (++displayCount == 1)
    {
        jassert (display == nullptr);

        String displayName (getenv ("DISPLAY"));

        if (displayName.isEmpty())
            displayName = ":0.0";

        // it seems that on some systems XOpenDisplay will occasionally
        // fail the first time, but succeed on a second attempt..
        for (int retries = 2; --retries >= 0;)
        {
            display = XOpenDisplay (displayName.toUTF8());

            if (display != nullptr)
                break;
        }

        initialiseXDisplay();
    }

    return display;
}

::Display* XWindowSystem::displayUnref() noexcept
{
    jassert (display != nullptr);
    jassert (displayCount.get() > 0);

    if (--displayCount == 0)
    {
        destroyXDisplay();
        XCloseDisplay (display);
        display = nullptr;
    }

    return display;
}

void XWindowSystem::initialiseXDisplay() noexcept
{
    // This is fatal!  Print error and closedown
    if (display == nullptr)
    {
        Logger::outputDebugString ("Failed to connect to the X Server.");
        Process::terminate();
    }

    // Create a context to store user data associated with Windows we create
    windowHandleXContext = XUniqueContext();

    // We're only interested in client messages for this window, which are always sent
    XSetWindowAttributes swa;
    swa.event_mask = NoEventMask;

    // Create our message window (this will never be mapped)
    const int screen = DefaultScreen (display);
    juce_messageWindowHandle = XCreateWindow (display, RootWindow (display, screen),
                                              0, 0, 1, 1, 0, 0, InputOnly,
                                              DefaultVisual (display, screen),
                                              CWEventMask, &swa);

    XSync (display, False);

    // Setup input event handler
    int fd = XConnectionNumber (display);

    LinuxEventLoop::setWindowSystemFd (fd,
         [this](int /*fd*/)
         {
            do
            {
                XEvent evt;

                {
                    ScopedXLock xlock (display);

                    if (! XPending (display))
                        return false;

                    XNextEvent (display, &evt);
                }

                if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle
                     && handleSelectionRequest != nullptr)
                {
                    handleSelectionRequest (evt.xselectionrequest);
                }
                else if (evt.xany.window != juce_messageWindowHandle
                          && dispatchWindowMessage != nullptr)
                {
                    dispatchWindowMessage (evt);
                }

            } while (display != nullptr);

            return false;
        });
}

void XWindowSystem::destroyXDisplay() noexcept
{
    ScopedXLock xlock (display);
    XDestroyWindow (display, juce_messageWindowHandle);
    juce_messageWindowHandle = 0;
    XSync (display, True);
    LinuxEventLoop::removeWindowSystemFd();
}

juce_ImplementSingleton (XWindowSystem)

//==============================================================================
ScopedXDisplay::ScopedXDisplay() : display (XWindowSystem::getInstance()->displayRef())
{
}

ScopedXDisplay::~ScopedXDisplay()
{
    XWindowSystem::getInstance()->displayUnref();
}

//==============================================================================
ScopedXLock::ScopedXLock (::Display* d) : display (d)
{
    if (display != nullptr)
        XLockDisplay (display);
}

ScopedXLock::~ScopedXLock()
{
    if (display != nullptr)
        XUnlockDisplay (display);
}

//==============================================================================
Atoms::Atoms (::Display* display)
{
    protocols                    = getIfExists (display, "WM_PROTOCOLS");
    protocolList [TAKE_FOCUS]    = getIfExists (display, "WM_TAKE_FOCUS");
    protocolList [DELETE_WINDOW] = getIfExists (display, "WM_DELETE_WINDOW");
    protocolList [PING]          = getIfExists (display, "_NET_WM_PING");
    changeState                  = getIfExists (display, "WM_CHANGE_STATE");
    state                        = getIfExists (display, "WM_STATE");
    userTime                     = getCreating (display, "_NET_WM_USER_TIME");
    activeWin                    = getCreating (display, "_NET_ACTIVE_WINDOW");
    pid                          = getCreating (display, "_NET_WM_PID");
    windowType                   = getIfExists (display, "_NET_WM_WINDOW_TYPE");
    windowState                  = getIfExists (display, "_NET_WM_STATE");

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

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

    XembedMsgType                = getCreating (display, "_XEMBED");
    XembedInfo                   = getCreating (display, "_XEMBED_INFO");

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

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

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

String Atoms::getName (::Display* display, const Atom atom)
{
    if (atom == None)
        return "None";

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

bool Atoms::isMimeTypeFile (::Display* display, const Atom atom)
{
    return getName (display, atom).equalsIgnoreCase ("text/uri-list");
}

const unsigned long Atoms::DndVersion = 3;

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

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

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

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

namespace juce
{

namespace FileChooserHelpers
{
    struct FileChooserCallbackInfo
    {
        String initialPath;
        String returnedString; // need this to get non-existent pathnames from the directory chooser
        ScopedPointer<Component> customComponent;
    };

    static int CALLBACK browseCallbackProc (HWND hWnd, UINT msg, LPARAM lParam, LPARAM lpData)
    {
        FileChooserCallbackInfo* info = (FileChooserCallbackInfo*) lpData;

        if (msg == BFFM_INITIALIZED)
            SendMessage (hWnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) info->initialPath.toWideCharPointer());
        else if (msg == BFFM_VALIDATEFAILEDW)
            info->returnedString = (LPCWSTR) lParam;
        else if (msg == BFFM_VALIDATEFAILEDA)
            info->returnedString = (const char*) lParam;

        return 0;
    }

    static UINT_PTR CALLBACK openCallback (HWND hdlg, UINT uiMsg, WPARAM /*wParam*/, LPARAM lParam)
    {
        if (uiMsg == WM_INITDIALOG)
        {
            Component* customComp = ((FileChooserCallbackInfo*) (((OPENFILENAMEW*) lParam)->lCustData))->customComponent;

            HWND dialogH = GetParent (hdlg);
            jassert (dialogH != 0);
            if (dialogH == 0)
                dialogH = hdlg;

            RECT r, cr;
            GetWindowRect (dialogH, &r);
            GetClientRect (dialogH, &cr);

            SetWindowPos (dialogH, 0,
                          r.left, r.top,
                          customComp->getWidth() + jmax (150, (int) (r.right - r.left)),
                          jmax (150, (int) (r.bottom - r.top)),
                          SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);

            customComp->setBounds (cr.right, cr.top, customComp->getWidth(), cr.bottom - cr.top);
            customComp->addToDesktop (0, dialogH);
        }
        else if (uiMsg == WM_NOTIFY)
        {
            LPOFNOTIFY ofn = (LPOFNOTIFY) lParam;

            if (ofn->hdr.code == CDN_SELCHANGE)
            {
                FileChooserCallbackInfo* info = (FileChooserCallbackInfo*) ofn->lpOFN->lCustData;

                if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (info->customComponent->getChildComponent(0)))
                {
                    WCHAR path [MAX_PATH * 2] = { 0 };
                    CommDlg_OpenSave_GetFilePath (GetParent (hdlg), (LPARAM) &path, MAX_PATH);

                    comp->selectedFileChanged (File (path));
                }
            }
        }

        return 0;
    }

    class CustomComponentHolder  : public Component
    {
    public:
        CustomComponentHolder (Component* const customComp)
        {
            setVisible (true);
            setOpaque (true);
            addAndMakeVisible (customComp);
            setSize (jlimit (20, 800, customComp->getWidth()), customComp->getHeight());
        }

        void paint (Graphics& g) override
        {
            g.fillAll (Colours::lightgrey);
        }

        void resized() override
        {
            if (Component* const c = getChildComponent(0))
                c->setBounds (getLocalBounds());
        }

    private:
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponentHolder)
    };
}

//==============================================================================
bool FileChooser::isPlatformDialogAvailable()
{
   #if JUCE_DISABLE_NATIVE_FILECHOOSERS
    return false;
   #else
    return true;
   #endif
}

void FileChooser::showPlatformDialog (Array<File>& results, const String& title_, const File& currentFileOrDirectory,
                                      const String& filter, bool selectsDirectory, bool /*selectsFiles*/,
                                      bool isSaveDialogue, bool warnAboutOverwritingExistingFiles,
                                      bool selectMultipleFiles, bool /*treatFilePackagesAsDirs*/,
                                      FilePreviewComponent* extraInfoComponent)
{
    using namespace FileChooserHelpers;

    const String title (title_);
    String defaultExtension; // scope of these strings must extend beyond dialog's lifetime.

    HeapBlock<WCHAR> files;
    const size_t charsAvailableForResult = 32768;
    files.calloc (charsAvailableForResult + 1);
    int filenameOffset = 0;

    FileChooserCallbackInfo info;

    // use a modal window as the parent for this dialog box
    // to block input from other app windows
    Component parentWindow;
    const Rectangle<int> mainMon (Desktop::getInstance().getDisplays().getMainDisplay().userArea);
    parentWindow.setBounds (mainMon.getX() + mainMon.getWidth() / 4,
                            mainMon.getY() + mainMon.getHeight() / 4,
                            0, 0);
    parentWindow.setOpaque (true);
    parentWindow.setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
    parentWindow.addToDesktop (0);

    if (extraInfoComponent == nullptr)
        parentWindow.enterModalState();

    auto parentDirectory = currentFileOrDirectory.getParentDirectory();

    // Handle nonexistent root directories in the same way as existing ones
    if (currentFileOrDirectory.isDirectory() || currentFileOrDirectory.isRoot())
    {
        info.initialPath = currentFileOrDirectory.getFullPathName();
    }
    else
    {
        currentFileOrDirectory.getFileName().copyToUTF16 (files, charsAvailableForResult * sizeof (WCHAR));
        info.initialPath = parentDirectory.getFullPathName();
    }

    if (selectsDirectory)
    {
        BROWSEINFO bi = { 0 };
        bi.hwndOwner = (HWND) parentWindow.getWindowHandle();
        bi.pszDisplayName = files;
        bi.lpszTitle = title.toWideCharPointer();
        bi.lParam = (LPARAM) &info;
        bi.lpfn = browseCallbackProc;
       #ifdef BIF_USENEWUI
        bi.ulFlags = BIF_USENEWUI | BIF_VALIDATE;
       #else
        bi.ulFlags = 0x50;
       #endif

        LPITEMIDLIST list = SHBrowseForFolder (&bi);

        if (! SHGetPathFromIDListW (list, files))
        {
            files[0] = 0;
            info.returnedString.clear();
        }

        LPMALLOC al;
        if (list != nullptr && SUCCEEDED (SHGetMalloc (&al)))
            al->Free (list);

        if (info.returnedString.isNotEmpty())
        {
            results.add (File (String (files.get())).getSiblingFile (info.returnedString));
            return;
        }
    }
    else
    {
        DWORD flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING;

        if (warnAboutOverwritingExistingFiles)
            flags |= OFN_OVERWRITEPROMPT;

        if (selectMultipleFiles)
            flags |= OFN_ALLOWMULTISELECT;

        if (extraInfoComponent != nullptr)
        {
            flags |= OFN_ENABLEHOOK;

            info.customComponent = new CustomComponentHolder (extraInfoComponent);
            info.customComponent->enterModalState (false);
        }

        const size_t filterSpaceNumChars = 2048;
        HeapBlock<WCHAR> filters;
        filters.calloc (filterSpaceNumChars);
        const size_t bytesWritten = filter.copyToUTF16 (filters.getData(), filterSpaceNumChars * sizeof (WCHAR));
        filter.copyToUTF16 (filters + (bytesWritten / sizeof (WCHAR)),
                            ((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten));

        for (size_t i = 0; i < filterSpaceNumChars; ++i)
            if (filters[i] == '|')
                filters[i] = 0;

        OPENFILENAMEW of = { 0 };
        String localPath (info.initialPath);

       #ifdef OPENFILENAME_SIZE_VERSION_400W
        of.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
       #else
        of.lStructSize = sizeof (of);
       #endif
        of.hwndOwner = (HWND) parentWindow.getWindowHandle();
        of.lpstrFilter = filters.getData();
        of.nFilterIndex = 1;
        of.lpstrFile = files;
        of.nMaxFile = (DWORD) charsAvailableForResult;
        of.lpstrInitialDir = localPath.toWideCharPointer();
        of.lpstrTitle = title.toWideCharPointer();
        of.Flags = flags;
        of.lCustData = (LPARAM) &info;

        if (extraInfoComponent != nullptr)
            of.lpfnHook = &openCallback;

        if (isSaveDialogue)
        {
            StringArray tokens;
            tokens.addTokens (filter, ";,", "\"'");
            tokens.trim();
            tokens.removeEmptyStrings();

            if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
            {
                defaultExtension = tokens[0].fromFirstOccurrenceOf (".", false, false);
                of.lpstrDefExt = defaultExtension.toWideCharPointer();
            }

            if (! GetSaveFileName (&of))
                return;
        }
        else
        {
            if (! GetOpenFileName (&of))
                return;
        }

        filenameOffset = of.nFileOffset;
    }

    if (selectMultipleFiles && filenameOffset > 0 && files [filenameOffset - 1] == 0)
    {
        const WCHAR* filename = files + filenameOffset;

        while (*filename != 0)
        {
            results.add (File (String (files.get())).getChildFile (String (filename)));
            filename += wcslen (filename) + 1;
        }
    }
    else if (files[0] != 0)
    {
        results.add (File (String (files.get())));
    }
}

} // namespace juce