w3m icon indicating copy to clipboard operation
w3m copied to clipboard

Auto inline images

Open el-remph opened this issue 1 year ago • 25 comments

Deduce an appropriate value for inline_img_protocol rather than requiring it to be set manually with eg. -o auto_inline_img=4 for kitty.

Tangentially, should struct _termialImage {...} TerminalImage at image.c:11,27 be _terminalImage? Not important currently since nothing uses the struct tag as opposed to the TerminalImage typedef, but could surprise someone eventually

el-remph avatar Jul 12 '24 19:07 el-remph

Some general remarks:

  • This should be one commit, not three.
  • Missing documentation update.

As someone who does not use images, what problem does this solve?

rkta avatar Jul 14 '24 06:07 rkta

As someone who does not use images, what problem does this solve?

It avoids having to manually set the inline image output method (since different methods work best depending on your environment). Normally you'd have to go through each option yourself; this patch does the detection automatically.

Some comments on the implementation:

  • Doing terminal capability detection in two passes seems needlessly inefficient. Could we merge the sixel test with get_pixel_per_cell?
  • As rkta says, we don't need a new source file for this. Just put it in terms.c and/or image.c.
  • autodetection should be an option (in rc.c) like the actual image protocols, not a CLI parameter. Then you can default to "autodetect" in new configs, without breaking existing configs. We might want to use a separate variable to store the final chosen image display method.
  • Kitty display depends on ImageMagick, and sixel display needs img2sixel. So we probably want to check if these programs exist on the system at all.
  • IIRC, the iTerm2 implementation does not support image cropping, unlike the sixel implementation (which also works in iTerm2). Personally, I'd just remove the iTerm2 test; correct image dimensions are more important than optimal color output.

bptato avatar Jul 14 '24 17:07 bptato

  • This should be one commit, not three.

Ah my apologies, that's my bad habit of committing WIP as I go so I remember what order I've done things in. But can't the merge simply be squashed? Or should I rebase and squash it myself and open a different PR? (with changes moved to image.c ofc)

  • Missing documentation update.

As far as I could see, -o inline_img_protocol has no documentation to update, though do point me towards it if I missed it

As someone who does not use images, what problem does this solve?

Not every user is aware of the different protocols available, nor which if any are supported by their terminal -- I was unaware that more recent Konsole versions support sixel, iterm2 and kitty images, since I'd been using it since before those versions, and xterm and xfce4-terminal both have undocumented sixel support. Of course, on Wayland the w3mimgdisplay X11 fallback doesn't work

Also, for -o inline_img_protocol=x it isn't clear to the user which values of x correspond to which protocol; even if this were documented, it would still be unintuitive and not easy to remember.

el-remph avatar Jul 14 '24 18:07 el-remph

  • Doing terminal capability detection in two passes seems needlessly inefficient. Could we merge the sixel test with get_pixel_per_cell?

I don't think so, get_pixel_per_cell uses XTWINOPS CSI Ps ; Ps ; Ps t, test_for_sixel uses CSI Ps c

  • autodetection should be an option (in rc.c) like the actual image protocols, not a CLI parameter. Then you can default to "autodetect" in new configs, without breaking existing configs. We might want to use a separate variable to store the final chosen image display method.

I'm not sure existing config would be broken -- if inline_img_protocol is set in a config, it would override default autodetection. I would have liked to use -o inline_img_protocol=-1, since that's what happens under the hood, so perhaps changing to P_CHARINT is the answer

el-remph avatar Jul 14 '24 19:07 el-remph

  • Doing terminal capability detection in two passes seems needlessly inefficient. Could we merge the sixel test with get_pixel_per_cell?

I don't think so, get_pixel_per_cell uses XTWINOPS CSI Ps ; Ps ; Ps t, test_for_sixel uses CSI Ps c

Ah, no, I meant sending them together in a single batch. Terminal query code usually does this in one pass, putting primary DA in the end, because one write/read/timeout cycle will always be faster than two. (Also, pretty much every terminal emulator responds to a primary DA but not necessarily to other queries, so sending it as the final query is a nice timeout prevention measure.)

We can live without that for now, since we only have two queries, both seem to be widely supported, and get_pixel_per_cell often returns without sending a query at all. Just a suggestion for improvement.

