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