1/*
2 * Copyright 2010 Wim van der Meer <WPJvanderMeer@gmail.com>
3 * Copyright Karsten Heimrich, host.haiku@gmx.de. All rights reserved.
4 * Distributed under the terms of the MIT License.
5 *
6 * Authors:
7 *		Karsten Heimrich
8 *		Fredrik Mod��en
9 *		Christophe Huriaux
10 *		Wim van der Meer
11 */
12
13
14#include "Screenshot.h"
15
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19
20#include <AppDefs.h>
21#include <Bitmap.h>
22#include <Catalog.h>
23#include <Locale.h>
24#include <Roster.h>
25#include <Screen.h>
26#include <TranslatorFormats.h>
27
28#include <WindowInfo.h>
29
30#include "Utility.h"
31
32
33#undef B_TRANSLATION_CONTEXT
34#define B_TRANSLATION_CONTEXT "Screenshot"
35
36
37Screenshot::Screenshot()
38	:
39	BApplication("application/x-vnd.haiku-screenshot-cli"),
40	fUtility(new Utility()),
41	fLaunchGui(true)
42{
43}
44
45
46Screenshot::~Screenshot()
47{
48	delete fUtility;
49}
50
51
52void
53Screenshot::ArgvReceived(int32 argc, char** argv)
54{
55	bigtime_t delay = 0;
56	const char* outputFilename = NULL;
57	bool includeBorder = false;
58	bool includeCursor = false;
59	bool grabActiveWindow = false;
60	bool saveScreenshotSilent = false;
61	bool copyToClipboard = false;
62	uint32 imageFileType = B_PNG_FORMAT;
63	for (int32 i = 0; i < argc; i++) {
64		if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
65			_ShowHelp();
66		else if (strcmp(argv[i], "-b") == 0
67			|| strcmp(argv[i], "--border") == 0)
68			includeBorder = true;
69		else if (strcmp(argv[i], "-m") == 0
70			|| strcmp(argv[i], "--mouse-pointer") == 0)
71			includeCursor = true;
72		else if (strcmp(argv[i], "-w") == 0
73			|| strcmp(argv[i], "--window") == 0)
74			grabActiveWindow = true;
75		else if (strcmp(argv[i], "-s") == 0
76			|| strcmp(argv[i], "--silent") == 0)
77			saveScreenshotSilent = true;
78		else if (strcmp(argv[i], "-f") == 0
79			|| strncmp(argv[i], "--format", 6) == 0
80			|| strncmp(argv[i], "--format=", 7) == 0)
81			imageFileType = _GetImageType(argv[i + 1]);
82		else if (strcmp(argv[i], "-d") == 0
83			|| strncmp(argv[i], "--delay", 7) == 0
84			|| strncmp(argv[i], "--delay=", 8) == 0) {
85			int32 seconds = -1;
86			if (argc > i + 1)
87				seconds = atoi(argv[i + 1]);
88			if (seconds >= 0) {
89				delay = seconds * 1000000;
90				i++;
91			} else {
92				printf("Screenshot: option requires an argument -- %s\n",
93					argv[i]);
94				fLaunchGui = false;
95				return;
96			}
97		} else if (strcmp(argv[i], "-c") == 0
98			|| strcmp(argv[i], "--clipboard") == 0)
99			copyToClipboard = true;
100		else if (i == argc - 1)
101			outputFilename = argv[i];
102	}
103
104	_New(delay);
105
106	if (copyToClipboard || saveScreenshotSilent) {
107		fLaunchGui = false;
108
109		BBitmap* screenshot = fUtility->MakeScreenshot(includeCursor,
110			grabActiveWindow, includeBorder);
111
112		if (screenshot == NULL)
113			return;
114
115		if (copyToClipboard)
116			fUtility->CopyToClipboard(*screenshot);
117
118		if (saveScreenshotSilent)
119			fUtility->Save(&screenshot, outputFilename, imageFileType);
120
121		delete screenshot;
122	}
123}
124
125
126void
127Screenshot::ReadyToRun()
128{
129	if (fLaunchGui) {
130		// Get a screenshot if we don't have one
131		if (fUtility->wholeScreen == NULL)
132			_New(0);
133
134		// Send the screenshot data to the GUI
135		BMessage message;
136		message.what = SS_UTILITY_DATA;
137
138		BMessage* bitmap = new BMessage();
139		fUtility->wholeScreen->Archive(bitmap);
140		message.AddMessage("wholeScreen", bitmap);
141
142		bitmap = new BMessage();
143		fUtility->cursorBitmap->Archive(bitmap);
144		message.AddMessage("cursorBitmap", bitmap);
145
146		bitmap = new BMessage();
147		fUtility->cursorAreaBitmap->Archive(bitmap);
148		message.AddMessage("cursorAreaBitmap", bitmap);
149
150		message.AddPoint("cursorPosition", fUtility->cursorPosition);
151		message.AddRect("activeWindowFrame", fUtility->activeWindowFrame);
152		message.AddRect("tabFrame", fUtility->tabFrame);
153		message.AddFloat("borderSize", fUtility->borderSize);
154
155		be_roster->Launch("application/x-vnd.haiku-screenshot",	&message);
156	}
157
158	be_app->PostMessage(B_QUIT_REQUESTED);
159}
160
161
162void
163Screenshot::_ShowHelp()
164{
165	printf("Screenshot [OPTIONS] [FILE]  Creates a bitmap of the current "
166		"screen\n\n");
167	printf("FILE is the optional output path / filename used in silent mode. "
168		"An exisiting\nfile with the same name will be overwritten without "
169		"warning. If FILE is not\ngiven the screenshot will be saved to a "
170		"file with the default filename in the\nuser's home directory.\n\n");
171	printf("OPTIONS\n");
172	printf("  -m, --mouse-pointer   Include the mouse pointer\n");
173	printf("  -b, --border          Include the window border\n");
174	printf("  -w, --window          Capture the active window instead of the "
175		"entire screen\n");
176	printf("  -d, --delay=seconds   Take screenshot after the specified delay "
177		"[in seconds]\n");
178	printf("  -s, --silent          Saves the screenshot without showing the "
179		"application\n                        window\n");
180	printf("  -f, --format=image	Give the image format you like to save "
181		"as\n");
182	printf("                        [bmp], [gif], [jpg], [png], [ppm], "
183		"[tga], [tif]\n");
184	printf("  -c, --clipboard       Copies the screenshot to the system "
185		"clipboard without\n                        showing the application "
186		"window\n");
187	printf("\n");
188	printf("Note: OPTION -b, --border takes only effect when used with -w, "
189		"--window\n");
190
191	fLaunchGui = false;
192}
193
194
195void
196Screenshot::_New(bigtime_t delay)
197{
198	delete fUtility->wholeScreen;
199	delete fUtility->cursorBitmap;
200	delete fUtility->cursorAreaBitmap;
201
202	if (delay > 0)
203		snooze(delay);
204
205	_GetActiveWindowFrame();
206
207	// There is a bug in the drawEngine code that prevents the drawCursor
208	// flag from hiding the cursor when GetBitmap is called, so we need to hide
209	// the cursor by ourselves. Refer to trac tickets #2988 and #2997
210	bool cursorIsHidden = IsCursorHidden();
211	if (!cursorIsHidden)
212		HideCursor();
213	if (BScreen().GetBitmap(&fUtility->wholeScreen, false) != B_OK)
214		return;
215	if (!cursorIsHidden)
216		ShowCursor();
217
218	// Get the current cursor position, bitmap, and hotspot
219	BPoint cursorHotSpot;
220	get_mouse(&fUtility->cursorPosition, NULL);
221	get_mouse_bitmap(&fUtility->cursorBitmap, &cursorHotSpot);
222	fUtility->cursorPosition -= cursorHotSpot;
223
224	// Put the mouse area in a bitmap
225	BRect bounds = fUtility->cursorBitmap->Bounds();
226	int cursorWidth = bounds.IntegerWidth() + 1;
227	int cursorHeight = bounds.IntegerHeight() + 1;
228	fUtility->cursorAreaBitmap = new BBitmap(bounds, B_RGBA32);
229
230	fUtility->cursorAreaBitmap->ImportBits(fUtility->wholeScreen->Bits(),
231		fUtility->wholeScreen->BitsLength(),
232		fUtility->wholeScreen->BytesPerRow(),
233		fUtility->wholeScreen->ColorSpace(),
234		fUtility->cursorPosition, BPoint(0, 0),
235		cursorWidth, cursorHeight);
236
237	// Fill in the background of the mouse bitmap
238	uint8* bits = (uint8*)fUtility->cursorBitmap->Bits();
239	uint8* areaBits = (uint8*)fUtility->cursorAreaBitmap->Bits();
240	for (int32 i = 0; i < cursorHeight; i++) {
241		for (int32 j = 0; j < cursorWidth; j++) {
242			uint8 alpha = 255 - bits[3];
243			bits[0] = ((areaBits[0] * alpha) >> 8) + bits[0];
244			bits[1] = ((areaBits[1] * alpha) >> 8) + bits[1];
245			bits[2] = ((areaBits[2] * alpha) >> 8) + bits[2];
246			bits[3] = 255;
247			areaBits += 4;
248			bits += 4;
249		}
250	}
251}
252
253
254status_t
255Screenshot::_GetActiveWindowFrame()
256{
257	fUtility->activeWindowFrame.Set(0, 0, -1, -1);
258
259	// Create a messenger to communicate with the active application
260	app_info appInfo;
261	status_t status = be_roster->GetActiveAppInfo(&appInfo);
262	if (status != B_OK)
263		return status;
264	BMessenger messenger(appInfo.signature, appInfo.team);
265	if (!messenger.IsValid())
266		return B_ERROR;
267
268	// Loop through the windows of the active application to find out which
269	// window is active
270	int32 tokenCount;
271	int32* tokens = get_token_list(appInfo.team, &tokenCount);
272	bool foundActiveWindow = false;
273	BMessage message;
274	BMessage reply;
275	int32 index = 0;
276	int32 token = -1;
277	client_window_info* windowInfo = NULL;
278	bool modalWindow = false;
279	while (true) {
280		message.MakeEmpty();
281		reply.MakeEmpty();
282
283		message.what = B_GET_PROPERTY;
284		message.AddSpecifier("Active");
285		message.AddSpecifier("Window", index);
286		messenger.SendMessage(&message, &reply, B_INFINITE_TIMEOUT, 50000);
287
288		if (reply.what == B_MESSAGE_NOT_UNDERSTOOD)
289			break;
290
291		if (!reply.what) {
292			// Reply timout, this probably means that we have a modal window
293			// so we'll just get the window frame of the top most window
294			modalWindow = true;
295			free(tokens);
296			status_t status = BPrivate::get_window_order(current_workspace(),
297				&tokens, &tokenCount);
298			if (status != B_OK || !tokens || tokenCount < 1)
299				return B_ERROR;
300			foundActiveWindow = true;
301		} else if (reply.FindBool("result", &foundActiveWindow) != B_OK)
302			foundActiveWindow = false;
303
304		if (foundActiveWindow) {
305			// Get the client_window_info of the active window
306			foundActiveWindow = false;
307			for (int i = 0; i < tokenCount; i++) {
308				token = tokens[i];
309				windowInfo = get_window_info(token);
310				if (windowInfo && !windowInfo->is_mini
311						&& !windowInfo->show_hide_level > 0) {
312					foundActiveWindow = true;
313					break;
314				}
315				free(windowInfo);
316			}
317			if (foundActiveWindow)
318				break;
319		}
320		index++;
321	}
322	free(tokens);
323
324	if (!foundActiveWindow)
325		return B_ERROR;
326
327	// Get the TabFrame using the scripting interface
328	if (!modalWindow) {
329		message.MakeEmpty();
330		message.what = B_GET_PROPERTY;
331		message.AddSpecifier("TabFrame");
332		message.AddSpecifier("Window", index);
333		reply.MakeEmpty();
334		messenger.SendMessage(&message, &reply);
335
336		if (reply.FindRect("result", &fUtility->tabFrame) != B_OK)
337			return B_ERROR;
338	} else
339		fUtility->tabFrame.Set(0, 0, 0, 0);
340
341	// Get the active window frame from the client_window_info
342	fUtility->activeWindowFrame.left = windowInfo->window_left;
343	fUtility->activeWindowFrame.top = windowInfo->window_top;
344	fUtility->activeWindowFrame.right = windowInfo->window_right;
345	fUtility->activeWindowFrame.bottom = windowInfo->window_bottom;
346	fUtility->borderSize = windowInfo->border_size;
347
348	free(windowInfo);
349
350	// Make sure that fActiveWindowFrame doesn't extend beyond the screen frame
351	BRect screenFrame(BScreen().Frame());
352	if (fUtility->activeWindowFrame.left < screenFrame.left)
353		fUtility->activeWindowFrame.left = screenFrame.left;
354	if (fUtility->activeWindowFrame.top < screenFrame.top)
355		fUtility->activeWindowFrame.top = screenFrame.top;
356	if (fUtility->activeWindowFrame.right > screenFrame.right)
357		fUtility->activeWindowFrame.right = screenFrame.right;
358	if (fUtility->activeWindowFrame.bottom > screenFrame.bottom)
359		fUtility->activeWindowFrame.bottom = screenFrame.bottom;
360
361	return B_OK;
362}
363
364
365int32
366Screenshot::_GetImageType(const char* name) const
367{
368	if (strcmp(name, "bmp") == 0)
369		return B_BMP_FORMAT;
370	else if (strcmp(name, "gif") == 0)
371		return B_GIF_FORMAT;
372	else if (strcmp(name, "jpg") == 0 || strcmp(name, "jpeg") == 0)
373		return B_JPEG_FORMAT;
374	else if (strcmp(name, "ppm") == 0)
375		return B_PPM_FORMAT;
376	else if (strcmp(name, "tga") == 0 || strcmp(name, "targa") == 0)
377		return B_TGA_FORMAT;
378	else if (strcmp(name, "tif") == 0 || strcmp(name, "tiff") == 0)
379		return B_TIFF_FORMAT;
380	else {
381		return B_PNG_FORMAT;
382	}
383}
384
385
386int
387main()
388{
389	Screenshot screenshot;
390	return screenshot.Run();
391}
392