1 /**
2  * OS clipboard interaction.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.sys.clipboard;
15 
16 version (Windows)
17 {
18 	import std.utf;
19 	import std.conv;
20 
21 	import ae.sys.windows.exception;
22 
23 	import ae.sys.windows.imports;
24 	mixin(importWin32!q{winbase});
25 	mixin(importWin32!q{windef});
26 	mixin(importWin32!q{winnls});
27 	mixin(importWin32!q{winuser});
28 
29 	/// Put text into the clipboard.
30 	void setClipboardText(string s)
31 	{
32 		auto ws = s.toUTF16();
33 
34 		char[] as;
35 		int readLen;
36 		as.length = WideCharToMultiByte(0, 0, ws.ptr, ws.length.to!DWORD, null, 0, null, null);
37 
38 		if (as.length)
39 		{
40 			readLen = WideCharToMultiByte(0, 0, ws.ptr, ws.length.to!DWORD, as.ptr, to!int(as.length), null, null);
41 			wenforce(readLen == as.length, "WideCharToMultiByte");
42 		}
43 
44 		as ~= 0;
45 		ws ~= 0;
46 
47 		setClipboard([
48 			ClipboardFormat(CF_TEXT,        as),
49 			ClipboardFormat(CF_UNICODETEXT, ws),
50 		]);
51 	}
52 
53 	/// Get text from the clipboard.
54 	string getClipboardText()
55 	{
56 		static immutable DWORD[] textFormat = [CF_UNICODETEXT];
57 		auto format = getClipboard(textFormat)[0];
58 		wchar[] ws = (cast(wchar[])format.data)[0..$-1];
59 		return ws.toUTF8();
60 	}
61 
62 	// Windows-specific
63 
64 	/// One format entry in the Windows clipboard.
65 	struct ClipboardFormat
66 	{
67 		DWORD format; /// The Windows clipboard format code, such as `CF_TEXT`.
68 		const (void)[] data; /// Data in this format.
69 
70 		/// `GetClipboardFormatNameW` wrapper.
71 		string getName()
72 		{
73 			import ae.utils.meta : progn;
74 
75 			wchar[256] sbuf;
76 			wchar[] buf = sbuf[];
77 			int ret;
78 			do
79 			{
80 				ret = wenforce(GetClipboardFormatNameW(format, buf.ptr, buf.length.to!DWORD), "GetClipboardFormatNameW");
81 			} while (ret == buf.length ? progn(buf.length *=2, true) : false);
82 			return buf[0..ret].toUTF8();
83 		}
84 	}
85 
86 	/// Get clipboard data for the specified (default: all) formats.
87 	ClipboardFormat[] getClipboard(in DWORD[] desiredFormatsP = null)
88 	{
89 		const(DWORD)[] desiredFormats = desiredFormatsP;
90 
91 		wenforce(OpenClipboard(null), "OpenClipboard");
92 		scope(exit) wenforce(CloseClipboard(), "CloseClipboard");
93 
94 		if (desiredFormats is null)
95 		{
96 			auto allFormats = new DWORD[CountClipboardFormats()];
97 			DWORD previous = 0;
98 			foreach (ref f; allFormats)
99 				f = previous = EnumClipboardFormats(previous);
100 			desiredFormats = allFormats;
101 		}
102 
103 		auto result = new ClipboardFormat[desiredFormats.length];
104 		foreach (n, ref r; result)
105 		{
106 			r.format = desiredFormats[n];
107 			auto hBuf = wenforce(GetClipboardData(r.format), "GetClipboardData");
108 			auto size = GlobalSize(hBuf);
109 			LPVOID buf = wenforce(GlobalLock(hBuf), "GlobalLock");
110 			r.data = buf[0..size].dup;
111 			wenforce(GlobalUnlock(hBuf) || GetLastError()==NO_ERROR, "GlobalUnlock");
112 		}
113 
114 		return result;
115 	}
116 
117 	/// Set the clipboard with data in the specified formats.
118 	void setClipboard(in ClipboardFormat[] formats)
119 	{
120 		wenforce(OpenClipboard(null), "OpenClipboard");
121 		scope(exit) wenforce(CloseClipboard(), "CloseClipboard");
122 		EmptyClipboard();
123 		foreach (ref format; formats)
124 		{
125 			HGLOBAL hBuf = wenforce(GlobalAlloc(GMEM_MOVEABLE, format.data.length.to!DWORD), "GlobalAlloc");
126 			scope(failure) wenforce(!GlobalFree(hBuf), "GlobalFree");
127 			LPVOID buf = wenforce(GlobalLock(hBuf), "GlobalLock");
128 			buf[0..format.data.length] = format.data[];
129 			wenforce(GlobalUnlock(hBuf) || GetLastError()==NO_ERROR, "GlobalUnlock");
130 			wenforce(SetClipboardData(format.format, hBuf), "SetClipboardData");
131 		}
132 	}
133 }
134 else
135 version (Posix)
136 {
137 	import std.process;
138 	import std.exception;
139 
140 	import ae.utils.path;
141 
142 	/// Put text into the clipboard.
143 	void setClipboardText(string s)
144 	{
145 		string[] cmdLine;
146 		if (haveExecutable("xclip"))
147 			cmdLine = ["xclip", "-in", "-selection", "clipboard"];
148 		else
149 		if (haveExecutable("xsel"))
150 			cmdLine = ["xsel", "--input", "--clipboard"];
151 		else
152 		if (haveExecutable("pbcopy"))
153 			cmdLine = ["pbcopy"];
154 		else
155 			throw new Exception("No clipboard management programs detected");
156 		auto p = pipe();
157 		auto pid = spawnProcess(cmdLine, p.readEnd);
158 		p.writeEnd.rawWrite(s);
159 		p.writeEnd.close();
160 		enforce(pid.wait() == 0, cmdLine[0] ~ " failed");
161 	}
162 
163 	/// Get text from the clipboard.
164 	string getClipboardText()
165 	{
166 		string[] cmdLine;
167 		if (haveExecutable("xclip"))
168 			cmdLine = ["xclip", "-out", "-selection", "clipboard"];
169 		else
170 		if (haveExecutable("xsel"))
171 			cmdLine = ["xsel", "--output", "--clipboard"];
172 		else
173 		if (haveExecutable("pbpaste"))
174 			cmdLine = ["pbpaste"];
175 		else
176 			throw new Exception("No clipboard management programs detected");
177 		auto result = execute(cmdLine);
178 		enforce(result.status == 0, cmdLine[0] ~ " failed");
179 		return result.output;
180 	}
181 }