xref: /haiku/src/apps/screenshot/Screenshot.cpp (revision 97dfeb96704e5dbc5bec32ad7b21379d0125e031)
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 
30 #include "Utility.h"
31 
32 
33 #undef B_TRANSLATION_CONTEXT
34 #define B_TRANSLATION_CONTEXT "Screenshot"
35 
36 
37 Screenshot::Screenshot()
38 	:
39 	BApplication("application/x-vnd.haiku-screenshot-cli"),
40 	fUtility(new Utility()),
41 	fLaunchGui(true)
42 {
43 }
44 
45 
46 Screenshot::~Screenshot()
47 {
48 	delete fUtility;
49 }
50 
51 
52 void
53 Screenshot::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 = _ImageType(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 
126 void
127 Screenshot::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 
162 void
163 Screenshot::_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 
195 void
196 Screenshot::_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 
254 status_t
255 Screenshot::_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 
365 int32
366 Screenshot::_ImageType(const char* name) const
367 {
368 	if (strcasecmp(name, "bmp") == 0)
369 		return B_BMP_FORMAT;
370 	if (strcasecmp(name, "gif") == 0)
371 		return B_GIF_FORMAT;
372 	if (strcasecmp(name, "jpg") == 0 || strcmp(name, "jpeg") == 0)
373 		return B_JPEG_FORMAT;
374 	if (strcasecmp(name, "ppm") == 0)
375 		return B_PPM_FORMAT;
376 	if (strcasecmp(name, "tga") == 0 || strcmp(name, "targa") == 0)
377 		return B_TGA_FORMAT;
378 	if (strcasecmp(name, "tif") == 0 || strcmp(name, "tiff") == 0)
379 		return B_TIFF_FORMAT;
380 
381 	return B_PNG_FORMAT;
382 }
383 
384 
385 // #pragma mark -
386 
387 
388 int
389 main()
390 {
391 	Screenshot screenshot;
392 	return screenshot.Run();
393 }
394