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 <vladimir@thecybershadow.net>
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 	void setClipboardText(string s)
30 	{
31 		auto ws = s.toUTF16();
32 
33 		char[] as;
34 		int readLen;
35 		as.length = WideCharToMultiByte(0, 0, ws.ptr, ws.length.to!DWORD, null, 0, null, null);
36 
37 		if (as.length)
38 		{
39 			readLen = WideCharToMultiByte(0, 0, ws.ptr, ws.length.to!DWORD, as.ptr, to!int(as.length), null, null);
40 			wenforce(readLen == as.length, "WideCharToMultiByte");
41 		}
42 
43 		as ~= 0;
44 		ws ~= 0;
45 
46 		setClipboard([
47 			ClipboardFormat(CF_TEXT,        as),
48 			ClipboardFormat(CF_UNICODETEXT, ws),
49 		]);
50 	}
51 
52 	string getClipboardText()
53 	{
54 		static immutable DWORD[] textFormat = [CF_UNICODETEXT];
55 		auto format = getClipboard(textFormat)[0];
56 		wchar[] ws = (cast(wchar[])format.data)[0..$-1];
57 		return ws.toUTF8();
58 	}
59 
60 	// Windows-specific
61 
62 	/// One format entry in the Windows clipboard.
63 	struct ClipboardFormat
64 	{
65 		DWORD format;
66 		const (void)[] data;
67 
68 		string getName()
69 		{
70 			import ae.utils.meta : progn;
71 
72 			wchar[256] sbuf;
73 			wchar[] buf = sbuf[];
74 			int ret;
75 			do
76 			{
77 				ret = wenforce(GetClipboardFormatNameW(format, buf.ptr, buf.length.to!DWORD), "GetClipboardFormatNameW");
78 			} while (ret == buf.length ? progn(buf.length *=2, true) : false);
79 			return buf[0..ret].toUTF8();
80 		}
81 	}
82 
83 	/// Get clipboard data for the specified (default: all) formats.
84 	ClipboardFormat[] getClipboard(in DWORD[] desiredFormatsP = null)
85 	{
86 		const(DWORD)[] desiredFormats = desiredFormatsP;
87 
88 		wenforce(OpenClipboard(null), "OpenClipboard");
89 		scope(exit) wenforce(CloseClipboard(), "CloseClipboard");
90 
91 		if (desiredFormats is null)
92 		{
93 			auto allFormats = new DWORD[CountClipboardFormats()];
94 			DWORD previous = 0;
95 			foreach (ref f; allFormats)
96 				f = previous = EnumClipboardFormats(previous);
97 			desiredFormats = allFormats;
98 		}
99 
100 		auto result = new ClipboardFormat[desiredFormats.length];
101 		foreach (n, ref r; result)
102 		{
103 			r.format = desiredFormats[n];
104 			auto hBuf = wenforce(GetClipboardData(r.format), "GetClipboardData");
105 			auto size = GlobalSize(hBuf);
106 			LPVOID buf = wenforce(GlobalLock(hBuf), "GlobalLock");
107 			r.data = buf[0..size].dup;
108 			wenforce(GlobalUnlock(hBuf) || GetLastError()==NO_ERROR, "GlobalUnlock");
109 		}
110 
111 		return result;
112 	}
113 
114 	void setClipboard(in ClipboardFormat[] formats)
115 	{
116 		wenforce(OpenClipboard(null), "OpenClipboard");
117 		scope(exit) wenforce(CloseClipboard(), "CloseClipboard");
118 		EmptyClipboard();
119 		foreach (ref format; formats)
120 		{
121 			HGLOBAL hBuf = wenforce(GlobalAlloc(GMEM_MOVEABLE, format.data.length.to!DWORD), "GlobalAlloc");
122 			scope(failure) wenforce(!GlobalFree(hBuf), "GlobalFree");
123 			LPVOID buf = wenforce(GlobalLock(hBuf), "GlobalLock");
124 			buf[0..format.data.length] = format.data[];
125 			wenforce(GlobalUnlock(hBuf) || GetLastError()==NO_ERROR, "GlobalUnlock");
126 			wenforce(SetClipboardData(format.format, hBuf), "SetClipboardData");
127 		}
128 	}
129 }
130 else
131 version (Posix)
132 {
133 	import std.process;
134 	import std.exception;
135 
136 	import ae.utils.path;
137 
138 	void setClipboardText(string s)
139 	{
140 		string[] cmdLine;
141 		if (haveExecutable("xclip"))
142 			cmdLine = ["xclip", "-in", "-selection", "clipboard"];
143 		else
144 		if (haveExecutable("xsel"))
145 			cmdLine = ["xsel", "--input", "--clipboard"];
146 		else
147 		if (haveExecutable("pbcopy"))
148 			cmdLine = ["pbcopy"];
149 		else
150 			throw new Exception("No clipboard management programs detected");
151 		auto p = pipe();
152 		auto pid = spawnProcess(cmdLine, p.readEnd);
153 		p.writeEnd.rawWrite(s);
154 		p.writeEnd.close();
155 		enforce(pid.wait() == 0, cmdLine[0] ~ " failed");
156 	}
157 
158 	string getClipboardText()
159 	{
160 		string[] cmdLine;
161 		if (haveExecutable("xclip"))
162 			cmdLine = ["xclip", "-out", "-selection", "clipboard"];
163 		else
164 		if (haveExecutable("xsel"))
165 			cmdLine = ["xsel", "--output", "--clipboard"];
166 		else
167 		if (haveExecutable("pbpaste"))
168 			cmdLine = ["pbpaste"];
169 		else
170 			throw new Exception("No clipboard management programs detected");
171 		auto result = execute(cmdLine);
172 		enforce(result.status == 0, cmdLine[0] ~ " failed");
173 		return result.output;
174 	}
175 }