diff --git a/Makefile b/Makefile index 44450c5e..663973e3 100644 --- a/Makefile +++ b/Makefile @@ -8,28 +8,12 @@ BINDIR ?= $(PREFIX)/bin MANDIR ?= $(PREFIX)/share/man/man1 PACKAGES = x11 xcomposite xfixes xdamage xrender xext xrandr -LIBS = -lm +LIBS = -lm -lrt INCS = # === Configuration flags === 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 ==== ifeq "$(NO_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)\"" LDFLAGS ?= -Wl,-O1 -Wl,--as-needed -CFLAGS ?= -DNDEBUG -O2 -D_FORTIFY_SOURCE=2 $(LDFLAGS) +CFLAGS ?= -DNDEBUG -O2 -D_FORTIFY_SOURCE=2 CFLAGS += $(CFG) LIBS += $(shell pkg-config --libs $(PACKAGES)) @@ -80,6 +64,8 @@ INCS += $(shell pkg-config --cflags $(PACKAGES)) CFLAGS += -Wall -std=c99 OBJS = compton.o +MANPAGES = man/compton.1 man/compton-trans.1 +MANPAGES_HTML = $(addsuffix .html,$(MANPAGES)) # === Recipes === .DEFAULT_GOAL := compton @@ -90,15 +76,15 @@ OBJS = compton.o compton: $(OBJS) $(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(OBJS) $(LIBS) -docs: - # HTML documentation - 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 +man/%.1: man/%.1.asciidoc + a2x --format manpage $< -install: compton +man/%.1.html: man/%.1.asciidoc + asciidoc $< + +docs: $(MANPAGES) $(MANPAGES_HTML) + +install: compton docs @install -Dm755 compton "$(DESTDIR)$(BINDIR)"/compton @install -Dm755 bin/compton-trans "$(DESTDIR)$(BINDIR)"/compton-trans @install -Dm644 man/compton.1 "$(DESTDIR)$(MANDIR)"/compton.1 @@ -111,6 +97,6 @@ uninstall: @rm -f "$(DESTDIR)$(MANDIR)/compton-trans.1" clean: - @rm -f $(OBJS) compton + @rm -f $(OBJS) compton $(MANPAGES) $(MANPAGES_HTML) .PHONY: uninstall clean docs diff --git a/README.md b/README.md index f2d3a8a8..bbdee8fd 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,7 @@ __R__ for runtime * libconfig (B,R) (Will probably be made optional soon) * libdrm (B) (Will probably be made optional soon) * libGL (B,R) (Will probably be made optional soon) -* asciidoc (B) (if you wish to run `make docs`) -* libevent (B,R) +* asciidoc (B) ### 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`. -* 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. ## Usage diff --git a/man/compton-trans.1 b/man/compton-trans.1 deleted file mode 100644 index dbf974b2..00000000 --- a/man/compton-trans.1 +++ /dev/null @@ -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) diff --git a/man/compton.1 b/man/compton.1 deleted file mode 100644 index 56445eb8..00000000 --- a/man/compton.1 +++ /dev/null @@ -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 = :[]: - - is one of "n" (window name), "i" (window class -instance), and "g" (window general class) - - 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). - - could be a series of flags. Currently the only defined -flag is "i" (ignore case). - - 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) diff --git a/src/compton.c b/src/compton.c index e1b8ade7..941134a1 100644 --- a/src/compton.c +++ b/src/compton.c @@ -57,8 +57,7 @@ static int fade_timeout(session_t *ps) { int diff = ps->o.fade_delta - get_time_ms() + ps->fade_time; - if (diff < 0) - diff = 0; + diff = normalize_i_range(diff, 0, ps->o.fade_delta * 2); return diff; } @@ -1196,11 +1195,14 @@ paint_preprocess(session_t *ps, win *list) { bool is_highest = true; // Fading step calculation - time_ms_t steps = ((get_time_ms() - ps->fade_time) + FADE_DELTA_TOLERANCE * ps->o.fade_delta) / ps->o.fade_delta; - if (steps < 0L) { - // Time disorder + time_ms_t steps = 0L; + if (ps->fade_time) { + 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(); - steps = 0; + steps = 0L; } 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); } +/** + * 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. */ static bool -fork_after(void) { +fork_after(session_t *ps) { if (getppid() == 1) return true; @@ -3941,18 +3961,13 @@ fork_after(void) { // Mainly to suppress the _FORTIFY_SOURCE warning 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) { printf_errf("(): freopen() failed."); return false; } + success = ostream_reopen(ps, NULL); - return true; + return success; } #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-fixed", no_argument, NULL, 285 }, { "dbus", no_argument, NULL, 286 }, + { "logpath", required_argument, NULL, 287 }, // Must terminate with a NULL entry { NULL, 0, NULL, 0 }, }; @@ -4405,8 +4421,7 @@ get_cfg(session_t *ps, int argc, char *const *argv) { case 'n': case 'a': case 's': - fprintf(stderr, "Warning: " - "-n, -a, and -s have been removed.\n"); + printf_errfq(1, "(): -n, -a, and -s have been removed."); break; case 'b': ps->o.fork_after_register = true; @@ -4535,8 +4550,13 @@ get_cfg(session_t *ps, int argc, char *const *argv) { // --dbus ps->o.dbus = true; break; + case 287: + // --logpath + ps->o.logpath = mstrcpy(optarg); + break; default: usage(); + break; } } @@ -4694,22 +4714,6 @@ swopti_init(session_t *ps) { return true; } -/** - * Get the smaller number that is bigger than dividend and is - * N times of divisor. - */ -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. * @@ -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. * @@ -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. */ @@ -5037,36 +5150,74 @@ redir_stop(session_t *ps) { */ static bool mainloop(session_t *ps) { - bool infinite_wait = false; - // 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)) { - evcallback_x(ConnectionNumber(ps->dpy), 0, NULL); + XEvent ev = { }; + + XNextEvent(ps->dpy, &ev); + ev_handle(ps, &ev); + ps->ev_received = true; + return true; } - // Add timeout - 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)) + if (ps->reset) + return false; + + // 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; + } + // Then consider fading timeout + else if (!ps->idling) { + ptv = malloc(sizeof(struct timeval)); + *ptv = ms_to_tv(fade_timeout(ps)); + } + + // Software optimization is to be applied on timeouts that require + // immediate painting only + if (ptv && ps->o.sw_opti) + swopti_handle_timeout(ps, ptv); + + // Don't continue looping for 0 timeout + if (ptv && timeval_isempty(ptv)) { + free(ptv); return false; - evtimer_add(ps->ev_tmout, &tv); - } - else { - infinite_wait = true; + } + + // Now consider the waiting time of other timeouts + 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; + } } - // Run libevent main loop - if (event_base_loop(ps->ev_base, EVLOOP_ONCE)) - printf_errfq(1, "(): Unexpected error when running event loop."); + // Polling + fds_poll(ps, ptv); + free(ptv); + ptv = NULL; - evtimer_del(ps->ev_tmout); - - if (infinite_wait) - ps->fade_time = get_time_ms(); + timeout_run(ps); return true; } @@ -5106,6 +5257,8 @@ session_init(session_t *ps_old, int argc, char **argv) { .detect_rounded_corners = false, .paint_on_overlay = false, .unredir_if_possible = false, + .dbus = false, + .logpath = NULL, .refresh_rate = 0, .sw_opti = false, @@ -5156,6 +5309,12 @@ session_init(session_t *ps_old, int argc, char **argv) { .track_leader = false, }, + .pfds_read = NULL, + .pfds_write = NULL, + .pfds_except = NULL, + .nfds_max = 0, + .tmout_lst = NULL, + .all_damage = None, .time_start = { 0, 0 }, .redirected = false, @@ -5163,7 +5322,7 @@ session_init(session_t *ps_old, int argc, char **argv) { .alpha_picts = NULL, .reg_ignore_expire = false, .idling = false, - .fade_time = 0, + .fade_time = 0L, .ignore_head = NULL, .ignore_tail = NULL, .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->all_damage = None; - - // 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."); + fds_insert(ps, ConnectionNumber(ps->dpy), POLLIN); XGrabServer(ps->dpy); @@ -5459,14 +5601,10 @@ session_init(session_t *ps_old, int argc, char **argv) { // Fork to background, if asked if (ps->o.fork_after_register) { - if (!fork_after()) { + if (!fork_after(ps)) { session_destroy(ps); return NULL; } - - // Reinitialize event base - if (event_reinit(ps->ev_base) < 0) - printf_errfq(1, "Failed to reinitialize event base."); } // Free the old session @@ -5565,6 +5703,10 @@ session_destroy(session_t *ps) { free(ps->shadow_top); free(ps->gaussian_map); 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 if (ps->reg_win) { @@ -5598,14 +5740,12 @@ session_destroy(session_t *ps) { 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 XSync(ps->dpy, True); + // Free timeouts + timeout_clear(ps); + if (ps == ps_g) ps_g = NULL; } @@ -5624,8 +5764,6 @@ session_run(session_t *ps) { ps->reg_ignore_expire = true; - ps->fade_time = get_time_ms(); - t = paint_preprocess(ps, ps->list); if (ps->redirected) @@ -5658,6 +5796,9 @@ session_run(session_t *ps) { XSync(ps->dpy, False); ps->all_damage = None; } + + if (ps->idling) + ps->fade_time = 0L; } } diff --git a/src/compton.h b/src/compton.h index 9bf25e24..cb4f623f 100644 --- a/src/compton.h +++ b/src/compton.h @@ -43,9 +43,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -54,20 +56,6 @@ #include #include -// libevent -#ifndef CONFIG_LIBEVENT_LEGACY -#include -#else -#include -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 #ifdef CONFIG_REGEX_PCRE #include @@ -127,8 +115,10 @@ typedef void(* event_callback_fn)(evutil_socket_t, short, void *); #define OPAQUE 0xffffffff #define REGISTER_PROP "_NET_WM_CM_S" +#define TIME_MS_MAX LONG_MAX #define FADE_DELTA_TOLERANCE 0.2 #define SWOPTI_TOLERANCE 3000 +#define TIMEOUT_RUN_TOLERANCE 0.2 #define WIN_GET_LEADER_MAX_RECURSION 20 #define SEC_WRAP (15L * 24L * 60L * 60L) @@ -286,6 +276,8 @@ typedef struct { double *data; } conv; +struct _timeout_t; + struct _win; /// Structure representing all options. @@ -308,6 +300,8 @@ typedef struct { bool unredir_if_possible; /// Whether to enable D-Bus support. bool dbus; + /// Path to log file. + char *logpath; /// Whether to work under synchronized mode for debugging. bool synchronize; @@ -447,12 +441,16 @@ typedef struct { // === Operation related === /// Program options. options_t o; - /// Libevent event base. - struct event_base *ev_base; - /// Libevent event for X connection. - struct event *ev_x; - /// Libevent event for timeout. - struct event *ev_tmout; + /// File descriptors to check for reading. + fd_set *pfds_read; + /// File descriptors to check for writing. + fd_set *pfds_write; + /// File descriptors to check for exceptions. + 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. bool ev_received; /// Whether the program is idling. I.e. no fading, no potential window @@ -770,6 +768,18 @@ struct options_tmp { 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 { WIN_EVMODE_UNKNOWN, WIN_EVMODE_FRAME, @@ -840,22 +850,6 @@ XFixesDestroyRegion_(Display *dpy, XserverRegion reg, // == 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: // http://clang.llvm.org/compatibility.html#inline @@ -1007,6 +1001,14 @@ min_i(int a, int b) { 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. * @@ -1055,8 +1057,41 @@ array_wid_exists(const Window *arr, int count, Window wid) { * Return whether a struct timeval value is empty. */ static inline bool -timeval_isempty(struct timeval tv) { - return tv.tv_sec <= 0 && tv.tv_usec <= 0; +timeval_isempty(struct timeval *ptv) { + 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 run_fade(session_t *ps, win *w, unsigned steps); @@ -2030,7 +2151,7 @@ inline static void ev_handle(session_t *ps, XEvent *ev); static bool -fork_after(void); +fork_after(session_t *ps); #ifdef CONFIG_LIBCONFIG /** @@ -2095,12 +2216,6 @@ swopti_init(session_t *ps); static void 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 vsync_drm_init(session_t *ps); @@ -2135,6 +2250,22 @@ redir_start(session_t *ps); static void 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 mainloop(session_t *ps);