I'm not sure existing config would be broken

Yeah, I now see you're right.

The only problem with the current solution remains that it doesn't let the user pick "autodetect" and then stick with it, which would be nice for when you use w3m in different terminals. (Even if you could pick -1 in its current form, it would automatically switch to another option as soon as you change something else in the option setting panel.) Hence my suggestion to use a separate internal variable for the actual image protocol choice.

bptato avatar Jul 15 '24 15:07 bptato

In the absence of any further comments on the clutter of the commit history, I'll just push any further commits and potentially clean up history later (I know `clean up later' is never a good thing to hear ;). The diff against master still looks fairly clean. This implements most of the changes just discussed

Ah, no, I meant sending them together in a single batch

Oh I get it

[...]and get_pixel_per_cell often returns without sending a query at all

inline_img_protocol_autodetect() may also itself return without sending the XTWINOPS query (if protocol_test_environment() returns nonzero), so implementing that may end up being more trouble than it's worth. For startup speed I'm more concerned about the exec call to find img2sixel in have_img2sixel() in image.c, even though it's the same way that img2sixel is found when it's needed in put_image_sixel() in terms.c.

The only problem with the current solution remains that it doesn't let the user pick "autodetect" and then stick with it, which would be nice for when you use w3m in different terminals. (Even if you could pick -1 in its current form, it would automatically switch to another option as soon as you change something else in the option setting panel.)

I'm afraid I don't follow here -- by the time you get to the option setting panel, if autodetect was set it will already have run at startup and resolved to a different value INLINE_IMG_*. Or do you mean it should not run only at startup? The way I see it right now, if the user picks a different setting for inline_img_protocol, this by design should override the auto-detected value

el-remph avatar Jul 21 '24 19:07 el-remph

The auto detect should be an option in the options panel, so that a user does not have to use the command line option in order to enable it.

Re: commit history, you can squash the commits and just do a force push into your branch.

Remember to also update the docs, when you are done with the implementation.

rkta avatar Jul 22 '24 07:07 rkta

The auto detect should be an option in the options panel, so that a user does not have to use the command line option in order to enable it.

Yeah, that's what I meant. You could do something like:

/* in fm.h */
#define INLINE_IMG_AUTODETECT -1
/* maybe move this to fm.h too */
global signed char enable_inline_image init(INLINE_IMG_NONE);
/* ...and set above variable based on this */
global signed char enable_inline_image_config init(INLINE_IMG_AUTODETECT);

So if enable_inline_image_config == INLINE_IMG_AUTODETECT, detect the method and set enable_inline_image to that. Otherwise, set enable_inline_image to enable_inline_image_config as is. (Also, make enable_inline_image_config the user-facing variable you set in rc.c.)

Remember to also update the docs, when you are done with the implementation.

What docs? :P

OK, there's doc/README.sixel, which is a bit outdated and only talks about the sixel method. Also, doc/README.img, which only talks about the X/framebuffer method. I think it would be best to merge the two, and also add some info on the other methods (+ how to select them).

It's kind of my fault for never updating the docs when working on this, so I'll prepare a PR for it once this patch is finalized.

bptato avatar Jul 22 '24 20:07 bptato

On Mon, Jul 22, 2024 at 01:32:28PM -0700, bptato wrote:

Remember to also update the docs, when you are done with the implementation.

What docs? :P

Well, adding a command line option should at least update the man page and probably also the MANUAL.html.

rkta avatar Jul 23 '24 05:07 rkta

Well, adding a command line option should at least update the man page and probably also the MANUAL.html.

Wait what? I thought we decided we don't need a command line option...

Looking at the man page and MANUAL.html, neither did ever mention image support. I've started working on an overhauled README.img, I'd say it's easiest to just link to that from MANUAL.html once it's finished.

bptato avatar Jul 23 '24 14:07 bptato

On Tue, Jul 23, 2024 at 07:43:47AM -0700, bptato wrote:

Well, adding a command line option should at least update the man page and probably also the MANUAL.html.

Wait what? I thought we decided we don't need a command line option...

Did we? That's good, because that's what I preferred. :-D

Looking at the man page and MANUAL.html, neither did ever mention image support. I've started working on an overhauled README.img, I'd say it's easiest to just link to that from MANUAL.html once it's finished.

