Tk Source Code

View Ticket
Login
Ticket UUID: e94c8bc845b4d5a658cd5421ef23ef2484560fa8
Title: X11 clipboard clear command has no effect when xclipboard is running
Type: Bug Version: 9.0.1
Submitter: anonymous Created on: 2025-05-23 23:18:51
Subsystem: 52. [clipboard] Assigned To: nobody
Priority: 5 Medium Severity: Minor
Status: Open Last Modified: 2025-06-10 17:10:05
Resolution: None Closed By: nobody
    Closed on:
Description:

The Tk clipboard commands seem to update the clipboard in some way that is invisible to clipboard managers on Mac OS. To be more precise, the first time Tk sets the clipboard, clipboard managers notice it, but all subsequent times, they don't. (See below for reproduction)

I do not think this is a bug in the clipboard managers, since I can see this behavior with not just my clipboard manager (Maccy), but also a more low-level program designed for debugging clipboard issues (Pasteboard Viewer, see the "simpler reproduction" below).

I originally experienced this bug with gitk. gitk sets the clipboard by simply running clipboard clear; clipboard append new_clipboard_contents. If this is wrong, it's a bug in gitk, let me know.

I tested this with tk 9.0.1 and tk 8.6.16, both installed via Homebrew, as well as tk 8.6.15 installed from nixpkgs. I'm using MacOS Sonoma 14.7.6 on ARM.

I am not a macOS expert, I just use it :). The author of Maccy is; perhaps he will reply if you comment on https://github.com/p0deje/Maccy/issues/1100.

Reproduction

I'm assuming that you have Homebrew installed (though you can also reproduce this with Nix, or likely see the same thing if you adapt the steps to some other setup).

After reproducing, it might help to see how Maccy and/or Pasteboard viewer interact with a normal Mac app by e.g. right-clicking on various links in Firefox and selecting "Copy link to clipboard", and observing Maccy or Pasteboard viewer.

Original reproduction with Maccy and gitk

