Skip to content

Commit 75027dd

Browse files
authored
Merge pull request #149 from hzeller/feature-20250110-full-cell-jump
sixel: Implement DEC-accurate calculation of cursor position for jumping
2 parents e5931b9 + 513f4c1 commit 75027dd

File tree

9 files changed

+69
-28
lines changed

9 files changed

+69
-28
lines changed

man/timg.1

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,9 +499,13 @@ connection that can\[cq]t keep up with playing videos.
499499
Or if you have a very slow CPU.
500500
.TP
501501
\f[B]TIMG_SIXEL_NEWLINE_WORKAROUND\f[R]
502-
Set this environment variable to 1 if you are on a Sixel terminal and
503-
notice that videos `scroll' or grid\-view items are not perfectly
504-
aligned vertically.
502+
Set this environment variable if you are on a Sixel terminal and notice
503+
that videos `scroll' or grid\-view items are not perfectly aligned
504+
vertically.
505+
Sometimes this manifests only for pictures of a particular height.
506+
Valid values are 0, 1, 2, 3 which address various subtle differences in
507+
which sixel terminals behave.
508+
Default 0.
505509
.SH EXAMPLES
506510
Some example invocations including scrolling text or streaming an online
507511
video are put together at \c

man/timg.1.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,9 +424,12 @@ Exit code is
424424
slow CPU.
425425

426426
**TIMG_SIXEL_NEWLINE_WORKAROUND**
427-
: Set this environment variable to 1 if you are on a Sixel terminal and
427+
: Set this environment variable if you are on a Sixel terminal and
428428
notice that videos 'scroll' or grid-view items are not perfectly aligned
429-
vertically.
429+
vertically. Sometimes this manifests only for pictures of a particular
430+
height.
431+
Valid values are 0, 1, 2, 3 which address various subtle differences in
432+
which sixel terminals behave. Default 0.
430433

431434
# EXAMPLES
432435

