2018-10-04 05:14:51 +08:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Copyright (c)
|
2012-02-27 12:00:12 +08:00
|
|
|
|
|
|
|
// Throw everything in here.
|
|
|
|
|
Feature: Issue #29: Alternative shadow blacklist implementation
- Add shadow blacklist feature, but a different implementation from
nicklan's. 5 matching modes (exact, starts-with, contains, wildcard,
PCRE) and 3 matching targets (window name, window class instance,
window general class). Not extensively tested, bugs to be expected.
It's slower for exact matching than nicklan's as it uses linear search
instead of hash table. Also, PCRE's JIT optimization may cause issues
on PaX kernels.
- Add dependency to libpcre. Could be made optional if we have a
graceful way to handle that in Makefile.
- Some matching functions are GNU extensions of glibc. So this version
may have troubles running on platforms not using glibc.
- Fix a bug that access freed memory blocks in set_fade_callcack() and
check_fade_fin(). valgrind found it out.
- Use WM_CLASS to detect client windows instead of WM_STATE. Some client
windows (like notification windows) have WM_CLASS but not WM_STATE.
- Mark the extents as damaged if shadow state changed in
determine_shadow().
- Rewrite wid_get_name(). Code clean-up.
- Two debugging options: DEBUG_WINDATA and DEBUG_WINMATCH.
- As the matching system is ready, it should be rather easy to add other
kinds of blacklists, like fading blacklist.
2012-09-22 11:42:39 +08:00
|
|
|
|
|
|
|
// === Includes ===
|
|
|
|
|
2012-02-08 18:31:39 +08:00
|
|
|
#include <math.h>
|
2013-01-11 21:31:02 +08:00
|
|
|
#include <sys/select.h>
|
|
|
|
#include <limits.h>
|
2012-02-08 18:31:39 +08:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <getopt.h>
|
Feature: Issue #29: Alternative shadow blacklist implementation
- Add shadow blacklist feature, but a different implementation from
nicklan's. 5 matching modes (exact, starts-with, contains, wildcard,
PCRE) and 3 matching targets (window name, window class instance,
window general class). Not extensively tested, bugs to be expected.
It's slower for exact matching than nicklan's as it uses linear search
instead of hash table. Also, PCRE's JIT optimization may cause issues
on PaX kernels.
- Add dependency to libpcre. Could be made optional if we have a
graceful way to handle that in Makefile.
- Some matching functions are GNU extensions of glibc. So this version
may have troubles running on platforms not using glibc.
- Fix a bug that access freed memory blocks in set_fade_callcack() and
check_fade_fin(). valgrind found it out.
- Use WM_CLASS to detect client windows instead of WM_STATE. Some client
windows (like notification windows) have WM_CLASS but not WM_STATE.
- Mark the extents as damaged if shadow state changed in
determine_shadow().
- Rewrite wid_get_name(). Code clean-up.
- Two debugging options: DEBUG_WINDATA and DEBUG_WINMATCH.
- As the matching system is ready, it should be rather easy to add other
kinds of blacklists, like fading blacklist.
2012-09-22 11:42:39 +08:00
|
|
|
#include <locale.h>
|
2012-11-19 09:46:07 +08:00
|
|
|
#include <signal.h>
|
2012-09-25 10:19:20 +08:00
|
|
|
|
Feature: #7: VSync
- Add VSync feature. 3 possible VSync methods available: "sw" (software,
not too reliable, but at least you have something to fallback to),
"drm" (using DRM_IOCTL_WAIT_VBLANK, should work only on DRI drivers),
"opengl" (using SGI_swap_control extension OpenGL, might work on more
drivers than the DRM method). "sw" and "opengl" are briefly tested,
"drm" received utterly no test (because I use the nVidia binary blob).
They are enabled with "--vsync sw" / "--vsync drm" / "--vsync opengl".
- Add --refresh-rate to let user specify a refresh rate for software
VSync, in case the automatic refresh rate detection does not work
well.
- Seemingly the automatic refresh rate detection using X RandR in
software VSync detects refresh rate incorrectly. Need further investigation.
- Fix a few bugs in fading timing.
- Add a workaround for client window detection on Fluxbox, as Fluxbox
(incorrectly?) sets the override-redirect flag upon all frame
windows.
- Software VSync adds dependency on librt (a part of glibc) for
nanosecond-level timing functions, and libXrandr for automatic refresh
rate detection; DRM VSync adds dependency on libdrm to use its drm.h,
but does not link to libdrm; OpenGL VSync adds dependency on libGL.
- Print timing information on DEBUG_REPAINT.
2012-10-08 10:20:01 +08:00
|
|
|
#ifdef CONFIG_VSYNC_DRM
|
|
|
|
#include <fcntl.h>
|
|
|
|
// We references some definitions in drm.h, which could also be found in
|
|
|
|
// /usr/src/linux/include/drm/drm.h, but that path is probably even less
|
|
|
|
// reliable than libdrm
|
2013-03-01 12:41:16 +08:00
|
|
|
#include <drm.h>
|
Feature: #7: VSync
- Add VSync feature. 3 possible VSync methods available: "sw" (software,
not too reliable, but at least you have something to fallback to),
"drm" (using DRM_IOCTL_WAIT_VBLANK, should work only on DRI drivers),
"opengl" (using SGI_swap_control extension OpenGL, might work on more
drivers than the DRM method). "sw" and "opengl" are briefly tested,
"drm" received utterly no test (because I use the nVidia binary blob).
They are enabled with "--vsync sw" / "--vsync drm" / "--vsync opengl".
- Add --refresh-rate to let user specify a refresh rate for software
VSync, in case the automatic refresh rate detection does not work
well.
- Seemingly the automatic refresh rate detection using X RandR in
software VSync detects refresh rate incorrectly. Need further investigation.
- Fix a few bugs in fading timing.
- Add a workaround for client window detection on Fluxbox, as Fluxbox
(incorrectly?) sets the override-redirect flag upon all frame
windows.
- Software VSync adds dependency on librt (a part of glibc) for
nanosecond-level timing functions, and libXrandr for automatic refresh
rate detection; DRM VSync adds dependency on libdrm to use its drm.h,
but does not link to libdrm; OpenGL VSync adds dependency on libGL.
- Print timing information on DEBUG_REPAINT.
2012-10-08 10:20:01 +08:00
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#endif
|
|
|
|
|
2018-10-26 06:31:40 +08:00
|
|
|
#include <X11/Xutil.h>
|
Convert XfixesRegion to pixman region
Re-did the painting logic, and document it.
It is unclear to me what is the previous painting logic. But the current
one is basically this:
1. Go through all windows top to bottom, and put visible windows (not
unmapped, opacity > 0, etc) into a linked list, from bottom to top
2. Accumulate a region of ignore on each window, which is basically the
region of screen that is obscured by all the windows above current
one.
3. Paint all the visible windows from bottom to top. Subtract the region
of ignore from the painting region. If we need to paint shadow, we
subtract the body of the window from the shadow painting region too,
because we don't want shadow behind the window.
4. region of ignore is invalidated when window stack change, an
window on top moved or changed shape, when window changed between
opaque and transparent, etc.
Notes:
It is unclear whether all the different shapes of a window (extents,
noframe, border, bounding shape, etc) are calculated correctly or not.
It is unclear if window shape related events are handled correctly or
not. Need more testing.
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2018-09-30 11:56:00 +08:00
|
|
|
#include <pixman.h>
|
2018-09-30 05:47:12 +08:00
|
|
|
#ifdef CONFIG_OPENGL
|
Convert XfixesRegion to pixman region
Re-did the painting logic, and document it.
It is unclear to me what is the previous painting logic. But the current
one is basically this:
1. Go through all windows top to bottom, and put visible windows (not
unmapped, opacity > 0, etc) into a linked list, from bottom to top
2. Accumulate a region of ignore on each window, which is basically the
region of screen that is obscured by all the windows above current
one.
3. Paint all the visible windows from bottom to top. Subtract the region
of ignore from the painting region. If we need to paint shadow, we
subtract the body of the window from the shadow painting region too,
because we don't want shadow behind the window.
4. region of ignore is invalidated when window stack change, an
window on top moved or changed shape, when window changed between
opaque and transparent, etc.
Notes:
It is unclear whether all the different shapes of a window (extents,
noframe, border, bounding shape, etc) are calculated correctly or not.
It is unclear if window shape related events are handled correctly or
not. Need more testing.
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2018-09-30 11:56:00 +08:00
|
|
|
#include "opengl.h" // XXX clean up
|
2018-09-30 05:47:12 +08:00
|
|
|
#endif
|
2018-08-22 20:26:42 +08:00
|
|
|
#include "common.h"
|
2018-10-21 10:10:12 +08:00
|
|
|
#include "win.h"
|
Convert XfixesRegion to pixman region
Re-did the painting logic, and document it.
It is unclear to me what is the previous painting logic. But the current
one is basically this:
1. Go through all windows top to bottom, and put visible windows (not
unmapped, opacity > 0, etc) into a linked list, from bottom to top
2. Accumulate a region of ignore on each window, which is basically the
region of screen that is obscured by all the windows above current
one.
3. Paint all the visible windows from bottom to top. Subtract the region
of ignore from the painting region. If we need to paint shadow, we
subtract the body of the window from the shadow painting region too,
because we don't want shadow behind the window.
4. region of ignore is invalidated when window stack change, an
window on top moved or changed shape, when window changed between
opaque and transparent, etc.
Notes:
It is unclear whether all the different shapes of a window (extents,
noframe, border, bounding shape, etc) are calculated correctly or not.
It is unclear if window shape related events are handled correctly or
not. Need more testing.
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2018-09-30 11:56:00 +08:00
|
|
|
#include "x.h"
|
2018-08-22 20:26:42 +08:00
|
|
|
#include "c2.h"
|
2018-12-16 01:53:17 +08:00
|
|
|
#include "log.h" // XXX clean up
|
2018-12-16 05:11:41 +08:00
|
|
|
#include "render.h"
|
2018-08-22 20:26:42 +08:00
|
|
|
|
2012-11-19 09:46:07 +08:00
|
|
|
// == Functions ==
|
2018-09-07 02:17:26 +08:00
|
|
|
// TODO move static inline functions that are only used in compton.c, into
|
|
|
|
// compton.c
|
2012-02-27 12:00:12 +08:00
|
|
|
|
2012-09-11 21:57:50 +08:00
|
|
|
// inline functions must be made static to compile correctly under clang:
|
|
|
|
// http://clang.llvm.org/compatibility.html#inline
|
|
|
|
|
Convert XfixesRegion to pixman region
Re-did the painting logic, and document it.
It is unclear to me what is the previous painting logic. But the current
one is basically this:
1. Go through all windows top to bottom, and put visible windows (not
unmapped, opacity > 0, etc) into a linked list, from bottom to top
2. Accumulate a region of ignore on each window, which is basically the
region of screen that is obscured by all the windows above current
one.
3. Paint all the visible windows from bottom to top. Subtract the region
of ignore from the painting region. If we need to paint shadow, we
subtract the body of the window from the shadow painting region too,
because we don't want shadow behind the window.
4. region of ignore is invalidated when window stack change, an
window on top moved or changed shape, when window changed between
opaque and transparent, etc.
Notes:
It is unclear whether all the different shapes of a window (extents,
noframe, border, bounding shape, etc) are calculated correctly or not.
It is unclear if window shape related events are handled correctly or
not. Need more testing.
Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
2018-09-30 11:56:00 +08:00
|
|
|
void add_damage(session_t *ps, const region_t *damage);
|
2018-09-09 09:29:45 +08:00
|
|
|
|
2018-12-28 04:45:38 +08:00
|
|
|
long determine_evmask(session_t *ps, xcb_window_t wid, win_evmode_t mode);
|
2012-09-16 23:12:02 +08:00
|
|
|
|
2018-10-03 20:59:27 +08:00
|
|
|
xcb_window_t
|
|
|
|
find_client_win(session_t *ps, xcb_window_t w);
|
2012-09-17 16:04:04 +08:00
|
|
|
|
2018-12-28 04:45:38 +08:00
|
|
|
win *find_toplevel2(session_t *ps, xcb_window_t wid);
|
2012-11-19 09:46:07 +08:00
|
|
|
|
2018-12-28 04:45:38 +08:00
|
|
|
void map_win(session_t *ps, xcb_window_t id);
|
2018-09-07 02:17:26 +08:00
|
|
|
|
Feature: #7: VSync
- Add VSync feature. 3 possible VSync methods available: "sw" (software,
not too reliable, but at least you have something to fallback to),
"drm" (using DRM_IOCTL_WAIT_VBLANK, should work only on DRI drivers),
"opengl" (using SGI_swap_control extension OpenGL, might work on more
drivers than the DRM method). "sw" and "opengl" are briefly tested,
"drm" received utterly no test (because I use the nVidia binary blob).
They are enabled with "--vsync sw" / "--vsync drm" / "--vsync opengl".
- Add --refresh-rate to let user specify a refresh rate for software
VSync, in case the automatic refresh rate detection does not work
well.
- Seemingly the automatic refresh rate detection using X RandR in
software VSync detects refresh rate incorrectly. Need further investigation.
- Fix a few bugs in fading timing.
- Add a workaround for client window detection on Fluxbox, as Fluxbox
(incorrectly?) sets the override-redirect flag upon all frame
windows.
- Software VSync adds dependency on librt (a part of glibc) for
nanosecond-level timing functions, and libXrandr for automatic refresh
rate detection; DRM VSync adds dependency on libdrm to use its drm.h,
but does not link to libdrm; OpenGL VSync adds dependency on libGL.
- Print timing information on DEBUG_REPAINT.
2012-10-08 10:20:01 +08:00
|
|
|
/**
|
|
|
|
* Subtract two unsigned long values.
|
|
|
|
*
|
|
|
|
* Truncate to 0 if the result is negative.
|
|
|
|
*/
|
2018-12-20 04:50:02 +08:00
|
|
|
static inline unsigned long attr_const
|
Feature: #7: VSync
- Add VSync feature. 3 possible VSync methods available: "sw" (software,
not too reliable, but at least you have something to fallback to),
"drm" (using DRM_IOCTL_WAIT_VBLANK, should work only on DRI drivers),
"opengl" (using SGI_swap_control extension OpenGL, might work on more
drivers than the DRM method). "sw" and "opengl" are briefly tested,
"drm" received utterly no test (because I use the nVidia binary blob).
They are enabled with "--vsync sw" / "--vsync drm" / "--vsync opengl".
- Add --refresh-rate to let user specify a refresh rate for software
VSync, in case the automatic refresh rate detection does not work
well.
- Seemingly the automatic refresh rate detection using X RandR in
software VSync detects refresh rate incorrectly. Need further investigation.
- Fix a few bugs in fading timing.
- Add a workaround for client window detection on Fluxbox, as Fluxbox
(incorrectly?) sets the override-redirect flag upon all frame
windows.
- Software VSync adds dependency on librt (a part of glibc) for
nanosecond-level timing functions, and libXrandr for automatic refresh
rate detection; DRM VSync adds dependency on libdrm to use its drm.h,
but does not link to libdrm; OpenGL VSync adds dependency on libGL.
- Print timing information on DEBUG_REPAINT.
2012-10-08 10:20:01 +08:00
|
|
|
sub_unslong(unsigned long a, unsigned long b) {
|
|
|
|
return (a > b) ? a - b : 0;
|
|
|
|
}
|
|
|
|
|
2012-12-10 10:31:24 +08:00
|
|
|
/**
|
|
|
|
* Set a <code>switch_t</code> array of all unset wintypes to true.
|
|
|
|
*/
|
|
|
|
static inline void
|
|
|
|
wintype_arr_enable_unset(switch_t arr[]) {
|
|
|
|
wintype_t i;
|
|
|
|
|
|
|
|
for (i = 0; i < NUM_WINTYPES; ++i)
|
|
|
|
if (UNSET == arr[i])
|
|
|
|
arr[i] = ON;
|
|
|
|
}
|
|
|
|
|
2012-09-11 22:22:58 +08:00
|
|
|
/**
|
|
|
|
* Check if a window ID exists in an array of window IDs.
|
|
|
|
*
|
|
|
|
* @param arr the array of window IDs
|
|
|
|
* @param count amount of elements in the array
|
|
|
|
* @param wid window ID to search for
|
|
|
|
*/
|
2012-11-19 09:46:07 +08:00
|
|
|
static inline bool
|
2018-12-28 04:45:38 +08:00
|
|
|
array_wid_exists(const xcb_window_t *arr, int count, xcb_window_t wid) {
|
2012-09-11 22:22:58 +08:00
|
|
|
while (count--) {
|
2012-09-13 13:58:05 +08:00
|
|
|
if (arr[count] == wid) {
|
2012-11-19 09:46:07 +08:00
|
|
|
return true;
|
2012-09-13 13:58:05 +08:00
|
|
|
}
|
2012-09-11 22:22:58 +08:00
|
|
|
}
|
2012-09-13 13:39:43 +08:00
|
|
|
|
2012-11-19 09:46:07 +08:00
|
|
|
return false;
|
2012-09-11 22:22:58 +08:00
|
|
|
}
|
2012-09-16 23:12:02 +08:00
|
|
|
|
2012-11-19 09:46:07 +08:00
|
|
|
/**
|
Feature #16: Advanced window matching
- Add advanced window matching system, capable of matching against
arbitrary window properties as well as a series of internal
properties, with 4 additional operators (>, <, >=, <=) useful for
integer targets, and support of logical operators. The old matching
system is removed, but compatibility with the format is retained.
- As the new matching system is pretty complicated, and I have no past
experience in writing a parser, it's pretty possible that bugs are
present. It also has inferior performance, but I hope it doesn't
matter on modern CPUs.
- It's possible to disable matching system at compile time with NO_C2=1
now.
- Add ps->o.config_file to track which config file we have actually
read. Queryable via D-Bus.
- Parse -d in first pass in get_cfg() as c2 needs to query X to get
atoms during condition parsing.
- Fix a bug in wid_get_prop_adv() that 0 == rformat is not handled
correctly.
- Fix incompatibility with FreeBSD sed in dbus-examples/cdbus-driver.sh
.
- Add recipe to generate .clang_complete in Makefile, used by Vim
clang_complete plugin.
- Add DEBUG_C2 for debugging condition string parsing. DEBUG_WINMATCH is
still used for match debugging.
- Rename win_on_wdata_change() to win_on_factor_change().
- Extra malloc() failure checks. Add const to matching cache members in
session_t. Code clean-up. Documentation update.
2013-01-28 21:39:38 +08:00
|
|
|
* Destroy a condition list.
|
2012-11-19 09:46:07 +08:00
|
|
|
*/
|
Feature #16: Advanced window matching
- Add advanced window matching system, capable of matching against
arbitrary window properties as well as a series of internal
properties, with 4 additional operators (>, <, >=, <=) useful for
integer targets, and support of logical operators. The old matching
system is removed, but compatibility with the format is retained.
- As the new matching system is pretty complicated, and I have no past
experience in writing a parser, it's pretty possible that bugs are
present. It also has inferior performance, but I hope it doesn't
matter on modern CPUs.
- It's possible to disable matching system at compile time with NO_C2=1
now.
- Add ps->o.config_file to track which config file we have actually
read. Queryable via D-Bus.
- Parse -d in first pass in get_cfg() as c2 needs to query X to get
atoms during condition parsing.
- Fix a bug in wid_get_prop_adv() that 0 == rformat is not handled
correctly.
- Fix incompatibility with FreeBSD sed in dbus-examples/cdbus-driver.sh
.
- Add recipe to generate .clang_complete in Makefile, used by Vim
clang_complete plugin.
- Add DEBUG_C2 for debugging condition string parsing. DEBUG_WINMATCH is
still used for match debugging.
- Rename win_on_wdata_change() to win_on_factor_change().
- Extra malloc() failure checks. Add const to matching cache members in
session_t. Code clean-up. Documentation update.
2013-01-28 21:39:38 +08:00
|
|
|
static inline void
|
|
|
|
free_wincondlst(c2_lptr_t **pcondlst) {
|
|
|
|
while ((*pcondlst = c2_free_lptr(*pcondlst)))
|
|
|
|
continue;
|
2013-05-09 21:47:09 +08:00
|
|
|
}
|
2012-11-19 09:46:07 +08:00
|
|
|
|
2018-12-16 05:11:41 +08:00
|
|
|
#ifndef CONFIG_OPENGL
|
2018-09-30 05:47:12 +08:00
|
|
|
static inline void
|
|
|
|
free_paint_glx(session_t *ps, paint_t *p) {}
|
|
|
|
static inline void
|
|
|
|
free_win_res_glx(session_t *ps, win *w) {}
|
|
|
|
#endif
|
2013-12-10 22:06:02 +08:00
|
|
|
|
2013-01-30 13:41:08 +08:00
|
|
|
/**
|
|
|
|
* Create a XTextProperty of a single string.
|
|
|
|
*/
|
|
|
|
static inline XTextProperty *
|
|
|
|
make_text_prop(session_t *ps, char *str) {
|
2014-08-07 02:42:57 +08:00
|
|
|
XTextProperty *pprop = ccalloc(1, XTextProperty);
|
2013-01-30 13:41:08 +08:00
|
|
|
|
|
|
|
if (XmbTextListToTextProperty(ps->dpy, &str, 1, XStringStyle, pprop)) {
|
2013-04-05 21:05:19 +08:00
|
|
|
cxfree(pprop->value);
|
2013-01-30 13:41:08 +08:00
|
|
|
free(pprop);
|
|
|
|
pprop = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pprop;
|
|
|
|
}
|
|
|
|
|
2013-11-10 10:13:18 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a single-string text property on a window.
|
|
|
|
*/
|
|
|
|
static inline bool
|
2018-12-28 04:45:38 +08:00
|
|
|
wid_set_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop_atom, char *str) {
|
2013-11-10 10:13:18 +08:00
|
|
|
XTextProperty *pprop = make_text_prop(ps, str);
|
|
|
|
if (!pprop) {
|
2018-12-21 00:54:51 +08:00
|
|
|
log_error("Failed to make text property: %s.", str);
|
2013-11-10 10:13:18 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
XSetTextProperty(ps->dpy, wid, pprop, prop_atom);
|
|
|
|
cxfree(pprop->value);
|
|
|
|
cxfree(pprop);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-01-09 20:25:01 +08:00
|
|
|
/**
|
|
|
|
* Dump an drawable's info.
|
|
|
|
*/
|
|
|
|
static inline void
|
2018-12-28 04:45:38 +08:00
|
|
|
dump_drawable(session_t *ps, xcb_drawable_t drawable) {
|
|
|
|
auto r = xcb_get_geometry_reply(ps->c, xcb_get_geometry(ps->c, drawable), NULL);
|
|
|
|
if (!r) {
|
|
|
|
log_trace("Drawable %#010x: Failed", drawable);
|
|
|
|
return;
|
2013-01-09 20:25:01 +08:00
|
|
|
}
|
2018-12-28 04:45:38 +08:00
|
|
|
log_trace("Drawable %#010x: x = %u, y = %u, wid = %u, hei = %d, b = %u, d = %u",
|
|
|
|
drawable, r->x, r->y, r->width, r->height, r->border_width, r->depth);
|
|
|
|
free(r);
|
2012-10-31 08:54:09 +08:00
|
|
|
}
|
|
|
|
|
2013-05-01 22:08:43 +08:00
|
|
|
/**
|
|
|
|
* Validate pixmap of a window, and destroy pixmap and picture if invalid.
|
|
|
|
*/
|
|
|
|
static inline void
|
|
|
|
win_validate_pixmap(session_t *ps, win *w) {
|
|
|
|
// Destroy pixmap and picture, if invalid
|
2018-12-16 05:11:41 +08:00
|
|
|
if (!x_validate_pixmap(ps, w->paint.pixmap))
|
2013-05-01 22:08:43 +08:00
|
|
|
free_paint(ps, &w->paint);
|
|
|
|
}
|
2013-01-29 09:57:04 +08:00
|
|
|
|
2018-08-12 01:22:49 +08:00
|
|
|
// vim: set et sw=2 :
|