Bug fix: Issue #36: Chromium window painting problems
More descriptions on issue #36. - Listens ShapeNotify event to get around the Chromium window painting issues. - Adds dependency on X Shape extension. - Adds a few functions for convenience, so a bit code clean up. - Better event debug support, adds restack_win() debug.
This commit is contained in:
parent
8628371a83
commit
f5aed89a67
2
Makefile
2
Makefile
|
@ -4,7 +4,7 @@ PREFIX ?= /usr
|
||||||
BINDIR ?= $(PREFIX)/bin
|
BINDIR ?= $(PREFIX)/bin
|
||||||
MANDIR ?= $(PREFIX)/share/man/man1
|
MANDIR ?= $(PREFIX)/share/man/man1
|
||||||
|
|
||||||
PACKAGES = x11 xcomposite xfixes xdamage xrender
|
PACKAGES = x11 xcomposite xfixes xdamage xrender xext
|
||||||
LIBS = $(shell pkg-config --libs $(PACKAGES)) -lm
|
LIBS = $(shell pkg-config --libs $(PACKAGES)) -lm
|
||||||
INCS = $(shell pkg-config --cflags $(PACKAGES))
|
INCS = $(shell pkg-config --cflags $(PACKAGES))
|
||||||
CFLAGS += -Wall
|
CFLAGS += -Wall
|
||||||
|
|
|
@ -43,6 +43,7 @@ __R__ for runtime
|
||||||
* libxcomposite (B,R)
|
* libxcomposite (B,R)
|
||||||
* libxdamage (B,R)
|
* libxdamage (B,R)
|
||||||
* libxfixes (B,R)
|
* libxfixes (B,R)
|
||||||
|
* libXext (B,R)
|
||||||
* libxrender (B,R)
|
* libxrender (B,R)
|
||||||
* pkg-config (B)
|
* pkg-config (B)
|
||||||
* make (B)
|
* make (B)
|
||||||
|
|
229
src/compton.c
229
src/compton.c
|
@ -10,6 +10,10 @@
|
||||||
|
|
||||||
#include "compton.h"
|
#include "compton.h"
|
||||||
|
|
||||||
|
#if DEBUG_EVENTS
|
||||||
|
static int window_get_name(Window w, char **name);
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared
|
* Shared
|
||||||
*/
|
*/
|
||||||
|
@ -36,6 +40,10 @@ ignore *ignore_head, **ignore_tail = &ignore_head;
|
||||||
int xfixes_event, xfixes_error;
|
int xfixes_event, xfixes_error;
|
||||||
int damage_event, damage_error;
|
int damage_event, damage_error;
|
||||||
int composite_event, composite_error;
|
int composite_event, composite_error;
|
||||||
|
/// Whether X Shape extension exists.
|
||||||
|
Bool shape_exists = True;
|
||||||
|
/// Event base number and error base number for X Shape extension.
|
||||||
|
int shape_event, shape_error;
|
||||||
int render_event, render_error;
|
int render_event, render_error;
|
||||||
int composite_opcode;
|
int composite_opcode;
|
||||||
|
|
||||||
|
@ -954,12 +962,7 @@ paint_all(Display *dpy, XserverRegion region) {
|
||||||
win *t = 0;
|
win *t = 0;
|
||||||
|
|
||||||
if (!region) {
|
if (!region) {
|
||||||
XRectangle r;
|
region = get_screen_region(dpy);
|
||||||
r.x = 0;
|
|
||||||
r.y = 0;
|
|
||||||
r.width = root_width;
|
|
||||||
r.height = root_height;
|
|
||||||
region = XFixesCreateRegion(dpy, &r, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MONITOR_REPAINT
|
#if MONITOR_REPAINT
|
||||||
|
@ -1029,11 +1032,7 @@ paint_all(Display *dpy, XserverRegion region) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (clip_changed) {
|
if (clip_changed) {
|
||||||
if (w->border_size) {
|
win_free_border_size(dpy, w);
|
||||||
set_ignore(dpy, NextRequest(dpy));
|
|
||||||
XFixesDestroyRegion(dpy, w->border_size);
|
|
||||||
w->border_size = None;
|
|
||||||
}
|
|
||||||
if (w->extents) {
|
if (w->extents) {
|
||||||
XFixesDestroyRegion(dpy, w->extents);
|
XFixesDestroyRegion(dpy, w->extents);
|
||||||
w->extents = None;
|
w->extents = None;
|
||||||
|
@ -1376,6 +1375,10 @@ map_win(Display *dpy, Window id,
|
||||||
so that no property changes are lost */
|
so that no property changes are lost */
|
||||||
if (!override_redirect) {
|
if (!override_redirect) {
|
||||||
XSelectInput(dpy, id, PropertyChangeMask | FocusChangeMask);
|
XSelectInput(dpy, id, PropertyChangeMask | FocusChangeMask);
|
||||||
|
// Notify compton when the shape of a window changes
|
||||||
|
if (shape_exists) {
|
||||||
|
XShapeSelectInput(dpy, id, ShapeNotifyMask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this causes problems for inactive transparency
|
// this causes problems for inactive transparency
|
||||||
|
@ -1429,11 +1432,7 @@ finish_unmap_win(Display *dpy, win *w) {
|
||||||
w->picture = None;
|
w->picture = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w->border_size) {
|
win_free_border_size(dpy, w);
|
||||||
set_ignore(dpy, NextRequest(dpy));
|
|
||||||
XFixesDestroyRegion(dpy, w->border_size);
|
|
||||||
w->border_size = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (w->shadow) {
|
if (w->shadow) {
|
||||||
XRenderFreePicture(dpy, w->shadow);
|
XRenderFreePicture(dpy, w->shadow);
|
||||||
|
@ -1696,6 +1695,33 @@ restack_win(Display *dpy, win *w, Window new_above) {
|
||||||
|
|
||||||
w->next = *prev;
|
w->next = *prev;
|
||||||
*prev = w;
|
*prev = w;
|
||||||
|
|
||||||
|
#if DEBUG_RESTACK
|
||||||
|
{
|
||||||
|
const char *desc;
|
||||||
|
char *window_name;
|
||||||
|
Bool to_free;
|
||||||
|
win* c = list;
|
||||||
|
|
||||||
|
printf("restack_win(%#010lx, %#010lx): Window stack modified. Current stack:\n", w->id, new_above);
|
||||||
|
for (; c; c = c->next) {
|
||||||
|
window_name = "(Failed to get title)";
|
||||||
|
if (root == c->id)
|
||||||
|
window_name = "(Root window)";
|
||||||
|
else
|
||||||
|
to_free = window_get_name(c->id, &window_name);
|
||||||
|
desc = "";
|
||||||
|
if (c->destroyed)
|
||||||
|
desc = "(D) ";
|
||||||
|
printf("%#010lx \"%s\" %s-> ", c->id, window_name, desc);
|
||||||
|
if (to_free) {
|
||||||
|
XFree(window_name);
|
||||||
|
window_name = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fputs("\n", stdout);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1961,6 +1987,60 @@ error(Display *dpy, XErrorEvent *ev) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (ev->error_code) {
|
||||||
|
case BadAccess:
|
||||||
|
name = "BadAccess";
|
||||||
|
break;
|
||||||
|
case BadAlloc:
|
||||||
|
name = "BadAlloc";
|
||||||
|
break;
|
||||||
|
case BadAtom:
|
||||||
|
name = "BadAtom";
|
||||||
|
break;
|
||||||
|
case BadColor:
|
||||||
|
name = "BadColor";
|
||||||
|
break;
|
||||||
|
case BadCursor:
|
||||||
|
name = "BadCursor";
|
||||||
|
break;
|
||||||
|
case BadDrawable:
|
||||||
|
name = "BadDrawable";
|
||||||
|
break;
|
||||||
|
case BadFont:
|
||||||
|
name = "BadFont";
|
||||||
|
break;
|
||||||
|
case BadGC:
|
||||||
|
name = "BadGC";
|
||||||
|
break;
|
||||||
|
case BadIDChoice:
|
||||||
|
name = "BadIDChoice";
|
||||||
|
break;
|
||||||
|
case BadImplementation:
|
||||||
|
name = "BadImplementation";
|
||||||
|
break;
|
||||||
|
case BadLength:
|
||||||
|
name = "BadLength";
|
||||||
|
break;
|
||||||
|
case BadMatch:
|
||||||
|
name = "BadMatch";
|
||||||
|
break;
|
||||||
|
case BadName:
|
||||||
|
name = "BadName";
|
||||||
|
break;
|
||||||
|
case BadPixmap:
|
||||||
|
name = "BadPixmap";
|
||||||
|
break;
|
||||||
|
case BadRequest:
|
||||||
|
name = "BadRequest";
|
||||||
|
break;
|
||||||
|
case BadValue:
|
||||||
|
name = "BadValue";
|
||||||
|
break;
|
||||||
|
case BadWindow:
|
||||||
|
name = "BadWindow";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
printf("error %d (%s) request %d minor %d serial %lu\n",
|
printf("error %d (%s) request %d minor %d serial %lu\n",
|
||||||
ev->error_code, name, ev->request_code,
|
ev->error_code, name, ev->request_code,
|
||||||
ev->minor_code, ev->serial);
|
ev->minor_code, ev->serial);
|
||||||
|
@ -1974,10 +2054,36 @@ expose_root(Display *dpy, Window root, XRectangle *rects, int nrects) {
|
||||||
add_damage(dpy, region);
|
add_damage(dpy, region);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG_EVENTS
|
#if DEBUG_EVENTS || DEBUG_RESTACK
|
||||||
|
static int window_get_name(Window w, char **name) {
|
||||||
|
Atom prop = XInternAtom(dpy, "_NET_WM_NAME", False);
|
||||||
|
Atom utf8_type = XInternAtom(dpy, "UTF8_STRING", False);
|
||||||
|
Atom actual_type;
|
||||||
|
int actual_format;
|
||||||
|
unsigned long nitems;
|
||||||
|
unsigned long leftover;
|
||||||
|
char *data = NULL;
|
||||||
|
Status ret;
|
||||||
|
|
||||||
|
set_ignore(dpy, NextRequest(dpy));
|
||||||
|
if (Success != (ret = XGetWindowProperty(dpy, w, prop, 0L, (long) BUFSIZ,
|
||||||
|
False, utf8_type, &actual_type, &actual_format, &nitems,
|
||||||
|
&leftover, (unsigned char **) &data))) {
|
||||||
|
if (BadWindow == ret)
|
||||||
|
return 0;
|
||||||
|
set_ignore(dpy, NextRequest(dpy));
|
||||||
|
printf("Window %#010lx: _NET_WM_NAME unset, falling back to WM_NAME.\n", w);
|
||||||
|
if (!XFetchName(dpy, w, &data))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// if (actual_type == utf8_type && actual_format == 8)
|
||||||
|
*name = (char *) data;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
ev_serial(XEvent *ev) {
|
ev_serial(XEvent *ev) {
|
||||||
if (ev->type & 0x7f != KeymapNotify) {
|
if ((ev->type & 0x7f) != KeymapNotify) {
|
||||||
return ev->xany.serial;
|
return ev->xany.serial;
|
||||||
}
|
}
|
||||||
return NextRequest(ev->xany.display);
|
return NextRequest(ev->xany.display);
|
||||||
|
@ -1987,8 +2093,16 @@ static char *
|
||||||
ev_name(XEvent *ev) {
|
ev_name(XEvent *ev) {
|
||||||
static char buf[128];
|
static char buf[128];
|
||||||
switch (ev->type & 0x7f) {
|
switch (ev->type & 0x7f) {
|
||||||
case Expose:
|
case FocusIn:
|
||||||
return "Expose";
|
return "FocusIn";
|
||||||
|
case FocusOut:
|
||||||
|
return "FocusOut";
|
||||||
|
case CreateNotify:
|
||||||
|
return "CreateNotify";
|
||||||
|
case ConfigureNotify:
|
||||||
|
return "ConfigureNotify";
|
||||||
|
case DestroyNotify:
|
||||||
|
return "DestroyNotify";
|
||||||
case MapNotify:
|
case MapNotify:
|
||||||
return "Map";
|
return "Map";
|
||||||
case UnmapNotify:
|
case UnmapNotify:
|
||||||
|
@ -1997,10 +2111,16 @@ ev_name(XEvent *ev) {
|
||||||
return "Reparent";
|
return "Reparent";
|
||||||
case CirculateNotify:
|
case CirculateNotify:
|
||||||
return "Circulate";
|
return "Circulate";
|
||||||
|
case Expose:
|
||||||
|
return "Expose";
|
||||||
|
case PropertyNotify:
|
||||||
|
return "PropertyNotify";
|
||||||
default:
|
default:
|
||||||
if (ev->type == damage_event + XDamageNotify) {
|
if (ev->type == damage_event + XDamageNotify) {
|
||||||
return "Damage";
|
return "Damage";
|
||||||
}
|
}
|
||||||
|
if (shape_exists && ev->type == shape_event)
|
||||||
|
return "ShapeNotify";
|
||||||
sprintf(buf, "Event %d", ev->type);
|
sprintf(buf, "Event %d", ev->type);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
@ -2009,8 +2129,13 @@ ev_name(XEvent *ev) {
|
||||||
static Window
|
static Window
|
||||||
ev_window(XEvent *ev) {
|
ev_window(XEvent *ev) {
|
||||||
switch (ev->type) {
|
switch (ev->type) {
|
||||||
case Expose:
|
case FocusIn:
|
||||||
return ev->xexpose.window;
|
case FocusOut:
|
||||||
|
return ev->xfocus.window;
|
||||||
|
case CreateNotify:
|
||||||
|
return ev->xcreatewindow.window;
|
||||||
|
case ConfigureNotify:
|
||||||
|
return ev->xconfigure.window;
|
||||||
case MapNotify:
|
case MapNotify:
|
||||||
return ev->xmap.window;
|
return ev->xmap.window;
|
||||||
case UnmapNotify:
|
case UnmapNotify:
|
||||||
|
@ -2019,10 +2144,16 @@ ev_window(XEvent *ev) {
|
||||||
return ev->xreparent.window;
|
return ev->xreparent.window;
|
||||||
case CirculateNotify:
|
case CirculateNotify:
|
||||||
return ev->xcirculate.window;
|
return ev->xcirculate.window;
|
||||||
|
case Expose:
|
||||||
|
return ev->xexpose.window;
|
||||||
|
case PropertyNotify:
|
||||||
|
return ev->xproperty.window;
|
||||||
default:
|
default:
|
||||||
if (ev->type == damage_event + XDamageNotify) {
|
if (ev->type == damage_event + XDamageNotify) {
|
||||||
return ((XDamageNotifyEvent *)ev)->drawable;
|
return ((XDamageNotifyEvent *)ev)->drawable;
|
||||||
}
|
}
|
||||||
|
if (shape_exists && ev->type == shape_event)
|
||||||
|
return ((XShapeEvent *) ev)->window;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2068,6 +2199,9 @@ ev_create_notify(XCreateWindowEvent *ev) {
|
||||||
|
|
||||||
inline static void
|
inline static void
|
||||||
ev_configure_notify(XConfigureEvent *ev) {
|
ev_configure_notify(XConfigureEvent *ev) {
|
||||||
|
#if DEBUG_EVENTS
|
||||||
|
printf("{ send_event: %d, above: %#010lx, override_redirect: %d }\n", ev->send_event, ev->above, ev->override_redirect);
|
||||||
|
#endif
|
||||||
configure_win(dpy, ev);
|
configure_win(dpy, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2168,16 +2302,56 @@ ev_damage_notify(XDamageNotifyEvent *ev) {
|
||||||
damage_win(dpy, ev);
|
damage_win(dpy, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ev_shape_notify(XShapeEvent *ev) {
|
||||||
|
win *w = find_win(dpy, ev->window);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Empty border_size may indicated an
|
||||||
|
* unmapped/destroyed window, in which case
|
||||||
|
* seemingly BadRegion errors would be triggered
|
||||||
|
* if we attempt to rebuild border_size
|
||||||
|
*/
|
||||||
|
if (w->border_size) {
|
||||||
|
// Mark the old border_size as damaged
|
||||||
|
add_damage(dpy, w->border_size);
|
||||||
|
|
||||||
|
w->border_size = border_size(dpy, w);
|
||||||
|
|
||||||
|
// Mark the new border_size as damaged
|
||||||
|
add_damage(dpy, copy_region(dpy, w->border_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
inline static void
|
inline static void
|
||||||
ev_handle(XEvent *ev) {
|
ev_handle(XEvent *ev) {
|
||||||
|
|
||||||
|
#if DEBUG_EVENTS
|
||||||
|
Window w;
|
||||||
|
char *window_name;
|
||||||
|
Bool to_free = False;
|
||||||
|
#endif
|
||||||
|
|
||||||
if ((ev->type & 0x7f) != KeymapNotify) {
|
if ((ev->type & 0x7f) != KeymapNotify) {
|
||||||
discard_ignore(dpy, ev->xany.serial);
|
discard_ignore(dpy, ev->xany.serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG_EVENTS
|
#if DEBUG_EVENTS
|
||||||
|
w = ev_window(ev);
|
||||||
|
window_name = "(Failed to get title)";
|
||||||
|
if (w) {
|
||||||
|
if (root == w)
|
||||||
|
window_name = "(Root window)";
|
||||||
|
else
|
||||||
|
to_free = (Bool) window_get_name(w, &window_name);
|
||||||
|
}
|
||||||
if (ev->type != damage_event + XDamageNotify) {
|
if (ev->type != damage_event + XDamageNotify) {
|
||||||
printf("event %10.10s serial 0x%08x window 0x%08x\n",
|
printf("event %10.10s serial %#010x window %#010lx \"%s\"\n",
|
||||||
ev_name(ev), ev_serial(ev), ev_window(ev));
|
ev_name(ev), ev_serial(ev), w, window_name);
|
||||||
|
}
|
||||||
|
if (to_free) {
|
||||||
|
XFree(window_name);
|
||||||
|
window_name = NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -2216,6 +2390,10 @@ ev_handle(XEvent *ev) {
|
||||||
ev_property_notify((XPropertyEvent *)ev);
|
ev_property_notify((XPropertyEvent *)ev);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
if (shape_exists && ev->type == shape_event) {
|
||||||
|
ev_shape_notify((XShapeEvent *) ev);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (ev->type == damage_event + XDamageNotify) {
|
if (ev->type == damage_event + XDamageNotify) {
|
||||||
ev_damage_notify((XDamageNotifyEvent *)ev);
|
ev_damage_notify((XDamageNotifyEvent *)ev);
|
||||||
}
|
}
|
||||||
|
@ -2528,6 +2706,9 @@ main(int argc, char **argv) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!XShapeQueryExtension(dpy, &shape_event, &shape_error))
|
||||||
|
shape_exists = False;
|
||||||
|
|
||||||
register_cm(scr);
|
register_cm(scr);
|
||||||
|
|
||||||
if (fork_after_register) fork_after();
|
if (fork_after_register) fork_after();
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <X11/extensions/Xcomposite.h>
|
#include <X11/extensions/Xcomposite.h>
|
||||||
#include <X11/extensions/Xdamage.h>
|
#include <X11/extensions/Xdamage.h>
|
||||||
#include <X11/extensions/Xrender.h>
|
#include <X11/extensions/Xrender.h>
|
||||||
|
#include <X11/extensions/shape.h>
|
||||||
|
|
||||||
#if COMPOSITE_MAJOR > 0 || COMPOSITE_MINOR >= 2
|
#if COMPOSITE_MAJOR > 0 || COMPOSITE_MINOR >= 2
|
||||||
#define HAS_NAME_WINDOW_PIXMAP 1
|
#define HAS_NAME_WINDOW_PIXMAP 1
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
#define CAN_DO_USABLE 0
|
#define CAN_DO_USABLE 0
|
||||||
#define DEBUG_REPAINT 0
|
#define DEBUG_REPAINT 0
|
||||||
#define DEBUG_EVENTS 0
|
#define DEBUG_EVENTS 0
|
||||||
|
#define DEBUG_RESTACK 0
|
||||||
#define DEBUG_WINTYPE 0
|
#define DEBUG_WINTYPE 0
|
||||||
#define MONITOR_REPAINT 0
|
#define MONITOR_REPAINT 0
|
||||||
|
|
||||||
|
@ -124,6 +126,7 @@ typedef struct _fade {
|
||||||
Display *dpy;
|
Display *dpy;
|
||||||
} fade;
|
} fade;
|
||||||
|
|
||||||
|
extern int root_height, root_width;
|
||||||
/**
|
/**
|
||||||
* Functions
|
* Functions
|
||||||
*/
|
*/
|
||||||
|
@ -345,6 +348,42 @@ ev_property_notify(XPropertyEvent *ev);
|
||||||
inline static void
|
inline static void
|
||||||
ev_damage_notify(XDamageNotifyEvent *ev);
|
ev_damage_notify(XDamageNotifyEvent *ev);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destory the cached border_size of a window.
|
||||||
|
*/
|
||||||
|
inline static void win_free_border_size(Display *dpy, win *w) {
|
||||||
|
if (w->border_size) {
|
||||||
|
set_ignore(dpy, NextRequest(dpy));
|
||||||
|
XFixesDestroyRegion(dpy, w->border_size);
|
||||||
|
w->border_size = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a region of the screen size.
|
||||||
|
*/
|
||||||
|
inline static XserverRegion get_screen_region(Display *dpy) {
|
||||||
|
XRectangle r;
|
||||||
|
|
||||||
|
r.x = 0;
|
||||||
|
r.y = 0;
|
||||||
|
r.width = root_width;
|
||||||
|
r.height = root_height;
|
||||||
|
return XFixesCreateRegion(dpy, &r, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies a region
|
||||||
|
*/
|
||||||
|
inline static XserverRegion copy_region(Display *dpy,
|
||||||
|
XserverRegion oldregion) {
|
||||||
|
XserverRegion region = XFixesCreateRegion(dpy, NULL, 0);
|
||||||
|
|
||||||
|
XFixesCopyRegion(dpy, region, oldregion);
|
||||||
|
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
inline static void
|
inline static void
|
||||||
ev_handle(XEvent *ev);
|
ev_handle(XEvent *ev);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue