Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DisplayServer] Implement screen_get_pixel method for LinuxBSD/X11, macOS and Windows. #74087

Merged
merged 1 commit into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,15 @@
[b]Note:[/b] This method is implemented on Android and iOS.
</description>
</method>
<method name="screen_get_pixel" qualifiers="const">
<return type="Color" />
<param index="0" name="position" type="Vector2i" />
<description>
Returns color of the display pixel at the [param position].
[b]Note:[/b] This method is implemented on Linux (X11), macOS, and Windows.
[b]Note:[/b] On macOS, this method requires "Screen Recording" permission, if permission is not granted it will return desktop wallpaper color.
</description>
</method>
<method name="screen_get_position" qualifiers="const">
<return type="Vector2i" />
<param index="0" name="screen" type="int" default="-1" />
Expand Down Expand Up @@ -1543,6 +1552,9 @@
<constant name="FEATURE_EXTEND_TO_TITLE" value="20" enum="Feature">
Display server supports expanding window content to the title. See [constant WINDOW_FLAG_EXTEND_TO_TITLE]. [b]macOS[/b]
</constant>
<constant name="FEATURE_SCREEN_CAPTURE" value="21" enum="Feature">
Display server supports reading screen pixels. See [method screen_get_pixel].
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
</constant>
Expand Down
24 changes: 24 additions & 0 deletions platform/linuxbsd/x11/display_server_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
#endif
case FEATURE_CLIPBOARD_PRIMARY:
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_SCREEN_CAPTURE:
return true;
default: {
}
Expand Down Expand Up @@ -1169,6 +1170,29 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const {
return 96;
}

Color DisplayServerX11::screen_get_pixel(const Point2i &p_position) const {
Point2i pos = p_position;

int number_of_screens = XScreenCount(x11_display);
for (int i = 0; i < number_of_screens; i++) {
Window root = XRootWindow(x11_display, i);
XWindowAttributes root_attrs;
XGetWindowAttributes(x11_display, root, &root_attrs);
if ((pos.x >= root_attrs.x) && (pos.x <= root_attrs.x + root_attrs.width) && (pos.y >= root_attrs.y) && (pos.y <= root_attrs.y + root_attrs.height)) {
XImage *image = XGetImage(x11_display, root, pos.x, pos.y, 1, 1, AllPlanes, XYPixmap);
if (image) {
XColor c;
c.pixel = XGetPixel(image, 0, 0);
XFree(image);
XQueryColor(x11_display, XDefaultColormap(x11_display, i), &c);
return Color(float(c.red) / 65535.0, float(c.green) / 65535.0, float(c.blue) / 65535.0, 1.0);
}
}
}

return Color();
}

float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_

Expand Down
1 change: 1 addition & 0 deletions platform/linuxbsd/x11/display_server_x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ class DisplayServerX11 : public DisplayServer {
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Color screen_get_pixel(const Point2i &p_position) const override;

#if defined(DBUS_ENABLED)
virtual void screen_set_keep_on(bool p_enable) override;
Expand Down
1 change: 1 addition & 0 deletions platform/macos/display_server_macos.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ class DisplayServerMacOS : public DisplayServer {
virtual float screen_get_max_scale() const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Color screen_get_pixel(const Point2i &p_position) const override;
virtual void screen_set_keep_on(bool p_enable) override;
virtual bool screen_is_kept_on() const override;

Expand Down
30 changes: 30 additions & 0 deletions platform/macos/display_server_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@
case FEATURE_SWAP_BUFFERS:
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_EXTEND_TO_TITLE:
case FEATURE_SCREEN_CAPTURE:
return true;
default: {
}
Expand Down Expand Up @@ -2249,6 +2250,35 @@
return Rect2i();
}

Color DisplayServerMacOS::screen_get_pixel(const Point2i &p_position) const {
Point2i position = p_position;
// OS X native y-coordinate relative to _get_screens_origin() is negative,
// Godot passes a positive value.
position.y *= -1;
position += _get_screens_origin();
position /= screen_get_max_scale();

for (NSScreen *screen in [NSScreen screens]) {
NSRect frame = [screen frame];
if (NSMouseInRect(NSMakePoint(position.x, position.y), frame, NO)) {
NSDictionary *screenDescription = [screen deviceDescription];
CGDirectDisplayID display_id = [[screenDescription objectForKey:@"NSScreenNumber"] unsignedIntValue];
CGImageRef image = CGDisplayCreateImageForRect(display_id, CGRectMake(position.x - frame.origin.x, frame.size.height - (position.y - frame.origin.y), 1, 1));
if (image) {
NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithCGImage:image];
CGImageRelease(image);
NSColor *color = [bitmap colorAtX:0 y:0];
if (color) {
CGFloat components[4];
[color getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]];
return Color(components[0], components[1], components[2], components[3]);
}
}
}
}
return Color();
}

float DisplayServerMacOS::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_

