Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
719 lines (550 sloc) 19 KB
////////////////////////////////////////////////////////////////////////////////
// -------------------------------------------------------------------------- //
// //
// (C) 2010-2016 Robot Developers //
// See LICENSE for licensing info //
// //
// -------------------------------------------------------------------------- //
////////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------//
// Prefaces //
//----------------------------------------------------------------------------//
#include "Screen.h"
#ifdef ROBOT_OS_LINUX
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/extensions/Xinerama.h>
// Reference default display
extern Display* _Robot_Display;
#define gDisplay _Robot_Display
#endif
#ifdef ROBOT_OS_MAC
#include <AppKit/AppKit.h>
#endif
#ifdef ROBOT_OS_WIN
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#endif
ROBOT_NS_BEGIN
//----------------------------------------------------------------------------//
// Locals //
//----------------------------------------------------------------------------//
#ifdef ROBOT_OS_LINUX
//----------------------------------------------------------------------------//
// Error Handling //
//----------------------------------------------------------------------------//
////////////////////////////////////////////////////////////////////////////////
typedef int (*XErrorHandler) (Display*, XErrorEvent*);
////////////////////////////////////////////////////////////////////////////////
static int XHandleError (Display* dp, XErrorEvent* e) { return 0; }
////////////////////////////////////////////////////////////////////////////////
class XDismissErrors
{
public:
////////////////////////////////////////////////////////////////////////////////
XDismissErrors (void)
{
// Save old handler and dismiss errors
mOld = XSetErrorHandler (XHandleError);
}
////////////////////////////////////////////////////////////////////////////////
~XDismissErrors (void)
{
// Flush output buffer
XSync (gDisplay, False);
// Reinstate old handler
XSetErrorHandler (mOld);
}
private:
XErrorHandler mOld;
};
//----------------------------------------------------------------------------//
// Functions //
//----------------------------------------------------------------------------//
////////////////////////////////////////////////////////////////////////////////
static bool IsXineramaAvailable (void)
{
// Initialize only one time
static int8 available = -1;
// If not checked yet
if (available == -1)
{
available = 0;
// Check for a valid X-Window display
if (gDisplay == nullptr) return false;
// Check for Xinerama extension
int32 major, minor, evt, error;
if (!XQueryExtension (gDisplay,
"XINERAMA", &major, &evt, &error))
return false;
// Check the Xinerama version value
if (!XineramaQueryVersion (gDisplay,
&major, &minor)) return false;
available = 1;
}
return available == 1;
}
#endif
#ifdef ROBOT_OS_WIN
////////////////////////////////////////////////////////////////////////////////
#ifndef DWM_EC_DISABLECOMPOSITION
#define DWM_EC_DISABLECOMPOSITION 0
#endif
#ifndef DWM_EC_ENABLECOMPOSITION
#define DWM_EC_ENABLECOMPOSITION 1
#endif
typedef HRESULT (WINAPI *DWMENABLECOMPOSITION ) (UINT enabled);
typedef HRESULT (WINAPI *DWMISCOMPOSITIONENABLED) (BOOL* enabled);
static DWMENABLECOMPOSITION gDwmEnableComposition = nullptr;
static DWMISCOMPOSITIONENABLED gDwmIsCompositionEnabled = nullptr;
////////////////////////////////////////////////////////////////////////////////
static void InitializeDWM (void)
{
// Initialize DWM functions once
static bool initialized = false;
if (!initialized)
{
initialized = true;
// Attempt to load windows DWMAPI DLL library
HMODULE d = LoadLibrary (TEXT ("Dwmapi.dll"));
if (d != nullptr)
{
// Attempt to load the DWM Aero functionality
gDwmEnableComposition = (DWMENABLECOMPOSITION)
GetProcAddress (d, "DwmEnableComposition");
gDwmIsCompositionEnabled = (DWMISCOMPOSITIONENABLED)
GetProcAddress (d, "DwmIsCompositionEnabled");
}
}
}
////////////////////////////////////////////////////////////////////////////////
static BOOL CALLBACK EnumMonitorsProc (HMONITOR hmon, HDC, LPRECT, LPARAM lParam)
{
// Retrieve lParam data as a screen deque
ScreenList* screens = (ScreenList*) lParam;
MONITORINFO info = { 0 };
info.cbSize =
sizeof (MONITORINFO);
// Attempt to retrieve screen info
if (!GetMonitorInfo (hmon, &info))
return TRUE;
Bounds bounds;
// Process the total bounds of the current screen
bounds.SetLTRB (info.rcMonitor.left, info.rcMonitor.top,
info.rcMonitor.right, info.rcMonitor.bottom);
Bounds usable;
// Process the usable bounds of the current screen
usable.SetLTRB (info.rcWork.left, info.rcWork.top,
info.rcWork.right, info.rcWork.bottom);
// Check if the current screen is primary
if (info.dwFlags & MONITORINFOF_PRIMARY)
screens->push_front (new Screen (bounds, usable));
else
screens->push_back (new Screen (bounds, usable));
return TRUE;
}
#endif
//----------------------------------------------------------------------------//
// Fields Screen //
//----------------------------------------------------------------------------//
////////////////////////////////////////////////////////////////////////////////
Bounds Screen::mTotalBounds;
Bounds Screen::mTotalUsable;
ScreenList Screen::mScreens;
//----------------------------------------------------------------------------//
// Constructors Screen //
//----------------------------------------------------------------------------//
////////////////////////////////////////////////////////////////////////////////
Screen::Screen (void)
{
}
////////////////////////////////////////////////////////////////////////////////
Screen::Screen (const Bounds& bounds,
const Bounds& usable)
{
mBounds = bounds;
mUsable = usable;
}
//----------------------------------------------------------------------------//
// Functions Screen //
//----------------------------------------------------------------------------//
////////////////////////////////////////////////////////////////////////////////
Bounds Screen::GetBounds (void) const { return mBounds; }
Bounds Screen::GetUsable (void) const { return mUsable; }
bool Screen::IsPortrait (void) const { return mBounds.W < mBounds.H; }
bool Screen::IsLandscape (void) const { return mBounds.W >= mBounds.H; }
////////////////////////////////////////////////////////////////////////////////
bool Screen::Synchronize (void)
{
// Loop through every available screen value
for (uintptr i = 0; i < mScreens.size(); ++i)
delete mScreens[i];
// Clear screens
mScreens.clear();
mTotalBounds = Bounds();
mTotalUsable = Bounds();
#ifdef ROBOT_OS_LINUX
// Check for a valid X-Window display
if (gDisplay == nullptr) return false;
// Ignore X errors
XDismissErrors xe;
// Get the net_workarea atom for later
static Atom NET_WORKAREA = XInternAtom
(gDisplay, "_NET_WORKAREA", True);
// Get the screen count traditionally
int32 count = XScreenCount (gDisplay);
// Used Xinerama to get screens
bool isVirtualDesktop = true;
// Ignore the Xinerama extension when
// the display is using multi-screens
// with multiple root windows
if (count == 1 && IsXineramaAvailable() &&
XineramaIsActive (gDisplay) == True)
{
int32 xineCount = 0;
// Query for screen info
XineramaScreenInfo* info =
XineramaQueryScreens (gDisplay, &xineCount);
if (info != nullptr)
{
// Iterate through every screen and append it
for (int32 i = 0, j = 0; i < xineCount; ++i)
{
Bounds bounds (info[i].x_org, info[i].y_org,
info[i].width, info[i].height);
// Merge any cloned or mirrored screens with the previous one
if (j > 0 && mScreens[j-1]->mBounds.Intersects (bounds, false))
{
if (mScreens[j ]->mBounds.W * mScreens[j ]->mBounds.H >
mScreens[j-1]->mBounds.W * mScreens[j-1]->mBounds.H)
{
mScreens[j-1]->mBounds = bounds;
mScreens[j-1]->mUsable = bounds;
}
}
else
{
++j;
// Append the current screen to the screens list
mScreens.push_back (new Screen (bounds, bounds));
}
}
XFree (info);
}
}
// Traditional method
if (mScreens.empty())
{
isVirtualDesktop = false;
// Retrieve default screen of the system
int32 primary = XDefaultScreen (gDisplay);
// Loop and append every screen
for (int32 i = 0; i < count; ++i)
{
auto screen = XScreenOfDisplay (gDisplay, i);
Bounds bounds (0, 0, XWidthOfScreen (screen),
XHeightOfScreen (screen));
if (i == primary)
mScreens.push_front (new Screen (bounds, bounds));
else
mScreens.push_back (new Screen (bounds, bounds));
}
}
// Validate workarea atom
if (NET_WORKAREA != None)
{
// Loop through every available screen value
for (uintptr i = 0; i < mScreens.size(); ++i)
{
::Window win = XRootWindow
(gDisplay, isVirtualDesktop ?
XDefaultScreen (gDisplay) : i);
// Property variables
Atom type; int format;
unsigned long nItems;
unsigned long bAfter;
unsigned char* result = nullptr;
// Retrieve and validate the net_workarea property
if (!XGetWindowProperty (gDisplay, win, NET_WORKAREA,
0, 4, False, AnyPropertyType, &type, &format,
&nItems, &bAfter, &result) && result != nullptr &&
type == XA_CARDINAL && format == 32 && nItems == 4)
{
long* usable = (long*) result;
mScreens[i]->mUsable = Bounds
((int32) usable[0], (int32) usable[1],
(int32) usable[2], (int32) usable[3]);
if (isVirtualDesktop)
// Confine the work area to the current screen
mScreens[i]->mUsable &= mScreens[i]->mBounds;
}
// Free the result if it got allocated
if (result != nullptr) XFree (result);
}
}
#endif
#ifdef ROBOT_OS_MAC
// Retrieves the list of available screens
NSMutableArray* screens = [NSMutableArray
arrayWithArray: [NSScreen screens]];
if ([screens count] == 0)
{
// Add main screen if screens are empty
NSScreen* screen = [NSScreen mainScreen];
if (screen) [screens addObject: screen];
}
// Loop through every screen on the system
for (int32 i = 0; i < [screens count]; ++i)
{
// Retrieve the pointer to the current screen
NSScreen* screen = [screens objectAtIndex: i];
if (screen == nullptr) continue;
// Retrieve the unique ID for the current screen
CGDirectDisplayID id = [[[screen deviceDescription]
objectForKey: @"NSScreenNumber"] unsignedIntValue];
// Ignore mirrored screens (based on Qt implementation)
if ([screens count] > 1 && CGDisplayIsInMirrorSet (id))
{
CGDirectDisplayID primary = CGDisplayMirrorsDisplay (id);
if (primary != kCGNullDirectDisplay && primary != id)
continue;
}
// Retrieve screen dimensions
Bounds bounds; Bounds usable;
NSRect frame = [screen frame];
if (mScreens.empty())
{
NSRect visible = [screen visibleFrame];
bounds = Bounds (frame.origin.x, frame.origin.y,
frame.size.width, frame.size.height);
// The primary screen is the only one which
// can have a different usable bounds region
usable = Bounds (visible.origin.x, bounds.H -
(visible.origin.y + visible.size.height),
visible.size.width, visible.size.height);
}
else
{
bounds = usable = Bounds (frame.origin.x,
mScreens[0]->mBounds.H -
(frame.origin.y + frame.size.height),
frame.size.width, frame.size.height);
}
// Append the current screen to the screens list
mScreens.push_back (new Screen (bounds, usable));
}
#endif
#ifdef ROBOT_OS_WIN
// Populate the screen list with system screens
if (EnumDisplayMonitors (0, 0, EnumMonitorsProc,
(LPARAM) &mScreens) == FALSE) return false;
#endif
// Check if one valid screen exists
if (mScreens.empty()) return false;
// Loop through every available screen value
for (uintptr i = 0; i < mScreens.size(); ++i)
{
// Add to the current running total
mTotalBounds |= mScreens[i]->mBounds;
mTotalUsable |= mScreens[i]->mUsable;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
Screen* Screen::GetMain (void)
{
// The primary screen is always the first element
return mScreens.empty() ? nullptr : mScreens[0];
}
////////////////////////////////////////////////////////////////////////////////
ScreenList Screen::GetList (void) { return mScreens; }
////////////////////////////////////////////////////////////////////////////////
Screen* Screen::GetScreen (const Window& window)
{
// Check whether the window is valid
if (!window.IsValid()) return nullptr;
// Get screen holding largest part of the window
return GetScreen (window.GetBounds().GetCenter());
}
////////////////////////////////////////////////////////////////////////////////
Screen* Screen::GetScreen (const Point& point)
{
return GetScreen (point.X, point.Y);
}
////////////////////////////////////////////////////////////////////////////////
Screen* Screen::GetScreen (int32 px, int32 py)
{
// Loop through every available screen value
for (uintptr i = 0; i < mScreens.size(); ++i)
{
if (mScreens[i]->mBounds.Contains (px, py))
return mScreens[i];
}
// The primary screen is always the first element
return mScreens.empty() ? nullptr : mScreens[0];
}
////////////////////////////////////////////////////////////////////////////////
bool Screen::GrabScreen (Image& image, const Bounds&
bounds, const Window& window)
{
return GrabScreen (image,
bounds.X, bounds.Y,
bounds.W, bounds.H, window);
}
////////////////////////////////////////////////////////////////////////////////
bool Screen::GrabScreen (Image& image,
int32 x, int32 y, int32 w, int32 h,
const Window& window)
{
if (w <= 0 || h <= 0)
{
// Get the size of windows' client
Bounds bounds = window.GetClient();
if (bounds.W <= 0 || bounds.H <= 0)
{
bounds.W = mTotalBounds.W;
bounds.H = mTotalBounds.H;
}
// Compute bounds values
if (w <= 0) w = bounds.W;
if (h <= 0) h = bounds.H;
}
if (w <= 0 || h <= 0) return false;
#ifdef ROBOT_OS_LINUX
// Check for a valid X-Window display
if (gDisplay == nullptr) return false;
// Ignore X errors
XDismissErrors xe;
// Get an X11 handle to the window
::Window win = !window.IsValid() ?
XDefaultRootWindow (gDisplay) :
window.GetHandle();
// Grab the current contents of window
XImage* img = XGetImage (gDisplay, win,
x, y, w, h, AllPlanes, ZPixmap);
if (img == nullptr) return false;
Color* data; bool result = false;
// Resize the resulting image value
w = img->width; h = img->height;
if (w <= 0 || h <= 0) goto exit;
image.Create (w, h);
data = (Color*) image.GetData();
// Convert the resulting XImage
for (int32 yy = 0; yy < h; ++yy)
for (int32 xx = 0; xx < w; ++xx, ++data)
{
auto pixel = XGetPixel (img, xx, yy);
data->R = (pixel & img->red_mask ) >> 16;
data->G = (pixel & img->green_mask) >> 8;
data->B = (pixel & img->blue_mask ) >> 0;
data->A = 255;
}
result = true;
exit:
// Dispose of image
XDestroyImage (img);
return result;
#endif
#ifdef ROBOT_OS_MAC
CGImageRef img = nullptr;
// Verify the window
if (window.IsValid())
{
// Convert window point to screen coordinates
Point p = window.MapToScreen (Point (x, y));
img = CGWindowListCreateImage (CGRectMake (p.X, p.Y, w, h),
kCGWindowListOptionIncludingWindow, (CGWindowID)
window.GetHandle(), kCGWindowImageBoundsIgnoreFraming);
}
else
{
img = CGWindowListCreateImage (CGRectMake (x, y, w, h),
kCGWindowListOptionOnScreenOnly, kCGNullWindowID,
kCGWindowImageBoundsIgnoreFraming);
}
// Verify that results are valid
if (img == nullptr) return false;
bool result = false;
CGContextRef context;
CGColorSpaceRef colorSpace;
// Resize the resulting image value
w = (int32) CGImageGetWidth (img);
h = (int32) CGImageGetHeight (img);
if (w <= 0 || h <= 0) goto exit;
image.Create (w, h);
// Bind result buffer to a graphics context
colorSpace = CGColorSpaceCreateDeviceRGB();
context = CGBitmapContextCreate
(image.GetData(), w, h, 8, w * sizeof (uint32), colorSpace,
kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease (colorSpace);
// Draw clipboard image into the buffer and release context
CGContextDrawImage (context, CGRectMake (0, 0, w, h), img);
CGContextFlush (context); CGContextRelease (context);
result = true;
exit:
// Dispose of image
CGImageRelease (img);
return result;
#endif
#ifdef ROBOT_OS_WIN
// Retrieve the current window handle
HWND hwnd = (HWND) window.GetHandle();
// Retrieve the source DC
HDC hSrcDC = GetDC (hwnd);
// Create compatible destination objects
HDC hDstDC = CreateCompatibleDC (hSrcDC);
HBITMAP hImage = CreateCompatibleBitmap (hSrcDC, w, h);
HGDIOBJ hOldDC = SelectObject (hDstDC, hImage);
// Copy the specified screen area in the source DC
BitBlt (hDstDC, 0, 0, w, h, hSrcDC, x, y, SRCCOPY);
// Convert the windows bitmap value
image.Create (w, h); BITMAPINFO bmi;
memset (&bmi, 0, sizeof (BITMAPINFO));
bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = w;
bmi.bmiHeader.biHeight = -h;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
GetDIBits (hDstDC, hImage, 0, h, image.
GetData(), &bmi, DIB_RGB_COLORS);
// Dispose & clean up objects
SelectObject (hDstDC, hOldDC);
DeleteDC (hDstDC);
ReleaseDC (hwnd, hSrcDC);
DeleteObject (hImage);
return true;
#endif
}
////////////////////////////////////////////////////////////////////////////////
Bounds Screen::GetTotalBounds (void) { return mTotalBounds; }
Bounds Screen::GetTotalUsable (void) { return mTotalUsable; }
////////////////////////////////////////////////////////////////////////////////
bool Screen::IsCompositing (void)
{
#if defined (ROBOT_OS_MAC) || \
defined (ROBOT_OS_LINUX)
return true;
#endif
#ifdef ROBOT_OS_WIN
InitializeDWM(); BOOL enabled = FALSE;
if (gDwmIsCompositionEnabled != nullptr)
gDwmIsCompositionEnabled (&enabled);
return enabled != FALSE;
#endif
}
////////////////////////////////////////////////////////////////////////////////
void Screen::SetCompositing (bool enabled)
{
#ifdef ROBOT_OS_WIN
InitializeDWM();
if (gDwmEnableComposition != nullptr)
gDwmEnableComposition (enabled ?
DWM_EC_ENABLECOMPOSITION :
DWM_EC_DISABLECOMPOSITION);
#endif
}
ROBOT_NS_END