Simpler reproduction

  • Install https://github.com/sindresorhus/Pasteboard-Viewer from https://apps.apple.com/us/app/pasteboard-viewer/id1499215709.

  • Install wish with brew install tcl-tk (installs 9.0.1). (You can also do brew install tcl-tk@8). Start Pasteboard Viewer.

  • Open wish. For Homebrew, wish would start Tk 9 (if you installed it; make sure that which wish points to Homebrew. The PATH can also point to a broken Apple-installed wish that doesn't even work on my machine), and /opt/homebrew/opt/tcl-tk@8/bin/wish starts Tk 8.

  • Monitor the Pasteboard Viewer as you run something like

clipboard append aa; # Pasteboard viewer usually shows this immediately
clipboard append bb; # Pasteboard viewer still shows `aa`
clipboard get;       # Will show `aabb`
clipboard clear
clipboard append cc

In my experience, the first of these (adding aa) is shown immediately. The rest aren't shown in Pasteboard viewer, though you can check that the clipboard got updated by running clipboard get or pasting into any application.

(You can get the Pasteboard Viewer to update by interacting with it and e.g. switching between the hex view and the text view. However, it's supposed to update without any interaction, as you can test out with non-Tk mac apps)

User Comments: marc_culler (claiming to be Marc Culler) added on 2025-06-10 17:10:05:
I have removed the fancy clipboard transient command and merged just the
fix for this bug into the main branch.  The fix makes it so that Pasteboard
Viewer sees all changes to the Tk clipboard.

However, in the course of this project a different bug, similar in nature
but affecting linux, was observed.  Specifically, when the xclipboard app
is running, the Tk clipboard clear command has no effect.  The xclipboard
app is the X11 equivalent of Pasteboard Viewer.app.

So I have changed the name of this ticket, assigned it to nobody, and will
leave it open.

marc_culler (claiming to be Marc Culler) added on 2025-06-07 04:00:46:
I have now completely redesigned this change.

The clipboard flush command is gone.

The clipboard append and clipboard get commands always (with one exceeption
explaned below) cause an increment of the changeCount, which means that the
change is noticed by Pasteboard Viewer.

There is a new command: clipboard transient ?-displayof window? data
The effect of the this command is to change the state of the pasteboard in a
way which makes the next call to clipboard append behave differently.  Namely:
* The changeCount is not incremented, so Pasteboard Viewer is not aware of
  the append.
* The text provided as data is available in the Apple pasteboard, so it will
  be pasted on the next Cmd v operation.
* The text can only be pasted once.  After the paste, the clipboard is cleared.

This should allow pasting a password into another app without having the
password beome visible to any clipboard manager which is polling the
changeCount property to detect clipboard activity.

Code that only uses get, clear and append should behave as before, except
that the appends will be visible to clipboard managers.

The clipboard transient command is a no-op on other platforms.

Finally, the branch has been renamed as clipboard_transient.

@ilyagr: Can you please test the clipboard_transient branch?  Thanks.

marc_culler (claiming to be Marc Culler) added on 2025-06-05 19:46:02:
@ilyagr: Thank you for testing!

I am coming around to your point of view on this.  I now agree that the
default behavior should be to make the clipboard copies viewable by a clipboard
manager.  One major advantage of this is that making that change, without
changing the API, turns this project into a bugfix - not a change requiring
a TIP.

I am still concerned about how to make it possible to keep things like passwords
away from clipboard managers.  (OK.  I admit that I am working on a Tk-based
password manager.) But accommodating that situation should not be part of
the default behavior.  One fact which supports that last statement is that
when the NSApp becomes the owner of a NSPasteboard type, the name of the type is
public.utf8-plain-text.  Public data should be public.

I have some ideas for how to deal with private data, but I intend to make
the default behavior become the one which makes all clipboard copies available
to a a clipboard manager which is watching the changeCount.  Stay tuned ...

ilyagr added on 2025-06-05 00:40:09:

The other question that's unclear to me is whether there's any downsides to doing clipboard flush automatically after clipboard clear or clipboard append, instead of adding a new command.

As a quick test,

clipboard clear
clipboard append blah; clipboard flush
clipboard append bleh; clipboard flush

works as expected. (As I said previously, clipboard clear; clipborad flush does not quite currently work as expected, but that's a separate issue; it doesn't mess up subsequent clipboard append-s in any way).

This is a bit of a fraught decision, since I don't know how to prove that no Tk program relies on the old behavior. It's hard for me to imagine why they would intend it, and I imagine most would benefit from this change. Still, programs could rely on the old behavior in some way or for some reason I haven't imagined.


ilyagr added on 2025-06-04 21:35:02:

Thank you!

I did manage to compile your branch (following the instructions for building Wish.app), and it seems to work at a first glance. In particular, clipboard clear; clipboard append something; clipboard flush works great, every time, even if you run it several times in a row while changing the something.

I found one minor bug: if you run clipboard clear; clipboard flush, the clipboard manager does not detect that the clipboard became empty.

Re your long post

The post is mostly great, but there is one point that seems slightly off. (Unfortunately, I guess there is no way to edit comments?)

So appends to the Tk clipboard, except for the first append after a clear command, will not be visible to a clipboard manager which uses this strategy.

In my experience, only the first append after the first clear command is visible to Pasteboard Viewer/Maccy. If this quote were correct, and the first clipboard append after every clipboard clear worked reliably, gitk would not have a problem. It always runs clipboard clear before every clipboard append.

Again, what actually happens is that only the first clipboard append after another program copied something to the clipboard is visible to clipboard managers. This is consistent with everything else you say in your post AFAICT, and your fix addresses this fine.

Aside on how other tools seem to work

I also played with Firefox a bit more. I opened about:about, then opened the developer tools, and tried running the following in the JS console:

await navigator.clipboard.writeText("aa")
await navigator.clipboard.writeText("bb")

The clipboard manager picked up both changes immediately. I have no idea whether Firefox is doing the same thing you are suggesting, or something else.


marc_culler (claiming to be Marc Culler) added on 2025-05-31 22:25:30:
I have implemented my proposed extension of the clipboard command which adds
a new action "flush".

It works as I proposed, and I think it would allow gitk to work as you would
like.  Testing with the Pasteboard Viewer I see that the first append to a
cleared clipboard is shown on the Pasteboard Viewer.  A second append is not
shown in the Viewer although the appended text does appear when I do a paste.
If I call `clipboard flush` then the Pasteboard Viewer updates to show the
appended text.

Since this would be a change to the Tk API, a TIP would be required.  But the
first question would be whether it works.

@ilyagr: The changes are in the clipboard_flush branch of the Tk fossil
repository.  Would you be able to clone, build and install that branch and
check whether the `clipboard flush` command works for you with gitk?

marc_culler (claiming to be Marc Culler) added on 2025-05-31 15:41:21:
The purpose of this post is to record how the Tk clipboard currently works
on macOS, and suggest a possible extension.

Here are the main points about the current implementation:

* Apple declares a protocol named NSPasteboardTypeOwner.  In order to support
  this protocol an object must implement the method
  - pasteboard: provideDataForTyype:

The protocal also includes a callback method - pasteboardChangedOwner: which
will be called on the current owner of a pasteboard type when a new Object
becomes the owner.

* The TKApplication subclass of NSApplication supports the
  NSPAsteboardTypeOwner protocol but does not implement the callback method.
  The only type that it supports is the String type.

* The TKApplication instance behaves as follows:
  + It maintains a string which is cleared by `clipboard clear`, appended to
    by `clipboard append` and returned by `clipboard get`.
  + If Tk's string is empty, and `clipboard append` is called, the TKApplication
    registers itself as the owner of the String Type for the general
    NSPasteboard.
  + The TKApplication's implementation of -pasteboard: provideDataForType
    just provides a copy of its saved string.

A consequence of this design is that only an append to an empty string
will change the owner of the String type for the general Pasteboard.
Therfore, only an append to an empty saved string will change the value
of the changeCount attribute of the general NSPasteBoard.

To avoid unnecessary copies of the clipboard content, a typical clipboard
manager evidently uses the changeCount value to determine whether the value
of the general NSPAsteboard string Type has changed, even though that is not
what the changeCount means - it means that the owner has changed, not
that the value has changed. So appends to the Tk clipboard, except for
the first append after a clear command, will not be visible to a clipboard
manager which uses this strategy.

It is possible (actually, likely) that Apple is not telling the whole truth
about the meaning of changeCount.  Possibly when the owner is nil and the
value of the String Type is changed, but the owner remains nil, the changeCount
gets incremented.  An experiment would be needed to see if that is the case
since it is not covered in the documentation (as usual).  If so, it muddles
the meaning of changeCount, but makes it closer to having the meaning assumed
by the clipboard managers, but only in the case where the owner of the String
type is always nil.

I now think that the motivation for the Tk design was simply to match the Apple
clipboard protocol to the Tk protocol in a clean way.  I doubt that privacy
was a consideration then.  I do think it is a consideration now, although I
don't really think that any accidental privacy benefits of the current design
are very effective.

Here is an idea for how to address this issue in Tk.

Perhaps the Tk clipboard command could be extended by adding a new action:
`clipboard flush`. On macOS the implementation of `clipboard flush` would
be that the TKApplication would resign ownership of the general NSPasteboard 
String Type by assigning a nil owner and (as is required) immediately sending
the entire saved string to the general NSPasteboard.  The saved string would
not change, but the ownership status would be recorded in a state variable.
Essentially, non-ownership status would replace the prior meaning of having
an empty saved string.  So a subsequent append to the Tk clipboard would
result in Tk taking ownership of the String Type until the next clear or
flush.  This would also involve implementing the callback method, which would
be used to maintain the ownership state variable.

Quite possibly other platforms would not have any meaningful way of implementing
the flush action.  So I guess it could just be a no-op on other platforms.

ilyagr added on 2025-05-30 21:50:40:

gitk does not need to explicitly interact with a clipboard manager. However, it would be more convenient to use with a clipboard manager if that worked like it does with every other program.

Specifically, gitk has a command to copy the current commit's description to the clipboard, and it has an option where clicking on each commit results in copying the commit id to the clipboard automatically.

The latter mode is inconvenient without a clipboard manager (or while gitk doesn't interact well with clipboard managers), since without a clipboard manager, copying to the clipboard is a destructive operation.

Also, when talking about several commits, it's convenient to use the command to copy a commit's description several times in a row (on several commits), without switching to any other window. Then, you can (say) switch to the window where you are writing an email and paste all of these descriptions as you are writing about them, one after another.

There is probably some room for gitk to offer a more convenient interface here, but a clipboard manager really helps with the current interface.


marc_culler (claiming to be Marc Culler) added on 2025-05-30 21:25:36:
Can you please explain why gitk needs to interact with a clipboard
manager?  I.e. why it is not sufficient for gitk to write to the clipboard
so a user can then paste the clipboard into a different application?

ilyagr added on 2025-05-30 20:49:23:

Aside regarding password managers: You may know this already, but there are features to prevent clipboard managers from capturing passwords, but they are imperfect. For example, Bitwarden'd macOS desktop client supports this feature, but the browser extension currently does not. I'm not sure whether something similar exists on Windows/Linux; I think something does exist, but it's a different API.

I, personally, don't find it a huge hassle to manually remove the passwords from the clipboard manager's history, but it's certainly something to be aware of.


ilyagr added on 2025-05-30 20:41:21:

Thank you again for looking into it!

Again, I'm not an expert, but you diagnosis seems reasonable to me.

I can't judge why Tk behaves the way it does, and I'm not sure about the best course of action. Two naive thoughts:

  • Is it possible to copy the value lazily but then somehow explicitly update the changeCount counter?

  • I'm not sure about the privacy advantages of lazy copying. It might mean that a clipboard manager or another program that wanted to snoop couldn't be very efficient, but I would guess it could still check the clipboard's value every X seconds (without consulting the changeCount), if it cared about the snooping more than it cared about efficiency.

I'm not sure whether there are other reasons for lazy copying or what other changes in behavior that would occur if copying became strict.

My first guess would be that strict vs lazy copying would mainly make a difference when copying large amount of data into the clipboard. I did not test how pbcopy or Firefox or other Mac OS apps are supposed to behave when doing so. Perhaps Apple mentions something about that in their docs. The usecase I had in mind (gitk) never copies large amounts of data with clipboard append.


marc_culler added on 2025-05-29 22:44:13:
I notice that, anticipating the conclusion of my post, I accidentally referred
to "password manager" when I meant to say "clipboard manager": I meant:
"it makes sense that a clipboard manager which is watching the changeCount
of the NSPasteboard will not be aware that anything has changed."

marc_culler (claiming to be Marc Culler) added on 2025-05-29 22:36:16:

I may have found some clues about this.

The internet says that there is no way to get notification when an item is added to the general NSPasteBoard. The only way to handle this seems to be to poll the pasteboard at some time interval to see whether the value of its changeCount has changed. I will assume that this is what your clipboard managers do. Moreover, the internet suggests that not providing a notification was an intentional decision on Apple's part.

The Apple documentation says "The change count subsequently increments each time the pasteboard ownership changes." That does not mean that the changeCount will be incremented if the string value of the first item on the pasteBoard changes. Especially given that it is allowed for the string value of an item on the pasteBoard to be provided lazily. And it appears that is what Tk is doing.

The function TkSelUpdateClipboard does this:

changeCount = [pb addTypes:[NSArray arrayWithObject:NSStringPboardType]
                      owner:NSApp];

The TKApplication subclass of NSApplication has a method tkProvidePasteboard which appears to be the function that the NSPasteBoard calls when it needs to know the value of a string item which Tk has placed on the pasteboard.

In your example, I am guessing, the first call to "clipboard append" makes Tk's NSApplication object become the owner of the clipboard. The second append changes the value of the first item on the clipboard, which is only known to Tk since it is being provided lazily, and does not change the owner of the clipboard, which remains set to Tk's TKApplicaton. if the ownership does not change and the actual string value is not known to the NSPasteBoard then it makes sense that a password manager which is watching the changeCount of the NSPasteboard will not be aware that anything has changed.

The owner is allowed to be nil. According to Apple, when the owner is nil the the value of the first item on the NSPasteBoard must be provided immediately. Here is an example from the Chrome browser, which uses the general NSPasteBoard that way:

void Clipboard::WriteText(const char* text_data, size_t text_len) {
  std::string text_str(text_data, text_len);
  NSString *text = base::SysUTF8ToNSString(text_str);
  NSPasteboard* pb = GetPasteboard();
  [pb addTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
  [pb setString:text forType:NSStringPboardType];
}

The call to the addTypes method, with nil owner, is immediately followed by a call to the setString method.

It seems likely that changing from providing the string value lazily to providing it immediately would "fix" the behavior being reported in this ticket. However, that is not a trivial change, and it may be unwise to "fix" it.

Experience suggests that Tk design decisions, like whether to provide clipboard string values lazily, were usually made for a reason. Changing a basic part of the design almost always breaks things. Experience also suggests that it will be difficult to predict in advance which things will break. After that is finally known, the question remains whether the broken things were more important than making sure that a random clipboard manager is notified whenever Tk puts anything on its clipboard.

I know of one particular context where not notifying other apps that the clipboard has changed is a very important positive feature. That is for a Tk based password manager. Many password managers allow copying the password to the clipboard so that the user can then paste it into a web browser. That operation is a lot safer if it is not possible for some other app that happens to be running on the same computer to record everything which gets placed on the clipboard.

I think it is quite likely that this sort of privacy issue was what was behind the choice to provide values for clipboard strings lazily. Perhaps changing terminology from "clipboard manager" to "clipboard snooper" would help to view this issue from a better perspective.


ilyagr added on 2025-05-29 22:28:31:

Thank you!

As I mentioned before, the minimal C example from https://nathancraddock.com/blog/writing-to-the-clipboard-the-hard-way/ and https://github.com/natecraddock/sandbox/tree/main/objc seem to work fine too. However, they set the clipboard, they don't do clipboard append.


marc_culler (claiming to be Marc Culler) added on 2025-05-29 11:42:01:
Well, maybe there is something Tk should be doing to notify other apps about
a write to the clipboard.  I didn't think so, since there would seem to be no
way for Tk to know who the listeners are.

I will look at the code for these clipboard managers and see if I can figure
out how they are getting notified.

ilyagr (claiming to be original poster, made an account) added on 2025-05-29 06:05:09:

The pasting is not the problem, the correct info is getting pasted either way.

The difference is in when or whether a clipboard manager (or Pasteboard viewer) detect that the clipboard changed. When I copy from Firefox or when I use pbcopy to copy from the terminal, both the clipboard manager and the Pasteboard viewer get updated immediately without further interaction. When I copy using clipboard append, this does not happen.

So, I can't be 100% sure that Pasteboard Viewer or Maccy (the clipboard manager) are bug-free, but I'm 100% sure that clipboard append copies the text in a way that interacts worse with them compared to the way pbcopy copies text.

Unfortunately, this does also mean that I don't know a good way to detect the problem without these tools, since I don't understand what the difference is.


marc_culler (claiming to be Marc Culler) added on 2025-05-28 13:20:00:
I repeated my experiment, but pasting into TextEdit to see what was in
the clipboard.  Again, it seemed to behave as expected.

marc_culler (claiming to be Marc Culler) added on 2025-05-28 13:06:16:
It seems like the first question would be whether this is a bug in Tk or
a bug in Pasteboard Viewer (or a bug in Apple's clipboard management).

Your statement:
> (You *can* get the Pasteboard Viewer to update by interacting with it
> and e.g. switching between the hex view and the text view.

strongly suggests that it is not Tk that is misbehaving, although the claim
that you see the failures with non-Tk mac apps suggests otherwise.

I tried running your clipboard command commands in wish and
pasting into a different Terminal window, Terminal being a non-Tk app. After
appending aa I pasted in the other Terminal window and got aa.  After
appending bb I got aabb in the other window.  After the get, clean and
append cc I got aabbcc.

So it is not clear to me how to reliably reproduce the problem, which would
be needed in order to degug it.  It would be great if you could
provide a self-contained script so that we could focus on debugging Tk,
rather than wondering whether we are actually seeing bugs in various
clipboard managers that are being used for testing.

anonymous added on 2025-05-24 23:43:15:

A workaround is to use pbcopy, which doesn't have this problem. Example: https://github.com/j6t/gitk/pull/19.


anonymous added on 2025-05-24 02:46:17:

For reference, I found a seemingly very nice and minimal implementation of copying to clipboard on a Mac in C or objective C here:

https://nathancraddock.com/blog/writing-to-the-clipboard-the-hard-way/#fnref3

https://github.com/natecraddock/sandbox/tree/main/objc

This implementation seems to work fine with Maccy or Pasteboard Viewer. To test it, I just followed the first set of instructions in the README (which use lua).

Its one downside is that this example implements a "clipboard set" function, while Tk seems to need an "clipboard append" function.