|
37 | 37 | #include <sys/wait.h>
|
38 | 38 | #include <sys/resource.h>
|
39 | 39 | #include <sys/uio.h>
|
| 40 | +#include <sys/queue.h> |
| 41 | +#include <sys/random.h> |
40 | 42 | #include <signal.h>
|
41 | 43 | #include <poll.h>
|
42 | 44 | #include <errno.h>
|
@@ -2592,11 +2594,85 @@ static void process_xevent_xembed(Ghandles * g, const XClientMessageEvent * ev)
|
2592 | 2594 |
|
2593 | 2595 | }
|
2594 | 2596 |
|
| 2597 | +/* get current time */ |
| 2598 | +static int64_t ebuf_current_time_ms() |
| 2599 | +{ |
| 2600 | + int64_t timeval; |
| 2601 | + struct timespec spec; |
| 2602 | + clock_gettime(CLOCK_MONOTONIC, &spec); |
| 2603 | + timeval = (((int64_t)spec.tv_sec) * 1000LL) + (((int64_t)spec.tv_nsec) / 1000000LL); |
| 2604 | + return timeval; |
| 2605 | +} |
| 2606 | + |
| 2607 | +/* get random delay value */ |
| 2608 | +static uint32_t ebuf_random_delay(uint32_t upper_bound, uint32_t lower_bound) |
| 2609 | +{ |
| 2610 | + uint32_t maxval; |
| 2611 | + uint32_t randval; |
| 2612 | + size_t randsize; |
| 2613 | + union ebuf_rand ebuf_rand_data; |
| 2614 | + |
| 2615 | + if (lower_bound >= upper_bound) { |
| 2616 | + if (lower_bound > upper_bound) { |
| 2617 | + fprintf(stderr, |
| 2618 | + "Bug detected - lower_bound > upper_bound, events may get briefly stuck"); |
| 2619 | + } |
| 2620 | + return upper_bound; |
| 2621 | + } |
| 2622 | + |
| 2623 | + maxval = upper_bound - lower_bound + 1; |
| 2624 | + |
| 2625 | + do { |
| 2626 | + randsize = getrandom(ebuf_rand_data.raw, sizeof(uint32_t), 0); |
| 2627 | + if (randsize != sizeof(uint32_t)) |
| 2628 | + continue; |
| 2629 | + } while (ebuf_rand_data.val > UINT32_MAX - ((uint32_t)((((uint64_t)UINT32_MAX) + 1) % maxval))); |
| 2630 | + |
| 2631 | + randval = ebuf_rand_data.val % maxval; |
| 2632 | + return lower_bound + randval; |
| 2633 | +} |
| 2634 | + |
| 2635 | +/* queue input event */ |
| 2636 | +static void ebuf_queue_xevent(Ghandles * g, XEvent xev) |
| 2637 | +{ |
| 2638 | + int64_t current_time; |
| 2639 | + uint32_t random_delay; |
| 2640 | + struct ebuf_entry *new_ebuf_entry; |
| 2641 | + uint32_t lower_bound; |
| 2642 | + |
| 2643 | + current_time = ebuf_current_time_ms(); |
| 2644 | + |
| 2645 | + /* |
| 2646 | + * Each event is scheduled by taking the current time and adding a delay. |
| 2647 | + * We do not want events later in the queue having a release timestamp |
| 2648 | + * that is *less* than an event earlier in the queue. This means that |
| 2649 | + * whatever delay we add *must* be at least enough to give a release |
| 2650 | + * timestamp larger than the one generated last time. To facilitate this, |
| 2651 | + * we set lower_bound to the scheduled release time of the last event |
| 2652 | + * minus the current time. Some sanity checks are included to make sure |
| 2653 | + * lower_bound is never less than 0 or greater than ebuf_max_delay. |
| 2654 | + */ |
| 2655 | + lower_bound = min(max(g->ebuf_prev_release_time - current_time, 0), g->ebuf_max_delay); |
| 2656 | + |
| 2657 | + random_delay = ebuf_random_delay(g->ebuf_max_delay, lower_bound); |
| 2658 | + new_ebuf_entry = malloc(sizeof(struct ebuf_entry)); |
| 2659 | + if (new_ebuf_entry == NULL) { |
| 2660 | + perror("Could not allocate ebuf_entry:"); |
| 2661 | + exit(1); |
| 2662 | + } |
| 2663 | + if (current_time > 0 && random_delay > (INT64_MAX - current_time)) { |
| 2664 | + fprintf(stderr, "Event scheduler overflow detected, cannot continue"); |
| 2665 | + exit(1); |
| 2666 | + } |
| 2667 | + new_ebuf_entry->time = current_time + random_delay; |
| 2668 | + new_ebuf_entry->xev = xev; |
| 2669 | + TAILQ_INSERT_TAIL(&(g->ebuf_head), new_ebuf_entry, entries); |
| 2670 | + g->ebuf_prev_release_time = new_ebuf_entry->time; |
| 2671 | +} |
| 2672 | + |
2595 | 2673 | /* dispatch local Xserver event */
|
2596 |
| -static void process_xevent(Ghandles * g) |
| 2674 | +static void process_xevent_core(Ghandles * g, XEvent event_buffer) |
2597 | 2675 | {
|
2598 |
| - XEvent event_buffer; |
2599 |
| - XNextEvent(g->display, &event_buffer); |
2600 | 2676 | switch (event_buffer.type) {
|
2601 | 2677 | case KeyPress:
|
2602 | 2678 | case KeyRelease:
|
@@ -2655,6 +2731,40 @@ static void process_xevent(Ghandles * g)
|
2655 | 2731 | }
|
2656 | 2732 | }
|
2657 | 2733 |
|
| 2734 | +/* dispatch queued events */ |
| 2735 | +static void ebuf_release_xevents(Ghandles * g) |
| 2736 | +{ |
| 2737 | + int64_t current_time; |
| 2738 | + struct ebuf_entry *current_ebuf_entry; |
| 2739 | + |
| 2740 | + current_time = ebuf_current_time_ms(); |
| 2741 | + while ((current_ebuf_entry = TAILQ_FIRST(&(g->ebuf_head))) |
| 2742 | + && (current_time >= current_ebuf_entry->time)) { |
| 2743 | + XEvent event_buffer = current_ebuf_entry->xev; |
| 2744 | + process_xevent_core(g, event_buffer); |
| 2745 | + TAILQ_REMOVE(&(g->ebuf_head), current_ebuf_entry, entries); |
| 2746 | + free(current_ebuf_entry); |
| 2747 | + } |
| 2748 | + current_ebuf_entry = TAILQ_FIRST(&(g->ebuf_head)); |
| 2749 | + if (current_ebuf_entry == NULL) { |
| 2750 | + g->ebuf_next_timeout = VCHAN_DEFAULT_POLL_DURATION; |
| 2751 | + } else { |
| 2752 | + g->ebuf_next_timeout = (int)(current_ebuf_entry->time - current_time); |
| 2753 | + } |
| 2754 | +} |
| 2755 | + |
| 2756 | +/* handle or queue local Xserver event */ |
| 2757 | +static void process_xevent(Ghandles * g) |
| 2758 | +{ |
| 2759 | + XEvent event_buffer; |
| 2760 | + XNextEvent(g->display, &event_buffer); |
| 2761 | + if (g->ebuf_max_delay > 0) { |
| 2762 | + ebuf_queue_xevent(g, event_buffer); |
| 2763 | + } else { |
| 2764 | + process_xevent_core(g, event_buffer); |
| 2765 | + } |
| 2766 | +} |
| 2767 | + |
2658 | 2768 |
|
2659 | 2769 | /* handle VM message: MSG_SHMIMAGE
|
2660 | 2770 | * pass message data to do_shm_update - there input validation will be done */
|
@@ -4482,6 +4592,18 @@ static void parse_vm_config(Ghandles * g, config_setting_t * group)
|
4482 | 4592 | exit(1);
|
4483 | 4593 | }
|
4484 | 4594 | }
|
| 4595 | + |
| 4596 | + if ((setting = |
| 4597 | + config_setting_get_member(group, "events_max_delay"))) { |
| 4598 | + int delay_val = config_setting_get_int(setting); |
| 4599 | + if (delay_val < 0 || delay_val > 5000) { |
| 4600 | + fprintf(stderr, |
| 4601 | + "unsupported value '%d' for events_max_delay (must be >= 0 and <= 5000)", |
| 4602 | + delay_val); |
| 4603 | + exit(1); |
| 4604 | + } |
| 4605 | + g->ebuf_max_delay = delay_val; |
| 4606 | + } |
4485 | 4607 | }
|
4486 | 4608 |
|
4487 | 4609 | static void parse_config(Ghandles * g)
|
@@ -4604,6 +4726,8 @@ int main(int argc, char **argv)
|
4604 | 4726 | /* parse cmdline, possibly overriding values from config */
|
4605 | 4727 | parse_cmdline(&ghandles, argc, argv);
|
4606 | 4728 | get_boot_lock(ghandles.domid);
|
| 4729 | + /* init event queue */ |
| 4730 | + TAILQ_INIT(&(ghandles.ebuf_head)); |
4607 | 4731 |
|
4608 | 4732 | if (!ghandles.nofork) {
|
4609 | 4733 | // daemonize...
|
@@ -4799,8 +4923,15 @@ int main(int argc, char **argv)
|
4799 | 4923 | handle_message(&ghandles);
|
4800 | 4924 | busy = 1;
|
4801 | 4925 | }
|
| 4926 | + if (ghandles.ebuf_max_delay > 0) { |
| 4927 | + ebuf_release_xevents(&ghandles); |
| 4928 | + } |
4802 | 4929 | } while (busy);
|
4803 |
| - wait_for_vchan_or_argfd(ghandles.vchan, xfd); |
| 4930 | + if (ghandles.ebuf_max_delay > 0) { |
| 4931 | + wait_for_vchan_or_argfd_once(ghandles.vchan, xfd, ghandles.ebuf_next_timeout); |
| 4932 | + } else { |
| 4933 | + wait_for_vchan_or_argfd_once(ghandles.vchan, xfd, VCHAN_DEFAULT_POLL_DURATION); |
| 4934 | + } |
4804 | 4935 | }
|
4805 | 4936 | return 0;
|
4806 | 4937 | }
|
0 commit comments