/* * w32cbrd: collection of common clipboard manipulation routines shared by * the Win32 console- and GUI-based vile editor. * * Caveats * ======= * -- On a stock Win95 host, the first copy to the clipboard from the * console version of vile causes the busy thread cursor to be displayed * (i.e., cursor changes to a pointer/hourglass icon). This cursor stays * active for 5-10 seconds (all apps are active) and then goes away. * Subsequent copies do not show this cursor. On an NT host, this * phenomenon does not occur. * * $Header: /usr/build/vile/vile/RCS/w32cbrd.c,v 1.25 2001/12/21 13:05:12 tom Exp $ */ #include "estruct.h" #include "edef.h" #include #include #define CLIPBOARD_BUSY "[Clipboard currently busy]" #define CLIPBOARD_COPY_MB "[Clipboard copy from minibuffer not supported]" #define CLIPBOARD_COPYING "[Copying...]" #define CLIPBOARD_COPY_FAIL "[Clipboad copy failed]" #define CLIPBOARD_COPY_MEM "[Insufficient memory for copy operation]" typedef struct rgn_cpyarg_struct { UINT nbyte, nline; UCHAR *dst; } RGN_CPYARG; static int print_low, print_high; /* ------------------------------------------------------------------ */ static void minibuffer_abort(void) { char str[3]; /* * Aborting out of the minibuffer is not easy. When in doubt, use a * sledge hammer. */ str[0] = ESC; str[1] = '\0'; (void) w32_keybrd_write(str); update(TRUE); } static void report_cbrdstats(UINT nbyte, UINT nline, int pasted) { char buf[128]; if (! global_b_val(MDTERSE)) { lsprintf(buf, "[%s %d line%s, %d bytes %s clipboard]", (pasted) ? "Pasted" : "Copied", nline, PLURAL(nline), nbyte, (pasted) ? "from" : "to"); mlwrite(buf); } else mlforce("[%d lines]", nline); } /* The memory block handle _must_ be unlocked before calling this fn. */ static int setclipboard(HGLOBAL hClipMem, UINT nbyte, UINT nline) { int rc, i; for (rc = i = 0; i < 8 && (! rc); i++) { /* Try to open clipboard */ if (! OpenClipboard(NULL)) Sleep(500); else rc = 1; } if (! rc) { mlforce(CLIPBOARD_BUSY); GlobalFree(hClipMem); return (FALSE); } EmptyClipboard(); rc = (SetClipboardData(CF_TEXT, hClipMem) != NULL); CloseClipboard(); if (! rc) { mlforce(CLIPBOARD_COPY_FAIL); GlobalFree(hClipMem); } else { /* success */ report_cbrdstats(nbyte - 1, /* subtract terminating NUL */ nline, FALSE); } return (rc); } /* Count lines and nonbuffer data added during "copy to clipboard" operation. */ static void cbrd_count_meta_data(int len, UINT *nbyte, UINT *nline, char *src) { register UINT c; while (len--) { if ((c = (UCHAR) *src++) == '\n') { (*nline)++; (*nbyte)++; /* API requires CR/LF terminator */ } else if (c < _SPC_ && c != _TAB_) /* assumes ASCII char set */ (*nbyte)++; /* account for '^' meta char */ else if (c > _TILDE_ && (! PASS_HIGH(c))) /* assumes ASCII char set */ (*nbyte) += 3; /* account for '\xdd' meta chars */ } } /* * This function is called to process each logical line of data in a * user-selected region. It counts the number of bytes of data in the line. */ static int count_rgn_data(void *argp, int l, int r) { RGN_CPYARG *cpyp; int len; LINE *lp; lp = DOT.l; /* Rationalize offsets */ if (llength(lp) < l) return (TRUE); if (r > llength(lp)) r = llength(lp); cpyp = argp; if (r == llength(lp) || regionshape == RECTANGLE) { /* process implied newline */ cpyp->nline++; cpyp->nbyte += 2; /* CBRD API maps NL -> CR/LF */ } len = r - l; cpyp->nbyte += len; cbrd_count_meta_data(len, &cpyp->nbyte, &cpyp->nline, lp->l_text + l); return (TRUE); } static void cbrd_copy_and_xlate(int len, UCHAR **cbrd_ptr, char *src) { register UINT c; UCHAR *dst = *cbrd_ptr; while (len--) { if ((c = (UCHAR) *src++) == '\n') { *dst++ = '\r'; *dst++ = '\n'; } else if ((c >= _SPC_ && c <= _TILDE_) || (c == _TAB_)) *dst++ = (UCHAR) c; else if (c < _SPC_) { *dst++ = '^'; *dst++ = ctrldigits[c]; } else { /* c > _TILDE_ */ if (! PASS_HIGH(c)) { *dst++ = '\\'; *dst++ = 'x'; *dst++ = hexdigits[(c & 0xf0) >> 4]; *dst++ = hexdigits[c & 0xf]; } else *dst++ = (UCHAR) c; } } *cbrd_ptr = dst; } /* * This function is called to process each logical line of data in a * user-selected region. It copies region data to a buffer allocated on * the heap. */ static int copy_rgn_data(void *argp, int l, int r) { RGN_CPYARG *cpyp; int len; LINE *lp; lp = DOT.l; /* Rationalize offsets */ if (llength(lp) < l) return (TRUE); if (r > llength(lp)) r = llength(lp); cpyp = argp; len = r - l; cbrd_copy_and_xlate(len, &cpyp->dst, lp->l_text + l); if (r == llength(lp) || regionshape == RECTANGLE) { /* process implied newline */ *cpyp->dst++ = '\r'; *cpyp->dst++ = '\n'; } return (TRUE); } /* * Copy contents of [un]named register to Windows clipboard. The control * flow is shamelessly copied from kwrite(). */ static int cbrd_reg_copy(void) { HGLOBAL hClipMem; register int i; KILL *kp; /* pointer into [un]named register */ UINT nbyte; UINT nline; UCHAR *dst; /* make sure there is something to put */ if (kbs[ukb].kbufh == NULL) { mlforce("[Nothing to copy]"); return (FALSE); /* not an error, just nothing */ } print_high = global_g_val(GVAL_PRINT_HIGH); print_low = global_g_val(GVAL_PRINT_LOW); /* tell us we're writing */ mlwrite(CLIPBOARD_COPYING); nline = 0; nbyte = 0; /* * Make 2 passes over the data. 1st pass counts the data and * adjusts for the fact that: * * 1) each '\n' must be warped to "\r\n" to satisfy clipboard APIs. * 2) unprintable data (modulo tabs) must be warped to a printable * equivalent. */ for (kp = kbs[ukb].kbufh; kp; kp = kp->d_next) { i = KbSize(ukb, kp); nbyte += i; cbrd_count_meta_data(i, &nbyte, &nline, (char *) kp->d_chunk); } nbyte++; /* Add room for terminating null */ /* 2nd pass -- alloc storage for data and copy to clipboard. */ if ((hClipMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, nbyte)) == NULL) { mlforce(CLIPBOARD_COPY_MEM); return (FALSE); } dst = GlobalLock(hClipMem); for (kp = kbs[ukb].kbufh; kp; kp = kp->d_next) cbrd_copy_and_xlate((int) KbSize(ukb, kp), &dst, (char *) kp->d_chunk); *dst = '\0'; GlobalUnlock(hClipMem); return (setclipboard(hClipMem, nbyte, nline)); } /* * Copy contents of unnamed register to Windows clipboard. * * Bound to Alt+Insert. */ int cbrdcpy_unnamed(int unused1, int unused2) { int rc; if (reading_msg_line) { minibuffer_abort(); /* FIXME -- goes away some day? */ mlerase(); mlforce(CLIPBOARD_COPY_MB); return (ABORT); } kregcirculate(FALSE); rc = cbrd_reg_copy(); ukb = 0; return (rc); } /* * Copy the currently-selected region (i.e., the range of lines from DOT to * MK, inclusive) to the windows clipboard. Lots of code has been borrowed * and/or adapted from operyank() and writereg(). */ static int cbrdcpy_region(void) { RGN_CPYARG cpyarg; DORGNLINES dorgn; HGLOBAL hClipMem; MARK odot; int rc; mlwrite(CLIPBOARD_COPYING); print_high = global_g_val(GVAL_PRINT_HIGH); print_low = global_g_val(GVAL_PRINT_LOW); odot = DOT; /* do_lines_in_region() moves DOT. */ cpyarg.nbyte = cpyarg.nline = 0; dorgn = get_do_lines_rgn(); /* * Make 2 passes over the data. 1st pass counts the data and * adjusts for the fact that: * * 1) each '\n' must be warped to "\r\n" to satisfy clipboard APIs. * 2) unprintable data (modulo tabs) must be warped to a printable * equivalent. */ rc = dorgn(count_rgn_data, &cpyarg, TRUE); DOT = odot; if (!rc) return (FALSE); cpyarg.nbyte++; /* Terminating nul */ /* 2nd pass -- alloc storage for data and copy to clipboard. */ if ((hClipMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, cpyarg.nbyte)) == NULL) { mlforce(CLIPBOARD_COPY_MEM); return (FALSE); } cpyarg.dst = GlobalLock(hClipMem); /* * Pass #2 -> The actual copy. Don't need to restore DOT, that * is handled by opercbrdcpy(). */ rc = dorgn(copy_rgn_data, &cpyarg, TRUE); GlobalUnlock(hClipMem); if (! rc) { GlobalFree(hClipMem); return (FALSE); } *cpyarg.dst = '\0'; return (setclipboard(hClipMem, cpyarg.nbyte, cpyarg.nline)); } /* * Copy contents of specified region or register to Windows clipboard. * This command is an operaor and mimics the functionality of ^W, but * mimics operyank()'s implemenation. * * Bound to Ctrl+Insert. */ int opercbrdcpy(int f, int n) { if (reading_msg_line) { minibuffer_abort(); /* FIXME -- goes away some day? */ mlerase(); mlforce(CLIPBOARD_COPY_MB); return (ABORT); } if (ukb != 0) return (cbrd_reg_copy()); else { MARK odot; int rc; odot = DOT; opcmd = OPDEL; rc = vile_op(f, n, cbrdcpy_region, "Clipboard copy"); DOT = odot; /* cursor does not move */ return (rc); } } /* ------------------- Paste Functionality ----------------------- */ #define MAX_MAPPED_STR 16 /* fairly conservative :-) */ static int map_and_insert(UINT, UINT *); typedef struct { UINT val; char *str; } MAP; /* Keep this table sorted by "val" . */ static MAP cbrdmap[] = { { 0x85, "..." }, { 0x8B, "<" }, { 0x91, "'" }, { 0x92, "'" }, { 0x93, "\"" }, { 0x94, "\"" }, { 0x96, "-" }, { 0x97, "--" }, { 0x99, "(TM)" }, { 0x9B, ">" }, { 0xA6, "|" }, { 0xA9, "(C)" }, { 0xAB, "<<" }, { 0xAD, "-" }, { 0xAE, "(R)" }, { 0xB1, "+/-" }, { 0xBB, ">>" }, { 0xBC, "1/4" }, { 0xBD, "1/2" }, { 0xBE, "3/4" }, }; /* --------------------------------------------------------------- */ static int map_compare(const void *elem1, const void *elem2) { return (((MAP *) elem1)->val - ((MAP *) elem2)->val); } static int map_cbrd_char(UINT c, UCHAR mapped_rslt[MAX_MAPPED_STR]) { MAP key, *rslt_p; int nmapped = 0; char *str; key.val = c; rslt_p = bsearch(&key, cbrdmap, sizeof(cbrdmap) / sizeof(cbrdmap[0]), sizeof(cbrdmap[0]), map_compare); if (! rslt_p) mapped_rslt[nmapped++] = (UCHAR) c; else { for (str = rslt_p->str; *str; str++) mapped_rslt[nmapped++] = *str; } mapped_rslt[nmapped] = '\0'; return (nmapped); } /* paste a single line from the clipboard to the minibuffer. */ static int paste_to_minibuffer(UCHAR *cbrddata) { int rc = TRUE; UCHAR *cp = cbrddata, *eol = NULL, map_str[MAX_MAPPED_STR + 1], one_char[2]; while(*cp) { if (*cp == '\r' && *(cp + 1) == '\n') { eol = cp; /* * Don't allow more than one line of data to be pasted into the * minibuffer (to protect the user from seriously bad side * effects when s/he pastes in the wrong buffer). We don't * report an error here in an effort to retain compatibility * with a couple of "significant" win32 apps (e.g., IE and * Outlook) that simply truncate a paste at one line when it * only makes sense to take a single line of input. */ break; } cp++; } if (eol) *eol = '\0'; /* chop delimiter */ one_char[1] = '\0'; while (*cbrddata && rc) { if (*cbrddata > _TILDE_) { (void) map_cbrd_char(*cbrddata, map_str); rc = w32_keybrd_write((char *) map_str); } else { one_char[0] = *cbrddata; rc = w32_keybrd_write((char *) one_char); } cbrddata++; } return (rc); } /* * Paste contents of windows clipboard (if TEXT) to current buffer. * Bound to Shift+Insert. */ int cbrdpaste(int f, int n) { register UINT c; register UCHAR *data; HANDLE hClipMem; int i, rc, suppressnl; UINT nbyte, nline; for (rc = i = 0; i < 8 && (! rc); i++) { /* Try to open clipboard */ if (! OpenClipboard(NULL)) Sleep(500); else rc = 1; } if (! rc) { if (reading_msg_line) { minibuffer_abort(); /* FIXME -- goes away some day? */ rc = ABORT; } else rc = FALSE; mlforce(CLIPBOARD_BUSY); return (rc); } if ((hClipMem = GetClipboardData(CF_TEXT)) == NULL) { CloseClipboard(); if (reading_msg_line) { minibuffer_abort(); /* FIXME -- goes away some day? */ rc = ABORT; } else rc = FALSE; mlforce("[Clipboard empty or not TEXT data]"); return (rc); } if ((data = GlobalLock(hClipMem)) == NULL) { CloseClipboard(); if (reading_msg_line) { minibuffer_abort(); /* FIXME -- goes away some day? */ rc = ABORT; } else rc = FALSE; mlforce("[Can't lock clipboard memory]"); return (rc); } if (reading_msg_line) { rc = paste_to_minibuffer(data); GlobalUnlock(hClipMem); CloseClipboard(); return (rc); } mlwrite(CLIPBOARD_COPYING); nbyte = nline = 0; rc = TRUE; /* * Before stuffing data in the current buffer, save info regarding dot * and mark. The dot/mark manipulation code is more or less cribbed * from doput() and PutChar(). Note also that clipboard data is always * copied into the current region as if it were an "exact" shape, which * should be the most intuitive result for Windows users who work with * the clipboard (I hope). */ suppressnl = is_header_line(DOT, curbp); /* this pastes after DOT instead of at DOT - very annoying if (! is_at_end_of_line(DOT)) forwchar(TRUE,1); */ (void) setmark(); while(*data && rc) { if ((c = *data) == '\n') { nbyte++; nline++; rc = lnewline(); } else if (c == '\r' && *(data + 1) == '\n') { /* Clipboard end of line delimiter is crlf. Ignore cr. */ ; } else if (c > _TILDE_) rc = map_and_insert(c, &nbyte); else { nbyte++; rc = linsert(1, (int) c); } data++; } if (rc) { if (nbyte > 0 && (data[-1] == '\n') && suppressnl) { /* * Last byte inserted was a newline and DOT was originally * pointing at the beginning of the buffer(??). In this * situation, linsert() has added an additional newline to the * buffer. Remove it. */ (void) ldelete(1, FALSE); } } GlobalUnlock(hClipMem); CloseClipboard(); if (! rc) (void) no_memory("cbrdpaste()"); else { /* * Success. Fiddle with dot and mark again (another chunk of doput() * code). "Tha' boy shore makes keen use of cut and paste." * Don't swap if inserting - this allows you to continue typing * after a paste operation without additional futzing with DOT. */ if (!insertmode) swapmark(); /* I understand this. */ if (is_header_line(DOT, curbp)) DOT.l = lback(DOT.l); /* This is a mystery. */ report_cbrdstats(nbyte, nline, TRUE); } return (rc); } /* * Map selected characters from the ANSI character set to their ASCII * equivalents and insert same in the current buffer. */ static int map_and_insert(UINT c, /* ANSI char to insert */ UINT *nbyte /* total #chars inserted */ ) { UCHAR mapped_str[MAX_MAPPED_STR]; int i, nmapped, rc; nmapped = map_cbrd_char(c, mapped_str); *nbyte += nmapped; for (rc = TRUE, i = 0; i < nmapped && rc; i++) rc = linsert(1, mapped_str[i]); return (rc); } /* --------------------- Cut Functionality ----------------------- */ /* * Cut current [win]vile selection to windows clipboard. * Bound to Shift+Delete. */ int cbrdcut(int f, int n) { return (w32_del_selection(TRUE)); }