diff --git a/mtk/meson.build b/mtk/meson.build index f7862f378..e86f2a01f 100644 --- a/mtk/meson.build +++ b/mtk/meson.build @@ -32,6 +32,10 @@ mtk_pkg_deps = [ graphene_dep, ] +if have_x11 + mtk_pkg_deps += x11_dep +endif + mtk_deps = [ mtk_pkg_deps, m_dep diff --git a/mtk/mtk/meson.build b/mtk/mtk/meson.build index 648e6d99b..8dca0558e 100644 --- a/mtk/mtk/meson.build +++ b/mtk/mtk/meson.build @@ -10,6 +10,14 @@ mtk_sources = [ 'mtk-rectangle.c', ] +if have_x11 + mtk_sources += 'mtk-x11-errors.c' + mtk_headers += [ + 'mtk-x11-errors.h', + 'mtk-x11.h', + ] +endif + mtk_private_headers = [ ] diff --git a/mtk/mtk/mtk-x11-errors.c b/mtk/mtk/mtk-x11-errors.c new file mode 100644 index 000000000..9b84a398f --- /dev/null +++ b/mtk/mtk/mtk-x11-errors.c @@ -0,0 +1,318 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2001 Havoc Pennington, error trapping inspired by GDK + * code copyrighted by the GTK team. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/** + * Errors: + * + * Mutter X error handling + */ + +#include "mtk/mtk-x11-errors.h" + +#include +#include +#include +#include + +/* compare X sequence numbers handling wraparound */ +#define SEQUENCE_COMPARE(a,op,b) (((long) (a) - (long) (b)) op 0) + +typedef struct _MtkErrorTrap +{ + /* Next sequence when trap was pushed, i.e. first sequence to + * ignore + */ + unsigned long start_sequence; + + /* Next sequence when trap was popped, i.e. first sequence + * to not ignore. 0 if trap is still active. + */ + unsigned long end_sequence; + + /* Most recent error code within the sequence */ + int error_code; +} MtkErrorTrap; + +/* Previously existing error handler */ +typedef int (* MtkXErrorHandler) (Display *, XErrorEvent *); +static MtkXErrorHandler old_error_handler = NULL; +/* number of times we've pushed the error handler */ +static int error_handler_push_count = 0; +static GList *error_traps = NULL; + +/* look up the extension name for a given major opcode. grubs around in + * xlib to do it since a) it’s already cached there b) XQueryExtension + * emits protocol so we can’t use it in an error handler. + */ +static const char * +decode_request_code (Display *xdisplay, + int code) +{ + _XExtension *ext; + + if (code < 128) + return "core protocol"; + + for (ext = xdisplay->ext_procs; ext; ext = ext->next) + { + if (ext->codes.major_opcode == code) + return ext->name; + } + + return "unknown"; +} + +static void +display_error_event (Display *xdisplay, + XErrorEvent *error) +{ + GList *l; + gboolean ignore = FALSE; + + for (l = error_traps; l; l = l->next) + { + MtkErrorTrap *trap; + + trap = l->data; + + if (SEQUENCE_COMPARE (trap->start_sequence, <=, error->serial) && + (trap->end_sequence == 0 || + SEQUENCE_COMPARE (trap->end_sequence, >, error->serial))) + { + ignore = TRUE; + trap->error_code = error->error_code; + break; /* only innermost trap gets the error code */ + } + } + + if (!ignore) + { + char buf[64]; + + XGetErrorText (xdisplay, error->error_code, buf, 63); + + g_error ("Received an X Window System error.\n" + "This probably reflects a bug in the program.\n" + "The error was '%s'.\n" + " (Details: serial %ld error_code %d request_code %d (%s) minor_code %d)\n" + " (Note to programmers: normally, X errors are reported asynchronously;\n" + " that is, you will receive the error a while after causing it.\n" + " To debug your program, run it with the MUTTER_SYNC environment\n" + " variable to change this behavior. You can then get a meaningful\n" + " backtrace from your debugger if you break on the mtk_x_error() function.)", + buf, + error->serial, + error->error_code, + error->request_code, + decode_request_code (xdisplay, error->request_code), + error->minor_code); + } +} + +static int +mtk_x_error (Display *xdisplay, + XErrorEvent *error) +{ + if (error->error_code) + display_error_event (xdisplay, error); + + return 0; +} + +static void +error_handler_push (void) +{ + MtkXErrorHandler previous_handler; + + previous_handler = XSetErrorHandler (mtk_x_error); + + if (error_handler_push_count > 0) + { + if (previous_handler != mtk_x_error) + g_warning ("XSetErrorHandler() called with a Mutter X11 error trap pushed. Don't do that."); + } + else + { + old_error_handler = previous_handler; + } + + error_handler_push_count += 1; +} + +static void +error_handler_pop (void) +{ + g_return_if_fail (error_handler_push_count > 0); + + error_handler_push_count -= 1; + + if (error_handler_push_count == 0) + { + XSetErrorHandler (old_error_handler); + old_error_handler = NULL; + } +} + +static void +delete_outdated_error_traps (Display *xdisplay) +{ + GList *l; + unsigned long processed_sequence; + + processed_sequence = XLastKnownRequestProcessed (xdisplay); + l = error_traps; + + while (l != NULL) + { + MtkErrorTrap *trap = l->data; + + if (trap->end_sequence != 0 && + SEQUENCE_COMPARE (trap->end_sequence, <=, processed_sequence)) + { + GList *link = l; + + l = l->next; + error_traps = g_list_delete_link (error_traps, link); + g_free (trap); + } + else + { + l = l->next; + } + } +} + +/** + * mtk_x11_errors_init: (skip): + */ +void +mtk_x11_errors_init (void) +{ + XSetErrorHandler (mtk_x_error); +} + +/** + * mtk_x11_errors_destroy: (skip): + */ +void +mtk_x11_errors_deinit (void) +{ + g_clear_list (&error_traps, g_free); + XSetErrorHandler (NULL); +} + +/** + * mtk_x11_error_trap_push: (skip): + */ +void +mtk_x11_error_trap_push (Display *xdisplay) +{ + MtkErrorTrap *trap; + + delete_outdated_error_traps (xdisplay); + + /* set up the Xlib callback to tell us about errors */ + error_handler_push (); + + trap = g_new0 (MtkErrorTrap, 1); + trap->start_sequence = XNextRequest (xdisplay); + trap->error_code = Success; + + error_traps = g_list_prepend (error_traps, trap); +} + +static int +mtk_x11_error_trap_pop_internal (Display *xdisplay, + gboolean need_code) +{ + MtkErrorTrap *trap = NULL; + GList *l; + int result; + + g_return_val_if_fail (error_traps != NULL, Success); + + /* Find the first trap that hasn't been popped already */ + for (l = error_traps; l; l = l->next) + { + trap = l->data; + + if (trap->end_sequence == 0) + break; + } + + g_return_val_if_fail (trap != NULL, Success); + g_assert (trap->end_sequence == 0); + + /* May need to sync to fill in trap->error_code if we care about + * getting an error code. + */ + if (need_code) + { + unsigned long processed_sequence, next_sequence; + + next_sequence = XNextRequest (xdisplay); + processed_sequence = XLastKnownRequestProcessed (xdisplay); + + /* If our last request was already processed, there is no point + * in syncing. i.e. if last request was a round trip (or even if + * we got an event with the serial of a non-round-trip) + */ + if ((next_sequence - 1) != processed_sequence) + { + XSync (xdisplay, False); + } + + result = trap->error_code; + } + else + { + result = Success; + } + + /* record end of trap, giving us a range of + * error sequences we'll ignore. + */ + trap->end_sequence = XNextRequest (xdisplay); + + /* remove the Xlib callback */ + error_handler_pop (); + + /* we may already be outdated */ + delete_outdated_error_traps (xdisplay); + + return result; +} + +/** + * mtk_x11_error_trap_pop: (skip): + */ +void +mtk_x11_error_trap_pop (Display *xdisplay) +{ + mtk_x11_error_trap_pop_internal (xdisplay, FALSE); +} + +/** + * mtk_x11_error_trap_pop_with_return: (skip): + */ +int +mtk_x11_error_trap_pop_with_return (Display *xdisplay) +{ + return mtk_x11_error_trap_pop_internal (xdisplay, TRUE); +} diff --git a/mtk/mtk/mtk-x11-errors.h b/mtk/mtk/mtk-x11-errors.h new file mode 100644 index 000000000..0d742be01 --- /dev/null +++ b/mtk/mtk/mtk-x11-errors.h @@ -0,0 +1,41 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Mutter X error handling */ + +/* + * Copyright (C) 2001 Havoc Pennington + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#pragma once + +#include "mtk/mtk-macros.h" + +#include + +MTK_EXPORT +void mtk_x11_errors_init (void); + +MTK_EXPORT +void mtk_x11_errors_deinit (void); + +MTK_EXPORT +void mtk_x11_error_trap_push (Display *xdisplay); + +MTK_EXPORT +void mtk_x11_error_trap_pop (Display *xdisplay); + +MTK_EXPORT +int mtk_x11_error_trap_pop_with_return (Display *xdisplay); diff --git a/mtk/mtk/mtk-x11.h b/mtk/mtk/mtk-x11.h new file mode 100644 index 000000000..beec31d13 --- /dev/null +++ b/mtk/mtk/mtk-x11.h @@ -0,0 +1,29 @@ +/* + * Mtk + * + * A low-level base library. + * + * Copyright (C) 2023 Red Hat + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#define __MTK_H_INSIDE__ + +#include "mtk/mtk-macros.h" +#include "mtk/mtk-x11-errors.h" + +#undef __MTK_H_INSIDE__