Improvement: Use select() for main loop

- Back to using select() for main loop. Thus we are not longer relying
  on libevent.

- Add generic timeout system (untested) to prepare for D-Bus support.

- Drop troff man pages. Revise Makefile to improve documentation
  building, fix double LDFLAGS inclusion, and re-add -lrt. This turns
  asciidoc into a build time dependency.

- Change fading time calculation.

- Add --logpath and ostream_reopen() for debugging with -b.

- Drop unused lceil_ntimes() and other helper functions.

- Only very limited tests are done. Bugs to be expected.
This commit is contained in:
Richard Grenville 2013-01-11 21:31:02 +08:00
parent 7188054825
commit 57c5854fd0
6 changed files with 443 additions and 433 deletions

View File

@ -8,28 +8,12 @@ BINDIR ?= $(PREFIX)/bin
MANDIR ?= $(PREFIX)/share/man/man1 MANDIR ?= $(PREFIX)/share/man/man1
PACKAGES = x11 xcomposite xfixes xdamage xrender xext xrandr PACKAGES = x11 xcomposite xfixes xdamage xrender xext xrandr
LIBS = -lm LIBS = -lm -lrt
INCS = INCS =
# === Configuration flags === # === Configuration flags ===
CFG = CFG =
# ==== libevent ====
# Seemingly, libevent1 has no pkg-config file!
# FreeBSD 9.1 probably has issues handling --atleast-version in pkg-config.
ifeq "$(shell pkg-config --modversion --print-errors libevent)" ""
$(warning libevent-2.0 not found, assuming libevent-1.4.x.)
CFG += -DCONFIG_LIBEVENT_LEGACY
LIBS += -levent
else
# Using pkg-config --libs for linking with libevent will result in
# linking with libevent.so instead of the smaller libevent_core.so.
# FreeBSD keeps libevent2 .so files at a separate place, and we must
# learn it from pkg-config.
LIBS += $(shell pkg-config --libs-only-L libevent) -levent_core
INCS += $(shell pkg-config --cflags libevent)
endif
# ==== libconfig ==== # ==== libconfig ====
ifeq "$(NO_LIBCONFIG)" "" ifeq "$(NO_LIBCONFIG)" ""
CFG += -DCONFIG_LIBCONFIG CFG += -DCONFIG_LIBCONFIG
@ -72,7 +56,7 @@ COMPTON_VERSION ?= git-$(shell git describe --always --dirty)-$(shell git log -1
CFG += -DCOMPTON_VERSION="\"$(COMPTON_VERSION)\"" CFG += -DCOMPTON_VERSION="\"$(COMPTON_VERSION)\""
LDFLAGS ?= -Wl,-O1 -Wl,--as-needed LDFLAGS ?= -Wl,-O1 -Wl,--as-needed
CFLAGS ?= -DNDEBUG -O2 -D_FORTIFY_SOURCE=2 $(LDFLAGS) CFLAGS ?= -DNDEBUG -O2 -D_FORTIFY_SOURCE=2
CFLAGS += $(CFG) CFLAGS += $(CFG)
LIBS += $(shell pkg-config --libs $(PACKAGES)) LIBS += $(shell pkg-config --libs $(PACKAGES))
@ -80,6 +64,8 @@ INCS += $(shell pkg-config --cflags $(PACKAGES))
CFLAGS += -Wall -std=c99 CFLAGS += -Wall -std=c99
OBJS = compton.o OBJS = compton.o
MANPAGES = man/compton.1 man/compton-trans.1
MANPAGES_HTML = $(addsuffix .html,$(MANPAGES))
# === Recipes === # === Recipes ===
.DEFAULT_GOAL := compton .DEFAULT_GOAL := compton
@ -90,15 +76,15 @@ OBJS = compton.o
compton: $(OBJS) compton: $(OBJS)
$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJS) $(LIBS)
docs: man/%.1: man/%.1.asciidoc
# HTML documentation a2x --format manpage $<
asciidoc man/compton.1.asciidoc
asciidoc man/compton-trans.1.asciidoc
# man page
a2x --format manpage man/compton.1.asciidoc
a2x --format manpage man/compton-trans.1.asciidoc
install: compton man/%.1.html: man/%.1.asciidoc
asciidoc $<
docs: $(MANPAGES) $(MANPAGES_HTML)
install: compton docs
@install -Dm755 compton "$(DESTDIR)$(BINDIR)"/compton @install -Dm755 compton "$(DESTDIR)$(BINDIR)"/compton
@install -Dm755 bin/compton-trans "$(DESTDIR)$(BINDIR)"/compton-trans @install -Dm755 bin/compton-trans "$(DESTDIR)$(BINDIR)"/compton-trans
@install -Dm644 man/compton.1 "$(DESTDIR)$(MANDIR)"/compton.1 @install -Dm644 man/compton.1 "$(DESTDIR)$(MANDIR)"/compton.1
@ -111,6 +97,6 @@ uninstall:
@rm -f "$(DESTDIR)$(MANDIR)/compton-trans.1" @rm -f "$(DESTDIR)$(MANDIR)/compton-trans.1"
clean: clean:
@rm -f $(OBJS) compton @rm -f $(OBJS) compton $(MANPAGES) $(MANPAGES_HTML)
.PHONY: uninstall clean docs .PHONY: uninstall clean docs

View File

