/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * X11 helper utilities
 *
 * Copyright 2023 Armin Novak <armin.novak@thincast.com>
 * Copyringht 2023 Thincast Technologies GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string.h>
#include <winpr/assert.h>
#include <winpr/wtypes.h>
#include <winpr/path.h>

#include "xf_utils.h"
#include "xfreerdp.h"

#include <freerdp/utils/helpers.h>
#include <freerdp/log.h>

#define TAG CLIENT_TAG("xfreerdp.utils")

static const DWORD log_level = WLOG_TRACE;

static const char* error_to_string(wLog* log, Display* display, int error, char* buffer,
                                   size_t size)
{
	WINPR_ASSERT(size <= INT32_MAX);
	const int rc = XGetErrorText(display, error, buffer, (int)size);
	if (rc != Success)
		WLog_Print(log, WLOG_WARN, "XGetErrorText returned %d", rc);
	return buffer;
}

WINPR_ATTR_FORMAT_ARG(6, 7)
static void write_log(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line,
                      WINPR_FORMAT_ARG const char* fmt, ...)
{
	va_list ap = WINPR_C_ARRAY_INIT;
	va_start(ap, fmt);
	WLog_PrintTextMessageVA(log, level, line, fname, fkt, fmt, ap);
	va_end(ap);
}

static BOOL ignore_code(int rc, size_t count, va_list ap)
{
	for (size_t x = 0; x < count; x++)
	{
		const int val = va_arg(ap, int);
		if (rc == val)
			return TRUE;
	}
	return FALSE;
}

/* libx11 return codes are not really well documented, so checked against
 * https://gitlab.freedesktop.org/xorg/lib/libx11.git */
static int write_result_log_va(wLog* log, DWORD level, const char* fname, const char* fkt,
                               size_t line, Display* display, char* name, int rc, size_t count,
                               va_list ap)
{
	const BOOL ignore = ignore_code(rc, count, ap);
	if (!ignore)
	{
		char buffer[128] = WINPR_C_ARRAY_INIT;

		if (WLog_IsLevelActive(log, level))
		{
			WLog_PrintTextMessage(log, level, line, fname, fkt, "%s returned %s", name,
			                      error_to_string(log, display, rc, buffer, sizeof(buffer)));
		}
	}
	return rc;
}

static int write_result_log_expect_success(wLog* log, DWORD level, const char* fname,
                                           const char* fkt, size_t line, Display* display,
                                           char* name, int rc)
{
	if (rc != Success)
	{
		va_list ap = WINPR_C_ARRAY_INIT;
		(void)write_result_log_va(log, level, fname, fkt, line, display, name, rc, 0, ap);
	}
	return rc;
}

static int write_result_log_expect_one(wLog* log, DWORD level, const char* fname, const char* fkt,
                                       size_t line, Display* display, char* name, int rc)
{
	if (rc != 1)
	{
		va_list ap = WINPR_C_ARRAY_INIT;
		(void)write_result_log_va(log, level, fname, fkt, line, display, name, rc, 0, ap);
	}
	return rc;
}

char* Safe_XGetAtomNameEx(wLog* log, Display* display, Atom atom, const char* varname)
{
	WLog_Print(log, log_level, "XGetAtomName(%s, 0x%08lx)", varname, atom);
	if (atom == None)
		return strdup("Atom_None");
	return XGetAtomName(display, atom);
}

Atom Logging_XInternAtom(wLog* log, Display* display, _Xconst char* atom_name, Bool only_if_exists)
{
	Atom atom = XInternAtom(display, atom_name, only_if_exists);
	if (WLog_IsLevelActive(log, log_level))
	{
		WLog_Print(log, log_level, "XInternAtom(%p, %s, %s) -> 0x%08" PRIx32, (void*)display,
		           atom_name, only_if_exists ? "True" : "False",
		           WINPR_CXX_COMPAT_CAST(UINT32, atom));
	}
	return atom;
}

