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.1"
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
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
604 605 606 607 |
# File 'ext/winclip/winclip.cpp', line 604
static VALUE wc_available(VALUE self, VALUE vid) {
UINT id = (UINT)NUM2UINT(vid);
return IsClipboardFormatAvailable(id) ? Qtrue : Qfalse;
}
|
._get_files ⇒ Object
488 489 490 491 492 493 494 495 496 497 498 499 |
# File 'ext/winclip/winclip.cpp', line 488
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
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# File 'ext/winclip/winclip.cpp', line 529
static VALUE wc_get_image(VALUE self) {
if (!IsClipboardFormatAvailable(CF_DIBV5) && !IsClipboardFormatAvailable(CF_DIB))
return Qnil;
wc_ensure_com(); /* one process-lifetime STA; never CoUninitialize'd */
/* Pure-POD frame: all C++/EH is inside get_image_guarded, so the rb_raise
* below never unwinds through a frame carrying C++ exception state. */
BYTE *buf = NULL; size_t buflen = 0; HRESULT hr = E_FAIL;
int rc = get_image_guarded(&buf, &buflen, &hr);
if (rc == 1) 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 (rc == 2) return Qnil;
VALUE s = rb_str_new((char *)buf, (long)buflen);
LocalFree(buf);
rb_enc_associate(s, rb_ascii8bit_encoding());
return s;
}
|
._get_text ⇒ Object
Ruby-facing methods =============================
470 471 472 473 474 475 476 |
# File 'ext/winclip/winclip.cpp', line 470
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
609 610 611 612 613 614 615 616 617 |
# File 'ext/winclip/winclip.cpp', line 609
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
501 502 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 |
# File 'ext/winclip/winclip.cpp', line 501
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
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'ext/winclip/winclip.cpp', line 551
static VALUE wc_set_image(VALUE self, VALUE png) {
StringValue(png);
wc_ensure_com(); /* one process-lifetime STA; never CoUninitialize'd */
/* Pure-POD frame: the byte-vector and try/catch live in set_image_guarded so
* the rb_raise calls below never unwind through a C++ EH frame. */
const BYTE *p = (const BYTE *)RSTRING_PTR(png);
size_t n = (size_t)RSTRING_LEN(png);
HRESULT hr = E_FAIL;
int threw = set_image_guarded(p, n, &hr);
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
478 479 480 481 482 483 484 485 486 |
# File 'ext/winclip/winclip.cpp', line 478
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
569 570 571 572 573 574 |
# File 'ext/winclip/winclip.cpp', line 569
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
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
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 |
# File 'ext/winclip/winclip.cpp', line 576
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
628 629 630 |
# File 'ext/winclip/winclip.cpp', line 628 static VALUE wc_has_files(VALUE self) { return IsClipboardFormatAvailable(CF_HDROP) ? Qtrue : Qfalse; } |
.has_image? ⇒ Boolean
623 624 625 626 627 |
# File 'ext/winclip/winclip.cpp', line 623 static VALUE wc_has_image(VALUE self) { return (IsClipboardFormatAvailable(CF_DIBV5) || IsClipboardFormatAvailable(CF_DIB) || IsClipboardFormatAvailable(CF_BITMAP)) ? Qtrue : Qfalse; } |
.has_text? ⇒ Boolean
619 620 621 622 |
# File 'ext/winclip/winclip.cpp', line 619 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 |