xref: /haiku/src/bin/notify.cpp (revision a3e794ae459fec76826407f8ba8c94cd3535f128)
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 
23 const char* kSignature			= "application/x-vnd.Haiku-notify";
24 const char* kSmallIconAttribute = "BEOS:M:STD_ICON";
25 const char* kLargeIconAttribute = "BEOS:L:STD_ICON";
26 const char* kIconAttribute		= "BEOS:ICON";
27 
28 const char *kTypeNames[] = {
29 	"information",
30 	"important",
31 	"error",
32 	"progress",
33 	NULL
34 };
35 
36 const int32 kErrorInitFail		= 127;
37 const int32 kErrorArgumentsFail	= 126;
38 
39 class NotifyApp : public BApplication {
40 public:
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 
50 private:
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 
72 NotifyApp::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 
86 NotifyApp::~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 
98 void
99 NotifyApp::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 
180 void
181 NotifyApp::_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 
205 BBitmap*
206 NotifyApp::_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 
229 void
230 NotifyApp::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 
286 int
287 main(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