const char* x11_error_to_string(xfContext* xfc, int error, char* buffer, size_t size)
{
	WINPR_ASSERT(xfc);
	return error_to_string(xfc->log, xfc->display, error, buffer, size);
}

int LogDynAndXChangeProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                Display* display, Window w, Atom property, Atom type, int format,
                                int mode, const unsigned char* data, int nelements)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		char* propstr = Safe_XGetAtomName(log, display, property);
		char* typestr = Safe_XGetAtomName(log, display, type);
		write_log(log, log_level, file, fkt, line,
		          "XChangeProperty(%p, %lu, %s [%lu], %s [%lu], %d, %d, %p, %d)", (void*)display, w,
		          propstr, property, typestr, type, format, mode, (const void*)data, nelements);
		XFree(propstr);
		XFree(typestr);
	}
	const int rc = XChangeProperty(display, w, property, type, format, mode, data, nelements);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XChangeProperty",
	                                   rc);
}

int LogDynAndXDeleteProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                Display* display, Window w, Atom property)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		char* propstr = Safe_XGetAtomName(log, display, property);
		write_log(log, log_level, file, fkt, line, "XDeleteProperty(%p, %lu, %s [%lu])",
		          (void*)display, w, propstr, property);
		XFree(propstr);
	}
	const int rc = XDeleteProperty(display, w, property);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XDeleteProperty",
	                                   rc);
}

int LogDynAndXConvertSelection_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                  Display* display, Atom selection, Atom target, Atom property,
                                  Window requestor, Time time)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		char* selectstr = Safe_XGetAtomName(log, display, selection);
		char* targetstr = Safe_XGetAtomName(log, display, target);
		char* propstr = Safe_XGetAtomName(log, display, property);
		write_log(log, log_level, file, fkt, line,
		          "XConvertSelection(%p, %s [%lu], %s [%lu], %s [%lu], %lu, %lu)", (void*)display,
		          selectstr, selection, targetstr, target, propstr, property, requestor, time);
		XFree(propstr);
		XFree(targetstr);
		XFree(selectstr);
	}
	const int rc = XConvertSelection(display, selection, target, property, requestor, time);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display,
	                                   "XConvertSelection", rc);
}

int LogDynAndXGetWindowProperty_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                   Display* display, Window w, Atom property, long long_offset,
                                   long long_length, int c_delete, Atom req_type,
                                   Atom* actual_type_return, int* actual_format_return,
                                   unsigned long* nitems_return, unsigned long* bytes_after_return,
                                   unsigned char** prop_return)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		char* propstr = Safe_XGetAtomName(log, display, property);
		char* req_type_str = Safe_XGetAtomName(log, display, req_type);
		write_log(
		    log, log_level, file, fkt, line,
		    "XGetWindowProperty(%p, %lu, %s [%lu], %ld, %ld, %d, %s [%lu], %p, %p, %p, %p, %p)",
		    (void*)display, w, propstr, property, long_offset, long_length, c_delete, req_type_str,
		    req_type, (void*)actual_type_return, (void*)actual_format_return, (void*)nitems_return,
		    (void*)bytes_after_return, (void*)prop_return);
		XFree(propstr);
		XFree(req_type_str);
	}
	const int rc = XGetWindowProperty(display, w, property, long_offset, long_length, c_delete,
	                                  req_type, actual_type_return, actual_format_return,
	                                  nitems_return, bytes_after_return, prop_return);
	return write_result_log_expect_success(log, WLOG_WARN, file, fkt, line, display,
	                                       "XGetWindowProperty", rc);
}

BOOL IsGnome(void)
{
	// NOLINTNEXTLINE(concurrency-mt-unsafe)
	char* env = getenv("DESKTOP_SESSION");
	return (env != nullptr && strcmp(env, "gnome") == 0);
}

