Files
FlaxEngine/Source/Engine/Platform/Linux/LinuxWindow.cpp
Wojtek Figat 09f2be907e Merge remote-tracking branch 'origin/master' into 1.7
# Conflicts:
#	Flax.flaxproj
2023-10-09 12:40:47 +02:00

961 lines
26 KiB
C++

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if PLATFORM_LINUX
#include "../Window.h"
#include "Engine/Input/Input.h"
#include "Engine/Input/Mouse.h"
#include "Engine/Input/Keyboard.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Utilities/StringConverter.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/TextureData.h"
// hack using TextureTool in Platform module -> TODO: move texture data sampling to texture data itself
#define COMPILE_WITH_TEXTURE_TOOL 1
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "IncludeX11.h"
// ICCCM
#define WM_NormalState 1L // window normal state
#define WM_IconicState 3L // window minimized
// EWMH
#define _NET_WM_STATE_REMOVE 0L // remove/unset property
#define _NET_WM_STATE_ADD 1L // add/set property
#define _NET_WM_STATE_TOGGLE 2L // toggle property
#define DefaultDPI 96
// Window routines function prolog
#define LINUX_WINDOW_PROLOG X11::Display* display = (X11::Display*)LinuxPlatform::GetXDisplay(); X11::Window window = (X11::Window)_window
extern X11::XIC IC;
extern X11::Atom xAtomDeleteWindow;
extern X11::Atom xAtomWmWindowOpacity;
extern X11::Atom xAtomWmName;
extern Dictionary<StringAnsi, X11::KeyCode> KeyNameMap;
extern Array<KeyboardKeys> KeyCodeMap;
extern X11::Cursor Cursors[(int32)CursorType::MAX];
static constexpr uint32 MouseDoubleClickTime = 500;
static constexpr uint32 MaxDoubleClickDistanceSquared = 10;
static X11::Time MouseLastButtonPressTime = 0;
static Float2 OldMouseClickPosition;
LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
: WindowBase(settings)
{
auto display = (X11::Display*)LinuxPlatform::GetXDisplay();
if (!display)
return;
auto screen = XDefaultScreen(display);
// Cache data
int32 width = Math::TruncToInt(settings.Size.X);
int32 height = Math::TruncToInt(settings.Size.Y);
_clientSize = Float2((float)width, (float)height);
int32 x = 0, y = 0;
switch (settings.StartPosition)
{
case WindowStartPosition::CenterParent:
if (settings.Parent)
{
Rectangle parentBounds = settings.Parent->GetClientBounds();
x = Math::TruncToInt(parentBounds.Location.X + (parentBounds.Size.X - _clientSize.X) * 0.5f);
y = Math::TruncToInt(parentBounds.Location.Y + (parentBounds.Size.Y - _clientSize.Y) * 0.5f);
}
break;
case WindowStartPosition::CenterScreen:
{
Rectangle desktopBounds;
int event, err;
const bool ok = X11::XineramaQueryExtension(display, &event, &err);
if (X11::XineramaQueryExtension(display, &event, &err))
{
int count;
X11::XineramaScreenInfo* xsi = X11::XineramaQueryScreens(display, &count);
ASSERT(screen < count);
desktopBounds = Rectangle(Float2((float)xsi[screen].x_org, (float)xsi[screen].y_org), Float2((float)xsi[screen].width, (float)xsi[screen].height));
X11::XFree(xsi);
}
else
desktopBounds = Rectangle(Float2::Zero, Platform::GetDesktopSize());
x = Math::TruncToInt(desktopBounds.Location.X + (desktopBounds.Size.X - _clientSize.X) * 0.5f);
y = Math::TruncToInt(desktopBounds.Location.Y + (desktopBounds.Size.Y - _clientSize.Y) * 0.5f);
}
break;
case WindowStartPosition::Manual:
x = Math::TruncToInt(settings.Position.X);
y = Math::TruncToInt(settings.Position.Y);
break;
}
_resizeDisabled = !settings.HasSizingFrame;
auto rootWindow = XRootWindow(display, screen);
long visualMask = VisualScreenMask;
int numberOfVisuals;
X11::XVisualInfo vInfoTemplate = {};
vInfoTemplate.screen = screen;
X11::XVisualInfo* visualInfo = X11::XGetVisualInfo(display, visualMask, &vInfoTemplate, &numberOfVisuals);
X11::Colormap colormap = X11::XCreateColormap(display, rootWindow, visualInfo->visual, AllocNone);
X11::XSetWindowAttributes windowAttributes = {};
windowAttributes.colormap = colormap;
windowAttributes.background_pixel = XBlackPixel(display, screen);
windowAttributes.border_pixel = XBlackPixel(display, screen);
windowAttributes.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
// TODO: implement all window settings
/*
bool Fullscreen;
bool AllowMinimize;
bool AllowMaximize;
*/
const X11::Window window = X11::XCreateWindow(
display, X11::XRootWindow(display, screen), x, y,
width, height, 0, visualInfo->depth, InputOutput,
visualInfo->visual,
CWBackPixel | CWBorderPixel | CWEventMask | CWColormap, &windowAttributes);
_window = window;
LinuxWindow::SetTitle(settings.Title);
// Link process id with window
X11::Atom wmPid = X11::XInternAtom(display, "_NET_WM_PID", 0);
const uint64 pid = Platform::GetCurrentProcessId();
X11::XChangeProperty(display, window, wmPid, 6, 32, PropModeReplace, (unsigned char*)&pid, 1);
// Position/size might have (and usually will) get overridden by the WM, so re-apply them
X11::XSizeHints hints;
hints.flags = PPosition | PSize | PMinSize | PMaxSize;
hints.x = x;
hints.y = y;
hints.width = width;
hints.height = height;
if (_resizeDisabled)
{
// Block resizing
hints.min_width = width;
hints.max_width = width;
hints.min_height = height;
hints.max_height = height;
}
else
{
// Set resizing range
hints.min_width = (int)settings.MinimumSize.X;
hints.max_width = (int)settings.MaximumSize.X;
hints.min_height = (int)settings.MinimumSize.Y;
hints.max_height = (int)settings.MaximumSize.Y;
hints.flags |= USSize;
}
// honor the WM placement except for manual (overriding) placements
if (settings.StartPosition == WindowStartPosition::Manual)
{
X11::XSetNormalHints(display, window, &hints);
}
// Ensures the child window is always on top of the parent window
if (settings.Parent)
{
X11::XSetTransientForHint(display, window, (X11::Window)((LinuxWindow*)settings.Parent)->GetNativePtr());
}
_dpi = Platform::GetDpi();
_dpiScale = (float)_dpi / (float)DefaultDPI;
// Set input mask
long eventMask =
ExposureMask | FocusChangeMask |
KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask |
EnterWindowMask | LeaveWindowMask |
VisibilityChangeMask | ExposureMask |
PointerMotionMask | ButtonMotionMask |
StructureNotifyMask | PropertyChangeMask;
if (!settings.Parent)
eventMask |= SubstructureNotifyMask | SubstructureRedirectMask;
X11::XSelectInput(display, window, eventMask);
// Make sure we get the window delete message from WM
X11::XSetWMProtocols(display, window, &xAtomDeleteWindow, 1);
// Adjust style for borderless windows
if (!settings.HasBorder)
{
// [Reference: https://www.tonyobryan.com//index.php?article=9]
typedef struct X11Hints
{
unsigned long flags;
unsigned long functions;
unsigned long decorations;
long inputMode;
unsigned long status;
} X11Hints;
X11Hints hints;
hints.flags = 2;
hints.decorations = 0;
X11::Atom wmHints = X11::XInternAtom(display, "_MOTIF_WM_HINTS", 0);
X11::Atom property;
if (wmHints)
X11::XChangeProperty(display, window, property, property, 32, PropModeReplace, (unsigned char*)&hints, 5);
}
// Adjust type for utility windows
if (!settings.IsRegularWindow)
{
X11::Atom value = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", 0);
X11::Atom wmType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0);
if (value && wmType)
X11::XChangeProperty(display, window, wmType, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)&value, 1);
}
// Initialize state
X11::Atom wmState = X11::XInternAtom(display, "_NET_WM_STATE", 0);
X11::Atom wmStateAbove = X11::XInternAtom(display, "_NET_WM_STATE_ABOVE", 0);
X11::Atom wmSateSkipTaskbar = X11::XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", 0);
X11::Atom wmStateSkipPager = X11::XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", 0);
X11::Atom wmStateFullscreen = X11::XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", 0);
X11::Atom states[4];
int32 statesCount = 0;
if (settings.IsTopmost)
{
states[statesCount++] = wmStateAbove;
}
if (!settings.ShowInTaskbar)
{
states[statesCount++] = wmSateSkipTaskbar;
states[statesCount++] = wmStateSkipPager;
}
if (settings.Fullscreen)
{
states[statesCount++] = wmStateFullscreen;
}
X11::XChangeProperty(display, window, wmState, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)states, statesCount);
// Drag&drop support
if (settings.AllowDragAndDrop)
{
auto xdndVersion = 5;
auto xdndAware = XInternAtom(display, "XdndAware", 0);
if (xdndAware != 0)
X11::XChangeProperty(display, window, xdndAware, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)&xdndVersion, 1);
}
// Sync
X11::XFlush(display);
X11::XSync(display, 0);
X11::XFree(visualInfo);
}
LinuxWindow::~LinuxWindow()
{
// Cleanup
LINUX_WINDOW_PROLOG;
if (!display)
return;
X11::XDestroyWindow(display, window);
}
void* LinuxWindow::GetNativePtr() const
{
return (void*)_window;
}
void LinuxWindow::Show()
{
if (!_visible)
{
InitSwapChain();
if (_showAfterFirstPaint)
{
if (RenderTask)
RenderTask->Enabled = true;
return;
}
// Show
LINUX_WINDOW_PROLOG;
if (display)
{
X11::XMapWindow(display, window);
X11::XFlush(display);
}
_focusOnMapped = _settings.AllowInput && _settings.ActivateWhenFirstShown;
// Base
WindowBase::Show();
}
}
void LinuxWindow::Hide()
{
if (_visible)
{
// Hide
LINUX_WINDOW_PROLOG;
if (display)
{
X11::XUnmapWindow(display, window);
}
// Base
WindowBase::Hide();
}
}
void LinuxWindow::Minimize()
{
Minimize(true);
}
void LinuxWindow::Maximize()
{
Maximize(true);
}
void LinuxWindow::Restore()
{
if (IsMaximized())
Maximize(false);
else if (IsMinimized())
Minimize(false);
}
void LinuxWindow::BringToFront(bool force)
{
LINUX_WINDOW_PROLOG;
if (!display)
return;
X11::Atom activeWindow = X11::XInternAtom(display, "_NET_ACTIVE_WINDOW", 0);
X11::XEvent event;
Platform::MemoryClear(&event, sizeof(event));
event.type = ClientMessage;
event.xclient.window = window;
event.xclient.message_type = activeWindow;
event.xclient.format = 32;
event.xclient.data.l[0] = 1;
event.xclient.data.l[1] = CurrentTime;
X11::XSendEvent(display, X11_DefaultRootWindow(display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &event);
X11::XFlush(display);
}
bool LinuxWindow::IsClosed() const
{
return _isClosing;
}
bool LinuxWindow::IsForegroundWindow() const
{
return _focused || _focusOnMapped;
}
void LinuxWindow::SetClientBounds(const Rectangle& clientArea)
{
int32 x = Math::TruncToInt(clientArea.GetX());
int32 y = Math::TruncToInt(clientArea.GetY());
int32 width = Math::TruncToInt(clientArea.GetWidth());
int32 height = Math::TruncToInt(clientArea.GetHeight());
LINUX_WINDOW_PROLOG;
if (!display)
return;
// If resize is disabled on WM level, we need to force it
if (_resizeDisabled)
{
X11::XSizeHints hints;
hints.flags = PMinSize | PMaxSize;
hints.min_height = height;
hints.max_height = height;
hints.min_width = width;
hints.max_width = width;
X11::XSetNormalHints(display, window, &hints);
}
X11::XMapWindow(display, window);
_clientSize = Float2((float)width, (float)height);
X11::XResizeWindow(display, window, width, height);
X11::XMoveWindow(display, window, x, y);
X11::XFlush(display);
}
void LinuxWindow::SetPosition(const Float2& position)
{
int32 x = Math::TruncToInt(position.X);
int32 y = Math::TruncToInt(position.Y);
LINUX_WINDOW_PROLOG;
if (!display)
return;
X11::XMoveWindow(display, window, x, y);
X11::XFlush(display);
}
void LinuxWindow::SetClientPosition(const Float2& position)
{
int32 x = Math::TruncToInt(position.X);
int32 y = Math::TruncToInt(position.Y);
LINUX_WINDOW_PROLOG;
if (!display)
return;
X11::XWindowAttributes xwa;
X11::XGetWindowAttributes(display, window, &xwa);
X11::XMoveWindow(display, window, x - xwa.border_width, y - xwa.border_width);
X11::XFlush(display);
}
Float2 LinuxWindow::GetPosition() const
{
LINUX_WINDOW_PROLOG;
if (!display)
return Float2::Zero;
X11::XWindowAttributes xwa;
X11::XGetWindowAttributes(display, window, &xwa);
return Float2((float)xwa.x, (float)xwa.y);
}
Float2 LinuxWindow::GetSize() const
{
LINUX_WINDOW_PROLOG;
if (!display)
return _clientSize;
X11::XWindowAttributes xwa;
X11::XGetWindowAttributes(display, window, &xwa);
return Float2((float)(xwa.width + xwa.border_width), (float)(xwa.height + xwa.border_width));
}
Float2 LinuxWindow::GetClientSize() const
{
return _clientSize;
}
Float2 LinuxWindow::ScreenToClient(const Float2& screenPos) const
{
LINUX_WINDOW_PROLOG;
if (!display)
return screenPos;
int32 x, y;
X11::Window child;
X11::XTranslateCoordinates(display, X11_DefaultRootWindow(display), window, (int32)screenPos.X, (int32)screenPos.Y, &x, &y, &child);
return Float2((float)x, (float)y);
}
Float2 LinuxWindow::ClientToScreen(const Float2& clientPos) const
{
LINUX_WINDOW_PROLOG;
if (!display)
return clientPos;
int32 x, y;
X11::Window child;
X11::XTranslateCoordinates(display, window, X11_DefaultRootWindow(display), (int32)clientPos.X, (int32)clientPos.Y, &x, &y, &child);
return Float2((float)x, (float)y);
}
void LinuxWindow::FlashWindow()
{
LINUX_WINDOW_PROLOG;
if (!display)
return;
const X11::Atom wmState = X11::XInternAtom(display, "_NET_WM_STATE", 0);
const X11::Atom wmAttention = X11::XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0);
X11::XEvent event;
Platform::MemoryClear(&event, sizeof(event));
event.type = ClientMessage;
event.xclient.window = window;
event.xclient.message_type = wmState;
event.xclient.format = 32;
event.xclient.data.l[0] = _NET_WM_STATE_ADD;
event.xclient.data.l[1] = wmAttention;
X11::XSendEvent(display, X11_DefaultRootWindow(display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &event);
}
void LinuxWindow::GetScreenInfo(int32& x, int32& y, int32& width, int32& height) const
{
LINUX_WINDOW_PROLOG;
x = y = width = height = 0;
if (!display)
return;
int event, err;
const bool ok = X11::XineramaQueryExtension(display, &event, &err);
if (!ok)
return;
X11::XWindowAttributes xwa;
X11::XGetWindowAttributes(display, window, &xwa);
int32 posX = xwa.x;
int32 posY = xwa.y;
int count;
X11::XineramaScreenInfo* xsi = X11::XineramaQueryScreens(display, &count);
for (int32 i = 0; i < count; i++)
{
auto& screen = xsi[i];
if (screen.x_org <= posX && screen.y_org <= posY && posX < screen.x_org + screen.width && posY < screen.y_org + screen.height)
{
x = screen.x_org;
y = screen.y_org;
width = screen.width;
height = screen.height;
break;
}
}
X11::XFree(xsi);
}
void LinuxWindow::CheckForWindowResize()
{
// Skip for minimized window
if (_minimized)
return;
LINUX_WINDOW_PROLOG;
if (!display)
return;
// Get client size
X11::XWindowAttributes xwa;
X11::XGetWindowAttributes(display, window, &xwa);
const int32 width = xwa.width;
const int32 height = xwa.height;
const Float2 clientSize((float)width, (float)height);
// Check if window size has been changed
if (clientSize != _clientSize && width > 0 && height > 0)
{
_clientSize = clientSize;
OnResize(width, height);
}
}
void LinuxWindow::OnKeyPress(void* event)
{
auto keyEvent = (X11::XKeyPressedEvent*)event;
auto display = (X11::Display*)LinuxPlatform::GetXDisplay();
Input::Keyboard->OnKeyDown(KeyCodeMap[keyEvent->keycode]);
// Process text input
X11::KeySym keySym = X11::XkbKeycodeToKeysym(display, (X11::KeyCode)keyEvent->keycode, 0, 0);
// Check if input manager wants this event
if (X11::XFilterEvent((X11::XEvent*)event, 0) != 0)
return;
// Send a text input event
int status;
char buffer[16];
int32 length = X11::Xutf8LookupString(IC, keyEvent, buffer, sizeof(buffer), nullptr, &status);
if (length > 0)
{
buffer[length] = '\0';
String utfStr(buffer);
if (utfStr.HasChars())
{
Input::Keyboard->OnCharInput(utfStr[0]);
}
}
}
void LinuxWindow::OnKeyRelease(void* event)
{
auto keyEvent = (X11::XKeyPressedEvent*)event;
Input::Keyboard->OnKeyUp(KeyCodeMap[keyEvent->keycode]);
}
void LinuxWindow::OnButtonPress(void* event)
{
auto buttonEvent = (X11::XButtonPressedEvent*)event;
Float2 mousePos((float)buttonEvent->x, (float)buttonEvent->y);
MouseButton mouseButton;
switch (buttonEvent->button)
{
case Button1:
mouseButton = MouseButton::Left;
break;
case Button2:
mouseButton = MouseButton::Middle;
break;
case Button3:
mouseButton = MouseButton::Right;
break;
default:
return;
}
// Handle double-click
if (buttonEvent->button == Button1)
{
if (
buttonEvent->time < (MouseLastButtonPressTime + MouseDoubleClickTime) &&
Float2::DistanceSquared(mousePos, OldMouseClickPosition) < MaxDoubleClickDistanceSquared)
{
Input::Mouse->OnMouseDoubleClick(ClientToScreen(mousePos), mouseButton, this);
MouseLastButtonPressTime = 0;
OldMouseClickPosition = mousePos;
return;
}
else
{
MouseLastButtonPressTime = buttonEvent->time;
OldMouseClickPosition = mousePos;
}
}
Input::Mouse->OnMouseDown(ClientToScreen(mousePos), mouseButton, this);
}
void LinuxWindow::OnButtonRelease(void* event)
{
auto buttonEvent = (X11::XButtonReleasedEvent*)event;
Float2 mousePos((float)buttonEvent->x, (float)buttonEvent->y);
switch (buttonEvent->button)
{
case Button1:
Input::Mouse->OnMouseUp(ClientToScreen(mousePos), MouseButton::Left, this);
break;
case Button2:
Input::Mouse->OnMouseUp(ClientToScreen(mousePos), MouseButton::Middle, this);
break;
case Button3:
Input::Mouse->OnMouseUp(ClientToScreen(mousePos), MouseButton::Right, this);
break;
case Button4:
Input::Mouse->OnMouseWheel(ClientToScreen(mousePos), 1.0f, this);
break;
case Button5:
Input::Mouse->OnMouseWheel(ClientToScreen(mousePos), -1.0f, this);
break;
default:
return;
}
}
void LinuxWindow::OnMotionNotify(void* event)
{
auto motionEvent = (X11::XMotionEvent*)event;
Float2 mousePos((float)motionEvent->x, (float)motionEvent->y);
Input::Mouse->OnMouseMove(ClientToScreen(mousePos), this);
}
void LinuxWindow::OnLeaveNotify(void* event)
{
auto crossingEvent = (X11::XCrossingEvent*)event;
Input::Mouse->OnMouseLeave(this);
}
void LinuxWindow::OnConfigureNotify(void* event)
{
auto configureEvent = (X11::XConfigureEvent*)event;
const Float2 clientSize((float)configureEvent->width, (float)configureEvent->height);
if (clientSize != _clientSize)
{
_clientSize = clientSize;
OnResize(configureEvent->width, configureEvent->height);
}
}
void LinuxWindow::Maximize(bool enable)
{
LINUX_WINDOW_PROLOG;
if (!display)
return;
X11::Atom wmState = X11::XInternAtom(display, "_NET_WM_STATE", 0);
X11::Atom wmMaxHorz = X11::XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0);
X11::Atom wmMaxVert = X11::XInternAtom(display, "_NET_WM_STATE_MAXIMIZED_VERT", 0);
if (IsWindowMapped())
{
X11::XEvent event;
Platform::MemoryClear(&event, sizeof(event));
event.type = ClientMessage;
event.xclient.window = window;
event.xclient.message_type = wmState;
event.xclient.format = 32;
event.xclient.data.l[0] = enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
event.xclient.data.l[1] = wmMaxHorz;
event.xclient.data.l[2] = wmMaxVert;
XSendEvent(display, X11_DefaultRootWindow(display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &event);
}
else if (enable)
{
X11::Atom states[2];
states[0] = wmMaxVert;
states[1] = wmMaxHorz;
X11::XChangeProperty(display, window, wmState, (X11::Atom)4, 32, PropModeReplace, (unsigned char*)states, 2);
}
}
void LinuxWindow::Minimize(bool enable)
{
LINUX_WINDOW_PROLOG;
if (!display)
return;
X11::Atom wmChange = X11::XInternAtom(display, "WM_CHANGE_STATE", 0);
X11::XEvent event;
Platform::MemoryClear(&event, sizeof(event));
event.type = ClientMessage;
event.xclient.window = window;
event.xclient.message_type = wmChange;
event.xclient.format = 32;
event.xclient.data.l[0] = enable ? WM_IconicState : WM_NormalState;
XSendEvent(display, X11_DefaultRootWindow(display), 0, SubstructureRedirectMask | SubstructureNotifyMask, &event);
}
bool LinuxWindow::IsWindowMapped()
{
LINUX_WINDOW_PROLOG;
if (!display)
return false;
X11::XWindowAttributes xwa;
X11::XGetWindowAttributes(display, window, &xwa);
return xwa.map_state != IsUnmapped;
}
float LinuxWindow::GetOpacity() const
{
return _opacity;
}
void LinuxWindow::SetOpacity(const float opacity)
{
LINUX_WINDOW_PROLOG;
if (!display)
return;
if (Math::IsOne(opacity))
{
X11::XDeleteProperty(display, window, xAtomWmWindowOpacity);
}
else
{
const uint32 fullyOpaque = 0xFFFFFFFF;
const long alpha = (long)((double)opacity * (double)fullyOpaque);
X11::XChangeProperty(display, window, xAtomWmWindowOpacity, 6, 32, PropModeReplace, (unsigned char *)&alpha, 1);
}
_opacity = opacity;
}
void LinuxWindow::Focus()
{
if (_focused || !IsWindowMapped())
return;
LINUX_WINDOW_PROLOG;
if (!display)
return;
X11::XSetInputFocus(display, window, RevertToPointerRoot, CurrentTime);
X11::XFlush(display);
X11::XSync(display, 0);
}
void LinuxWindow::SetTitle(const StringView& title)
{
LINUX_WINDOW_PROLOG;
_title = title;
if (!display)
return;
const StringAsANSI<512> titleAnsi(title.Get(), title.Length());
const char* text = titleAnsi.Get();
X11::XStoreName(display, window, text);
X11::XSetIconName(display, window, text);
const X11::Atom netWmName = X11::XInternAtom(display, "_NET_WM_NAME", false);
const X11::Atom utf8String = X11::XInternAtom(display, "UTF8_STRING", false);
X11::XChangeProperty(display, window, netWmName, utf8String, 8, PropModeReplace, (unsigned char*)text, titleAnsi.Length());
X11::XTextProperty titleProp;
const int status = X11::Xutf8TextListToTextProperty(display, (char**)&text, 1, X11::XUTF8StringStyle, &titleProp);
if (status == Success)
{
X11::XSetTextProperty(display, window, &titleProp, xAtomWmName);
//X11::XSetWMIconName(display, window, &titleProp);
X11::XFree(titleProp.value);
}
}
void LinuxWindow::StartTrackingMouse(bool useMouseScreenOffset)
{
// TODO: impl this
}
void LinuxWindow::EndTrackingMouse()
{
// TODO: impl this
}
void LinuxWindow::SetCursor(CursorType type)
{
WindowBase::SetCursor(type);
LINUX_WINDOW_PROLOG;
if (!display)
return;
X11::XDefineCursor(display, window, Cursors[(int32)type]);
}
bool IconErrorFlag;
int SetIconErrorHandler(X11::Display *dpy, X11::XErrorEvent *ev)
{
IconErrorFlag = true;
return 0;
}
void LinuxWindow::SetIcon(TextureData& icon)
{
WindowBase::SetIcon(icon);
LINUX_WINDOW_PROLOG;
if (!display)
return;
IconErrorFlag = false;
int (*oldHandler)(X11::Display*, X11::XErrorEvent*) = X11::XSetErrorHandler(&SetIconErrorHandler);
X11::Atom iconAtom = X11::XInternAtom(display, "_NET_WM_ICON", 0);
X11::Atom cardinalAtom = X11::XInternAtom(display, "CARDINAL", 0);
if (icon.Width != 0)
{
TextureData img;
if (icon.Format == PixelFormat::R8G8B8A8_UNorm)
{
img = icon;
}
else
{
img.Width = icon.Width;
img.Height = icon.Height;
img.Depth = icon.Depth;
img.Format = PixelFormat::R8G8B8A8_UNorm;
img.Items.Resize(1);
auto& item = img.Items[0];
item.Mips.Resize(1);
auto& mip = item.Mips[0];
mip.RowPitch = img.Width * sizeof(Color32);
mip.DepthPitch = mip.RowPitch * img.Height;
mip.Lines = img.Height;
mip.Data.Allocate(mip.DepthPitch);
const auto mipData = mip.Data.Get<Color32>();
const auto iconData = icon.GetData(0, 0);
const Int2 iconSize(icon.Width, icon.Height);
const auto sampler = TextureTool::GetSampler(icon.Format);
for (int32 y = 0; y < icon.Height; y++)
{
for (int32 x = 0; x < icon.Width; x++)
{
const Float2 uv((float)x / icon.Width, (float)y / icon.Height);
Color color = TextureTool::SampleLinear(sampler, uv, iconData->Data.Get(), iconSize, iconData->RowPitch);
*(mipData + y * icon.Width + x) = Color32(color);
}
}
}
while (true)
{
if (IconErrorFlag)
{
IconErrorFlag = false;
LOG(Warning, "Icon too large, attempting to resize icon.");
int32 newWidth, newHeight;
if (img.Width > img.Height)
{
newWidth = img.Width / 2;
newHeight = img.Height * newWidth / img.Width;
} else
{
newHeight = img.Height / 2;
newWidth = img.Width * newHeight / img.Height;
}
if (!newWidth || !newHeight)
{
LOG(Warning, "Unable to set icon.");
break;
}
BytesContainer newData;
newData.Allocate(newWidth * newHeight * sizeof(Color32));
auto& mip = img.Items[0].Mips[0];
const auto mipData = mip.Data.Get<Color32>();
const auto iconData = icon.GetData(0, 0);
const Int2 iconSize(icon.Width, icon.Height);
const auto sampler = TextureTool::GetSampler(img.Format);
for (int32 y = 0; y < newHeight; y++)
{
for (int32 x = 0; x < newWidth; x++)
{
const Float2 uv((float)x / newWidth, (float)y / newHeight);
Color color = TextureTool::SampleLinear(sampler, uv, iconData->Data.Get(), iconSize, iconData->RowPitch);
*(mipData + y * newWidth + x) = Color32(color);
}
}
img.Width = newWidth;
img.Height = newHeight;
mip.RowPitch = img.Width * sizeof(Color32);
mip.DepthPitch = mip.RowPitch * img.Height;
mip.Lines = img.Height;
mip.Data.Swap(newData);
}
// Use long to have wordsize (32bit build -> 32 bits, 64bit build -> 64 bits)
Array<long> pd;
pd.Resize(2 + img.Width * img.Height);
pd[0] = img.Width;
pd[1] = img.Height;
const uint8_t* pr = (const uint8_t*)img.Items[0].Mips[0].Data.Get();
long *wr = &pd[2];
for (int i = 0; i < img.Width * img.Height; i++)
{
long v = 0;
// A R G B
v |= pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];
*wr++ = v;
pr += 4;
}
X11::XChangeProperty(display, window, iconAtom, cardinalAtom, 32, PropModeReplace, (unsigned char*)pd.Get(), pd.Count());
if (!IconErrorFlag)
break;
}
}
else
{
X11::XDeleteProperty(display, window, iconAtom);
}
X11::XFlush(display);
X11::XSetErrorHandler(oldHandler);
}
#endif