2019-06-29 21:03:28 +02:00
|
|
|
#include "terminal.h"
|
|
|
|
|
|
2021-01-19 15:05:22 +00:00
|
|
|
#if defined(__GLIBC__)
|
2019-11-03 12:48:18 +01:00
|
|
|
#include <malloc.h>
|
2021-01-19 15:05:22 +00:00
|
|
|
#endif
|
|
|
|
|
#include <signal.h>
|
2019-06-29 21:03:28 +02:00
|
|
|
#include <string.h>
|
2019-07-05 14:24:51 +02:00
|
|
|
#include <unistd.h>
|
2019-10-28 18:25:19 +01:00
|
|
|
#include <errno.h>
|
2022-10-09 16:14:49 +02:00
|
|
|
#include <limits.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>
|
2020-06-02 19:59:28 +02:00
|
|
|
#include <sys/ioctl.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"
|
2021-08-05 18:24:52 +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"
|
2022-05-11 17:58:18 +02:00
|
|
|
#include "commands.h"
|
2020-02-22 00:23:19 +01:00
|
|
|
#include "config.h"
|
2021-01-15 20:39:45 +00:00
|
|
|
#include "debug.h"
|
2020-07-15 11:33:37 +02:00
|
|
|
#include "extract.h"
|
2019-07-01 12:23:38 +02:00
|
|
|
#include "grid.h"
|
2020-12-04 18:39:11 +01:00
|
|
|
#include "ime.h"
|
2021-08-05 18:34:09 +02:00
|
|
|
#include "input.h"
|
2020-12-10 18:22:48 +01:00
|
|
|
#include "notify.h"
|
2020-03-01 13:09:25 +01:00
|
|
|
#include "quirks.h"
|
2020-05-21 20:17:29 +02:00
|
|
|
#include "reaper.h"
|
2019-07-21 17:35:53 +02:00
|
|
|
#include "render.h"
|
2019-08-01 20:51:11 +02:00
|
|
|
#include "selection.h"
|
2022-05-11 17:58:18 +02:00
|
|
|
#include "shm.h"
|
2020-02-22 00:23:19 +01:00
|
|
|
#include "sixel.h"
|
2019-10-28 18:25:19 +01:00
|
|
|
#include "slave.h"
|
2020-07-15 12:39:10 +02:00
|
|
|
#include "spawn.h"
|
2021-01-31 11:12:07 +01:00
|
|
|
#include "url-mode.h"
|
2020-05-01 11:46:24 +02:00
|
|
|
#include "util.h"
|
2020-02-22 00:23:19 +01:00
|
|
|
#include "vt.h"
|
2020-08-08 20:34:30 +01:00
|
|
|
#include "xmalloc.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
|
|
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
static void
|
|
|
|
|
enqueue_data_for_slave(const void *data, size_t len, size_t offset,
|
|
|
|
|
ptmx_buffer_list_t *buffer_list)
|
2019-11-03 01:25:41 +01:00
|
|
|
{
|
2020-08-22 09:25:25 +02:00
|
|
|
void *copy = xmalloc(len);
|
|
|
|
|
memcpy(copy, data, len);
|
2020-02-03 19:58:32 +01:00
|
|
|
|
2020-08-22 09:25:25 +02:00
|
|
|
struct ptmx_buffer queued = {
|
|
|
|
|
.data = copy,
|
|
|
|
|
.len = len,
|
|
|
|
|
.idx = offset,
|
|
|
|
|
};
|
|
|
|
|
tll_push_back(*buffer_list, queued);
|
2020-08-22 09:14:18 +02:00
|
|
|
}
|
2019-11-03 01:25:41 +01:00
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
static bool
|
|
|
|
|
data_to_slave(struct terminal *term, const void *data, size_t len,
|
|
|
|
|
ptmx_buffer_list_t *buffer_list)
|
|
|
|
|
{
|
2019-11-03 12:13:51 +01:00
|
|
|
/*
|
|
|
|
|
* Try a synchronous write first. If we fail to write everything,
|
|
|
|
|
* switch to asynchronous.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
size_t async_idx = 0;
|
|
|
|
|
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;
|
2020-08-22 09:14:18 +02:00
|
|
|
enqueue_data_for_slave(data, len, async_idx, buffer_list);
|
|
|
|
|
return true;
|
2019-11-03 01:25:41 +01:00
|
|
|
|
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
|
|
|
|
2021-02-09 13:52:33 +00:00
|
|
|
BUG("Unexpected async_write() return value");
|
2019-11-03 12:13:51 +01:00
|
|
|
return false;
|
2020-08-22 09:14:18 +02:00
|
|
|
}
|
2019-11-03 12:13:51 +01:00
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
bool
|
|
|
|
|
term_paste_data_to_slave(struct terminal *term, const void *data, size_t len)
|
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->is_sending_paste_data);
|
2019-11-03 01:03:52 +01:00
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
if (term->ptmx < 0) {
|
|
|
|
|
/* We're probably in "hold" */
|
|
|
|
|
return false;
|
2019-11-03 01:03:52 +01:00
|
|
|
}
|
2020-08-22 09:14:18 +02:00
|
|
|
|
|
|
|
|
if (tll_length(term->ptmx_paste_buffers) > 0) {
|
|
|
|
|
/* Don't even try to send data *now* if there's queued up
|
|
|
|
|
* data, since that would result in events arriving out of
|
|
|
|
|
* order. */
|
|
|
|
|
enqueue_data_for_slave(data, len, 0, &term->ptmx_paste_buffers);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return data_to_slave(term, data, len, &term->ptmx_paste_buffers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_to_slave(struct terminal *term, const void *data, size_t len)
|
|
|
|
|
{
|
|
|
|
|
if (term->ptmx < 0) {
|
|
|
|
|
/* We're probably in "hold" */
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tll_length(term->ptmx_buffers) > 0 || term->is_sending_paste_data) {
|
|
|
|
|
/*
|
|
|
|
|
* Don't even try to send data *now* if there's queued up
|
|
|
|
|
* data, since that would result in events arriving out of
|
|
|
|
|
* order.
|
|
|
|
|
*
|
|
|
|
|
* Furthermore, if we're currently sending paste data to the
|
|
|
|
|
* client, do *not* mix that stream with other events
|
|
|
|
|
* (https://codeberg.org/dnkl/foot/issues/101).
|
|
|
|
|
*/
|
|
|
|
|
enqueue_data_for_slave(data, len, 0, &term->ptmx_buffers);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return data_to_slave(term, data, len, &term->ptmx_buffers);
|
2019-11-03 01:03:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 */
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(tll_length(term->ptmx_buffers) > 0 ||
|
2020-08-22 09:14:18 +02:00
|
|
|
tll_length(term->ptmx_paste_buffers) > 0);
|
|
|
|
|
|
|
|
|
|
/* Writes a single buffer, returns if not all of it could be written */
|
|
|
|
|
#define write_one_buffer(buffer_list) \
|
|
|
|
|
{ \
|
|
|
|
|
switch (async_write(term->ptmx, it->item.data, it->item.len, &it->item.idx)) { \
|
|
|
|
|
case ASYNC_WRITE_DONE: \
|
|
|
|
|
free(it->item.data); \
|
|
|
|
|
tll_remove(buffer_list, it); \
|
|
|
|
|
break; \
|
|
|
|
|
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:25:41 +01:00
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
tll_foreach(term->ptmx_paste_buffers, it)
|
|
|
|
|
write_one_buffer(term->ptmx_paste_buffers);
|
2019-11-04 13:46:04 +01:00
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
/* If we get here, *all* paste data buffers were successfully
|
|
|
|
|
* flushed */
|
|
|
|
|
|
|
|
|
|
if (!term->is_sending_paste_data) {
|
|
|
|
|
tll_foreach(term->ptmx_buffers, it)
|
|
|
|
|
write_one_buffer(term->ptmx_buffers);
|
2019-11-03 01:03:52 +01:00
|
|
|
}
|
|
|
|
|
|
2020-08-22 09:14:18 +02:00
|
|
|
/*
|
|
|
|
|
* If we get here, *all* buffers were successfully flushed.
|
|
|
|
|
*
|
|
|
|
|
* Or, we're still sending paste data, in which case we do *not*
|
|
|
|
|
* want to send the "normal" queued up data
|
|
|
|
|
*
|
|
|
|
|
* In both cases, we want to *disable* the FDM callback since
|
|
|
|
|
* otherwise we'd just be called right away again, with nothing to
|
|
|
|
|
* write.
|
|
|
|
|
*/
|
2020-01-10 19:51:16 +01:00
|
|
|
fdm_event_del(term->fdm, term->ptmx, EPOLLOUT);
|
2019-11-03 01:03:52 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-23 20:24:04 +02:00
|
|
|
static bool
|
|
|
|
|
add_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx)
|
|
|
|
|
{
|
2023-05-17 20:51:40 +02:00
|
|
|
#if defined(UTMP_ADD)
|
2022-09-23 20:24:04 +02:00
|
|
|
if (ptmx < 0)
|
|
|
|
|
return true;
|
2023-05-17 20:51:40 +02:00
|
|
|
if (conf->utmp_helper_path == NULL)
|
2022-09-23 20:24:04 +02:00
|
|
|
return true;
|
|
|
|
|
|
2023-05-17 20:51:40 +02:00
|
|
|
char *const argv[] = {conf->utmp_helper_path, UTMP_ADD, getenv("WAYLAND_DISPLAY"), NULL};
|
2022-09-23 20:24:04 +02:00
|
|
|
return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL);
|
2023-05-17 20:51:40 +02:00
|
|
|
#else
|
|
|
|
|
return true;
|
|
|
|
|
#endif
|
2022-09-23 20:24:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
del_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx)
|
|
|
|
|
{
|
2023-05-17 20:51:40 +02:00
|
|
|
#if defined(UTMP_DEL)
|
2022-09-23 20:24:04 +02:00
|
|
|
if (ptmx < 0)
|
|
|
|
|
return true;
|
2023-05-17 20:51:40 +02:00
|
|
|
if (conf->utmp_helper_path == NULL)
|
2022-09-23 20:24:04 +02:00
|
|
|
return true;
|
|
|
|
|
|
2023-05-17 20:51:40 +02:00
|
|
|
char *del_argument =
|
|
|
|
|
#if defined(UTMP_DEL_HAVE_ARGUMENT)
|
|
|
|
|
getenv("WAYLAND_DISPLAY")
|
|
|
|
|
#else
|
|
|
|
|
NULL
|
|
|
|
|
#endif
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
char *const argv[] = {conf->utmp_helper_path, UTMP_DEL, del_argument, NULL};
|
2022-09-23 20:24:04 +02:00
|
|
|
return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL);
|
2023-05-17 20:51:40 +02:00
|
|
|
#else
|
|
|
|
|
return true;
|
|
|
|
|
#endif
|
2022-09-23 20:24:04 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-31 20:37:43 +01:00
|
|
|
#if PTMX_TIMING
|
2020-08-23 07:42:20 +02:00
|
|
|
static struct timespec last = {0};
|
2019-12-31 20:26:30 +01:00
|
|
|
#endif
|
|
|
|
|
|
2020-11-26 18:08:28 +01:00
|
|
|
static bool cursor_blink_rearm_timer(struct terminal *term);
|
|
|
|
|
|
2020-11-21 13:25:56 +01:00
|
|
|
/* Externally visible, but not declared in terminal.h, to enable pgo
|
|
|
|
|
* to call this function directly */
|
|
|
|
|
bool
|
2019-10-28 18:35:16 +01:00
|
|
|
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 */
|
2020-11-26 18:08:28 +01:00
|
|
|
if (term->cursor_blink.fd >= 0) {
|
|
|
|
|
term->cursor_blink.state = CURSOR_BLINK_ON;
|
|
|
|
|
cursor_blink_rearm_timer(term);
|
|
|
|
|
}
|
2020-02-05 19:50:49 +01:00
|
|
|
|
2022-10-09 16:15:29 +02:00
|
|
|
if (unlikely(term->interactive_resizing.grid != NULL)) {
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Don't consume PTMX while we're doing an interactive resize,
|
|
|
|
|
* since the 'normal' grid we're currently using is a
|
2022-10-09 16:15:29 +02:00
|
|
|
* temporary one - all changes done to it will be lost when
|
|
|
|
|
* the interactive resize ends.
|
|
|
|
|
*/
|
2022-10-18 18:28:51 +02:00
|
|
|
return true;
|
2022-10-09 16:15:29 +02:00
|
|
|
}
|
|
|
|
|
|
2020-01-10 19:51:16 +01:00
|
|
|
uint8_t buf[24 * 1024];
|
2022-10-09 16:14:49 +02:00
|
|
|
const size_t max_iterations = !hup ? 10 : SIZE_MAX;
|
2019-10-28 18:35:16 +01:00
|
|
|
|
2021-04-07 19:09:31 +02:00
|
|
|
for (size_t i = 0; i < max_iterations && pollin; i++) {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(pollin);
|
2021-09-05 12:39:25 +02:00
|
|
|
ssize_t 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) {
|
2021-04-07 19:09:31 +02:00
|
|
|
if (errno == EAGAIN || errno == EIO) {
|
|
|
|
|
/*
|
|
|
|
|
* EAGAIN: no more to read - FDM will trigger us again
|
|
|
|
|
* EIO: assume PTY was closed - we already have, or will get, a EPOLLHUP
|
|
|
|
|
*/
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-06-19 11:33:03 +02:00
|
|
|
|
2020-02-05 19:50:49 +01:00
|
|
|
LOG_ERRNO("failed to read from pseudo terminal");
|
|
|
|
|
return false;
|
2021-04-18 13:56:56 +03:00
|
|
|
} else if (count == 0) {
|
2021-04-18 14:05:28 +03:00
|
|
|
/* Reached end-of-file */
|
2021-04-18 13:56:56 +03:00
|
|
|
break;
|
2020-02-05 19:50:49 +01:00
|
|
|
}
|
|
|
|
|
|
2022-10-09 16:15:29 +02:00
|
|
|
xassert(term->interactive_resizing.grid == NULL);
|
2020-02-05 19:50:49 +01:00
|
|
|
vt_from_slave(term, buf, count);
|
|
|
|
|
}
|
2019-12-19 07:23:58 +01:00
|
|
|
|
2020-12-14 19:05:03 +01:00
|
|
|
if (!term->render.app_sync_updates.enabled) {
|
2020-03-17 16:32:57 +01:00
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
|
2022-01-14 11:39:27 -03:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
2020-01-12 12:23:29 +01:00
|
|
|
if (last.tv_sec > 0 || last.tv_nsec > 0) {
|
2022-01-15 14:56:13 +05:30
|
|
|
struct timespec diff;
|
2020-01-12 12:23:29 +01:00
|
|
|
|
2022-01-15 14:56:13 +05:30
|
|
|
timespec_sub(&now, &last, &diff);
|
|
|
|
|
LOG_INFO("waited %lds %ldns for more input",
|
|
|
|
|
(long)diff.tv_sec, diff.tv_nsec);
|
2020-01-12 12:23:29 +01:00
|
|
|
}
|
|
|
|
|
last = now;
|
2019-12-31 20:26:30 +01:00
|
|
|
#endif
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(lower_ns < 1000000000);
|
|
|
|
|
xassert(upper_ns < 1000000000);
|
|
|
|
|
xassert(upper_ns > lower_ns);
|
2020-03-17 16:46:54 +01:00
|
|
|
|
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) {
|
2022-09-23 20:24:04 +02:00
|
|
|
del_utmp_record(term->conf, term->reaper, term->ptmx);
|
2020-12-26 01:29:40 +01:00
|
|
|
fdm_del(fdm, fd);
|
|
|
|
|
term->ptmx = -1;
|
2020-02-03 19:58:32 +01:00
|
|
|
}
|
2020-12-14 19:05:03 +01:00
|
|
|
|
2019-10-30 20:03:11 +01:00
|
|
|
return true;
|
2019-10-28 18:35:16 +01:00
|
|
|
}
|
|
|
|
|
|
2022-10-09 16:16:23 +02:00
|
|
|
bool
|
|
|
|
|
term_ptmx_pause(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
return fdm_event_del(term->fdm, term->ptmx, EPOLLIN);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_ptmx_resume(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
return fdm_event_add(term->fdm, term->ptmx, EPOLLIN);
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
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.state = BLINK_ON;
|
2020-10-13 18:40:20 +02:00
|
|
|
fdm_del(term->fdm, term->blink.fd);
|
|
|
|
|
term->blink.fd = -1;
|
2019-12-17 19:11:27 +01:00
|
|
|
} 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)
|
|
|
|
|
{
|
2020-10-13 18:40:20 +02:00
|
|
|
if (term->blink.fd >= 0)
|
2019-12-17 19:11:27 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("arming blink timer");
|
2020-10-13 18:40:20 +02:00
|
|
|
|
|
|
|
|
int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
LOG_ERRNO("failed to create blink timer FD");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_blink, term)) {
|
|
|
|
|
close(fd);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 19:11:27 +01:00
|
|
|
struct itimerspec alarm = {
|
|
|
|
|
.it_value = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
|
|
|
|
|
.it_interval = {.tv_sec = 0, .tv_nsec = 500 * 1000000},
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-13 18:40:20 +02:00
|
|
|
if (timerfd_settime(fd, 0, &alarm, NULL) < 0) {
|
2019-12-17 19:11:27 +01:00
|
|
|
LOG_ERRNO("failed to arm blink timer");
|
2020-10-13 18:40:20 +02:00
|
|
|
fdm_del(term->fdm, fd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
term->blink.fd = fd;
|
2019-12-17 19:11:27 +01:00
|
|
|
}
|
|
|
|
|
|
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-08-23 07:42:20 +02:00
|
|
|
last = (struct timespec){0};
|
2019-12-31 20:26:30 +01:00
|
|
|
#endif
|
|
|
|
|
|
2019-11-01 20:27:45 +01:00
|
|
|
/* Reset timers */
|
2020-08-23 07:42:20 +02:00
|
|
|
struct itimerspec reset = {{0}};
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 15:53:47 +02:00
|
|
|
static bool
|
|
|
|
|
fdm_title_update_timeout(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
if (events & EPOLLHUP)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
uint64_t unused;
|
|
|
|
|
ssize_t ret = read(term->render.title.timer_fd, &unused, sizeof(unused));
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
if (errno == EAGAIN)
|
|
|
|
|
return true;
|
|
|
|
|
LOG_ERRNO("failed to read title update throttle timer");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct itimerspec reset = {{0}};
|
|
|
|
|
timerfd_settime(term->render.title.timer_fd, 0, &reset, NULL);
|
|
|
|
|
|
|
|
|
|
render_refresh_title(term);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 17:45:59 +01:00
|
|
|
static bool
|
|
|
|
|
initialize_render_workers(struct terminal *term)
|
|
|
|
|
{
|
2021-11-06 12:01:57 +01:00
|
|
|
LOG_INFO("using %hu rendering threads", term->render.workers.count);
|
2019-10-30 17:45:59 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-08 20:34:30 +01:00
|
|
|
term->render.workers.threads = xcalloc(
|
2019-10-30 17:45:59 +01:00
|
|
|
term->render.workers.count, sizeof(term->render.workers.threads[0]));
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < term->render.workers.count; i++) {
|
2020-08-08 20:34:30 +01:00
|
|
|
struct render_worker_context *ctx = xmalloc(sizeof(*ctx));
|
2019-10-30 17:45:59 +01:00
|
|
|
*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
|
|
|
}
|
|
|
|
|
|
2021-06-09 10:31:24 +02:00
|
|
|
static void
|
2021-09-14 09:50:49 +02:00
|
|
|
free_custom_glyph(struct fcft_glyph **glyph)
|
2021-06-09 10:31:24 +02:00
|
|
|
{
|
2021-09-14 09:50:49 +02:00
|
|
|
if (*glyph == NULL)
|
2021-06-09 10:31:24 +02:00
|
|
|
return;
|
|
|
|
|
|
2021-09-14 09:50:49 +02:00
|
|
|
free(pixman_image_get_data((*glyph)->pix));
|
|
|
|
|
pixman_image_unref((*glyph)->pix);
|
|
|
|
|
free(*glyph);
|
|
|
|
|
*glyph = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
free_custom_glyphs(struct fcft_glyph ***glyphs, size_t count)
|
|
|
|
|
{
|
|
|
|
|
if (*glyphs == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < count; i++)
|
|
|
|
|
free_custom_glyph(&(*glyphs)[i]);
|
|
|
|
|
|
|
|
|
|
free(*glyphs);
|
|
|
|
|
*glyphs = NULL;
|
2021-06-09 10:31:24 +02:00
|
|
|
}
|
|
|
|
|
|
2022-11-24 14:34:31 +01:00
|
|
|
static void
|
|
|
|
|
term_line_height_update(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
|
2022-11-24 17:09:31 +01:00
|
|
|
if (term->conf->line_height.px < 0) {
|
|
|
|
|
term->font_line_height.pt = 0;
|
|
|
|
|
term->font_line_height.px = -1;
|
2022-11-24 14:34:31 +01:00
|
|
|
return;
|
2022-11-24 17:09:31 +01:00
|
|
|
}
|
2022-11-24 14:34:31 +01:00
|
|
|
|
|
|
|
|
const float dpi = term->font_is_sized_by_dpi ? term->font_dpi : 96.;
|
|
|
|
|
|
|
|
|
|
const float font_original_pt_size =
|
|
|
|
|
conf->fonts[0].arr[0].px_size > 0
|
|
|
|
|
? conf->fonts[0].arr[0].px_size * 72. / dpi
|
|
|
|
|
: conf->fonts[0].arr[0].pt_size;
|
|
|
|
|
const float font_current_pt_size =
|
|
|
|
|
term->font_sizes[0][0].px_size > 0
|
|
|
|
|
? term->font_sizes[0][0].px_size * 72. / dpi
|
|
|
|
|
: term->font_sizes[0][0].pt_size;
|
|
|
|
|
|
|
|
|
|
const float change = font_current_pt_size / font_original_pt_size;
|
|
|
|
|
const float line_original_pt_size = conf->line_height.px > 0
|
|
|
|
|
? conf->line_height.px * 72. / dpi
|
|
|
|
|
: conf->line_height.pt;
|
|
|
|
|
|
|
|
|
|
term->font_line_height.px = 0;
|
|
|
|
|
term->font_line_height.pt = fmaxf(line_original_pt_size * change, 0.);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 17:50:12 +01:00
|
|
|
static bool
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4],
|
|
|
|
|
bool resize_grid)
|
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++) {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(fonts[i] != 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
|
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-14 09:50:49 +02:00
|
|
|
free_custom_glyphs(
|
|
|
|
|
&term->custom_glyphs.box_drawing, GLYPH_BOX_DRAWING_COUNT);
|
|
|
|
|
free_custom_glyphs(
|
|
|
|
|
&term->custom_glyphs.braille, GLYPH_BRAILLE_COUNT);
|
|
|
|
|
free_custom_glyphs(
|
|
|
|
|
&term->custom_glyphs.legacy, GLYPH_LEGACY_COUNT);
|
2020-12-26 16:44:11 +01:00
|
|
|
|
2021-01-07 17:00:58 +01:00
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
const struct fcft_glyph *M = fcft_rasterize_char_utf32(
|
|
|
|
|
fonts[0], U'M', term->font_subpixel);
|
|
|
|
|
int advance = M != NULL ? M->advance.x : term->fonts[0]->max_advance.x;
|
2021-08-31 19:56:59 +02:00
|
|
|
|
2022-11-24 14:34:31 +01:00
|
|
|
term_line_height_update(term);
|
|
|
|
|
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
term->cell_width = advance +
|
|
|
|
|
term_pt_or_px_as_pixels(term, &conf->letter_spacing);
|
2021-01-07 17:00:58 +01:00
|
|
|
|
2021-03-20 15:32:31 +01:00
|
|
|
term->cell_height = term->font_line_height.px >= 0
|
2021-04-30 20:31:47 +02:00
|
|
|
? term_pt_or_px_as_pixels(term, &term->font_line_height)
|
2021-01-07 11:17:23 +01:00
|
|
|
: max(term->fonts[0]->height,
|
|
|
|
|
term->fonts[0]->ascent + term->fonts[0]->descent);
|
2021-01-07 17:00:58 +01:00
|
|
|
|
2021-12-04 18:41:36 +01:00
|
|
|
if (term->cell_width <= 0)
|
|
|
|
|
term->cell_width = 1;
|
|
|
|
|
if (term->cell_height <= 0)
|
|
|
|
|
term->cell_height = 1;
|
|
|
|
|
|
2021-04-30 20:31:47 +02:00
|
|
|
term->font_x_ofs = term_pt_or_px_as_pixels(term, &conf->horizontal_letter_offset);
|
|
|
|
|
term->font_y_ofs = term_pt_or_px_as_pixels(term, &conf->vertical_letter_offset);
|
2021-01-07 17:00:58 +01:00
|
|
|
|
2023-10-10 14:23:33 +02:00
|
|
|
term->font_baseline = term_font_baseline(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
|
|
|
LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height);
|
|
|
|
|
|
2023-06-29 14:49:54 +02:00
|
|
|
sixel_cell_size_changed(term);
|
2020-10-04 13:10:56 +02:00
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
/* Optimization - some code paths (are forced to) call
|
|
|
|
|
* render_resize() after this function */
|
|
|
|
|
if (resize_grid) {
|
|
|
|
|
/* Use force, since cell-width/height may have changed */
|
2024-01-17 15:00:14 -05:00
|
|
|
render_resize(
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
term,
|
2023-07-25 15:56:30 +02:00
|
|
|
(int)roundf(term->width / term->scale),
|
2024-01-17 15:00:14 -05:00
|
|
|
(int)roundf(term->height / term->scale),
|
|
|
|
|
RESIZE_FORCE | RESIZE_KEEP_GRID);
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02: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
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-26 07:45:03 +02:00
|
|
|
static float
|
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
|
|
|
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.
|
|
|
|
|
*
|
2023-07-17 16:19:14 +02:00
|
|
|
* However, to deal with legacy 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.
|
2020-03-11 16:10:55 +01:00
|
|
|
*
|
|
|
|
|
* For integral scaling factors the resulting DPI is the same as
|
|
|
|
|
* if we had used the physical DPI.
|
|
|
|
|
*
|
2023-07-17 16:19:14 +02:00
|
|
|
* For legacy fractional scaling factors we'll get a DPI *larger*
|
|
|
|
|
* than the physical DPI, that ends up being right when later
|
2020-03-11 16:10:55 +01:00
|
|
|
* downscaled by the compositor.
|
2023-07-17 16:19:14 +02:00
|
|
|
*
|
|
|
|
|
* With the newer fractional-scale-v1 protocol, we use the
|
2024-02-06 12:36:45 +01:00
|
|
|
* monitor's real DPI, since we scale everything to the correct
|
2023-07-17 16:19:14 +02:00
|
|
|
* scaling factor (no downscaling done by the compositor).
|
2020-03-11 16:10:55 +01:00
|
|
|
*/
|
|
|
|
|
|
2023-07-17 16:19:14 +02:00
|
|
|
xassert(tll_length(term->wl->monitors) > 0);
|
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
|
|
|
|
2023-07-17 16:19:14 +02:00
|
|
|
const struct wl_window *win = term->window;
|
|
|
|
|
const struct monitor *mon = tll_length(win->on_outputs) > 0
|
|
|
|
|
? tll_back(win->on_outputs)
|
|
|
|
|
: &tll_front(term->wl->monitors);
|
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
|
|
|
|
2023-07-25 15:56:30 +02:00
|
|
|
if (term_fractional_scaling(term))
|
2023-10-01 09:20:13 +02:00
|
|
|
return mon != NULL ? mon->dpi.physical : 96.;
|
2023-07-17 16:19:14 +02:00
|
|
|
else
|
2023-10-01 09:20:13 +02:00
|
|
|
return mon != NULL ? mon->dpi.scaled : 96.;
|
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-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
|
2023-07-17 16:28:10 +02:00
|
|
|
* much but select *an* output. So, we pick the one we were most
|
|
|
|
|
* recently mapped on.
|
2020-04-20 18:37:59 +02:00
|
|
|
*
|
|
|
|
|
* 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)
|
2023-07-17 16:28:10 +02:00
|
|
|
wl_subpixel = tll_back(term->window->on_outputs)->subpixel;
|
2020-04-20 18:37:59 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
pt-or-px: heed the dpi-aware setting
Before this patch, pt-or-px values, like letter-spacing, were *always*
scaled using the current DPI value.
This is wrong; if the fonts are scaled using the output’s scaling
factor, then so should all other point values.
This also fixes an issue where e.g. letter-spacing would use one DPI
value at startup, but then when increasing/decreasing or resetting the
font size, would be re-calculated using a different DPI value, leading
to completely different spacing.
This happened when there were multiple monitors, with different DPI
values, and foot guessed the initial DPI value wrong. Normally, foot
would correct itself as soon as the window was mapped, and the
“correct” DPI value known. But if the fonts were scaled using the
scaling factor, it was possible that the font reload never happened.
This patch also updates the thickness calculation (for LIGHT and HEAVY
box drawing characters) to use the scaling factor when appropriate.
Closes #680
2021-08-13 17:38:56 +02:00
|
|
|
int
|
|
|
|
|
term_pt_or_px_as_pixels(const struct terminal *term,
|
|
|
|
|
const struct pt_or_px *pt_or_px)
|
|
|
|
|
{
|
2023-07-25 15:56:30 +02:00
|
|
|
float scale = !term->font_is_sized_by_dpi ? term->scale : 1.;
|
|
|
|
|
float dpi = term->font_is_sized_by_dpi ? term->font_dpi : 96.;
|
pt-or-px: heed the dpi-aware setting
Before this patch, pt-or-px values, like letter-spacing, were *always*
scaled using the current DPI value.
This is wrong; if the fonts are scaled using the output’s scaling
factor, then so should all other point values.
This also fixes an issue where e.g. letter-spacing would use one DPI
value at startup, but then when increasing/decreasing or resetting the
font size, would be re-calculated using a different DPI value, leading
to completely different spacing.
This happened when there were multiple monitors, with different DPI
values, and foot guessed the initial DPI value wrong. Normally, foot
would correct itself as soon as the window was mapped, and the
“correct” DPI value known. But if the fonts were scaled using the
scaling factor, it was possible that the font reload never happened.
This patch also updates the thickness calculation (for LIGHT and HEAVY
box drawing characters) to use the scaling factor when appropriate.
Closes #680
2021-08-13 17:38:56 +02:00
|
|
|
|
|
|
|
|
return pt_or_px->px == 0
|
2023-07-25 15:56:30 +02:00
|
|
|
? (int)roundf(pt_or_px->pt * scale * dpi / 72)
|
|
|
|
|
: (int)roundf(pt_or_px->px * scale);
|
pt-or-px: heed the dpi-aware setting
Before this patch, pt-or-px values, like letter-spacing, were *always*
scaled using the current DPI value.
This is wrong; if the fonts are scaled using the output’s scaling
factor, then so should all other point values.
This also fixes an issue where e.g. letter-spacing would use one DPI
value at startup, but then when increasing/decreasing or resetting the
font size, would be re-calculated using a different DPI value, leading
to completely different spacing.
This happened when there were multiple monitors, with different DPI
values, and foot guessed the initial DPI value wrong. Normally, foot
would correct itself as soon as the window was mapped, and the
“correct” DPI value known. But if the fonts were scaled using the
scaling factor, it was possible that the font reload never happened.
This patch also updates the thickness calculation (for LIGHT and HEAVY
box drawing characters) to use the scaling factor when appropriate.
Closes #680
2021-08-13 17:38:56 +02:00
|
|
|
}
|
2020-12-17 12:05:22 +01: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
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
reload_fonts(struct terminal *term, bool resize_grid)
|
2019-10-30 17:50:12 +01:00
|
|
|
{
|
2021-06-17 18:15:29 +02:00
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
|
2020-10-20 21:04:47 +02:00
|
|
|
const size_t counts[4] = {
|
2021-06-17 18:15:29 +02:00
|
|
|
conf->fonts[0].count,
|
|
|
|
|
conf->fonts[1].count,
|
|
|
|
|
conf->fonts[2].count,
|
|
|
|
|
conf->fonts[3].count,
|
2020-10-20 21:04:47 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Configure size (which may have been changed run-time) */
|
|
|
|
|
char **names[4];
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
names[i] = xmalloc(counts[i] * sizeof(names[i][0]));
|
2019-10-30 17:50:12 +01:00
|
|
|
|
2021-06-17 18:15:29 +02:00
|
|
|
const struct config_font_list *font_list = &conf->fonts[i];
|
|
|
|
|
|
|
|
|
|
for (size_t j = 0; j < font_list->count; j++) {
|
|
|
|
|
const struct config_font *font = &font_list->arr[j];
|
2020-10-20 21:04:47 +02:00
|
|
|
bool use_px_size = term->font_sizes[i][j].px_size > 0;
|
|
|
|
|
char size[64];
|
2019-10-30 17:50:12 +01:00
|
|
|
|
2023-06-22 14:39:34 +02:00
|
|
|
const float scale = term->font_is_sized_by_dpi ? 1. : term->scale;
|
2020-11-17 17:59:31 +01:00
|
|
|
|
2020-10-20 21:04:47 +02:00
|
|
|
if (use_px_size)
|
2020-11-17 17:59:31 +01:00
|
|
|
snprintf(size, sizeof(size), ":pixelsize=%d",
|
2023-07-25 15:56:30 +02:00
|
|
|
(int)roundf(term->font_sizes[i][j].px_size * scale));
|
2020-10-20 21:04:47 +02:00
|
|
|
else
|
2020-11-17 17:59:31 +01:00
|
|
|
snprintf(size, sizeof(size), ":size=%.2f",
|
2023-06-22 14:39:34 +02:00
|
|
|
term->font_sizes[i][j].pt_size * scale);
|
2020-07-07 10:44:55 +02:00
|
|
|
|
2024-01-23 20:33:46 +00:00
|
|
|
names[i][j] = xstrjoin(font->pattern, size);
|
2020-10-20 21:04:47 +02:00
|
|
|
}
|
2020-07-07 10:44:55 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-20 21:04:47 +02:00
|
|
|
/* Did user configure custom bold/italic fonts?
|
|
|
|
|
* Or should we use the regular font, with weight/slant attributes? */
|
|
|
|
|
const bool custom_bold = counts[1] > 0;
|
|
|
|
|
const bool custom_italic = counts[2] > 0;
|
|
|
|
|
const bool custom_bold_italic = counts[3] > 0;
|
|
|
|
|
|
|
|
|
|
const size_t count_regular = counts[0];
|
|
|
|
|
const char **names_regular = (const char **)names[0];
|
|
|
|
|
|
|
|
|
|
const size_t count_bold = custom_bold ? counts[1] : counts[0];
|
|
|
|
|
const char **names_bold = (const char **)(custom_bold ? names[1] : names[0]);
|
|
|
|
|
|
|
|
|
|
const size_t count_italic = custom_italic ? counts[2] : counts[0];
|
2020-12-17 12:05:22 +01:00
|
|
|
const char **names_italic = (const char **)(custom_italic ? names[2] : names[0]);
|
2020-10-20 21:04:47 +02:00
|
|
|
|
|
|
|
|
const size_t count_bold_italic = custom_bold_italic ? counts[3] : counts[0];
|
|
|
|
|
const char **names_bold_italic = (const char **)(custom_bold_italic ? names[3] : names[0]);
|
|
|
|
|
|
2021-09-19 11:58:34 +02:00
|
|
|
const bool use_dpi = term->font_is_sized_by_dpi;
|
2024-01-23 20:33:46 +00:00
|
|
|
char *dpi = xasprintf("dpi=%.2f", use_dpi ? term->font_dpi : 96.);
|
2020-11-17 17:59:31 +01:00
|
|
|
|
2024-01-23 20:33:46 +00:00
|
|
|
char *attrs[4] = {
|
|
|
|
|
[0] = dpi, /* Takes ownership */
|
|
|
|
|
[1] = xstrjoin(dpi, !custom_bold ? ":weight=bold" : ""),
|
|
|
|
|
[2] = xstrjoin(dpi, !custom_italic ? ":slant=italic" : ""),
|
|
|
|
|
[3] = xstrjoin(dpi, !custom_bold_italic ? ":weight=bold:slant=italic" : ""),
|
|
|
|
|
};
|
2019-12-04 22:02:02 +01:00
|
|
|
|
2020-07-07 10:44:55 +02:00
|
|
|
struct fcft_font *fonts[4];
|
2020-04-28 22:07:02 +02:00
|
|
|
struct font_load_data data[4] = {
|
2020-10-20 21:04:47 +02:00
|
|
|
{count_regular, names_regular, attrs[0], &fonts[0]},
|
|
|
|
|
{count_bold, names_bold, attrs[1], &fonts[1]},
|
|
|
|
|
{count_italic, names_italic, attrs[2], &fonts[2]},
|
|
|
|
|
{count_bold_italic, names_bold_italic, attrs[3], &fonts[3]},
|
2020-04-28 22:07:02 +02:00
|
|
|
};
|
|
|
|
|
|
2020-08-23 07:42:20 +02:00
|
|
|
thrd_t tids[4] = {0};
|
2020-05-03 12:25:04 +02:00
|
|
|
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-10-20 21:04:47 +02:00
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
for (size_t j = 0; j < counts[i]; j++)
|
|
|
|
|
free(names[i][j]);
|
|
|
|
|
free(names[i]);
|
|
|
|
|
free(attrs[i]);
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
return success ? term_set_fonts(term, fonts, resize_grid) : success;
|
2020-07-07 10:44:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
load_fonts_from_conf(struct terminal *term)
|
|
|
|
|
{
|
2021-06-17 18:15:29 +02:00
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
|
2020-10-20 21:04:47 +02:00
|
|
|
for (size_t i = 0; i < 4; i++) {
|
2021-06-17 18:15:29 +02:00
|
|
|
const struct config_font_list *font_list = &conf->fonts[i];
|
|
|
|
|
|
2021-06-18 15:56:34 +02:00
|
|
|
for (size_t j = 0; j < font_list->count; j++) {
|
2021-06-17 18:15:29 +02:00
|
|
|
const struct config_font *font = &font_list->arr[j];
|
2021-06-18 15:56:34 +02:00
|
|
|
term->font_sizes[i][j] = (struct config_font){
|
2021-06-17 18:15:29 +02:00
|
|
|
.pt_size = font->pt_size, .px_size = font->px_size};
|
2020-10-20 21:04:47 +02:00
|
|
|
}
|
2020-07-07 10:44:55 +02:00
|
|
|
}
|
|
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
return reload_fonts(term, true);
|
2019-10-30 17:50:12 +01:00
|
|
|
}
|
|
|
|
|
|
2021-07-31 18:27:02 +02:00
|
|
|
static void fdm_client_terminated(
|
|
|
|
|
struct reaper *reaper, pid_t pid, int status, void *data);
|
2020-12-26 01:29:40 +01:00
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
struct terminal *
|
2020-05-21 20:17:29 +02:00
|
|
|
term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
|
|
|
|
|
struct wayland *wayl, const char *foot_exe, const char *cwd,
|
slave: don't skip setting environment variables when using a custom environment
When launching footclient with -E,--client-environment the environment
variables that should be set by foot, wasn't.
Those variables are:
* TERM
* COLORTERM
* PWD
* SHELL
and all variables defined by the user in the [environment] section in
foot.ini.
In the same way, we did not *unset* TERM_PROGRAM and
TERM_PROGRAM_VERSION.
This patch fixes it by "cloning" the custom environment, making it
mutable, and then adding/removing the variables above from it.
Instead of calling setenv()/unsetenv() directly, we add the wrapper
functions add_to_env() and del_from_env().
When *not* using a custom environment, they simply call
setenv()/unsetenv().
When we *are* using a custom environment, add_to_env() first loops all
existing variables, looking for a match. If a match is found, it's
updated with the new value. If it's not found, a new entry is added.
del_from_env() loops all entries, and removes it when a match is
found. If no match is found, nothing is done.
The mutable environment is allocated on the heap, but never free:d. We
don't need to free it, since it's only allocated after forking, in the
child process.
Closes #1568
2024-01-09 16:50:47 +01:00
|
|
|
const char *token, int argc, char *const *argv, const char *const *envp,
|
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 delay_lower_fd = -1;
|
|
|
|
|
int delay_upper_fd = -1;
|
2020-01-12 12:55:19 +01:00
|
|
|
int app_sync_updates_fd = -1;
|
2021-06-18 15:53:47 +02:00
|
|
|
int title_update_fd = -1;
|
2019-10-28 18:46:03 +01:00
|
|
|
|
2019-10-30 20:22:01 +01:00
|
|
|
struct terminal *term = malloc(sizeof(*term));
|
2020-08-08 20:34:30 +01:00
|
|
|
if (unlikely(term == NULL)) {
|
|
|
|
|
LOG_ERRNO("malloc() failed");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2019-10-28 18:46:03 +01:00
|
|
|
|
2021-06-18 15:53:47 +02:00
|
|
|
if ((ptmx = posix_openpt(O_RDWR | O_NOCTTY)) < 0) {
|
2019-10-28 18:46:03 +01:00
|
|
|
LOG_ERRNO("failed to open PTY");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
2021-06-18 15:53:47 +02:00
|
|
|
if ((flash_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0) {
|
2019-10-28 18:46:03 +01:00
|
|
|
LOG_ERRNO("failed to create flash timer FD");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
2021-06-18 15:53:47 +02:00
|
|
|
if ((delay_lower_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0 ||
|
|
|
|
|
(delay_upper_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0)
|
2019-10-28 18:46:03 +01:00
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to create delayed rendering timer FDs");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 15:53:47 +02:00
|
|
|
if ((app_sync_updates_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0)
|
2020-01-12 12:40:42 +01:00
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to create application synchronized updates timer FD");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 15:53:47 +02:00
|
|
|
if ((title_update_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK)) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to create title update throttle timer FD");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 18:06:04 +01:00
|
|
|
if (ioctl(ptmx, (unsigned int)TIOCSWINSZ,
|
2020-06-02 19:59:28 +02:00
|
|
|
&(struct winsize){.ws_row = 24, .ws_col = 80}) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to set initial TIOCSWINSZ");
|
|
|
|
|
goto close_fds;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Need to register *very* early (before the first "goto err"), to
|
|
|
|
|
* ensure term_destroy() doesn't unref a key-binding we haven't
|
terminal: don’t unref a not-yet-referenced key-binding set
Key-binding sets are bound to a seat/configuration pair. The conf
reference is done when a new terminal instance is created.
When that same terminal instance is destroyed, the key binding set is
unref:ed.
If the terminal instance is destroyed *before* the key binding set has
been referenced, we’ll still unref it. This creates an imbalance.
In particular, when the there is exactly one other terminal instance
referencing that same key binding set, that terminal instance will
trigger a foot server crash as soon as it receives a key press/release
event. This happens because the next-to-last terminal instance brought
the reference count of the binding set down to 0, causing it to be
free:d.
Thus, we *must* reference the binding set *before* we can error
out (when instantiating a new terminal instance).
At this point, we don’t yet have a valid terminal instance. But,
that’s ok, because all the key_binding_new_for_term() did with the
terminal instance was get the "struct wayland" and "struct config"
pointers. So, rename the function and simply pass these pointers
explicitly.
Similarly, change key_binding_for() to take a "struct config" pointer,
rather than a "struct terminal" pointer.
Also rename key_binding_unref_term() -> key_binding_unref().
2022-09-05 19:23:40 +02:00
|
|
|
* yet ref:d */
|
|
|
|
|
key_binding_new_for_conf(wayl->key_binding_manager, wayl, conf);
|
|
|
|
|
|
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, 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) ||
|
2021-06-18 15:53:47 +02:00
|
|
|
!fdm_add(fdm, app_sync_updates_fd, EPOLLIN, &fdm_app_sync_updates_timeout, term) ||
|
|
|
|
|
!fdm_add(fdm, title_update_fd, EPOLLIN, &fdm_title_update_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-05-21 20:17:29 +02:00
|
|
|
.reaper = reaper,
|
2020-02-08 14:09:06 +01:00
|
|
|
.conf = conf,
|
2019-10-28 18:46:03 +01:00
|
|
|
.ptmx = ptmx,
|
2020-08-22 09:14:18 +02:00
|
|
|
.ptmx_buffers = tll_init(),
|
|
|
|
|
.ptmx_paste_buffers = tll_init(),
|
2020-10-20 21:04:47 +02:00
|
|
|
.font_sizes = {
|
2021-06-17 18:15:29 +02:00
|
|
|
xmalloc(sizeof(term->font_sizes[0][0]) * conf->fonts[0].count),
|
|
|
|
|
xmalloc(sizeof(term->font_sizes[1][0]) * conf->fonts[1].count),
|
|
|
|
|
xmalloc(sizeof(term->font_sizes[2][0]) * conf->fonts[2].count),
|
|
|
|
|
xmalloc(sizeof(term->font_sizes[3][0]) * conf->fonts[3].count),
|
2020-10-20 21:04:47 +02:00
|
|
|
},
|
2020-07-26 07:45:03 +02:00
|
|
|
.font_dpi = 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,
|
2020-10-06 18:42:26 +02:00
|
|
|
.reverse_wrap = true,
|
2019-10-28 18:25:19 +01:00
|
|
|
.auto_margin = true,
|
|
|
|
|
.window_title_stack = tll_init(),
|
2023-06-22 14:39:34 +02:00
|
|
|
.scale = 1.,
|
term: improve fallback logic when selecting scaling factor while unmapped
The foot window may, for various reasons, become completely
unmapped (that is, being removed from all outputs) at run time.
One example is wlroots based compositors; they unmap all other windows
when an opaque window is fullscreened.
21d99f8dced335826964ca96b8ba7ccac059e598 introduced a regression,
where instead of picking the scaling factor from one of the available
outputs (at random), we started falling back to '1' as soon as we were
unmapped.
This patch restores the original logic, but also improves upon it.
As soon as a scaling factor has been assigned to the window, we store
a copy of it in the term struct ('scale_before_unmap').
When unmapped, we check if it has a valid value (the only time it
doesn't is before the initial map). If so, we use it.
Only if it hasn't been set do we fall back to picking an output at
random, and using its scaling factor.
Closes #1464
2023-08-18 16:39:00 +02:00
|
|
|
.scale_before_unmap = -1,
|
2019-10-28 18:46:03 +01:00
|
|
|
.flash = {.fd = flash_fd},
|
2020-10-13 18:40:20 +02:00
|
|
|
.blink = {.fd = -1},
|
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
|
|
|
.alpha = conf->colors.alpha,
|
2021-04-07 08:09:40 +02:00
|
|
|
.selection_fg = conf->colors.selection_fg,
|
|
|
|
|
.selection_bg = conf->colors.selection_bg,
|
|
|
|
|
.use_custom_selection = conf->colors.use_custom.selection,
|
2019-10-28 18:25:19 +01:00
|
|
|
},
|
2019-11-05 13:27:37 +01:00
|
|
|
.origin = ORIGIN_ABSOLUTE,
|
2019-10-28 18:25:19 +01:00
|
|
|
.cursor_style = conf->cursor.style,
|
2019-12-15 15:07:56 +01:00
|
|
|
.cursor_blink = {
|
2020-11-26 18:08:28 +01:00
|
|
|
.decset = false,
|
|
|
|
|
.deccsusr = conf->cursor.blink,
|
2019-12-15 15:07:56 +01:00
|
|
|
.state = CURSOR_BLINK_ON,
|
2020-10-13 19:23:04 +02:00
|
|
|
.fd = -1,
|
2019-12-15 15:07:56 +01:00
|
|
|
},
|
2019-10-28 18:25:19 +01:00
|
|
|
.cursor_color = {
|
|
|
|
|
.text = conf->cursor.color.text,
|
|
|
|
|
.cursor = conf->cursor.color.cursor,
|
|
|
|
|
},
|
|
|
|
|
.selection = {
|
2022-04-09 15:09:02 +02:00
|
|
|
.coords = {
|
|
|
|
|
.start = {-1, -1},
|
|
|
|
|
.end = {-1, -1},
|
|
|
|
|
},
|
2022-02-07 10:36:59 +01:00
|
|
|
.pivot = {
|
|
|
|
|
.start = {-1, -1},
|
|
|
|
|
.end = {-1, -1},
|
|
|
|
|
},
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
.auto_scroll = {
|
|
|
|
|
.fd = -1,
|
|
|
|
|
},
|
2019-10-28 18:25:19 +01:00
|
|
|
},
|
2020-08-04 18:07:22 +02:00
|
|
|
.normal = {.scroll_damage = tll_init(), .sixel_images = tll_init()},
|
|
|
|
|
.alt = {.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 = NULL,
|
2020-09-15 19:09:00 +02:00
|
|
|
.alt_scrolling = conf->mouse.alternate_scroll_mode,
|
2020-01-20 18:38:50 +01:00
|
|
|
.meta = {
|
|
|
|
|
.esc_prefix = true,
|
|
|
|
|
.eight_bit = true,
|
|
|
|
|
},
|
2020-11-11 18:26:47 +01:00
|
|
|
.num_lock_modifier = true,
|
2020-12-10 18:22:48 +01:00
|
|
|
.bell_action_enabled = true,
|
2019-11-16 10:54:21 +01:00
|
|
|
.tab_stops = tll_init(),
|
2019-10-28 18:25:19 +01:00
|
|
|
.wl = wayl,
|
|
|
|
|
.render = {
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
.chains = {
|
|
|
|
|
.grid = shm_chain_new(wayl->shm, true, 1 + conf->render_worker_count),
|
|
|
|
|
.search = shm_chain_new(wayl->shm, false, 1),
|
|
|
|
|
.scrollback_indicator = shm_chain_new(wayl->shm, false, 1),
|
|
|
|
|
.render_timer = shm_chain_new(wayl->shm, false, 1),
|
|
|
|
|
.url = shm_chain_new(wayl->shm, false, 1),
|
2021-07-18 16:46:43 +02:00
|
|
|
.csd = shm_chain_new(wayl->shm, false, 1),
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
Search mode and ‘flash’ (OSC-555) both achieves similar visual
effects: flash tints the entire window yellow, and search mode dims
it (except the search match).
But, they do so in completely different ways. Search mode is detected
in render_cell(), and the colors are then dimmed there.
Flash is implemented by blending a yellow, semi-transparent color on
top of the rendered grid.
This patch replaces those two implementations with a single one. We
add a new sub-surface, called the ‘overlay’. In normal mode, it’s
unmapped.
When either search mode, or flash, is enabled, we enable it, and
fill it with a semi-transparent color. Yellow for ‘flash’, and
“black” (i.e. no color) for search mode.
The compositor then blends it with the grid. Hopefully on the GPU,
meaning it’ll be faster than if we blend in software.
There are more performance benefits however. By using a separate
surface, we can do much better damage tracking.
The normal grid rendering code no longer have to care about neither
search mode, nor flash. Thus, we get rid of a couple of ‘if’
statements in render_cell(), which is nice. But more importantly, we
can drop full grid repaints in a couple of circumstances:
* Entering/exiting search mode
* Every frame while flash is active
Now, when rendering the search mode overlay, we do want to do some
damage tracking, also of the overlay.
This, since search mode doesn’t dim the *entire* window. The search
match is *not* dimmed. This is implemented by punching a hole in the
overlay sub-surface. That is, we make part of it *fully*
transparent. The basic idea is to set a clip region that excludes the
search match, and then dim the rest of the overlay.
It’s slightly more complicated than that however, if we want to reuse
the last frame’s overlay buffer (i.e we don’t want to re-render
the *entire* overlay every frame).
In short, we need to:
* Clear (punch hole) in areas that are part of this frame’s search
match, but not the last frame’s (since those parts are _already_
cleared).
* Dim the areas that were part of the last frame’s search match, but
aren’t anymore (the rest of the overlay should already be dimmed).
To do this, we save the last frame’s “holes” (as a pixman
region). Then, when rendering the next frame, we first calculate the
new frame’s “holes” region.
The region to clear is “this frame’s holes minus last frame’s holes”
The region to dim is “last frame’s holes minus this frames holes”.
Finally, we compute the bounding box of all modified cells by taking
the union of the two diff regions mentioned above. This allows us to
limit the buffer damage sent to the compositor.
2022-04-16 17:49:46 +02:00
|
|
|
.overlay = shm_chain_new(wayl->shm, false, 1),
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
},
|
2020-07-25 14:31:45 +02:00
|
|
|
.scrollback_lines = conf->scrollback.lines,
|
2020-01-12 12:55:19 +01:00
|
|
|
.app_sync_updates.timer_fd = app_sync_updates_fd,
|
render: use a timer instead of relying on the frame callback for title update throttling
Using the frame callback works most of the time, but e.g. Sway doesn’t
call it while the window is hidden, and thus prevents us from updating
the title in e.g. stacked views.
This patch uses a timer FD instead. We store a timestamp from when the
title was last updated. When the application wants to update the
title, we first check if we already have a timer running, and if so,
does nothing.
If no timer is running, check the timestamp. If enough time has
passed, update the title immediately.
If not, instantiate a timer and wait for it to trigger.
Set the minimum time between two updates to ~8ms (twice per frame, for
a 60Hz output, and ~once per frame on a 120Hz output).
Closes #591
2021-06-15 17:27:50 +02:00
|
|
|
.title = {
|
2021-06-18 15:53:47 +02:00
|
|
|
.timer_fd = title_update_fd,
|
render: use a timer instead of relying on the frame callback for title update throttling
Using the frame callback works most of the time, but e.g. Sway doesn’t
call it while the window is hidden, and thus prevents us from updating
the title in e.g. stacked views.
This patch uses a timer FD instead. We store a timestamp from when the
title was last updated. When the application wants to update the
title, we first check if we already have a timer running, and if so,
does nothing.
If no timer is running, check the timestamp. If enough time has
passed, update the title immediately.
If not, instantiate a timer and wait for it to trigger.
Set the minimum time between two updates to ~8ms (twice per frame, for
a 60Hz output, and ~once per frame on a 120Hz output).
Closes #591
2021-06-15 17:27:50 +02:00
|
|
|
},
|
2019-10-28 18:25:19 +01:00
|
|
|
.workers = {
|
|
|
|
|
.count = conf->render_worker_count,
|
|
|
|
|
.queue = tll_init(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
.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 = {
|
sixel: implement private mode 80 - sixel scrolling
When enabled (the default), sixels behave much like normal output; the
start where the cursor is, and the cursor moves with the
sixel. I.e. after emitting a sixel the cursor is left after the image;
either to the right, if private mode 8452 is enabled, or otherwise on
the next line. Terminal content is scrolled up if the sixel is larger
than the screen.
When disabled, sixels *always* start at (0,0), the cursor never moves,
and the terminal content never scrolls.
In other words, the ‘disabled’ mode is a much simpler mode.
All we need to do to support both modes is re-write the sixel-emitting
loop to:
* break early if we’re “out of rows”, i.e. we’ve reached the bottom of
the screen.
* not linefeed, or move the cursor when scrolling is disabled
This patch also fixes a bug in the (new) implementation of private
mode 8452.
When emitting a sixel, we may break it up into smaller pieces, to
ensure a single sixel (as tracked internally) does not cross the
scrollback wrap-around.
The code that checked if we should do a linefeed or not, would skip
the linefeed on the last row of *each* such sixel piece. The correct
thing to do is to skip it only on the last row of the *last* piece.
I chose not to fix this bug in a separate patch since doing so would
have meant re-writing it again when implementing private mode 80.
2021-02-26 09:28:03 +01:00
|
|
|
.scrolling = true,
|
2021-02-16 19:37:49 +01:00
|
|
|
.use_private_palette = true,
|
2020-02-22 14:02:00 +01:00
|
|
|
.palette_size = SIXEL_MAX_COLORS,
|
2020-11-23 20:10:55 +01:00
|
|
|
.max_width = SIXEL_MAX_WIDTH,
|
|
|
|
|
.max_height = SIXEL_MAX_HEIGHT,
|
2020-02-22 14:02:00 +01:00
|
|
|
},
|
2021-07-31 19:08:51 +02:00
|
|
|
.shutdown = {
|
|
|
|
|
.terminate_timeout_fd = -1,
|
|
|
|
|
.cb = shutdown_cb,
|
|
|
|
|
.cb_data = shutdown_data,
|
|
|
|
|
},
|
2020-08-08 20:34:30 +01:00
|
|
|
.foot_exe = xstrdup(foot_exe),
|
|
|
|
|
.cwd = xstrdup(cwd),
|
2023-09-20 13:45:06 +02:00
|
|
|
.grapheme_shaping = conf->tweak.grapheme_shaping,
|
2020-12-04 18:39:11 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
.ime_enabled = true,
|
2020-12-04 18:39:11 +01:00
|
|
|
#endif
|
2019-10-28 18:25:19 +01:00
|
|
|
};
|
|
|
|
|
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
Search mode and ‘flash’ (OSC-555) both achieves similar visual
effects: flash tints the entire window yellow, and search mode dims
it (except the search match).
But, they do so in completely different ways. Search mode is detected
in render_cell(), and the colors are then dimmed there.
Flash is implemented by blending a yellow, semi-transparent color on
top of the rendered grid.
This patch replaces those two implementations with a single one. We
add a new sub-surface, called the ‘overlay’. In normal mode, it’s
unmapped.
When either search mode, or flash, is enabled, we enable it, and
fill it with a semi-transparent color. Yellow for ‘flash’, and
“black” (i.e. no color) for search mode.
The compositor then blends it with the grid. Hopefully on the GPU,
meaning it’ll be faster than if we blend in software.
There are more performance benefits however. By using a separate
surface, we can do much better damage tracking.
The normal grid rendering code no longer have to care about neither
search mode, nor flash. Thus, we get rid of a couple of ‘if’
statements in render_cell(), which is nice. But more importantly, we
can drop full grid repaints in a couple of circumstances:
* Entering/exiting search mode
* Every frame while flash is active
Now, when rendering the search mode overlay, we do want to do some
damage tracking, also of the overlay.
This, since search mode doesn’t dim the *entire* window. The search
match is *not* dimmed. This is implemented by punching a hole in the
overlay sub-surface. That is, we make part of it *fully*
transparent. The basic idea is to set a clip region that excludes the
search match, and then dim the rest of the overlay.
It’s slightly more complicated than that however, if we want to reuse
the last frame’s overlay buffer (i.e we don’t want to re-render
the *entire* overlay every frame).
In short, we need to:
* Clear (punch hole) in areas that are part of this frame’s search
match, but not the last frame’s (since those parts are _already_
cleared).
* Dim the areas that were part of the last frame’s search match, but
aren’t anymore (the rest of the overlay should already be dimmed).
To do this, we save the last frame’s “holes” (as a pixman
region). Then, when rendering the next frame, we first calculate the
new frame’s “holes” region.
The region to clear is “this frame’s holes minus last frame’s holes”
The region to dim is “last frame’s holes minus this frames holes”.
Finally, we compute the bounding box of all modified cells by taking
the union of the two diff regions mentioned above. This allows us to
limit the buffer damage sent to the compositor.
2022-04-16 17:49:46 +02:00
|
|
|
pixman_region32_init(&term->render.last_overlay_clip);
|
|
|
|
|
|
|
|
|
|
term_update_ascii_printer(term);
|
2021-03-14 19:19:10 +01:00
|
|
|
|
2020-10-20 21:04:47 +02:00
|
|
|
for (size_t i = 0; i < 4; i++) {
|
2021-06-17 18:15:29 +02:00
|
|
|
const struct config_font_list *font_list = &conf->fonts[i];
|
|
|
|
|
for (size_t j = 0; j < font_list->count; j++) {
|
|
|
|
|
const struct config_font *font = &font_list->arr[j];
|
2021-06-18 15:56:34 +02:00
|
|
|
term->font_sizes[i][j] = (struct config_font){
|
2021-06-17 18:15:29 +02:00
|
|
|
.pt_size = font->pt_size, .px_size = font->px_size};
|
2020-07-07 10:44:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-23 20:24:04 +02:00
|
|
|
add_utmp_record(conf, reaper, ptmx);
|
|
|
|
|
|
2020-04-30 11:39:41 +02:00
|
|
|
/* Start the slave/client */
|
|
|
|
|
if ((term->slave = slave_spawn(
|
2022-05-28 19:27:29 +02:00
|
|
|
term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars,
|
2020-07-29 19:42:12 +02:00
|
|
|
conf->term, conf->shell, conf->login_shell,
|
2020-07-30 18:57:21 +02:00
|
|
|
&conf->notifications)) == -1)
|
2020-04-30 11:39:41 +02:00
|
|
|
{
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-31 18:27:02 +02:00
|
|
|
reaper_add(term->reaper, term->slave, &fdm_client_terminated, term);
|
2020-12-26 01:29:40 +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
|
|
|
/* Guess scale; we're not mapped yet, so we don't know on which
|
2023-07-17 16:19:14 +02:00
|
|
|
* output we'll be. Use scaling factor from first monitor */
|
|
|
|
|
xassert(tll_length(term->wl->monitors) > 0);
|
|
|
|
|
term->scale = tll_front(term->wl->monitors).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
|
|
|
|
2021-11-03 14:25:38 +01:00
|
|
|
memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table));
|
2020-04-30 11:39:41 +02:00
|
|
|
|
2020-01-03 11:15:35 +01:00
|
|
|
/* Initialize the Wayland window backend */
|
2021-10-28 17:51:44 -07:00
|
|
|
if ((term->window = wayl_win_init(term, token)) == 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 */
|
2023-07-16 08:28:21 +02:00
|
|
|
if (!term_font_dpi_changed(term, 0.))
|
2020-02-28 18:35:05 +01:00
|
|
|
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:
|
2021-07-31 19:08:51 +02:00
|
|
|
term->shutdown.in_progress = 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, delay_lower_fd);
|
|
|
|
|
fdm_del(fdm, delay_upper_fd);
|
2020-01-12 12:55:19 +01:00
|
|
|
fdm_del(fdm, app_sync_updates_fd);
|
2021-06-18 15:53:47 +02:00
|
|
|
fdm_del(fdm, title_update_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 */
|
2021-07-31 19:08:51 +02:00
|
|
|
if (!term->shutdown.in_progress) {
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->window->is_configured);
|
2020-05-04 20:49:28 +02:00
|
|
|
fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term);
|
|
|
|
|
}
|
2020-04-30 17:22:57 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-31 18:18:48 +02:00
|
|
|
/*
|
|
|
|
|
* Shutdown logic
|
|
|
|
|
*
|
|
|
|
|
* A foot instance can be terminated in two ways:
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* - the client application terminates (user types 'exit', or pressed C-d in the
|
2021-07-31 18:18:48 +02:00
|
|
|
* shell, etc)
|
|
|
|
|
* - the foot window is closed
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Both variants need to trigger to "other" action. I.e. if the client
|
2021-07-31 18:18:48 +02:00
|
|
|
* application is terminated, then we need to close the window. If the window is
|
|
|
|
|
* closed, we need to terminate the client application.
|
|
|
|
|
*
|
|
|
|
|
* Only when *both* tasks have completed do we consider ourselves fully
|
|
|
|
|
* shutdown. This is when we can call term_destroy(), and the user provided
|
|
|
|
|
* shutdown callback.
|
|
|
|
|
*
|
|
|
|
|
* The functions involved with this are:
|
|
|
|
|
*
|
|
|
|
|
* - shutdown_maybe_done(): called after any of the two tasks above have
|
|
|
|
|
* completed. When it determines that *both* tasks are done, it calls
|
|
|
|
|
* term_destroy() and the user provided shutdown callback.
|
|
|
|
|
*
|
2021-07-31 18:27:02 +02:00
|
|
|
* - fdm_client_terminated(): reaper callback, called when the client
|
|
|
|
|
* application has terminated.
|
2021-07-31 18:18:48 +02:00
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* + Kills the "terminate" timeout timer
|
2021-07-31 18:18:48 +02:00
|
|
|
* + Calls shutdown_maybe_done() if the shutdown procedure has already
|
|
|
|
|
* started (i.e. the window being closed initiated the shutdown)
|
|
|
|
|
* -OR-
|
|
|
|
|
* Initiates the shutdown itself, by calling term_shutdown() (client
|
|
|
|
|
* application termination initiated the shutdown).
|
|
|
|
|
*
|
|
|
|
|
* - term_shutdown(): unregisters all FDM callbacks, sends SIGTERM to the client
|
2024-02-06 12:36:45 +01:00
|
|
|
* application and installs a "terminate" timeout timer (if it hasn't already
|
2021-07-31 18:18:48 +02:00
|
|
|
* terminated). Finally registers an event FD with the FDM, which is
|
|
|
|
|
* immediately triggered. This is done to ensure any pending FDM events are
|
|
|
|
|
* handled before shutting down.
|
|
|
|
|
*
|
|
|
|
|
* - fdm_shutdown(): FDM callback, triggered by the event FD in
|
|
|
|
|
* term_shutdown(). Unmaps and destroys the window resources, and ensures the
|
2024-02-06 12:36:45 +01:00
|
|
|
* seats' focused pointers don't reference us. Finally calls
|
2021-07-31 18:18:48 +02:00
|
|
|
* shutdown_maybe_done().
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* - fdm_terminate_timeout(): FDM callback for the "terminate" timeout
|
|
|
|
|
* timer. This function is called when the client application hasn't
|
2021-07-31 18:18:48 +02:00
|
|
|
* terminated after 60 seconds (after the SIGTERM). Sends SIGKILL to the
|
|
|
|
|
* client application.
|
|
|
|
|
*
|
|
|
|
|
* - term_destroy(): normally called from shutdown_maybe_done(), when both the
|
|
|
|
|
* window has been unmapped, and the client application has terminated. In
|
|
|
|
|
* this case, it simply destroys all resources.
|
|
|
|
|
*
|
|
|
|
|
* It may however also be called without term_shutdown() having been called
|
|
|
|
|
* (typically in error code paths - for example, when the Wayland connection
|
|
|
|
|
* is closed by the compositor). In this case, the client application is
|
2024-02-06 12:36:45 +01:00
|
|
|
* typically still running, and we can't assume the FDM is running. To handle
|
2021-07-31 18:18:48 +02:00
|
|
|
* this, we install configure a 60 second SIGALRM, send SIGTERM to the client
|
|
|
|
|
* application, and then enter a blocking waitpid().
|
|
|
|
|
*
|
|
|
|
|
* If the alarm triggers, we send SIGKILL and once again enter a blocking
|
|
|
|
|
* waitpid().
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
shutdown_maybe_done(struct terminal *term)
|
|
|
|
|
{
|
2021-07-31 19:08:51 +02:00
|
|
|
bool shutdown_done =
|
|
|
|
|
term->window == NULL && term->shutdown.client_has_terminated;
|
2021-07-31 18:18:48 +02:00
|
|
|
|
|
|
|
|
LOG_DBG("window=%p, slave-has-been-reaped=%d --> %s",
|
2021-07-31 19:08:51 +02:00
|
|
|
(void *)term->window, term->shutdown.client_has_terminated,
|
2021-07-31 18:18:48 +02:00
|
|
|
(shutdown_done
|
|
|
|
|
? "shutdown done, calling term_destroy()"
|
|
|
|
|
: "no action"));
|
|
|
|
|
|
|
|
|
|
if (!shutdown_done)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
void (*cb)(void *, int) = term->shutdown.cb;
|
|
|
|
|
void *cb_data = term->shutdown.cb_data;
|
2021-07-31 18:18:48 +02:00
|
|
|
|
|
|
|
|
int exit_code = term_destroy(term);
|
|
|
|
|
if (cb != NULL)
|
|
|
|
|
cb(cb_data, exit_code);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2021-07-31 18:27:02 +02:00
|
|
|
fdm_client_terminated(struct reaper *reaper, pid_t pid, int status, void *data)
|
2021-07-31 18:18:48 +02:00
|
|
|
{
|
|
|
|
|
struct terminal *term = data;
|
|
|
|
|
LOG_DBG("slave (PID=%u) died", pid);
|
|
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
term->shutdown.client_has_terminated = true;
|
|
|
|
|
term->shutdown.exit_status = status;
|
2021-07-31 18:18:48 +02:00
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
if (term->shutdown.terminate_timeout_fd >= 0) {
|
|
|
|
|
fdm_del(term->fdm, term->shutdown.terminate_timeout_fd);
|
|
|
|
|
term->shutdown.terminate_timeout_fd = -1;
|
2021-07-31 18:18:48 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
if (term->shutdown.in_progress)
|
2021-07-31 18:18:48 +02:00
|
|
|
shutdown_maybe_done(term);
|
|
|
|
|
else if (!term->conf->hold_at_exit)
|
|
|
|
|
term_shutdown(term);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 20:03:11 +01:00
|
|
|
static bool
|
|
|
|
|
fdm_shutdown(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
2020-08-07 20:42:34 +01:00
|
|
|
struct wayland *wayl = 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-07-08 16:45:26 +02:00
|
|
|
tll_foreach(wayl->seats, it) {
|
|
|
|
|
if (it->item.kbd_focus == term)
|
|
|
|
|
it->item.kbd_focus = NULL;
|
|
|
|
|
if (it->item.mouse_focus == term)
|
|
|
|
|
it->item.mouse_focus = NULL;
|
|
|
|
|
}
|
2019-10-30 20:03:11 +01:00
|
|
|
|
2021-07-31 18:18:48 +02:00
|
|
|
shutdown_maybe_done(term);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-10-28 18:25:19 +01:00
|
|
|
|
2021-07-31 18:18:48 +02:00
|
|
|
static bool
|
|
|
|
|
fdm_terminate_timeout(struct fdm *fdm, int fd, int events, void *data)
|
|
|
|
|
{
|
|
|
|
|
uint64_t unused;
|
|
|
|
|
ssize_t bytes = read(fd, &unused, sizeof(unused));
|
|
|
|
|
if (bytes < 0) {
|
|
|
|
|
LOG_ERRNO("failed to read from slave terminate timeout FD");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-11-01 20:30:58 +01:00
|
|
|
|
2021-07-31 18:18:48 +02:00
|
|
|
struct terminal *term = data;
|
2021-07-31 19:08:51 +02:00
|
|
|
xassert(!term->shutdown.client_has_terminated);
|
2021-07-31 18:18:48 +02:00
|
|
|
|
|
|
|
|
LOG_DBG("slave (PID=%u) has not terminated, sending SIGKILL (%d)",
|
|
|
|
|
term->slave, SIGKILL);
|
|
|
|
|
|
2021-07-31 22:50:19 +02:00
|
|
|
kill(-term->slave, SIGKILL);
|
2019-10-30 20:03:11 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_shutdown(struct terminal *term)
|
|
|
|
|
{
|
2021-07-31 19:08:51 +02:00
|
|
|
if (term->shutdown.in_progress)
|
2019-11-01 20:30:58 +01:00
|
|
|
return true;
|
2019-10-30 20:03:11 +01:00
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
term->shutdown.in_progress = 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
|
|
|
*/
|
|
|
|
|
|
2020-11-26 18:08:28 +01:00
|
|
|
term_cursor_blink_update(term);
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->cursor_blink.fd < 0);
|
2019-12-16 21:32:57 +01:00
|
|
|
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
fdm_del(term->fdm, term->selection.auto_scroll.fd);
|
2020-01-12 12:55:19 +01:00
|
|
|
fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
|
render: use a timer instead of relying on the frame callback for title update throttling
Using the frame callback works most of the time, but e.g. Sway doesn’t
call it while the window is hidden, and thus prevents us from updating
the title in e.g. stacked views.
This patch uses a timer FD instead. We store a timestamp from when the
title was last updated. When the application wants to update the
title, we first check if we already have a timer running, and if so,
does nothing.
If no timer is running, check the timestamp. If enough time has
passed, update the title immediately.
If not, instantiate a timer and wait for it to trigger.
Set the minimum time between two updates to ~8ms (twice per frame, for
a 60Hz output, and ~once per frame on a 120Hz output).
Closes #591
2021-06-15 17:27:50 +02:00
|
|
|
fdm_del(term->fdm, term->render.title.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);
|
|
|
|
|
fdm_del(term->fdm, term->blink.fd);
|
|
|
|
|
fdm_del(term->fdm, term->flash.fd);
|
2020-04-30 17:22:57 +02:00
|
|
|
|
2022-09-23 20:24:04 +02:00
|
|
|
del_utmp_record(term->conf, term->reaper, term->ptmx);
|
|
|
|
|
|
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
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
if (!term->shutdown.client_has_terminated) {
|
2021-07-31 22:50:19 +02:00
|
|
|
LOG_DBG("initiating asynchronous terminate of slave; "
|
|
|
|
|
"sending SIGTERM to PID=%u", term->slave);
|
2021-07-31 18:18:48 +02:00
|
|
|
|
2021-07-31 22:50:19 +02:00
|
|
|
kill(-term->slave, SIGTERM);
|
2021-07-31 18:18:48 +02:00
|
|
|
|
|
|
|
|
const struct itimerspec timeout = {.it_value = {.tv_sec = 60}};
|
|
|
|
|
|
|
|
|
|
int timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
|
|
|
|
|
if (timeout_fd < 0 ||
|
|
|
|
|
timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0 ||
|
|
|
|
|
!fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_terminate_timeout, term))
|
|
|
|
|
{
|
|
|
|
|
if (timeout_fd >= 0)
|
|
|
|
|
close(timeout_fd);
|
|
|
|
|
LOG_ERRNO("failed to create slave terminate timeout FD");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
xassert(term->shutdown.terminate_timeout_fd < 0);
|
|
|
|
|
term->shutdown.terminate_timeout_fd = timeout_fd;
|
2021-07-31 18:18:48 +02:00
|
|
|
}
|
|
|
|
|
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
term->selection.auto_scroll.fd = -1;
|
2020-01-12 12:55:19 +01:00
|
|
|
term->render.app_sync_updates.timer_fd = -1;
|
render: use a timer instead of relying on the frame callback for title update throttling
Using the frame callback works most of the time, but e.g. Sway doesn’t
call it while the window is hidden, and thus prevents us from updating
the title in e.g. stacked views.
This patch uses a timer FD instead. We store a timestamp from when the
title was last updated. When the application wants to update the
title, we first check if we already have a timer running, and if so,
does nothing.
If no timer is running, check the timestamp. If enough time has
passed, update the title immediately.
If not, instantiate a timer and wait for it to trigger.
Set the minimum time between two updates to ~8ms (twice per frame, for
a 60Hz output, and ~once per frame on a 120Hz output).
Closes #591
2021-06-15 17:27:50 +02:00
|
|
|
term->render.title.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;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-23 20:24:04 +02:00
|
|
|
del_utmp_record(term->conf, term->reaper, term->ptmx);
|
|
|
|
|
|
selection: auto-scroll: selection keeps scrolling while mouse is outside grid
Moving the mouse outside the grid while we have an on-going selection
now starts a timer. The interval of this timer depends on the mouse’s
distance from the grid - the further away the mouse is, the shorter
interval.
On each timer timeout, we scroll one line, and update the
selection. Thus, the shorter the interval, the faster we scroll.
The timer is canceled as soon as the mouse enters the grid again, or
the selection is either canceled or finalized.
The timer FD is created and destroyed on-demand.
Most of the logic is now in selection.c. The exception is the
calculation of the timer interval, which depends on the mouse’s
position. Thus, this is done in input.c.
The scroll+selection update logic needs to know a) which direction
we’re scrolling in, and b) which *column* the selection should be
updated with.
If the mouse is outside the grid’s left or right margins, the stored
mouse column will be -1. I.e. we don’t know whether the mouse is on
the left or right side of the grid. This is why the caller, that
starts the timer, must provide this value.
The same applies to top and bottom margins, but since we already have
the scroll *direction*, which row value to use can be derived from this.
2020-10-11 15:44:20 +02:00
|
|
|
fdm_del(term->fdm, term->selection.auto_scroll.fd);
|
2020-01-12 12:55:19 +01:00
|
|
|
fdm_del(term->fdm, term->render.app_sync_updates.timer_fd);
|
render: use a timer instead of relying on the frame callback for title update throttling
Using the frame callback works most of the time, but e.g. Sway doesn’t
call it while the window is hidden, and thus prevents us from updating
the title in e.g. stacked views.
This patch uses a timer FD instead. We store a timestamp from when the
title was last updated. When the application wants to update the
title, we first check if we already have a timer running, and if so,
does nothing.
If no timer is running, check the timestamp. If enough time has
passed, update the title immediately.
If not, instantiate a timer and wait for it to trigger.
Set the minimum time between two updates to ~8ms (twice per frame, for
a 60Hz output, and ~once per frame on a 120Hz output).
Closes #591
2021-06-15 17:27:50 +02:00
|
|
|
fdm_del(term->fdm, term->render.title.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);
|
2021-07-31 19:08:51 +02:00
|
|
|
if (term->shutdown.terminate_timeout_fd >= 0)
|
|
|
|
|
fdm_del(term->fdm, term->shutdown.terminate_timeout_fd);
|
2019-10-30 20:03:11 +01:00
|
|
|
|
2021-01-31 11:53:12 +01:00
|
|
|
if (term->window != NULL) {
|
2019-10-30 20:03:11 +01:00
|
|
|
wayl_win_destroy(term->window);
|
2021-01-31 11:53:12 +01:00
|
|
|
term->window = NULL;
|
|
|
|
|
}
|
2019-10-30 20:03:11 +01:00
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
mtx_lock(&term->render.workers.lock);
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(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);
|
|
|
|
|
}
|
2019-10-28 18:25:19 +01:00
|
|
|
}
|
|
|
|
|
mtx_unlock(&term->render.workers.lock);
|
2019-10-30 17:45:59 +01:00
|
|
|
|
2023-07-18 16:13:36 +02:00
|
|
|
key_binding_unref(term->wl->key_binding_manager, term->conf);
|
|
|
|
|
|
2021-06-28 22:33:57 +02:00
|
|
|
urls_reset(term);
|
|
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
free(term->vt.osc.data);
|
2021-03-28 21:01:22 +02:00
|
|
|
free(term->vt.osc8.uri);
|
2019-10-28 18:25:19 +01:00
|
|
|
|
composed: store compose chains in a binary search tree
The previous implementation stored compose chains in a dynamically
allocated array. Adding a chain was easy: resize the array and append
the new chain at the end. Looking up a compose chain given a compose
chain key/index was also easy: just index into the array.
However, searching for a pre-existing chain given a codepoint sequence
was very slow. Since the array wasn’t sorted, we typically had to scan
through the entire array, just to realize that there is no
pre-existing chain, and that we need to add a new one.
Since this happens for *each* codepoint in a grapheme cluster, things
quickly became really slow.
Things were ok:ish as long as the compose chain struct was small, as
that made it possible to hold all the chains in the cache. Once the
number of chains reached a certain point, or when we were forced to
bump maximum number of allowed codepoints in a chain, we started
thrashing the cache and things got much much worse.
So what can we do?
We can’t sort the array, because
a) that would invalidate all existing chain keys in the grid (and
iterating the entire scrollback and updating compose keys is *not* an
option).
b) inserting a chain becomes slow as we need to first find _where_ to
insert it, and then memmove() the rest of the array.
This patch uses a binary search tree to store the chains instead of a
simple array.
The tree is sorted on a “key”, which is the XOR of all codepoints,
truncated to the CELL_COMB_CHARS_HI-CELL_COMB_CHARS_LO range.
The grid now stores CELL_COMB_CHARS_LO+key, instead of
CELL_COMB_CHARS_LO+index.
Since the key is truncated, collisions may occur. This is handled by
incrementing the key by 1.
Lookup is of course slower than before, O(log n) instead of
O(1).
Insertion is slightly slower as well: technically it’s O(log n)
instead of O(1). However, we also need to take into account the
re-allocating the array will occasionally force a full copy of the
array when it cannot simply be growed.
But finding a pre-existing chain is now *much* faster: O(log n)
instead of O(n). In most cases, the first lookup will either
succeed (return a true match), or fail (return NULL). However, since
key collisions are possible, it may also return false matches. This
means we need to verify the contents of the chain before deciding to
use it instead of inserting a new chain. But remember that this
comparison was being done for each and every chain in the previous
implementation.
With lookups being much faster, and in particular, no longer requiring
us to check the chain contents for every singlec chain, we can now use
a dynamically allocated ‘chars’ array in the chain. This was
previously a hardcoded array of 10 chars.
Using a dynamic allocated array means looking in the array is slower,
since we now need two loads: one to load the pointer, and a second to
load _from_ the pointer.
As a result, the base size of a compose chain (i.e. an “empty” chain)
has now been reduced from 48 bytes to 32. A chain with two codepoints
is 40 bytes. This means we have up to 4 codepoints while still using
less, or the same amount, of memory as before.
Furthermore, the Unicode random test (i.e. write random “unicode”
chars) is now **faster** than current master (i.e. before text-shaping
support was added), **with** test-shaping enabled. With text-shaping
disabled, we’re _even_ faster.
2021-06-24 13:17:07 +02:00
|
|
|
composed_free(term->composed);
|
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
|
|
|
|
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]);
|
2020-10-20 21:04:47 +02:00
|
|
|
for (size_t i = 0; i < 4; i++)
|
|
|
|
|
free(term->font_sizes[i]);
|
2019-10-28 18:25:19 +01:00
|
|
|
|
2021-09-14 09:50:49 +02:00
|
|
|
|
|
|
|
|
free_custom_glyphs(
|
|
|
|
|
&term->custom_glyphs.box_drawing, GLYPH_BOX_DRAWING_COUNT);
|
|
|
|
|
free_custom_glyphs(
|
|
|
|
|
&term->custom_glyphs.braille, GLYPH_BRAILLE_COUNT);
|
|
|
|
|
free_custom_glyphs(
|
|
|
|
|
&term->custom_glyphs.legacy, GLYPH_LEGACY_COUNT);
|
2020-12-26 16:24:16 +01:00
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
free(term->search.buf);
|
2022-01-27 18:36:28 +01:00
|
|
|
free(term->search.last.buf);
|
2019-10-28 18:25:19 +01:00
|
|
|
|
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);
|
|
|
|
|
mtx_destroy(&term->render.workers.lock);
|
|
|
|
|
sem_destroy(&term->render.workers.start);
|
|
|
|
|
sem_destroy(&term->render.workers.done);
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(tll_length(term->render.workers.queue) == 0);
|
2019-10-28 18:25:19 +01:00
|
|
|
tll_free(term->render.workers.queue);
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
|
2021-07-16 16:47:57 +02:00
|
|
|
shm_unref(term->render.last_buf);
|
shm: refactor: move away from a single, global, buffer list
Up until now, *all* buffers have been tracked in a single, global
buffer list. We've used 'cookies' to separate buffers from different
contexts (so that shm_get_buffer() doesn't try to re-use e.g. a
search-box buffer for the main grid).
This patch refactors this, and completely removes the global
list.
Instead of cookies, we now use 'chains'. A chain tracks both the
properties to apply to newly created buffers (scrollable, number of
pixman instances to instantiate etc), as well as the instantiated
buffers themselves.
This means there's strictly speaking not much use for shm_fini()
anymore, since its up to the chain owner to call shm_chain_free(),
which will also purge all buffers.
However, since purging a buffer may be deferred, if the buffer is
owned by the compositor at the time of the call to shm_purge() or
shm_chain_free(), we still keep a global 'deferred' list, on to which
deferred buffers are pushed. shm_fini() iterates this list and
destroys the buffers _even_ if they are still owned by the
compositor. This only happens at program termination, and not when
destroying a terminal instance. I.e. closing a window in a “foot
--server” does *not* trigger this.
Each terminal instatiates a number of chains, and these chains are
destroyed when the terminal instance is destroyed. Note that some
buffers may be put on the deferred list, as mentioned above.
2021-07-16 16:48:49 +02:00
|
|
|
shm_chain_free(term->render.chains.grid);
|
|
|
|
|
shm_chain_free(term->render.chains.search);
|
|
|
|
|
shm_chain_free(term->render.chains.scrollback_indicator);
|
|
|
|
|
shm_chain_free(term->render.chains.render_timer);
|
|
|
|
|
shm_chain_free(term->render.chains.url);
|
2021-07-18 16:46:43 +02:00
|
|
|
shm_chain_free(term->render.chains.csd);
|
render: implement ‘flash’ and search mode’s ‘dimming’ with a sub-surface
Search mode and ‘flash’ (OSC-555) both achieves similar visual
effects: flash tints the entire window yellow, and search mode dims
it (except the search match).
But, they do so in completely different ways. Search mode is detected
in render_cell(), and the colors are then dimmed there.
Flash is implemented by blending a yellow, semi-transparent color on
top of the rendered grid.
This patch replaces those two implementations with a single one. We
add a new sub-surface, called the ‘overlay’. In normal mode, it’s
unmapped.
When either search mode, or flash, is enabled, we enable it, and
fill it with a semi-transparent color. Yellow for ‘flash’, and
“black” (i.e. no color) for search mode.
The compositor then blends it with the grid. Hopefully on the GPU,
meaning it’ll be faster than if we blend in software.
There are more performance benefits however. By using a separate
surface, we can do much better damage tracking.
The normal grid rendering code no longer have to care about neither
search mode, nor flash. Thus, we get rid of a couple of ‘if’
statements in render_cell(), which is nice. But more importantly, we
can drop full grid repaints in a couple of circumstances:
* Entering/exiting search mode
* Every frame while flash is active
Now, when rendering the search mode overlay, we do want to do some
damage tracking, also of the overlay.
This, since search mode doesn’t dim the *entire* window. The search
match is *not* dimmed. This is implemented by punching a hole in the
overlay sub-surface. That is, we make part of it *fully*
transparent. The basic idea is to set a clip region that excludes the
search match, and then dim the rest of the overlay.
It’s slightly more complicated than that however, if we want to reuse
the last frame’s overlay buffer (i.e we don’t want to re-render
the *entire* overlay every frame).
In short, we need to:
* Clear (punch hole) in areas that are part of this frame’s search
match, but not the last frame’s (since those parts are _already_
cleared).
* Dim the areas that were part of the last frame’s search match, but
aren’t anymore (the rest of the overlay should already be dimmed).
To do this, we save the last frame’s “holes” (as a pixman
region). Then, when rendering the next frame, we first calculate the
new frame’s “holes” region.
The region to clear is “this frame’s holes minus last frame’s holes”
The region to dim is “last frame’s holes minus this frames holes”.
Finally, we compute the bounding box of all modified cells by taking
the union of the two diff regions mentioned above. This allows us to
limit the buffer damage sent to the compositor.
2022-04-16 17:49:46 +02:00
|
|
|
shm_chain_free(term->render.chains.overlay);
|
|
|
|
|
pixman_region32_fini(&term->render.last_overlay_clip);
|
2019-10-28 18:25:19 +01:00
|
|
|
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_free(term->tab_stops);
|
|
|
|
|
|
|
|
|
|
tll_foreach(term->ptmx_buffers, it) {
|
2020-08-22 09:14:18 +02:00
|
|
|
free(it->item.data);
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_remove(term->ptmx_buffers, it);
|
|
|
|
|
}
|
|
|
|
|
tll_foreach(term->ptmx_paste_buffers, it) {
|
2019-11-03 01:03:52 +01:00
|
|
|
free(it->item.data);
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_remove(term->ptmx_paste_buffers, it);
|
|
|
|
|
}
|
2019-12-21 15:35:54 +01:00
|
|
|
|
2020-06-10 18:36:54 +02:00
|
|
|
sixel_fini(term);
|
2020-02-21 23:40:35 +01:00
|
|
|
|
2020-12-04 18:39:11 +01:00
|
|
|
term_ime_reset(term);
|
2020-12-02 18:52:50 +01:00
|
|
|
|
2021-06-28 22:33:57 +02:00
|
|
|
grid_free(&term->normal);
|
|
|
|
|
grid_free(&term->alt);
|
2022-10-18 18:29:20 +02:00
|
|
|
grid_free(term->interactive_resizing.grid);
|
|
|
|
|
free(term->interactive_resizing.grid);
|
2021-06-28 22:33:57 +02:00
|
|
|
|
2019-12-21 15:27:17 +01:00
|
|
|
free(term->foot_exe);
|
2019-12-21 15:35:54 +01:00
|
|
|
free(term->cwd);
|
2022-01-01 13:56:50 +01:00
|
|
|
free(term->mouse_user_cursor);
|
2019-11-03 01:03:52 +01:00
|
|
|
|
2019-10-28 18:25:19 +01:00
|
|
|
int ret = EXIT_SUCCESS;
|
|
|
|
|
|
|
|
|
|
if (term->slave > 0) {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* We'll deal with this explicitly */
|
2021-07-31 18:18:48 +02:00
|
|
|
reaper_del(term->reaper, term->slave);
|
|
|
|
|
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
int exit_status;
|
|
|
|
|
|
2021-07-31 19:08:51 +02:00
|
|
|
if (term->shutdown.client_has_terminated)
|
|
|
|
|
exit_status = term->shutdown.exit_status;
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
else {
|
2021-07-31 22:50:19 +02:00
|
|
|
LOG_DBG("initiating blocking terminate of slave; "
|
|
|
|
|
"sending SIGTERM to PID=%u", term->slave);
|
2021-07-31 18:18:48 +02:00
|
|
|
|
2021-07-31 22:50:19 +02:00
|
|
|
kill(-term->slave, SIGTERM);
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* we've closed the ptxm, and sent SIGTERM to the client
|
2021-07-31 18:18:48 +02:00
|
|
|
* application. It *should* exit...
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
*
|
|
|
|
|
* But, since it is possible to write clients that ignore
|
|
|
|
|
* this, we need to handle it in *some* way.
|
|
|
|
|
*
|
2021-07-31 18:18:48 +02:00
|
|
|
* So, what we do is register a SIGALRM handler, and configure a 30
|
|
|
|
|
* second alarm. If the slave hasn't died after this time, we send
|
|
|
|
|
* it a SIGKILL,
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
Explicitly initialize sigaction::sa_mask members with sigemptyset(3)
Not doing so before calling sigaction(3) is "undefined" according to
POSIX[1]:
> Applications shall call either sigemptyset() or sigfillset() at least
> once for each object of type sigset_t prior to any other use of that
> object. If such an object is not initialized in this way, but is
> nonetheless supplied as an argument to any of pthread_sigmask(),
> sigaction(), sigaddset(), sigdelset(), sigismember(), sigpending(),
> sigprocmask(), sigsuspend(), sigtimedwait(), sigwait(), or
> sigwaitinfo(), the results are undefined.
The use of designated initializers means that sa_mask members were
still being initialized, but sigset_t is an opaque type and implicit
initialization doesn't necessarily produce the same results as using
sigemptyset(3) (although it typically does on most implementations).
[1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaddset.html
2022-02-12 12:04:57 +00:00
|
|
|
struct sigaction action = {.sa_handler = &sig_alarm};
|
|
|
|
|
sigemptyset(&action.sa_mask);
|
|
|
|
|
sigaction(SIGALRM, &action, NULL);
|
2021-07-31 18:18:48 +02:00
|
|
|
alarm(60);
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
int r = waitpid(term->slave, &exit_status, 0);
|
|
|
|
|
|
|
|
|
|
if (r == term->slave)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (r == -1) {
|
|
|
|
|
xassert(errno == EINTR);
|
|
|
|
|
|
|
|
|
|
if (alarm_raised) {
|
2021-07-31 18:18:48 +02:00
|
|
|
LOG_DBG(
|
|
|
|
|
"slave (PID=%u) has not terminate yet, "
|
|
|
|
|
"sending: SIGKILL (%d)", term->slave, SIGKILL);
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
|
2021-07-31 22:50:19 +02:00
|
|
|
kill(-term->slave, SIGKILL);
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
}
|
2019-11-02 12:02:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
/* Cancel alarm */
|
|
|
|
|
alarm(0);
|
Explicitly initialize sigaction::sa_mask members with sigemptyset(3)
Not doing so before calling sigaction(3) is "undefined" according to
POSIX[1]:
> Applications shall call either sigemptyset() or sigfillset() at least
> once for each object of type sigset_t prior to any other use of that
> object. If such an object is not initialized in this way, but is
> nonetheless supplied as an argument to any of pthread_sigmask(),
> sigaction(), sigaddset(), sigdelset(), sigismember(), sigpending(),
> sigprocmask(), sigsuspend(), sigtimedwait(), sigwait(), or
> sigwaitinfo(), the results are undefined.
The use of designated initializers means that sa_mask members were
still being initialized, but sigset_t is an opaque type and implicit
initialization doesn't necessarily produce the same results as using
sigemptyset(3) (although it typically does on most implementations).
[1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaddset.html
2022-02-12 12:04:57 +00:00
|
|
|
action.sa_handler = SIG_DFL;
|
|
|
|
|
sigaction(SIGALRM, &action, NULL);
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
}
|
2019-10-28 18:25:19 +01:00
|
|
|
|
2019-11-02 11:30:32 +01:00
|
|
|
ret = EXIT_FAILURE;
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
if (WIFEXITED(exit_status)) {
|
|
|
|
|
ret = WEXITSTATUS(exit_status);
|
2019-11-02 12:02:11 +01:00
|
|
|
LOG_DBG("slave exited with code %d", ret);
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
} else if (WIFSIGNALED(exit_status)) {
|
|
|
|
|
ret = WTERMSIG(exit_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 {
|
reaper: monitor SIGCHLD using the FDM instead of via a signalfd
In addition to letting the FDM do the low-level signal watching, this
patch also fixes a bug; multiple SIGCHLDs, be it delivered either through a
signal, or via a signalfd, can be coalesced, like all signals.
This means we need to loop on waitpid() with WNOHANG until there are
no more processes to reap.
This in turn requires a small change to the way reaper callbacks are
implemented.
Previously, the callback was allowed to do the wait(). This was
signalled back to the reaper through the callback’s return value.
Now, since we’ve already wait():ed, the process’ exit status is passed
as an argument to the reaper callback.
The callback for the client application has been updated accordingly;
it sets a flag in the terminal struct, telling term_destroy() that the
process has already been wait():ed on, and also stores the exit
status.
2021-02-10 16:22:51 +01:00
|
|
|
LOG_WARN("slave exited for unknown reason (status = 0x%08x)",
|
|
|
|
|
exit_status);
|
2019-10-28 18:25:19 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(start < term->cols);
|
|
|
|
|
xassert(end < term->cols);
|
2020-04-13 11:42:10 +02:00
|
|
|
|
2020-05-17 16:29:09 +02:00
|
|
|
row->dirty = true;
|
|
|
|
|
|
2021-11-20 16:29:57 +01:00
|
|
|
const enum color_source bg_src = term->vt.attrs.bg_src;
|
|
|
|
|
|
|
|
|
|
if (unlikely(bg_src != COLOR_DEFAULT)) {
|
2020-04-13 11:42:10 +02:00
|
|
|
for (int col = start; col <= end; col++) {
|
|
|
|
|
struct cell *c = &row->cells[col];
|
|
|
|
|
c->wc = 0;
|
2021-11-20 16:29:57 +01:00
|
|
|
c->attrs = (struct attributes){.bg_src = bg_src, .bg = term->vt.attrs.bg};
|
2020-04-13 11:42:10 +02:00
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
memset(&row->cells[start], 0, (end - start + 1) * sizeof(row->cells[0]));
|
2021-02-23 15:10:40 +01:00
|
|
|
|
2021-11-20 14:40:03 +01:00
|
|
|
if (unlikely(row->extra != NULL))
|
|
|
|
|
grid_row_uri_range_erase(row, start, end);
|
2020-04-13 11:42:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
|
erase_line(struct terminal *term, struct row *row)
|
|
|
|
|
{
|
|
|
|
|
erase_cell_range(term, row, 0, term->cols - 1);
|
2022-06-30 19:37:01 +02:00
|
|
|
row->linebreak = false;
|
2022-12-08 10:35:30 +01:00
|
|
|
row->shell_integration.prompt_marker = false;
|
2022-12-08 10:46:46 +01:00
|
|
|
row->shell_integration.cmd_start = -1;
|
|
|
|
|
row->shell_integration.cmd_end = -1;
|
2020-04-13 11:42:10 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-01 20:51:11 +02:00
|
|
|
void
|
|
|
|
|
term_reset(struct terminal *term, bool hard)
|
|
|
|
|
{
|
2023-10-04 08:23:27 +02:00
|
|
|
LOG_INFO("%s resetting the terminal", hard ? "hard" : "soft");
|
|
|
|
|
|
2019-08-01 20:51:11 +02:00
|
|
|
term->cursor_keys_mode = CURSOR_KEYS_NORMAL;
|
|
|
|
|
term->keypad_keys_mode = KEYPAD_NUMERICAL;
|
|
|
|
|
term->reverse = false;
|
|
|
|
|
term->hide_cursor = false;
|
2020-10-06 18:42:26 +02:00
|
|
|
term->reverse_wrap = true;
|
2019-08-01 20:51:11 +02:00
|
|
|
term->auto_margin = true;
|
|
|
|
|
term->insert_mode = false;
|
|
|
|
|
term->bracketed_paste = false;
|
|
|
|
|
term->focus_events = false;
|
2020-12-10 18:22:48 +01:00
|
|
|
term->num_lock_modifier = true;
|
|
|
|
|
term->bell_action_enabled = true;
|
2019-08-01 20:51:11 +02:00
|
|
|
term->mouse_tracking = MOUSE_NONE;
|
|
|
|
|
term->mouse_reporting = MOUSE_NORMAL;
|
2021-06-09 09:51:48 +01:00
|
|
|
term->charsets.selected = G0;
|
|
|
|
|
term->charsets.set[G0] = CHARSET_ASCII;
|
|
|
|
|
term->charsets.set[G1] = CHARSET_ASCII;
|
|
|
|
|
term->charsets.set[G2] = CHARSET_ASCII;
|
|
|
|
|
term->charsets.set[G3] = 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);
|
2020-06-22 14:33:16 +02:00
|
|
|
term_set_window_title(term, term->conf->title);
|
2019-08-01 20:51:11 +02:00
|
|
|
|
2022-01-01 13:56:50 +01:00
|
|
|
term_set_user_mouse_cursor(term, NULL);
|
|
|
|
|
|
2023-05-12 09:42:35 +02:00
|
|
|
term->modify_other_keys_2 = false;
|
2021-11-16 16:32:58 +01:00
|
|
|
memset(term->normal.kitty_kbd.flags, 0, sizeof(term->normal.kitty_kbd.flags));
|
|
|
|
|
memset(term->alt.kitty_kbd.flags, 0, sizeof(term->alt.kitty_kbd.flags));
|
|
|
|
|
term->normal.kitty_kbd.idx = term->alt.kitty_kbd.idx = 0;
|
|
|
|
|
|
2019-08-01 20:51:11 +02:00
|
|
|
term->scroll_region.start = 0;
|
|
|
|
|
term->scroll_region.end = term->rows;
|
|
|
|
|
|
2021-05-08 15:18:25 +02:00
|
|
|
free(term->vt.osc8.uri);
|
2019-08-01 20:51:11 +02:00
|
|
|
free(term->vt.osc.data);
|
2021-03-28 21:01:22 +02:00
|
|
|
|
2021-05-08 19:07:37 +02:00
|
|
|
term->vt = (struct vt){
|
|
|
|
|
.state = 0, /* STATE_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;
|
|
|
|
|
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_foreach(term->normal.sixel_images, it) {
|
2020-03-13 18:44:23 +01:00
|
|
|
sixel_destroy(&it->item);
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_remove(term->normal.sixel_images, it);
|
|
|
|
|
}
|
|
|
|
|
tll_foreach(term->alt.sixel_images, it) {
|
2020-02-22 10:47:16 +01:00
|
|
|
sixel_destroy(&it->item);
|
2021-02-07 14:52:04 +01:00
|
|
|
tll_remove(term->alt.sixel_images, it);
|
|
|
|
|
}
|
2020-02-22 10:47:16 +01:00
|
|
|
|
2023-09-20 13:45:06 +02:00
|
|
|
term->grapheme_shaping = term->conf->tweak.grapheme_shaping;
|
|
|
|
|
|
2020-12-04 18:39:11 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
|
|
|
|
term_ime_enable(term);
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-03-14 19:19:10 +01:00
|
|
|
term_update_ascii_printer(term);
|
|
|
|
|
|
2019-08-01 20:51:11 +02:00
|
|
|
if (!hard)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
term->flash.active = false;
|
|
|
|
|
term->blink.state = BLINK_ON;
|
2020-10-13 19:28:42 +02:00
|
|
|
fdm_del(term->fdm, term->blink.fd); term->blink.fd = -1;
|
2021-04-07 08:07:43 +02:00
|
|
|
term->colors.fg = term->conf->colors.fg;
|
|
|
|
|
term->colors.bg = term->conf->colors.bg;
|
2021-04-07 19:04:25 +02:00
|
|
|
term->colors.alpha = term->conf->colors.alpha;
|
2021-04-07 08:09:40 +02:00
|
|
|
term->colors.selection_fg = term->conf->colors.selection_fg;
|
|
|
|
|
term->colors.selection_bg = term->conf->colors.selection_bg;
|
|
|
|
|
term->colors.use_custom_selection = term->conf->colors.use_custom.selection;
|
2021-04-07 08:07:43 +02:00
|
|
|
memcpy(term->colors.table, term->conf->colors.table,
|
|
|
|
|
sizeof(term->colors.table));
|
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}};
|
2020-11-26 18:08:28 +01:00
|
|
|
term->cursor_style = term->conf->cursor.style;
|
|
|
|
|
term->cursor_blink.decset = false;
|
|
|
|
|
term->cursor_blink.deccsusr = term->conf->cursor.blink;
|
|
|
|
|
term_cursor_blink_update(term);
|
|
|
|
|
term->cursor_color.text = term->conf->cursor.color.text;
|
|
|
|
|
term->cursor_color.cursor = term->conf->cursor.color.cursor;
|
2019-08-01 20:51:11 +02:00
|
|
|
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.scroll_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_damage_all(term);
|
2022-04-26 21:05:17 +02:00
|
|
|
|
|
|
|
|
term->sixel.scrolling = true;
|
|
|
|
|
term->sixel.cursor_right_of_graphics = false;
|
|
|
|
|
term->sixel.use_private_palette = true;
|
|
|
|
|
term->sixel.max_width = SIXEL_MAX_WIDTH;
|
|
|
|
|
term->sixel.max_height = SIXEL_MAX_HEIGHT;
|
|
|
|
|
term->sixel.palette_size = SIXEL_MAX_COLORS;
|
|
|
|
|
free(term->sixel.private_palette);
|
|
|
|
|
free(term->sixel.shared_palette);
|
|
|
|
|
term->sixel.private_palette = term->sixel.shared_palette = NULL;
|
2019-08-01 20:51:11 +02: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
|
|
|
static bool
|
2022-12-15 11:10:32 +01:00
|
|
|
term_font_size_adjust_by_points(struct terminal *term, float amount)
|
2020-02-08 14:09:06 +01:00
|
|
|
{
|
2021-06-17 18:15:29 +02:00
|
|
|
const struct config *conf = term->conf;
|
2022-12-15 11:10:32 +01:00
|
|
|
const float dpi = term->font_is_sized_by_dpi ? term->font_dpi : 96.;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
const struct config_font_list *font_list = &conf->fonts[i];
|
|
|
|
|
|
|
|
|
|
for (size_t j = 0; j < font_list->count; j++) {
|
|
|
|
|
struct config_font *font = &term->font_sizes[i][j];
|
|
|
|
|
float old_pt_size = font->pt_size;
|
2021-06-17 18:15:29 +02:00
|
|
|
|
2022-12-15 11:10:32 +01:00
|
|
|
if (font->px_size > 0)
|
|
|
|
|
old_pt_size = font->px_size * 72. / dpi;
|
|
|
|
|
|
|
|
|
|
font->pt_size = fmaxf(old_pt_size + amount, 0.);
|
|
|
|
|
font->px_size = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
return reload_fonts(term, true);
|
2022-12-15 11:10:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
term_font_size_adjust_by_pixels(struct terminal *term, int amount)
|
|
|
|
|
{
|
|
|
|
|
const struct config *conf = term->conf;
|
2021-12-14 17:45:12 +01:00
|
|
|
const float dpi = term->font_is_sized_by_dpi ? term->font_dpi : 96.;
|
|
|
|
|
|
2020-10-20 21:04:47 +02:00
|
|
|
for (size_t i = 0; i < 4; i++) {
|
2021-06-17 18:15:29 +02:00
|
|
|
const struct config_font_list *font_list = &conf->fonts[i];
|
|
|
|
|
|
|
|
|
|
for (size_t j = 0; j < font_list->count; j++) {
|
2022-12-15 11:10:32 +01:00
|
|
|
struct config_font *font = &term->font_sizes[i][j];
|
|
|
|
|
int old_px_size = font->px_size;
|
2020-10-20 21:04:47 +02:00
|
|
|
|
2022-12-15 11:10:32 +01:00
|
|
|
if (font->px_size <= 0)
|
|
|
|
|
old_px_size = font->pt_size * dpi / 72.;
|
2020-10-20 21:04:47 +02:00
|
|
|
|
2022-12-15 11:10:32 +01:00
|
|
|
font->px_size = max(old_px_size + amount, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-29 20:09:21 +02:00
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
return reload_fonts(term, true);
|
2022-12-15 11:10:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
term_font_size_adjust_by_percent(struct terminal *term, bool increment, float percent)
|
|
|
|
|
{
|
|
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
const float multiplier = increment
|
|
|
|
|
? 1. + percent
|
|
|
|
|
: 1. / (1. + percent);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < 4; i++) {
|
|
|
|
|
const struct config_font_list *font_list = &conf->fonts[i];
|
|
|
|
|
|
|
|
|
|
for (size_t j = 0; j < font_list->count; j++) {
|
|
|
|
|
struct config_font *font = &term->font_sizes[i][j];
|
|
|
|
|
|
|
|
|
|
if (font->px_size > 0)
|
|
|
|
|
font->px_size = max(font->px_size * multiplier, 1);
|
|
|
|
|
else
|
|
|
|
|
font->pt_size = fmax(font->pt_size * multiplier, 0);
|
2020-07-07 10:44:55 +02:00
|
|
|
}
|
2020-02-08 14:09:06 +01:00
|
|
|
}
|
|
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
return reload_fonts(term, 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)
|
|
|
|
|
{
|
2022-12-15 11:10:32 +01:00
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
const struct font_size_adjustment *inc_dec = &conf->font_size_adjustment;
|
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
|
|
|
|
2022-12-15 11:10:32 +01:00
|
|
|
if (inc_dec->percent > 0.)
|
|
|
|
|
return term_font_size_adjust_by_percent(term, true, inc_dec->percent);
|
|
|
|
|
else if (inc_dec->pt_or_px.px > 0)
|
|
|
|
|
return term_font_size_adjust_by_pixels(term, inc_dec->pt_or_px.px);
|
|
|
|
|
else
|
|
|
|
|
return term_font_size_adjust_by_points(term, inc_dec->pt_or_px.pt);
|
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)
|
|
|
|
|
{
|
2022-12-15 11:10:32 +01:00
|
|
|
const struct config *conf = term->conf;
|
|
|
|
|
const struct font_size_adjustment *inc_dec = &conf->font_size_adjustment;
|
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
|
|
|
|
2022-12-15 11:10:32 +01:00
|
|
|
if (inc_dec->percent > 0.)
|
|
|
|
|
return term_font_size_adjust_by_percent(term, false, inc_dec->percent);
|
|
|
|
|
else if (inc_dec->pt_or_px.px > 0)
|
|
|
|
|
return term_font_size_adjust_by_pixels(term, -inc_dec->pt_or_px.px);
|
|
|
|
|
else
|
|
|
|
|
return term_font_size_adjust_by_points(term, -inc_dec->pt_or_px.pt);
|
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-07-07 10:44:55 +02:00
|
|
|
return load_fonts_from_conf(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
|
|
|
}
|
|
|
|
|
|
2023-07-25 15:56:30 +02:00
|
|
|
bool
|
|
|
|
|
term_fractional_scaling(const struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
return term->wl->fractional_scale_manager != NULL && term->window->scale > 0.;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-09 22:51:17 -06:00
|
|
|
bool
|
|
|
|
|
term_preferred_buffer_scale(const struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
return term->wl->has_wl_compositor_v6;
|
|
|
|
|
}
|
|
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
bool
|
|
|
|
|
term_update_scale(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
const struct wl_window *win = term->window;
|
|
|
|
|
|
|
|
|
|
/*
|
2024-02-06 12:36:45 +01:00
|
|
|
* We have a number of "sources" we can use as scale. We choose
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
* the scale in the following order:
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* - "preferred" scale, from the fractional-scale-v1 protocol
|
2024-01-09 22:51:17 -06:00
|
|
|
* - "preferred" scale, from wl_compositor version 6.
|
|
|
|
|
NOTE: if the compositor advertises version 6 we must use 1.0
|
|
|
|
|
until wl_surface.preferred_buffer_scale is sent
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
* - scaling factor of output we most recently were mapped on
|
term: improve fallback logic when selecting scaling factor while unmapped
The foot window may, for various reasons, become completely
unmapped (that is, being removed from all outputs) at run time.
One example is wlroots based compositors; they unmap all other windows
when an opaque window is fullscreened.
21d99f8dced335826964ca96b8ba7ccac059e598 introduced a regression,
where instead of picking the scaling factor from one of the available
outputs (at random), we started falling back to '1' as soon as we were
unmapped.
This patch restores the original logic, but also improves upon it.
As soon as a scaling factor has been assigned to the window, we store
a copy of it in the term struct ('scale_before_unmap').
When unmapped, we check if it has a valid value (the only time it
doesn't is before the initial map). If so, we use it.
Only if it hasn't been set do we fall back to picking an output at
random, and using its scaling factor.
Closes #1464
2023-08-18 16:39:00 +02:00
|
|
|
* - if we're not mapped, use the last known scaling factor
|
|
|
|
|
* - if we're not mapped, and we don't have a last known scaling
|
|
|
|
|
* factor, use the scaling factor from the first available
|
|
|
|
|
* output.
|
2024-02-06 12:36:45 +01:00
|
|
|
* - if there aren't any outputs available, use 1.0
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
*/
|
term: improve fallback logic when selecting scaling factor while unmapped
The foot window may, for various reasons, become completely
unmapped (that is, being removed from all outputs) at run time.
One example is wlroots based compositors; they unmap all other windows
when an opaque window is fullscreened.
21d99f8dced335826964ca96b8ba7ccac059e598 introduced a regression,
where instead of picking the scaling factor from one of the available
outputs (at random), we started falling back to '1' as soon as we were
unmapped.
This patch restores the original logic, but also improves upon it.
As soon as a scaling factor has been assigned to the window, we store
a copy of it in the term struct ('scale_before_unmap').
When unmapped, we check if it has a valid value (the only time it
doesn't is before the initial map). If so, we use it.
Only if it hasn't been set do we fall back to picking an output at
random, and using its scaling factor.
Closes #1464
2023-08-18 16:39:00 +02:00
|
|
|
const float new_scale = (term_fractional_scaling(term)
|
|
|
|
|
? win->scale
|
2024-01-09 22:51:17 -06:00
|
|
|
: term_preferred_buffer_scale(term)
|
|
|
|
|
? win->preferred_buffer_scale
|
|
|
|
|
: tll_length(win->on_outputs) > 0
|
|
|
|
|
? tll_back(win->on_outputs)->scale
|
|
|
|
|
: term->scale_before_unmap > 0.
|
|
|
|
|
? term->scale_before_unmap
|
|
|
|
|
: tll_length(term->wl->monitors) > 0
|
|
|
|
|
? tll_front(term->wl->monitors).scale
|
|
|
|
|
: 1.);
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
|
|
|
|
|
if (new_scale == term->scale)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("scaling factor changed: %.2f -> %.2f", term->scale, new_scale);
|
term: improve fallback logic when selecting scaling factor while unmapped
The foot window may, for various reasons, become completely
unmapped (that is, being removed from all outputs) at run time.
One example is wlroots based compositors; they unmap all other windows
when an opaque window is fullscreened.
21d99f8dced335826964ca96b8ba7ccac059e598 introduced a regression,
where instead of picking the scaling factor from one of the available
outputs (at random), we started falling back to '1' as soon as we were
unmapped.
This patch restores the original logic, but also improves upon it.
As soon as a scaling factor has been assigned to the window, we store
a copy of it in the term struct ('scale_before_unmap').
When unmapped, we check if it has a valid value (the only time it
doesn't is before the initial map). If so, we use it.
Only if it hasn't been set do we fall back to picking an output at
random, and using its scaling factor.
Closes #1464
2023-08-18 16:39:00 +02:00
|
|
|
term->scale_before_unmap = new_scale;
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
term->scale = new_scale;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2023-07-16 08:28:21 +02:00
|
|
|
term_font_dpi_changed(struct terminal *term, float old_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
|
|
|
{
|
2020-12-17 12:05:22 +01:00
|
|
|
float dpi = get_font_dpi(term);
|
2023-07-16 08:28:21 +02:00
|
|
|
xassert(term->scale > 0.);
|
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
|
|
|
|
2021-09-19 11:58:34 +02:00
|
|
|
bool was_scaled_using_dpi = term->font_is_sized_by_dpi;
|
2023-06-26 17:55:04 +02:00
|
|
|
bool will_scale_using_dpi = term->conf->dpi_aware;
|
2020-11-17 17:59:31 +01:00
|
|
|
|
2020-12-17 12:05:22 +01:00
|
|
|
bool need_font_reload =
|
|
|
|
|
was_scaled_using_dpi != will_scale_using_dpi ||
|
|
|
|
|
(will_scale_using_dpi
|
|
|
|
|
? term->font_dpi != dpi
|
2021-05-13 11:10:51 +02:00
|
|
|
: old_scale != term->scale);
|
2020-12-17 12:05:22 +01:00
|
|
|
|
|
|
|
|
if (need_font_reload) {
|
2023-07-17 16:19:14 +02:00
|
|
|
LOG_DBG("DPI/scale change: DPI-aware=%s, "
|
2023-07-16 08:28:21 +02:00
|
|
|
"DPI: %.2f -> %.2f, scale: %.2f -> %.2f, "
|
2020-12-17 12:05:22 +01:00
|
|
|
"sizing font based on monitor's %s",
|
2023-07-16 08:27:12 +02:00
|
|
|
term->conf->dpi_aware ? "yes" : "no",
|
2021-09-19 11:58:34 +02:00
|
|
|
term->font_dpi, dpi, old_scale, term->scale,
|
2020-12-17 12:05:22 +01:00
|
|
|
will_scale_using_dpi ? "DPI" : "scaling factor");
|
2020-11-17 17:59:31 +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
|
|
|
|
2020-12-17 12:05:22 +01:00
|
|
|
term->font_dpi = dpi;
|
2021-09-19 11:58:34 +02:00
|
|
|
term->font_is_sized_by_dpi = will_scale_using_dpi;
|
2020-12-17 12:05:22 +01:00
|
|
|
|
|
|
|
|
if (!need_font_reload)
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
return false;
|
2020-12-17 12:05:22 +01:00
|
|
|
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
return reload_fonts(term, 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[] = {
|
2020-10-13 18:39:36 +02:00
|
|
|
[FCFT_SUBPIXEL_DEFAULT] = "default",
|
|
|
|
|
[FCFT_SUBPIXEL_NONE] = "disabled",
|
|
|
|
|
[FCFT_SUBPIXEL_HORIZONTAL_RGB] = "RGB",
|
|
|
|
|
[FCFT_SUBPIXEL_HORIZONTAL_BGR] = "BGR",
|
|
|
|
|
[FCFT_SUBPIXEL_VERTICAL_RGB] = "V-RGB",
|
|
|
|
|
[FCFT_SUBPIXEL_VERTICAL_BGR] = "V-BGR",
|
2020-04-20 18:37:59 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
LOG_DBG("subpixel mode changed: %s -> %s", str[term->font_subpixel], str[subpixel]);
|
2021-07-31 19:52:23 +02:00
|
|
|
#endif
|
|
|
|
|
|
2020-04-20 18:37:59 +02:00
|
|
|
term->font_subpixel = subpixel;
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
render_refresh(term);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-23 17:35:57 +02:00
|
|
|
int
|
|
|
|
|
term_font_baseline(const struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
const struct fcft_font *font = term->fonts[0];
|
|
|
|
|
const int line_height = term->cell_height;
|
2023-10-10 10:52:35 +02:00
|
|
|
const int font_height = font->ascent + font->descent;
|
2023-07-23 17:35:57 +02:00
|
|
|
|
2023-10-10 13:52:24 +02:00
|
|
|
/*
|
|
|
|
|
* Center glyph on the line *if* using a custom line height,
|
|
|
|
|
* otherwise the baseline is simply 'descent' pixels above the
|
|
|
|
|
* bottom of the cell
|
|
|
|
|
*/
|
|
|
|
|
const int glyph_top_y = term->font_line_height.px >= 0
|
|
|
|
|
? round((line_height - font_height) / 2.)
|
|
|
|
|
: 0;
|
|
|
|
|
|
|
|
|
|
return term->font_y_ofs + line_height - glyph_top_y - font->descent;
|
2023-07-23 17:35:57 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-11 09:51:51 +02:00
|
|
|
void
|
|
|
|
|
term_damage_rows(struct terminal *term, int start, int end)
|
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(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)
|
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(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
|
|
|
}
|
|
|
|
|
|
2020-09-29 10:03:00 +02:00
|
|
|
void
|
|
|
|
|
term_damage_cursor(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
term->grid->cur_row->cells[term->grid->cursor.point.col].attrs.clean = 0;
|
|
|
|
|
term->grid->cur_row->dirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_damage_margins(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
term->render.margins = true;
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
term: protect against integer overflow when accumulating scroll damage
When accumulating scroll damage, we check if the last scroll damage’s
scrolling region, and type, matches the new/current scroll damage. If
so, the number of lines in the last scroll damage is increased,
instead of adding a new scroll damage instance to the list.
If the scroll damage list isn’t consumed, this build up of scroll
damage would eventually overflow.
And, even if it didn’t overflow, it could become large enough, that
when later used to calculate e.g. the affected surface area, while
rendering a frame, would cause an overflow there instead.
This patch fixes both issues by:
a) do an overflow check before increasing the line count
b) limit the line count to UINT16_MAX
2023-03-28 18:37:41 +02:00
|
|
|
if (likely(tll_length(term->grid->scroll_damage) > 0)) {
|
2019-06-29 21:23:36 +02:00
|
|
|
struct damage *dmg = &tll_back(term->grid->scroll_damage);
|
2019-06-29 21:03:28 +02:00
|
|
|
|
term: protect against integer overflow when accumulating scroll damage
When accumulating scroll damage, we check if the last scroll damage’s
scrolling region, and type, matches the new/current scroll damage. If
so, the number of lines in the last scroll damage is increased,
instead of adding a new scroll damage instance to the list.
If the scroll damage list isn’t consumed, this build up of scroll
damage would eventually overflow.
And, even if it didn’t overflow, it could become large enough, that
when later used to calculate e.g. the affected surface area, while
rendering a frame, would cause an overflow there instead.
This patch fixes both issues by:
a) do an overflow check before increasing the line count
b) limit the line count to UINT16_MAX
2023-03-28 18:37:41 +02:00
|
|
|
if (likely(
|
|
|
|
|
dmg->type == damage_type &&
|
|
|
|
|
dmg->region.start == region.start &&
|
|
|
|
|
dmg->region.end == region.end))
|
2019-06-29 21:03:28 +02:00
|
|
|
{
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Make sure we don't overflow... */
|
term: protect against integer overflow when accumulating scroll damage
When accumulating scroll damage, we check if the last scroll damage’s
scrolling region, and type, matches the new/current scroll damage. If
so, the number of lines in the last scroll damage is increased,
instead of adding a new scroll damage instance to the list.
If the scroll damage list isn’t consumed, this build up of scroll
damage would eventually overflow.
And, even if it didn’t overflow, it could become large enough, that
when later used to calculate e.g. the affected surface area, while
rendering a frame, would cause an overflow there instead.
This patch fixes both issues by:
a) do an overflow check before increasing the line count
b) limit the line count to UINT16_MAX
2023-03-28 18:37:41 +02:00
|
|
|
int new_line_count = (int)dmg->lines + lines;
|
|
|
|
|
if (likely(new_line_count <= UINT16_MAX)) {
|
|
|
|
|
dmg->lines = new_line_count;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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
|
2021-12-25 19:12:51 +01:00
|
|
|
term_erase(struct terminal *term, int start_row, int start_col,
|
|
|
|
|
int end_row, int end_col)
|
2019-06-29 21:03:28 +02:00
|
|
|
{
|
2021-12-25 19:12:51 +01:00
|
|
|
xassert(start_row <= end_row);
|
|
|
|
|
xassert(start_col <= end_col || start_row < end_row);
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2021-12-25 19:12:51 +01:00
|
|
|
if (start_row == end_row) {
|
|
|
|
|
struct row *row = grid_row(term->grid, start_row);
|
|
|
|
|
erase_cell_range(term, row, start_col, end_col);
|
|
|
|
|
sixel_overwrite_by_row(term, start_row, start_col, end_col - start_col + 1);
|
2019-07-08 13:57:31 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-25 19:12:51 +01:00
|
|
|
xassert(end_row > start_row);
|
2019-07-08 13:57:31 +02:00
|
|
|
|
|
|
|
|
erase_cell_range(
|
2021-12-25 19:12:51 +01:00
|
|
|
term, grid_row(term->grid, start_row), start_col, term->cols - 1);
|
|
|
|
|
sixel_overwrite_by_row(term, start_row, start_col, term->cols - start_col);
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2021-12-25 19:12:51 +01:00
|
|
|
for (int r = start_row + 1; r < end_row; r++)
|
2019-07-08 13:57:31 +02:00
|
|
|
erase_line(term, grid_row(term->grid, r));
|
2020-06-27 15:29:47 +02:00
|
|
|
sixel_overwrite_by_rectangle(
|
2021-12-25 19:12:51 +01:00
|
|
|
term, start_row + 1, 0, end_row - start_row, term->cols);
|
2019-07-08 13:57:31 +02:00
|
|
|
|
2021-12-25 19:12:51 +01:00
|
|
|
erase_cell_range(term, grid_row(term->grid, end_row), 0, end_col);
|
|
|
|
|
sixel_overwrite_by_row(term, end_row, 0, end_col + 1);
|
2019-06-29 21:03:28 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 16:45:36 +02:00
|
|
|
void
|
|
|
|
|
term_erase_scrollback(struct terminal *term)
|
|
|
|
|
{
|
2022-04-25 20:00:14 +02:00
|
|
|
const struct grid *grid = term->grid;
|
|
|
|
|
const int num_rows = grid->num_rows;
|
2021-07-18 16:31:33 +02:00
|
|
|
const int mask = num_rows - 1;
|
|
|
|
|
|
2022-04-25 20:00:14 +02:00
|
|
|
const int start = (grid->offset + term->rows) & mask;
|
|
|
|
|
const int end = (grid->offset - 1) & mask;
|
2021-07-18 16:31:33 +02:00
|
|
|
|
2022-04-25 20:00:14 +02:00
|
|
|
const int rel_start = grid_row_abs_to_sb(grid, term->rows, start);
|
|
|
|
|
const int rel_end = grid_row_abs_to_sb(grid, term->rows, end);
|
2021-07-18 16:31:33 +02:00
|
|
|
|
2022-04-25 20:00:14 +02:00
|
|
|
const int sel_start = selection_get_start(term).row;
|
|
|
|
|
const int sel_end = selection_get_end(term).row;
|
2021-07-16 16:45:36 +02:00
|
|
|
|
|
|
|
|
if (sel_end >= 0) {
|
|
|
|
|
/*
|
|
|
|
|
* Cancel selection if it touches any of the rows in the
|
2024-02-06 12:36:45 +01:00
|
|
|
* scrollback, since we can't have the selection reference
|
2021-07-16 16:45:36 +02:00
|
|
|
* soon-to-be deleted rows.
|
|
|
|
|
*
|
|
|
|
|
* This is done by range checking the selection range against
|
|
|
|
|
* the scrollback range.
|
|
|
|
|
*
|
|
|
|
|
* To make this comparison simpler, the start/end absolute row
|
2024-02-06 12:36:45 +01:00
|
|
|
* numbers are "rebased" against the scrollback start, where
|
2021-07-16 16:45:36 +02:00
|
|
|
* row 0 is the *first* row in the scrollback. A high number
|
|
|
|
|
* thus means the row is further *down* in the scrollback,
|
|
|
|
|
* closer to the screen bottom.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-04-25 20:00:14 +02:00
|
|
|
const int rel_sel_start = grid_row_abs_to_sb(grid, term->rows, sel_start);
|
|
|
|
|
const int rel_sel_end = grid_row_abs_to_sb(grid, term->rows, sel_end);
|
2021-07-16 16:45:36 +02:00
|
|
|
|
|
|
|
|
if ((rel_sel_start <= rel_start && rel_sel_end >= rel_start) ||
|
|
|
|
|
(rel_sel_start <= rel_end && rel_sel_end >= rel_end) ||
|
|
|
|
|
(rel_sel_start >= rel_start && rel_sel_end <= rel_end))
|
|
|
|
|
{
|
|
|
|
|
selection_cancel(term);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-18 16:31:33 +02:00
|
|
|
tll_foreach(term->grid->sixel_images, it) {
|
|
|
|
|
struct sixel *six = &it->item;
|
2022-04-25 20:00:14 +02:00
|
|
|
const int six_start = grid_row_abs_to_sb(grid, term->rows, six->pos.row);
|
|
|
|
|
const int six_end = grid_row_abs_to_sb(
|
|
|
|
|
grid, term->rows, six->pos.row + six->rows - 1);
|
2021-07-18 16:31:33 +02:00
|
|
|
|
|
|
|
|
if ((six_start <= rel_start && six_end >= rel_start) ||
|
|
|
|
|
(six_start <= rel_end && six_end >= rel_end) ||
|
|
|
|
|
(six_start >= rel_start && six_end <= rel_end))
|
|
|
|
|
{
|
|
|
|
|
sixel_destroy(six);
|
|
|
|
|
tll_remove(term->grid->sixel_images, it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 16:45:36 +02:00
|
|
|
for (int i = start;; i = (i + 1) & mask) {
|
|
|
|
|
struct row *row = term->grid->rows[i];
|
|
|
|
|
if (row != NULL) {
|
|
|
|
|
if (term->render.last_cursor.row == row)
|
|
|
|
|
term->render.last_cursor.row = NULL;
|
|
|
|
|
|
|
|
|
|
grid_row_free(row);
|
|
|
|
|
term->grid->rows[i] = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i == end)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
term->grid->view = term->grid->offset;
|
|
|
|
|
term_damage_view(term);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-18 15:34:19 +02:00
|
|
|
UNITTEST
|
|
|
|
|
{
|
|
|
|
|
const int scrollback_rows = 16;
|
|
|
|
|
const int term_rows = 5;
|
|
|
|
|
const int cols = 5;
|
|
|
|
|
|
|
|
|
|
struct fdm *fdm = fdm_init();
|
|
|
|
|
xassert(fdm != NULL);
|
|
|
|
|
|
|
|
|
|
struct terminal term = {
|
|
|
|
|
.fdm = fdm,
|
|
|
|
|
.rows = term_rows,
|
|
|
|
|
.cols = cols,
|
|
|
|
|
.normal = {
|
|
|
|
|
.rows = xcalloc(scrollback_rows, sizeof(term.normal.rows[0])),
|
|
|
|
|
.num_rows = scrollback_rows,
|
|
|
|
|
.num_cols = cols,
|
|
|
|
|
},
|
|
|
|
|
.grid = &term.normal,
|
|
|
|
|
.selection = {
|
2022-04-09 15:09:02 +02:00
|
|
|
.coords = {
|
|
|
|
|
.start = {-1, -1},
|
|
|
|
|
.end = {-1, -1},
|
|
|
|
|
},
|
2021-07-18 15:34:19 +02:00
|
|
|
.kind = SELECTION_NONE,
|
|
|
|
|
.auto_scroll = {
|
|
|
|
|
.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
xassert(term.selection.auto_scroll.fd >= 0);
|
|
|
|
|
|
|
|
|
|
#define populate_scrollback() do { \
|
|
|
|
|
for (int i = 0; i < scrollback_rows; i++) { \
|
|
|
|
|
if (term.normal.rows[i] == NULL) { \
|
|
|
|
|
struct row *r = xcalloc(1, sizeof(*term.normal.rows[i])); \
|
|
|
|
|
r->cells = xcalloc(cols, sizeof(r->cells[0])); \
|
|
|
|
|
term.normal.rows[i] = r; \
|
|
|
|
|
} \
|
|
|
|
|
} \
|
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Test case 1 - no selection, just verify all rows except those
|
|
|
|
|
* on screen have been deleted.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
populate_scrollback();
|
|
|
|
|
term.normal.offset = 11;
|
|
|
|
|
term_erase_scrollback(&term);
|
|
|
|
|
for (int i = 0; i < scrollback_rows; i++) {
|
|
|
|
|
if (i >= term.normal.offset && i < term.normal.offset + term_rows)
|
|
|
|
|
xassert(term.normal.rows[i] != NULL);
|
|
|
|
|
else
|
|
|
|
|
xassert(term.normal.rows[i] == NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Test case 2 - selection that touches the scrollback. Verify the
|
|
|
|
|
* selection is cancelled.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
term.normal.offset = 14; /* Screen covers rows 14,15,0,1,2 */
|
|
|
|
|
|
|
|
|
|
/* Selection covers rows 15,0,1,2,3 */
|
2022-04-09 15:09:02 +02:00
|
|
|
term.selection.coords.start = (struct coord){.row = 15};
|
|
|
|
|
term.selection.coords.end = (struct coord){.row = 19};
|
2021-07-18 15:34:19 +02:00
|
|
|
term.selection.kind = SELECTION_CHAR_WISE;
|
|
|
|
|
|
|
|
|
|
populate_scrollback();
|
|
|
|
|
term_erase_scrollback(&term);
|
2022-04-09 15:09:02 +02:00
|
|
|
xassert(term.selection.coords.start.row < 0);
|
|
|
|
|
xassert(term.selection.coords.end.row < 0);
|
2021-07-18 15:34:19 +02:00
|
|
|
xassert(term.selection.kind == SELECTION_NONE);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Test case 3 - selection that does *not* touch the
|
|
|
|
|
* scrollback. Verify the selection is *not* cancelled.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Selection covers rows 15,0 */
|
2022-04-09 15:09:02 +02:00
|
|
|
term.selection.coords.start = (struct coord){.row = 15};
|
|
|
|
|
term.selection.coords.end = (struct coord){.row = 16};
|
2021-07-18 15:34:19 +02:00
|
|
|
term.selection.kind = SELECTION_CHAR_WISE;
|
|
|
|
|
|
|
|
|
|
populate_scrollback();
|
|
|
|
|
term_erase_scrollback(&term);
|
2022-04-09 15:09:02 +02:00
|
|
|
xassert(term.selection.coords.start.row == 15);
|
|
|
|
|
xassert(term.selection.coords.end.row == 16);
|
2021-07-18 15:34:19 +02:00
|
|
|
xassert(term.selection.kind == SELECTION_CHAR_WISE);
|
|
|
|
|
|
2022-04-09 15:09:02 +02:00
|
|
|
term.selection.coords.start = (struct coord){-1, -1};
|
|
|
|
|
term.selection.coords.end = (struct coord){-1, -1};
|
2021-07-18 16:31:33 +02:00
|
|
|
term.selection.kind = SELECTION_NONE;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Test case 4 - sixel that touch the scrollback
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
struct sixel six = {
|
|
|
|
|
.rows = 5,
|
|
|
|
|
.pos = {
|
|
|
|
|
.row = 15,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
tll_push_back(term.normal.sixel_images, six);
|
|
|
|
|
populate_scrollback();
|
|
|
|
|
term_erase_scrollback(&term);
|
|
|
|
|
xassert(tll_length(term.normal.sixel_images) == 0);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Test case 5 - sixel that does *not* touch the scrollback
|
|
|
|
|
*/
|
|
|
|
|
six.rows = 3;
|
|
|
|
|
tll_push_back(term.normal.sixel_images, six);
|
|
|
|
|
populate_scrollback();
|
|
|
|
|
term_erase_scrollback(&term);
|
|
|
|
|
xassert(tll_length(term.normal.sixel_images) == 1);
|
|
|
|
|
|
|
|
|
|
/* Cleanup */
|
|
|
|
|
tll_free(term.normal.sixel_images);
|
2021-07-18 15:34:19 +02:00
|
|
|
close(term.selection.auto_scroll.fd);
|
|
|
|
|
for (int i = 0; i < scrollback_rows; i++)
|
|
|
|
|
grid_row_free(term.normal.rows[i]);
|
|
|
|
|
free(term.normal.rows);
|
|
|
|
|
fdm_destroy(fdm);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2021-02-09 13:52:33 +00:00
|
|
|
BUG("Invalid cursor_origin value");
|
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)
|
|
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(row < term->rows);
|
|
|
|
|
xassert(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
|
|
|
}
|
|
|
|
|
|
2023-06-16 16:33:15 +02:00
|
|
|
void
|
|
|
|
|
term_cursor_col(struct terminal *term, int col)
|
|
|
|
|
{
|
|
|
|
|
xassert(col < term->cols);
|
|
|
|
|
|
|
|
|
|
term->grid->cursor.lcf = false;
|
|
|
|
|
term->grid->cursor.point.col = col;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-29 21:03:28 +02:00
|
|
|
void
|
|
|
|
|
term_cursor_left(struct terminal *term, int count)
|
|
|
|
|
{
|
2021-04-08 12:58:47 +02:00
|
|
|
int move_amount = min(term->grid->cursor.point.col, count);
|
|
|
|
|
term->grid->cursor.point.col -= move_amount;
|
|
|
|
|
xassert(term->grid->cursor.point.col >= 0);
|
2020-04-16 18:51:14 +02:00
|
|
|
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;
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->grid->cursor.point.col < term->cols);
|
2020-04-16 18:51:14 +02:00
|
|
|
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;
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(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;
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(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
|
2020-11-26 18:08:28 +01:00
|
|
|
cursor_blink_rearm_timer(struct terminal *term)
|
2019-12-15 15:07:56 +01:00
|
|
|
{
|
2020-10-13 19:23:04 +02:00
|
|
|
if (term->cursor_blink.fd < 0) {
|
|
|
|
|
int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
LOG_ERRNO("failed to create cursor blink timer FD");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fdm_add(term->fdm, fd, EPOLLIN, &fdm_cursor_blink, term)) {
|
|
|
|
|
close(fd);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
term->cursor_blink.fd = fd;
|
|
|
|
|
}
|
|
|
|
|
|
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");
|
2020-10-13 19:23:04 +02:00
|
|
|
fdm_del(term->fdm, term->cursor_blink.fd);
|
|
|
|
|
term->cursor_blink.fd = -1;
|
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
|
2020-11-26 18:08:28 +01:00
|
|
|
cursor_blink_disarm_timer(struct terminal *term)
|
2019-12-16 21:31:40 +01:00
|
|
|
{
|
2020-10-13 19:23:04 +02:00
|
|
|
fdm_del(term->fdm, term->cursor_blink.fd);
|
|
|
|
|
term->cursor_blink.fd = -1;
|
|
|
|
|
return true;
|
2019-12-16 21:31:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2020-11-26 18:08:28 +01:00
|
|
|
term_cursor_blink_update(struct terminal *term)
|
2019-12-16 21:31:40 +01:00
|
|
|
{
|
2020-11-26 18:08:28 +01:00
|
|
|
bool enable = term->cursor_blink.decset || term->cursor_blink.deccsusr;
|
2021-08-17 18:31:42 +02:00
|
|
|
bool activate = !term->shutdown.in_progress && enable && term->visual_focus;
|
2019-12-15 15:07:56 +01:00
|
|
|
|
2020-11-26 18:08:28 +01:00
|
|
|
LOG_DBG("decset=%d, deccsrusr=%d, focus=%d, shutting-down=%d, enable=%d, activate=%d",
|
|
|
|
|
term->cursor_blink.decset, term->cursor_blink.deccsusr,
|
2021-08-17 18:31:42 +02:00
|
|
|
term->visual_focus, term->shutdown.in_progress,
|
2020-11-26 18:08:28 +01:00
|
|
|
enable, activate);
|
2019-12-16 21:31:40 +01:00
|
|
|
|
2020-11-26 18:08:28 +01:00
|
|
|
if (activate && term->cursor_blink.fd < 0) {
|
2019-12-19 07:27:41 +01:00
|
|
|
term->cursor_blink.state = CURSOR_BLINK_ON;
|
2020-11-26 18:08:28 +01:00
|
|
|
cursor_blink_rearm_timer(term);
|
|
|
|
|
} else if (!activate && term->cursor_blink.fd >= 0)
|
|
|
|
|
cursor_blink_disarm_timer(term);
|
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-19 18:47:38 +02:00
|
|
|
/* Verify scroll amount has been clamped */
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(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 */
|
2022-04-09 15:09:02 +02:00
|
|
|
if (unlikely(term->selection.coords.end.row >= 0)) {
|
2020-05-19 18:49:42 +02:00
|
|
|
/*
|
|
|
|
|
* Selection is (partly) inside either the top or bottom
|
|
|
|
|
* scrolling regions, or on (at least one) of the lines
|
2023-10-03 14:11:55 +02:00
|
|
|
* scrolled in (i.e. reused lines).
|
2020-05-19 18:49:42 +02:00
|
|
|
*/
|
|
|
|
|
if (selection_on_top_region(term, region) ||
|
2022-07-28 18:27:13 +02:00
|
|
|
selection_on_bottom_region(term, region))
|
2020-05-19 18:49:42 +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
|
|
|
selection_cancel(term);
|
2022-07-28 18:27:13 +02:00
|
|
|
} else
|
|
|
|
|
selection_scroll_up(term, rows);
|
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
|
|
|
}
|
2020-05-16 21:27:56 +02:00
|
|
|
|
2020-06-29 22:01:02 +02:00
|
|
|
sixel_scroll_up(term, rows);
|
|
|
|
|
|
2022-05-11 17:58:18 +02:00
|
|
|
/* How many lines from the scrollback start is the current viewport? */
|
|
|
|
|
int view_sb_start_distance = grid_row_abs_to_sb(
|
|
|
|
|
term->grid, term->rows, term->grid->view);
|
|
|
|
|
|
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
|
|
|
|
2022-05-11 17:58:18 +02:00
|
|
|
if (likely(view_follows)) {
|
term: scroll: only record scroll damage when viewport is at the bottom
We don’t need to record scroll damage if the viewport isn’t at the
bottom, since in this case, the renderer ignores the scroll damage
anyway.
This fixes a performance corner case, when the viewport is at the top
of the scrollback history.
When application scrolls the terminal contents, and the scrollback
history is full, and the viewport is at top of the history, then the
viewport needs to be moved (the scrollback history is a circular
buffer, and thus the top of the history “moves” when we’re scrolling
in new contents).
Moving the viewport typically results in another type of scroll
damage (DAMAGE_SCROLL_IN_VIEW, instead of the “normal” DAMAGE_SCROLL).
Thus, each application triggered scroll, will result in two scroll
damage records: one DAMAGE_SCROLL, and one
DAMAGE_SCROLL_IN_VIEW. These two are incompatible, meaning they can’t
be merged. What’s worse, it also means the DAMAGE_SCROLL records from
two application triggered scrolls cannot be merged (since there’s a
DAMAGE_SCROLL_IN_VIEW in between).
As a result, the renderer will not see one, or “a few” scroll damage
events, but a *ton*. _Each_ one typically a single line, or so. And
each one resulting in lots of traffic on the wayland socket, as we
create and destroy new buffer pools, when doing “shm scrolling”.
This eventually leads to the socket not being able to keep up, and the
socket is closed on us, forcing us to exit.
The fix is really simple: don’t record “normal” scroll damage when
scrolling, _unless_ the viewport is at the bottom (and thus “follows”
the application output).
As soon as the user scrolls up in the history, we’ll stop emitting
normal scroll damage records. This is just fine, since, as mentioned
above, the renderer ignores them when the viewport isn’t at the
bottom.
What if the viewport is moved back down again, before the next frame
has been rendered? Wont there be “missing” scroll damage records? No,
because moving the viewport results in scroll damage records by
itself.
Closes #1380
2023-06-23 20:20:01 +02:00
|
|
|
term_damage_scroll(term, DAMAGE_SCROLL, region, rows);
|
2020-05-19 18:49:42 +02:00
|
|
|
selection_view_down(term, term->grid->offset);
|
2019-07-09 16:26:36 +02:00
|
|
|
term->grid->view = term->grid->offset;
|
2022-05-11 17:58:18 +02:00
|
|
|
} else if (unlikely(rows > view_sb_start_distance)) {
|
|
|
|
|
/* Part of current view is being scrolled out */
|
|
|
|
|
int new_view = grid_row_sb_to_abs(term->grid, term->rows, 0);
|
|
|
|
|
selection_view_down(term, new_view);
|
|
|
|
|
cmd_scrollback_down(term, rows - view_sb_start_distance);
|
2020-05-19 18:49:42 +02:00
|
|
|
}
|
2019-07-09 16:26:36 +02:00
|
|
|
|
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 */
|
2021-02-13 12:31:55 +01:00
|
|
|
for (int r = region.end - rows; r < region.end; r++) {
|
|
|
|
|
struct row *row = grid_row_and_alloc(term->grid, r);
|
|
|
|
|
erase_line(term, row);
|
|
|
|
|
}
|
2020-05-16 16:28:32 +02:00
|
|
|
|
term: scroll: only record scroll damage when viewport is at the bottom
We don’t need to record scroll damage if the viewport isn’t at the
bottom, since in this case, the renderer ignores the scroll damage
anyway.
This fixes a performance corner case, when the viewport is at the top
of the scrollback history.
When application scrolls the terminal contents, and the scrollback
history is full, and the viewport is at top of the history, then the
viewport needs to be moved (the scrollback history is a circular
buffer, and thus the top of the history “moves” when we’re scrolling
in new contents).
Moving the viewport typically results in another type of scroll
damage (DAMAGE_SCROLL_IN_VIEW, instead of the “normal” DAMAGE_SCROLL).
Thus, each application triggered scroll, will result in two scroll
damage records: one DAMAGE_SCROLL, and one
DAMAGE_SCROLL_IN_VIEW. These two are incompatible, meaning they can’t
be merged. What’s worse, it also means the DAMAGE_SCROLL records from
two application triggered scrolls cannot be merged (since there’s a
DAMAGE_SCROLL_IN_VIEW in between).
As a result, the renderer will not see one, or “a few” scroll damage
events, but a *ton*. _Each_ one typically a single line, or so. And
each one resulting in lots of traffic on the wayland socket, as we
create and destroy new buffer pools, when doing “shm scrolling”.
This eventually leads to the socket not being able to keep up, and the
socket is closed on us, forcing us to exit.
The fix is really simple: don’t record “normal” scroll damage when
scrolling, _unless_ the viewport is at the bottom (and thus “follows”
the application output).
As soon as the user scrolls up in the history, we’ll stop emitting
normal scroll damage records. This is just fine, since, as mentioned
above, the renderer ignores them when the viewport isn’t at the
bottom.
What if the viewport is moved back down again, before the next frame
has been rendered? Wont there be “missing” scroll damage records? No,
because moving the viewport results in scroll damage records by
itself.
Closes #1380
2023-06-23 20:20:01 +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++)
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(grid_row(term->grid, r) != NULL);
|
2020-05-16 23:53:23 +02:00
|
|
|
#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-19 18:47:38 +02:00
|
|
|
/* Verify scroll amount has been clamped */
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(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 */
|
2022-04-09 15:09:02 +02:00
|
|
|
if (unlikely(term->selection.coords.end.row >= 0)) {
|
2020-05-19 18:49:42 +02:00
|
|
|
/*
|
|
|
|
|
* Selection is (partly) inside either the top or bottom
|
|
|
|
|
* scrolling regions, or on (at least one) of the lines
|
2023-10-03 14:11:55 +02:00
|
|
|
* scrolled in (i.e. reused lines).
|
2020-05-19 18:49:42 +02:00
|
|
|
*/
|
|
|
|
|
if (selection_on_top_region(term, region) ||
|
2022-07-28 18:27:13 +02:00
|
|
|
selection_on_bottom_region(term, region))
|
2020-05-19 18:49:42 +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
|
|
|
selection_cancel(term);
|
2022-07-28 18:27:13 +02:00
|
|
|
} else
|
|
|
|
|
selection_scroll_down(term, rows);
|
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
|
|
|
}
|
2020-05-16 21:27:56 +02:00
|
|
|
|
2022-10-14 18:00:48 +02:00
|
|
|
/* Unallocate scrolled out lines */
|
|
|
|
|
for (int r = region.end - rows; r < region.end; r++) {
|
|
|
|
|
const int abs_r = grid_row_absolute(term->grid, r);
|
|
|
|
|
struct row *row = term->grid->rows[abs_r];
|
|
|
|
|
|
|
|
|
|
grid_row_free(row);
|
|
|
|
|
term->grid->rows[abs_r] = NULL;
|
|
|
|
|
|
|
|
|
|
if (term->render.last_cursor.row == row)
|
|
|
|
|
term->render.last_cursor.row = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-29 22:01:02 +02:00
|
|
|
sixel_scroll_down(term, rows);
|
|
|
|
|
|
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;
|
2022-05-11 17:58:18 +02:00
|
|
|
term->grid->offset += term->grid->num_rows;
|
2019-08-22 17:33:23 +02:00
|
|
|
term->grid->offset &= term->grid->num_rows - 1;
|
|
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(term->grid->offset >= 0);
|
|
|
|
|
xassert(term->grid->offset < term->grid->num_rows);
|
2019-06-29 21:03:28 +02:00
|
|
|
|
2020-05-19 18:49:42 +02:00
|
|
|
if (view_follows) {
|
term: scroll: only record scroll damage when viewport is at the bottom
We don’t need to record scroll damage if the viewport isn’t at the
bottom, since in this case, the renderer ignores the scroll damage
anyway.
This fixes a performance corner case, when the viewport is at the top
of the scrollback history.
When application scrolls the terminal contents, and the scrollback
history is full, and the viewport is at top of the history, then the
viewport needs to be moved (the scrollback history is a circular
buffer, and thus the top of the history “moves” when we’re scrolling
in new contents).
Moving the viewport typically results in another type of scroll
damage (DAMAGE_SCROLL_IN_VIEW, instead of the “normal” DAMAGE_SCROLL).
Thus, each application triggered scroll, will result in two scroll
damage records: one DAMAGE_SCROLL, and one
DAMAGE_SCROLL_IN_VIEW. These two are incompatible, meaning they can’t
be merged. What’s worse, it also means the DAMAGE_SCROLL records from
two application triggered scrolls cannot be merged (since there’s a
DAMAGE_SCROLL_IN_VIEW in between).
As a result, the renderer will not see one, or “a few” scroll damage
events, but a *ton*. _Each_ one typically a single line, or so. And
each one resulting in lots of traffic on the wayland socket, as we
create and destroy new buffer pools, when doing “shm scrolling”.
This eventually leads to the socket not being able to keep up, and the
socket is closed on us, forcing us to exit.
The fix is really simple: don’t record “normal” scroll damage when
scrolling, _unless_ the viewport is at the bottom (and thus “follows”
the application output).
As soon as the user scrolls up in the history, we’ll stop emitting
normal scroll damage records. This is just fine, since, as mentioned
above, the renderer ignores them when the viewport isn’t at the
bottom.
What if the viewport is moved back down again, before the next frame
has been rendered? Wont there be “missing” scroll damage records? No,
because moving the viewport results in scroll damage records by
itself.
Closes #1380
2023-06-23 20:20:01 +02:00
|
|
|
term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows);
|
2020-05-19 18:49:42 +02:00
|
|
|
selection_view_up(term, term->grid->offset);
|
2019-07-09 16:26:36 +02:00
|
|
|
term->grid->view = term->grid->offset;
|
2020-05-19 18:49:42 +02:00
|
|
|
}
|
2019-07-09 16:26:36 +02:00
|
|
|
|
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 */
|
2021-02-13 12:31:55 +01:00
|
|
|
for (int r = region.start; r < region.start + rows; r++) {
|
|
|
|
|
struct row *row = grid_row_and_alloc(term->grid, r);
|
|
|
|
|
erase_line(term, row);
|
|
|
|
|
}
|
2020-05-16 16:28:32 +02:00
|
|
|
|
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++)
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(grid_row(term->grid, r) != NULL);
|
2020-05-16 23:53:23 +02:00
|
|
|
#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
|
2020-07-14 09:29:10 +02:00
|
|
|
term_carriage_return(struct terminal *term)
|
2020-02-10 21:52:14 +01:00
|
|
|
{
|
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-07-14 10:49:44 +02:00
|
|
|
term->grid->cursor.lcf = false;
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-15 17:08:30 +01:00
|
|
|
void
|
|
|
|
|
term_save_cursor(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
term->grid->saved_cursor = term->grid->cursor;
|
|
|
|
|
term->vt.saved_attrs = term->vt.attrs;
|
|
|
|
|
term->saved_charsets = term->charsets;
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
2021-01-15 17:08:30 +01:00
|
|
|
|
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;
|
2021-01-15 17:08:30 +01:00
|
|
|
|
|
|
|
|
term->vt.attrs = term->vt.saved_attrs;
|
|
|
|
|
term->charsets = term->saved_charsets;
|
2021-03-14 19:19:10 +01:00
|
|
|
term_update_ascii_printer(term);
|
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;
|
2020-11-26 18:08:28 +01:00
|
|
|
term_cursor_blink_update(term);
|
2020-03-06 19:16:54 +01:00
|
|
|
render_refresh_csd(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;
|
2020-11-26 18:08:28 +01:00
|
|
|
term_cursor_blink_update(term);
|
2020-03-06 19:16:54 +01:00
|
|
|
render_refresh_csd(term);
|
2020-01-02 19:35:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_kbd_focus_in(struct terminal *term)
|
|
|
|
|
{
|
2020-07-11 09:04:46 +02:00
|
|
|
if (term->kbd_focus)
|
2020-07-09 09:16:54 +02:00
|
|
|
return;
|
2020-07-09 08:46:25 +02:00
|
|
|
|
2020-07-11 09:04:46 +02:00
|
|
|
term->kbd_focus = true;
|
2020-10-08 19:55:32 +02:00
|
|
|
|
|
|
|
|
if (term->render.urgency) {
|
|
|
|
|
term->render.urgency = false;
|
|
|
|
|
term_damage_margins(term);
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-11 11:14:32 +02:00
|
|
|
cursor_refresh(term);
|
2020-07-11 09:04:46 +02:00
|
|
|
|
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)
|
|
|
|
|
{
|
2020-07-11 09:04:46 +02:00
|
|
|
if (!term->kbd_focus)
|
2020-07-09 09:16:54 +02:00
|
|
|
return;
|
2020-07-09 08:46:25 +02:00
|
|
|
|
2020-07-11 09:04:46 +02:00
|
|
|
tll_foreach(term->wl->seats, it)
|
|
|
|
|
if (it->item.kbd_focus == term)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-12-03 18:36:56 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
if (term_ime_reset(term))
|
2020-12-02 18:52:50 +01:00
|
|
|
render_refresh(term);
|
2020-12-03 18:36:56 +01:00
|
|
|
#endif
|
2020-12-02 18:52:50 +01:00
|
|
|
|
2020-07-11 09:04:46 +02:00
|
|
|
term->kbd_focus = false;
|
2020-07-11 11:14:32 +02:00
|
|
|
cursor_refresh(term);
|
2020-07-11 09:04:46 +02:00
|
|
|
|
2020-01-02 19:35:32 +01:00
|
|
|
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) {
|
2021-08-05 18:34:09 +02:00
|
|
|
case BTN_LEFT: return 1;
|
|
|
|
|
case BTN_MIDDLE: return 2;
|
|
|
|
|
case BTN_RIGHT: return 3;
|
|
|
|
|
case BTN_BACK: return 4;
|
|
|
|
|
case BTN_FORWARD: return 5;
|
|
|
|
|
case BTN_WHEEL_LEFT: return 6; /* Foot custom define */
|
|
|
|
|
case BTN_WHEEL_RIGHT: return 7; /* Foot custom define */
|
|
|
|
|
case BTN_SIDE: return 8;
|
|
|
|
|
case BTN_EXTRA: return 9;
|
|
|
|
|
case BTN_TASK: return -1; /* TODO: ??? */
|
2019-07-05 14:24:51 +02:00
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
LOG_WARN("unrecognized mouse button: %d (0x%x)", button, button);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-08 18:16:43 +02:00
|
|
|
|
2019-07-05 14:24:51 +02:00
|
|
|
static int
|
|
|
|
|
encode_xbutton(int xbutton)
|
|
|
|
|
{
|
|
|
|
|
switch (xbutton) {
|
|
|
|
|
case 1: case 2: case 3:
|
|
|
|
|
return xbutton - 1;
|
|
|
|
|
|
2021-01-22 17:03:43 +01:00
|
|
|
case 4: case 5: case 6: case 7:
|
2019-07-05 14:24:51 +02:00
|
|
|
/* Like button 1 and 2, but with 64 added */
|
|
|
|
|
return xbutton - 4 + 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-08 18:16:43 +02:00
|
|
|
|
2019-07-05 14:24:51 +02:00
|
|
|
static void
|
2019-07-05 15:13:06 +02:00
|
|
|
report_mouse_click(struct terminal *term, int encoded_button, int row, int col,
|
2021-12-30 05:13:45 -06:00
|
|
|
int row_pixels, int col_pixels, bool release)
|
2019-07-05 15:13:06 +02:00
|
|
|
{
|
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;
|
|
|
|
|
|
2021-12-30 05:13:45 -06:00
|
|
|
case MOUSE_SGR_PIXELS:
|
|
|
|
|
snprintf(response, sizeof(response), "\033[<%d;%d;%d%c",
|
|
|
|
|
encoded_button, col_pixels + 1, row_pixels + 1, release ? 'm' : 'M');
|
|
|
|
|
break;
|
|
|
|
|
|
2019-07-05 15:29:16 +02:00
|
|
|
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
|
|
|
}
|
2020-07-08 18:16:43 +02:00
|
|
|
|
2019-07-05 15:13:06 +02:00
|
|
|
static void
|
2021-12-30 05:13:45 -06:00
|
|
|
report_mouse_motion(struct terminal *term, int encoded_button, int row, int col, int row_pixels, int col_pixels)
|
2019-07-05 15:13:06 +02:00
|
|
|
{
|
2021-12-30 05:13:45 -06:00
|
|
|
report_mouse_click(term, encoded_button, row, col, row_pixels, col_pixels, false);
|
2019-07-05 15:13:06 +02:00
|
|
|
}
|
2020-07-08 18:16:43 +02:00
|
|
|
|
2019-11-30 17:06:15 +01:00
|
|
|
bool
|
2021-12-24 15:21:37 +01:00
|
|
|
term_mouse_grabbed(const struct terminal *term, const struct seat *seat)
|
2019-11-30 17:06:15 +01:00
|
|
|
{
|
|
|
|
|
/*
|
2021-12-24 15:21:37 +01:00
|
|
|
* Mouse is grabbed by us, regardless of whether mouse tracking
|
|
|
|
|
* has been enabled or not.
|
2019-11-30 17:06:15 +01:00
|
|
|
*/
|
2021-09-27 19:09:07 +00:00
|
|
|
|
|
|
|
|
xkb_mod_mask_t mods;
|
2024-02-06 10:41:01 +01:00
|
|
|
get_current_modifiers(seat, &mods, NULL, 0, true);
|
2021-09-27 19:09:07 +00:00
|
|
|
|
2022-04-19 17:24:25 +02:00
|
|
|
const struct key_binding_set *bindings =
|
terminal: don’t unref a not-yet-referenced key-binding set
Key-binding sets are bound to a seat/configuration pair. The conf
reference is done when a new terminal instance is created.
When that same terminal instance is destroyed, the key binding set is
unref:ed.
If the terminal instance is destroyed *before* the key binding set has
been referenced, we’ll still unref it. This creates an imbalance.
In particular, when the there is exactly one other terminal instance
referencing that same key binding set, that terminal instance will
trigger a foot server crash as soon as it receives a key press/release
event. This happens because the next-to-last terminal instance brought
the reference count of the binding set down to 0, causing it to be
free:d.
Thus, we *must* reference the binding set *before* we can error
out (when instantiating a new terminal instance).
At this point, we don’t yet have a valid terminal instance. But,
that’s ok, because all the key_binding_new_for_term() did with the
terminal instance was get the "struct wayland" and "struct config"
pointers. So, rename the function and simply pass these pointers
explicitly.
Similarly, change key_binding_for() to take a "struct config" pointer,
rather than a "struct terminal" pointer.
Also rename key_binding_unref_term() -> key_binding_unref().
2022-09-05 19:23:40 +02:00
|
|
|
key_binding_for(term->wl->key_binding_manager, term->conf, seat);
|
2022-04-19 17:24:25 +02:00
|
|
|
const xkb_mod_mask_t override_modmask = bindings->selection_overrides;
|
2021-09-27 19:09:07 +00:00
|
|
|
bool override_mods_pressed = (mods & override_modmask) == override_modmask;
|
|
|
|
|
|
2021-02-02 09:51:22 +01:00
|
|
|
return term->mouse_tracking == MOUSE_NONE ||
|
2021-09-27 19:09:07 +00:00
|
|
|
(seat->kbd_focus == term && override_mods_pressed);
|
2019-11-30 17:06:15 +01:00
|
|
|
}
|
|
|
|
|
|
2019-07-05 15:13:06 +02:00
|
|
|
void
|
2020-07-08 18:16:43 +02:00
|
|
|
term_mouse_down(struct terminal *term, int button, int row, int col,
|
2021-12-30 05:13:45 -06:00
|
|
|
int row_pixels, int col_pixels,
|
2020-07-08 18:16:43 +02:00
|
|
|
bool _shift, bool _alt, bool _ctrl)
|
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-07-11 09:06:20 +02:00
|
|
|
bool has_focus = term->kbd_focus;
|
2020-07-08 18:16:43 +02:00
|
|
|
bool shift = has_focus ? _shift : false;
|
|
|
|
|
bool alt = has_focus ? _alt : false;
|
|
|
|
|
bool ctrl = has_focus ? _ctrl : false;
|
2019-11-30 17:11:00 +01:00
|
|
|
|
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:
|
2021-12-30 05:13:45 -06:00
|
|
|
report_mouse_click(term, encoded, row, col, row_pixels, col_pixels, false);
|
2019-07-05 14:24:51 +02:00
|
|
|
break;
|
2019-11-18 11:18:48 +01:00
|
|
|
|
|
|
|
|
case MOUSE_X10:
|
|
|
|
|
/* Never enabled */
|
2021-02-09 13:52:33 +00:00
|
|
|
BUG("X10 mouse mode not implemented");
|
2019-11-18 11:18:48 +01:00
|
|
|
break;
|
2019-07-05 14:24:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2020-07-08 18:16:43 +02:00
|
|
|
term_mouse_up(struct terminal *term, int button, int row, int col,
|
2021-12-30 05:13:45 -06:00
|
|
|
int row_pixels, int col_pixels,
|
2020-07-08 18:16:43 +02:00
|
|
|
bool _shift, bool _alt, bool _ctrl)
|
2019-07-05 14:24:51 +02:00
|
|
|
{
|
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) {
|
2021-08-05 18:34:09 +02:00
|
|
|
/* No release events for vertical scroll wheel buttons */
|
2019-07-05 15:13:06 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 15:29:16 +02:00
|
|
|
int encoded = encode_xbutton(xbutton);
|
|
|
|
|
if (encoded == -1)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-07-11 09:06:20 +02:00
|
|
|
bool has_focus = term->kbd_focus;
|
2020-07-08 18:16:43 +02:00
|
|
|
bool shift = has_focus ? _shift : false;
|
|
|
|
|
bool alt = has_focus ? _alt : false;
|
|
|
|
|
bool ctrl = has_focus ? _ctrl : false;
|
2019-11-30 17:11:00 +01:00
|
|
|
|
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:
|
2021-12-30 05:13:45 -06:00
|
|
|
report_mouse_click(term, encoded, row, col, row_pixels, col_pixels, true);
|
2019-07-05 15:13:06 +02:00
|
|
|
break;
|
2019-11-18 11:18:48 +01:00
|
|
|
|
|
|
|
|
case MOUSE_X10:
|
|
|
|
|
/* Never enabled */
|
2021-02-09 13:52:33 +00:00
|
|
|
BUG("X10 mouse mode not implemented");
|
2019-11-18 11:18:48 +01:00
|
|
|
break;
|
2019-07-05 15:13:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
2020-07-08 18:16:43 +02:00
|
|
|
term_mouse_motion(struct terminal *term, int button, int row, int col,
|
2021-12-30 05:13:45 -06:00
|
|
|
int row_pixels, int col_pixels,
|
2020-07-08 18:16:43 +02:00
|
|
|
bool _shift, bool _alt, bool _ctrl)
|
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-07-11 09:06:20 +02:00
|
|
|
bool has_focus = term->kbd_focus;
|
2020-07-08 18:16:43 +02:00
|
|
|
bool shift = has_focus ? _shift : false;
|
|
|
|
|
bool alt = has_focus ? _alt : false;
|
|
|
|
|
bool ctrl = has_focus ? _ctrl : false;
|
2019-11-30 17:11:00 +01:00
|
|
|
|
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:
|
2021-12-30 05:13:45 -06:00
|
|
|
report_mouse_motion(term, encoded, row, col, row_pixels, col_pixels);
|
2019-07-05 14:24:51 +02:00
|
|
|
break;
|
2019-11-18 11:18:48 +01:00
|
|
|
|
|
|
|
|
case MOUSE_X10:
|
|
|
|
|
/* Never enabled */
|
2021-02-09 13:52:33 +00:00
|
|
|
BUG("X10 mouse mode not implemented");
|
2019-11-18 11:18:48 +01:00
|
|
|
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
|
2020-07-31 17:09:06 +02:00
|
|
|
term_xcursor_update_for_seat(struct terminal *term, struct seat *seat)
|
2019-11-28 19:35:47 +01:00
|
|
|
{
|
2023-06-27 16:57:33 +02:00
|
|
|
enum cursor_shape shape = CURSOR_SHAPE_NONE;
|
2021-11-19 15:02:48 +01:00
|
|
|
|
|
|
|
|
switch (term->active_surface) {
|
2023-06-27 18:40:44 +02:00
|
|
|
case TERM_SURF_GRID:
|
|
|
|
|
if (seat->pointer.hidden)
|
|
|
|
|
shape = CURSOR_SHAPE_HIDDEN;
|
|
|
|
|
|
2023-07-31 16:33:16 +02:00
|
|
|
else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 ||
|
|
|
|
|
render_xcursor_is_valid(seat, term->mouse_user_cursor))
|
2023-06-28 13:25:08 +02:00
|
|
|
{
|
2023-06-27 18:40:44 +02:00
|
|
|
shape = CURSOR_SHAPE_CUSTOM;
|
2023-06-28 13:25:08 +02:00
|
|
|
}
|
2023-06-27 18:40:44 +02:00
|
|
|
|
|
|
|
|
else if (seat->mouse.col >= 0 &&
|
|
|
|
|
seat->mouse.row >= 0 &&
|
|
|
|
|
term_mouse_grabbed(term, seat))
|
|
|
|
|
{
|
|
|
|
|
shape = CURSOR_SHAPE_TEXT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
shape = CURSOR_SHAPE_LEFT_PTR;
|
2021-11-19 15:02:48 +01:00
|
|
|
break;
|
2023-06-27 18:40:44 +02:00
|
|
|
|
2021-11-19 15:02:48 +01:00
|
|
|
case TERM_SURF_TITLE:
|
|
|
|
|
case TERM_SURF_BUTTON_MINIMIZE:
|
|
|
|
|
case TERM_SURF_BUTTON_MAXIMIZE:
|
|
|
|
|
case TERM_SURF_BUTTON_CLOSE:
|
2023-06-27 16:57:33 +02:00
|
|
|
shape = CURSOR_SHAPE_LEFT_PTR;
|
2021-11-19 15:02:48 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_BORDER_LEFT:
|
|
|
|
|
case TERM_SURF_BORDER_RIGHT:
|
|
|
|
|
case TERM_SURF_BORDER_TOP:
|
|
|
|
|
case TERM_SURF_BORDER_BOTTOM:
|
2023-06-27 16:57:33 +02:00
|
|
|
shape = xcursor_for_csd_border(term, seat->mouse.x, seat->mouse.y);
|
2021-11-19 15:02:48 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case TERM_SURF_NONE:
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-11-28 19:35:47 +01:00
|
|
|
|
2023-06-27 16:57:33 +02:00
|
|
|
if (shape == CURSOR_SHAPE_NONE)
|
2021-11-30 22:25:32 +01:00
|
|
|
BUG("xcursor not set");
|
|
|
|
|
|
2023-06-27 16:57:33 +02:00
|
|
|
render_xcursor_set(seat, term, shape);
|
2020-07-31 17:09:06 +02:00
|
|
|
}
|
2020-07-09 09:52:11 +02:00
|
|
|
|
2020-07-31 17:09:06 +02:00
|
|
|
void
|
|
|
|
|
term_xcursor_update(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
tll_foreach(term->wl->seats, it)
|
|
|
|
|
term_xcursor_update_for_seat(term, &it->item);
|
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)
|
|
|
|
|
{
|
2021-07-04 17:59:40 +02:00
|
|
|
if (term->conf->locked_title && term->window_title_has_been_set)
|
|
|
|
|
return;
|
|
|
|
|
|
2024-01-24 23:17:28 +00:00
|
|
|
if (term->window_title != NULL && streq(term->window_title, title))
|
2021-10-22 18:01:53 +02:00
|
|
|
return;
|
|
|
|
|
|
2024-02-06 13:08:43 +01:00
|
|
|
if (mbsntoc32(NULL, title, strlen(title), 0) == (char32_t)-1) {
|
|
|
|
|
/* It's an xdg_toplevel::set_title() protocol violation to set
|
|
|
|
|
a title with an invalid UTF-8 sequence */
|
|
|
|
|
LOG_WARN("%s: title is not valid UTF-8, ignoring", title);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-21 17:35:53 +02:00
|
|
|
free(term->window_title);
|
2020-08-08 20:34:30 +01:00
|
|
|
term->window_title = xstrdup(title);
|
2020-03-25 18:23:55 +01:00
|
|
|
render_refresh_title(term);
|
2021-07-04 17:59:40 +02:00
|
|
|
term->window_title_has_been_set = true;
|
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
|
|
|
|
2020-10-08 19:55:32 +02:00
|
|
|
void
|
|
|
|
|
term_bell(struct terminal *term)
|
|
|
|
|
{
|
2023-10-07 19:37:04 +02:00
|
|
|
|
2021-04-29 04:12:55 -05:00
|
|
|
if (!term->bell_action_enabled)
|
2020-10-08 19:55:32 +02:00
|
|
|
return;
|
|
|
|
|
|
2021-10-22 20:04:23 +02:00
|
|
|
if (term->conf->bell.urgent && !term->kbd_focus) {
|
|
|
|
|
if (!wayl_win_set_urgent(term->window)) {
|
|
|
|
|
/*
|
|
|
|
|
* Urgency (xdg-activation) is relatively new in
|
2024-02-06 12:36:45 +01:00
|
|
|
* Wayland. Fallback to our old, "faked", urgency -
|
2021-10-22 20:04:23 +02:00
|
|
|
* rendering our window margins in red
|
|
|
|
|
*/
|
|
|
|
|
term->render.urgency = true;
|
|
|
|
|
term_damage_margins(term);
|
2021-04-29 04:12:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-10 18:22:48 +01:00
|
|
|
|
2021-10-05 23:07:01 +02:00
|
|
|
if (term->conf->bell.notify)
|
|
|
|
|
notify_notify(term, "Bell", "Bell in terminal");
|
|
|
|
|
|
2023-10-07 19:37:04 +02:00
|
|
|
if (term->conf->bell.flash)
|
|
|
|
|
term_flash(term, 100);
|
|
|
|
|
|
2021-06-18 16:18:41 +02:00
|
|
|
if ((term->conf->bell.command.argv.args != NULL) &&
|
2021-05-09 12:13:14 +02:00
|
|
|
(!term->kbd_focus || term->conf->bell.command_focused))
|
|
|
|
|
{
|
2021-04-29 04:12:55 -05:00
|
|
|
int devnull = open("/dev/null", O_RDONLY);
|
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.
Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.
Re-write wayl_win_set_urgent() to use the new helper function.
Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).
Closes #1058
2022-05-03 19:37:04 +02:00
|
|
|
spawn(term->reaper, NULL, term->conf->bell.command.argv.args,
|
|
|
|
|
devnull, -1, -1, NULL);
|
2020-12-10 18:22:48 +01:00
|
|
|
|
2021-04-29 04:12:55 -05:00
|
|
|
if (devnull >= 0)
|
|
|
|
|
close(devnull);
|
2020-12-10 18:22:48 +01:00
|
|
|
}
|
2020-10-08 19:55:32 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-21 15:27:17 +01:00
|
|
|
bool
|
|
|
|
|
term_spawn_new(const struct terminal *term)
|
|
|
|
|
{
|
2020-07-15 12:39:10 +02:00
|
|
|
return spawn(
|
2020-07-15 13:33:56 +02:00
|
|
|
term->reaper, term->cwd, (char *const []){term->foot_exe, NULL},
|
url-mode: add support for XDG activation when opening URLs
First, add a ‘token’ argument to spawn(). When non-NULL, spawn() will
set the ‘XDG_ACTIVATION_TOKEN’ environment variable in the forked
process. If DISPLAY is non-NULL, we also set DESKTOP_STARTUP_ID, for
compatibility with X11 applications. Note that failing to set either
of these environment variables are considered non-fatal - i.e. we
ignore failures.
Next, add a helper function, wayl_get_activation_token(), to generate
an XDG activation token, and call a user-provided callback when it’s
‘done (since token generation is asynchronous). This function takes an
optional ‘seat’ and ‘serial’ arguments - when both are non-NULL/zero,
we set the serial on the token. ‘win’ is a required argument, used to
set the surface on the token.
Re-write wayl_win_set_urgent() to use the new helper function.
Finally, rewrite activate_url() to first try to get an activation
token (and spawn the URL launcher in the token callback). If that
fails, or if we don’t have XDG activation support, spawn the URL
launcher immediately (like before this patch).
Closes #1058
2022-05-03 19:37:04 +02:00
|
|
|
-1, -1, -1, NULL);
|
2019-12-21 15:27:17 +01:00
|
|
|
}
|
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-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
|
|
|
|
2020-12-14 19:05:03 +01:00
|
|
|
/* Disable pending refresh *iff* the grid is the *only* thing
|
|
|
|
|
* scheduled to be re-rendered */
|
|
|
|
|
if (!term->render.refresh.csd && !term->render.refresh.search &&
|
render: use a timer instead of relying on the frame callback for title update throttling
Using the frame callback works most of the time, but e.g. Sway doesn’t
call it while the window is hidden, and thus prevents us from updating
the title in e.g. stacked views.
This patch uses a timer FD instead. We store a timestamp from when the
title was last updated. When the application wants to update the
title, we first check if we already have a timer running, and if so,
does nothing.
If no timer is running, check the timestamp. If enough time has
passed, update the title immediately.
If not, instantiate a timer and wait for it to trigger.
Set the minimum time between two updates to ~8ms (twice per frame, for
a 60Hz output, and ~once per frame on a 120Hz output).
Closes #591
2021-06-15 17:27:50 +02:00
|
|
|
!term->render.pending.csd && !term->render.pending.search)
|
2020-12-14 19:05:03 +01:00
|
|
|
{
|
|
|
|
|
term->render.refresh.grid = false;
|
|
|
|
|
term->render.pending.grid = false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-12 12:45:34 +01:00
|
|
|
/* Disarm delayed rendering timers */
|
|
|
|
|
timerfd_settime(
|
|
|
|
|
term->delayed_render_timer.lower_fd, 0,
|
2020-08-23 07:42:20 +02:00
|
|
|
&(struct itimerspec){{0}}, NULL);
|
2020-01-12 12:45:34 +01:00
|
|
|
timerfd_settime(
|
|
|
|
|
term->delayed_render_timer.upper_fd, 0,
|
2020-08-23 07:42:20 +02:00
|
|
|
&(struct itimerspec){{0}}, 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
|
|
|
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-08-23 07:42:20 +02:00
|
|
|
&(struct itimerspec){{0}}, 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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-02 16:31:46 +02:00
|
|
|
term->grid->cur_row->linebreak = false;
|
2020-07-14 11:25:06 +02:00
|
|
|
term->grid->cursor.lcf = false;
|
2020-07-16 08:47:37 +02:00
|
|
|
|
|
|
|
|
const int row = term->grid->cursor.point.row;
|
|
|
|
|
|
|
|
|
|
if (row == term->scroll_region.end - 1)
|
2020-01-20 18:34:32 +01:00
|
|
|
term_scroll(term, 1);
|
2020-07-14 11:25:06 +02:00
|
|
|
else {
|
2020-07-16 08:47:37 +02:00
|
|
|
const int new_row = min(row + 1, term->rows - 1);
|
|
|
|
|
term->grid->cursor.point.row = new_row;
|
|
|
|
|
term->grid->cur_row = grid_row(term->grid, new_row);
|
2020-07-14 11:25:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
term->grid->cursor.point.col = 0;
|
2020-01-20 18:34:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
|
print_insert(struct terminal *term, int width)
|
|
|
|
|
{
|
2020-06-09 17:31:28 +02:00
|
|
|
if (likely(!term->insert_mode))
|
|
|
|
|
return;
|
2020-01-20 18:34:32 +01:00
|
|
|
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(width > 0);
|
2020-07-14 10:50:38 +02:00
|
|
|
|
2020-06-09 17:31:28 +02:00
|
|
|
struct row *row = term->grid->cur_row;
|
|
|
|
|
const size_t move_count = max(0, term->cols - term->grid->cursor.point.col - width);
|
2020-01-20 18:34:32 +01:00
|
|
|
|
2020-06-09 17:31:28 +02:00
|
|
|
memmove(
|
|
|
|
|
&row->cells[term->grid->cursor.point.col + width],
|
|
|
|
|
&row->cells[term->grid->cursor.point.col],
|
|
|
|
|
move_count * sizeof(struct cell));
|
|
|
|
|
|
|
|
|
|
/* Mark moved cells as dirty */
|
|
|
|
|
for (size_t i = term->grid->cursor.point.col + width; i < term->cols; i++)
|
|
|
|
|
row->cells[i].attrs.clean = 0;
|
2020-01-20 18:34:32 +01:00
|
|
|
}
|
|
|
|
|
|
2020-07-14 20:24:52 +02:00
|
|
|
static void
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
print_spacer(struct terminal *term, int col, int remaining)
|
2020-07-14 20:24:52 +02:00
|
|
|
{
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
struct grid *grid = term->grid;
|
|
|
|
|
struct row *row = grid->cur_row;
|
2020-07-14 20:24:52 +02:00
|
|
|
struct cell *cell = &row->cells[col];
|
|
|
|
|
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
cell->wc = CELL_SPACER + remaining;
|
2020-07-14 20:24:52 +02:00
|
|
|
cell->attrs = term->vt.attrs;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-20 18:34:32 +01:00
|
|
|
void
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
term_print(struct terminal *term, char32_t wc, int width)
|
2020-01-20 18:34:32 +01:00
|
|
|
{
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(width > 0);
|
2020-01-20 18:34:32 +01:00
|
|
|
|
2021-11-20 13:54:30 +01:00
|
|
|
struct grid *grid = term->grid;
|
|
|
|
|
|
2021-03-14 19:19:10 +01:00
|
|
|
if (unlikely(term->charsets.set[term->charsets.selected] == CHARSET_GRAPHIC) &&
|
|
|
|
|
wc >= 0x60 && wc <= 0x7e)
|
|
|
|
|
{
|
|
|
|
|
/* 0x60 - 0x7e */
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
static const char32_t vt100_0[] = {
|
|
|
|
|
U'◆', U'▒', U'␉', U'␌', U'␍', U'␊', U'°', U'±', /* ` - g */
|
|
|
|
|
U'', U'␋', U'┘', U'┐', U'┌', U'└', U'┼', U'⎺', /* h - o */
|
|
|
|
|
U'⎻', U'─', U'⎼', U'⎽', U'├', U'┤', U'┴', U'┬', /* p - w */
|
|
|
|
|
U'│', U'≤', U'≥', U'π', U'≠', U'£', U'·', /* x - ~ */
|
2021-03-14 19:19:10 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
xassert(width == 1);
|
|
|
|
|
wc = vt100_0[wc - 0x60];
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-14 17:03:20 +02:00
|
|
|
print_linewrap(term);
|
|
|
|
|
print_insert(term, width);
|
|
|
|
|
|
2021-11-20 13:54:30 +01:00
|
|
|
int col = grid->cursor.point.col;
|
|
|
|
|
|
2020-07-15 08:04:51 +02:00
|
|
|
if (unlikely(width > 1) && likely(term->auto_margin) &&
|
2021-11-20 13:54:30 +01:00
|
|
|
col + width > term->cols)
|
2020-07-14 11:26:14 +02:00
|
|
|
{
|
2020-07-14 10:58:57 +02:00
|
|
|
/* Multi-column character that doesn't fit on current line -
|
2020-07-14 16:49:11 +02:00
|
|
|
* pad with spacers */
|
2021-11-20 13:54:30 +01:00
|
|
|
for (size_t i = col; i < term->cols; i++)
|
term: rename CELL_MULT_COL_SPACER -> CELL_SPACER, and change its definition
Instead of using CELL_SPACER for *all* cells that previously used
CELL_MULT_COL_SPACER, include the remaining number of spacers
following, and including, itself. This is encoded by adding to the
CELL_SPACER value.
So, a double width character will now store the character itself in
the first cell (just like before), and CELL_SPACER+1 in the second
cell.
A three-cell character would store the character itself, then
CELL_SPACER+2, and finally CELL_SPACER+1.
In other words, the last spacer is always CELL_SPACER+1.
CELL_SPACER+0 is used when padding at the right margin. I.e. when
writing e.g. a double width character in the last column, we insert a
CELL_SPACER+0 pad character, and then write the double width character
in the first column on the next row.
2021-05-14 14:41:02 +02:00
|
|
|
print_spacer(term, i, 0);
|
2020-07-14 13:17:50 +02:00
|
|
|
|
2020-07-14 16:49:11 +02:00
|
|
|
/* And force a line-wrap */
|
2021-11-20 13:54:30 +01:00
|
|
|
grid->cursor.lcf = 1;
|
2020-07-14 17:03:20 +02:00
|
|
|
print_linewrap(term);
|
2021-11-21 14:47:24 +01:00
|
|
|
col = 0;
|
2020-07-14 10:58:57 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-28 11:01:19 +02:00
|
|
|
sixel_overwrite_at_cursor(term, width);
|
2020-02-22 21:35:45 +01:00
|
|
|
|
2020-01-22 18:22:15 +01:00
|
|
|
/* *Must* get current cell *after* linewrap+insert */
|
2021-11-20 13:54:30 +01:00
|
|
|
struct row *row = grid->cur_row;
|
2021-11-21 14:13:27 +01:00
|
|
|
row->dirty = true;
|
2022-06-30 19:37:01 +02:00
|
|
|
row->linebreak = true;
|
2020-01-22 18:22:15 +01:00
|
|
|
|
2021-11-21 14:13:27 +01:00
|
|
|
struct cell *cell = &row->cells[col];
|
2020-01-20 18:34:32 +01:00
|
|
|
cell->wc = term->vt.last_printed = wc;
|
|
|
|
|
cell->attrs = term->vt.attrs;
|
|
|
|
|
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
if (term->vt.osc8.uri != NULL) {
|
|
|
|
|
grid_row_uri_range_put(
|
|
|
|
|
row, col, term->vt.osc8.uri, term->vt.osc8.id);
|
|
|
|
|
|
|
|
|
|
switch (term->conf->url.osc8_underline) {
|
|
|
|
|
case OSC8_UNDERLINE_ALWAYS:
|
|
|
|
|
cell->attrs.url = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case OSC8_UNDERLINE_URL_MODE:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-12-26 14:51:26 +01:00
|
|
|
} else if (row->extra != NULL)
|
|
|
|
|
grid_row_uri_range_erase(row, col, col + width - 1);
|
2021-11-20 13:54:30 +01:00
|
|
|
|
2020-01-20 18:34:32 +01:00
|
|
|
/* Advance cursor the 'additional' columns while dirty:ing the cells */
|
2021-11-20 13:54:30 +01:00
|
|
|
for (int i = 1; i < width && col < term->cols - 1; i++) {
|
|
|
|
|
col++;
|
|
|
|
|
print_spacer(term, col, width - i);
|
2020-01-20 18:34:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Advance cursor */
|
2021-11-20 13:54:30 +01:00
|
|
|
if (unlikely(++col >= term->cols)) {
|
|
|
|
|
grid->cursor.lcf = true;
|
|
|
|
|
col--;
|
term: term_print(): assume we’re *not* at the right margin
That is, assume we can, and should, increment the cursor column.
This changes the emitted assembly from:
518fb: 8b 8b f8 05 00 00 mov 0x5f8(%rbx),%ecx
51901: ff c9 dec %ecx
51903: 39 d1 cmp %edx,%ecx
51905: 7e 11 jle 51918 <term_print.constprop.0+0x78>
51907: ff c2 inc %edx
51909: 89 50 10 mov %edx,0x10(%rax)
5190c: 5b pop %rbx
5190d: 5d pop %rbp
5190e: 41 5c pop %r12
51910: c3 ret
51911: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
51918: c6 40 18 01 movb $0x1,0x18(%rax)
5191c: 5b pop %rbx
5191d: 5d pop %rbp
5191e: 41 5c pop %r12
51920: c3 ret
To:
5191c: 41 8d 50 01 lea 0x1(%r8),%edx
51920: 89 50 10 mov %edx,0x10(%rax)
51923: 3b 93 f8 05 00 00 cmp 0x5f8(%rbx),%edx
51929: 0f 8d 21 01 00 00 jge 51a50 <term_print.constprop.0+0x190>
5192f: 5b pop %rbx
51930: 5d pop %rbp
51931: 41 5c pop %r12
51933: c3 ret
...
51a50: c6 40 18 01 movb $0x1,0x18(%rax)
51a54: 44 89 40 10 mov %r8d,0x10(%rax)
51a58: 5b pop %rbx
51a59: 5d pop %rbp
51a5a: 41 5c pop %r12
51a5c: c3 ret
I.e. it cuts the normal path from 10 instructions down to 8. It
increases the "bad" path with one extra instruction.
2021-03-14 20:47:44 +01:00
|
|
|
} else
|
2021-11-20 13:54:30 +01:00
|
|
|
xassert(!grid->cursor.lcf);
|
|
|
|
|
|
|
|
|
|
grid->cursor.point.col = col;
|
2020-01-20 18:34:32 +01:00
|
|
|
}
|
2020-02-24 22:38:35 +01:00
|
|
|
|
2021-03-14 19:19:10 +01:00
|
|
|
static void
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
ascii_printer_generic(struct terminal *term, char32_t wc)
|
2021-03-14 19:19:10 +01:00
|
|
|
{
|
|
|
|
|
term_print(term, wc, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
ascii_printer_fast(struct terminal *term, char32_t wc)
|
2021-03-14 19:19:10 +01:00
|
|
|
{
|
2021-11-20 13:54:30 +01:00
|
|
|
struct grid *grid = term->grid;
|
|
|
|
|
|
2021-03-14 19:19:10 +01:00
|
|
|
xassert(term->charsets.set[term->charsets.selected] == CHARSET_ASCII);
|
|
|
|
|
xassert(!term->insert_mode);
|
2021-11-20 13:54:30 +01:00
|
|
|
xassert(tll_length(grid->sixel_images) == 0);
|
2021-03-14 19:19:10 +01:00
|
|
|
|
|
|
|
|
print_linewrap(term);
|
|
|
|
|
|
|
|
|
|
/* *Must* get current cell *after* linewrap+insert */
|
2021-11-20 13:54:30 +01:00
|
|
|
int col = grid->cursor.point.col;
|
2021-11-21 14:13:27 +01:00
|
|
|
const int uri_start = col;
|
|
|
|
|
|
2021-11-20 13:54:30 +01:00
|
|
|
struct row *row = grid->cur_row;
|
2021-11-21 14:13:27 +01:00
|
|
|
row->dirty = true;
|
2022-06-30 19:37:01 +02:00
|
|
|
row->linebreak = true;
|
2021-03-14 19:19:10 +01:00
|
|
|
|
2021-11-21 14:13:27 +01:00
|
|
|
struct cell *cell = &row->cells[col];
|
2021-03-14 19:19:10 +01:00
|
|
|
cell->wc = term->vt.last_printed = wc;
|
|
|
|
|
cell->attrs = term->vt.attrs;
|
|
|
|
|
|
|
|
|
|
/* Advance cursor */
|
2021-11-20 13:54:30 +01:00
|
|
|
if (unlikely(++col >= term->cols)) {
|
|
|
|
|
grid->cursor.lcf = true;
|
|
|
|
|
col--;
|
2021-03-14 19:19:10 +01:00
|
|
|
} else
|
2021-11-20 13:54:30 +01:00
|
|
|
xassert(!grid->cursor.lcf);
|
|
|
|
|
|
|
|
|
|
grid->cursor.point.col = col;
|
2021-11-20 14:39:32 +01:00
|
|
|
|
|
|
|
|
if (unlikely(row->extra != NULL))
|
|
|
|
|
grid_row_uri_range_erase(row, uri_start, uri_start);
|
2021-03-14 19:19:10 +01:00
|
|
|
}
|
|
|
|
|
|
2021-06-08 21:09:40 +01:00
|
|
|
static void
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
ascii_printer_single_shift(struct terminal *term, char32_t wc)
|
2021-06-08 21:09:40 +01:00
|
|
|
{
|
|
|
|
|
ascii_printer_generic(term, wc);
|
|
|
|
|
term->charsets.selected = term->charsets.saved;
|
|
|
|
|
term_update_ascii_printer(term);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-14 19:19:10 +01:00
|
|
|
void
|
|
|
|
|
term_update_ascii_printer(struct terminal *term)
|
|
|
|
|
{
|
fcft: adapt to API changes in fcft-3.x
Fcft no longer uses wchar_t, but plain uint32_t to represent
codepoints.
Since we do a fair amount of string operations in foot, it still makes
sense to use something that actually _is_ a string (or character),
rather than an array of uint32_t.
For this reason, we switch out all wchar_t usage in foot to
char32_t. We also verify, at compile-time, that char32_t used
UTF-32 (which is what fcft expects).
Unfortunately, there are no string functions for char32_t. To avoid
having to re-implement all wcs*() functions, we add a small wrapper
layer of c32*() functions.
These wrapper functions take char32_t arguments, but then simply call
the corresponding wcs*() function.
For this to work, wcs*() must _also_ be UTF-32 compatible. We can
check for the presence of the __STDC_ISO_10646__ macro. If set,
wchar_t is at least 4 bytes and its internal representation is UTF-32.
FreeBSD does *not* define this macro, because its internal wchar_t
representation depends on the current locale. It _does_ use UTF-32
_if_ the current locale is UTF-8.
Since foot enforces UTF-8, we simply need to check if __FreeBSD__ is
defined.
Other fcft API changes:
* fcft_glyph_rasterize() -> fcft_codepoint_rasterize()
* font.space_advance has been removed
* ‘tags’ have been removed from fcft_grapheme_rasterize()
* ‘fcft_log_init()’ removed
* ‘fcft_init()’ and ‘fcft_fini()’ must be explicitly called
2021-08-21 14:50:42 +02:00
|
|
|
void (*new_printer)(struct terminal *term, char32_t wc) =
|
2021-03-14 19:19:10 +01:00
|
|
|
unlikely(tll_length(term->grid->sixel_images) > 0 ||
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
term->vt.osc8.uri != NULL ||
|
2021-03-14 19:19:10 +01:00
|
|
|
term->charsets.set[term->charsets.selected] == CHARSET_GRAPHIC ||
|
|
|
|
|
term->insert_mode)
|
|
|
|
|
? &ascii_printer_generic
|
|
|
|
|
: &ascii_printer_fast;
|
2021-03-16 12:57:25 +01:00
|
|
|
|
|
|
|
|
#if defined(_DEBUG) && LOG_ENABLE_DBG
|
|
|
|
|
if (term->ascii_printer != new_printer) {
|
terminal: break out scaling factor updating, and reduce number of calls to render_resize()
Break out the logic that updates the terminal’s scaling factor value,
from render_resize(), to a new function, term_update_scale(). This
allows us to update the scaling factor without a full grid resize.
We also change how we pick the scaling factor (when fractional scaling
is not in use). Before, we’d use the highest scaling factor from all
monitors we were mapped on. Now, we use the scaling factor from the
monitor we were *last* mapped on.
Then, add a boolean parameter to term_set_fonts(), and when
false, *don’t* call render_resize_force().
Also change term_font_dpi_changed() to only return true if the font
was changed in any way.
Finally, rewrite update_term_for_output_change() to:
* Call term_update_scale() before doing anything else
* Call render_resize{,_force} *last*, and *only* if either the scale
or the fonts were updated.
This fixes several things:
* A bug where we failed to update the fonts when fractional scaling
was in use, and we guessed the initial scale/DPI wrong. The bug
happened because updated the internal "preferred" scale value, and a
later call to render_resize() updated the terminal’s scale value,
but since that code path didn’t call term_font_dpi_changed() (and it
shouldn’t), the fonts weren’t resized properly.
* It ensures we only resize the grid *once* when the scaling factor,
or DPI is changed. Before this, we’d resize it twice. And this
happened when e.g. dragging the window between monitors.
2023-07-17 16:21:16 +02:00
|
|
|
LOG_DBG("switching ASCII printer %s -> %s",
|
2021-03-16 12:57:25 +01:00
|
|
|
term->ascii_printer == &ascii_printer_fast ? "fast" : "generic",
|
|
|
|
|
new_printer == &ascii_printer_fast ? "fast" : "generic");
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
term->ascii_printer = new_printer;
|
2021-03-14 19:19:10 +01:00
|
|
|
}
|
|
|
|
|
|
2021-06-08 21:09:40 +01:00
|
|
|
void
|
2021-06-09 09:51:48 +01:00
|
|
|
term_single_shift(struct terminal *term, enum charset_designator idx)
|
2021-06-08 21:09:40 +01:00
|
|
|
{
|
|
|
|
|
term->charsets.saved = term->charsets.selected;
|
2021-06-09 09:51:48 +01:00
|
|
|
term->charsets.selected = idx;
|
2021-06-08 21:09:40 +01:00
|
|
|
term->ascii_printer = &ascii_printer_single_shift;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-24 22:38:35 +01:00
|
|
|
enum term_surface
|
|
|
|
|
term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
|
|
|
|
|
{
|
2023-06-26 16:10:40 +02:00
|
|
|
if (likely(surface == term->window->surface.surf))
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_GRID;
|
2023-06-26 16:10:40 +02:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_TITLE].surface.surf)
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_TITLE;
|
2023-06-26 16:10:40 +02:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_LEFT].surface.surf)
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_BORDER_LEFT;
|
2023-06-26 16:10:40 +02:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_RIGHT].surface.surf)
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_BORDER_RIGHT;
|
2023-06-26 16:10:40 +02:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_TOP].surface.surf)
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_BORDER_TOP;
|
2023-06-26 16:10:40 +02:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_BOTTOM].surface.surf)
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_BORDER_BOTTOM;
|
2023-06-26 16:10:40 +02:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_MINIMIZE].surface.surf)
|
2020-03-02 20:29:28 +01:00
|
|
|
return TERM_SURF_BUTTON_MINIMIZE;
|
2023-06-26 16:10:40 +02:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_MAXIMIZE].surface.surf)
|
2020-03-02 20:29:28 +01:00
|
|
|
return TERM_SURF_BUTTON_MAXIMIZE;
|
2023-06-26 16:10:40 +02:00
|
|
|
else if (surface == term->window->csd.surface[CSD_SURF_CLOSE].surface.surf)
|
2020-03-02 20:29:28 +01:00
|
|
|
return TERM_SURF_BUTTON_CLOSE;
|
2022-04-16 17:41:14 +02:00
|
|
|
else
|
2020-02-24 22:38:35 +01:00
|
|
|
return TERM_SURF_NONE;
|
|
|
|
|
}
|
2020-07-15 11:33:37 +02:00
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
rows_to_text(const struct terminal *term, int start, int end,
|
2022-12-08 11:45:23 +01:00
|
|
|
int col_start, int col_end, char **text, size_t *len)
|
2020-07-15 11:33:37 +02:00
|
|
|
{
|
2021-03-30 14:40:21 +02:00
|
|
|
struct extraction_context *ctx = extract_begin(SELECTION_NONE, true);
|
2020-07-15 11:33:37 +02:00
|
|
|
if (ctx == NULL)
|
|
|
|
|
return false;
|
|
|
|
|
|
2022-02-07 14:56:13 +01:00
|
|
|
const int grid_rows = term->grid->num_rows;
|
|
|
|
|
int r = start;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
2020-07-15 11:33:37 +02:00
|
|
|
const struct row *row = term->grid->rows[r];
|
2021-01-16 20:16:00 +00:00
|
|
|
xassert(row != NULL);
|
2020-07-15 11:33:37 +02:00
|
|
|
|
2022-12-08 11:45:23 +01:00
|
|
|
const int c_end = r == end ? col_end : term->cols;
|
|
|
|
|
|
|
|
|
|
for (int c = col_start; c < c_end; c++) {
|
2020-07-15 11:33:37 +02:00
|
|
|
if (!extract_one(term, row, &row->cells[c], c, ctx))
|
|
|
|
|
goto out;
|
2022-12-08 11:45:23 +01:00
|
|
|
}
|
2022-02-07 14:56:13 +01:00
|
|
|
|
|
|
|
|
if (r == end)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
r++;
|
|
|
|
|
r &= grid_rows - 1;
|
2022-12-08 11:45:23 +01:00
|
|
|
|
|
|
|
|
col_start = 0;
|
2020-07-15 11:33:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out:
|
2021-03-30 14:40:21 +02:00
|
|
|
return extract_finish(ctx, text, len);
|
2020-07-15 11:33:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_scrollback_to_text(const struct terminal *term, char **text, size_t *len)
|
|
|
|
|
{
|
2022-02-07 14:56:13 +01:00
|
|
|
const int grid_rows = term->grid->num_rows;
|
|
|
|
|
int start = (term->grid->offset + term->rows) & (grid_rows - 1);
|
|
|
|
|
int end = (term->grid->offset + term->rows - 1) & (grid_rows - 1);
|
|
|
|
|
|
|
|
|
|
xassert(start >= 0);
|
|
|
|
|
xassert(start < grid_rows);
|
|
|
|
|
xassert(end >= 0);
|
|
|
|
|
xassert(end < grid_rows);
|
2020-07-15 11:33:37 +02:00
|
|
|
|
|
|
|
|
/* If scrollback isn't full yet, this may be NULL, so scan forward
|
|
|
|
|
* until we find the first non-NULL row */
|
|
|
|
|
while (term->grid->rows[start] == NULL) {
|
|
|
|
|
start++;
|
2022-02-07 14:56:13 +01:00
|
|
|
start &= grid_rows - 1;
|
2020-07-15 11:33:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (term->grid->rows[end] == NULL) {
|
|
|
|
|
end--;
|
|
|
|
|
if (end < 0)
|
|
|
|
|
end += term->grid->num_rows;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-08 11:45:23 +01:00
|
|
|
return rows_to_text(term, start, end, 0, term->cols, text, len);
|
2020-07-15 11:33:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_view_to_text(const struct terminal *term, char **text, size_t *len)
|
|
|
|
|
{
|
|
|
|
|
int start = grid_row_absolute_in_view(term->grid, 0);
|
|
|
|
|
int end = grid_row_absolute_in_view(term->grid, term->rows - 1);
|
2022-12-08 11:45:23 +01:00
|
|
|
return rows_to_text(term, start, end, 0, term->cols, text, len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
term_command_output_to_text(const struct terminal *term, char **text, size_t *len)
|
|
|
|
|
{
|
|
|
|
|
int start_row = -1;
|
|
|
|
|
int end_row = -1;
|
|
|
|
|
int start_col = -1;
|
|
|
|
|
int end_col = -1;
|
|
|
|
|
|
|
|
|
|
const struct grid *grid = term->grid;
|
|
|
|
|
const int sb_end = grid_row_absolute(grid, term->rows - 1);
|
2022-12-08 13:06:24 +01:00
|
|
|
const int sb_start = (sb_end + 1) & (grid->num_rows - 1);
|
|
|
|
|
int r = sb_end;
|
2022-12-08 11:45:23 +01:00
|
|
|
|
2022-12-08 13:06:24 +01:00
|
|
|
while (start_row < 0) {
|
2022-12-08 11:45:23 +01:00
|
|
|
const struct row *row = grid->rows[r];
|
|
|
|
|
if (row == NULL)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (row->shell_integration.cmd_end >= 0) {
|
|
|
|
|
end_row = r;
|
|
|
|
|
end_col = row->shell_integration.cmd_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (end_row >= 0 && row->shell_integration.cmd_start >= 0) {
|
|
|
|
|
start_row = r;
|
|
|
|
|
start_col = row->shell_integration.cmd_start;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-08 13:06:24 +01:00
|
|
|
if (r == sb_start)
|
|
|
|
|
break;
|
|
|
|
|
|
2022-12-08 11:45:23 +01:00
|
|
|
r = (r - 1 + grid->num_rows) & (grid->num_rows - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (start_row < 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
bool ret = rows_to_text(term, start_row, end_row, start_col, end_col, text, len);
|
|
|
|
|
if (!ret)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* If the FTCS_COMMAND_FINISHED marker was emitted at the *first*
|
|
|
|
|
* column, then the *entire* previous line is part of the command
|
|
|
|
|
* output. *Including* the newline, if any.
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Since rows_to_text() doesn't extract the column
|
2022-12-08 11:45:23 +01:00
|
|
|
* FTCS_COMMAND_FINISHED was emitted at (that would be wrong -
|
|
|
|
|
* FTCS_COMMAND_FINISHED is emitted *after* the command output,
|
|
|
|
|
* not at its last character), the extraction logic will not see
|
|
|
|
|
* the last newline (this is true for all non-line-wise selection
|
|
|
|
|
* types), and the extracted text will *not* end with a newline.
|
|
|
|
|
*
|
2024-02-06 12:36:45 +01:00
|
|
|
* Here we try to compensate for that. Note that if 'end_col' is
|
2022-12-08 11:45:23 +01:00
|
|
|
* not 0, then the command output only covers a partial row, and
|
|
|
|
|
* thus we do *not* want to append a newline.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (end_col > 0) {
|
2024-02-06 12:36:45 +01:00
|
|
|
/* Command output covers partial row - don't append newline */
|
2022-12-08 11:45:23 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int next_to_last_row = (end_row - 1 + grid->num_rows) & (grid->num_rows - 1);
|
|
|
|
|
const struct row *row = grid->rows[next_to_last_row];
|
|
|
|
|
|
|
|
|
|
/* Add newline if last row has a hard linebreak */
|
|
|
|
|
if (row->linebreak) {
|
|
|
|
|
char *new_text = xrealloc(*text, *len + 1 + 1);
|
|
|
|
|
|
|
|
|
|
if (new_text == NULL) {
|
|
|
|
|
/* Ignore failure - use text as is (without inserting newline) */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*text = new_text;
|
|
|
|
|
(*len)++;
|
|
|
|
|
(*text)[*len - 1] = '\n';
|
|
|
|
|
(*text)[*len] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2020-07-15 11:33:37 +02:00
|
|
|
}
|
2020-12-03 18:36:56 +01:00
|
|
|
|
2020-12-04 18:39:11 +01:00
|
|
|
bool
|
|
|
|
|
term_ime_is_enabled(const struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
return term->ime_enabled;
|
2020-12-04 18:39:11 +01:00
|
|
|
#else
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-03 18:36:56 +01:00
|
|
|
void
|
2020-12-04 18:39:11 +01:00
|
|
|
term_ime_enable(struct terminal *term)
|
2020-12-03 18:36:56 +01:00
|
|
|
{
|
|
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
if (term->ime_enabled)
|
2020-12-04 18:39:11 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("IME enabled");
|
|
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
term->ime_enabled = true;
|
2020-12-04 18:39:11 +01:00
|
|
|
|
2020-12-04 20:08:22 +01:00
|
|
|
/* IME is per seat - enable on all seat currently focusing us */
|
2020-12-04 18:39:11 +01:00
|
|
|
tll_foreach(term->wl->seats, it) {
|
|
|
|
|
if (it->item.kbd_focus == term)
|
|
|
|
|
ime_enable(&it->item);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_ime_disable(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
if (!term->ime_enabled)
|
2020-12-04 18:39:11 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
LOG_DBG("IME disabled");
|
|
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
term->ime_enabled = false;
|
2020-12-04 18:39:11 +01:00
|
|
|
|
2020-12-04 20:33:15 +01:00
|
|
|
/* IME is per seat - disable on all seat currently focusing us */
|
2020-12-04 18:39:11 +01:00
|
|
|
tll_foreach(term->wl->seats, it) {
|
|
|
|
|
if (it->item.kbd_focus == term)
|
|
|
|
|
ime_disable(&it->item);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 13:03:07 +01:00
|
|
|
bool
|
2020-12-04 18:39:11 +01:00
|
|
|
term_ime_reset(struct terminal *term)
|
|
|
|
|
{
|
2021-03-23 13:03:07 +01:00
|
|
|
bool at_least_one_seat_was_reset = false;
|
|
|
|
|
|
2020-12-04 18:39:11 +01:00
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
2021-03-23 13:03:07 +01:00
|
|
|
tll_foreach(term->wl->seats, it) {
|
|
|
|
|
struct seat *seat = &it->item;
|
|
|
|
|
|
|
|
|
|
if (seat->kbd_focus != term)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
ime_reset_preedit(seat);
|
|
|
|
|
at_least_one_seat_was_reset = true;
|
2020-12-04 18:39:11 +01:00
|
|
|
}
|
2020-12-03 18:36:56 +01:00
|
|
|
#endif
|
2021-03-23 13:03:07 +01:00
|
|
|
|
|
|
|
|
return at_least_one_seat_was_reset;
|
2020-12-03 18:36:56 +01:00
|
|
|
}
|
2020-12-20 15:01:21 -07:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_ime_set_cursor_rect(struct terminal *term, int x, int y, int width,
|
|
|
|
|
int height)
|
|
|
|
|
{
|
|
|
|
|
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
|
|
|
|
tll_foreach(term->wl->seats, it) {
|
|
|
|
|
if (it->item.kbd_focus == term) {
|
|
|
|
|
it->item.ime.cursor_rect.pending.x = x;
|
|
|
|
|
it->item.ime.cursor_rect.pending.y = y;
|
|
|
|
|
it->item.ime.cursor_rect.pending.width = width;
|
|
|
|
|
it->item.ime.cursor_rect.pending.height = height;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2021-01-31 11:12:07 +01:00
|
|
|
|
2021-02-13 12:34:48 +01:00
|
|
|
void
|
2021-02-13 13:44:07 +01:00
|
|
|
term_osc8_open(struct terminal *term, uint64_t id, const char *uri)
|
2021-02-13 12:34:48 +01:00
|
|
|
{
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
term_osc8_close(term);
|
2021-05-08 15:17:55 +02:00
|
|
|
xassert(term->vt.osc8.uri == NULL);
|
|
|
|
|
|
2021-02-13 13:44:07 +01:00
|
|
|
term->vt.osc8.id = id;
|
2021-02-13 12:34:48 +01:00
|
|
|
term->vt.osc8.uri = xstrdup(uri);
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
term_update_ascii_printer(term);
|
2021-02-13 12:34:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_osc8_close(struct terminal *term)
|
|
|
|
|
{
|
|
|
|
|
free(term->vt.osc8.uri);
|
|
|
|
|
term->vt.osc8.uri = NULL;
|
osc8: update URI ranges as we print data, *not* when the URI is closed
At first, an OSC-8 URI range was added when we received the closing
OSC-8 escape (i.e. with an empty URI).
But, this meant that cursor movements while the OSC-8 escape was in
effect wasn’t handled correctly, since we’d add a range that spanned
the cursor movements.
Attempts were made to handle this in the cursor movement functions, by
closing and re-opening the URI.
However, there are too many corner cases to make this a viable
approach. Scrolling is one such example, line-wrapping another.
This patch takes a different approach; emit, or update the URI range
when we print to the grid. This models the intended behavior much more
closely, where an active OSC-8 URI act like any other SGR attribute -
it is applied to all cells printed to, but otherwise have no effect.
To avoid killing performance, this is only done in the “generic”
printer. This means OSC-8 open/close calls must now “switch” the ASCII
printer.
Note that the “fast” printer still needs to *erase* pre-existing OSC-8
URIs.
Closes #816
2021-11-25 19:22:52 +01:00
|
|
|
term->vt.osc8.id = 0;
|
|
|
|
|
term_update_ascii_printer(term);
|
2021-02-13 12:34:48 +01:00
|
|
|
}
|
2022-01-01 13:56:50 +01:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
term_set_user_mouse_cursor(struct terminal *term, const char *cursor)
|
|
|
|
|
{
|
|
|
|
|
free(term->mouse_user_cursor);
|
2023-06-28 13:25:08 +02:00
|
|
|
term->mouse_user_cursor = cursor != NULL && strlen(cursor) > 0
|
|
|
|
|
? xstrdup(cursor)
|
|
|
|
|
: NULL;
|
2022-01-01 13:56:50 +01:00
|
|
|
term_xcursor_update(term);
|
|
|
|
|
}
|