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