/src/opensc/src/ui/notify.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * notify.c: Notification implementation |
3 | | * |
4 | | * Copyright (C) 2017 Frank Morgner <frankmorgner@gmail.com> |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public |
17 | | * License along with this library; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | | */ |
20 | | |
21 | | #ifdef HAVE_CONFIG_H |
22 | | #include "config.h" |
23 | | #endif |
24 | | |
25 | | #include "notify.h" |
26 | | |
27 | | #if defined(ENABLE_NOTIFY) && (defined(__APPLE__)) |
28 | | |
29 | | #include "libopensc/internal.h" |
30 | | #include "libopensc/log.h" |
31 | | #include <signal.h> |
32 | | #include <stdlib.h> |
33 | | #include <string.h> |
34 | | #include <sys/wait.h> |
35 | | #include <unistd.h> |
36 | | |
37 | | static pid_t child = -1; |
38 | | |
39 | | void sc_notify_init(void) |
40 | | { |
41 | | } |
42 | | |
43 | | void sc_notify_close(void) |
44 | | { |
45 | | if (child > 0) { |
46 | | int i, status; |
47 | | for (i = 0; child != waitpid(child, &status, WNOHANG); i++) { |
48 | | switch (i) { |
49 | | case 0: |
50 | | kill(child, SIGKILL); |
51 | | break; |
52 | | case 1: |
53 | | kill(child, SIGTERM); |
54 | | break; |
55 | | default: |
56 | | /* SIGTERM was our last resort */ |
57 | | return; |
58 | | } |
59 | | usleep(100); |
60 | | } |
61 | | child = -1; |
62 | | } |
63 | | } |
64 | | |
65 | | #endif |
66 | | |
67 | | #if defined(ENABLE_NOTIFY) && defined(_WIN32) |
68 | | |
69 | | #include "common/compat_strlcpy.h" |
70 | | #include "invisible_window.h" |
71 | | #include <shellapi.h> |
72 | | #include <shlwapi.h> |
73 | | |
74 | | // {83C35893-99C6-4600-BFDB-45925C53BDD9} |
75 | | static const GUID myGUID = { 0x83c35893, 0x99c6, 0x4600, { 0xbf, 0xdb, 0x45, 0x92, 0x5c, 0x53, 0xbd, 0xd9 } }; |
76 | | HINSTANCE sc_notify_instance = NULL; |
77 | | HWND sc_notify_hwnd = NULL; |
78 | | BOOL delete_icon = TRUE; |
79 | | #define IDI_SMARTCARD 102 |
80 | | #define WMAPP_NOTIFYCALLBACK (WM_APP + 1) |
81 | | static BOOL RestoreTooltip(); |
82 | | void ShowContextMenu(HWND hwnd, POINT pt); |
83 | | |
84 | | // we need commctrl v6 for LoadIconMetric() |
85 | | #include <commctrl.h> |
86 | | |
87 | | static int GetMonitorScalingRatio(void) |
88 | | { |
89 | | POINT pt = { 0, 0 }; |
90 | | HMONITOR monitor; |
91 | | MONITORINFOEX info = { .cbSize = sizeof(MONITORINFOEX) }; |
92 | | DEVMODE devmode = { .dmSize = sizeof(DEVMODE) }; |
93 | | monitor = MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY); |
94 | | if (!monitor) |
95 | | goto err; |
96 | | if (!GetMonitorInfo(monitor, (LPMONITORINFO)&info)) |
97 | | goto err; |
98 | | if (!EnumDisplaySettings(info.szDevice, ENUM_CURRENT_SETTINGS, &devmode)) |
99 | | goto err; |
100 | | if (info.rcMonitor.left == info.rcMonitor.right) |
101 | | goto err; |
102 | | return 100*devmode.dmPelsWidth/(info.rcMonitor.right - info.rcMonitor.left); |
103 | | err: |
104 | | return 100; |
105 | | } |
106 | | |
107 | | static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) |
108 | | { |
109 | | switch (message) { |
110 | | case WM_COMMAND: |
111 | | { |
112 | | int const wmId = LOWORD(wParam); |
113 | | // Parse the menu selection: |
114 | | switch (wmId) |
115 | | { |
116 | | case WMAPP_EXIT: |
117 | | break; |
118 | | |
119 | | default: |
120 | | return DefWindowProc(hwnd, message, wParam, lParam); |
121 | | } |
122 | | } |
123 | | break; |
124 | | |
125 | | case WMAPP_NOTIFYCALLBACK: |
126 | | switch (LOWORD(lParam)) { |
127 | | case NIN_BALLOONTIMEOUT: |
128 | | case NIN_BALLOONUSERCLICK: |
129 | | RestoreTooltip(); |
130 | | break; |
131 | | |
132 | | case WM_CONTEXTMENU: |
133 | | { |
134 | | int scaling = GetMonitorScalingRatio(); |
135 | | POINT const pt = { 100*LOWORD(wParam)/scaling, 100*HIWORD(wParam)/scaling }; |
136 | | ShowContextMenu(hwnd, pt); |
137 | | } |
138 | | break; |
139 | | } |
140 | | break; |
141 | | |
142 | | default: |
143 | | return DefWindowProc(hwnd, message, wParam, lParam); |
144 | | } |
145 | | |
146 | | return 0; |
147 | | } |
148 | | |
149 | | static const char* lpszClassName = "NOTIFY_CLASS"; |
150 | | |
151 | | static BOOL AddNotificationIcon(void) |
152 | | { |
153 | | NOTIFYICONDATA nid = {0}; |
154 | | TCHAR path[MAX_PATH]={0}; |
155 | | BOOL r; |
156 | | |
157 | | sc_notify_hwnd = create_invisible_window(lpszClassName, WndProc, |
158 | | sc_notify_instance); |
159 | | if (!sc_notify_hwnd) { |
160 | | return FALSE; |
161 | | } |
162 | | |
163 | | nid.cbSize = sizeof(NOTIFYICONDATA); |
164 | | nid.hWnd = sc_notify_hwnd; |
165 | | // add the icon, setting the icon, tooltip, and callback message. |
166 | | // the icon will be identified with the GUID |
167 | | nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP | NIF_GUID; |
168 | | nid.guidItem = myGUID; |
169 | | nid.uCallbackMessage = WMAPP_NOTIFYCALLBACK; |
170 | | LoadIconMetric(sc_notify_instance, MAKEINTRESOURCEW(IDI_SMARTCARD), LIM_SMALL, &nid.hIcon); |
171 | | if (GetModuleFileName(NULL, path, ARRAYSIZE(path))) { |
172 | | const char *basename = strrchr(path, '\\'); |
173 | | if (basename) { |
174 | | basename++; |
175 | | if (0 != strcmp(basename, "opensc-notify.exe")) { |
176 | | /* Allow creation of system tray icon only for |
177 | | * "opensc-notify.exe" to avoid creation of the same icon by |
178 | | * multiple processes. */ |
179 | | delete_icon = FALSE; |
180 | | return FALSE; |
181 | | } |
182 | | } |
183 | | strlcpy(nid.szTip, path, ARRAYSIZE(nid.szTip)); |
184 | | } else { |
185 | | strcpy(nid.szTip, PACKAGE_NAME); |
186 | | } |
187 | | |
188 | | r = Shell_NotifyIcon(NIM_ADD, &nid); |
189 | | if (TRUE == r) { |
190 | | nid.uVersion = NOTIFYICON_VERSION_4; |
191 | | r = Shell_NotifyIcon(NIM_SETVERSION, &nid); |
192 | | } |
193 | | |
194 | | return r; |
195 | | } |
196 | | |
197 | | static BOOL DeleteNotificationIcon(void) |
198 | | { |
199 | | BOOL r; |
200 | | NOTIFYICONDATA nid = {0}; |
201 | | |
202 | | if (!delete_icon) { |
203 | | return FALSE; |
204 | | } |
205 | | |
206 | | nid.cbSize = sizeof(NOTIFYICONDATA); |
207 | | nid.uFlags = NIF_GUID; |
208 | | nid.guidItem = myGUID; |
209 | | |
210 | | r = Shell_NotifyIcon(NIM_DELETE, &nid); |
211 | | |
212 | | r &= delete_invisible_window(sc_notify_hwnd, lpszClassName, |
213 | | sc_notify_instance); |
214 | | sc_notify_hwnd = NULL; |
215 | | |
216 | | return r; |
217 | | } |
218 | | |
219 | | static BOOL RestoreTooltip() |
220 | | { |
221 | | // After the balloon is dismissed, restore the tooltip. |
222 | | NOTIFYICONDATA nid = {0}; |
223 | | |
224 | | nid.cbSize = sizeof(NOTIFYICONDATA); |
225 | | nid.uFlags = NIF_SHOWTIP | NIF_GUID; |
226 | | nid.guidItem = myGUID; |
227 | | |
228 | | return Shell_NotifyIcon(NIM_MODIFY, &nid); |
229 | | } |
230 | | |
231 | | void ShowContextMenu(HWND hwnd, POINT pt) |
232 | | { |
233 | | HMENU hMenu; |
234 | | hMenu=CreatePopupMenu(); |
235 | | |
236 | | if (hMenu) { |
237 | | AppendMenu(hMenu, MF_STRING, WMAPP_EXIT, ui_get_str(NULL, NULL, NULL, NOTIFY_EXIT)); |
238 | | |
239 | | // our window must be foreground before calling TrackPopupMenu or the menu will not disappear when the user clicks away |
240 | | SetForegroundWindow(hwnd); |
241 | | |
242 | | // respect menu drop alignment |
243 | | UINT uFlags = TPM_RIGHTBUTTON; |
244 | | if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0) { |
245 | | uFlags |= TPM_RIGHTALIGN; |
246 | | } else { |
247 | | uFlags |= TPM_LEFTALIGN; |
248 | | } |
249 | | |
250 | | TrackPopupMenuEx(hMenu, uFlags, pt.x, pt.y, hwnd, NULL); |
251 | | } |
252 | | } |
253 | | |
254 | | |
255 | | static void notify_shell(struct sc_context *ctx, |
256 | | const char *title, const char *text, LPCTSTR icon_path, int icon_index) |
257 | | { |
258 | | NOTIFYICONDATA nid = {0}; |
259 | | HICON icon = NULL; |
260 | | |
261 | | nid.cbSize = sizeof(NOTIFYICONDATA); |
262 | | nid.uFlags = NIF_GUID; |
263 | | nid.guidItem = myGUID; |
264 | | |
265 | | if (title) { |
266 | | strlcpy(nid.szInfoTitle, title, ARRAYSIZE(nid.szInfoTitle)); |
267 | | } |
268 | | if (text) { |
269 | | nid.uFlags |= NIF_INFO; |
270 | | strlcpy(nid.szInfo, text, ARRAYSIZE(nid.szInfo)); |
271 | | } |
272 | | if (icon_path) { |
273 | | ExtractIconEx(icon_path, icon_index, &icon, NULL, 1); |
274 | | if (icon) { |
275 | | nid.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON; |
276 | | nid.hBalloonIcon = icon; |
277 | | } |
278 | | } |
279 | | |
280 | | Shell_NotifyIcon(NIM_MODIFY, &nid); |
281 | | |
282 | | if (icon) { |
283 | | DestroyIcon(icon); |
284 | | } |
285 | | } |
286 | | |
287 | | void sc_notify_init(void) |
288 | | { |
289 | | if (!sc_notify_instance) { |
290 | | /* returns the HINSTANCE of the exe. If the code executes in a DLL, |
291 | | * sc_notify_instance_notify should be pre-initialized */ |
292 | | sc_notify_instance = GetModuleHandle(NULL); |
293 | | } |
294 | | AddNotificationIcon(); |
295 | | } |
296 | | |
297 | | void sc_notify_close(void) |
298 | | { |
299 | | DeleteNotificationIcon(); |
300 | | } |
301 | | |
302 | | void sc_notify(const char *title, const char *text) |
303 | | { |
304 | | notify_shell(NULL, title, text, NULL, 0); |
305 | | } |
306 | | |
307 | | void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr, |
308 | | struct sc_pkcs15_card *p15card, enum ui_str id) |
309 | | { |
310 | | const char *title, *text; |
311 | | LPCTSTR icon_path = NULL; |
312 | | int icon_index = 0; |
313 | | title = ui_get_str(ctx, atr, p15card, id); |
314 | | text = ui_get_str(ctx, atr, p15card, id+1); |
315 | | |
316 | | switch (id) { |
317 | | case NOTIFY_CARD_INSERTED: |
318 | | icon_path = TEXT("%SYSTEMROOT%\\system32\\SCardDlg.dll"); |
319 | | icon_index = 3; |
320 | | break; |
321 | | case NOTIFY_CARD_REMOVED: |
322 | | icon_path = TEXT("%SYSTEMROOT%\\system32\\SCardDlg.dll"); |
323 | | icon_index = 2; |
324 | | break; |
325 | | case NOTIFY_PIN_GOOD: |
326 | | icon_path = TEXT("%SYSTEMROOT%\\system32\\certmgr.dll"); |
327 | | icon_index = 16; |
328 | | break; |
329 | | case NOTIFY_PIN_BAD: |
330 | | icon_path = TEXT("%SYSTEMROOT%\\system32\\certmgr.dll"); |
331 | | icon_index = 11; |
332 | | break; |
333 | | default: |
334 | | break; |
335 | | } |
336 | | |
337 | | notify_shell(ctx, title, text, icon_path, icon_index); |
338 | | } |
339 | | |
340 | | #elif defined(ENABLE_NOTIFY) && defined(__APPLE__) |
341 | | |
342 | | static void notify_proxy(struct sc_context *ctx, |
343 | | const char *title, const char* subtitle, |
344 | | const char *text, const char *icon, const char *sound, |
345 | | const char *group) |
346 | | { |
347 | | /* terminal-notifier does not reliably activate keychain when clicked on |
348 | | * the notification |
349 | | * (https://github.com/julienXX/terminal-notifier/issues/196), that's why |
350 | | * we're including NotificationProxy which has similar features */ |
351 | | const char notificationproxy[] = "/Library/OpenSC/Applications/NotificationProxy.app/Contents/MacOS/NotificationProxy"; |
352 | | |
353 | | if (ctx && ctx->app_name |
354 | | && (0 == strcmp(ctx->app_name, "opensc-pkcs11") |
355 | | || 0 == strcmp(ctx->app_name, "onepin-opensc-pkcs11"))) { |
356 | | /* some programs don't like forking when loading our PKCS#11 module, |
357 | | * see https://github.com/OpenSC/OpenSC/issues/1174. |
358 | | * TODO implementing an XPC service which sends the notification should |
359 | | * work, though. See |
360 | | * https://github.com/OpenSC/OpenSC/issues/1304#issuecomment-376656003 */ |
361 | | return; |
362 | | } |
363 | | |
364 | | if (child > 0) { |
365 | | int status; |
366 | | if (0 == waitpid(child, &status, WNOHANG)) { |
367 | | kill(child, SIGKILL); |
368 | | usleep(100); |
369 | | if (0 == waitpid(child, &status, WNOHANG)) { |
370 | | sc_log(ctx, "Can't kill %ld, skipping current notification", (long) child); |
371 | | return; |
372 | | } |
373 | | } |
374 | | } |
375 | | |
376 | | child = fork(); |
377 | | switch (child) { |
378 | | case 0: |
379 | | /* child process */ |
380 | | |
381 | | /* for some reason the user _tokend can call brew's installation of |
382 | | * terminal-notifier, but it cannot call `/usr/bin/open` with |
383 | | * NotificationProxy.app that we're shipping... However, while |
384 | | * `sudo -u _tokend /usr/local/bin/terminal-notifier -title test` |
385 | | * works in the terminal, it hangs when executed from the tokend |
386 | | * process. For now, we try to deliver the notification anyway |
387 | | * making sure that we are waiting for only one forked process. */ |
388 | | if (0 > execl(notificationproxy, notificationproxy, |
389 | | title ? title : "", |
390 | | subtitle ? subtitle : "", |
391 | | text ? text : "", |
392 | | icon ? icon : "", |
393 | | group ? group : "", |
394 | | sound ? sound : "", |
395 | | (char *) NULL)) { |
396 | | perror("exec failed"); |
397 | | exit(0); |
398 | | } |
399 | | break; |
400 | | case -1: |
401 | | sc_log(ctx, "failed to fork for notification"); |
402 | | break; |
403 | | default: |
404 | | if (ctx) { |
405 | | sc_log(ctx, "Created %ld for notification:", (long) child); |
406 | | sc_log(ctx, "%s %s %s %s %s %s %s", notificationproxy, |
407 | | title ? title : "", |
408 | | subtitle ? subtitle : "", |
409 | | text ? text : "", |
410 | | icon ? icon : "", |
411 | | group ? group : "", |
412 | | sound ? sound : ""); |
413 | | } |
414 | | break; |
415 | | } |
416 | | } |
417 | | |
418 | | void sc_notify(const char *title, const char *text) |
419 | | { |
420 | | notify_proxy(NULL, title, NULL, text, NULL, NULL, NULL); |
421 | | } |
422 | | |
423 | | void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr, |
424 | | struct sc_pkcs15_card *p15card, enum ui_str id) |
425 | | { |
426 | | const char *title, *text, *icon, *group; |
427 | | title = ui_get_str(ctx, atr, p15card, id); |
428 | | text = ui_get_str(ctx, atr, p15card, id+1); |
429 | | |
430 | | if (p15card && p15card->card && p15card->card->reader) { |
431 | | group = p15card->card->reader->name; |
432 | | } else { |
433 | | group = ctx ? ctx->app_name : NULL; |
434 | | } |
435 | | |
436 | | switch (id) { |
437 | | case NOTIFY_CARD_INSERTED: |
438 | | icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/VCard.icns"; |
439 | | break; |
440 | | case NOTIFY_CARD_REMOVED: |
441 | | icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/EjectMediaIcon.icns"; |
442 | | break; |
443 | | case NOTIFY_PIN_GOOD: |
444 | | icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/UnlockedIcon.icns"; |
445 | | break; |
446 | | case NOTIFY_PIN_BAD: |
447 | | icon = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/LockedIcon.icns"; |
448 | | break; |
449 | | default: |
450 | | icon = NULL; |
451 | | break; |
452 | | } |
453 | | |
454 | | notify_proxy(ctx, title, NULL, text, icon, NULL, group); |
455 | | } |
456 | | |
457 | | #elif defined(ENABLE_NOTIFY) && defined(ENABLE_GIO2) |
458 | | |
459 | | #include <gio/gio.h> |
460 | | #include "libopensc/log.h" |
461 | | |
462 | | static GApplication *application = NULL; |
463 | | |
464 | | void sc_notify_init(void) |
465 | | { |
466 | | if (!application) { |
467 | | application = g_application_new("org.opensc.notify", G_APPLICATION_NON_UNIQUE); |
468 | | } |
469 | | if (application && FALSE == g_application_get_is_registered(application)) { |
470 | | g_application_register(application, NULL, NULL); |
471 | | } |
472 | | } |
473 | | |
474 | | void sc_notify_close(void) |
475 | | { |
476 | | if (application) { |
477 | | g_object_unref(application); |
478 | | application = NULL; |
479 | | } |
480 | | } |
481 | | |
482 | | static void notify_gio(struct sc_context *ctx, |
483 | | const char *title, const char *text, const char *icon, |
484 | | const char *group) |
485 | | { |
486 | | if (application |
487 | | && g_application_get_is_registered(application) |
488 | | && g_application_get_dbus_connection(application)) { |
489 | | GIcon *gicon = NULL; |
490 | | GNotification *notification = g_notification_new(title); |
491 | | if (!notification) { |
492 | | return; |
493 | | } |
494 | | |
495 | | if (text) { |
496 | | g_notification_set_body(notification, text); |
497 | | } |
498 | | if (icon) { |
499 | | gicon = g_themed_icon_new(icon); |
500 | | if (gicon) { |
501 | | g_notification_set_icon(notification, gicon); |
502 | | } |
503 | | } |
504 | | |
505 | | if (ctx) { |
506 | | sc_log(ctx, "%s %s %s %s", |
507 | | title ? title : "", |
508 | | text ? text : "", |
509 | | icon ? icon : "", |
510 | | group ? group : ""); |
511 | | } |
512 | | |
513 | | g_application_send_notification(application, group, notification); |
514 | | |
515 | | if (gicon) { |
516 | | g_object_unref(gicon); |
517 | | } |
518 | | g_object_unref(notification); |
519 | | } |
520 | | } |
521 | | |
522 | | #else |
523 | | |
524 | 0 | void sc_notify_init(void) {} |
525 | 0 | void sc_notify_close(void) {} |
526 | 0 | void sc_notify(const char *title, const char *text) {} |
527 | | void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr, |
528 | 0 | struct sc_pkcs15_card *p15card, enum ui_str id) {} |
529 | | |
530 | | #endif |
531 | | |
532 | | #if defined(ENABLE_NOTIFY) && defined(ENABLE_GIO2) |
533 | | void sc_notify(const char *title, const char *text) |
534 | | { |
535 | | notify_gio(NULL, title, text, NULL, NULL); |
536 | | } |
537 | | |
538 | | void sc_notify_id(struct sc_context *ctx, struct sc_atr *atr, |
539 | | struct sc_pkcs15_card *p15card, enum ui_str id) |
540 | | { |
541 | | const char *title, *text, *icon, *group; |
542 | | title = ui_get_str(ctx, atr, p15card, id); |
543 | | text = ui_get_str(ctx, atr, p15card, id+1); |
544 | | |
545 | | if (p15card && p15card->card && p15card->card->reader) { |
546 | | group = p15card->card->reader->name; |
547 | | } else { |
548 | | group = ctx ? ctx->app_name : NULL; |
549 | | } |
550 | | |
551 | | switch (id) { |
552 | | case NOTIFY_CARD_INSERTED: |
553 | | icon = "contact-new"; |
554 | | break; |
555 | | case NOTIFY_CARD_REMOVED: |
556 | | icon = "media-eject"; |
557 | | break; |
558 | | case NOTIFY_PIN_GOOD: |
559 | | icon = "changes-allow"; |
560 | | break; |
561 | | case NOTIFY_PIN_BAD: |
562 | | icon = "changes-prevent"; |
563 | | break; |
564 | | default: |
565 | | icon = NULL; |
566 | | break; |
567 | | } |
568 | | |
569 | | notify_gio(ctx, title, text, icon, group); |
570 | | } |
571 | | |
572 | | #endif |