@ -55,8 +55,7 @@ __R__ for runtime
* libconfig (B,R) (Will probably be made optional soon) * libconfig (B,R) (Will probably be made optional soon)
* libdrm (B) (Will probably be made optional soon) * libdrm (B) (Will probably be made optional soon)
* libGL (B,R) (Will probably be made optional soon) * libGL (B,R) (Will probably be made optional soon)
* asciidoc (B) (if you wish to run `make docs`) * asciidoc (B)
* libevent (B,R)
### How to build ### How to build
@ -83,8 +82,6 @@ $ make install
* Compton may give ugly shadow to windows with ARGB background if `-z` is enabled, because compton cannot determine their real shapes. One may have to disable shadows on those windows with window-type-specific settings in configuration file or `--shadow-exclude`. * Compton may give ugly shadow to windows with ARGB background if `-z` is enabled, because compton cannot determine their real shapes. One may have to disable shadows on those windows with window-type-specific settings in configuration file or `--shadow-exclude`.
* There are two sets of man pages in the repository: the man pages in groff format (`man/compton.1` & `man/compton-trans.1`) and the man pages in Asciidoc format (`man/compton.1.asciidoc` & `man/compton-trans.1.asciidoc`). The Asciidoc man pages are much more up-to-date than the groff ones, and it is viewable online. As chjj has not yet expressed his attitude towards switching to Asciidoc man pages, I kept both versions. By default the groff version is installed, unless you run `make docs`.
* The performance of blurring is terrible, probably because of a problem in the X Render implementation. Its behavior is driver-dependent: With nvidia-drivers it works but there are strange 1px lines remaining when you operate on windows (not sure if it's a bug in compton or in the driver); with nouveau it's utterly broken. * The performance of blurring is terrible, probably because of a problem in the X Render implementation. Its behavior is driver-dependent: With nvidia-drivers it works but there are strange 1px lines remaining when you operate on windows (not sure if it's a bug in compton or in the driver); with nouveau it's utterly broken.
## Usage ## Usage

View File

@ -1,68 +0,0 @@
.ds q \N'34'
.TH compton\-trans 1
.SH NAME
compton\-trans \- an opacity setter tool
.SH SYNOPSIS
.nf
.B compton-trans [-w WINDOW_ID] [-n WINDOW_NAME] [-c] [-s] OPACITY
.fi
.SH DESCRIPTION
.B compton-trans
is a bash script that sets _NET_WM_WINDOW_OPACITY only using standard
command-line utilities for X11, including xprop(1) and xwininfo(1).
It is similar to other utilities like transset(1) or transset-df(1).
.SH OPTIONS
.TP
.BI \-w\ window\-id
Specify the window id to target.
.TP
.BI \-n\ window\-name
Specify and try to match a window name.
.TP
.BI \-c
Specify the current window as a target.
.TP
.BI \-s
Select target window with mouse cursor.
This is the default if no window has been specified.
.TP
.BI \-o\ opacity
Specify the new opacity value for the window. This value
can be anywhere from 1-100. If it is prefixed with a plus
or minus (+/-), this will increment or decrement from the
target window's current opacity instead.
.SH EXAMPLES
.TP
Set window id to opacity of 75%.
compton-trans -w "$WINDOWID" 75
.TP
Set window name, "urxvt", to opacity of 75%.
compton-trans -n "urxvt" 75
.TP
Set current window to opacity of 75%.
compton-trans -c 75
.TP
Select target window and set opacity to 75%.
compton-trans -s 75
.TP
Increment current window opacity by 5%.
compton-trans -c +5
.TP
Decrement current window opacity by 5%.
compton-trans -c -- -5
.SH BUGS
Please report any you find to https://github.com/chjj/compton.
.SH AUTHORS
Christopher Jeffrey (https://github.com/chjj)
.SH SEE ALSO
.BR compton(1),
.BR xprop(1),
.BR xwininfo(1)

View File

@ -1,177 +0,0 @@
.ds q \N'34'
.TH compton 1
.SH NAME
compton \- a compositor for X11
.SH SYNOPSIS
.B compton
[\-d display] [\-r radius] [\-o opacity] [\-l left\-offset]
[\-t top\-offset] [\-i opacity] [\-e opacity] [\-cCfFSdG]
[\-\-config path] [\-\-shadow\-red value]
[\-\-shadow\-green value] [\-\-shadow\-blue value]
[\-\-inactive\-opacity\-override] [\-\-inactive\-dim value]
[\-\-mark\-wmwin\-focused] [\-\-shadow\-exclude condition]
[\-\-mark\-ovredir\-focused] [\-\-no\-fading\-openclose]
[\-\-shadow\-ignore\-shaped] [\-\-detect\-round\-corners]
.SH DESCRIPTION
.B compton
is a compositor based on Dana Jansens' version of xcompmgr (which itself was
written by Keith Packard). It includes many improvements over the original
xcompmgr, including window frame opacity, inactive window transparency,
and shadows on argb windows.
.SH EXAMPLE
$ compton -cC -i 0.6 -e 0.6 -f
$ compton --config ~/compton.conf
.SH OPTIONS
.TP
.BI \-d\ display
Which display should be managed.
.TP
.BI \-r\ radius
The blur radius for shadows. (default 12)
.TP
.BI \-o\ opacity
The translucency for shadows. (default .75)
.TP
.BI \-l\ left\-offset
The left offset for shadows. (default -15)
.TP
.BI \-t\ top\-offset
The top offset for shadows. (default -15)
.TP
.BI \-I\ fade\-in\-step
Opacity change between steps while fading in. (default 0.028)
.TP
.BI \-O\ fade\-out\-step
Opacity change between steps while fading out. (default 0.03)
.TP
.BI \-D\ fade\-delta\-time
The time between steps in a fade in milliseconds. (default 10)
.TP
.BI \-m\ opacity
The opacity for menus. (default 1.0)
.TP
.BI \-c
Enabled client-side shadows on windows.
.TP
.BI \-C
Avoid drawing shadows on dock/panel windows.
.TP
.BI \-z
Zero the part of the shadow's mask behind the window (experimental).
.TP
.BI \-f
Fade windows in/out when opening/closing and when opacity
changes, unless --no-fading-openclose is used.
.TP
.BI \-F
Equals -f. Deprecated.
.TP
.BI \-i\ opacity
Opacity of inactive windows. (0.1 - 1.0)
.TP
.BI \-e\ opacity
Opacity of window titlebars and borders. (0.1 - 1.0)
.TP
.BI \-G
Don't draw shadows on DND windows
.TP
.BI \-b
Daemonize/background process.
.TP
.BI \-S
Enable synchronous operation (for debugging).
.TP
.BI \-\-config\ path
Look for configuration file at the path.
.TP
.BI \-\-shadow\-red\ value
Red color value of shadow (0.0 - 1.0, defaults to 0).
.TP
.BI \-\-shadow\-green\ value
Green color value of shadow (0.0 - 1.0, defaults to 0).
.TP
.BI \-\-shadow\-blue\ value
Blue color value of shadow (0.0 - 1.0, defaults to 0).
.TP
.BI \-\-inactive\-opacity\-override
Inactive opacity set by -i overrides value of _NET_WM_OPACITY.
.TP
.BI \-\-inactive\-dim\ value
Dim inactive windows. (0.0 - 1.0, defaults to 0)
.TP
.BI \-\-mark\-wmwin\-focused
Try to detect WM windows and mark them as active.
.TP
.BI \-\-shadow\-exclude\ condition
Exclude conditions for shadows.
.TP
.BI \--mark\-ovredir\-focused
Mark over-redirect windows as active.
.TP
.BI \-\-no\-fading\-openclose
Do not fade on window open/close.
.TP
.BI \-\-shadow\-ignore\-shaped
Do not paint shadows on shaped windows.
.TP
.BI \-\-detect\-rounded\-corners
Try to detect windows with rounded corners and don't consider
them shaped windows.
.TP
.BI Format\ of\ a\ condition:
condition = <target>:<type>[<flags>]:<pattern>
<target> is one of "n" (window name), "i" (window class
instance), and "g" (window general class)
<type> is one of "e" (exact match), "a" (match anywhere),
"s" (match from start), "w" (wildcard), and "p" (PCRE
regular expressions, if compiled with the support).
<flags> could be a series of flags. Currently the only defined
flag is "i" (ignore case).
<pattern> is the actual pattern string.
.SH CONFIGURATION
(A more robust sample configuration file exists in the compton
repository.)
.B Example
.B ~/compton.conf:
# Shadows
shadow = true;
# Opacity
inactive-opacity = 0.8;
frame-opacity = 0.7;
# Fades
fading = true;
.B Run with:
$ compton --config ~/compton.conf
.SH BUGS
Please report any you find to https://github.com/chjj/compton.
.SH AUTHORS
xcompmgr, originally written by Keith Packard, with contributions from
Matthew Allum, Eric Anholt, Dan Doel, Thomas Luebking, Matthew Hawn,
Ely Levy, Phil Blundell, and Carl Worth.
Compton by Christopher Jeffrey, based on Dana Jansens' original work,
with numerous contributions from Richard Grenville.
.SH SEE ALSO
.BR compton-trans(1)

View File

@ -57,8 +57,7 @@ static int
fade_timeout(session_t *ps) { fade_timeout(session_t *ps) {
int diff = ps->o.fade_delta - get_time_ms() + ps->fade_time; int diff = ps->o.fade_delta - get_time_ms() + ps->fade_time;
if (diff < 0) diff = normalize_i_range(diff, 0, ps->o.fade_delta * 2);
diff = 0;
return diff; return diff;
} }
@ -1196,11 +1195,14 @@ paint_preprocess(session_t *ps, win *list) {
bool is_highest = true; bool is_highest = true;
// Fading step calculation // Fading step calculation
time_ms_t steps = ((get_time_ms() - ps->fade_time) + FADE_DELTA_TOLERANCE * ps->o.fade_delta) / ps->o.fade_delta; time_ms_t steps = 0L;
if (steps < 0L) { if (ps->fade_time) {
// Time disorder steps = ((get_time_ms() - ps->fade_time) + FADE_DELTA_TOLERANCE * ps->o.fade_delta) / ps->o.fade_delta;
}
// Reset fade_time if unset, or there appears to be a time disorder
if (!ps->fade_time || steps < 0L) {
ps->fade_time = get_time_ms(); ps->fade_time = get_time_ms();
steps = 0; steps = 0L;
} }
ps->fade_time += steps * ps->o.fade_delta; ps->fade_time += steps * ps->o.fade_delta;
@ -3920,11 +3922,29 @@ register_cm(session_t *ps, bool want_glxct) {
XSetSelectionOwner(ps->dpy, a, ps->reg_win, 0); XSetSelectionOwner(ps->dpy, a, ps->reg_win, 0);
} }
/**
* Reopen streams for logging.
*/
static bool
ostream_reopen(session_t *ps, const char *path) {
if (!path)
path = ps->o.logpath;
if (!path)
path = "/dev/null";
bool success = freopen(path, "a", stdout);
success = freopen(path, "a", stderr) && success;
if (!success)
printf_errfq(1, "(%s): freopen() failed.", path);
return success;
}
/** /**
* Fork program to background and disable all I/O streams. * Fork program to background and disable all I/O streams.
*/ */
static bool static bool
fork_after(void) { fork_after(session_t *ps) {
if (getppid() == 1) if (getppid() == 1)
return true; return true;
@ -3941,18 +3961,13 @@ fork_after(void) {
// Mainly to suppress the _FORTIFY_SOURCE warning // Mainly to suppress the _FORTIFY_SOURCE warning
bool success = freopen("/dev/null", "r", stdin); bool success = freopen("/dev/null", "r", stdin);
success = freopen("/dev/null", "w", stdout) && success;
if (!success) {
printf_errf("(): freopen() failed.");
return false;
}
success = freopen("/dev/null", "w", stderr);
if (!success) { if (!success) {
printf_errf("(): freopen() failed."); printf_errf("(): freopen() failed.");
return false; return false;
} }
success = ostream_reopen(ps, NULL);
return true; return success;
} }
#ifdef CONFIG_LIBCONFIG #ifdef CONFIG_LIBCONFIG
@ -4299,6 +4314,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) {
{ "blur-background-frame", no_argument, NULL, 284 }, { "blur-background-frame", no_argument, NULL, 284 },
{ "blur-background-fixed", no_argument, NULL, 285 }, { "blur-background-fixed", no_argument, NULL, 285 },
{ "dbus", no_argument, NULL, 286 }, { "dbus", no_argument, NULL, 286 },
{ "logpath", required_argument, NULL, 287 },
// Must terminate with a NULL entry // Must terminate with a NULL entry
{ NULL, 0, NULL, 0 }, { NULL, 0, NULL, 0 },
}; };
@ -4405,8 +4421,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) {
case 'n': case 'n':
case 'a': case 'a':
case 's': case 's':
fprintf(stderr, "Warning: " printf_errfq(1, "(): -n, -a, and -s have been removed.");
"-n, -a, and -s have been removed.\n");
break; break;
case 'b': case 'b':
ps->o.fork_after_register = true; ps->o.fork_after_register = true;
@ -4535,8 +4550,13 @@ get_cfg(session_t *ps, int argc, char *const *argv) {
// --dbus // --dbus
ps->o.dbus = true; ps->o.dbus = true;
break; break;
case 287:
// --logpath
ps->o.logpath = mstrcpy(optarg);
break;
default: default:
usage(); usage();
break;
} }
} }
@ -4694,22 +4714,6 @@ swopti_init(session_t *ps) {
return true; return true;
} }
/**
* Get the smaller number that is bigger than <code>dividend</code> and is
* N times of <code>divisor</code>.
*/
static inline long
lceil_ntimes(long dividend, long divisor) {
// It's possible to use the more beautiful expression here:
// ret = ((dividend - 1) / divisor + 1) * divisor;
// But it does not work well for negative values.
long ret = dividend / divisor * divisor;
if (ret < dividend)
ret += divisor;
return ret;
}
/** /**
* Modify a struct timeval timeout value to render at a fixed pace. * Modify a struct timeval timeout value to render at a fixed pace.
* *
@ -4743,32 +4747,6 @@ swopti_handle_timeout(session_t *ps, struct timeval *ptv) {
} }
} }
/**
* Libevent callback function to handle X events.
*/
static void
evcallback_x(evutil_socket_t fd, short what, void *arg) {
session_t *ps = ps_g;
// Sometimes poll() returns 1 but no events are actually read,
// causing XNextEvent() to block, I have no idea what's wrong, so we
// check for the number of events here
if (XEventsQueued(ps->dpy, QueuedAfterReading)) {
XEvent ev = { };
XNextEvent(ps->dpy, &ev);
ev_handle(ps, &ev);
ps->ev_received = true;
}
}
/**
* NULL libevent callback function.
*/
static void
evcallback_null(evutil_socket_t fd, short what, void *arg) {
}
/** /**
* Initialize DRM VSync. * Initialize DRM VSync.
* *
@ -5003,6 +4981,141 @@ redir_start(session_t *ps) {
} }
} }
/**
* Get the poll time.
*/
static time_ms_t
timeout_get_poll_time(session_t *ps) {
const time_ms_t now = get_time_ms();
time_ms_t wait = TIME_MS_MAX;
// Traverse throught the timeout linked list
for (timeout_t *ptmout = ps->tmout_lst; ptmout; ptmout = ptmout->next) {
if (ptmout->enabled) {
// Truncate the last run time to the closest interval
time_ms_t newrun = ptmout->firstrun + ((ptmout->lastrun - ptmout->firstrun) / ptmout->interval + 1) * ptmout->interval;
if (newrun <= now) {
wait = 0;
break;
}
else {
time_ms_t newwait = newrun - now;
if (newwait < wait)
wait = newwait;
}
}
}
return wait;
}
/**
* Insert a new timeout.
*/
static timeout_t *
timeout_insert(session_t *ps, time_ms_t interval,
bool (*callback)(session_t *ps, timeout_t *ptmout), void *data) {
const static timeout_t tmout_def = {
.enabled = true,
.data = NULL,
.callback = NULL,
.firstrun = 0L,
.lastrun = 0L,
.interval = 0L,
};
const time_ms_t now = get_time_ms();
timeout_t *ptmout = malloc(sizeof(timeout_t));
if (!ptmout)
printf_errfq(1, "(): Failed to allocate memory for timeout.");
memcpy(ptmout, &tmout_def, sizeof(timeout_t));
ptmout->interval = interval;
ptmout->firstrun = now;
ptmout->lastrun = now;
ptmout->data = data;
ptmout->callback = callback;
ptmout->next = ps->tmout_lst;
ps->tmout_lst = ptmout;
return ptmout;
}
/**
* Drop a timeout.
*
* @return true if we have found the timeout and removed it, false
* otherwise
*/
static bool
timeout_drop(session_t *ps, timeout_t *prm) {
timeout_t **pplast = &ps->tmout_lst;
for (timeout_t *ptmout = ps->tmout_lst; ptmout;
pplast = &ptmout->next, ptmout = ptmout->next) {
if (prm == ptmout) {
*pplast = ptmout->next;
free(ptmout);
return true;
}
}
return false;
}
/**
* Clear all timeouts.
*/
static void
timeout_clear(session_t *ps) {
timeout_t *ptmout = ps->tmout_lst;
timeout_t *next = NULL;
while (ptmout) {
next = ptmout->next;
free(ptmout);
ptmout = next;
}
}
/**
* Run timeouts.
*
* @return true if we have ran a timeout, false otherwise
*/
static bool
timeout_run(session_t *ps) {
const time_ms_t now = get_time_ms();
bool ret = false;
for (timeout_t *ptmout = ps->tmout_lst; ptmout; ptmout = ptmout->next) {
if (ptmout->enabled) {
const time_ms_t max = now +
(time_ms_t) (ptmout->interval * TIMEOUT_RUN_TOLERANCE);
time_ms_t newrun = ptmout->firstrun + ((ptmout->lastrun - ptmout->firstrun) / ptmout->interval + 1) * ptmout->interval;
if (newrun <= max) {
ret = true;
timeout_invoke(ps, ptmout);
}
}
}
return ret;
}
/**
* Invoke a timeout.
*/
static void
timeout_invoke(session_t *ps, timeout_t *ptmout) {
const time_ms_t now = get_time_ms();
ptmout->lastrun = now;
// Avoid modifying the timeout structure after running timeout, to
// make it possible to remove timeout in callback
if (ptmout->callback)
ptmout->callback(ps, ptmout);
}
/** /**
* Unredirect all windows. * Unredirect all windows.
*/ */
@ -5037,36 +5150,74 @@ redir_stop(session_t *ps) {
*/ */
static bool static bool
mainloop(session_t *ps) { mainloop(session_t *ps) {
bool infinite_wait = false;
// Process existing events // Process existing events
// Sometimes poll() returns 1 but no events are actually read,
// causing XNextEvent() to block, I have no idea what's wrong, so we
// check for the number of events here.
if (XEventsQueued(ps->dpy, QueuedAfterReading)) { if (XEventsQueued(ps->dpy, QueuedAfterReading)) {
evcallback_x(ConnectionNumber(ps->dpy), 0, NULL); XEvent ev = { };
XNextEvent(ps->dpy, &ev);
ev_handle(ps, &ev);
ps->ev_received = true;
return true; return true;
} }
// Add timeout if (ps->reset)
if (ps->ev_received || !ps->idling) {
struct timeval tv = ms_to_tv(ps->ev_received ? 0: fade_timeout(ps));
if (ps->o.sw_opti)
swopti_handle_timeout(ps, &tv);
assert(tv.tv_sec >= 0 && tv.tv_usec >= 0);
if (timeval_isempty(tv))
return false; return false;
evtimer_add(ps->ev_tmout, &tv);
// Calculate timeout
struct timeval *ptv = NULL;
{
// Consider ev_received firstly
if (ps->ev_received) {
ptv = malloc(sizeof(struct timeval));
ptv->tv_sec = 0L;
ptv->tv_usec = 0L;
} }
else { // Then consider fading timeout
infinite_wait = true; else if (!ps->idling) {
ptv = malloc(sizeof(struct timeval));
*ptv = ms_to_tv(fade_timeout(ps));
} }
// Run libevent main loop // Software optimization is to be applied on timeouts that require
if (event_base_loop(ps->ev_base, EVLOOP_ONCE)) // immediate painting only
printf_errfq(1, "(): Unexpected error when running event loop."); if (ptv && ps->o.sw_opti)
swopti_handle_timeout(ps, ptv);
evtimer_del(ps->ev_tmout); // Don't continue looping for 0 timeout
if (ptv && timeval_isempty(ptv)) {
free(ptv);
return false;
}
if (infinite_wait) // Now consider the waiting time of other timeouts
ps->fade_time = get_time_ms(); time_ms_t tmout_ms = timeout_get_poll_time(ps);
if (tmout_ms < TIME_MS_MAX) {
if (!ptv) {
ptv = malloc(sizeof(struct timeval));
*ptv = ms_to_tv(tmout_ms);
}
else if (timeval_ms_cmp(ptv, tmout_ms) > 0) {
*ptv = ms_to_tv(tmout_ms);
}
}
// Don't continue looping for 0 timeout
if (ptv && timeval_isempty(ptv)) {
free(ptv);
return false;
}
}
// Polling
fds_poll(ps, ptv);
free(ptv);
ptv = NULL;
timeout_run(ps);
return true; return true;
} }
@ -5106,6 +5257,8 @@ session_init(session_t *ps_old, int argc, char **argv) {
.detect_rounded_corners = false, .detect_rounded_corners = false,
.paint_on_overlay = false, .paint_on_overlay = false,
.unredir_if_possible = false, .unredir_if_possible = false,
.dbus = false,
.logpath = NULL,
.refresh_rate = 0, .refresh_rate = 0,
.sw_opti = false, .sw_opti = false,
@ -5156,6 +5309,12 @@ session_init(session_t *ps_old, int argc, char **argv) {
.track_leader = false, .track_leader = false,
}, },
.pfds_read = NULL,
.pfds_write = NULL,
.pfds_except = NULL,
.nfds_max = 0,
.tmout_lst = NULL,
.all_damage = None, .all_damage = None,
.time_start = { 0, 0 }, .time_start = { 0, 0 },
.redirected = false, .redirected = false,
@ -5163,7 +5322,7 @@ session_init(session_t *ps_old, int argc, char **argv) {
.alpha_picts = NULL, .alpha_picts = NULL,
.reg_ignore_expire = false, .reg_ignore_expire = false,
.idling = false, .idling = false,
.fade_time = 0, .fade_time = 0L,
.ignore_head = NULL, .ignore_head = NULL,
.ignore_tail = NULL, .ignore_tail = NULL,
.reset = false, .reset = false,
@ -5406,24 +5565,7 @@ session_init(session_t *ps_old, int argc, char **argv) {
ps->o.shadow_red, ps->o.shadow_green, ps->o.shadow_blue); ps->o.shadow_red, ps->o.shadow_green, ps->o.shadow_blue);
} }
ps->all_damage = None; fds_insert(ps, ConnectionNumber(ps->dpy), POLLIN);
// Build event base
if (!(ps->ev_base =
#ifndef CONFIG_LIBEVENT_LEGACY
event_base_new()
#else
event_init()
#endif
))
printf_errfq(1, "(): Failed to build event base.");
if (!(ps->ev_x = EVENT_NEW(ps->ev_base, ConnectionNumber(ps->dpy),
EV_READ | EV_PERSIST, evcallback_x, NULL)))
printf_errfq(1, "(): Failed to build event.");
if (event_add(ps->ev_x, NULL))
printf_errfq(1, "(): Failed to add event.");
if (!(ps->ev_tmout = evtimer_new(ps->ev_base, evcallback_null, NULL)))
printf_errfq(1, "(): Failed to build event.");
XGrabServer(ps->dpy); XGrabServer(ps->dpy);
@ -5459,14 +5601,10 @@ session_init(session_t *ps_old, int argc, char **argv) {
// Fork to background, if asked // Fork to background, if asked
if (ps->o.fork_after_register) { if (ps->o.fork_after_register) {
if (!fork_after()) { if (!fork_after(ps)) {
session_destroy(ps); session_destroy(ps);
return NULL; return NULL;
} }
// Reinitialize event base
if (event_reinit(ps->ev_base) < 0)
printf_errfq(1, "Failed to reinitialize event base.");
} }
// Free the old session // Free the old session
@ -5565,6 +5703,10 @@ session_destroy(session_t *ps) {
free(ps->shadow_top); free(ps->shadow_top);
free(ps->gaussian_map); free(ps->gaussian_map);
free(ps->o.display); free(ps->o.display);
free(ps->o.logpath);
free(ps->pfds_read);
free(ps->pfds_write);
free(ps->pfds_except);
// Free reg_win and glx_context // Free reg_win and glx_context
if (ps->reg_win) { if (ps->reg_win) {
@ -5598,14 +5740,12 @@ session_destroy(session_t *ps) {
ps->overlay = None; ps->overlay = None;
} }
// Free libevent things
event_free(ps->ev_x);
event_free(ps->ev_tmout);
event_base_free(ps->ev_base);
// Flush all events // Flush all events
XSync(ps->dpy, True); XSync(ps->dpy, True);
// Free timeouts
timeout_clear(ps);
if (ps == ps_g) if (ps == ps_g)
ps_g = NULL; ps_g = NULL;
} }
@ -5624,8 +5764,6 @@ session_run(session_t *ps) {
ps->reg_ignore_expire = true; ps->reg_ignore_expire = true;
ps->fade_time = get_time_ms();
t = paint_preprocess(ps, ps->list); t = paint_preprocess(ps, ps->list);
if (ps->redirected) if (ps->redirected)
@ -5658,6 +5796,9 @@ session_run(session_t *ps) {
XSync(ps->dpy, False); XSync(ps->dpy, False);
ps->all_damage = None; ps->all_damage = None;
} }
if (ps->idling)
ps->fade_time = 0L;
} }
} }

