Module: Winclip
- Defined in:
- lib/winclip.rb,
lib/winclip/version.rb,
ext/winclip/winclip.cpp
Overview
winclip — read and write the Windows clipboard: text, images (PNG), and file lists.
Winclip.text = "hello"
Winclip.text # => "hello"
Winclip.image = File.binread("pic.png")
File.binwrite("out.png", Winclip.image) if Winclip.has_image?
Winclip.files = ["C:/a.txt", "C:/b.txt"]
Winclip.files # => ["C:/a.txt", "C:/b.txt"]
Winclip.clear
Defined Under Namespace
Classes: Error
Constant Summary collapse
- STANDARD_FORMATS =
Standard clipboard format ids, for Winclip.available?(“CF_…”).
{ "CF_TEXT" => 1, "CF_BITMAP" => 2, "CF_METAFILEPICT" => 3, "CF_SYLK" => 4, "CF_DIF" => 5, "CF_TIFF" => 6, "CF_OEMTEXT" => 7, "CF_DIB" => 8, "CF_PALETTE" => 9, "CF_PENDATA" => 10, "CF_RIFF" => 11, "CF_WAVE" => 12, "CF_UNICODETEXT" => 13, "CF_ENHMETAFILE" => 14, "CF_HDROP" => 15, "CF_LOCALE" => 16, "CF_DIBV5" => 17 }.freeze
- VERSION =
"0.1.0"
Class Method Summary collapse
- ._available?(vid) ⇒ Boolean
- ._get_files ⇒ Object
- ._get_image ⇒ Object
-
._get_text ⇒ Object
Ruby-facing methods =============================.
- ._register_format(name) ⇒ Object
- ._set_files(ary) ⇒ Object
- ._set_image(png) ⇒ Object
- ._set_text(str) ⇒ Object
-
.available?(format) ⇒ Boolean
Is the given clipboard format present? Accepts an Integer format id, a standard format name (“CF_UNICODETEXT”), a registered format name (“PNG”), or one of :text / :image / :files.
- .clear ⇒ Object
-
.copy(string) ⇒ Object
Copy text to the clipboard (alias for text=).
-
.files ⇒ Object
The clipboard file list as an Array<String> of paths, or nil if none.
-
.files=(paths) ⇒ Object
Put a file list on the clipboard (CF_HDROP).
- .formats ⇒ Object
- .has_files? ⇒ Boolean
- .has_image? ⇒ Boolean
- .has_text? ⇒ Boolean
-
.image ⇒ Object
The clipboard image as PNG bytes (binary String), or nil if no image is present.
-
.image=(png) ⇒ Object
Put an image on the clipboard from PNG bytes (a binary String).
-
.paste ⇒ Object
Read text from the clipboard (alias for #text).
-
.text ⇒ Object
Clipboard text as a UTF-8 String, or nil if no text is present.
-
.text=(value) ⇒ Object
Set the clipboard to the given text (coerced with #to_s).
Class Method Details
._available?(vid) ⇒ Boolean
531 532 533 534 |
# File 'ext/winclip/winclip.cpp', line 531
static VALUE wc_available(VALUE self, VALUE vid) {
UINT id = (UINT)NUM2UINT(vid);
return IsClipboardFormatAvailable(id) ? Qtrue : Qfalse;
}
|
._get_files ⇒ Object
411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'ext/winclip/winclip.cpp', line 411
static VALUE wc_get_files(VALUE self) {
char **arr = NULL;
int n = clip_get_files(&arr);
if (n < 0) return Qnil;
VALUE rary = rb_ary_new_capa(n);
for (int i = 0; i < n; i++) {
rb_ary_push(rary, rb_utf8_str_new_cstr(arr[i]));
LocalFree(arr[i]);
}
LocalFree(arr);
return rary;
}
|
._get_image ⇒ Object
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'ext/winclip/winclip.cpp', line 452
static VALUE wc_get_image(VALUE self) {
if (!IsClipboardFormatAvailable(CF_DIBV5) && !IsClipboardFormatAvailable(CF_DIB))
return Qnil;
HRESULT cohr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
bool com_owned = (cohr == S_OK || cohr == S_FALSE);
std::vector<BYTE> png;
HRESULT hr = E_FAIL;
bool threw = false;
try { hr = clip_get_image(png); }
catch (...) { threw = true; } /* e.g. bad_alloc in bgra_to_png (clipboard already closed) */
if (com_owned) CoUninitialize();
if (threw) rb_raise(eWinclipError, "winclip: out of memory decoding the clipboard image");
/* Format present but not decodable (palettized / malformed) -> nil, matching
* the "getters return nil when there's no usable content" contract. */
if (FAILED(hr)) return Qnil;
VALUE s = rb_str_new((char *)png.data(), (long)png.size());
rb_enc_associate(s, rb_ascii8bit_encoding());
return s;
}
|
._get_text ⇒ Object
Ruby-facing methods =============================
393 394 395 396 397 398 399 |
# File 'ext/winclip/winclip.cpp', line 393
static VALUE wc_get_text(VALUE self) {
char *u8 = clip_get_text();
if (!u8) return Qnil;
VALUE s = rb_utf8_str_new_cstr(u8);
LocalFree(u8);
return s;
}
|
._register_format(name) ⇒ Object
536 537 538 539 540 541 542 543 544 |
# File 'ext/winclip/winclip.cpp', line 536
static VALUE wc_register_format(VALUE self, VALUE name) {
VALUE u8 = rb_str_export_to_enc(StringValue(name), rb_utf8_encoding());
wchar_t *w = utf8_to_utf16(RSTRING_PTR(u8), -1);
if (!w) rb_raise(eWinclipError, "winclip: could not encode format name");
UINT id = RegisterClipboardFormatW(w);
LocalFree(w);
if (id == 0) rb_raise(eWinclipError, "winclip: could not register clipboard format");
return UINT2NUM(id);
}
|
._set_files(ary) ⇒ Object
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'ext/winclip/winclip.cpp', line 424
static VALUE wc_set_files(VALUE self, VALUE ary) {
Check_Type(ary, T_ARRAY);
long n = RARRAY_LEN(ary);
if (n <= 0) rb_raise(rb_eArgError, "winclip: files list must not be empty");
/* Convert all paths to UTF-16 up front (raises here, before any clipboard
* work or HGLOBAL exists). */
wchar_t **wp = (wchar_t **)LocalAlloc(LPTR, (size_t)n * sizeof(wchar_t *));
if (!wp) rb_raise(rb_eNoMemError, "winclip: out of memory");
int built = 0;
int failed = 0;
for (long i = 0; i < n; i++) {
VALUE e = rb_ary_entry(ary, i);
if (!RB_TYPE_P(e, T_STRING)) { failed = 1; break; }
VALUE u8 = rb_str_export_to_enc(e, rb_utf8_encoding());
wp[i] = utf8_to_utf16(RSTRING_PTR(u8), -1);
if (!wp[i]) { failed = 1; break; }
built++;
}
int ok = 0;
if (!failed) ok = clip_set_files((const wchar_t **)wp, (int)n);
for (int i = 0; i < built; i++) LocalFree(wp[i]);
LocalFree(wp);
if (failed) rb_raise(rb_eArgError, "winclip: every file path must be a String");
if (!ok) rb_raise(eWinclipError, "winclip: failed to set clipboard files");
return Qnil;
}
|
._set_image(png) ⇒ Object
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'ext/winclip/winclip.cpp', line 476
static VALUE wc_set_image(VALUE self, VALUE png) {
StringValue(png);
HRESULT cohr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
bool com_owned = (cohr == S_OK || cohr == S_FALSE);
HRESULT hr = E_FAIL;
bool threw = false;
try {
std::vector<BYTE> in((BYTE *)RSTRING_PTR(png), (BYTE *)RSTRING_PTR(png) + RSTRING_LEN(png));
hr = clip_set_image(in.data(), in.size());
} catch (...) { threw = true; } /* e.g. bad_alloc decoding a huge PNG (no clipboard held) */
if (com_owned) CoUninitialize();
if (threw) rb_raise(eWinclipError, "winclip: out of memory encoding the clipboard image");
if (FAILED(hr))
rb_raise(eWinclipError, "winclip: failed to set clipboard image "
"(not a valid PNG?) (hr=0x%08lX)", (unsigned long)hr);
return Qnil;
}
|
._set_text(str) ⇒ Object
401 402 403 404 405 406 407 408 409 |
# File 'ext/winclip/winclip.cpp', line 401
static VALUE wc_set_text(VALUE self, VALUE str) {
VALUE u8 = rb_str_export_to_enc(StringValue(str), rb_utf8_encoding());
wchar_t *w = utf8_to_utf16(RSTRING_PTR(u8), -1);
if (!w) rb_raise(eWinclipError, "winclip: could not encode text");
int ok = clip_set_text(w);
LocalFree(w);
if (!ok) rb_raise(eWinclipError, "winclip: failed to set clipboard text");
return Qnil;
}
|
.available?(format) ⇒ Boolean
Is the given clipboard format present? Accepts an Integer format id, a standard format name (“CF_UNICODETEXT”), a registered format name (“PNG”), or one of :text / :image / :files.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/winclip.rb', line 78 def available?(format) case format when Integer then _available?(format) when :text then has_text? when :image then has_image? when :files then has_files? else name = format.to_s if (id = STANDARD_FORMATS[name]) _available?(id) else # Resolve a custom/registered format by name WITHOUT registering it as a # side effect (RegisterClipboardFormat would create unknown names). # `formats` reports what's actually present — exactly the answer needed. formats.include?(name) end end end |
.clear ⇒ Object
496 497 498 499 500 501 |
# File 'ext/winclip/winclip.cpp', line 496
static VALUE wc_clear(VALUE self) {
if (!clip_open_retry()) rb_raise(eWinclipError, "winclip: could not open the clipboard");
EmptyClipboard();
CloseClipboard();
return Qnil;
}
|
.copy(string) ⇒ Object
Copy text to the clipboard (alias for text=). Returns the string.
66 67 68 |
# File 'lib/winclip.rb', line 66 def copy(string) self.text = string end |
.files ⇒ Object
The clipboard file list as an Array<String> of paths, or nil if none.
52 53 54 |
# File 'lib/winclip.rb', line 52 def files _get_files end |
.files=(paths) ⇒ Object
Put a file list on the clipboard (CF_HDROP). Accepts an Array of paths or a single path String. Returns the value.
58 59 60 61 62 63 |
# File 'lib/winclip.rb', line 58 def files=(paths) list = Array(paths).map(&:to_s) raise ArgumentError, "winclip: provide at least one file path" if list.empty? _set_files(list) paths end |
.formats ⇒ Object
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
# File 'ext/winclip/winclip.cpp', line 503
static VALUE wc_formats(VALUE self) {
if (!clip_open_retry()) rb_raise(eWinclipError, "winclip: could not open the clipboard");
/* Collect ids while open; build Ruby objects only after closing (so an
* allocation that raises can't leave the clipboard open). std::vector grows
* as needed — no silent cap on the number of formats reported. */
std::vector<UINT> ids;
UINT f = 0;
while ((f = EnumClipboardFormats(f)) != 0) ids.push_back(f);
CloseClipboard();
VALUE ary = rb_ary_new_capa((long)ids.size());
for (size_t i = 0; i < ids.size(); i++) {
const char *nm = clip_std_name(ids[i]);
if (nm) {
rb_ary_push(ary, rb_str_new_cstr(nm));
} else {
wchar_t wbuf[256];
int wn = GetClipboardFormatNameW(ids[i], wbuf, 256); /* no open needed */
if (wn > 0) {
char *u8 = utf16_to_utf8(wbuf, wn);
if (u8) { rb_ary_push(ary, rb_utf8_str_new_cstr(u8)); LocalFree(u8); }
}
}
}
return ary;
}
|
.has_files? ⇒ Boolean
555 556 557 |
# File 'ext/winclip/winclip.cpp', line 555 static VALUE wc_has_files(VALUE self) { return IsClipboardFormatAvailable(CF_HDROP) ? Qtrue : Qfalse; } |
.has_image? ⇒ Boolean
550 551 552 553 554 |
# File 'ext/winclip/winclip.cpp', line 550 static VALUE wc_has_image(VALUE self) { return (IsClipboardFormatAvailable(CF_DIBV5) || IsClipboardFormatAvailable(CF_DIB) || IsClipboardFormatAvailable(CF_BITMAP)) ? Qtrue : Qfalse; } |
.has_text? ⇒ Boolean
546 547 548 549 |
# File 'ext/winclip/winclip.cpp', line 546 static VALUE wc_has_text(VALUE self) { return (IsClipboardFormatAvailable(CF_UNICODETEXT) || IsClipboardFormatAvailable(CF_TEXT)) ? Qtrue : Qfalse; } |
.image ⇒ Object
The clipboard image as PNG bytes (binary String), or nil if no image is present. Pairs with windraw: ‘File.binwrite(“x.png”, Winclip.image)`.
40 41 42 |
# File 'lib/winclip.rb', line 40 def image _get_image end |
.image=(png) ⇒ Object
Put an image on the clipboard from PNG bytes (a binary String). Returns the value. Sets both CF_DIBV5 (alpha-aware) and CF_DIB for broad compatibility.
46 47 48 49 |
# File 'lib/winclip.rb', line 46 def image=(png) _set_image(png) png end |
.paste ⇒ Object
Read text from the clipboard (alias for #text).
71 72 73 |
# File 'lib/winclip.rb', line 71 def paste text end |
.text ⇒ Object
Clipboard text as a UTF-8 String, or nil if no text is present.
28 29 30 |
# File 'lib/winclip.rb', line 28 def text _get_text end |
.text=(value) ⇒ Object
Set the clipboard to the given text (coerced with #to_s). Returns the value.
33 34 35 36 |
# File 'lib/winclip.rb', line 33 def text=(value) _set_text(value.to_s) value end |