Expand Down
24 changes: 24 additions & 0 deletions platform/windows/display_server_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_SWAP_BUFFERS:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_SCREEN_CAPTURE:
return true;
default:
return false;
Expand Down Expand Up @@ -631,6 +632,26 @@ int DisplayServerWindows::screen_get_dpi(int p_screen) const {
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data);
return data.dpi;
}

Color DisplayServerWindows::screen_get_pixel(const Point2i &p_position) const {
Point2i pos = p_position + _get_screens_origin();

POINT p;
p.x = pos.x;
p.y = pos.y;
if (win81p_LogicalToPhysicalPointForPerMonitorDPI) {
win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p);
}
HDC dc = GetDC(0);
COLORREF col = GetPixel(dc, p.x, p.y);
if (col != CLR_INVALID) {
return Color(float(col & 0x000000FF) / 256.0, float((col & 0x0000FF00) >> 8) / 256.0, float((col & 0x00FF0000) >> 16) / 256.0, 1.0);
}
ReleaseDC(NULL, dc);
Comment on lines +645 to +650
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we don't call ReleaseDC(...) if the color is valid?
And why we don't check that GetDC(0) is not NULL?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not wrong, window's style class uses CS_OWNDC flag, meaning the DC is unique and shared for all drawing operations.


return Color();
}

float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const {
_THREAD_SAFE_METHOD_

Expand Down Expand Up @@ -4020,6 +4041,7 @@ GetImmersiveUserColorSetPreferencePtr DisplayServerWindows::GetImmersiveUserColo
bool DisplayServerWindows::winink_available = false;
GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr;
GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr;

typedef enum _SHC_PROCESS_DPI_AWARENESS {
SHC_PROCESS_DPI_UNAWARE = 0,
Expand Down Expand Up @@ -4148,10 +4170,12 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}

// Note: Windows Ink API for pen input, available on Windows 8+ only.
// Note: DPI conversion API, available on Windows 8.1+ only.
HMODULE user32_lib = LoadLibraryW(L"user32.dll");
if (user32_lib) {
win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo");
win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI");

winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo;
}
Expand Down
5 changes: 5 additions & 0 deletions platform/windows/display_server_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ typedef struct tagPOINTER_PEN_INFO {

typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
typedef BOOL(WINAPI *LogicalToPhysicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint);

typedef struct {
BYTE bWidth; // Width, in pixels, of the image
Expand Down Expand Up @@ -305,6 +306,9 @@ class DisplayServerWindows : public DisplayServer {
static GetPointerTypePtr win8p_GetPointerType;
static GetPointerPenInfoPtr win8p_GetPointerPenInfo;

// DPI conversion API
static LogicalToPhysicalPointForPerMonitorDPIPtr win81p_LogicalToPhysicalPointForPerMonitorDPI;

void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver);
String tablet_driver;
Vector<String> tablet_drivers;
Expand Down Expand Up @@ -524,6 +528,7 @@ class DisplayServerWindows : public DisplayServer {
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Color screen_get_pixel(const Point2i &p_position) const override;

virtual void screen_set_keep_on(bool p_enable) override; //disable screensaver
virtual bool screen_is_kept_on() const override;
Expand Down
2 changes: 2 additions & 0 deletions servers/display_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_touchscreen_available"), &DisplayServer::is_touchscreen_available, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_max_scale"), &DisplayServer::screen_get_max_scale);
ClassDB::bind_method(D_METHOD("screen_get_refresh_rate", "screen"), &DisplayServer::screen_get_refresh_rate, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_pixel", "position"), &DisplayServer::screen_get_pixel);

ClassDB::bind_method(D_METHOD("screen_set_orientation", "orientation", "screen"), &DisplayServer::screen_set_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_orientation", "screen"), &DisplayServer::screen_get_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
Expand Down Expand Up @@ -785,6 +786,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_CLIPBOARD_PRIMARY);
BIND_ENUM_CONSTANT(FEATURE_TEXT_TO_SPEECH);
BIND_ENUM_CONSTANT(FEATURE_EXTEND_TO_TITLE);
BIND_ENUM_CONSTANT(FEATURE_SCREEN_CAPTURE);

BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
Expand Down
2 changes: 2 additions & 0 deletions servers/display_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class DisplayServer : public Object {
FEATURE_CLIPBOARD_PRIMARY,
FEATURE_TEXT_TO_SPEECH,
FEATURE_EXTEND_TO_TITLE,
FEATURE_SCREEN_CAPTURE,
};

virtual bool has_feature(Feature p_feature) const = 0;
Expand Down Expand Up @@ -275,6 +276,7 @@ class DisplayServer : public Object {
return scale;
}
virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0;
virtual Color screen_get_pixel(const Point2i &p_position) const { return Color(); };
virtual bool is_touchscreen_available() const;

// Keep the ScreenOrientation enum values in sync with the `display/window/handheld/orientation`
Expand Down