BOOL run_action_script(xfContext* xfc, const char* what, const char* arg, fn_action_script_run fkt,
                       void* user)
{
	BOOL rc = FALSE;
	FILE* keyScript = nullptr;
	WINPR_ASSERT(xfc);

	rdpSettings* settings = xfc->common.context.settings;
	WINPR_ASSERT(settings);

	const char* ActionScript = freerdp_settings_get_string(settings, FreeRDP_ActionScript);

	xfc->actionScriptExists = winpr_PathFileExists(ActionScript);

	if (!xfc->actionScriptExists)
	{
		WLog_DBG(TAG, "[ActionScript] no such script '%s'", ActionScript);
		goto fail;
	}

	{
		char command[2048] = WINPR_C_ARRAY_INIT;
		(void)sprintf_s(command, sizeof(command), "%s %s", ActionScript, what);

		// NOLINTNEXTLINE(bugprone-command-processor)
		keyScript = popen(command, "r");

		if (!keyScript)
		{
			WLog_ERR(TAG, "[ActionScript] Failed to execute '%s'", command);
			goto fail;
		}

		{
			BOOL read_data = FALSE;
			char buffer[2048] = WINPR_C_ARRAY_INIT;
			while (fgets(buffer, sizeof(buffer), keyScript) != nullptr)
			{
				char* end = strchr(buffer, '\n');
				if (end)
					*end = '\0';

				if (fkt)
				{
					if (!fkt(xfc, buffer, strnlen(buffer, sizeof(buffer)), user, what, arg))
						goto fail;
				}
				read_data = TRUE;
			}

			rc = read_data;
		}
		if (!rc)
			WLog_ERR(TAG, "[ActionScript] No data returned from command '%s'", command);
	}
fail:
	if (keyScript)
		pclose(keyScript);
	const BOOL res = rc || !xfc->actionScriptExists;
	if (!rc)
		xfc->actionScriptExists = FALSE;
	return res;
}

int LogDynAndXCopyArea_ex(wLog* log, const char* file, const char* fkt, size_t line,
                          Display* display, Pixmap src, Window dest, GC gc, int src_x, int src_y,
                          unsigned int width, unsigned int height, int dest_x, int dest_y)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		XWindowAttributes attr = WINPR_C_ARRAY_INIT;
		const Status rc = XGetWindowAttributes(display, dest, &attr);

		write_log(log, log_level, file, fkt, line,
		          "XCopyArea(%p, src: {%lu}, dest: [%d]{%lu, %lu, %d}, gc: {%p}, src_x: {%d}, "
		          "src_y: {%d}, "
		          "width: {%u}, "
		          "height: {%u}, dest_x: {%d}, dest_y: {%d})",
		          (void*)display, src, rc, dest, attr.root, attr.depth, (void*)gc, src_x, src_y,
		          width, height, dest_x, dest_y);
	}

	if ((width == 0) || (height == 0))
	{
		const DWORD lvl = WLOG_WARN;
		if (WLog_IsLevelActive(log, lvl))
			write_log(log, lvl, file, fkt, line, "XCopyArea(width=%u, height=%u) !", width, height);
		return Success;
	}

	const int rc = XCopyArea(display, src, dest, gc, src_x, src_y, width, height, dest_x, dest_y);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XCopyArea", rc);
}

int LogDynAndXPutImage_ex(wLog* log, const char* file, const char* fkt, size_t line,
                          Display* display, Drawable d, GC gc, XImage* image, int src_x, int src_y,
                          int dest_x, int dest_y, unsigned int width, unsigned int height)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line,
		          "XPutImage(%p, d: {%lu}, gc: {%p}, image: [%p]{%d}, src_x: {%d}, src_y: {%d}, "
		          "dest_x: {%d}, "
		          "dest_y: {%d}, width: {%u}, "
		          "height: {%u})",
		          (void*)display, d, (void*)gc, (void*)image, image ? image->depth : -1, src_x,
		          src_y, dest_x, dest_y, width, height);
	}

	if ((width == 0) || (height == 0))
	{
		const DWORD lvl = WLOG_WARN;
		if (WLog_IsLevelActive(log, lvl))
			write_log(log, lvl, file, fkt, line, "XPutImage(width=%u, height=%u) !", width, height);
		return Success;
	}

	const int rc = XPutImage(display, d, gc, image, src_x, src_y, dest_x, dest_y, width, height);
	return write_result_log_expect_success(log, WLOG_WARN, file, fkt, line, display, "XPutImage",
	                                       rc);
}

