X Tutup
The Wayback Machine - https://web.archive.org/web/20230217045845/https://github.com/python/cpython/issues/84632
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tkinter/IDLE: preserve clipboard on closure #84632

Open
E-Paine mannequin opened this issue Apr 30, 2020 · 45 comments
Open

Tkinter/IDLE: preserve clipboard on closure #84632

E-Paine mannequin opened this issue Apr 30, 2020 · 45 comments
Assignees
Labels
expert-IDLE expert-tkinter type-bug An unexpected behavior, bug, or error

Comments

@E-Paine
Copy link
Mannequin

E-Paine mannequin commented Apr 30, 2020

BPO 40452
Nosy @terryjreedy, @taleinat, @serhiy-storchaka, @eryksun, @pablogsal, @E-Paine
PRs
  • bpo-40452 #19819
  • gh-84632: IDLE: fix clipboard being cleared upon exit #26163
  • Files
  • tkinter-clipboard-on-exit.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/terryjreedy'
    closed_at = None
    created_at = <Date 2020-04-30.17:35:47.539>
    labels = ['type-bug', 'expert-tkinter', '3.9', '3.10', '3.8', 'expert-IDLE']
    title = 'Tkinter/IDLE: preserve clipboard on closure'
    updated_at = <Date 2021-10-28.20:01:31.523>
    user = 'https://github.com/E-Paine'

    bugs.python.org fields:

    activity = <Date 2021-10-28.20:01:31.523>
    actor = 'primexx'
    assignee = 'terry.reedy'
    closed = False
    closed_date = None
    closer = None
    components = ['IDLE', 'Tkinter']
    creation = <Date 2020-04-30.17:35:47.539>
    creator = 'epaine'
    dependencies = []
    files = ['49173']
    hgrepos = []
    issue_num = 40452
    keywords = ['patch']
    message_count = 45.0
    messages = ['367765', '369002', '369135', '369156', '369165', '369199', '369287', '369304', '369322', '369334', '369338', '369383', '369387', '369389', '369391', '369392', '369395', '369406', '369440', '369444', '369453', '369456', '369601', '376354', '380199', '382635', '393742', '393743', '393750', '393753', '393755', '393756', '393757', '393770', '393788', '393790', '393794', '393795', '393797', '393798', '393804', '393805', '393806', '393817', '393818']
    nosy_count = 8.0
    nosy_names = ['terry.reedy', 'taleinat', 'serhiy.storchaka', 'eryksun', 'pablogsal', 'epaine', 'Spacekiwigames', 'primexx']
    pr_nums = ['19819', '26163']
    priority = 'normal'
    resolution = None
    stage = 'needs patch'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue40452'
    versions = ['Python 3.8', 'Python 3.9', 'Python 3.10']

    @E-Paine
    Copy link
    Mannequin Author

    E-Paine mannequin commented Apr 30, 2020

    Currently, on a Windows machine, the clipboard contents is lost when the root is closed. This, therefore requires you to keep the IDLE instance open until after the copy has been complete (particularly annoying when copying between different IDLE instances). The solution is to pipe the tkinter clipboard contents to "clip.exe" (built-in to Windows) which will preserve it after root closure.

    @E-Paine E-Paine mannequin added the 3.9 label Apr 30, 2020
    @E-Paine E-Paine mannequin assigned terryjreedy Apr 30, 2020
    @E-Paine E-Paine mannequin assigned terryjreedy Apr 30, 2020
    @E-Paine E-Paine mannequin added expert-IDLE type-feature A feature request or enhancement labels Apr 30, 2020
    @terryjreedy
    Copy link
    Member

    Please write out a manual test example (steps 1, 2, ..., N) that fails now and passes with the patch.

    @terryjreedy terryjreedy changed the title IDLE preserve clipboard on closure (Windows) IDLE: preserve clipboard on closure on Windows May 16, 2020
    @terryjreedy terryjreedy changed the title IDLE preserve clipboard on closure (Windows) IDLE: preserve clipboard on closure on Windows May 16, 2020
    @E-Paine
    Copy link
    Mannequin Author

    E-Paine mannequin commented May 17, 2020

    Test example:

    1. Open IDLE (shell or editor)
    2. Copy some text in the IDLE window
    3. Close the IDLE window (instance should end)
    4. Paste into application of choice
      Without the patch, the clipboard is cleared when the instance ends so nothing is pasted. With the patch, the content remains in the clipboard as expected.

    Note: This is not a problem if clipboard history is turned on or the content is pasted before closure, however the order of events above is fairly common for me.

    Encoding:

    In the latest commit to the pull, I have changed the encoding from UTF-8 to UTF-16 and my reasoning is as follows:

    1. Most importantly, using UTF-8, characters with a unicode value >=2^8 are incorrectly copied as multiple characters. UTF-8 does support these characters but uses a different number of bytes per character (which is presumably what is causing these issues - for example, "AĀ" is encoded as "\x41\xc4\x80", which pastes as "A─Ç") UTF-16, however, correctly works for all characters supported by Tcl (see next point).

    2. "Strings in Tcl are encoded using 16-bit Unicode characters" (https://www.tcl.tk/man/tcl8.2.3/TclCmd/encoding.htm). Therefore, the encoding we choose should have at least 16 bits allocated per character (meaning either UTF-16 or UTF-32).

    3. Windows' "Unicode-enabled functions [...] use UTF-16 (wide character) encoding, which is [...] used for native Unicode encoding on Windows operating systems" (https://docs.microsoft.com/en-us/windows/win32/intl/unicode). For me, this was what decided the new encoding (between the two given in the previous point).

    @terryjreedy
    Copy link
    Member

    Now that I think about it, I have run into enough problems with ^V not pasting something copied with ^C that I always leave source windows open until successful. I had not noticed that there is only a problem between windows (but this may be true) or after closing IDLE or that it only occurs when copying *from* IDLE. In fact, the last might not be true if other apps have the same bug. (I will start paying more attention after this is fixed.)

    @terryjreedy terryjreedy added type-bug An unexpected behavior, bug, or error and removed type-feature A feature request or enhancement labels May 17, 2020
    @terryjreedy
    Copy link
    Member

    eryksun, Piping to clip.exe is not working well. On the patch, I asked if you know what Windows system call it uses, but I cannot request your review there.

    @taleinat
    Copy link
    Contributor

    I can reproduce this behavior on macOS as well, so this doesn't seem to be Windows-specific.

    This does not happen with other apps in general, so it is not normal behavior for apps.

    Testing with a minimal tkinter app (see code below) gives similar behavior, so this appears to be an issue with tkinter and/or tcl/tk.

    import tkinter
    text = tkinter.Text()
    text.pack()
    tkinter.mainloop()

    @E-Paine E-Paine mannequin closed this as completed May 18, 2020
    @E-Paine E-Paine mannequin closed this as completed May 18, 2020
    @taleinat
    Copy link
    Contributor

    I closed the PR but IMO this issue should remain open.

    I am changing the title, though, since this is not actually Windows-specific.

    @taleinat taleinat changed the title IDLE: preserve clipboard on closure on Windows IDLE: preserve clipboard on closure May 18, 2020
    @taleinat taleinat reopened this May 18, 2020
    @taleinat taleinat changed the title IDLE: preserve clipboard on closure on Windows IDLE: preserve clipboard on closure May 18, 2020
    @terryjreedy
    Copy link
    Member

    Serhiy: what do you know of tcl finalization?

    Tal: why does your patch only expose the option that you said crashes ubuntu.

    Spacekiwigames: if you have a cpython fork and local repository, you could try the patch on your linux mint.

    @terryjreedy
    Copy link
    Member

    Tal: cancel comment above. I was confused because FinalizeThread is exposed as just finalize.

    On Windows, PR fixes issue whether Shell or editor is closed last.

    @terryjreedy
    Copy link
    Member

    Can we backport? The change to _tkinter, not directly exposed in tkinter, could be considered an enhancement, but is necessary to fix what I consider a real bug in tkinter, and hence in IDLE.

    Pablo, would you allow the _tkinter change in .0b2?

    If we cannot backport the _tkinter change, can the tcl function be accessed through root.tk.call('????')? I am guessing that 'exit' exits the process from tcl, which is probably not what we want.
    https://www.tcl.tk/man/tcl8.6/TclLib/Exit.htm
    suggests that when tcl is loaded as an extention, Tcl_Finalize should be called instead of Tcl_Exit to *not* exit the process.

    @terryjreedy
    Copy link
    Member

    As Tal notes on the PR, the _tkinter patch is the minimum needed to fix the problem for IDLE, not the undetermined patch to tkinter that should be done to properly expose the fix to all tkinter users.

    If the tcl function were exposed with a presumably temporary private name, '_finalize_tcl', rather than a public name, 'finalize_tcl', then I presume we should be able to backport without question.

    @taleinat
    Copy link
    Contributor

    Can we backport? The change to _tkinter, not directly exposed in tkinter, could be considered an enhancement, but is necessary to fix what I consider a real bug in tkinter, and hence in IDLE.

    I'm +1 for backporting the latest PR. It only adds the clearly internal and undocumented _tkinter.finalize_tcl(), which is only used by IDLE.

    @taleinat taleinat added 3.11 and removed 3.8 labels May 16, 2021
    @pablogsal
    Copy link
    Member

    Pablo, would you allow the _tkinter change in .0b2?

    I checked the change and I think is scoped enough and doesn't change the public API (and it solves a bug). Is fine with me, but please make sure of the following:

    • At least a core dev reviews and approves the change.
    • You run with the buildbots before merging.

    Thanks for checking!

    @serhiy-storchaka
    Copy link
    Member

    What if register Tcl_Finalize as an atexit callback?

    @taleinat
    Copy link
    Contributor

    What if register Tcl_Finalize as an atexit callback?

    Precisely that was tried a long time ago (2002?) and decided against, as can be seen by the comment left behind at the end of _tkinter.c:

    #if 0
        /* This was not a good idea; through <Destroy> bindings,
           Tcl_Finalize() may invoke Python code but at that point the
           interpreter and thread state have already been destroyed! */
        Py_AtExit(Tcl_Finalize);
    #endif

    @serhiy-storchaka
    Copy link
    Member

    That comment may be outdated. The atexit callbacks are called very early at the interpreter shutdown stage. The interpreter is still entirely intact at this point.

    @taleinat
    Copy link
    Contributor

    Wow, I'm very surprised! Simply uncommenting that works perfectly on Ubuntu 20.04 and Windows 10.

    I'll make a new PR...

    @serhiy-storchaka
    Copy link
    Member

    It was commented out in

    commit 43ff868
    Author: Guido van Rossum <guido@python.org>
    Date: Tue Jul 14 18:02:13 1998 +0000

    Temporarily get rid of the registration of Tcl_Finalize() as a
    low-level Python exit handler.  This can attempt to call Python code
    at a point that the interpreter and thread state have already been
    destroyed, causing a Bus Error.  Given the intended use of
    Py_AtExit(), I'm not convinced that it's a good idea to call it
    earlier during Python's finalization sequence...  (Although this is
    the only use for it in the entire distribution.)
    

    Maybe Guido remember more details about reproducing a Bus Error. Was it an issue from the other side, when the Tcl/Tk interpreter was used after calling Tcl_Finalize()?

    @E-Paine
    Copy link
    Mannequin Author

    E-Paine mannequin commented May 17, 2021

    Simply uncommenting that works perfectly on Ubuntu 20.04 and Windows 10.

    I can't remember the details, but I don't think this worked if the Tcl interpreter had been garbage collected before the Python interpreter shutdown. Therefore, Tcl_Finalize (or similar) would preferably be called at Tkapp dealloc, but we cannot do that because the user may create more interpreters later. I will try and give a reproducible example when I have time.

    @terryjreedy
    Copy link
    Member

    Tcl_Finalize can be called more than once, says the tcl doc. It also says it, or T_FThread should be called when unloading. If the tcl implementation on some system does not match the doc, then there is a bug that should be reported.

    If the registration is done by tkinter, rather than IDLE, then all tkinter apps benefit.

    @terryjreedy terryjreedy added 3.8 and removed 3.11 labels May 17, 2021
    @taleinat
    Copy link
    Contributor

    Tcl_Finalize can be called more than once, says the tcl doc.

    I take that to mean that when calling Tcl_Finalize more than once, the second and following calls will do nothing rather than crash the process, corrupt memory etc.

    The Tcl docs are quite clear that Tcl is no longer usable after calling Tcl_Finalize. Therefore, E. Paine's latest comment appears to be correct in the sense that we can't call Tcl_Finalize when cleaning up a tkinter.Tk instance. The same goes for Tcl_FinalizeThread.

    @taleinat
    Copy link
    Contributor

    After thinking about this a bit more, it seems to me that using an atexit handler is definitely worth pursuing, but not for 3.10.0.

    I'm in favor of merging the existing PR as an immediate fix for IDLE, and following up with a general solution for tkinter using atexit, which should also include some tests so that we can see that it works on our CI and buildbots.

    @E-Paine
    Copy link
    Mannequin Author

    E-Paine mannequin commented May 17, 2021

    I don't think this worked if the Tcl interpreter had been garbage collected

    Turns out I was wrong, it appears it *does* work. The only thing we need to change is ensuring the GIL is held when calling finalise (because, as Guido's comment says, this process can call Python code).

    @gvanrossum
    Copy link
    Member

    I don't recall anything about that, sorry.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    expert-IDLE expert-tkinter type-bug An unexpected behavior, bug, or error
    Projects
    Status: In Progress
    Development

    No branches or pull requests

    5 participants
    X Tutup