Sounds good.

rkta avatar Jul 23 '24 15:07 rkta

Alright, I've squashed the commits from up til now, and the second commit adds the separate variables for enable_inline_image_config, and INLINE_IMAGE_AUTODETECT to the option table in rc.c. The original enable_inline_image can be unsigned now, so there's no possibility of a negative value being taken as a false positive in a boolean test.

Only issue is now the user can't see in the options panel which protocol was selected automagically, but that's not the end of the world. If it does become necessary, maybe that information would be more suited to debug output on stderr anyway.

Also, looks like enable_inline_image can be ifdef'd out after all

el-remph avatar Aug 19 '24 04:08 el-remph

JFTR, I have this on my radar. In the current form it's not easy to review, because there seem to be a lot of unrelated changes mixed into the commits, but I will try to work it out when my time permits.

rkta avatar Sep 06 '24 13:09 rkta

  • Doing terminal capability detection in two passes seems needlessly inefficient. Could we merge the sixel test with get_pixel_per_cell?

I don't think so, get_pixel_per_cell uses XTWINOPS CSI Ps ; Ps ; Ps t, test_for_sixel uses CSI Ps c

Ah, no, I meant sending them together in a single batch. Terminal query code usually does this in one pass, putting primary DA in the end, because one write/read/timeout cycle will always be faster than two. (Also, pretty much every terminal emulator responds to a primary DA but not necessarily to other queries, so sending it as the final query is a nice timeout prevention measure.)

We can live without that for now, since we only have two queries, both seem to be widely supported, and get_pixel_per_cell often returns without sending a query at all. Just a suggestion for improvement.

Is this still valid? If yes, can you make that improvement @el-remph , please.

rkta avatar Sep 28 '24 13:09 rkta

Sorry I was away, here's a mockup that seems to work, but I'm not sure if this does optimise anything after all, because to know if we need pixel/char we need to know whether we're using enable_inline_img or not, so (if kitty and mlterm protocols are unavailable) we need to know if the terminal supports sixel before we query pixel/char.

(There's more that could be done to make this prettier, but I'm leaving that until I know if this optimises anything or not)

My 'solution' in this commit, is just call get_pixel_per_char() unconditionally, which will lead to an unnecessary check for pixel/char for those without any supported inline image protocol. On most systems, I don't think this will use XTWINOPS, since TIOCGWINSZ is usually available, so this works out to an extra ioctl call for the typical (I guess) user. I don't know the cost of ioctl so this may not be much, but the only benefits are for those on systems without TIOCGWINSZ so I doubt it's worth it.

Unless anyone has any ideas, I'll rollback this commit

el-remph avatar Dec 07 '24 21:12 el-remph

When I wrote "We can live without that for now", I meant it. It isn't that relevant with just two queries, especially when one of those is just a fallback.

The current (as in, before this patch) pixel per column/line logic seems flawed anyway, as the same variables are used for tables too (width attribute, etc.) So right now, toggling inline image support influences table rendering. Querying cell dimensions unconditionally would be preferable, but then it has to be moved outside the image ifdefs, and we have to figure out what to do with the pixel per char/line options... I think it's far beyond the scope of this PR.

bptato avatar Dec 08 '24 14:12 bptato

When I wrote "We can live without that for now", I meant it.

Wrote the commit in response to @rkta here, but yes, I agree; rolling back as planned

el-remph avatar Dec 10 '24 17:12 el-remph

Rebased with detection of ghostty, which has had support for kitty images since very early on afaict.

Anyone know the status of this pr?

el-remph avatar Jan 16 '25 03:01 el-remph

On Wed, Jan 15, 2025 at 07:10:09PM -0800, el-remph wrote: [...]

Anyone know the status of this pr?

I didn't follow the last changes that closely, but I'd be willing to accept this into my fork when all current issues are addressed. I'd like to have an OK from bptato and maybe from j4james first though.

Also I'd prefer if you would send it the mailing list1 then I will do the final review.

Chances that it will be merged here are next to zero, I guess.

rkta avatar Jan 16 '25 15:01 rkta

Rebased with the changes recommended:

  • Primary devattr querying moved to terms.c, reading from tty into a dynamically resized buffer
  • Test for sixel before testing for mlterm OSC5379, to catch newer versions of mlterm which support sixel but not OSC5379
  • Made rkta's style changes

