Skip to content

Fix fread clipboard handling on Windows (fixes #1292)#7640

Open
AmanKashyap0807 wants to merge 8 commits intoRdatatable:masterfrom
AmanKashyap0807:fix-fread-clipboard-issue1292
Open

Fix fread clipboard handling on Windows (fixes #1292)#7640
AmanKashyap0807 wants to merge 8 commits intoRdatatable:masterfrom
AmanKashyap0807:fix-fread-clipboard-issue1292

Conversation

@AmanKashyap0807
Copy link

Closes #1292

Fix fread clipboard handling on Windows using readClipboard(), add test (#2366), and update NEWS.

Changed files :

  • R/fread.R : Added logic to detect clipboard inputs on Windows, reading it by using readClipboard(), handle errors and process clipboard content as a temporary file for parsing.
  • inst/tests/tests.Rraw : Added test 2366 for clipboard functionality.
  • Updated NEWS.md with a brief entry under BUG FIXES.

To keep the scope of issue #1292, I only solved it for Windows. I'd be happy to submit another PR for macOS and Linux later.

Reproduction:

# Write tab delimited data to clipboard
writeLines("a\tb\n1\t4\n2\t5\n3\t6\n", "clipboard")

# read.delim works
read.delim("clipboard-128")
# [1] read.delim.clipboard.128.
# <0 rows> (or 0-length row.names)
# Warning message:
# In read.table(file = file, header = header, sep = sep, quote = quote,  :
#   incomplete final line found by readTableHeader on 'clipboard'

# fread fails
library(data.table)
fread("clipboard-128")
# Error in fread("clipboard-128") : 
#   File 'clipboard-128' does not exist or is non-readable. getwd()=='D:/OpenSource/data.table'

fread() does not recognize "clipboard" / "clipboard-128" as clipboard input on Windows. Due to the long period since the issue was reported, there might have been some changes in fread() over time, which is why the error message differs now, but the core problem is the same, clipboard input is not detected.

Environment:

  • OS: Windows
  • R: 4.5.2

Please let me know if any changes are needed.

@codecov
Copy link

codecov bot commented Feb 9, 2026

Codecov Report

❌ Patch coverage is 3.22581% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.85%. Comparing base (67e7840) to head (5e33fe9).

Files with missing lines Patch % Lines
R/fread.R 3.22% 30 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #7640      +/-   ##
==========================================
- Coverage   99.02%   98.85%   -0.18%     
==========================================
  Files          87       87              
  Lines       16897    16928      +31     
==========================================
+ Hits        16733    16734       +1     
- Misses        164      194      +30     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@AmanKashyap0807
Copy link
Author

Hi @MichaelChirico,

I’ve synced the branch with master.
The atime check is still hitting an 'invalid reference' error, which I suspect might be a CI issue with external forks. Also, the Codecov failure is likely due to my changes being Windows-specific while the CI runs on Linux.

Let me know if there’s anything else I should do on my end to help resolve these!

R/fread.R Outdated
} else if (grepl("^clipboard(-[0-9]+)?$", tolower(input))) {
is_windows = identical(.Platform$OS.type, "windows")
if (is_windows) {
clip = tryCatch(utils::readClipboard(), error = identity)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't normally use namespacing for functions from utils, see e.g. head/tail

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have blanket utils import so it'll need to be included:

importFrom(utils, capture.output, contrib.url, download.file, flush.console, getS3method, head, packageVersion, tail, untar, unzip)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

readClipboard() is documented as Windows‑only in the utils::clipboard help:
https://search.r-project.org/R/refmans/utils/help/clipboard.html
There are also reports on non‑Windows where readClipboard() simply isn’t available even with utils loaded,
(https://stackoverflow.com/questions/24365249/readclipboard-removed-from-utils-package)
so importing it unconditionally in NAMESPACE risks errors when the package is loaded on those platforms. That’s why I kept utils::readClipboard() inside the Windows guard.

Regarding head/tail, data.table::last() has a small fallback that explicitly calls utils::head and utils::tail instead of using them unqualified everywhere, and that pattern was in my mind when I treated utils::readClipboard() as a similar Windows‑specific special case. I’m happy to change this to plain readClipboard() with a NAMESPACE import if you prefer and are confident it behaves well on non‑Windows platforms.

@ben-schwen
Copy link
Member

I would also prefer to add support for all major systems once and not part after part.

The commands should be the following:

OS Command
Windows readClipboard()
Mac system("pbpaste", intern = TRUE)
Linux (xsel) system("xsel --clipboard --output", intern = TRUE)
Linux (xclip) system("xclip -o -selection clipboard", intern = TRUE)

@AmanKashyap0807
Copy link
Author

@ben-schwen thanks for outlining the commands. I’ll work on adding support for macOS and Linux using these and push an update soon.

@AmanKashyap0807
Copy link
Author

Hi @ben-schwen, while looking into the Linux clipboard side I noticed that newer distros (Ubuntu 22.04+, Fedora) default to Wayland, so I’m planning to add wl-paste alongside xclip/xsel to keep clipboard reading working smoothly there.

For testing:

  • Linux: I'll test xclip/xsel and wl-paste on a WSL
  • macOS: I'll test pbpaste on a friend's Mac.

Let me know if you'd like me to do it differently or if you'd like me to add more commands.

@AmanKashyap0807
Copy link
Author

I've pushed an update adding clipboard support for macOS and Linux as discussed.

  • macOS: Uses pbpaste.
  • Linux: Prioritizes wl-paste (for Wayland), then checks for xclip, and finally xsel (for X11)

Let me know if there any adjustment are required.

NEWS.md Outdated

5. Non-equi joins combining an equality condition with two inequality conditions on the same column (e.g., `on = .(id == id, val >= lo, val <= hi)`) no longer error, [#7641](https://github.com/Rdatatable/data.table/issues/7641). The internal `chmatchdup` remapping of duplicate `rightcols` was overwriting the original column indices, causing downstream code to reference non-existent columns. Thanks @tarun-t for the report and fix, and @aitap for the diagnosis.

6. `fread("clipboard")` now works on Linux (via `xclip`, `xsel`, or `wl-paste`), macOS (via `pbpaste`), and Windows, [#1292](https://github.com/Rdatatable/data.table/issues/1292). Thanks @mbacou for the report, @ben-schwen for the suggestion, and @AmanKashyap0807 for the fix.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls elaborate more what this means, e.g. that fread supports now reading from the clipboard via fread("clipboard")

@aitap
Copy link
Member

aitap commented Feb 19, 2026 via email

} else {
warning("Clipboard reading is not supported on this platform.", call. = FALSE)
}
if (!length(clip) || !any(nzchar(trimws(clip)))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on non windows and non-unix this will error because of trimws(clip) but clip was never assigned

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have now added return(data.table()) inside the else block for unsupported platforms.

@AmanKashyap0807
Copy link
Author

Hi @ben-schwen and @aitap I have pushed changes addressing your comments

Platform group OS.type command
Windows windows readClipboard()
macOS unix pbpaste
Linux/BSD/Solaris (X11/Wayland) unix wl-paste/xclip/xsel

Please let me know if anything should be adjusted

Comment on lines +76 to +81
} else if (nzchar(Sys.which("wl-paste"))) {
"wl-paste --no-newline"
} else if (nzchar(Sys.which("xclip"))) {
"xclip -o -selection clipboard"
} else if (nzchar(Sys.which("xsel"))) {
"xsel --clipboard --output"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This choice should probably depend not on what's available in the $PATH, but what's actually running. It's possible to have a Wayland compositor & tools installed but an X11 session running, and vice versa.

For X11 clients to work, the DISPLAY environment variable is required to be set.

For Wayland, WAYLAND_DISPLAY may be set, but Wayland clients can work without it, defaulting to wayland-0. Moreover, a Wayland session is likely to be running with $DISPLAY set, ready to launch Xwayland.

So if WAYLAND_DISPLAY is set, it's definitely a Wayland session, but if only DISPLAY is, then it's probably an X11 session, but it could also be a Wayland session in disguise.

Why not follow identical(Sys.info()[["sysname"]], "Darwin")) when choosing to run pbpaste?


5. Non-equi joins combining an equality condition with two inequality conditions on the same column (e.g., `on = .(id == id, val >= lo, val <= hi)`) no longer error, [#7641](https://github.com/Rdatatable/data.table/issues/7641). The internal `chmatchdup` remapping of duplicate `rightcols` was overwriting the original column indices, causing downstream code to reference non-existent columns. Thanks @tarun-t for the report and fix, and @aitap for the diagnosis.

`fread()` now supports reading from the system clipboard by passing `input="clipboard"`, implemented for Windows, macOS (via `pbpaste`), and Linux (via `wl-paste`, `xclip`, or `xsel`), [#1292](https://github.com/Rdatatable/data.table/issues/1292). Thanks @mbacou for the report, @ben-schwen and @aitab for the suggestion, and @AmanKashyap0807 for the fix.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`fread()` now supports reading from the system clipboard by passing `input="clipboard"`, implemented for Windows, macOS (via `pbpaste`), and Linux (via `wl-paste`, `xclip`, or `xsel`), [#1292](https://github.com/Rdatatable/data.table/issues/1292). Thanks @mbacou for the report, @ben-schwen and @aitab for the suggestion, and @AmanKashyap0807 for the fix.
`fread()` now supports reading from the system clipboard by passing `input="clipboard"`, implemented for Windows, macOS (via `pbpaste`), and Linux (via `wl-paste`, `xclip`, or `xsel`), [#1292](https://github.com/Rdatatable/data.table/issues/1292). Thanks @mbacou for the report, @ben-schwen and @aitap for the suggestion, and @AmanKashyap0807 for the fix.

df_dcast = data.frame(a = c("x", "y"), b = 1:2, v = 3:4)
dt_dcast = data.table(a = c("x", "y"), b = 1:2, v = 3:4)
test(2365.2, dcast(df_dcast, a ~ b, value.var = "v"), dcast(dt_dcast, a ~ b, value.var = "v"))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid there's no portable way to test this without violating CRAN policy:

Packages should not write in the user’s home filespace (including clipboards)

'https://cran.r-project.org/src/contrib/PACKAGES.in' |> url() |> read.dcf() |> _[,'X-CRAN-Comment'] |> grep(pattern = 'clipb', value = TRUE)
# [1] "Archived on 2019-10-16 for policy violation.\n\nSometimes attempts to write to the clipboard."
# [2] "Archived on 2019-01-06 for policy violation.\n\nwriting to clipboard hung the check run."

Emptying the clipboard afterwards is not enough, as it could previously contain something else, e.g., an image, which we have no way to read and restore later.

os_type = .Platform$OS.type
if (identical(os_type, "windows")) {
clip = tryCatch(utils::readClipboard(),
error = function(e) stopf("Reading clipboard failed on Windows: %s", conditionMessage(e))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why catch an exception to immediately rethrow it? Arguably this is an opportunity to give a better error message, but on the other hand, this obscures the original traceback() and makes it harder to debug problems.

)
} else if (identical(os_type, "unix")) {
# Valid on macOS, Linux, BSD, etc.
clip_cmd = if (nzchar(Sys.which("pbpaste"))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you set cmd here and make the if (!is.null(cmd)) branch below do the rest of the work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fread from "clipboard" or "clipboard-128" (on Windows) fails

5 participants