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:
361
src/compton.c
361
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 <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.
|
||||
*
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user