Also I'd prefer if you would send it the mailing list[1] then I will do the final review.

Oh, I didn't realise about the mailing list. If these changes pass muster here then I'll submit it to the mailing list

el-remph avatar Jan 24 '25 15:01 el-remph

I've tested the latest version:

  • Piping to stdin works now, thanks.
  • It seems initImage gets called twice for some reason, so detection is performed twice too :/ I guess that's why the activeImage check is there before getCharSize? Maybe you could reuse it.
  • Obscure, but SyncTERM returns \033[= to primary DA instead of \033[?; might as well support it:
diff --git a/terms.c b/terms.c
index f75dcfc6..d663d18e 100644
--- a/terms.c
+++ b/terms.c
@@ -924,7 +924,8 @@ img_protocol_test_for_sixel(void)
 	}
 
 	/* first useful iteration; validate response */
-	if (nchars_read + len >= 3 && memcmp(response, "\033[?", 3) != 0) {
+	if (nchars_read + len >= 3 && memcmp(response, "\033[?", 3) != 0 &&
+	    memcmp(response, "\033[=", 3) != 0) {
 	    fputs("Malformed terminal attributes\n", stderr);
 	    return INLINE_IMG_NONE;
 	}

I still think, this needs to be documented somewhere.

Well, but the image docs in your fork are now different... I can update them in a separate patch, if you're OK with that.

bptato avatar Jan 27 '25 20:01 bptato

  • Obscure, but SyncTERM returns \033[= to primary DA instead of \033[?; might as well support it:

@bptato I don't think accepting the \033[= prefix is going to help, though, because the rest of the DA report isn't DEC-compatible. You can't search for a 4 parameter to check for Sixel support - it's just an ASCII encoding of the "CTerm" library name and a version number. So you'd probably have to check specifically for the parameter sequence 67;84;101;114;109 at the start (assuming all versions supported Sixel).

j4james avatar Jan 27 '25 21:01 j4james

Ah, no, by "support" I meant "don't print an error and stop looping" :P

SyncTERM (at least the version I have, from a few months ago) doesn't do private color registers, so you have to manually configure W3M_IMG2SIXEL for it anyway.

bptato avatar Jan 27 '25 23:01 bptato

  • Obscure, but SyncTERM returns \033[= to primary DA instead of \033[?; might as well support it

@bptato Yep that's obscure alright (: Done that and also skipped processing the response if it's SyncTERM

  • It seems initImage gets called twice for some reason, so detection is performed twice too :/ I guess that's why the activeImage check is there before getCharSize? Maybe you could reuse it.

afaict activeImage is only set to TRUE once and never set back to FALSE, so if w3m is started and initImage called without auto-detection, and the user then enables auto-detection, that won't work. I haven't been able to solve this.

One option could be to cache the result of auto-detection once it's done, then reuse that on subsequent calls -- is it possible for the result of auto-detection to change during a session? For example, if the user starts w3m in GNU screen in a non-sixel terminal, then reattaches in a sixel terminal? Is it possible for the TERM environment variable to be modified during a session?

el-remph avatar Jan 29 '25 19:01 el-remph

afaict activeImage is only set to TRUE once and never set back to FALSE, so if w3m is started and initImage called without auto-detection, and the user then enables auto-detection, that won't work. I haven't been able to solve this.

Ah, I see. I would have expected that I have to restart w3m, but it's definitely nicer if it works immediately :)

One option could be to cache the result of auto-detection once it's done, then reuse that on subsequent calls -- is it possible for the result of auto-detection to change during a session? For example, if the user starts w3m in GNU screen in a non-sixel terminal, then reattaches in a sixel terminal?

On GNU screen, img2sixel only seems to work with the "penetrate" hack. (But maybe I'm missing something.)

For a working example, see tmux: it reports Sixel capability even on terminals that cannot display Sixel, so you can attach both a Sixel-capable and a non-Sixel-capable terminal at the same time (and see a placeholder when appropriate).

In other words: I wouldn't worry about this.

Is it possible for the TERM environment variable to be modified during a session?

No.

bptato avatar Jan 29 '25 23:01 bptato