2019-06-29 21:03:28 +02:00
|
|
|
#include "terminal.h"
|
|
|
|
|
|
2019-11-03 12:48:18 +01:00
|
|
|
#include <malloc.h>
|
2019-06-29 21:03:28 +02:00
|
|
|
#include <string.h>
|
2019-07-05 14:24:51 +02:00
|
|
|
#include <unistd.h>
|
2019-06-29 21:03:28 +02:00
|
|
|
#include <assert.h>
|
2019-10-28 18:25:19 +01:00
|
|
|
#include <errno.h>
|
2019-06-29 21:03:28 +02:00
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <sys/wait.h>
|
2019-10-28 18:35:16 +01:00
|
|
|
#include <sys/epoll.h>
|
2019-10-30 20:03:11 +01:00
|
|
|
#include <sys/eventfd.h>
|
2019-07-30 22:06:02 +02:00
|
|
|
#include <sys/timerfd.h>
|
2019-10-28 18:25:19 +01:00
|
|
|
#include <fcntl.h>
|
2019-07-09 09:17:24 +02:00
|
|
|
#include <linux/input-event-codes.h>
|
2020-03-26 19:39:12 +01:00
|
|
|
#include <xdg-shell.h>
|
2019-07-09 09:17:24 +02:00
|
|
|
|
2019-06-29 21:03:28 +02:00
|
|
|
#define LOG_MODULE "terminal"
|
2019-07-03 20:21:03 +02:00
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-06-29 21:03:28 +02:00
|
|
|
#include "log.h"
|
2019-11-04 13:46:04 +01:00
|
|
|
|
|
|
|
|
#include "async.h"
|
2020-02-22 00:23:19 +01:00
|
|
|
#include "config.h"
|
2019-07-01 12:23:38 +02:00
|
|
|
#include "grid.h"
|
2020-03-01 13:09:25 +01:00
|
|
|
#include "quirks.h"
|
2019-07-21 17:35:53 +02:00
|
|
|
#include "render.h"
|
2019-08-01 20:51:11 +02:00
|
|
|
#include "selection.h"
|
2020-02-22 00:23:19 +01:00
|
|
|
#include "sixel.h"
|
2019-10-28 18:25:19 +01:00
|
|
|
#include "slave.h"
|
2020-05-01 11:46:24 +02:00
|
|
|
#include "util.h"
|
2020-02-22 00:23:19 +01:00
|
|
|
#include "vt.h"
|
2019-06-29 21:03:28 +02:00
|
|
|
|
delayed rendering: ignore frame callback if delayed rendering is active
Before, we applied delayed rendering (that is, we gave the client a
chance to do more writes before we scheduled a render refresh) only
when the renderer were idle.
However, with e.g. a high keyboard repeat rate, it is very much
possible to start the render loop and then never break out of it while
receiving keyboard input.
This causes screen flickering, as we're no longer even trying to
detect the clients transaction boundaries.
So, let's rewrite how this is done.
First, we give the user the ability to disable delayed rendering
altogether, by setting either the lower or upper timeout to 0.
Second, when delayed rendering is enabled, we ignore the frame
callback. That is, when receiving input, we *always* reschedule the
lower timeout timer, regardless of whether the render is idle or not.
The render's frame callback handler will *not* render the grid if the
delayed render timers are armed.
This means for longer client data bursts, we may now skip frames. That
is, we're trading screen flicker for the occasional frame hickup.
For short client data bursts we should behave roughly as before.
This greatly improves the behavior of fullscreen, or near fullscreen,
updates of large grids (example, scrolling in emacs in fullscreen,
with a vertical buffer split).
2020-03-23 19:21:41 +01:00
|
|
|
#define PTMX_TIMING 0
|
|
|
|
|
|
2019-11-30 12:43:06 +01:00
|
|
|
static const char *const XCURSOR_LEFT_PTR = "left_ptr";
|
|
|
|
|
static const char *const XCURSOR_TEXT = "text";
|
|
|
|
|
static const char *const XCURSOR_HAND2 = "hand2";
|
|
|
|
|
|
2019-11-03 01:25:41 +01:00
|
|
|
bool
|
|
|
|
|
term_to_slave(struct terminal *term, const void *_data, size_t len)
|
|
|
|
|
{
|
2020-02-03 19:58:32 +01:00
|
|
|
if (term->ptmx < 0) {
|
|
|
|
|
/* We're probably in "hold" */
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-28 19:20:25 +01:00
|
|
|
size_t async_idx = 0;
|
2019-11-03 01:25:41 +01:00
|
|
|
if (tll_length(term->ptmx_buffer) > 0) {
|
2020-01-10 19:51:16 +01:00
|
|
|
/* With a non-empty queue, EPOLLOUT has already been enabled */
|
2019-11-03 01:25:41 +01:00
|
|
|
goto enqueue_data;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-03 12:13:51 +01:00
|
|
|
/*
|
|
|
|
|
* Try a synchronous write first. If we fail to write everything,
|
|
|
|
|
* switch to asynchronous.
|
|
|
|
|
*/
|
|
|
|
|
|
2019-11-28 19:20:25 +01:00
|
|
|
switch (async_write(term->ptmx, _data, len, &async_idx)) {
|
2019-11-04 13:46:04 +01:00
|
|
|
case ASYNC_WRITE_REMAIN:
|
2020-01-10 19:51:16 +01:00
|
|
|
/* Switch to asynchronous mode; let FDM write the remaining data */
|
|
|
|
|
if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT))
|
|
|
|
|
return false;
|
2019-11-03 01:25:41 +01:00
|
|
|
goto enqueue_data;
|
|
|
|
|
|
2019-11-04 13:46:04 +01:00
|
|
|
case ASYNC_WRITE_DONE:
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case ASYNC_WRITE_ERR:
|
|
|
|
|
LOG_ERRNO("failed to synchronously write %zu bytes to slave", len);
|
|
|
|
|
return false;
|
2019-11-03 01:25:41 +01:00
|
|
|
}
|
2019-11-03 01:03:52 +01:00
|
|
|
|
2019-11-03 12:13:51 +01:00
|
|
|
/* Shouldn't get here */
|
|
|
|
|
assert(false);
|
|
|
|
|
return false;
|
|
|
|
|
|
2019-11-03 01:03:52 +01:00
|
|
|
enqueue_data:
|
2019-11-04 12:36:43 +01:00
|
|
|
/*
|
|
|
|
|
* We're in asynchronous mode - push data to queue and let the FDM
|
|
|
|
|
* handler take care of it
|
|
|
|
|
*/
|
2019-11-03 01:03:52 +01:00
|
|
|
{
|
|
|
|
|
void *copy = malloc(len);
|
|
|
|
|
memcpy(copy, _data, len);
|
|
|
|
|
|
|
|
|
|
struct ptmx_buffer queued = {
|
|
|
|
|
.data = copy,
|
|
|
|
|
.len = len,
|
2019-11-28 19:20:25 +01:00
|
|
|
.idx = async_idx,
|
2019-11-03 01:03:52 +01:00
|
|
|
};
|
|
|
|
|
tll_push_back(term->ptmx_buffer, queued);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct terminal *term = data;
|
2019-11-04 12:36:43 +01:00
|
|
|
|
2020-01-10 19:51:16 +01:00
|
|
|
/* If there is no queued data, then we shouldn't be in asynchronous mode */
|
|
|
|
|
assert(tll_length(term->ptmx_buffer) > 0);
|
2019-11-03 01:03:52 +01:00
|
|
|
|
2019-11-04 12:36:43 +01:00
|
|
|
/* Don't use pop() since we may not be able to write the entire buffer */
|
2019-11-03 01:03:52 +01:00
|
|
|
tll_foreach(term->ptmx_buffer, it) {
|
2019-11-04 13:46:04 +01:00
|
|
|
switch (async_write(term->ptmx, it->item.data, it->item.len, &it->item.idx)) {
|
|
|
|
|
case ASYNC_WRITE_DONE:
|
2019-11-03 01:25:41 +01:00
|
|
|
free(it->item.data);
|
|
|
|
|
tll_remove(term->ptmx_buffer, it);
|
|
|
|
|
break;
|
|
|
|
|
|
2019-11-04 13:46:04 +01:00
|
|
|
case ASYNC_WRITE_REMAIN:
|
|
|
|
|
/* to_slave() updated it->item.idx */
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
case ASYNC_WRITE_ERR:
|
|
|
|
|
LOG_ERRNO("failed to asynchronously write %zu bytes to slave",
|
|
|
|
|
it->item.len - it->item.idx);
|
|
|
|
|
return false;
|
2019-11-03 01:03:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-10 19:51:16 +01:00
|
|
|
/* No more queued data, switch back to synchronous mode */
|
|
|
|
|
fdm_event_del(term->fdm, term->ptmx, EPOLLOUT);
|
2019-11-03 01:03:52 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-31 20:37:43 +01:00
|
|
|
#if PTMX_TIMING
|
2020-04-13 12:03:11 +02:00
|
|
|
static struct timespec last = {};
|
2019-12-31 20:26:30 +01:00
|
|
|
#endif
|
|
|
|
|
|
2019-10-28 18:35:16 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
|
2020-02-03 19:58:32 +01:00
|
|
|
const bool pollin = events & EPOLLIN;
|
|
|
|
|
const bool pollout = events & EPOLLOUT;
|
|
|
|
|
const bool hup = events & EPOLLHUP;
|
2019-11-05 09:23:13 +01:00
|
|
|
|
|
|
|
|
if (pollout) {
|
2019-11-03 01:03:52 +01:00
|
|
|
if (!fdm_ptmx_out(fdm, fd, events, data))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-05 19:50:49 +01:00
|
|
|
/* Prevent blinking while typing */
|
|
|
|
|
term_cursor_blink_restart(term);
|
|
|
|
|
|
2020-03-16 17:05:44 +01:00
|
|
|
term->render.app_sync_updates.flipped = false;
|
|
|
|
|
|
2020-01-10 19:51:16 +01:00
|
|
|
uint8_t buf[24 * 1024];
|
2020-02-05 19:50:49 +01:00
|
|
|
ssize_t count = sizeof(buf);
|
2020-01-10 19:24:45 +01:00
|
|
|
|
2020-02-05 20:24:46 +01:00
|
|
|
const size_t max_iterations = 10;
|
2019-10-28 18:35:16 +01:00
|
|
|
|
2020-02-05 19:50:49 +01:00
|
|
|
for (size_t i = 0; i < max_iterations && pollin && count == sizeof(buf); i++) {
|
|
|
|
|
assert(pollin);
|
|
|
|
|
count = read(term->ptmx, buf, sizeof(buf));
|
2020-01-10 19:51:16 +01:00
|
|
|
|
2020-02-05 19:50:49 +01:00
|
|
|
if (count < 0) {
|
|
|
|
|
LOG_ERRNO("failed to read from pseudo terminal");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vt_from_slave(term, buf, count);
|
|
|
|
|
}
|
2019-12-19 07:23:58 +01:00
|
|
|
|
2020-03-17 16:32:57 +01:00
|
|
|
if (!term->render.app_sync_updates.enabled &&
|
|
|
|
|
!term->render.app_sync_updates.flipped)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* We likely need to re-render. But, we don't want to do it
|
|
|
|
|
* immediately. Often, a single client update is done through
|
|
|
|
|
* multiple writes. This could lead to us rendering one frame with
|
|
|
|
|
* "intermediate" state.
|
|
|
|
|
*
|
|
|
|
|
* For example, we might end up rendering a frame
|
|
|
|
|
* where the client just erased a line, while in the
|
|
|
|
|
* next frame, the client wrote to the same line. This
|
|
|
|
|
* causes screen "flickering".
|
|
|
|
|
*
|
|
|
|
|
* Mitigate by always incuring a small delay before
|
|
|
|
|
* rendering the next frame. This gives the client
|
|
|
|
|
* some time to finish the operation (and thus gives
|
|
|
|
|
* us time to receive the last writes before doing any
|
|
|
|
|
* actual rendering).
|
|
|
|
|
*
|
|
|
|
|
* We incur this delay *every* time we receive
|
|
|
|
|
* input. To ensure we don't delay rendering
|
|
|
|
|
* indefinitely, we start a second timer that is only
|
|
|
|
|
* reset when we render.
|
|
|
|
|
*
|
|
|
|
|
* Note that when the client is producing data at a
|
|
|
|
|
* very high pace, we're rate limited by the wayland
|
|
|
|
|
* compositor anyway. The delay we introduce here only
|
|
|
|
|
* has any effect when the renderer is idle.
|
|
|
|
|
*/
|
delayed rendering: ignore frame callback if delayed rendering is active
Before, we applied delayed rendering (that is, we gave the client a
chance to do more writes before we scheduled a render refresh) only
when the renderer were idle.
However, with e.g. a high keyboard repeat rate, it is very much
possible to start the render loop and then never break out of it while
receiving keyboard input.
This causes screen flickering, as we're no longer even trying to
detect the clients transaction boundaries.
So, let's rewrite how this is done.
First, we give the user the ability to disable delayed rendering
altogether, by setting either the lower or upper timeout to 0.
Second, when delayed rendering is enabled, we ignore the frame
callback. That is, when receiving input, we *always* reschedule the
lower timeout timer, regardless of whether the render is idle or not.
The render's frame callback handler will *not* render the grid if the
delayed render timers are armed.
This means for longer client data bursts, we may now skip frames. That
is, we're trading screen flicker for the occasional frame hickup.
For short client data bursts we should behave roughly as before.
This greatly improves the behavior of fullscreen, or near fullscreen,
updates of large grids (example, scrolling in emacs in fullscreen,
with a vertical buffer split).
2020-03-23 19:21:41 +01:00
|
|
|
uint64_t lower_ns = term->conf->tweak.delayed_render_lower_ns;
|
|
|
|
|
uint64_t upper_ns = term->conf->tweak.delayed_render_upper_ns;
|
2019-12-31 20:26:30 +01:00
|
|
|
|
delayed rendering: ignore frame callback if delayed rendering is active
Before, we applied delayed rendering (that is, we gave the client a
chance to do more writes before we scheduled a render refresh) only
when the renderer were idle.
However, with e.g. a high keyboard repeat rate, it is very much
possible to start the render loop and then never break out of it while
receiving keyboard input.
This causes screen flickering, as we're no longer even trying to
detect the clients transaction boundaries.
So, let's rewrite how this is done.
First, we give the user the ability to disable delayed rendering
altogether, by setting either the lower or upper timeout to 0.
Second, when delayed rendering is enabled, we ignore the frame
callback. That is, when receiving input, we *always* reschedule the
lower timeout timer, regardless of whether the render is idle or not.
The render's frame callback handler will *not* render the grid if the
delayed render timers are armed.
This means for longer client data bursts, we may now skip frames. That
is, we're trading screen flicker for the occasional frame hickup.
For short client data bursts we should behave roughly as before.
This greatly improves the behavior of fullscreen, or near fullscreen,
updates of large grids (example, scrolling in emacs in fullscreen,
with a vertical buffer split).
2020-03-23 19:21:41 +01:00
|
|
|
if (lower_ns > 0 && upper_ns > 0) {
|
2020-01-12 12:23:29 +01:00
|
|
|
#if PTMX_TIMING
|
|
|
|
|
struct timespec now;
|
|
|
|
|
|
|
|
|
|
clock_gettime(1, &now);
|
|
|
|
|
if (last.tv_sec > 0 || last.tv_nsec > 0) {
|
|
|
|
|
struct timeval diff;
|
|
|
|
|
struct timeval l = {last.tv_sec, last.tv_nsec / 1000};
|
|
|
|
|
struct timeval n = {now.tv_sec, now.tv_nsec / 1000};
|
|
|
|
|
|
|
|
|
|
timersub(&n, &l, &diff);
|
|
|
|
|
LOG_INFO("waited %lu µs for more input", diff.tv_usec);
|
|
|
|
|
}
|
|
|
|
|
last = now;
|
2019-12-31 20:26:30 +01:00
|
|
|
#endif
|
|
|
|
|
|
2020-03-17 16:46:54 +01:00
|
|
|
assert(lower_ns < 1000000000);
|
|
|
|
|
assert(upper_ns < 1000000000);
|
|
|
|
|
assert(upper_ns > lower_ns);
|
|
|
|
|
|
2019-10-28 18:35:16 +01:00
|
|
|
timerfd_settime(
|
2020-01-12 12:23:29 +01:00
|
|
|
term->delayed_render_timer.lower_fd, 0,
|
2020-03-17 16:46:54 +01:00
|
|
|
&(struct itimerspec){.it_value = {.tv_nsec = lower_ns}},
|
2019-10-28 18:35:16 +01:00
|
|
|
NULL);
|
2020-01-12 12:23:29 +01:00
|
|
|
|
|
|
|
|
/* Second timeout - only reset when we render. Set to one
|
|
|
|
|
* frame (assuming 60Hz) */
|
|
|
|
|
if (!term->delayed_render_timer.is_armed) {
|
|
|
|
|
timerfd_settime(
|
|
|
|
|
term->delayed_render_timer.upper_fd, 0,
|
2020-03-17 16:46:54 +01:00
|
|
|
&(struct itimerspec){.it_value = {.tv_nsec = upper_ns}},
|
2020-01-12 12:23:29 +01:00
|
|
|
NULL);
|
|
|
|
|
term->delayed_render_timer.is_armed = true;
|
|
|
|
|
}
|
2020-03-17 16:32:57 +01:00
|
|
|
} else
|
delayed rendering: ignore frame callback if delayed rendering is active
Before, we applied delayed rendering (that is, we gave the client a
chance to do more writes before we scheduled a render refresh) only
when the renderer were idle.
However, with e.g. a high keyboard repeat rate, it is very much
possible to start the render loop and then never break out of it while
receiving keyboard input.
This causes screen flickering, as we're no longer even trying to
detect the clients transaction boundaries.
So, let's rewrite how this is done.
First, we give the user the ability to disable delayed rendering
altogether, by setting either the lower or upper timeout to 0.
Second, when delayed rendering is enabled, we ignore the frame
callback. That is, when receiving input, we *always* reschedule the
lower timeout timer, regardless of whether the render is idle or not.
The render's frame callback handler will *not* render the grid if the
delayed render timers are armed.
This means for longer client data bursts, we may now skip frames. That
is, we're trading screen flicker for the occasional frame hickup.
For short client data bursts we should behave roughly as before.
This greatly improves the behavior of fullscreen, or near fullscreen,
updates of large grids (example, scrolling in emacs in fullscreen,
with a vertical buffer split).
2020-03-23 19:21:41 +01:00
|
|
|
render_refresh(term);
|
2020-03-17 16:32:57 +01:00
|
|
|
}
|
2019-10-28 18:35:16 +01:00
|
|
|
|
2020-02-03 19:58:32 +01:00
|
|
|
if (hup) {
|
|
|
|
|
if (term->hold_at_exit) {
|
|
|
|
|
fdm_del(fdm, fd);
|
|
|
|
|
term->ptmx = -1;
|
|
|
|
|
return true;
|
|
|
|
|
} else
|
|
|
|
|
return term_shutdown(term);
|
|
|
|
|
}
|
2019-10-30 20:03:11 +01:00
|
|
|
return true;
|
2019-10-28 18:35:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
fdm_flash(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
uint64_t expiration_count;
|
|
|
|
|
ssize_t ret = read(
|
|
|
|
|
term->flash.fd, &expiration_count, sizeof(expiration_count));
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
2019-11-02 01:44:01 +01:00
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
return true;
|
|
|
|
|
|
2019-10-28 18:35:16 +01:00
|
|
|
LOG_ERRNO("failed to read flash timer");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_DBG("flash timer expired %llu times",
|
|
|
|
|
(unsigned long long)expiration_count);
|
|
|
|
|
|
|
|
|
|
term->flash.active = false;
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
render_refresh(term);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
fdm_blink(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
uint64_t expiration_count;
|
|
|
|
|
ssize_t ret = read(
|
|
|
|
|
term->blink.fd, &expiration_count, sizeof(expiration_count));
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
2019-11-02 01:44:01 +01:00
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
return true;
|
|
|
|
|
|
2019-10-28 18:35:16 +01:00
|
|
|
LOG_ERRNO("failed to read blink timer");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_DBG("blink timer expired %llu times",
|
|
|
|
|
(unsigned long long)expiration_count);
|
|
|
|
|
|
2019-11-04 13:11:15 +01:00
|
|
|
/* Invert blink state */
|
2019-10-28 18:35:16 +01:00
|
|
|
term->blink.state = term->blink.state == BLINK_ON
|
|
|
|
|
? BLINK_OFF : BLINK_ON;
|
|
|
|
|
|
|
|
|
|
/* Scan all visible cells and mark rows with blinking cells dirty */
|
2019-12-17 19:11:27 +01:00
|
|
|
bool no_blinking_cells = true;
|
2019-10-28 18:35:16 +01:00
|
|
|
for (int r = 0; r < term->rows; r++) {
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
for (int col = 0; col < term->cols; col++) {
|
|
|
|
|
struct cell *cell = &row->cells[col];
|
|
|
|
|
|
|
|
|
|
if (cell->attrs.blink) {
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
row->dirty = true;
|
2019-12-17 19:11:27 +01:00
|
|
|
no_blinking_cells = false;
|
2019-10-28 18:35:16 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 19:11:27 +01:00
|
|
|
if (no_blinking_cells) {
|
|
|
|
|
LOG_DBG("disarming blink timer");
|
|
|
|
|
|
|
|
|
|
term->blink.active = false;
|
|
|
|
|
term->blink.state = BLINK_ON;
|
|
|
|
|
|
2020-04-13 12:03:11 +02:00
|
|
|
static const struct itimerspec disarm = {};
|
2019-12-17 19:11:27 +01:00
|
|
|
if (timerfd_settime(term->blink.fd, 0, &disarm, NULL) < 0)
|
|
|
|
|
LOG_ERRNO("failed to disarm blink timer");
|
|
|
|
|
} else
|
|
|
|
|
render_refresh(term);
|
2019-10-28 18:35:16 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 19:11:27 +01:00
|
|
|
void
|
|
|
|
|
term_arm_blink_timer(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term->blink.active)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("arming blink timer");
|
|
|
|
|
struct itimerspec alarm = {
|
|
|
|
|
.it_value = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
|
|
|
|
|
.it_interval = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (timerfd_settime(term->blink.fd, 0, &alarm, NULL) < 0)
|
|
|
|
|
LOG_ERRNO("failed to arm blink timer");
|
|
|
|
|
else
|
|
|
|
|
term->blink.active = true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-16 21:31:40 +01:00
|
|
|
static void
|
|
|
|
|
cursor_refresh(struct terminal *term)
|
|
|
|
|
{
|
2020-04-16 18:51:14 +02:00
|
|
|
term->grid->cur_row->cells[term->grid->cursor.point.col].attrs.clean = 0;
|
2019-12-16 21:31:40 +01:00
|
|
|
term->grid->cur_row->dirty = true;
|
|
|
|
|
render_refresh(term);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-15 15:07:56 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_cursor_blink(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
uint64_t expiration_count;
|
|
|
|
|
ssize_t ret = read(
|
|
|
|
|
term->cursor_blink.fd, &expiration_count, sizeof(expiration_count));
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
LOG_ERRNO("failed to read cursor blink timer");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_DBG("cursor blink timer expired %llu times",
|
|
|
|
|
(unsigned long long)expiration_count);
|
|
|
|
|
|
|
|
|
|
/* Invert blink state */
|
|
|
|
|
term->cursor_blink.state = term->cursor_blink.state == CURSOR_BLINK_ON
|
|
|
|
|
? CURSOR_BLINK_OFF : CURSOR_BLINK_ON;
|
|
|
|
|
|
2019-12-16 21:31:40 +01:00
|
|
|
cursor_refresh(term);
|
2019-12-15 15:07:56 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-28 18:35:16 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_delayed_render(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
|
|
|
|
|
uint64_t unused;
|
|
|
|
|
ssize_t ret1 = 0;
|
|
|
|
|
ssize_t ret2 = 0;
|
|
|
|
|
|
|
|
|
|
if (fd == term->delayed_render_timer.lower_fd)
|
|
|
|
|
ret1 = read(term->delayed_render_timer.lower_fd, &unused, sizeof(unused));
|
|
|
|
|
if (fd == term->delayed_render_timer.upper_fd)
|
|
|
|
|
ret2 = read(term->delayed_render_timer.upper_fd, &unused, sizeof(unused));
|
|
|
|
|
|
2019-11-01 20:27:45 +01:00
|
|
|
if ((ret1 < 0 || ret2 < 0)) {
|
2019-11-02 01:44:01 +01:00
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
return true;
|
|
|
|
|
|
2019-10-28 18:35:16 +01:00
|
|
|
LOG_ERRNO("failed to read timeout timer");
|
2019-11-01 20:27:45 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-31 15:39:40 +01:00
|
|
|
if (ret1 > 0)
|
|
|
|
|
LOG_DBG("lower delay timer expired");
|
|
|
|
|
else if (ret2 > 0)
|
|
|
|
|
LOG_DBG("upper delay timer expired");
|
|
|
|
|
|
2020-03-24 17:41:33 +01:00
|
|
|
if (ret1 == 0 && ret2 == 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
2019-12-31 20:37:43 +01:00
|
|
|
#if PTMX_TIMING
|
2020-04-13 12:03:11 +02:00
|
|
|
last = (struct timespec){};
|
2019-12-31 20:26:30 +01:00
|
|
|
#endif
|
|
|
|
|
|
2019-11-01 20:27:45 +01:00
|
|
|
/* Reset timers */
|
2020-04-13 12:03:11 +02:00
|
|
|
struct itimerspec reset = {};
|
2019-11-01 20:27:45 +01:00
|
|
|
timerfd_settime(term->delayed_render_timer.lower_fd, 0, &reset, NULL);
|
|
|
|
|
timerfd_settime(term->delayed_render_timer.upper_fd, 0, &reset, NULL);
|
2020-03-24 17:41:33 +01:00
|
|
|
term->delayed_render_timer.is_armed = false;
|
2020-03-23 19:16:53 +01:00
|
|
|
|
2020-03-24 17:41:33 +01:00
|
|
|
render_refresh(term);
|
2019-10-28 18:35:16 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-12 12:40:42 +01:00
|
|
|
static bool
|
2020-01-12 12:55:19 +01:00
|
|
|
fdm_app_sync_updates_timeout(
|
2020-01-12 12:40:42 +01:00
|
|
|
struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
uint64_t unused;
|
2020-01-12 12:55:19 +01:00
|
|
|
ssize_t ret = read(term->render.app_sync_updates.timer_fd,
|
2020-01-12 12:40:42 +01:00
|
|
|
&unused, sizeof(unused));
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
return true;
|
|
|
|
|
LOG_ERRNO("failed to read application synchronized updates timeout timer");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
term_disable_app_sync_updates(term);
|
2020-01-12 12:40:42 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 17:40:09 +01:00
|
|
|
static void
|
|
|
|
|
initialize_color_cube(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
/* First 16 entries have already been initialized from conf */
|
|
|
|
|
for (size_t r = 0; r < 6; r++) {
|
|
|
|
|
for (size_t g = 0; g < 6; g++) {
|
|
|
|
|
for (size_t b = 0; b < 6; b++) {
|
|
|
|
|
term->colors.default_table[16 + r * 6 * 6 + g * 6 + b]
|
|
|
|
|
= r * 51 << 16 | g * 51 << 8 | b * 51;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < 24; i++)
|
|
|
|
|
term->colors.default_table[232 + i] = i * 11 << 16 | i * 11 << 8 | i * 11;
|
|
|
|
|
|
|
|
|
|
memcpy(term->colors.table, term->colors.default_table, sizeof(term->colors.table));
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 17:45:59 +01:00
|
|
|
static bool
|
|
|
|
|
initialize_render_workers(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
LOG_INFO("using %zu rendering threads", term->render.workers.count);
|
|
|
|
|
|
2020-05-04 20:11:45 +02:00
|
|
|
if (sem_init(&term->render.workers.start, 0, 0) < 0 ||
|
|
|
|
|
sem_init(&term->render.workers.done, 0, 0) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to instantiate render worker semaphores");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int err;
|
|
|
|
|
if ((err = mtx_init(&term->render.workers.lock, mtx_plain)) != thrd_success) {
|
|
|
|
|
LOG_ERR("failed to instantiate render worker mutex: %s (%d)",
|
|
|
|
|
thrd_err_as_string(err), err);
|
|
|
|
|
goto err_sem_destroy;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((err = cnd_init(&term->render.workers.cond)) != thrd_success) {
|
|
|
|
|
LOG_ERR(
|
|
|
|
|
"failed to instantiate render worker condition variable: %s (%d)",
|
|
|
|
|
thrd_err_as_string(err), err);
|
|
|
|
|
goto err_sem_destroy;
|
|
|
|
|
}
|
2019-10-30 17:45:59 +01:00
|
|
|
|
|
|
|
|
term->render.workers.threads = calloc(
|
|
|
|
|
term->render.workers.count, sizeof(term->render.workers.threads[0]));
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++) {
|
|
|
|
|
struct render_worker_context *ctx = malloc(sizeof(*ctx));
|
|
|
|
|
*ctx = (struct render_worker_context) {
|
|
|
|
|
.term = term,
|
|
|
|
|
.my_id = 1 + i,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int ret = thrd_create(
|
|
|
|
|
&term->render.workers.threads[i], &render_worker_thread, ctx);
|
2020-05-03 14:17:54 +02:00
|
|
|
if (ret != thrd_success) {
|
2020-05-03 12:25:04 +02:00
|
|
|
|
|
|
|
|
LOG_ERR("failed to create render worker thread: %s (%d)",
|
|
|
|
|
thrd_err_as_string(ret), ret);
|
2019-10-30 17:45:59 +01:00
|
|
|
term->render.workers.threads[i] = 0;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2020-05-04 20:11:45 +02:00
|
|
|
|
|
|
|
|
err_sem_destroy:
|
|
|
|
|
sem_destroy(&term->render.workers.start);
|
|
|
|
|
sem_destroy(&term->render.workers.done);
|
|
|
|
|
return false;
|
2019-10-30 17:45:59 +01:00
|
|
|
}
|
|
|
|
|
|
2019-10-30 17:50:12 +01:00
|
|
|
static bool
|
2020-04-21 19:29:36 +02:00
|
|
|
term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4])
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
assert(fonts[i] != NULL);
|
|
|
|
|
|
2020-04-21 19:29:36 +02:00
|
|
|
fcft_destroy(term->fonts[i]);
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
term->fonts[i] = fonts[i];
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-23 11:54:03 +02:00
|
|
|
term->cell_width = term->fonts[0]->space_advance.x > 0
|
|
|
|
|
? term->fonts[0]->space_advance.x : term->fonts[0]->max_advance.x;
|
2020-03-02 18:43:23 +01:00
|
|
|
term->cell_height = max(term->fonts[0]->height,
|
|
|
|
|
term->fonts[0]->ascent + term->fonts[0]->descent);
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height);
|
|
|
|
|
|
2020-03-10 18:07:12 +01:00
|
|
|
render_resize_force(term, term->width / term->scale, term->height / term->scale);
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static unsigned
|
|
|
|
|
get_font_dpi(const struct terminal *term)
|
|
|
|
|
{
|
2020-03-11 16:10:55 +01:00
|
|
|
/*
|
|
|
|
|
* Use output's DPI to scale font. This is to ensure the font has
|
|
|
|
|
* the same physical height (if measured by a ruler) regardless of
|
|
|
|
|
* monitor.
|
|
|
|
|
*
|
|
|
|
|
* Conceptually, we use the physical monitor specs to calculate
|
|
|
|
|
* the DPI, and we ignore the output's scaling factor.
|
|
|
|
|
*
|
|
|
|
|
* However, to deal with fractional scaling, where we're told to
|
|
|
|
|
* render at e.g. 2x, but are then downscaled by the compositor to
|
|
|
|
|
* e.g. 1.25, we use the scaled DPI value multiplied by the scale
|
|
|
|
|
* factor instead.
|
|
|
|
|
*
|
|
|
|
|
* For integral scaling factors the resulting DPI is the same as
|
|
|
|
|
* if we had used the physical DPI.
|
|
|
|
|
*
|
|
|
|
|
* For fractional scaling factors we'll get a DPI *larger* than
|
|
|
|
|
* the physical DPI, that ends up being right when later
|
|
|
|
|
* downscaled by the compositor.
|
|
|
|
|
*/
|
|
|
|
|
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
/* Use highest DPI from outputs we're mapped on */
|
|
|
|
|
unsigned dpi = 0;
|
|
|
|
|
assert(term->window != NULL);
|
|
|
|
|
tll_foreach(term->window->on_outputs, it) {
|
2020-03-11 16:10:55 +01:00
|
|
|
if (it->item->ppi.scaled.y > dpi)
|
|
|
|
|
dpi = it->item->ppi.scaled.y * term->scale;
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we're not mapped, use DPI from first monitor. Hopefully this is where we'll get mapped later... */
|
|
|
|
|
if (dpi == 0) {
|
|
|
|
|
tll_foreach(term->wl->monitors, it) {
|
2020-03-11 16:10:55 +01:00
|
|
|
dpi = it->item.ppi.scaled.y * term->scale;
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dpi == 0) {
|
|
|
|
|
/* No monitors? */
|
|
|
|
|
dpi = 96;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dpi;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 19:29:36 +02:00
|
|
|
static enum fcft_subpixel
|
2020-04-20 18:37:59 +02:00
|
|
|
get_font_subpixel(const struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term->colors.alpha != 0xffff) {
|
|
|
|
|
/* Can't do subpixel rendering on transparent background */
|
2020-04-21 19:29:36 +02:00
|
|
|
return FCFT_SUBPIXEL_NONE;
|
2020-04-20 18:37:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum wl_output_subpixel wl_subpixel;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Wayland doesn't tell us *which* part of the surface that goes
|
|
|
|
|
* on a specific output, only whether the surface is mapped to an
|
|
|
|
|
* output or not.
|
|
|
|
|
*
|
|
|
|
|
* Thus, when determining which subpixel mode to use, we can't do
|
|
|
|
|
* much but select *an* output. So, we pick the first one.
|
|
|
|
|
*
|
|
|
|
|
* If we're not mapped at all, we pick the first available
|
|
|
|
|
* monitor, and hope that's where we'll eventually get mapped.
|
|
|
|
|
*
|
|
|
|
|
* If there aren't any monitors we use the "default" subpixel
|
|
|
|
|
* mode.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (tll_length(term->window->on_outputs) > 0)
|
|
|
|
|
wl_subpixel = tll_front(term->window->on_outputs)->subpixel;
|
|
|
|
|
else if (tll_length(term->wl->monitors) > 0)
|
|
|
|
|
wl_subpixel = tll_front(term->wl->monitors).subpixel;
|
|
|
|
|
else
|
|
|
|
|
wl_subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
|
|
|
|
|
|
|
|
|
|
switch (wl_subpixel) {
|
2020-04-21 19:29:36 +02:00
|
|
|
case WL_OUTPUT_SUBPIXEL_UNKNOWN: return FCFT_SUBPIXEL_DEFAULT;
|
|
|
|
|
case WL_OUTPUT_SUBPIXEL_NONE: return FCFT_SUBPIXEL_NONE;
|
|
|
|
|
case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: return FCFT_SUBPIXEL_HORIZONTAL_RGB;
|
|
|
|
|
case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: return FCFT_SUBPIXEL_HORIZONTAL_BGR;
|
|
|
|
|
case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: return FCFT_SUBPIXEL_VERTICAL_RGB;
|
|
|
|
|
case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: return FCFT_SUBPIXEL_VERTICAL_BGR;
|
2020-04-20 18:37:59 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-21 19:29:36 +02:00
|
|
|
return FCFT_SUBPIXEL_DEFAULT;
|
2020-04-20 18:37:59 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-28 22:07:02 +02:00
|
|
|
struct font_load_data {
|
|
|
|
|
size_t count;
|
|
|
|
|
const char **names;
|
|
|
|
|
const char *attrs;
|
|
|
|
|
|
|
|
|
|
struct fcft_font **font;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
font_loader_thread(void *_data)
|
|
|
|
|
{
|
|
|
|
|
struct font_load_data *data = _data;
|
|
|
|
|
*data->font = fcft_from_name(data->count, data->names, data->attrs);
|
|
|
|
|
return *data->font != NULL;
|
|
|
|
|
}
|
|
|
|
|
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
static bool
|
|
|
|
|
load_fonts_from_conf(const struct terminal *term, const struct config *conf,
|
2020-04-21 19:29:36 +02:00
|
|
|
struct fcft_font *fonts[static 4])
|
2019-10-30 17:50:12 +01:00
|
|
|
{
|
2019-12-01 19:22:25 +01:00
|
|
|
const size_t count = tll_length(conf->fonts);
|
|
|
|
|
const char *names[count];
|
2019-10-30 17:50:12 +01:00
|
|
|
|
2019-12-01 19:22:25 +01:00
|
|
|
size_t i = 0;
|
|
|
|
|
tll_foreach(conf->fonts, it)
|
|
|
|
|
names[i++] = it->item;
|
2019-10-30 17:50:12 +01:00
|
|
|
|
2019-12-04 22:02:02 +01:00
|
|
|
char attrs0[64], attrs1[64], attrs2[64], attrs3[64];
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
snprintf(attrs0, sizeof(attrs0), "dpi=%u", term->font_dpi);
|
|
|
|
|
snprintf(attrs1, sizeof(attrs1), "dpi=%u:weight=bold", term->font_dpi);
|
|
|
|
|
snprintf(attrs2, sizeof(attrs2), "dpi=%u:slant=italic", term->font_dpi);
|
|
|
|
|
snprintf(attrs3, sizeof(attrs3), "dpi=%u:weight=bold:slant=italic", term->font_dpi);
|
2019-12-04 22:02:02 +01:00
|
|
|
|
2020-04-28 22:07:02 +02:00
|
|
|
struct font_load_data data[4] = {
|
|
|
|
|
{count, names, attrs0, &fonts[0]},
|
|
|
|
|
{count, names, attrs1, &fonts[1]},
|
|
|
|
|
{count, names, attrs2, &fonts[2]},
|
|
|
|
|
{count, names, attrs3, &fonts[3]},
|
|
|
|
|
};
|
|
|
|
|
|
2020-05-03 12:25:04 +02:00
|
|
|
thrd_t tids[4] = {};
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
int ret = thrd_create(&tids[i], &font_loader_thread, &data[i]);
|
2020-05-03 14:17:54 +02:00
|
|
|
if (ret != thrd_success) {
|
2020-05-03 12:25:04 +02:00
|
|
|
LOG_ERR("failed to create font loader thread: %s (%d)",
|
|
|
|
|
thrd_err_as_string(ret), ret);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-28 22:07:02 +02:00
|
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
2020-05-03 12:25:04 +02:00
|
|
|
if (tids[i] != 0) {
|
|
|
|
|
int ret;
|
|
|
|
|
thrd_join(tids[i], &ret);
|
|
|
|
|
success = success && ret;
|
|
|
|
|
} else
|
|
|
|
|
success = false;
|
2020-04-28 22:07:02 +02:00
|
|
|
}
|
2020-02-08 17:57:50 +01:00
|
|
|
|
2020-04-28 22:07:02 +02:00
|
|
|
if (!success) {
|
2020-02-09 16:56:59 +01:00
|
|
|
LOG_ERR("failed to load primary fonts");
|
2020-02-08 17:57:50 +01:00
|
|
|
for (size_t i = 0; i < 4; i++) {
|
2020-04-21 19:29:36 +02:00
|
|
|
fcft_destroy(fonts[i]);
|
2020-02-08 17:57:50 +01:00
|
|
|
fonts[i] = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-28 22:07:02 +02:00
|
|
|
return success;
|
2019-10-30 17:50:12 +01:00
|
|
|
}
|
|
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
struct terminal *
|
|
|
|
|
term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl,
|
2020-03-27 21:14:49 +01:00
|
|
|
const char *foot_exe, const char *cwd, int argc, char *const *argv,
|
2019-11-01 20:34:32 +01:00
|
|
|
void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data)
|
2019-10-28 18:25:19 +01:00
|
|
|
{
|
2019-10-28 18:46:03 +01:00
|
|
|
int ptmx = -1;
|
|
|
|
|
int flash_fd = -1;
|
|
|
|
|
int blink_fd = -1;
|
2019-12-15 15:07:56 +01:00
|
|
|
int cursor_blink_fd = -1;
|
2019-10-28 18:46:03 +01:00
|
|
|
int delay_lower_fd = -1;
|
|
|
|
|
int delay_upper_fd = -1;
|
2020-01-12 12:55:19 +01:00
|
|
|
int app_sync_updates_fd = -1;
|
2019-10-28 18:46:03 +01:00
|
|
|
|
2019-10-30 20:22:01 +01:00
|
|
|
struct terminal *term = malloc(sizeof(*term));
|
2019-10-28 18:46:03 +01:00
|
|
|
|
|
|
|
|
if ((ptmx = posix_openpt(O_RDWR | O_NOCTTY)) == -1) {
|
|
|
|
|
LOG_ERRNO("failed to open PTY");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
2020-01-10 21:33:40 +01:00
|
|
|
if ((flash_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) {
|
2019-10-28 18:46:03 +01:00
|
|
|
LOG_ERRNO("failed to create flash timer FD");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
2020-01-10 21:33:40 +01:00
|
|
|
if ((blink_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) {
|
2019-10-28 18:46:03 +01:00
|
|
|
LOG_ERRNO("failed to create blink timer FD");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
2020-01-10 21:33:40 +01:00
|
|
|
if ((cursor_blink_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1) {
|
2019-12-15 15:07:56 +01:00
|
|
|
LOG_ERRNO("failed to create cursor blink timer FD");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
2020-01-10 21:33:40 +01:00
|
|
|
if ((delay_lower_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1 ||
|
|
|
|
|
(delay_upper_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1)
|
2019-10-28 18:46:03 +01:00
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to create delayed rendering timer FDs");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
if ((app_sync_updates_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) == -1)
|
2020-01-12 12:40:42 +01:00
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to create application synchronized updates timer FD");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-03 01:03:52 +01:00
|
|
|
int ptmx_flags;
|
|
|
|
|
if ((ptmx_flags = fcntl(ptmx, F_GETFL)) < 0 ||
|
|
|
|
|
fcntl(ptmx, F_SETFL, ptmx_flags | O_NONBLOCK) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to configure ptmx as non-blocking");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-30 17:22:57 +02:00
|
|
|
/*
|
|
|
|
|
* Enable all FDM callbackes *except* ptmx - we can't do that
|
|
|
|
|
* until the window has been 'configured' since we don't have a
|
|
|
|
|
* size (and thus no grid) before then.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (!fdm_add(fdm, flash_fd, EPOLLIN, &fdm_flash, term) ||
|
2019-11-03 00:25:17 +01:00
|
|
|
!fdm_add(fdm, blink_fd, EPOLLIN, &fdm_blink, term) ||
|
2019-12-15 15:07:56 +01:00
|
|
|
!fdm_add(fdm, cursor_blink_fd, EPOLLIN, &fdm_cursor_blink, term) ||
|
2019-11-03 00:25:17 +01:00
|
|
|
!fdm_add(fdm, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) ||
|
2020-01-12 12:40:42 +01:00
|
|
|
!fdm_add(fdm, delay_upper_fd, EPOLLIN, &fdm_delayed_render, term) ||
|
2020-01-12 12:55:19 +01:00
|
|
|
!fdm_add(fdm, app_sync_updates_fd, EPOLLIN, &fdm_app_sync_updates_timeout, term))
|
2019-10-30 20:22:01 +01:00
|
|
|
{
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 18:05:03 +01:00
|
|
|
/* Initialize configure-based terminal attributes */
|
2019-10-28 18:25:19 +01:00
|
|
|
*term = (struct terminal) {
|
|
|
|
|
.fdm = fdm,
|
2020-02-08 14:09:06 +01:00
|
|
|
.conf = conf,
|
2019-10-28 18:25:19 +01:00
|
|
|
.quit = false,
|
2019-10-28 18:46:03 +01:00
|
|
|
.ptmx = ptmx,
|
2019-11-03 01:03:52 +01:00
|
|
|
.ptmx_buffer = tll_init(),
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
.font_dpi = 0,
|
|
|
|
|
.font_adjustments = 0,
|
2020-04-20 18:37:59 +02:00
|
|
|
.font_subpixel = (conf->colors.alpha == 0xffff /* Can't do subpixel rendering on transparent background */
|
2020-04-21 19:29:36 +02:00
|
|
|
? FCFT_SUBPIXEL_DEFAULT
|
|
|
|
|
: FCFT_SUBPIXEL_NONE),
|
2019-10-28 18:25:19 +01:00
|
|
|
.cursor_keys_mode = CURSOR_KEYS_NORMAL,
|
|
|
|
|
.keypad_keys_mode = KEYPAD_NUMERICAL,
|
|
|
|
|
.auto_margin = true,
|
|
|
|
|
.window_title_stack = tll_init(),
|
|
|
|
|
.scale = 1,
|
2019-10-28 18:46:03 +01:00
|
|
|
.flash = {.fd = flash_fd},
|
|
|
|
|
.blink = {.fd = blink_fd},
|
2019-10-28 18:25:19 +01:00
|
|
|
.vt = {
|
2020-01-20 18:35:13 +01:00
|
|
|
.state = 0, /* STATE_GROUND */
|
2019-10-28 18:25:19 +01:00
|
|
|
},
|
|
|
|
|
.colors = {
|
2019-10-30 17:40:09 +01:00
|
|
|
.fg = conf->colors.fg,
|
|
|
|
|
.bg = conf->colors.bg,
|
2019-10-28 18:25:19 +01:00
|
|
|
.default_fg = conf->colors.fg,
|
|
|
|
|
.default_bg = conf->colors.bg,
|
|
|
|
|
.default_table = {
|
|
|
|
|
conf->colors.regular[0],
|
|
|
|
|
conf->colors.regular[1],
|
|
|
|
|
conf->colors.regular[2],
|
|
|
|
|
conf->colors.regular[3],
|
|
|
|
|
conf->colors.regular[4],
|
|
|
|
|
conf->colors.regular[5],
|
|
|
|
|
conf->colors.regular[6],
|
|
|
|
|
conf->colors.regular[7],
|
|
|
|
|
|
|
|
|
|
conf->colors.bright[0],
|
|
|
|
|
conf->colors.bright[1],
|
|
|
|
|
conf->colors.bright[2],
|
|
|
|
|
conf->colors.bright[3],
|
|
|
|
|
conf->colors.bright[4],
|
|
|
|
|
conf->colors.bright[5],
|
|
|
|
|
conf->colors.bright[6],
|
|
|
|
|
conf->colors.bright[7],
|
|
|
|
|
},
|
|
|
|
|
.alpha = conf->colors.alpha,
|
|
|
|
|
},
|
2019-11-05 13:27:37 +01:00
|
|
|
.origin = ORIGIN_ABSOLUTE,
|
2019-10-28 18:25:19 +01:00
|
|
|
.default_cursor_style = conf->cursor.style,
|
|
|
|
|
.cursor_style = conf->cursor.style,
|
2019-12-15 15:07:56 +01:00
|
|
|
.cursor_blink = {
|
2019-12-16 21:31:40 +01:00
|
|
|
.active = false,
|
2019-12-15 15:07:56 +01:00
|
|
|
.state = CURSOR_BLINK_ON,
|
|
|
|
|
.fd = cursor_blink_fd,
|
|
|
|
|
},
|
2019-10-28 18:25:19 +01:00
|
|
|
.default_cursor_color = {
|
|
|
|
|
.text = conf->cursor.color.text,
|
|
|
|
|
.cursor = conf->cursor.color.cursor,
|
|
|
|
|
},
|
|
|
|
|
.cursor_color = {
|
|
|
|
|
.text = conf->cursor.color.text,
|
|
|
|
|
.cursor = conf->cursor.color.cursor,
|
|
|
|
|
},
|
2019-11-28 19:35:47 +01:00
|
|
|
.xcursor = "text",
|
2019-10-28 18:25:19 +01:00
|
|
|
.selection = {
|
|
|
|
|
.start = {-1, -1},
|
|
|
|
|
.end = {-1, -1},
|
|
|
|
|
},
|
2020-03-13 18:44:23 +01:00
|
|
|
.normal = {.damage = tll_init(), .scroll_damage = tll_init(), .sixel_images = tll_init()},
|
|
|
|
|
.alt = {.damage = tll_init(), .scroll_damage = tll_init(), .sixel_images = tll_init()},
|
2019-10-28 18:25:19 +01:00
|
|
|
.grid = &term->normal,
|
unicode-combining: store seen combining chains "globally" in the term struct
Instead of storing combining data per cell, realize that most
combinations are re-occurring and that there's lots of available space
left in the unicode range, and store seen base+combining combinations
chains in a per-terminal array.
When we encounter a combining character, we first try to pre-compose,
like before. If that fails, we then search for the current
base+combining combo in the list of previously seen combinations. If
not found there either, we allocate a new combo and add it to the
list. Regardless, the result is an index into this array. We store
this index, offsetted by COMB_CHARS_LO=0x40000000ul in the cell.
When rendering, we need to check if the cell character is a plain
character, or if it's a composed character (identified by checking if
the cell character is >= COMB_CHARS_LO).
Then we render the grapheme pretty much like before.
2020-05-03 11:03:22 +02:00
|
|
|
.composed_count = 0,
|
|
|
|
|
.composed = NULL,
|
2020-01-20 18:38:50 +01:00
|
|
|
.meta = {
|
|
|
|
|
.esc_prefix = true,
|
|
|
|
|
.eight_bit = true,
|
|
|
|
|
},
|
2019-11-16 10:54:21 +01:00
|
|
|
.tab_stops = tll_init(),
|
2019-10-28 18:25:19 +01:00
|
|
|
.wl = wayl,
|
|
|
|
|
.render = {
|
|
|
|
|
.scrollback_lines = conf->scrollback_lines,
|
2020-01-12 12:55:19 +01:00
|
|
|
.app_sync_updates.timer_fd = app_sync_updates_fd,
|
2019-10-28 18:25:19 +01:00
|
|
|
.workers = {
|
|
|
|
|
.count = conf->render_worker_count,
|
|
|
|
|
.queue = tll_init(),
|
|
|
|
|
},
|
2019-12-31 15:39:40 +01:00
|
|
|
.presentation_timings = conf->presentation_timings,
|
2019-10-28 18:25:19 +01:00
|
|
|
},
|
|
|
|
|
.delayed_render_timer = {
|
|
|
|
|
.is_armed = false,
|
2019-10-28 18:46:03 +01:00
|
|
|
.lower_fd = delay_lower_fd,
|
|
|
|
|
.upper_fd = delay_upper_fd,
|
2019-10-28 18:25:19 +01:00
|
|
|
},
|
2020-02-22 14:02:00 +01:00
|
|
|
.sixel = {
|
|
|
|
|
.palette_size = SIXEL_MAX_COLORS,
|
|
|
|
|
},
|
2020-02-03 19:58:32 +01:00
|
|
|
.hold_at_exit = conf->hold_at_exit,
|
2019-11-01 20:34:32 +01:00
|
|
|
.shutdown_cb = shutdown_cb,
|
|
|
|
|
.shutdown_data = shutdown_data,
|
2019-12-21 15:27:17 +01:00
|
|
|
.foot_exe = strdup(foot_exe),
|
2019-12-21 19:57:28 +01:00
|
|
|
.cwd = strdup(cwd),
|
2019-10-28 18:25:19 +01:00
|
|
|
};
|
|
|
|
|
|
2020-04-30 11:39:41 +02:00
|
|
|
/* Start the slave/client */
|
|
|
|
|
if ((term->slave = slave_spawn(
|
|
|
|
|
term->ptmx, argc, term->cwd, argv,
|
|
|
|
|
conf->term, conf->shell, conf->login_shell)) == -1)
|
|
|
|
|
{
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
/* Guess scale; we're not mapped yet, so we don't know on which
|
|
|
|
|
* output we'll be. Pick highest scale we find for now */
|
|
|
|
|
tll_foreach(term->wl->monitors, it) {
|
|
|
|
|
if (it->item.scale > term->scale)
|
|
|
|
|
term->scale = it->item.scale;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 17:40:09 +01:00
|
|
|
initialize_color_cube(term);
|
2020-04-30 11:39:41 +02:00
|
|
|
|
2020-01-03 11:15:35 +01:00
|
|
|
/* Initialize the Wayland window backend */
|
2020-01-03 13:37:03 +01:00
|
|
|
if ((term->window = wayl_win_init(term)) == NULL)
|
2019-10-28 18:46:03 +01:00
|
|
|
goto err;
|
2019-10-28 18:25:19 +01:00
|
|
|
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
/* Load fonts */
|
2020-02-28 18:35:05 +01:00
|
|
|
if (!term_font_dpi_changed(term))
|
|
|
|
|
goto err;
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
|
2020-04-22 19:38:38 +02:00
|
|
|
term->font_subpixel = get_font_subpixel(term);
|
|
|
|
|
|
2020-04-01 19:59:47 +02:00
|
|
|
term_set_window_title(term, conf->title);
|
2020-03-09 18:46:50 +01:00
|
|
|
|
|
|
|
|
/* Let the Wayland backend know we exist */
|
|
|
|
|
tll_push_back(wayl->terms, term);
|
|
|
|
|
|
2020-03-26 19:39:12 +01:00
|
|
|
switch (conf->startup_mode) {
|
|
|
|
|
case STARTUP_WINDOWED:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case STARTUP_MAXIMIZED:
|
|
|
|
|
xdg_toplevel_set_maximized(term->window->xdg_toplevel);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case STARTUP_FULLSCREEN:
|
|
|
|
|
xdg_toplevel_set_fullscreen(term->window->xdg_toplevel, NULL);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-29 20:07:21 +02:00
|
|
|
if (!initialize_render_workers(term))
|
|
|
|
|
goto err;
|
2019-10-28 18:25:19 +01:00
|
|
|
|
|
|
|
|
return term;
|
|
|
|
|
|
2019-10-28 18:46:03 +01:00
|
|
|
err:
|
2020-05-04 20:46:27 +02:00
|
|
|
term->is_shutting_down = true;
|
2019-10-28 18:25:19 +01:00
|
|
|
term_destroy(term);
|
|
|
|
|
return NULL;
|
2019-10-28 18:46:03 +01:00
|
|
|
|
|
|
|
|
close_fds:
|
2020-04-30 17:22:57 +02:00
|
|
|
close(ptmx);
|
2019-11-01 20:29:16 +01:00
|
|
|
fdm_del(fdm, flash_fd);
|
|
|
|
|
fdm_del(fdm, blink_fd);
|
2019-12-15 15:07:56 +01:00
|
|
|
fdm_del(fdm, cursor_blink_fd);
|
2019-11-01 20:29:16 +01:00
|
|
|
fdm_del(fdm, delay_lower_fd);
|
|
|
|
|
fdm_del(fdm, delay_upper_fd);
|
2020-01-12 12:55:19 +01:00
|
|
|
fdm_del(fdm, app_sync_updates_fd);
|
2019-11-01 20:29:16 +01:00
|
|
|
|
2019-10-30 20:22:01 +01:00
|
|
|
free(term);
|
2019-10-28 18:46:03 +01:00
|
|
|
return NULL;
|
2019-10-28 18:25:19 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-30 17:22:57 +02:00
|
|
|
void
|
|
|
|
|
term_window_configured(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
/* Enable ptmx FDM callback */
|
2020-05-04 20:49:28 +02:00
|
|
|
if (!term->is_shutting_down) {
|
|
|
|
|
assert(term->window->is_configured);
|
|
|
|
|
fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term);
|
|
|
|
|
}
|
2020-04-30 17:22:57 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-30 20:03:11 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_shutdown(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
LOG_DBG("FDM shutdown");
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
|
2019-11-01 20:30:58 +01:00
|
|
|
/* Kill the event FD */
|
2019-10-30 20:03:11 +01:00
|
|
|
fdm_del(term->fdm, fd);
|
|
|
|
|
|
2019-11-01 20:30:58 +01:00
|
|
|
wayl_win_destroy(term->window);
|
|
|
|
|
term->window = NULL;
|
2019-10-30 20:03:11 +01:00
|
|
|
|
2019-11-01 20:30:58 +01:00
|
|
|
struct wayland *wayl __attribute__((unused)) = term->wl;
|
2019-11-21 18:18:35 +01:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Normally we'd get unmapped when we destroy the Wayland
|
|
|
|
|
* above.
|
|
|
|
|
*
|
|
|
|
|
* However, it appears that under certain conditions, those events
|
|
|
|
|
* are deferred (for example, when a screen locker is active), and
|
|
|
|
|
* thus we can get here without having been unmapped.
|
|
|
|
|
*/
|
2020-01-02 15:58:52 +01:00
|
|
|
if (wayl->kbd_focus == term)
|
|
|
|
|
wayl->kbd_focus = NULL;
|
|
|
|
|
if (wayl->mouse_focus == term)
|
|
|
|
|
wayl->mouse_focus = NULL;
|
2019-11-21 18:18:35 +01:00
|
|
|
|
2020-01-02 15:58:52 +01:00
|
|
|
assert(wayl->kbd_focus != term);
|
|
|
|
|
assert(wayl->mouse_focus != term);
|
2019-10-30 20:03:11 +01:00
|
|
|
|
2019-11-01 20:34:32 +01:00
|
|
|
void (*cb)(void *, int) = term->shutdown_cb;
|
|
|
|
|
void *cb_data = term->shutdown_data;
|
2019-10-28 18:25:19 +01:00
|
|
|
|
2019-11-01 20:30:58 +01:00
|
|
|
int exit_code = term_destroy(term);
|
2019-11-01 20:34:32 +01:00
|
|
|
if (cb != NULL)
|
|
|
|
|
cb(cb_data, exit_code);
|
2019-11-01 20:30:58 +01:00
|
|
|
|
2019-10-30 20:03:11 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_shutdown(struct terminal *term)
|
|
|
|
|
{
|
2019-11-01 20:30:58 +01:00
|
|
|
if (term->is_shutting_down)
|
|
|
|
|
return true;
|
2019-10-30 20:03:11 +01:00
|
|
|
|
2019-11-01 20:30:58 +01:00
|
|
|
term->is_shutting_down = true;
|
2019-10-30 20:03:11 +01:00
|
|
|
|
|
|
|
|
/*
|
2019-11-01 20:30:58 +01:00
|
|
|
* Close FDs then postpone self-destruction to the next poll
|
|
|
|
|
* iteration, by creating an event FD that we trigger immediately.
|
2019-10-30 20:03:11 +01:00
|
|
|
*/
|
|
|
|
|
|
2019-12-16 21:32:57 +01:00
|
|
|
term_cursor_blink_disable(term);
|
|
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
|
2019-11-01 20:30:58 +01:00
|
|
|
fdm_del(term->fdm, term->delayed_render_timer.lower_fd);
|
|
|
|
|
fdm_del(term->fdm, term->delayed_render_timer.upper_fd);
|
2019-12-16 21:32:57 +01:00
|
|
|
fdm_del(term->fdm, term->cursor_blink.fd);
|
2019-11-01 20:30:58 +01:00
|
|
|
fdm_del(term->fdm, term->blink.fd);
|
|
|
|
|
fdm_del(term->fdm, term->flash.fd);
|
2020-04-30 17:22:57 +02:00
|
|
|
|
|
|
|
|
if (term->window != NULL && term->window->is_configured)
|
|
|
|
|
fdm_del(term->fdm, term->ptmx);
|
|
|
|
|
else
|
|
|
|
|
close(term->ptmx);
|
2019-11-01 20:30:58 +01:00
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
term->render.app_sync_updates.timer_fd = -1;
|
2019-11-01 20:30:58 +01:00
|
|
|
term->delayed_render_timer.lower_fd = -1;
|
|
|
|
|
term->delayed_render_timer.upper_fd = -1;
|
2019-12-16 21:32:57 +01:00
|
|
|
term->cursor_blink.fd = -1;
|
2019-11-01 20:30:58 +01:00
|
|
|
term->blink.fd = -1;
|
|
|
|
|
term->flash.fd = -1;
|
|
|
|
|
term->ptmx = -1;
|
|
|
|
|
|
2020-01-10 19:25:56 +01:00
|
|
|
int event_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
2019-10-30 20:03:11 +01:00
|
|
|
if (event_fd == -1) {
|
|
|
|
|
LOG_ERRNO("failed to create terminal shutdown event FD");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-10-28 18:25:19 +01:00
|
|
|
|
2019-10-30 20:03:11 +01:00
|
|
|
if (!fdm_add(term->fdm, event_fd, EPOLLIN, &fdm_shutdown, term)) {
|
|
|
|
|
close(event_fd);
|
|
|
|
|
return false;
|
2019-10-28 18:35:16 +01:00
|
|
|
}
|
|
|
|
|
|
2019-10-30 20:03:11 +01:00
|
|
|
if (write(event_fd, &(uint64_t){1}, sizeof(uint64_t)) != sizeof(uint64_t)) {
|
|
|
|
|
LOG_ERRNO("failed to send terminal shutdown event");
|
|
|
|
|
fdm_del(term->fdm, event_fd);
|
|
|
|
|
return false;
|
2019-10-28 18:35:16 +01:00
|
|
|
}
|
2019-10-28 18:25:19 +01:00
|
|
|
|
2019-10-30 20:03:11 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-02 12:02:11 +01:00
|
|
|
static volatile sig_atomic_t alarm_raised;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
sig_alarm(int signo)
|
|
|
|
|
{
|
|
|
|
|
LOG_DBG("SIGALRM");
|
|
|
|
|
alarm_raised = 1;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 20:03:11 +01:00
|
|
|
int
|
|
|
|
|
term_destroy(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term == NULL)
|
|
|
|
|
return 0;
|
|
|
|
|
|
2019-11-01 20:30:58 +01:00
|
|
|
tll_foreach(term->wl->terms, it) {
|
|
|
|
|
if (it->item == term) {
|
|
|
|
|
tll_remove(term->wl->terms, it);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
|
2019-11-01 20:30:58 +01:00
|
|
|
fdm_del(term->fdm, term->delayed_render_timer.lower_fd);
|
|
|
|
|
fdm_del(term->fdm, term->delayed_render_timer.upper_fd);
|
2019-12-15 15:07:56 +01:00
|
|
|
fdm_del(term->fdm, term->cursor_blink.fd);
|
2019-11-01 20:30:58 +01:00
|
|
|
fdm_del(term->fdm, term->blink.fd);
|
|
|
|
|
fdm_del(term->fdm, term->flash.fd);
|
|
|
|
|
fdm_del(term->fdm, term->ptmx);
|
2019-10-30 20:03:11 +01:00
|
|
|
|
|
|
|
|
if (term->window != NULL)
|
|
|
|
|
wayl_win_destroy(term->window);
|
|
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
mtx_lock(&term->render.workers.lock);
|
|
|
|
|
assert(tll_length(term->render.workers.queue) == 0);
|
2019-10-30 17:45:59 +01:00
|
|
|
|
|
|
|
|
/* Count livinig threads - we may get here when only some of the
|
|
|
|
|
* threads have been successfully started */
|
|
|
|
|
size_t worker_count = 0;
|
2020-04-29 20:08:19 +02:00
|
|
|
if (term->render.workers.threads != NULL) {
|
|
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++, worker_count++) {
|
|
|
|
|
if (term->render.workers.threads[i] == 0)
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-10-30 17:45:59 +01:00
|
|
|
|
2020-04-29 20:08:19 +02:00
|
|
|
for (size_t i = 0; i < worker_count; i++) {
|
|
|
|
|
sem_post(&term->render.workers.start);
|
|
|
|
|
tll_push_back(term->render.workers.queue, -2);
|
|
|
|
|
}
|
|
|
|
|
cnd_broadcast(&term->render.workers.cond);
|
2019-10-28 18:25:19 +01:00
|
|
|
}
|
|
|
|
|
mtx_unlock(&term->render.workers.lock);
|
2019-10-30 17:45:59 +01:00
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
free(term->vt.osc.data);
|
|
|
|
|
for (int row = 0; row < term->normal.num_rows; row++)
|
|
|
|
|
grid_row_free(term->normal.rows[row]);
|
|
|
|
|
free(term->normal.rows);
|
|
|
|
|
for (int row = 0; row < term->alt.num_rows; row++)
|
|
|
|
|
grid_row_free(term->alt.rows[row]);
|
|
|
|
|
free(term->alt.rows);
|
|
|
|
|
|
2020-04-19 15:26:52 +02:00
|
|
|
tll_free(term->normal.scroll_damage);
|
|
|
|
|
tll_free(term->alt.scroll_damage);
|
|
|
|
|
|
unicode-combining: store seen combining chains "globally" in the term struct
Instead of storing combining data per cell, realize that most
combinations are re-occurring and that there's lots of available space
left in the unicode range, and store seen base+combining combinations
chains in a per-terminal array.
When we encounter a combining character, we first try to pre-compose,
like before. If that fails, we then search for the current
base+combining combo in the list of previously seen combinations. If
not found there either, we allocate a new combo and add it to the
list. Regardless, the result is an index into this array. We store
this index, offsetted by COMB_CHARS_LO=0x40000000ul in the cell.
When rendering, we need to check if the cell character is a plain
character, or if it's a composed character (identified by checking if
the cell character is >= COMB_CHARS_LO).
Then we render the grapheme pretty much like before.
2020-05-03 11:03:22 +02:00
|
|
|
free(term->composed);
|
|
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
free(term->window_title);
|
|
|
|
|
tll_free_and_free(term->window_title_stack, free);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof(term->fonts) / sizeof(term->fonts[0]); i++)
|
2020-04-21 19:29:36 +02:00
|
|
|
fcft_destroy(term->fonts[i]);
|
2019-10-28 18:25:19 +01:00
|
|
|
|
|
|
|
|
free(term->search.buf);
|
|
|
|
|
|
2020-04-29 20:08:19 +02:00
|
|
|
if (term->render.workers.threads != NULL) {
|
|
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++) {
|
|
|
|
|
if (term->render.workers.threads[i] != 0)
|
|
|
|
|
thrd_join(term->render.workers.threads[i], NULL);
|
|
|
|
|
}
|
2019-10-30 17:45:59 +01:00
|
|
|
}
|
2019-10-28 18:25:19 +01:00
|
|
|
free(term->render.workers.threads);
|
|
|
|
|
cnd_destroy(&term->render.workers.cond);
|
|
|
|
|
mtx_destroy(&term->render.workers.lock);
|
|
|
|
|
sem_destroy(&term->render.workers.start);
|
|
|
|
|
sem_destroy(&term->render.workers.done);
|
|
|
|
|
assert(tll_length(term->render.workers.queue) == 0);
|
|
|
|
|
tll_free(term->render.workers.queue);
|
|
|
|
|
|
2019-11-03 01:03:52 +01:00
|
|
|
tll_foreach(term->ptmx_buffer, it)
|
|
|
|
|
free(it->item.data);
|
|
|
|
|
tll_free(term->ptmx_buffer);
|
2019-11-16 10:54:21 +01:00
|
|
|
tll_free(term->tab_stops);
|
2019-12-21 15:35:54 +01:00
|
|
|
|
2020-03-13 18:44:23 +01:00
|
|
|
tll_foreach(term->normal.sixel_images, it)
|
2020-02-22 00:23:19 +01:00
|
|
|
sixel_destroy(&it->item);
|
2020-03-13 18:44:23 +01:00
|
|
|
tll_free(term->normal.sixel_images);
|
|
|
|
|
tll_foreach(term->alt.sixel_images, it)
|
|
|
|
|
sixel_destroy(&it->item);
|
|
|
|
|
tll_free(term->alt.sixel_images);
|
2020-02-21 23:40:35 +01:00
|
|
|
|
2019-12-21 15:27:17 +01:00
|
|
|
free(term->foot_exe);
|
2019-12-21 15:35:54 +01:00
|
|
|
free(term->cwd);
|
2019-11-03 01:03:52 +01:00
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
int ret = EXIT_SUCCESS;
|
|
|
|
|
|
|
|
|
|
if (term->slave > 0) {
|
2019-11-02 12:02:11 +01:00
|
|
|
LOG_DBG("waiting for slave (PID=%u) to die", term->slave);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Note: we've closed ptmx, so the slave *should* exit...
|
|
|
|
|
*
|
|
|
|
|
* But, since it is possible to write clients that ignore
|
|
|
|
|
* this, we need to handle it in *some* way.
|
|
|
|
|
*
|
|
|
|
|
* So, what we do is register a SIGALRM handler, and configure
|
|
|
|
|
* a 2 second alarm. If the slave hasn't died after this time,
|
|
|
|
|
* we send it a SIGTERM, then wait another 2 seconds (using
|
|
|
|
|
* the same alarm mechanism). If it still hasn't died, we send
|
|
|
|
|
* it a SIGKILL.
|
|
|
|
|
*
|
|
|
|
|
* Note that this solution is *not* asynchronous, and any
|
|
|
|
|
* other events etc will be ignored during this time. This of
|
|
|
|
|
* course only applies to a 'foot --server' instance, where
|
|
|
|
|
* there might be other terminals running.
|
|
|
|
|
*/
|
|
|
|
|
sigaction(SIGALRM, &(const struct sigaction){.sa_handler = &sig_alarm}, NULL);
|
|
|
|
|
alarm(2);
|
|
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
int status;
|
2019-11-02 12:02:11 +01:00
|
|
|
int kill_signal = SIGTERM;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
int r = waitpid(term->slave, &status, 0);
|
|
|
|
|
|
|
|
|
|
if (r == term->slave)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (r == -1) {
|
|
|
|
|
assert(errno == EINTR);
|
|
|
|
|
|
|
|
|
|
if (alarm_raised) {
|
|
|
|
|
LOG_DBG("slave hasn't died yet, sending: %s (%d)",
|
|
|
|
|
kill_signal == SIGTERM ? "SIGTERM" : "SIGKILL",
|
|
|
|
|
kill_signal);
|
|
|
|
|
|
|
|
|
|
kill(term->slave, kill_signal);
|
|
|
|
|
|
|
|
|
|
alarm_raised = 0;
|
|
|
|
|
if (kill_signal != SIGKILL)
|
|
|
|
|
alarm(2);
|
|
|
|
|
|
|
|
|
|
kill_signal = SIGKILL;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Cancel alarm */
|
|
|
|
|
alarm(0);
|
|
|
|
|
sigaction(SIGALRM, &(const struct sigaction){.sa_handler = SIG_DFL}, NULL);
|
2019-10-28 18:25:19 +01:00
|
|
|
|
2019-11-02 11:30:32 +01:00
|
|
|
ret = EXIT_FAILURE;
|
2019-10-28 18:25:19 +01:00
|
|
|
if (WIFEXITED(status)) {
|
2019-11-02 11:30:32 +01:00
|
|
|
ret = WEXITSTATUS(status);
|
2019-11-02 12:02:11 +01:00
|
|
|
LOG_DBG("slave exited with code %d", ret);
|
2019-10-28 18:25:19 +01:00
|
|
|
} else if (WIFSIGNALED(status)) {
|
2019-11-02 11:30:32 +01:00
|
|
|
ret = WTERMSIG(status);
|
2019-11-02 12:02:11 +01:00
|
|
|
LOG_WARN("slave exited with signal %d (%s)", ret, strsignal(ret));
|
2019-10-28 18:25:19 +01:00
|
|
|
} else {
|
|
|
|
|
LOG_WARN("slave exited for unknown reason (status = 0x%08x)", status);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(term);
|
2019-11-03 12:57:47 +01:00
|
|
|
|
|
|
|
|
#if defined(__GLIBC__)
|
2019-11-03 12:48:18 +01:00
|
|
|
if (!malloc_trim(0))
|
|
|
|
|
LOG_WARN("failed to trim memory");
|
2019-11-03 12:57:47 +01:00
|
|
|
#endif
|
|
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-13 11:42:10 +02:00
|
|
|
static inline void
|
|
|
|
|
erase_cell_range(struct terminal *term, struct row *row, int start, int end)
|
|
|
|
|
{
|
|
|
|
|
assert(start < term->cols);
|
|
|
|
|
assert(end < term->cols);
|
|
|
|
|
|
|
|
|
|
if (unlikely(term->vt.attrs.have_bg)) {
|
|
|
|
|
for (int col = start; col <= end; col++) {
|
|
|
|
|
struct cell *c = &row->cells[col];
|
|
|
|
|
c->wc = 0;
|
|
|
|
|
c->attrs = (struct attributes){.have_bg = 1, .bg = term->vt.attrs.bg};
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
memset(&row->cells[start], 0, (end - start + 1) * sizeof(row->cells[0]));
|
|
|
|
|
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
|
erase_line(struct terminal *term, struct row *row)
|
|
|
|
|
{
|
|
|
|
|
erase_cell_range(term, row, 0, term->cols - 1);
|
|
|
|
|
row->linebreak = false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-01 20:51:11 +02:00
|
|
|
void
|
|
|
|
|
term_reset(struct terminal *term, bool hard)
|
|
|
|
|
{
|
|
|
|
|
term->cursor_keys_mode = CURSOR_KEYS_NORMAL;
|
|
|
|
|
term->keypad_keys_mode = KEYPAD_NUMERICAL;
|
|
|
|
|
term->reverse = false;
|
|
|
|
|
term->hide_cursor = false;
|
|
|
|
|
term->auto_margin = true;
|
|
|
|
|
term->insert_mode = false;
|
|
|
|
|
term->bracketed_paste = false;
|
|
|
|
|
term->focus_events = false;
|
|
|
|
|
term->mouse_tracking = MOUSE_NONE;
|
|
|
|
|
term->mouse_reporting = MOUSE_NORMAL;
|
2019-11-17 09:59:12 +01:00
|
|
|
term->charsets.selected = 0;
|
|
|
|
|
term->charsets.set[0] = CHARSET_ASCII;
|
|
|
|
|
term->charsets.set[1] = CHARSET_ASCII;
|
|
|
|
|
term->charsets.set[2] = CHARSET_ASCII;
|
|
|
|
|
term->charsets.set[3] = CHARSET_ASCII;
|
2019-11-17 10:02:46 +01:00
|
|
|
term->saved_charsets = term->charsets;
|
2019-08-01 20:51:11 +02:00
|
|
|
tll_free_and_free(term->window_title_stack, free);
|
|
|
|
|
free(term->window_title);
|
|
|
|
|
term->window_title = strdup("foot");
|
|
|
|
|
|
|
|
|
|
term->scroll_region.start = 0;
|
|
|
|
|
term->scroll_region.end = term->rows;
|
|
|
|
|
|
|
|
|
|
free(term->vt.osc.data);
|
|
|
|
|
memset(&term->vt, 0, sizeof(term->vt));
|
2020-01-20 18:35:13 +01:00
|
|
|
term->vt.state = 0; /* GROUND */
|
2019-08-01 20:51:11 +02:00
|
|
|
|
|
|
|
|
if (term->grid == &term->alt) {
|
|
|
|
|
term->grid = &term->normal;
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-20 18:38:50 +01:00
|
|
|
term->meta.esc_prefix = true;
|
|
|
|
|
term->meta.eight_bit = true;
|
|
|
|
|
|
2020-03-13 18:44:23 +01:00
|
|
|
tll_foreach(term->normal.sixel_images, it)
|
|
|
|
|
sixel_destroy(&it->item);
|
|
|
|
|
tll_free(term->normal.sixel_images);
|
|
|
|
|
tll_foreach(term->alt.sixel_images, it)
|
2020-02-22 10:47:16 +01:00
|
|
|
sixel_destroy(&it->item);
|
2020-03-13 18:44:23 +01:00
|
|
|
tll_free(term->alt.sixel_images);
|
2020-02-22 10:47:16 +01:00
|
|
|
|
2019-08-01 20:51:11 +02:00
|
|
|
if (!hard)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
term->flash.active = false;
|
|
|
|
|
term->blink.active = false;
|
|
|
|
|
term->blink.state = BLINK_ON;
|
|
|
|
|
term->colors.fg = term->colors.default_fg;
|
|
|
|
|
term->colors.bg = term->colors.default_bg;
|
2019-08-21 17:56:21 +02:00
|
|
|
for (size_t i = 0; i < 256; i++)
|
2019-08-21 18:50:24 +02:00
|
|
|
term->colors.table[i] = term->colors.default_table[i];
|
2019-11-30 00:32:06 +01:00
|
|
|
term->origin = ORIGIN_ABSOLUTE;
|
2020-04-16 18:51:14 +02:00
|
|
|
term->normal.cursor.lcf = false;
|
|
|
|
|
term->alt.cursor.lcf = false;
|
|
|
|
|
term->normal.cursor = (struct cursor){.point = {0, 0}};
|
|
|
|
|
term->normal.saved_cursor = (struct cursor){.point = {0, 0}};
|
|
|
|
|
term->alt.cursor = (struct cursor){.point = {0, 0}};
|
|
|
|
|
term->alt.saved_cursor = (struct cursor){.point = {0, 0}};
|
2019-08-01 20:51:11 +02:00
|
|
|
term->cursor_style = term->default_cursor_style;
|
2019-12-15 15:07:56 +01:00
|
|
|
term_cursor_blink_disable(term);
|
2019-08-01 20:51:11 +02:00
|
|
|
term->cursor_color.text = term->default_cursor_color.text;
|
|
|
|
|
term->cursor_color.cursor = term->default_cursor_color.cursor;
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
term->normal.offset = term->normal.view = 0;
|
|
|
|
|
term->alt.offset = term->alt.view = 0;
|
|
|
|
|
for (size_t i = 0; i < term->rows; i++) {
|
2020-04-13 11:42:10 +02:00
|
|
|
struct row *r = grid_row_and_alloc(&term->normal, i);
|
|
|
|
|
erase_line(term, r);
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = 0; i < term->rows; i++) {
|
|
|
|
|
struct row *r = grid_row_and_alloc(&term->alt, i);
|
|
|
|
|
erase_line(term, r);
|
2019-08-01 20:51:11 +02:00
|
|
|
}
|
|
|
|
|
for (size_t i = term->rows; i < term->normal.num_rows; i++) {
|
|
|
|
|
grid_row_free(term->normal.rows[i]);
|
|
|
|
|
term->normal.rows[i] = NULL;
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = term->rows; i < term->alt.num_rows; i++) {
|
|
|
|
|
grid_row_free(term->alt.rows[i]);
|
|
|
|
|
term->alt.rows[i] = NULL;
|
|
|
|
|
}
|
|
|
|
|
term->normal.cur_row = term->normal.rows[0];
|
|
|
|
|
term->alt.cur_row = term->alt.rows[0];
|
|
|
|
|
tll_free(term->normal.damage);
|
|
|
|
|
tll_free(term->normal.scroll_damage);
|
|
|
|
|
tll_free(term->alt.damage);
|
|
|
|
|
tll_free(term->alt.scroll_damage);
|
2020-05-01 11:56:13 +02:00
|
|
|
term->render.last_cursor.row = NULL;
|
2019-08-01 20:51:11 +02:00
|
|
|
term->render.was_flashing = false;
|
|
|
|
|
term_damage_all(term);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-29 20:09:21 +02:00
|
|
|
struct font_adjust_data {
|
|
|
|
|
struct fcft_font *font_in;
|
|
|
|
|
double amount;
|
|
|
|
|
struct fcft_font *font_out;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
font_size_adjust_thread(void *_data)
|
|
|
|
|
{
|
|
|
|
|
struct font_adjust_data *data = _data;
|
|
|
|
|
data->font_out = fcft_size_adjust(data->font_in, data->amount);
|
|
|
|
|
return data->font_out != NULL;
|
|
|
|
|
}
|
|
|
|
|
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
static bool
|
2020-02-08 14:09:06 +01:00
|
|
|
term_font_size_adjust(struct terminal *term, double amount)
|
|
|
|
|
{
|
2020-04-29 20:09:21 +02:00
|
|
|
struct font_adjust_data data[4] = {
|
|
|
|
|
{term->fonts[0], amount},
|
|
|
|
|
{term->fonts[1], amount},
|
|
|
|
|
{term->fonts[2], amount},
|
|
|
|
|
{term->fonts[3], amount},
|
2020-02-08 14:09:06 +01:00
|
|
|
};
|
|
|
|
|
|
2020-05-03 12:25:04 +02:00
|
|
|
thrd_t tids[4] = {};
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
int ret = thrd_create(&tids[i], &font_size_adjust_thread, &data[i]);
|
2020-05-03 14:17:54 +02:00
|
|
|
if (ret != thrd_success) {
|
2020-05-03 12:25:04 +02:00
|
|
|
LOG_ERR("failed to create font adjustmen thread: %s (%d)",
|
|
|
|
|
thrd_err_as_string(ret), ret);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-29 20:09:21 +02:00
|
|
|
|
2020-05-03 12:25:04 +02:00
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
if (tids[i] != 0)
|
|
|
|
|
thrd_join(tids[i], NULL);
|
|
|
|
|
}
|
2020-04-29 20:09:21 +02:00
|
|
|
|
|
|
|
|
if (data[0].font_out == NULL || data[1].font_out == NULL ||
|
|
|
|
|
data[2].font_out == NULL || data[3].font_out == NULL)
|
2020-02-08 14:09:06 +01:00
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < 4; i++)
|
2020-04-29 20:09:21 +02:00
|
|
|
fcft_destroy(data[i].font_out);
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
return false;
|
2020-02-08 14:09:06 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-29 20:09:21 +02:00
|
|
|
term_set_fonts(term, (struct fcft_font *[]){data[0].font_out, data[1].font_out, data[2].font_out, data[3].font_out});
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
return true;
|
2020-02-08 14:09:06 +01:00
|
|
|
}
|
|
|
|
|
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
bool
|
2020-02-08 14:09:06 +01:00
|
|
|
term_font_size_increase(struct terminal *term)
|
|
|
|
|
{
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
if (!term_font_size_adjust(term, 0.5))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
term->font_adjustments++;
|
|
|
|
|
return true;
|
2020-02-08 14:09:06 +01:00
|
|
|
}
|
|
|
|
|
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
bool
|
2020-02-08 14:09:06 +01:00
|
|
|
term_font_size_decrease(struct terminal *term)
|
|
|
|
|
{
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
if (!term_font_size_adjust(term, -0.5))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
term->font_adjustments--;
|
|
|
|
|
return true;
|
2020-02-08 14:09:06 +01:00
|
|
|
}
|
|
|
|
|
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
bool
|
2020-02-08 17:57:50 +01:00
|
|
|
term_font_size_reset(struct terminal *term)
|
|
|
|
|
{
|
2020-04-21 19:29:36 +02:00
|
|
|
struct fcft_font *fonts[4];
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
if (!load_fonts_from_conf(term, term->conf, fonts))
|
|
|
|
|
return false;
|
2020-02-08 17:57:50 +01:00
|
|
|
|
2020-02-08 18:23:08 +01:00
|
|
|
term_set_fonts(term, fonts);
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
term->font_adjustments = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_font_dpi_changed(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
unsigned dpi = get_font_dpi(term);
|
|
|
|
|
if (dpi == term->font_dpi)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("DPI changed (%u -> %u): reloading fonts", term->font_dpi, dpi);
|
|
|
|
|
term->font_dpi = dpi;
|
|
|
|
|
|
2020-04-21 19:29:36 +02:00
|
|
|
struct fcft_font *fonts[4];
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
if (!load_fonts_from_conf(term, term->conf, fonts))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (term->font_adjustments == 0)
|
|
|
|
|
return term_set_fonts(term, fonts);
|
|
|
|
|
|
|
|
|
|
/* User has adjusted the font size run-time, re-apply */
|
|
|
|
|
|
|
|
|
|
double amount = term->font_adjustments * 0.5;
|
|
|
|
|
|
2020-04-21 19:29:36 +02:00
|
|
|
struct fcft_font *adjusted_fonts[4] = {
|
|
|
|
|
fcft_size_adjust(fonts[0], amount),
|
|
|
|
|
fcft_size_adjust(fonts[1], amount),
|
|
|
|
|
fcft_size_adjust(fonts[2], amount),
|
|
|
|
|
fcft_size_adjust(fonts[3], amount),
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (adjusted_fonts[0] == NULL || adjusted_fonts[1] == NULL ||
|
|
|
|
|
adjusted_fonts[2] == NULL || adjusted_fonts[3] == NULL)
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < 4; i++)
|
2020-04-21 19:29:36 +02:00
|
|
|
fcft_destroy(adjusted_fonts[i]);
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
|
|
|
|
|
/* At least use the newly re-loaded default fonts */
|
|
|
|
|
term->font_adjustments = 0;
|
|
|
|
|
return term_set_fonts(term, fonts);
|
|
|
|
|
} else {
|
|
|
|
|
for (size_t i = 0; i < 4; i++)
|
2020-04-21 19:29:36 +02:00
|
|
|
fcft_destroy(fonts[i]);
|
term: implement term_font_dpi_changed()
This function reloads the font *if* the DPI has changed. To handle
user run-time adjusted font sizes, we record the number of adjustments
made.
Then, when re-loading the font, we first load the font as specified in
the configuration. Then, we re-apply the size adjustment using
font_size_adjust().
Note that this means we end up loading the fonts twice; first using
the default size (but with adjusted DPI), and then again with the
adjusted size. This can probably be improved upon.
The existing font code has been refactored to avoid code
duplication. For example, term_init() now calls
term_font_dpi_changed() to load the initial fonts, instead of directly
instantiating them.
Finally, the way we calculate the DPI to use has changed: instead of
using the highest DPI of all available outputs, we use the highest DPI
of the output's we're actually mapped on. If we're not mapped at all,
we use the globally highest DPI.
Doing it this way means we usually only have to load the fonts
once. Otherwise, we'd end up using the default DPI of 96 when the
terminal is first instantiated (since it's not mapped at that time).
On a single monitor system, we'll use the globally highest DPI at
first, before being mapped. Then when we get mapped, we re-load the
fonts using the highest mapped DPI. But since they'll be the same,
we can skip actually reloading the fonts.
2020-02-15 19:08:14 +01:00
|
|
|
return term_set_fonts(term, adjusted_fonts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
return false;
|
2020-02-08 17:57:50 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-20 18:37:59 +02:00
|
|
|
void
|
|
|
|
|
term_font_subpixel_changed(struct terminal *term)
|
|
|
|
|
{
|
2020-04-21 19:29:36 +02:00
|
|
|
enum fcft_subpixel subpixel = get_font_subpixel(term);
|
2020-04-20 18:37:59 +02:00
|
|
|
|
|
|
|
|
if (term->font_subpixel == subpixel)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
|
|
|
|
static const char *const str[] = {
|
|
|
|
|
[FCFT_SUBPIXEL_ORDER_DEFAULT] = "default",
|
|
|
|
|
[FCFT_SUBPIXEL_ORDER_NONE] = "disabled",
|
|
|
|
|
[FCFT_SUBPIXEL_ORDER_HORIZONTAL_RGB] = "RGB",
|
|
|
|
|
[FCFT_SUBPIXEL_ORDER_HORIZONTAL_BGR] = "BGR",
|
|
|
|
|
[FCFT_SUBPIXEL_ORDER_VERTICAL_RGB] = "V-RGB",
|
|
|
|
|
[FCFT_SUBPIXEL_ORDER_VERTICAL_BGR] = "V-BGR",
|
|
|
|
|
};
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
LOG_DBG("subpixel mode changed: %s -> %s", str[term->font_subpixel], str[subpixel]);
|
|
|
|
|
term->font_subpixel = subpixel;
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
render_refresh(term);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
void
|
|
|
|
|
term_damage_rows(struct terminal *term, int start, int end)
|
|
|
|
|
{
|
|
|
|
|
assert(start <= end);
|
2019-07-30 18:03:03 +02:00
|
|
|
for (int r = start; r <= end; r++) {
|
|
|
|
|
struct row *row = grid_row(term->grid, r);
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
for (int c = 0; c < term->grid->num_cols; c++)
|
|
|
|
|
row->cells[c].attrs.clean = 0;
|
|
|
|
|
}
|
2019-07-11 09:51:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_damage_rows_in_view(struct terminal *term, int start, int end)
|
|
|
|
|
{
|
|
|
|
|
assert(start <= end);
|
2019-07-30 18:03:03 +02:00
|
|
|
for (int r = start; r <= end; r++) {
|
|
|
|
|
struct row *row = grid_row_in_view(term->grid, r);
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
for (int c = 0; c < term->grid->num_cols; c++)
|
|
|
|
|
row->cells[c].attrs.clean = 0;
|
|
|
|
|
}
|
2019-07-11 09:51:51 +02:00
|
|
|
}
|
|
|
|
|
|
2019-06-29 21:03:28 +02:00
|
|
|
void
|
|
|
|
|
term_damage_all(struct terminal *term)
|
|
|
|
|
{
|
2019-07-30 18:03:03 +02:00
|
|
|
term_damage_rows(term, 0, term->rows - 1);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-10 14:32:40 +02:00
|
|
|
void
|
|
|
|
|
term_damage_view(struct terminal *term)
|
|
|
|
|
{
|
2019-07-30 18:03:03 +02:00
|
|
|
term_damage_rows_in_view(term, 0, term->rows - 1);
|
2019-07-10 14:32:40 +02:00
|
|
|
}
|
|
|
|
|
|
2019-06-29 21:03:28 +02:00
|
|
|
void
|
|
|
|
|
term_damage_scroll(struct terminal *term, enum damage_type damage_type,
|
|
|
|
|
struct scroll_region region, int lines)
|
|
|
|
|
{
|
2019-06-29 21:23:36 +02:00
|
|
|
if (tll_length(term->grid->scroll_damage) > 0) {
|
|
|
|
|
struct damage *dmg = &tll_back(term->grid->scroll_damage);
|
2019-06-29 21:03:28 +02:00
|
|
|
|
|
|
|
|
if (dmg->type == damage_type &&
|
2020-04-26 12:47:19 +02:00
|
|
|
dmg->region.start == region.start &&
|
|
|
|
|
dmg->region.end == region.end)
|
2019-06-29 21:03:28 +02:00
|
|
|
{
|
2020-04-26 12:47:19 +02:00
|
|
|
dmg->lines += lines;
|
2019-06-29 21:03:28 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
struct damage dmg = {
|
|
|
|
|
.type = damage_type,
|
2020-04-26 12:47:19 +02:00
|
|
|
.region = region,
|
|
|
|
|
.lines = lines,
|
2019-06-29 21:03:28 +02:00
|
|
|
};
|
2019-06-29 21:23:36 +02:00
|
|
|
tll_push_back(term->grid->scroll_damage, dmg);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
void
|
|
|
|
|
term_erase(struct terminal *term, const struct coord *start, const struct coord *end)
|
2019-06-29 21:03:28 +02:00
|
|
|
{
|
2019-07-08 13:57:31 +02:00
|
|
|
assert(start->row <= end->row);
|
|
|
|
|
assert(start->col <= end->col || start->row < end->row);
|
|
|
|
|
|
|
|
|
|
if (start->row == end->row) {
|
|
|
|
|
struct row *row = grid_row(term->grid, start->row);
|
|
|
|
|
erase_cell_range(term, row, start->col, end->col);
|
2020-02-24 18:40:41 +01:00
|
|
|
sixel_delete_at_row(term, start->row);
|
2019-07-08 13:57:31 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(end->row > start->row);
|
|
|
|
|
|
|
|
|
|
erase_cell_range(
|
|
|
|
|
term, grid_row(term->grid, start->row), start->col, term->cols - 1);
|
|
|
|
|
|
|
|
|
|
for (int r = start->row + 1; r < end->row; r++)
|
|
|
|
|
erase_line(term, grid_row(term->grid, r));
|
|
|
|
|
|
|
|
|
|
erase_cell_range(term, grid_row(term->grid, end->row), 0, end->col);
|
2020-02-24 18:40:41 +01:00
|
|
|
sixel_delete_in_range(term, start->row, end->row);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-17 18:52:27 +01:00
|
|
|
int
|
|
|
|
|
term_row_rel_to_abs(const struct terminal *term, int row)
|
2019-11-05 13:27:37 +01:00
|
|
|
{
|
|
|
|
|
switch (term->origin) {
|
|
|
|
|
case ORIGIN_ABSOLUTE:
|
2019-11-17 18:52:27 +01:00
|
|
|
return min(row, term->rows - 1);
|
2019-11-05 13:27:37 +01:00
|
|
|
|
2019-11-17 18:52:27 +01:00
|
|
|
case ORIGIN_RELATIVE:
|
|
|
|
|
return min(row + term->scroll_region.start, term->scroll_region.end - 1);
|
2019-11-05 13:27:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
2019-11-17 18:52:27 +01:00
|
|
|
return -1;
|
2019-11-05 13:27:37 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-29 21:03:28 +02:00
|
|
|
void
|
|
|
|
|
term_cursor_to(struct terminal *term, int row, int col)
|
|
|
|
|
{
|
2019-06-29 21:08:08 +02:00
|
|
|
assert(row < term->rows);
|
|
|
|
|
assert(col < term->cols);
|
2019-06-29 21:03:28 +02:00
|
|
|
|
2020-04-16 18:51:14 +02:00
|
|
|
term->grid->cursor.lcf = false;
|
2019-06-29 21:03:28 +02:00
|
|
|
|
2020-04-16 18:51:14 +02:00
|
|
|
term->grid->cursor.point.col = col;
|
|
|
|
|
term->grid->cursor.point.row = row;
|
2019-07-02 22:18:25 +02:00
|
|
|
|
2019-07-08 13:57:31 +02:00
|
|
|
term->grid->cur_row = grid_row(term->grid, row);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-05 13:27:37 +01:00
|
|
|
void
|
|
|
|
|
term_cursor_home(struct terminal *term)
|
|
|
|
|
{
|
2019-11-17 18:52:27 +01:00
|
|
|
term_cursor_to(term, term_row_rel_to_abs(term, 0), 0);
|
2019-11-05 13:27:37 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-29 21:03:28 +02:00
|
|
|
void
|
|
|
|
|
term_cursor_left(struct terminal *term, int count)
|
|
|
|
|
{
|
2020-04-16 18:51:14 +02:00
|
|
|
int move_amount = min(term->grid->cursor.point.col, count);
|
|
|
|
|
term->grid->cursor.point.col -= move_amount;
|
|
|
|
|
assert(term->grid->cursor.point.col >= 0);
|
|
|
|
|
term->grid->cursor.lcf = false;
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_cursor_right(struct terminal *term, int count)
|
|
|
|
|
{
|
2020-04-16 18:51:14 +02:00
|
|
|
int move_amount = min(term->cols - term->grid->cursor.point.col - 1, count);
|
|
|
|
|
term->grid->cursor.point.col += move_amount;
|
|
|
|
|
assert(term->grid->cursor.point.col < term->cols);
|
|
|
|
|
term->grid->cursor.lcf = false;
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_cursor_up(struct terminal *term, int count)
|
|
|
|
|
{
|
2019-11-16 12:14:58 +01:00
|
|
|
int top = term->origin == ORIGIN_ABSOLUTE ? 0 : term->scroll_region.start;
|
2020-04-16 18:51:14 +02:00
|
|
|
assert(term->grid->cursor.point.row >= top);
|
2019-11-16 12:14:58 +01:00
|
|
|
|
2020-04-16 18:51:14 +02:00
|
|
|
int move_amount = min(term->grid->cursor.point.row - top, count);
|
|
|
|
|
term_cursor_to(term, term->grid->cursor.point.row - move_amount, term->grid->cursor.point.col);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_cursor_down(struct terminal *term, int count)
|
|
|
|
|
{
|
2019-11-16 12:14:58 +01:00
|
|
|
int bottom = term->origin == ORIGIN_ABSOLUTE ? term->rows : term->scroll_region.end;
|
2020-04-16 18:51:14 +02:00
|
|
|
assert(bottom >= term->grid->cursor.point.row);
|
2019-11-16 12:14:58 +01:00
|
|
|
|
2020-04-16 18:51:14 +02:00
|
|
|
int move_amount = min(bottom - term->grid->cursor.point.row - 1, count);
|
|
|
|
|
term_cursor_to(term, term->grid->cursor.point.row + move_amount, term->grid->cursor.point.col);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-16 21:31:40 +01:00
|
|
|
static bool
|
|
|
|
|
cursor_blink_start_timer(struct terminal *term)
|
2019-12-15 15:07:56 +01:00
|
|
|
{
|
|
|
|
|
static const struct itimerspec timer = {
|
|
|
|
|
.it_value = {.tv_sec = 0, .tv_nsec = 500000000},
|
|
|
|
|
.it_interval = {.tv_sec = 0, .tv_nsec = 500000000},
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-16 21:31:40 +01:00
|
|
|
if (timerfd_settime(term->cursor_blink.fd, 0, &timer, NULL) < 0) {
|
2019-12-15 15:07:56 +01:00
|
|
|
LOG_ERRNO("failed to arm cursor blink timer");
|
2019-12-16 21:31:40 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
2019-12-15 15:07:56 +01:00
|
|
|
|
2019-12-16 21:31:40 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
cursor_blink_stop_timer(struct terminal *term)
|
|
|
|
|
{
|
2020-04-13 12:03:11 +02:00
|
|
|
return timerfd_settime(term->cursor_blink.fd, 0, &(struct itimerspec){}, NULL) == 0;
|
2019-12-16 21:31:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_cursor_blink_enable(struct terminal *term)
|
|
|
|
|
{
|
2019-12-15 15:07:56 +01:00
|
|
|
term->cursor_blink.state = CURSOR_BLINK_ON;
|
2020-01-02 15:58:52 +01:00
|
|
|
term->cursor_blink.active = term->wl->kbd_focus == term
|
2019-12-16 21:31:40 +01:00
|
|
|
? cursor_blink_start_timer(term) : true;
|
2019-12-15 15:07:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_cursor_blink_disable(struct terminal *term)
|
|
|
|
|
{
|
2019-12-16 21:31:40 +01:00
|
|
|
term->cursor_blink.active = false;
|
2019-12-15 15:07:56 +01:00
|
|
|
term->cursor_blink.state = CURSOR_BLINK_ON;
|
2019-12-16 21:31:40 +01:00
|
|
|
cursor_blink_stop_timer(term);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_cursor_blink_restart(struct terminal *term)
|
|
|
|
|
{
|
2019-12-19 07:27:41 +01:00
|
|
|
if (term->cursor_blink.active) {
|
|
|
|
|
term->cursor_blink.state = CURSOR_BLINK_ON;
|
2020-01-02 15:58:52 +01:00
|
|
|
term->cursor_blink.active = term->wl->kbd_focus == term
|
2019-12-19 07:27:41 +01:00
|
|
|
? cursor_blink_start_timer(term) : true;
|
|
|
|
|
}
|
2019-12-15 15:07:56 +01:00
|
|
|
}
|
|
|
|
|
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
static bool
|
|
|
|
|
selection_on_top_region(const struct terminal *term,
|
|
|
|
|
struct scroll_region region)
|
|
|
|
|
{
|
|
|
|
|
return region.start > 0 &&
|
|
|
|
|
selection_on_rows(term, 0, region.start - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
selection_on_bottom_region(const struct terminal *term,
|
|
|
|
|
struct scroll_region region)
|
|
|
|
|
{
|
|
|
|
|
return region.end < term->rows &&
|
|
|
|
|
selection_on_rows(term, region.end, term->rows - 1);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-29 21:03:28 +02:00
|
|
|
void
|
|
|
|
|
term_scroll_partial(struct terminal *term, struct scroll_region region, int rows)
|
|
|
|
|
{
|
2019-07-10 19:18:36 +02:00
|
|
|
LOG_DBG("scroll: rows=%d, region.start=%d, region.end=%d",
|
|
|
|
|
rows, region.start, region.end);
|
2019-06-29 21:03:28 +02:00
|
|
|
|
2020-05-16 23:44:54 +02:00
|
|
|
/* Clamp scroll amount */
|
|
|
|
|
rows = min(rows, region.end - region.start);
|
2019-06-29 21:03:28 +02:00
|
|
|
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
/* Cancel selections that cannot be scrolled */
|
2020-05-17 15:48:58 +02:00
|
|
|
if (unlikely(term->selection.start.row != -1)) {
|
|
|
|
|
if (likely(term->selection.end.row != -1)) {
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
/*
|
|
|
|
|
* Selection is (partly) inside either the top or bottom
|
|
|
|
|
* scrolling regions, or on (at least one) of the lines
|
|
|
|
|
* scrolled in (i.e. re-used lines).
|
|
|
|
|
*/
|
|
|
|
|
if (selection_on_top_region(term, region) ||
|
|
|
|
|
selection_on_bottom_region(term, region) ||
|
|
|
|
|
selection_on_rows(term, region.end - rows, region.end - 1))
|
|
|
|
|
{
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/*
|
|
|
|
|
* User started a selection, but didn't move the
|
|
|
|
|
* cursor.
|
|
|
|
|
*
|
|
|
|
|
* Not 100% sure this is needed for forward scrolling, but
|
|
|
|
|
* let's keep it here, for consistency with reverse
|
|
|
|
|
* scrolling.
|
|
|
|
|
*/
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-16 21:27:56 +02:00
|
|
|
|
2019-07-09 16:26:36 +02:00
|
|
|
bool view_follows = term->grid->view == term->grid->offset;
|
2019-07-08 13:57:31 +02:00
|
|
|
term->grid->offset += rows;
|
2019-08-22 17:33:23 +02:00
|
|
|
term->grid->offset &= term->grid->num_rows - 1;
|
2019-07-01 19:20:21 +02:00
|
|
|
|
2019-07-09 16:26:36 +02:00
|
|
|
if (view_follows)
|
|
|
|
|
term->grid->view = term->grid->offset;
|
|
|
|
|
|
2019-07-10 19:18:53 +02:00
|
|
|
/* Top non-scrolling region. */
|
|
|
|
|
for (int i = region.start - 1; i >= 0; i--)
|
2020-05-16 23:43:05 +02:00
|
|
|
grid_swap_row(term->grid, i - rows, i);
|
2019-07-10 19:18:53 +02:00
|
|
|
|
|
|
|
|
/* Bottom non-scrolling region */
|
|
|
|
|
for (int i = term->rows - 1; i >= region.end; i--)
|
2020-05-16 23:43:05 +02:00
|
|
|
grid_swap_row(term->grid, i - rows, i);
|
2019-07-10 19:18:53 +02:00
|
|
|
|
|
|
|
|
/* Erase scrolled in lines */
|
2020-05-16 23:53:03 +02:00
|
|
|
for (int r = region.end - rows; r < region.end; r++)
|
2019-08-22 17:33:23 +02:00
|
|
|
erase_line(term, grid_row_and_alloc(term->grid, r));
|
2020-05-16 16:28:32 +02:00
|
|
|
|
2020-05-17 11:46:44 +02:00
|
|
|
sixel_delete_in_range(term, region.end - rows, region.end - 1);
|
2019-07-01 12:23:38 +02:00
|
|
|
term_damage_scroll(term, DAMAGE_SCROLL, region, rows);
|
2020-04-16 18:51:14 +02:00
|
|
|
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
|
2020-05-16 23:53:23 +02:00
|
|
|
|
|
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
for (int r = 0; r < term->rows; r++)
|
|
|
|
|
assert(grid_row(term->grid, r) != NULL);
|
|
|
|
|
#endif
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_scroll(struct terminal *term, int rows)
|
|
|
|
|
{
|
2019-06-29 21:08:08 +02:00
|
|
|
term_scroll_partial(term, term->scroll_region, rows);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_scroll_reverse_partial(struct terminal *term,
|
|
|
|
|
struct scroll_region region, int rows)
|
|
|
|
|
{
|
2019-07-10 19:18:36 +02:00
|
|
|
LOG_DBG("scroll reverse: rows=%d, region.start=%d, region.end=%d",
|
|
|
|
|
rows, region.start, region.end);
|
|
|
|
|
|
2020-05-16 23:44:54 +02:00
|
|
|
/* Clamp scroll amount */
|
|
|
|
|
rows = min(rows, region.end - region.start);
|
2019-07-08 13:57:31 +02:00
|
|
|
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
/* Cancel selections that cannot be scrolled */
|
2020-05-17 15:48:58 +02:00
|
|
|
if (unlikely(term->selection.start.row != -1)) {
|
|
|
|
|
if (likely(term->selection.end.row != -1)) {
|
term: scrolling: hopefully fix all selection/scrolling related crashes
When scrolling, there are a couple of cases where an existing
selection must be canceled because we cannot meaningfully represent it
after scrolling.
These are when the selection is (partly) inside:
* The top scrolling region
* The bottom scrolling region
* The new lines scrolled in. I.e. re-used lines
For the scrolling regions, the real problem is when the selection
crosses the scrolling region boundary; a selection that is completely
inside a scrolling regions _might_ be possible to keep, but we would
need to translate the selection coordinates to the new scrolling
region lines.
For simplicity, we cancel the selection if it touches the scrolling
region. Period.
The last item, newly scrolled in lines is when the selection covers
very old lines and we're now wrapping around the scrollback history.
Then there's a fourth problem case: when the user has started a
selection, but hasn't yet moved the cursor. In this case, we have no
end point.
What's more problematic is that when the user (after scrolling) moves
the cursor, we try to create a huge selection that covers mostly
empty (NULL) rows, causing us to crash.
This can happen e.g. when reverse scrolling in such a way that we wrap
around the scrollback history.
The actual viewport in this case is something like `-n - m`. But the
selection we'll end up trying to create will be `m - (rows - n)`. This
range may very well contain NULL rows.
To deal with this, we simply cancel the selection.
2020-05-17 15:34:49 +02:00
|
|
|
/*
|
|
|
|
|
* Selection is (partly) inside either the top or bottom
|
|
|
|
|
* scrolling regions, or on (at least one) of the lines
|
|
|
|
|
* scrolled in (i.e. re-used lines).
|
|
|
|
|
*/
|
|
|
|
|
if (selection_on_top_region(term, region) ||
|
|
|
|
|
selection_on_bottom_region(term, region) ||
|
|
|
|
|
selection_on_rows(term, region.start, region.start + rows - 1))
|
|
|
|
|
{
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/*
|
|
|
|
|
* User started a selection, but didn't move the
|
|
|
|
|
* cursor.
|
|
|
|
|
*
|
|
|
|
|
* Since we're scrolling in reverse, the result may be a
|
|
|
|
|
* *huge* selection that covers empty (NULL) lines.
|
|
|
|
|
*/
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-16 21:27:56 +02:00
|
|
|
|
2019-07-09 16:26:36 +02:00
|
|
|
bool view_follows = term->grid->view == term->grid->offset;
|
2019-08-22 17:33:23 +02:00
|
|
|
term->grid->offset -= rows;
|
|
|
|
|
while (term->grid->offset < 0)
|
|
|
|
|
term->grid->offset += term->grid->num_rows;
|
|
|
|
|
term->grid->offset &= term->grid->num_rows - 1;
|
|
|
|
|
|
|
|
|
|
assert(term->grid->offset >= 0);
|
|
|
|
|
assert(term->grid->offset < term->grid->num_rows);
|
2019-06-29 21:03:28 +02:00
|
|
|
|
2019-07-09 16:26:36 +02:00
|
|
|
if (view_follows)
|
|
|
|
|
term->grid->view = term->grid->offset;
|
|
|
|
|
|
2019-07-09 09:17:24 +02:00
|
|
|
/* Bottom non-scrolling region */
|
2019-07-08 13:57:31 +02:00
|
|
|
for (int i = region.end + rows; i < term->rows + rows; i++)
|
2020-05-16 23:43:05 +02:00
|
|
|
grid_swap_row(term->grid, i, i - rows);
|
2019-07-09 09:17:24 +02:00
|
|
|
|
|
|
|
|
/* Top non-scrolling region */
|
2019-07-08 13:57:31 +02:00
|
|
|
for (int i = 0 + rows; i < region.start + rows; i++)
|
2020-05-16 23:43:05 +02:00
|
|
|
grid_swap_row(term->grid, i, i - rows);
|
2019-06-29 21:03:28 +02:00
|
|
|
|
2019-07-10 19:18:53 +02:00
|
|
|
/* Erase scrolled in lines */
|
2020-05-16 23:53:03 +02:00
|
|
|
for (int r = region.start; r < region.start + rows; r++)
|
2019-08-22 17:33:23 +02:00
|
|
|
erase_line(term, grid_row_and_alloc(term->grid, r));
|
2020-05-16 16:28:32 +02:00
|
|
|
|
2020-05-17 11:46:44 +02:00
|
|
|
sixel_delete_in_range(term, region.start, region.start + rows - 1);
|
2019-07-08 13:57:31 +02:00
|
|
|
term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows);
|
2020-04-16 18:51:14 +02:00
|
|
|
term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row);
|
2020-05-16 23:53:23 +02:00
|
|
|
|
|
|
|
|
#if defined(_DEBUG)
|
|
|
|
|
for (int r = 0; r < term->rows; r++)
|
|
|
|
|
assert(grid_row(term->grid, r) != NULL);
|
|
|
|
|
#endif
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_scroll_reverse(struct terminal *term, int rows)
|
|
|
|
|
{
|
2019-06-29 21:08:08 +02:00
|
|
|
term_scroll_reverse_partial(term, term->scroll_region, rows);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
2019-07-05 14:24:51 +02:00
|
|
|
|
2020-02-10 21:52:14 +01:00
|
|
|
void
|
|
|
|
|
term_formfeed(struct terminal *term)
|
|
|
|
|
{
|
2020-04-16 18:51:14 +02:00
|
|
|
term_cursor_left(term, term->grid->cursor.point.col);
|
2020-02-10 21:52:14 +01:00
|
|
|
}
|
|
|
|
|
|
2019-07-10 16:02:03 +02:00
|
|
|
void
|
|
|
|
|
term_linefeed(struct terminal *term)
|
|
|
|
|
{
|
2020-02-14 22:39:26 +01:00
|
|
|
term->grid->cur_row->linebreak = true;
|
2020-04-16 18:51:14 +02:00
|
|
|
if (term->grid->cursor.point.row == term->scroll_region.end - 1)
|
2019-07-10 16:02:03 +02:00
|
|
|
term_scroll(term, 1);
|
|
|
|
|
else
|
|
|
|
|
term_cursor_down(term, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_reverse_index(struct terminal *term)
|
|
|
|
|
{
|
2020-04-16 18:51:14 +02:00
|
|
|
if (term->grid->cursor.point.row == term->scroll_region.start)
|
2019-07-10 16:02:03 +02:00
|
|
|
term_scroll_reverse(term, 1);
|
|
|
|
|
else
|
|
|
|
|
term_cursor_up(term, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-28 17:25:42 +02:00
|
|
|
void
|
|
|
|
|
term_reset_view(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term->grid->view == term->grid->offset)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
term->grid->view = term->grid->offset;
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-23 17:57:07 +02:00
|
|
|
void
|
2020-03-16 12:00:25 +01:00
|
|
|
term_restore_cursor(struct terminal *term, const struct cursor *cursor)
|
2019-07-23 17:57:07 +02:00
|
|
|
{
|
2020-03-16 12:00:25 +01:00
|
|
|
int row = min(cursor->point.row, term->rows - 1);
|
|
|
|
|
int col = min(cursor->point.col, term->cols - 1);
|
2019-07-23 17:57:07 +02:00
|
|
|
term_cursor_to(term, row, col);
|
2020-04-16 18:51:14 +02:00
|
|
|
term->grid->cursor.lcf = cursor->lcf;
|
2019-07-23 17:57:07 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-16 10:34:08 +02:00
|
|
|
void
|
2020-01-02 19:35:32 +01:00
|
|
|
term_visual_focus_in(struct terminal *term)
|
2019-07-16 10:34:08 +02:00
|
|
|
{
|
2020-01-03 11:19:56 +01:00
|
|
|
if (term->visual_focus)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-01-02 19:35:32 +01:00
|
|
|
term->visual_focus = true;
|
2019-12-16 21:33:22 +01:00
|
|
|
if (term->cursor_blink.active)
|
|
|
|
|
cursor_blink_start_timer(term);
|
2020-01-03 19:27:57 +01:00
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
render_refresh_csd(term);
|
2019-12-16 21:33:22 +01:00
|
|
|
cursor_refresh(term);
|
2019-07-16 10:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2020-01-02 19:35:32 +01:00
|
|
|
term_visual_focus_out(struct terminal *term)
|
2019-07-16 10:34:08 +02:00
|
|
|
{
|
2020-01-03 11:19:56 +01:00
|
|
|
if (!term->visual_focus)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-01-02 19:35:32 +01:00
|
|
|
term->visual_focus = false;
|
2019-12-16 21:33:22 +01:00
|
|
|
if (term->cursor_blink.active)
|
|
|
|
|
cursor_blink_stop_timer(term);
|
|
|
|
|
|
2020-03-06 19:16:54 +01:00
|
|
|
render_refresh_csd(term);
|
2020-01-02 19:35:32 +01:00
|
|
|
cursor_refresh(term);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_kbd_focus_in(struct terminal *term)
|
|
|
|
|
{
|
2019-12-16 21:33:22 +01:00
|
|
|
if (term->focus_events)
|
2020-01-02 19:35:32 +01:00
|
|
|
term_to_slave(term, "\033[I", 3);
|
|
|
|
|
}
|
2019-12-16 21:33:22 +01:00
|
|
|
|
2020-01-02 19:35:32 +01:00
|
|
|
void
|
|
|
|
|
term_kbd_focus_out(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
if (term->focus_events)
|
|
|
|
|
term_to_slave(term, "\033[O", 3);
|
2019-07-16 10:34:08 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 14:24:51 +02:00
|
|
|
static int
|
|
|
|
|
linux_mouse_button_to_x(int button)
|
|
|
|
|
{
|
|
|
|
|
switch (button) {
|
|
|
|
|
case BTN_LEFT: return 1;
|
|
|
|
|
case BTN_MIDDLE: return 2;
|
2019-08-05 18:59:12 +02:00
|
|
|
case BTN_RIGHT: return 3;
|
2019-08-05 18:32:35 +02:00
|
|
|
case BTN_BACK: return 4;
|
|
|
|
|
case BTN_FORWARD: return 5;
|
2019-08-05 18:59:12 +02:00
|
|
|
case BTN_SIDE: return 8;
|
|
|
|
|
case BTN_EXTRA: return 9;
|
2019-07-05 14:24:51 +02:00
|
|
|
case BTN_TASK: return -1; /* TODO: ??? */
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
LOG_WARN("unrecognized mouse button: %d (0x%x)", button, button);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
encode_xbutton(int xbutton)
|
|
|
|
|
{
|
|
|
|
|
switch (xbutton) {
|
|
|
|
|
case 1: case 2: case 3:
|
|
|
|
|
return xbutton - 1;
|
|
|
|
|
|
|
|
|
|
case 4: case 5:
|
|
|
|
|
/* Like button 1 and 2, but with 64 added */
|
|
|
|
|
return xbutton - 4 + 64;
|
|
|
|
|
|
|
|
|
|
case 6: case 7:
|
|
|
|
|
/* Same as 4 and 5. Note: the offset should be something else? */
|
|
|
|
|
return xbutton - 6 + 64;
|
|
|
|
|
|
|
|
|
|
case 8: case 9: case 10: case 11:
|
|
|
|
|
/* Similar to 4 and 5, but adding 128 instead of 64 */
|
|
|
|
|
return xbutton - 8 + 128;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
LOG_ERR("cannot encode X mouse button: %d", xbutton);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2019-07-05 15:13:06 +02:00
|
|
|
report_mouse_click(struct terminal *term, int encoded_button, int row, int col,
|
|
|
|
|
bool release)
|
|
|
|
|
{
|
2019-07-05 19:40:52 +02:00
|
|
|
char response[128];
|
|
|
|
|
|
2019-07-05 15:29:16 +02:00
|
|
|
switch (term->mouse_reporting) {
|
2019-11-18 11:31:21 +01:00
|
|
|
case MOUSE_NORMAL: {
|
|
|
|
|
int encoded_col = 32 + col + 1;
|
|
|
|
|
int encoded_row = 32 + row + 1;
|
|
|
|
|
if (encoded_col > 255 || encoded_row > 255)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-05 15:29:16 +02:00
|
|
|
snprintf(response, sizeof(response), "\033[M%c%c%c",
|
2019-11-18 11:31:21 +01:00
|
|
|
32 + (release ? 3 : encoded_button), encoded_col, encoded_row);
|
2019-07-05 15:29:16 +02:00
|
|
|
break;
|
2019-11-18 11:31:21 +01:00
|
|
|
}
|
2019-07-05 15:29:16 +02:00
|
|
|
|
2019-07-05 19:40:52 +02:00
|
|
|
case MOUSE_SGR:
|
2019-07-05 15:29:16 +02:00
|
|
|
snprintf(response, sizeof(response), "\033[<%d;%d;%d%c",
|
|
|
|
|
encoded_button, col + 1, row + 1, release ? 'm' : 'M');
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MOUSE_URXVT:
|
2019-07-05 19:40:52 +02:00
|
|
|
snprintf(response, sizeof(response), "\033[%d;%d;%dM",
|
|
|
|
|
32 + (release ? 3 : encoded_button), col + 1, row + 1);
|
2019-07-05 15:29:16 +02:00
|
|
|
break;
|
2019-07-05 19:40:52 +02:00
|
|
|
|
|
|
|
|
case MOUSE_UTF8:
|
|
|
|
|
/* Unimplemented */
|
|
|
|
|
return;
|
2019-07-05 15:29:16 +02:00
|
|
|
}
|
2019-07-05 19:40:52 +02:00
|
|
|
|
2019-11-03 00:27:39 +01:00
|
|
|
term_to_slave(term, response, strlen(response));
|
2019-07-05 15:13:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
report_mouse_motion(struct terminal *term, int encoded_button, int row, int col)
|
|
|
|
|
{
|
|
|
|
|
report_mouse_click(term, encoded_button, row, col, false);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-30 17:06:15 +01:00
|
|
|
bool
|
|
|
|
|
term_mouse_grabbed(const struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Mouse is grabbed by us, regardless of whether mouse tracking has been enabled or not.
|
|
|
|
|
*/
|
|
|
|
|
return
|
2020-01-02 15:58:52 +01:00
|
|
|
term->wl->kbd_focus == term &&
|
2019-11-30 17:06:15 +01:00
|
|
|
term->wl->kbd.shift &&
|
2020-01-04 12:09:26 +01:00
|
|
|
!term->wl->kbd.alt && /*!term->wl->kbd.ctrl &&*/ !term->wl->kbd.meta;
|
2019-11-30 17:06:15 +01:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 15:13:06 +02:00
|
|
|
void
|
2019-11-30 17:11:00 +01:00
|
|
|
term_mouse_down(struct terminal *term, int button, int row, int col)
|
2019-07-05 14:24:51 +02:00
|
|
|
{
|
2019-11-30 17:06:15 +01:00
|
|
|
if (term_mouse_grabbed(term))
|
2019-08-08 17:58:50 +02:00
|
|
|
return;
|
|
|
|
|
|
2019-07-05 14:24:51 +02:00
|
|
|
/* Map libevent button event code to X button number */
|
|
|
|
|
int xbutton = linux_mouse_button_to_x(button);
|
|
|
|
|
if (xbutton == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-07-05 15:13:06 +02:00
|
|
|
int encoded = encode_xbutton(xbutton);
|
2019-07-05 14:24:51 +02:00
|
|
|
if (encoded == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-11-30 17:11:00 +01:00
|
|
|
|
2020-01-02 15:58:52 +01:00
|
|
|
bool has_focus = term->wl->kbd_focus == term;
|
2019-11-30 17:11:00 +01:00
|
|
|
bool shift = has_focus ? term->wl->kbd.shift : false;
|
|
|
|
|
bool alt = has_focus ? term->wl->kbd.alt : false;
|
|
|
|
|
bool ctrl = has_focus ? term->wl->kbd.ctrl : false;
|
|
|
|
|
|
2019-07-05 14:24:51 +02:00
|
|
|
encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
|
|
|
|
|
|
|
|
|
|
switch (term->mouse_tracking) {
|
|
|
|
|
case MOUSE_NONE:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MOUSE_CLICK:
|
|
|
|
|
case MOUSE_DRAG:
|
|
|
|
|
case MOUSE_MOTION:
|
2019-07-05 15:13:06 +02:00
|
|
|
report_mouse_click(term, encoded, row, col, false);
|
2019-07-05 14:24:51 +02:00
|
|
|
break;
|
2019-11-18 11:18:48 +01:00
|
|
|
|
|
|
|
|
case MOUSE_X10:
|
|
|
|
|
/* Never enabled */
|
|
|
|
|
assert(false && "unimplemented");
|
|
|
|
|
break;
|
2019-07-05 14:24:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2019-11-30 17:11:00 +01:00
|
|
|
term_mouse_up(struct terminal *term, int button, int row, int col)
|
2019-07-05 14:24:51 +02:00
|
|
|
{
|
2019-11-30 17:06:15 +01:00
|
|
|
if (term_mouse_grabbed(term))
|
2019-08-08 17:58:50 +02:00
|
|
|
return;
|
|
|
|
|
|
2019-07-05 15:13:06 +02:00
|
|
|
/* Map libevent button event code to X button number */
|
|
|
|
|
int xbutton = linux_mouse_button_to_x(button);
|
|
|
|
|
if (xbutton == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (xbutton == 4 || xbutton == 5) {
|
|
|
|
|
/* No release events for scroll buttons */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 15:29:16 +02:00
|
|
|
int encoded = encode_xbutton(xbutton);
|
|
|
|
|
if (encoded == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-01-02 15:58:52 +01:00
|
|
|
bool has_focus = term->wl->kbd_focus == term;
|
2019-11-30 17:11:00 +01:00
|
|
|
bool shift = has_focus ? term->wl->kbd.shift : false;
|
|
|
|
|
bool alt = has_focus ? term->wl->kbd.alt : false;
|
|
|
|
|
bool ctrl = has_focus ? term->wl->kbd.ctrl : false;
|
|
|
|
|
|
2019-07-05 15:13:06 +02:00
|
|
|
encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
|
|
|
|
|
|
2019-07-05 14:24:51 +02:00
|
|
|
switch (term->mouse_tracking) {
|
|
|
|
|
case MOUSE_NONE:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MOUSE_CLICK:
|
|
|
|
|
case MOUSE_DRAG:
|
|
|
|
|
case MOUSE_MOTION:
|
2019-07-05 15:13:06 +02:00
|
|
|
report_mouse_click(term, encoded, row, col, true);
|
|
|
|
|
break;
|
2019-11-18 11:18:48 +01:00
|
|
|
|
|
|
|
|
case MOUSE_X10:
|
|
|
|
|
/* Never enabled */
|
|
|
|
|
assert(false && "unimplemented");
|
|
|
|
|
break;
|
2019-07-05 15:13:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2019-11-30 17:11:00 +01:00
|
|
|
term_mouse_motion(struct terminal *term, int button, int row, int col)
|
2019-07-05 15:13:06 +02:00
|
|
|
{
|
2019-11-30 17:06:15 +01:00
|
|
|
if (term_mouse_grabbed(term))
|
2019-08-08 17:58:50 +02:00
|
|
|
return;
|
|
|
|
|
|
2019-07-05 15:13:06 +02:00
|
|
|
int encoded = 0;
|
|
|
|
|
|
|
|
|
|
if (button != 0) {
|
|
|
|
|
/* Map libevent button event code to X button number */
|
|
|
|
|
int xbutton = linux_mouse_button_to_x(button);
|
|
|
|
|
if (xbutton == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
encoded = encode_xbutton(xbutton);
|
|
|
|
|
if (encoded == -1)
|
|
|
|
|
return;
|
|
|
|
|
} else
|
|
|
|
|
encoded = 3; /* "released" */
|
|
|
|
|
|
2020-01-02 15:58:52 +01:00
|
|
|
bool has_focus = term->wl->kbd_focus == term;
|
2019-11-30 17:11:00 +01:00
|
|
|
bool shift = has_focus ? term->wl->kbd.shift : false;
|
|
|
|
|
bool alt = has_focus ? term->wl->kbd.alt : false;
|
|
|
|
|
bool ctrl = has_focus ? term->wl->kbd.ctrl : false;
|
|
|
|
|
|
2019-07-05 15:13:06 +02:00
|
|
|
encoded += 32; /* Motion event */
|
|
|
|
|
encoded += (shift ? 4 : 0) + (alt ? 8 : 0) + (ctrl ? 16 : 0);
|
|
|
|
|
|
|
|
|
|
switch (term->mouse_tracking) {
|
|
|
|
|
case MOUSE_NONE:
|
|
|
|
|
case MOUSE_CLICK:
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case MOUSE_DRAG:
|
|
|
|
|
if (button == 0)
|
|
|
|
|
return;
|
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
|
|
|
|
|
|
case MOUSE_MOTION:
|
|
|
|
|
report_mouse_motion(term, encoded, row, col);
|
2019-07-05 14:24:51 +02:00
|
|
|
break;
|
2019-11-18 11:18:48 +01:00
|
|
|
|
|
|
|
|
case MOUSE_X10:
|
|
|
|
|
/* Never enabled */
|
|
|
|
|
assert(false && "unimplemented");
|
|
|
|
|
break;
|
2019-07-05 14:24:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-07-21 17:35:53 +02:00
|
|
|
|
2019-11-28 19:35:47 +01:00
|
|
|
void
|
|
|
|
|
term_xcursor_update(struct terminal *term)
|
|
|
|
|
{
|
2019-11-29 22:30:56 +01:00
|
|
|
term->xcursor =
|
2019-11-30 12:43:06 +01:00
|
|
|
term->is_searching ? XCURSOR_LEFT_PTR :
|
|
|
|
|
selection_enabled(term) ? XCURSOR_TEXT :
|
|
|
|
|
XCURSOR_HAND2;
|
2019-11-28 19:35:47 +01:00
|
|
|
|
2020-01-04 22:01:19 +01:00
|
|
|
render_xcursor_set(term);
|
2019-11-28 19:35:47 +01:00
|
|
|
}
|
|
|
|
|
|
2019-07-21 17:35:53 +02:00
|
|
|
void
|
|
|
|
|
term_set_window_title(struct terminal *term, const char *title)
|
|
|
|
|
{
|
|
|
|
|
free(term->window_title);
|
|
|
|
|
term->window_title = strdup(title);
|
2020-03-25 18:23:55 +01:00
|
|
|
render_refresh_title(term);
|
2019-07-21 17:35:53 +02:00
|
|
|
}
|
2019-07-30 22:06:02 +02:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_flash(struct terminal *term, unsigned duration_ms)
|
|
|
|
|
{
|
|
|
|
|
LOG_DBG("FLASH for %ums", duration_ms);
|
|
|
|
|
|
|
|
|
|
struct itimerspec alarm = {
|
|
|
|
|
.it_value = {.tv_sec = 0, .tv_nsec = duration_ms * 1000000},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (timerfd_settime(term->flash.fd, 0, &alarm, NULL) < 0)
|
|
|
|
|
LOG_ERRNO("failed to arm flash timer");
|
|
|
|
|
else {
|
|
|
|
|
term->flash.active = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-21 15:27:17 +01:00
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_spawn_new(const struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
if (pid < 0) {
|
|
|
|
|
LOG_ERRNO("failed to fork new terminal");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pid == 0) {
|
|
|
|
|
/* Child */
|
|
|
|
|
int pipe_fds[2] = {-1, -1};
|
|
|
|
|
if (pipe2(pipe_fds, O_CLOEXEC) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to create pipe");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Double fork */
|
|
|
|
|
pid_t pid2 = fork();
|
|
|
|
|
if (pid2 < 0) {
|
|
|
|
|
LOG_ERRNO("failed to double fork new terminal");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pid2 == 0) {
|
|
|
|
|
/* Child */
|
|
|
|
|
close(pipe_fds[0]);
|
2020-02-20 18:46:16 +01:00
|
|
|
if (chdir(term->cwd) < 0 ||
|
|
|
|
|
execlp(term->foot_exe, term->foot_exe, NULL) < 0)
|
|
|
|
|
{
|
|
|
|
|
(void)!write(pipe_fds[1], &errno, sizeof(errno));
|
|
|
|
|
_exit(errno);
|
|
|
|
|
}
|
|
|
|
|
assert(false);
|
2019-12-21 15:27:17 +01:00
|
|
|
_exit(errno);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parent */
|
|
|
|
|
|
|
|
|
|
close(pipe_fds[1]);
|
|
|
|
|
|
|
|
|
|
int _errno;
|
|
|
|
|
static_assert(sizeof(_errno) == sizeof(errno), "errno size mismatch");
|
|
|
|
|
|
|
|
|
|
ssize_t ret = read(pipe_fds[0], &_errno, sizeof(_errno));
|
|
|
|
|
close(pipe_fds[0]);
|
|
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
|
_exit(0);
|
|
|
|
|
else if (ret < 0)
|
|
|
|
|
LOG_ERRNO("failed to read from pipe");
|
|
|
|
|
else {
|
|
|
|
|
LOG_ERRNO_P("%s: failed to spawn new terminal", _errno, term->foot_exe);
|
|
|
|
|
errno = _errno;
|
|
|
|
|
waitpid(pid2, NULL, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
if (pipe_fds[0] != -1)
|
|
|
|
|
close(pipe_fds[0]);
|
|
|
|
|
_exit(errno);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int result;
|
|
|
|
|
waitpid(pid, &result, 0);
|
|
|
|
|
return WIFEXITED(result) && WEXITSTATUS(result) == 0;
|
|
|
|
|
}
|
2020-01-12 12:43:28 +01:00
|
|
|
|
|
|
|
|
void
|
2020-01-12 12:55:19 +01:00
|
|
|
term_enable_app_sync_updates(struct terminal *term)
|
2020-01-12 12:43:28 +01:00
|
|
|
{
|
2020-03-16 17:05:44 +01:00
|
|
|
if (!term->render.app_sync_updates.enabled)
|
|
|
|
|
term->render.app_sync_updates.flipped = true;
|
|
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
term->render.app_sync_updates.enabled = true;
|
2020-01-12 12:43:28 +01:00
|
|
|
|
|
|
|
|
if (timerfd_settime(
|
2020-01-12 12:55:19 +01:00
|
|
|
term->render.app_sync_updates.timer_fd, 0,
|
2020-01-12 12:43:28 +01:00
|
|
|
&(struct itimerspec){.it_value = {.tv_sec = 1}}, NULL) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERR("failed to arm timer for application synchronized updates");
|
|
|
|
|
}
|
2020-01-12 12:45:34 +01:00
|
|
|
|
|
|
|
|
/* Disarm delayed rendering timers */
|
|
|
|
|
timerfd_settime(
|
|
|
|
|
term->delayed_render_timer.lower_fd, 0,
|
2020-04-13 12:03:11 +02:00
|
|
|
&(struct itimerspec){}, NULL);
|
2020-01-12 12:45:34 +01:00
|
|
|
timerfd_settime(
|
|
|
|
|
term->delayed_render_timer.upper_fd, 0,
|
2020-04-13 12:03:11 +02:00
|
|
|
&(struct itimerspec){}, NULL);
|
2020-03-25 18:24:58 +01:00
|
|
|
term->delayed_render_timer.is_armed = false;
|
2020-01-12 12:43:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2020-01-12 12:55:19 +01:00
|
|
|
term_disable_app_sync_updates(struct terminal *term)
|
2020-01-12 12:43:28 +01:00
|
|
|
{
|
2020-01-12 12:55:19 +01:00
|
|
|
if (!term->render.app_sync_updates.enabled)
|
2020-01-12 12:43:28 +01:00
|
|
|
return;
|
|
|
|
|
|
2020-01-12 12:55:19 +01:00
|
|
|
term->render.app_sync_updates.enabled = false;
|
2020-03-16 17:05:44 +01:00
|
|
|
term->render.app_sync_updates.flipped = true;
|
|
|
|
|
render_refresh(term);
|
2020-01-12 12:43:28 +01:00
|
|
|
|
|
|
|
|
/* Reset timers */
|
|
|
|
|
timerfd_settime(
|
2020-01-12 12:55:19 +01:00
|
|
|
term->render.app_sync_updates.timer_fd, 0,
|
2020-04-13 12:03:11 +02:00
|
|
|
&(struct itimerspec){}, NULL);
|
2020-01-12 12:43:28 +01:00
|
|
|
}
|
2020-01-20 18:34:32 +01:00
|
|
|
|
|
|
|
|
static inline void
|
2020-05-09 12:04:55 +02:00
|
|
|
print_linewrap(struct terminal *term)
|
2020-01-20 18:34:32 +01:00
|
|
|
{
|
2020-05-09 12:04:55 +02:00
|
|
|
if (likely(!term->grid->cursor.lcf)) {
|
2020-01-20 18:34:32 +01:00
|
|
|
/* Not and end of line */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unlikely(!term->auto_margin)) {
|
|
|
|
|
/* Auto-wrap disabled */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-16 18:51:14 +02:00
|
|
|
if (term->grid->cursor.point.row == term->scroll_region.end - 1) {
|
2020-01-20 18:34:32 +01:00
|
|
|
term_scroll(term, 1);
|
2020-04-16 18:51:14 +02:00
|
|
|
term_cursor_to(term, term->grid->cursor.point.row, 0);
|
2020-01-20 18:34:32 +01:00
|
|
|
} else
|
2020-04-16 18:51:14 +02:00
|
|
|
term_cursor_to(term, min(term->grid->cursor.point.row + 1, term->rows - 1), 0);
|
2020-01-20 18:34:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
|
print_insert(struct terminal *term, int width)
|
|
|
|
|
{
|
|
|
|
|
assert(width > 0);
|
|
|
|
|
|
|
|
|
|
if (unlikely(term->insert_mode)) {
|
|
|
|
|
struct row *row = term->grid->cur_row;
|
2020-04-16 18:51:14 +02:00
|
|
|
const size_t move_count = max(0, term->cols - term->grid->cursor.point.col - width);
|
2020-01-20 18:34:32 +01:00
|
|
|
|
|
|
|
|
memmove(
|
2020-04-16 18:51:14 +02:00
|
|
|
&row->cells[term->grid->cursor.point.col + width],
|
|
|
|
|
&row->cells[term->grid->cursor.point.col],
|
2020-01-20 18:34:32 +01:00
|
|
|
move_count * sizeof(struct cell));
|
|
|
|
|
|
|
|
|
|
/* Mark moved cells as dirty */
|
2020-04-16 18:51:14 +02:00
|
|
|
for (size_t i = term->grid->cursor.point.col + width; i < term->cols; i++)
|
2020-01-20 18:34:32 +01:00
|
|
|
row->cells[i].attrs.clean = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_print(struct terminal *term, wchar_t wc, int width)
|
|
|
|
|
{
|
|
|
|
|
if (unlikely(width <= 0))
|
|
|
|
|
return;
|
|
|
|
|
|
2020-05-09 12:04:55 +02:00
|
|
|
print_linewrap(term);
|
2020-01-20 18:34:32 +01:00
|
|
|
print_insert(term, width);
|
|
|
|
|
|
2020-02-22 23:06:11 +01:00
|
|
|
sixel_delete_at_cursor(term);
|
2020-02-22 21:35:45 +01:00
|
|
|
|
2020-01-22 18:22:15 +01:00
|
|
|
/* *Must* get current cell *after* linewrap+insert */
|
|
|
|
|
struct row *row = term->grid->cur_row;
|
2020-04-16 18:51:14 +02:00
|
|
|
struct cell *cell = &row->cells[term->grid->cursor.point.col];
|
2020-01-22 18:22:15 +01:00
|
|
|
|
2020-01-20 18:34:32 +01:00
|
|
|
cell->wc = term->vt.last_printed = wc;
|
|
|
|
|
cell->attrs = term->vt.attrs;
|
|
|
|
|
|
|
|
|
|
row->dirty = true;
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
|
|
|
|
|
/* Advance cursor the 'additional' columns while dirty:ing the cells */
|
2020-04-16 18:51:14 +02:00
|
|
|
for (int i = 1; i < width && term->grid->cursor.point.col < term->cols - 1; i++) {
|
2020-01-20 18:34:32 +01:00
|
|
|
term_cursor_right(term, 1);
|
|
|
|
|
|
2020-04-16 18:51:14 +02:00
|
|
|
assert(term->grid->cursor.point.col < term->cols);
|
|
|
|
|
struct cell *cell = &row->cells[term->grid->cursor.point.col];
|
2020-01-20 18:34:32 +01:00
|
|
|
cell->wc = 0;
|
|
|
|
|
cell->attrs.clean = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Advance cursor */
|
2020-04-16 18:51:14 +02:00
|
|
|
if (term->grid->cursor.point.col < term->cols - 1)
|
2020-01-20 18:34:32 +01:00
|
|
|
term_cursor_right(term, 1);
|
|
|
|
|
else
|
2020-04-16 18:51:14 +02:00
|
|
|
term->grid->cursor.lcf = true;
|
2020-01-20 18:34:32 +01:00
|
|
|
}
|
2020-02-24 22:38:35 +01:00
|
|
|
|
|
|
|
|
enum term_surface
|
|
|
|
|
term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
|
|
|
|
|
{
|
|
|
|
|
if (surface == term->window->surface)
|
|
|
|
|
return TERM_SURF_GRID;
|
|
|
|
|
else if (surface == term->window->search_surface)
|
|
|
|
|
return TERM_SURF_SEARCH;
|
2020-03-02 20:29:28 +01:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_TITLE])
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_TITLE;
|
2020-03-02 20:29:28 +01:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_LEFT])
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_BORDER_LEFT;
|
2020-03-02 20:29:28 +01:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_RIGHT])
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_BORDER_RIGHT;
|
2020-03-02 20:29:28 +01:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_TOP])
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_BORDER_TOP;
|
2020-03-02 20:29:28 +01:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_BOTTOM])
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_BORDER_BOTTOM;
|
2020-03-02 20:29:28 +01:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_MINIMIZE])
|
|
|
|
|
return TERM_SURF_BUTTON_MINIMIZE;
|
|
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_MAXIMIZE])
|
|
|
|
|
return TERM_SURF_BUTTON_MAXIMIZE;
|
|
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_CLOSE])
|
|
|
|
|
return TERM_SURF_BUTTON_CLOSE;
|
2020-02-24 22:38:35 +01:00
|
|
|
else
|
|
|
|
|
return TERM_SURF_NONE;
|
|
|
|
|
}
|