/* be careful here.
 * XSendEvent returns Status, but implementation always returns 1
 */
Status LogDynAndXSendEvent_ex(wLog* log, const char* file, const char* fkt, size_t line,
                              Display* display, Window w, int propagate, long event_mask,
                              XEvent* event_send)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line,
		          "XSendEvent(d: {%p}, w: {%lu}, propagate: {%d}, event_mask: {%ld}, "
		          "event_send: [%p]{TODO})",
		          (void*)display, w, propagate, event_mask, (void*)event_send);
	}

	const int rc = XSendEvent(display, w, propagate, event_mask, event_send);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSendEvent", rc);
}

int LogDynAndXFlush_ex(wLog* log, const char* file, const char* fkt, size_t line, Display* display)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XFlush(%p)", (void*)display);
	}

	const int rc = XFlush(display);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XFlush", rc);
}

Window LogDynAndXGetSelectionOwner_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                      Display* display, Atom selection)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		char* selectionstr = Safe_XGetAtomName(log, display, selection);
		write_log(log, log_level, file, fkt, line, "XGetSelectionOwner(%p, %s)", (void*)display,
		          selectionstr);
		XFree(selectionstr);
	}
	return XGetSelectionOwner(display, selection);
}

int LogDynAndXDestroyWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                               Display* display, Window window)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XDestroyWindow(%p, %lu)", (void*)display,
		          window);
	}
	const int rc = XDestroyWindow(display, window);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XDestroyWindow",
	                                   rc);
}

int LogDynAndXSync_ex(wLog* log, const char* file, const char* fkt, size_t line, Display* display,
                      Bool discard)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XSync(%p, %d)", (void*)display, discard);
	}
	const int rc = XSync(display, discard);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSync", rc);
}

int LogDynAndXChangeWindowAttributes_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                        Display* display, Window window, unsigned long valuemask,
                                        XSetWindowAttributes* attributes)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XChangeWindowAttributes(%p, %lu, 0x%08lu, %p)",
		          (void*)display, window, valuemask, (void*)attributes);
	}
	const int rc = XChangeWindowAttributes(display, window, valuemask, attributes);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display,
	                                   "XChangeWindowAttributes", rc);
}

int LogDynAndXSetTransientForHint_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                     Display* display, Window window, Window prop_window)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XSetTransientForHint(%p, %lu, %lu)",
		          (void*)display, window, prop_window);
	}
	const int rc = XSetTransientForHint(display, window, prop_window);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display,
	                                   "XSetTransientForHint", rc);
}

int LogDynAndXCloseDisplay_ex(wLog* log, const char* file, const char* fkt, size_t line,
                              Display* display)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XCloseDisplay(%p)", (void*)display);
	}
	const int rc = XCloseDisplay(display);
	return write_result_log_expect_success(log, WLOG_WARN, file, fkt, line, display,
	                                       "XCloseDisplay", rc);
}

XImage* LogDynAndXCreateImage_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                 Display* display, Visual* visual, unsigned int depth, int format,
                                 int offset, char* data, unsigned int width, unsigned int height,
                                 int bitmap_pad, int bytes_per_line)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XCreateImage(%p)", (void*)display);
	}
	return XCreateImage(display, visual, depth, format, offset, data, width, height, bitmap_pad,
	                    bytes_per_line);
}

Window LogDynAndXCreateWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                 Display* display, Window parent, int x, int y, unsigned int width,
                                 unsigned int height, unsigned int border_width, int depth,
                                 unsigned int c_class, Visual* visual, unsigned long valuemask,
                                 XSetWindowAttributes* attributes)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XCreateWindow(%p)", (void*)display);
	}
	return XCreateWindow(display, parent, x, y, width, height, border_width, depth, c_class, visual,
	                     valuemask, attributes);
}

