8

I’ve implemented this Python chat application by Saurabh Chaturvedi, to start learning about how networking works. The application is simple and makes use of the Tkinter module.

I’d like to extend the app's functionality by enabling users to type emoticons in the message box and have them rendered as emoji in the message list, when they send the messages. For example, if the user types ‘:)’ in the message box, it should render as .

So far, I've researched how to enable support for emoji in Tkinter applications. I came across Displaying emojis/symbols in Python using tkinter lib, but I don’t think it directly addresses my issue. I’m not sure if I’m currently going about solving this issue in the right way.

If it’s of any help, I’m running Windows 10 and using Python 3. And you should be able to implement the chat app by running the two scripts (server and client) described in Saurabh’s article. (Also, a potentially related problem is that emoji aren't displayed properly in my Python 3 interpreter. For example,

>>> import emoji
>>> print(emoji.emojize(":thumbs_up:"))

results in two question marks in boxes, rather than .)

Any suggestions on how I could enable emoji to be rendered in the message list?


EDIT:

As per @abarnert's comment below, it's worth mentioning that this problem did not originate from my trying to use the emoji library to translate a user’s ‘:)’ to emoji. Rather, the original question arose because I was unsure of how to go about carrying out the rendering of ‘:)’ as in a Tkinter-based app, in the first place.

  • 1
    The question you linked explains why this doesn't work, and the way to work around the problem. If you're looking for a less painful way to work around the problem, there isn't one. If you're looking for any other information that isn't that question, what information are you looking for? – abarnert Sep 07 '18 at 20:08
  • 1
    As for your related problem: it's probably not your Python 3 interpreter that has a problem displaying emoji, but the terminal (e.g., Windows cmd.exe) or terminal-ish thing (e.g., the output window in IDLE or PyCharm). If you want to ask about that, create a separate question and include the details about that (although it'll probably be a duplicate, so search first). – abarnert Sep 07 '18 at 20:10
  • @abarnert I don't yet see how the linked question explains the issue. But perhaps I haven't fully understood it. I'll go back and read it again. Regarding the related problem, thanks; I'll have a look for duplicates before posting a new question. – Caleb Owusu-Yianoma Sep 07 '18 at 20:13
  • 1
    There's not much to it: tkinter cannot handle emoji (or any other non-BMP characters), because of the bug Donal Fellows described in his answer. That's exactly the problem you're having: `emojize` is probably working fine, giving you perfectly good emoji characters, but tkinter can't handle them. The bug hasn't been fixed as of Python 3.7, and the only workaround is the one he describes. – abarnert Sep 07 '18 at 20:19
  • 1
    I haven't followed all the links so I won't post an answer, but can you just insert images instead of text? It's a lot more work, but a lot of tk widgets support images anyway... the idea would basically be to tokenize the output before displaying it, then display a stream of tk labels that are either text or pictures... or something like that – en_Knight Sep 07 '18 at 20:21
  • 1
    Basically, all you should need to do is, wherever you call `emoji.emojize(s)`, instead call `with_surrogates(emoji.emojize(s))`, using Martijn Pieters' `with_surrogates` function from the linked question. – abarnert Sep 07 '18 at 20:22
  • @en_Knight That's an interesting idea. I might give it a go. – Caleb Owusu-Yianoma Sep 07 '18 at 20:43
  • @abarnert I might be missing something. You suggest that I replace all calls of the form `emoji.emojize(s)`. But my original idea was that, for example, the user types ':)' and the app immediately renders this as when the message is sent. In that context, I don't see how or where calls of the form `emoji.emojize(s)` are being made. Could you expand, please? (If this thread isn't the best place for this, please let me know.) – Caleb Owusu-Yianoma Sep 07 '18 at 20:56
  • 1
    Somewhere in your code, you have to do something to translate the user's `:)` to emoji. Presumably in the handler that fires when the user submits a message or something. I assumed that's why you were talking about the `emoji` library, because you tried to use it to do that, and ran into the problem with tkinter not being able to display the result. If that _isn't_ how you ran into that problem, then you need to explain more. – abarnert Sep 07 '18 at 21:00
  • @abarnert I've edited the question accordingly. Hopefully, it's clearer how my original question arose: I had an idea of enabling the rendering of emoticons as emoji (as is the case in Facebook Messenger or Microsoft Word, for example), but wasn't sure how to actualise this idea. – Caleb Owusu-Yianoma Sep 07 '18 at 21:24
  • I think the comments given so far are enough for me to start implementing the workaround mentioned in the linked Stack Overflow post. – Caleb Owusu-Yianoma Sep 07 '18 at 21:36

1 Answers1

9

You have multiple problems here, and I'm not sure which one you're stuck on, so I'll try to cover everything.


First, how do you "emojify" a string?

You need to define exactly what that means—what set of inputs do you map to what outputs, is if :8 is an input does that mean 80:80 should turn the :8 in the middle into an emoji (and, if not, what exactly is the rule that says why not), etc.

Then it's not that hard to implement—whether you're looping over s.split() or a more complicated re.finditer, whether your rules need to take context outside the current pattern into account, etc. depends on what rules you chose.

At any rate, there are a number of libraries on PyPI that do a variety of variations on this, and you already found at least one, emoji. so I'll assume this problem is solved.


Next, where do you hook that into your code?

Presumably, you have a callback that gets fired whenever the user submits a chat message. I don't know exactly how you set that up, but it doesn't really matter, so let's just make up a simple example:

def callback(widget):
    msg = widget.get()
    conn.sendall(msg)
    msglog.append(f'>>> {msg}')

This function gets the string contents out of a message input widget, and then both sends that string to the server, and displays it in a message log widget. So, all you need to do is:

def callback(widget):
    msg = widget.get()
    msg = emojify(msg)
    conn.sendall(msg)
    msglog.append(f'>>> {msg}')

Third, how do you deal with the fact that tkinter can't handle emoji (and other non-BMP characters) correctly?

This part is a dup of the question you linked. Tkinter has a known bug, which is still there as of 3.7, and the best known workaround is kind of ugly.

But, while it may be ugly, it's not that hard. The answer to that question links to another question, where Martijn Pieters provides a nice with_surrogates function that you can just call on your emojified string.

If you want something simple but hacky—which will make the server basically useless with anything but a buggy tkinter client—you can do that before sending the messages over the wire:

def callback(widget):
    msg = widget.get()
    msg = emojify(msg)
    tkmsg = with_surrogates(msg)
    conn.sendall(tkmsg)
    msglog.append(f'>>> {tkmsg}')

But a cleaner solution is to send them over the wire as proper UTF-8, and only surrogatize it for display, both here:

def callback(widget):
    msg = widget.get()
    msg = emojify(msg)
    conn.sendall(msg)
    tkmsg = with_surrogates(msg)
    msglog.append(f'>>> {tkmsg}')

… and in the code that receives messages from other users:

def on_msg(msg):
    tkmsg = with_surrogates(msg)
    msglog.append(tkmsg)
abarnert
  • 354,177
  • 51
  • 601
  • 671