View File

@ -43,9 +43,11 @@
#include <string.h> #include <string.h>
#include <inttypes.h> #include <inttypes.h>
#include <math.h> #include <math.h>
#include <sys/select.h>
#include <sys/poll.h> #include <sys/poll.h>
#include <sys/time.h> #include <sys/time.h>
#include <time.h> #include <time.h>
#include <limits.h>
#include <unistd.h> #include <unistd.h>
#include <getopt.h> #include <getopt.h>
#include <stdbool.h> #include <stdbool.h>
@ -54,20 +56,6 @@
#include <fnmatch.h> #include <fnmatch.h>
#include <signal.h> #include <signal.h>
// libevent
#ifndef CONFIG_LIBEVENT_LEGACY
#include <event2/event.h>
#else
#include <event.h>
typedef int evutil_socket_t;
typedef void(* event_callback_fn)(evutil_socket_t, short, void *);
#define event_free(ev) (event_del((ev)), free((ev)))
#endif
#ifndef evtimer_new
#define evtimer_new(b, cb, arg) EVENT_NEW((b), -1, 0, (cb), (arg))
#endif
// libpcre // libpcre
#ifdef CONFIG_REGEX_PCRE #ifdef CONFIG_REGEX_PCRE
#include <pcre.h> #include <pcre.h>
@ -127,8 +115,10 @@ typedef void(* event_callback_fn)(evutil_socket_t, short, void *);
#define OPAQUE 0xffffffff #define OPAQUE 0xffffffff
#define REGISTER_PROP "_NET_WM_CM_S" #define REGISTER_PROP "_NET_WM_CM_S"
#define TIME_MS_MAX LONG_MAX
#define FADE_DELTA_TOLERANCE 0.2 #define FADE_DELTA_TOLERANCE 0.2
#define SWOPTI_TOLERANCE 3000 #define SWOPTI_TOLERANCE 3000
#define TIMEOUT_RUN_TOLERANCE 0.2
#define WIN_GET_LEADER_MAX_RECURSION 20 #define WIN_GET_LEADER_MAX_RECURSION 20
#define SEC_WRAP (15L * 24L * 60L * 60L) #define SEC_WRAP (15L * 24L * 60L * 60L)
@ -286,6 +276,8 @@ typedef struct {
double *data; double *data;
} conv; } conv;
struct _timeout_t;
struct _win; struct _win;
/// Structure representing all options. /// Structure representing all options.
@ -308,6 +300,8 @@ typedef struct {
bool unredir_if_possible; bool unredir_if_possible;
/// Whether to enable D-Bus support. /// Whether to enable D-Bus support.
bool dbus; bool dbus;
/// Path to log file.
char *logpath;
/// Whether to work under synchronized mode for debugging. /// Whether to work under synchronized mode for debugging.
bool synchronize; bool synchronize;
@ -447,12 +441,16 @@ typedef struct {
// === Operation related === // === Operation related ===
/// Program options. /// Program options.
options_t o; options_t o;
/// Libevent event base. /// File descriptors to check for reading.
struct event_base *ev_base; fd_set *pfds_read;
/// Libevent event for X connection. /// File descriptors to check for writing.
struct event *ev_x; fd_set *pfds_write;
/// Libevent event for timeout. /// File descriptors to check for exceptions.
struct event *ev_tmout; fd_set *pfds_except;
/// Largest file descriptor in fd_set-s above.
int nfds_max;
/// Linked list of all timeouts.
struct _timeout_t *tmout_lst;
/// Whether we have received an event in this cycle. /// Whether we have received an event in this cycle.
bool ev_received; bool ev_received;
/// Whether the program is idling. I.e. no fading, no potential window /// Whether the program is idling. I.e. no fading, no potential window
@ -770,6 +768,18 @@ struct options_tmp {
double menu_opacity; double menu_opacity;
}; };
/// Structure for a recorded timeout.
typedef struct _timeout_t {
bool enabled;
void *data;
bool (*callback)(session_t *ps, struct _timeout_t *ptmout);
time_ms_t interval;
time_ms_t firstrun;
time_ms_t lastrun;
struct _timeout_t *next;
} timeout_t;
/// Enumeration for window event hints.
typedef enum { typedef enum {
WIN_EVMODE_UNKNOWN, WIN_EVMODE_UNKNOWN,
WIN_EVMODE_FRAME, WIN_EVMODE_FRAME,
@ -840,22 +850,6 @@ XFixesDestroyRegion_(Display *dpy, XserverRegion reg,
// == Functions == // == Functions ==
/**
* Wrapper of libevent event_new(), for compatibility with libevent-1\.x.
*/
static inline struct event *
EVENT_NEW(struct event_base *base, evutil_socket_t fd,
short what, event_callback_fn cb, void *arg) {
#ifndef CONFIG_LIBEVENT_LEGACY
return event_new(base, fd, what, cb, arg);
#else
struct event *pev = malloc(sizeof(struct event));
if (pev)
event_set(pev, fd, what, cb, arg);
return pev;
#endif
}
// inline functions must be made static to compile correctly under clang: // inline functions must be made static to compile correctly under clang:
// http://clang.llvm.org/compatibility.html#inline // http://clang.llvm.org/compatibility.html#inline
@ -1007,6 +1001,14 @@ min_i(int a, int b) {
return (a > b ? b : a); return (a > b ? b : a);
} }
/**
* Select the smaller long integer of two.
*/
static inline long __attribute__((const))
min_l(long a, long b) {
return (a > b ? b : a);
}
/** /**
* Normalize a double value to a specific range. * Normalize a double value to a specific range.
* *
@ -1055,8 +1057,41 @@ array_wid_exists(const Window *arr, int count, Window wid) {
* Return whether a struct timeval value is empty. * Return whether a struct timeval value is empty.
*/ */
static inline bool static inline bool
timeval_isempty(struct timeval tv) { timeval_isempty(struct timeval *ptv) {
return tv.tv_sec <= 0 && tv.tv_usec <= 0; if (!ptv)
return false;
return ptv->tv_sec <= 0 && ptv->tv_usec <= 0;
}
/**
* Compare a struct timeval with a time in milliseconds.
*
* @return > 0 if ptv > ms, 0 if ptv == 0, -1 if ptv < ms
*/
static inline int
timeval_ms_cmp(struct timeval *ptv, time_ms_t ms) {
assert(ptv);
// We use those if statement instead of a - expression because of possible
// truncation problem from long to int.
{
long sec = ms / MS_PER_SEC;
if (ptv->tv_sec > sec)
return 1;
if (ptv->tv_sec < sec)
return -1;
}
{
long usec = ms % MS_PER_SEC * (US_PER_SEC / MS_PER_SEC);
if (ptv->tv_usec > usec)
return 1;
if (ptv->tv_usec < usec)
return -1;
}
return 0;
} }
/* /*
@ -1290,8 +1325,94 @@ ms_to_tv(int timeout) {
}; };
} }
static int /**
fade_timeout(session_t *ps); * Add a file descriptor to a select() fd_set.
*/
static inline bool
fds_insert_select(fd_set **ppfds, int fd) {
assert(fd <= FD_SETSIZE);
if (!*ppfds) {
if ((*ppfds = malloc(sizeof(fd_set)))) {
FD_ZERO(*ppfds);
}
else {
fprintf(stderr, "Failed to allocate memory for select() fdset.\n");
exit(1);
}
}
FD_SET(fd, *ppfds);
return true;
}
/**
* Add a new file descriptor to wait for.
*/
static inline bool
fds_insert(session_t *ps, int fd, short events) {
bool result = true;
ps->nfds_max = max_i(fd + 1, ps->nfds_max);
if (POLLIN & events)
result = fds_insert_select(&ps->pfds_read, fd) && result;
if (POLLOUT & events)
result = fds_insert_select(&ps->pfds_write, fd) && result;
if (POLLPRI & events)
result = fds_insert_select(&ps->pfds_except, fd) && result;
return result;
}
/**
* Delete a file descriptor to wait for.
*/
static inline void
fds_drop(session_t *ps, int fd, short events) {
// Drop fd from respective fd_set-s
if (POLLIN & events)
FD_CLR(fd, ps->pfds_read);
if (POLLOUT & events)
FD_CLR(fd, ps->pfds_write);
if (POLLPRI & events)
FD_CLR(fd, ps->pfds_except);
}
#define CPY_FDS(key) \
fd_set * key = NULL; \
if (ps->key) { \
key = malloc(sizeof(fd_set)); \
memcpy(key, ps->key, sizeof(fd_set)); \
if (!key) { \
fprintf(stderr, "Failed to allocate memory for copying select() fdset.\n"); \
exit(1); \
} \
} \
/**
* Poll for changes.
*
* poll() is much better than select(), but ppoll() does not exist on
* *BSD.
*/
static inline int
fds_poll(session_t *ps, struct timeval *ptv) {
// Copy fds
CPY_FDS(pfds_read);
CPY_FDS(pfds_write);
CPY_FDS(pfds_except);
int ret = select(ps->nfds_max, pfds_read, pfds_write, pfds_except, ptv);
free(pfds_read);
free(pfds_write);
free(pfds_except);
return ret;
}
#undef CPY_FDS
static void static void
run_fade(session_t *ps, win *w, unsigned steps); run_fade(session_t *ps, win *w, unsigned steps);
@ -2030,7 +2151,7 @@ inline static void
ev_handle(session_t *ps, XEvent *ev); ev_handle(session_t *ps, XEvent *ev);
static bool static bool
fork_after(void); fork_after(session_t *ps);
#ifdef CONFIG_LIBCONFIG #ifdef CONFIG_LIBCONFIG
/** /**
@ -2095,12 +2216,6 @@ swopti_init(session_t *ps);
static void static void
swopti_handle_timeout(session_t *ps, struct timeval *ptv); swopti_handle_timeout(session_t *ps, struct timeval *ptv);
static void
evcallback_x(evutil_socket_t fd, short what, void *arg);
static void
evcallback_null(evutil_socket_t fd, short what, void *arg);
static bool static bool
vsync_drm_init(session_t *ps); vsync_drm_init(session_t *ps);
@ -2135,6 +2250,22 @@ redir_start(session_t *ps);
static void static void
redir_stop(session_t *ps); redir_stop(session_t *ps);
static time_ms_t
timeout_get_poll_time(session_t *ps);
static timeout_t *
timeout_insert(session_t *ps, time_ms_t interval,
bool (*callback)(session_t *ps, timeout_t *ptmout), void *data);
static void
timeout_invoke(session_t *ps, timeout_t *ptmout);
static bool
timeout_drop(session_t *ps, timeout_t *prm);
static void
timeout_clear(session_t *ps);
static bool static bool
mainloop(session_t *ps); mainloop(session_t *ps);