GC LogDynAndXCreateGC_ex(wLog* log, const char* file, const char* fkt, size_t line,
                         Display* display, Drawable d, unsigned long valuemask, XGCValues* values)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XCreateGC(%p)", (void*)display);
	}
	return XCreateGC(display, d, valuemask, values);
}

int LogDynAndXFreeGC_ex(wLog* log, const char* file, const char* fkt, size_t line, Display* display,
                        GC gc)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XFreeGC(%p)", (void*)display);
	}
	const int rc = XFreeGC(display, gc);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XFreeGC", rc);
}

Pixmap LogDynAndXCreatePixmap_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                 Display* display, Drawable d, unsigned int width,
                                 unsigned int height, unsigned int depth)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XCreatePixmap(%p, 0x%08lu, %u, %u, %u)",
		          (void*)display, d, width, height, depth);
	}
	return XCreatePixmap(display, d, width, height, depth);
}

int LogDynAndXFreePixmap_ex(wLog* log, const char* file, const char* fkt, size_t line,
                            Display* display, Pixmap pixmap)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XFreePixmap(%p)", (void*)display);
	}
	const int rc = XFreePixmap(display, pixmap);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XFreePixmap", rc);
}

int LogDynAndXSetSelectionOwner_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                   Display* display, Atom selection, Window owner, Time time)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		char* selectionstr = Safe_XGetAtomName(log, display, selection);
		write_log(log, log_level, file, fkt, line, "XSetSelectionOwner(%p, %s, 0x%08lu, %lu)",
		          (void*)display, selectionstr, owner, time);
		XFree(selectionstr);
	}
	const int rc = XSetSelectionOwner(display, selection, owner, time);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display,
	                                   "XSetSelectionOwner", rc);
}

int LogDynAndXSetForeground_ex(wLog* log, const char* file, const char* fkt, size_t line,
                               Display* display, GC gc, unsigned long foreground)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XSetForeground(%p, %p, 0x%08lu)",
		          (void*)display, (void*)gc, foreground);
	}
	const int rc = XSetForeground(display, gc, foreground);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetForeground",
	                                   rc);
}

int LogDynAndXMoveWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                            Display* display, Window w, int x, int y)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XMoveWindow(%p, 0x%08lu, %d, %d)",
		          (void*)display, w, x, y);
	}
	const int rc = XMoveWindow(display, w, x, y);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XMoveWindow", rc);
}

int LogDynAndXSetFillStyle_ex(wLog* log, const char* file, const char* fkt, size_t line,
                              Display* display, GC gc, int fill_style)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XSetFillStyle(%p, %p, %d)", (void*)display,
		          (void*)gc, fill_style);
	}
	const int rc = XSetFillStyle(display, gc, fill_style);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetFillStyle",
	                                   rc);
}

int LogDynAndXSetFunction_ex(wLog* log, const char* file, const char* fkt, size_t line,
                             Display* display, GC gc, int function)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XSetFunction(%p, %p, %d)", (void*)display,
		          (void*)gc, function);
	}
	const int rc = XSetFunction(display, gc, function);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetFunction",
	                                   rc);
}

int LogDynAndXRaiseWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                             Display* display, Window w)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XRaiseWindow(%p, %lu)", (void*)display, w);
	}
	const int rc = XRaiseWindow(display, w);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XRaiseWindow",
	                                   rc);
}

int LogDynAndXMapWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                           Display* display, Window w)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XMapWindow(%p, %lu)", (void*)display, w);
	}
	const int rc = XMapWindow(display, w);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XMapWindow", rc);
}

int LogDynAndXUnmapWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                             Display* display, Window w)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XUnmapWindow(%p, %lu)", (void*)display, w);
	}
	const int rc = XUnmapWindow(display, w);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XUnmapWindow",
	                                   rc);
}

int LogDynAndXMoveResizeWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                  Display* display, Window w, int x, int y, unsigned int width,
                                  unsigned int height)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XMoveResizeWindow(%p, %lu, %d, %d, %u, %u)",
		          (void*)display, w, x, y, width, height);
	}
	const int rc = XMoveResizeWindow(display, w, x, y, width, height);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display,
	                                   "XMoveResizeWindow", rc);
}

Status LogDynAndXWithdrawWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                   Display* display, Window w, int screen_number)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XWithdrawWindow(%p, %lu, %d)", (void*)display,
		          w, screen_number);
	}
	const Status rc = XWithdrawWindow(display, w, screen_number);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XWithdrawWindow",
	                                   rc);
}

int LogDynAndXResizeWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                              Display* display, Window w, unsigned int width, unsigned int height)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XResizeWindow(%p, %lu, %u, %u)", (void*)display,
		          w, width, height);
	}
	const int rc = XResizeWindow(display, w, width, height);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XResizeWindow",
	                                   rc);
}

int LogDynAndXClearWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                             Display* display, Window w)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XClearWindow(%p, %lu)", (void*)display, w);
	}
	const int rc = XClearWindow(display, w);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XClearWindow",
	                                   rc);
}

int LogDynAndXSetBackground_ex(wLog* log, const char* file, const char* fkt, size_t line,
                               Display* display, GC gc, unsigned long background)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XSetBackground(%p, %p, %lu)", (void*)display,
		          (void*)gc, background);
	}
	const int rc = XSetBackground(display, gc, background);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetBackground",
	                                   rc);
}

int LogDynAndXSetClipMask_ex(wLog* log, const char* file, const char* fkt, size_t line,
                             Display* display, GC gc, Pixmap pixmap)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XSetClipMask(%p, %p, %lu)", (void*)display,
		          (void*)gc, pixmap);
	}
	const int rc = XSetClipMask(display, gc, pixmap);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetClipMask",
	                                   rc);
}

int LogDynAndXFillRectangle_ex(wLog* log, const char* file, const char* fkt, size_t line,
                               Display* display, Window w, GC gc, int x, int y, unsigned int width,
                               unsigned int height)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XFillRectangle(%p, %lu, %p, %d, %d, %u, %u)",
		          (void*)display, w, (void*)gc, x, y, width, height);
	}
	const int rc = XFillRectangle(display, w, gc, x, y, width, height);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XFillRectangle",
	                                   rc);
}

int LogDynAndXSetRegion_ex(wLog* log, const char* file, const char* fkt, size_t line,
                           Display* display, GC gc, Region r)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XSetRegion(%p, %p, %p)", (void*)display,
		          (void*)gc, (void*)r);
	}
	const int rc = XSetRegion(display, gc, r);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XSetRegion", rc);
}

int LogDynAndXReparentWindow_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                Display* display, Window w, Window parent, int x, int y)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XReparentWindow(%p, %lu, %lu, %d, %d)",
		          (void*)display, w, parent, x, y);
	}
	const int rc = XReparentWindow(display, w, parent, x, y);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XReparentWindow",
	                                   rc);
}

char* getConfigOption(BOOL system, const char* option)
{
	char* res = nullptr;
	WINPR_JSON* file = freerdp_GetJSONConfigFile(system, "xfreerdp.json");
	if (!file)
		return nullptr;

	WINPR_JSON* obj = WINPR_JSON_GetObjectItemCaseSensitive(file, option);
	if (obj)
	{
		const char* val = WINPR_JSON_GetStringValue(obj);
		if (val)
			res = _strdup(val);
	}
	WINPR_JSON_Delete(file);

	return res;
}

int LogDynAndXRestackWindows_ex(wLog* log, const char* file, const char* fkt, size_t line,
                                Display* display, Window* windows, int nwindows)
{
	if (WLog_IsLevelActive(log, log_level))
	{
		write_log(log, log_level, file, fkt, line, "XRestackWindows(%p, %p, %d)", (void*)display,
		          (const void*)windows, nwindows);
	}
	const int rc = XRestackWindows(display, windows, nwindows);
	return write_result_log_expect_one(log, WLOG_WARN, file, fkt, line, display, "XRestackWindows",
	                                   rc);
}
