1/*
2 * Copyright 2010, Haiku, Inc. All rights reserved.
3 * Copyright 2008, Pier Luigi Fiorini.
4 * Distributed under the terms of the MIT License.
5 *
6 * Authors:
7 *		Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
8 *		Stephan Aßmus <superstippi@gmx.de>
9 */
10
11#include <stdio.h>
12#include <stdlib.h>
13
14#include <Application.h>
15#include <Bitmap.h>
16#include <IconUtils.h>
17#include <List.h>
18#include <Mime.h>
19#include <Notification.h>
20#include <Path.h>
21#include <TranslationUtils.h>
22
23const char* kSignature			= "application/x-vnd.Haiku-notify";
24const char* kSmallIconAttribute = "BEOS:M:STD_ICON";
25const char* kLargeIconAttribute = "BEOS:L:STD_ICON";
26const char* kIconAttribute		= "BEOS:ICON";
27
28const char *kTypeNames[] = {
29	"information",
30	"important",
31	"error",
32	"progress",
33	NULL
34};
35
36const int32 kErrorInitFail		= 127;
37const int32 kErrorArgumentsFail	= 126;
38
39class NotifyApp : public BApplication {
40public:
41								NotifyApp();
42	virtual						~NotifyApp();
43
44	virtual void				ReadyToRun();
45	virtual void				ArgvReceived(int32 argc, char** argv);
46
47			bool				HasGoodArguments() const
48									{ return fHasGoodArguments; }
49
50private:
51			bool				fHasGoodArguments;
52			notification_type	fType;
53			BString				fGroup;
54			BString				fTitle;
55			BString				fMsgId;
56			float				fProgress;
57			bigtime_t			fTimeout;
58			BString				fIconFile;
59			entry_ref			fFileRef;
60			BString				fContent;
61			BString				fOnClickApp;
62			bool				fHasFile;
63			entry_ref			fFile;
64			BList*				fRefs;
65			BList*				fArgv;
66
67			void				_Usage() const;
68			BBitmap*			_GetBitmap(const entry_ref* ref) const;
69};
70
71
72NotifyApp::NotifyApp()
73	:
74	BApplication(kSignature),
75	fHasGoodArguments(false),
76	fType(B_INFORMATION_NOTIFICATION),
77	fProgress(0.0f),
78	fTimeout(-1),
79	fHasFile(false)
80{
81	fRefs = new BList();
82	fArgv = new BList();
83}
84
85
86NotifyApp::~NotifyApp()
87{
88	for (int32 i = 0; void* item = fRefs->ItemAt(i); i++)
89		delete (BEntry*)item;
90	delete fRefs;
91
92	for (int32 i = 0; void* item = fArgv->ItemAt(i); i++)
93		delete (BString*)item;
94	delete fArgv;
95}
96
97
98void
99NotifyApp::ArgvReceived(int32 argc, char** argv)
100{
101	const uint32 kArgCount = argc - 1;
102	uint32 index = 1;
103
104	// Look for valid options
105	for (; index <= kArgCount; ++index) {
106		if (argv[index][0] == '-' && argv[index][1] == '-') {
107			const char* option = argv[index] + 2;
108
109			if (++index > kArgCount) {
110				// No argument to option
111				fprintf(stderr, "Missing argument to option --%s\n\n", option);
112				return;
113			}
114
115			const char* argument = argv[index];
116
117			if (strcmp(option, "type") == 0) {
118				for (int32 i = 0; kTypeNames[i]; i++) {
119					if (strncmp(kTypeNames[i], argument, strlen(argument)) == 0)
120						fType = (notification_type)i;
121				}
122			} else if (strcmp(option, "group") == 0)
123				fGroup = argument;
124			else if (strcmp(option, "title") == 0)
125				fTitle = argument;
126			else if (strcmp(option, "messageID") == 0)
127				fMsgId = argument;
128			else if (strcmp(option, "progress") == 0)
129				fProgress = atof(argument);
130			else if (strcmp(option, "timeout") == 0)
131				fTimeout = atol(argument) * 1000000;
132			else if (strcmp(option, "icon") == 0) {
133				fIconFile = argument;
134
135				if (get_ref_for_path(fIconFile.String(), &fFileRef) < B_OK) {
136					fprintf(stderr, "Bad icon path!\n\n");
137					return;
138				}
139			} else if (strcmp(option, "onClickApp") == 0)
140				fOnClickApp = argument;
141			else if (strcmp(option, "onClickFile") == 0) {
142				if (get_ref_for_path(argument, &fFile) != B_OK) {
143					fprintf(stderr, "Bad path for --onClickFile!\n\n");
144					return;
145				}
146
147				fHasFile = true;
148			} else if (strcmp(option, "onClickRef") == 0) {
149				entry_ref ref;
150
151				if (get_ref_for_path(argument, &ref) != B_OK) {
152					fprintf(stderr, "Bad path for --onClickRef!\n\n");
153					return;
154				}
155
156				fRefs->AddItem(new BEntry(&ref));
157			} else if (strcmp(option, "onClickArgv") == 0)
158				fArgv->AddItem(new BString(argument));
159			else {
160				// Unrecognized option
161				fprintf(stderr, "Unrecognized option --%s\n\n", option);
162				return;
163			}
164		} else {
165			// Option doesn't start with '--'
166			break;
167		}
168
169		if (index == kArgCount) {
170			// No text argument provided, only '--' arguments
171			fprintf(stderr, "Missing message argument!\n\n");
172			return;
173		}
174	}
175
176	fContent = argv[index];
177	fHasGoodArguments = true;
178}
179
180void
181NotifyApp::_Usage() const
182{
183	fprintf(stderr, "Usage: notify [OPTION]... [MESSAGE]\n"
184		"Send notifications to notification_server.\n"
185		"  --type <type>\tNotification type,\n"
186		"               \t      <type> - \"information\" is assumed by default: ");
187
188	for (int32 i = 0; kTypeNames[i]; i++)
189		fprintf(stderr, kTypeNames[i + 1] ? "%s|" : "%s\n", kTypeNames[i]);
190
191	fprintf(stderr,
192		"  --group <group>\tGroup\n"
193		"  --title <title>\tMessage title\n"
194		"  --messageID <msg id>\tMessage ID\n"
195		"  --progress <float>\tProgress, value between 0.0 and 1.0  - if type is set to progress\n"
196		"  --timeout <secs>\tSpecify timeout\n"
197		"  --onClickApp <signature>\tApplication to open when notification is clicked\n"
198		"  --onClickFile <fullpath>\tFile to open when notification is clicked\n"
199		"  --onClickRef <fullpath>\tFile to open with the application when notification is clicked\n"
200		"  --onClickArgv <arg>\tArgument to the application when notification is clicked\n"
201		"  --icon <icon file> Icon\n");
202}
203
204
205BBitmap*
206NotifyApp::_GetBitmap(const entry_ref* ref) const
207{
208	BBitmap* bitmap = NULL;
209
210	// First try by contents
211	bitmap = BTranslationUtils::GetBitmap(ref);
212	if (bitmap)
213		return bitmap;
214
215	// Then, try reading its attribute
216	BNode node(BPath(ref).Path());
217	bitmap = new BBitmap(BRect(0, 0, (float)B_LARGE_ICON - 1,
218		(float)B_LARGE_ICON - 1), B_RGBA32);
219	if (BIconUtils::GetIcon(&node, kIconAttribute, kSmallIconAttribute,
220		kLargeIconAttribute, B_LARGE_ICON, bitmap) != B_OK) {
221		delete bitmap;
222		bitmap = NULL;
223	}
224
225	return bitmap;
226}
227
228
229void
230NotifyApp::ReadyToRun()
231{
232	if (HasGoodArguments()) {
233		BNotification notification(fType);
234		if (fGroup != "")
235			notification.SetGroup(fGroup);
236		if (fTitle != "")
237			notification.SetTitle(fTitle);
238		if (fContent != "")
239			notification.SetContent(fContent);
240
241		if (fMsgId != "")
242			notification.SetMessageID(fMsgId);
243
244		if (fType == B_PROGRESS_NOTIFICATION)
245			notification.SetProgress(fProgress);
246
247		if (fIconFile != "") {
248			BBitmap* bitmap = _GetBitmap(&fFileRef);
249			if (bitmap) {
250				notification.SetIcon(bitmap);
251				delete bitmap;
252			}
253		}
254
255		if (fOnClickApp != "")
256			notification.SetOnClickApp(fOnClickApp);
257
258		if (fHasFile)
259			notification.SetOnClickFile(&fFile);
260
261		for (int32 i = 0; void* item = fRefs->ItemAt(i); i++) {
262			BEntry* entry = (BEntry*)item;
263
264			entry_ref ref;
265			if (entry->GetRef(&ref) == B_OK)
266				notification.AddOnClickRef(&ref);
267		}
268
269		for (int32 i = 0; void* item = fArgv->ItemAt(i); i++) {
270			BString* arg = (BString*)item;
271			notification.AddOnClickArg(arg->String());
272		}
273
274		status_t ret = notification.Send(fTimeout);
275		if (ret != B_OK) {
276			fprintf(stderr, "Failed to deliver notification: %s\n",
277				strerror(ret));
278		}
279	} else
280		_Usage();
281
282	Quit();
283}
284
285
286int
287main(int argc, char** argv)
288{
289	NotifyApp app;
290	if (app.InitCheck() != B_OK)
291		return kErrorInitFail;
292
293	app.Run();
294	if (!app.HasGoodArguments())
295		return kErrorArgumentsFail;
296
297	return 0;
298}
299