src/sixel-canvas.cc

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ namespace timg {
3636

3737
SixelCanvas::SixelCanvas(BufferedWriteSequencer *ws, ThreadPool *thread_pool,
3838
bool required_cursor_placement_workaround,
39-
const DisplayOptions &opts)
40-
: TerminalCanvas(ws), options_(opts), executor_(thread_pool) {
39+
bool full_cell_jump, const DisplayOptions &opts)
40+
: TerminalCanvas(ws),
41+
options_(opts),
42+
full_cell_jump_(full_cell_jump),
43+
executor_(thread_pool) {
4144
// Terminals might have different understanding where the curosr is placed
4245
// after an image is sent.
4346
// Apparently the original dec terminal placed it afterwards, but some
@@ -153,11 +156,18 @@ void SixelCanvas::Send(int x, int dy, const Framebuffer &fb_orig,
153156
int SixelCanvas::cell_height_for_pixels(int pixels) const {
154157
assert(pixels <= 0); // Currently only use-case
155158
pixels = -pixels;
156-
// Unlike the other exact pixel canvases where we have to round to the
157-
// next even cell_y_px, here we first need to round to the next even 6
158-
// first.
159-
return -((round_to_sixel(pixels) + options_.cell_y_px - 1) /
160-
options_.cell_y_px);
159+
if (full_cell_jump_) {
160+
// https://github.com/hzeller/timg/issues/145#issuecomment-2579962760
161+
// As DEC intended.
162+
return -((round_to_sixel(pixels) - 6) / options_.cell_y_px + 1);
163+
}
164+
else {
165+
// Unlike the other exact pixel canvases where we have to round to the
166+
// next even cell_y_px, here we first need to round to the next even 6
167+
// first.
168+
return -((round_to_sixel(pixels) + options_.cell_y_px - 1) /
169+
options_.cell_y_px);
170+
}
161171
}
162172

163173
} // namespace timg

src/sixel-canvas.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ThreadPool;
2828
class SixelCanvas final : public TerminalCanvas {
2929
public:
3030
SixelCanvas(BufferedWriteSequencer *ws, ThreadPool *thread_pool,
31-
bool required_cursor_placement_workaround,
31+
bool required_cursor_placement_workaround, bool full_cell_jump,
3232
const DisplayOptions &opts);
3333

3434
int cell_height_for_pixels(int pixels) const final;
@@ -38,6 +38,7 @@ class SixelCanvas final : public TerminalCanvas {
3838

3939
private:
4040
const DisplayOptions &options_;
41+
const bool full_cell_jump_;
4142
ThreadPool *const executor_;
4243
const char *cursor_move_before_;
4344
const char *cursor_move_after_;

src/term-query.cc

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,12 @@ const char *QueryBackgroundColor() {
224224
}
225225

226226
TermGraphicsInfo QuerySupportedGraphicsProtocol() {
227-
TermGraphicsInfo result;
227+
TermGraphicsInfo result{};
228228
result.preferred_graphics = GraphicsProtocol::kNone;
229-
result.known_broken_sixel_cursor_placement =
230-
timg::GetBoolenEnv("TIMG_SIXEL_NEWLINE_WORKAROUND");
231-
result.in_tmux = false;
229+
const int sixel_env_bits = timg::GetIntEnv("TIMG_SIXEL_NEWLINE_WORKAROUND");
230+
result.known_broken_sixel_cursor_placement = sixel_env_bits & 0b01;
231+
result.sixel_full_cell_jump = sixel_env_bits & 0b10;
232+
result.in_tmux = false;
232233

233234
// Environment variables can be changed, so guesses from environment
234235
// variables are just that: guesses.
@@ -299,6 +300,12 @@ TermGraphicsInfo QuerySupportedGraphicsProtocol() {
299300
if (find_str(data, len, "tmux")) {
300301
result.in_tmux = true;
301302
}
303+
if (find_str(data, len, "WindowsTerminal")) {
304+
// TODO: check again once name is established
305+
// https://github.com/microsoft/terminal/issues/18382
306+
result.known_broken_sixel_cursor_placement = true;
307+
result.sixel_full_cell_jump = true;
308+
}
302309
// We finish once we found the response to DSR5
303310
return find_str(data, len, TERM_CSI "0");
304311
});

src/term-query.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ enum class GraphicsProtocol {
4444
struct TermGraphicsInfo {
4545
GraphicsProtocol preferred_graphics;
4646
bool known_broken_sixel_cursor_placement; // see SixelCanvas impl. doc
47+
bool sixel_full_cell_jump;
4748
bool in_tmux;
4849
};
4950

src/timg.cc

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ struct PresentationOptions {
128128
// Rendering
129129
Pixelation pixelation = Pixelation::kNotChosen;
130130
bool sixel_cursor_workaround = false;
131+
bool sixel_full_cell_jump = false;
131132
bool tmux_workaround = false;
132133
bool terminal_use_upper_block = false;
133134
bool use_256_color = false; // For terminals that don't do 24 bit color
@@ -331,9 +332,9 @@ static int PresentImages(LoadedImageSources *loaded_sources,
331332
#ifdef WITH_TIMG_SIXEL
332333
case Pixelation::kSixelGraphics:
333334
compression_pool.reset(new ThreadPool(sequencer->max_queue_len() + 1));
334-
canvas.reset(new timg::SixelCanvas(sequencer, compression_pool.get(),
335-
present.sixel_cursor_workaround,
336-
display_opts));
335+
canvas.reset(new timg::SixelCanvas(
336+
sequencer, compression_pool.get(), present.sixel_cursor_workaround,
337+
present.sixel_full_cell_jump, display_opts));
337338
break;
338339
#endif
339340
case Pixelation::kHalfBlock:
@@ -770,8 +771,8 @@ int main(int argc, char *argv[]) {
770771
if (present.pixelation == Pixelation::kNotChosen) {
771772
present.pixelation = Pixelation::kQuarterBlock; // Good default.
772773
if (term.font_width_px > 0 && term.font_height_px > 0) {
773-
auto graphics_info = timg::QuerySupportedGraphicsProtocol();
774-
present.tmux_workaround = graphics_info.in_tmux;
774+
const auto graphics_info = timg::QuerySupportedGraphicsProtocol();
775+
present.tmux_workaround = graphics_info.in_tmux;
775776
switch (graphics_info.preferred_graphics) {
776777
case timg::GraphicsProtocol::kIterm2:
777778
present.pixelation = Pixelation::kiTerm2Graphics;
@@ -784,6 +785,8 @@ int main(int argc, char *argv[]) {
784785
present.pixelation = Pixelation::kSixelGraphics;
785786
present.sixel_cursor_workaround =
786787
graphics_info.known_broken_sixel_cursor_placement;
788+
present.sixel_full_cell_jump =
789+
graphics_info.sixel_full_cell_jump;
787790
#else
788791
present.pixelation = Pixelation::kQuarterBlock;
789792
#endif
@@ -804,6 +807,7 @@ int main(int argc, char *argv[]) {
804807
auto graphics_info = timg::QuerySupportedGraphicsProtocol();
805808
present.sixel_cursor_workaround =
806809
graphics_info.known_broken_sixel_cursor_placement;
810+
present.sixel_full_cell_jump = graphics_info.sixel_full_cell_jump;
807811
}
808812
#endif
809813

@@ -1031,12 +1035,12 @@ int main(int argc, char *argv[]) {
10311035
PixelationToString(present.pixelation));
10321036
#ifdef WITH_TIMG_SIXEL
10331037
if (present.pixelation == Pixelation::kSixelGraphics) {
1034-
if (present.sixel_cursor_workaround) {
1035-
fprintf(stderr, " (with cursor placment workaround)");
1036-
}
1037-
else {
1038-
fprintf(stderr, " (with default cursor placement)");
1039-
}
1038+
fprintf(stderr, " (%s and %s)",
1039+
present.sixel_cursor_workaround
1040+
? "with cursor placment workaround"
1041+
: "with default cursor placement",
1042+
present.sixel_full_cell_jump ? "full cursor cell jump"
1043+
: "default cursor cell jump");
10401044
}
10411045
#endif
10421046
if (present.pixelation == Pixelation::kKittyGraphics) {

src/utils.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ float GetFloatEnv(const char *env_var, float default_value) {
3939
return (*err == '\0' ? result : default_value);
4040
}
4141

42+
int GetIntEnv(const char *env_name, int default_value) {
43+
const char *const value = getenv(env_name);
44+
if (!value) return default_value;
45+
char *err = nullptr;
46+
const int result = strtol(value, &err, 10);
47+
return (*err == '\0' ? result : default_value);
48+
}
49+
4250
std::string HumanReadableByteValue(int64_t byte_count) {
4351
float print_bytes = byte_count;
4452
const char *unit = "Bytes";

src/utils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ bool GetBoolenEnv(const char *env_name, bool default_value = false);
2626
// Get float value from named environment variable.
2727
float GetFloatEnv(const char *env_var, float default_value);
2828

29+
// Get float value from named environment variable.
30+
int GetIntEnv(const char *env_name, int default_value = 0);
31+
2932
// Given number of bytes, return a human-readable version of that
3033
// (e.g. "13.2 MiB").
3134
std::string HumanReadableByteValue(int64_t byte_count);

0 commit comments

Comments
 (0)