aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEli Zaretskii2015-11-11 18:29:36 +0200
committerEli Zaretskii2015-11-11 18:29:36 +0200
commit9d43941569fc3840fa9306d149461a8439a54d68 (patch)
treeeb92562206e85b6c5cb6ebe0f54c06544580df89 /src
parentef75c3b56b8ff034eb47e0c69328227127cc93fa (diff)
downloademacs-9d43941569fc3840fa9306d149461a8439a54d68.tar.gz
emacs-9d43941569fc3840fa9306d149461a8439a54d68.zip
Implement tray notifications for MS-Windows
* src/w32fns.c (MY_NOTIFYICONDATAW): New typedef. (NOTIFYICONDATAW_V1_SIZE, NOTIFYICONDATAW_V2_SIZE) (NOTIFYICONDATAW_V3_SIZE, NIF_INFO, NIIF_NONE, NIIF_INFO) (NIIF_WARNING, NIIF_ERROR, EMACS_TRAY_NOTIFICATION_ID) (EMACS_NOTIFICATION_MSG): New macros. (NI_Severity): New enumeration. (get_dll_version, utf8_mbslen_lim, add_tray_notification) (delete_tray_notification, Fw32_notification_notify) (Fw32_notification_close): New functions. (syms_of_w32fns): Defsubr functions exposed to Lisp. DEFSYM keywords used by w32-notification-notify. * doc/lispref/os.texi (Desktop Notifications): Describe the native w32 tray notifications.
Diffstat (limited to 'src')
-rw-r--r--src/w32fns.c478
1 files changed, 478 insertions, 0 deletions
diff --git a/src/w32fns.c b/src/w32fns.c
index d92352a9802..eed849f1034 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -55,6 +55,7 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
55#include <commctrl.h> 55#include <commctrl.h>
56#include <commdlg.h> 56#include <commdlg.h>
57#include <shellapi.h> 57#include <shellapi.h>
58#include <shlwapi.h>
58#include <ctype.h> 59#include <ctype.h>
59#include <winspool.h> 60#include <winspool.h>
60#include <objbase.h> 61#include <objbase.h>
@@ -8755,6 +8756,473 @@ Internal use only. */)
8755 return menubar_in_use ? Qt : Qnil; 8756 return menubar_in_use ? Qt : Qnil;
8756} 8757}
8757 8758
8759/***********************************************************************
8760 Tray notifications
8761 ***********************************************************************/
8762/* A private struct declaration to avoid compile-time limits. */
8763typedef struct MY_NOTIFYICONDATAW {
8764 DWORD cbSize;
8765 HWND hWnd;
8766 UINT uID;
8767 UINT uFlags;
8768 UINT uCallbackMessage;
8769 HICON hIcon;
8770 WCHAR szTip[128];
8771 DWORD dwState;
8772 DWORD dwStateMask;
8773 WCHAR szInfo[256];
8774 _ANONYMOUS_UNION union {
8775 UINT uTimeout;
8776 UINT uVersion;
8777 } DUMMYUNIONNAME;
8778 WCHAR szInfoTitle[64];
8779 DWORD dwInfoFlags;
8780 GUID guidItem;
8781 HICON hBalloonIcon;
8782} MY_NOTIFYICONDATAW;
8783
8784#ifndef NOTIFYICONDATAW_V1_SIZE
8785# define NOTIFYICONDATAW_V1_SIZE offsetof (MY_NOTIFYICONDATAW, szTip[64])
8786#endif
8787#ifndef NOTIFYICONDATAW_V2_SIZE
8788# define NOTIFYICONDATAW_V2_SIZE offsetof (MY_NOTIFYICONDATAW, guidItem)
8789#endif
8790#ifndef NOTIFYICONDATAW_V3_SIZE
8791# define NOTIFYICONDATAW_V3_SIZE offsetof (MY_NOTIFYICONDATAW, hBalloonIcon)
8792#endif
8793#ifndef NIF_INFO
8794# define NIF_INFO 0x00000010
8795#endif
8796#ifndef NIIF_NONE
8797# define NIIF_NONE 0x00000000
8798#endif
8799#ifndef NIIF_INFO
8800# define NIIF_INFO 0x00000001
8801#endif
8802#ifndef NIIF_WARNING
8803# define NIIF_WARNING 0x00000002
8804#endif
8805#ifndef NIIF_ERROR
8806# define NIIF_ERROR 0x00000003
8807#endif
8808
8809
8810#define EMACS_TRAY_NOTIFICATION_ID 42 /* arbitrary */
8811#define EMACS_NOTIFICATION_MSG (WM_APP + 1)
8812
8813enum NI_Severity {
8814 Ni_None,
8815 Ni_Info,
8816 Ni_Warn,
8817 Ni_Err
8818};
8819
8820/* Report the version of a DLL given by its name. The return value is
8821 constructed using MAKEDLLVERULL. */
8822static ULONGLONG
8823get_dll_version (const char *dll_name)
8824{
8825 ULONGLONG version = 0;
8826 HINSTANCE hdll = LoadLibrary (dll_name);
8827
8828 if (hdll)
8829 {
8830 DLLGETVERSIONPROC pDllGetVersion
8831 = (DLLGETVERSIONPROC) GetProcAddress (hdll, "DllGetVersion");
8832
8833 if (pDllGetVersion)
8834 {
8835 DLLVERSIONINFO dvi;
8836 HRESULT result;
8837
8838 memset (&dvi, 0, sizeof(dvi));
8839 dvi.cbSize = sizeof(dvi);
8840 result = pDllGetVersion (&dvi);
8841 if (SUCCEEDED (result))
8842 version = MAKEDLLVERULL (dvi.dwMajorVersion, dvi.dwMinorVersion,
8843 0, 0);
8844 }
8845 FreeLibrary (hdll);
8846 }
8847
8848 return version;
8849}
8850
8851/* Return the number of bytes in UTF-8 encoded string STR that
8852 corresponds to at most LIM characters. If STR ends before LIM
8853 characters, return the number of bytes in STR including the
8854 terminating null byte. */
8855static int
8856utf8_mbslen_lim (const char *str, int lim)
8857{
8858 const char *p = str;
8859 int mblen = 0, nchars = 0;
8860
8861 while (*p && nchars < lim)
8862 {
8863 int nbytes = CHAR_BYTES (*p);
8864
8865 mblen += nbytes;
8866 nchars++;
8867 p += nbytes;
8868 }
8869
8870 if (!*p && nchars < lim)
8871 mblen++;
8872
8873 return mblen;
8874}
8875
8876/* Low-level subroutine to show tray notifications. All strings are
8877 supposed to be unibyte UTF-8 encoded by the caller. */
8878static EMACS_INT
8879add_tray_notification (struct frame *f, const char *icon, const char *tip,
8880 enum NI_Severity severity, unsigned timeout,
8881 const char *title, const char *msg)
8882{
8883 EMACS_INT retval = EMACS_TRAY_NOTIFICATION_ID;
8884
8885 if (FRAME_W32_P (f))
8886 {
8887 MY_NOTIFYICONDATAW nidw;
8888 ULONGLONG shell_dll_version = get_dll_version ("Shell32.dll");
8889 wchar_t tipw[128], msgw[256], titlew[64];
8890 int tiplen;
8891
8892 memset (&nidw, 0, sizeof(nidw));
8893
8894 /* MSDN says the full struct is supported since Vista, whose
8895 Shell32.dll version is said to be 6.0.6. But DllGetVersion
8896 cannot report the 3rd field value, it reports "build number"
8897 instead, which is something else. So we use the Windows 7's
8898 version 6.1 as cutoff, and Vista loses. (Actually, the loss
8899 is not a real one, since we don't expose the hBalloonIcon
8900 member of the struct to Lisp.) */
8901 if (shell_dll_version >= MAKEDLLVERULL (6, 1, 0, 0)) /* >= Windows 7 */
8902 nidw.cbSize = sizeof (nidw);
8903 else if (shell_dll_version >= MAKEDLLVERULL (6, 0, 0, 0)) /* XP */
8904 nidw.cbSize = NOTIFYICONDATAW_V3_SIZE;
8905 else if (shell_dll_version >= MAKEDLLVERULL (5, 0, 0, 0)) /* W2K */
8906 nidw.cbSize = NOTIFYICONDATAW_V2_SIZE;
8907 else
8908 nidw.cbSize = NOTIFYICONDATAW_V1_SIZE; /* < W2K */
8909 nidw.hWnd = FRAME_W32_WINDOW (f);
8910 nidw.uID = EMACS_TRAY_NOTIFICATION_ID;
8911 nidw.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_INFO;
8912 nidw.uCallbackMessage = EMACS_NOTIFICATION_MSG;
8913 if (!*icon)
8914 nidw.hIcon = LoadIcon (hinst, EMACS_CLASS);
8915 else
8916 {
8917 if (w32_unicode_filenames)
8918 {
8919 wchar_t icon_w[MAX_PATH];
8920
8921 if (filename_to_utf16 (icon, icon_w) != 0)
8922 {
8923 errno = ENOENT;
8924 return -1;
8925 }
8926 nidw.hIcon = LoadImageW (NULL, icon_w, IMAGE_ICON, 0, 0,
8927 LR_DEFAULTSIZE | LR_LOADFROMFILE);
8928 }
8929 else
8930 {
8931 char icon_a[MAX_PATH];
8932
8933 if (filename_to_ansi (icon, icon_a) != 0)
8934 {
8935 errno = ENOENT;
8936 return -1;
8937 }
8938 nidw.hIcon = LoadImageA (NULL, icon_a, IMAGE_ICON, 0, 0,
8939 LR_DEFAULTSIZE | LR_LOADFROMFILE);
8940 }
8941 }
8942 if (!nidw.hIcon)
8943 {
8944 switch (GetLastError ())
8945 {
8946 case ERROR_FILE_NOT_FOUND:
8947 errno = ENOENT;
8948 break;
8949 default:
8950 errno = ENOMEM;
8951 break;
8952 }
8953 return -1;
8954 }
8955
8956 /* Windows 9X and NT4 support only 64 characters in the Tip,
8957 later versions support up to 128. */
8958 if (nidw.cbSize == NOTIFYICONDATAW_V1_SIZE)
8959 {
8960 tiplen = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
8961 tip, utf8_mbslen_lim (tip, 63),
8962 tipw, 64);
8963 if (tiplen >= 63)
8964 tipw[63] = 0;
8965 }
8966 else
8967 {
8968 tiplen = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
8969 tip, utf8_mbslen_lim (tip, 127),
8970 tipw, 128);
8971 if (tiplen >= 127)
8972 tipw[127] = 0;
8973 }
8974 if (tiplen == 0)
8975 {
8976 errno = EINVAL;
8977 retval = -1;
8978 goto done;
8979 }
8980 wcscpy (nidw.szTip, tipw);
8981
8982 /* The rest of the structure is only supported since Windows 2000. */
8983 if (nidw.cbSize > NOTIFYICONDATAW_V1_SIZE)
8984 {
8985 int slen;
8986
8987 slen = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
8988 msg, utf8_mbslen_lim (msg, 255),
8989 msgw, 256);
8990 if (slen >= 255)
8991 msgw[255] = 0;
8992 else if (slen == 0)
8993 {
8994 errno = EINVAL;
8995 retval = -1;
8996 goto done;
8997 }
8998 wcscpy (nidw.szInfo, msgw);
8999 nidw.uTimeout = timeout;
9000 slen = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
9001 title, utf8_mbslen_lim (title, 63),
9002 titlew, 64);
9003 if (slen >= 63)
9004 titlew[63] = 0;
9005 else if (slen == 0)
9006 {
9007 errno = EINVAL;
9008 retval = -1;
9009 goto done;
9010 }
9011 wcscpy (nidw.szInfoTitle, titlew);
9012
9013 switch (severity)
9014 {
9015 case Ni_None:
9016 nidw.dwInfoFlags = NIIF_NONE;
9017 break;
9018 case Ni_Info:
9019 default:
9020 nidw.dwInfoFlags = NIIF_INFO;
9021 break;
9022 case Ni_Warn:
9023 nidw.dwInfoFlags = NIIF_WARNING;
9024 break;
9025 case Ni_Err:
9026 nidw.dwInfoFlags = NIIF_ERROR;
9027 break;
9028 }
9029 }
9030
9031 if (!Shell_NotifyIconW (NIM_ADD, (PNOTIFYICONDATAW)&nidw))
9032 {
9033 /* GetLastError returns meaningless results when
9034 Shell_NotifyIcon fails. */
9035 DebPrint (("Shell_NotifyIcon ADD failed (err=%d)\n",
9036 GetLastError ()));
9037 errno = EINVAL;
9038 retval = -1;
9039 }
9040 done:
9041 if (*icon && !DestroyIcon (nidw.hIcon))
9042 DebPrint (("DestroyIcon failed (err=%d)\n", GetLastError ()));
9043 }
9044 return retval;
9045}
9046
9047/* Low-level subroutine to remove a tray notification. Note: we only
9048 pass the minimum data about the notification: its ID and the handle
9049 of the window to which it sends messages. MSDN doesn't say this is
9050 enough, but it works in practice. This allows us to avoid keeping
9051 the notification data around after we show the notification. */
9052static void
9053delete_tray_notification (struct frame *f, int id)
9054{
9055 if (FRAME_W32_P (f))
9056 {
9057 MY_NOTIFYICONDATAW nidw;
9058
9059 memset (&nidw, 0, sizeof(nidw));
9060 nidw.hWnd = FRAME_W32_WINDOW (f);
9061 nidw.uID = id;
9062
9063 if (!Shell_NotifyIconW (NIM_DELETE, (PNOTIFYICONDATAW)&nidw))
9064 {
9065 /* GetLastError returns meaningless results when
9066 Shell_NotifyIcon fails. */
9067 DebPrint (("Shell_NotifyIcon DELETE failed\n"));
9068 errno = EINVAL;
9069 return;
9070 }
9071 }
9072 return;
9073}
9074
9075DEFUN ("w32-notification-notify",
9076 Fw32_notification_notify, Sw32_notification_notify,
9077 0, MANY, 0,
9078 doc: /* Display an MS-Windows tray notification as specified by PARAMS.
9079
9080Value is the integer unique ID of the notification that can be used
9081to remove the notification using `w32-notification-close', which see.
9082If the function fails, the return value is nil.
9083
9084Tray notifications, a.k.a. \"taskbar messages\", are messages that
9085inform the user about events unrelated to the current user activity,
9086such as a significant system event, by briefly displaying informative
9087text in a balloon from an icon in the notification area of the taskbar.
9088
9089Parameters in PARAMS are specified as keyword/value pairs. All the
9090parameters are optional, but if no parameters are specified, the
9091function will do nothing and return nil.
9092
9093The following parameters are supported:
9094
9095:icon ICON -- Display ICON in the system tray. If ICON is a string,
9096 it should specify a file name from which to load the
9097 icon; the specified file should be a .ico Windows icon
9098 file. If ICON is not a string, or if this parameter
9099 is not specified, the standard Emacs icon will be used.
9100
9101:tip TIP -- Use TIP as the tooltip for the notification. If TIP
9102 is a string, this is the text of a tooltip that will
9103 be shown when the mouse pointer hovers over the tray
9104 icon added by the notification. If TIP is not a
9105 string, or if this parameter is not specified, the
9106 default tooltip text is \"Emacs notification\". The
9107 tooltip text can be up to 127 characters long (63
9108 on Windows versions before W2K). Longer strings
9109 will be truncated.
9110
9111:level LEVEL -- Notification severity level, one of `info',
9112 `warning', or `error'. If given, the value
9113 determines the icon displayed to the left of the
9114 notification title, but only if the `:title'
9115 parameter (see below) is also specified and is a
9116 string.
9117
9118:timeout TIMEOUT -- TIMEOUT is the time in seconds after which the
9119 notification disappears. The value can be integer
9120 or floating-point. This is ignored on Vista and
9121 later systems, where the duration is fixed at 9 sec
9122 and can only be customized via system-wide
9123 Accessibility settings.
9124
9125:title TITLE -- The title of the notification. If TITLE is a string,
9126 it is displayed in a larger font immediately above
9127 the body text. The title text can be up to 63
9128 characters long; longer text will be truncated.
9129
9130:body BODY -- The body of the notification. If BODY is a string,
9131 it specifies the text of the notification message.
9132 Use embedded newlines to control how the text is
9133 broken into lines. The body text can be up to 255
9134 characters long, and will be truncated if it's longer.
9135
9136Note that versions of Windows before W2K support only `:icon' and `:tip'.
9137You can pass the other parameters, but they will be ignored on those
9138old systems.
9139
9140There can be at most one active notification at any given time. An
9141active notification must be removed by calling `w32-notification-close'
9142before a new one can be shown.
9143
9144usage: (w32-notification-notify &rest PARAMS) */)
9145 (ptrdiff_t nargs, Lisp_Object *args)
9146{
9147 struct frame *f = SELECTED_FRAME ();
9148 Lisp_Object arg_plist, lres;
9149 EMACS_INT retval;
9150 char *icon, *tip, *title, *msg;
9151 enum NI_Severity severity;
9152 unsigned timeout;
9153
9154 if (nargs == 0)
9155 return Qnil;
9156
9157 arg_plist = Flist (nargs, args);
9158
9159 /* Icon. */
9160 lres = Fplist_get (arg_plist, QCicon);
9161 if (STRINGP (lres))
9162 icon = SSDATA (ENCODE_FILE (Fexpand_file_name (lres, Qnil)));
9163 else
9164 icon = "";
9165
9166 /* Tip. */
9167 lres = Fplist_get (arg_plist, QCtip);
9168 if (STRINGP (lres))
9169 tip = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1));
9170 else
9171 tip = "Emacs notification";
9172
9173 /* Severity. */
9174 lres = Fplist_get (arg_plist, QClevel);
9175 if (NILP (lres))
9176 severity = Ni_None;
9177 else if (EQ (lres, Qinfo))
9178 severity = Ni_Info;
9179 else if (EQ (lres, Qwarning))
9180 severity = Ni_Warn;
9181 else if (EQ (lres, Qerror))
9182 severity = Ni_Err;
9183 else
9184 severity = Ni_Info;
9185
9186 /* Timeout. */
9187 lres = Fplist_get (arg_plist, QCtimeout);
9188 if (NUMBERP (lres))
9189 timeout = 1000 * (INTEGERP (lres) ? XINT (lres) : XFLOAT_DATA (lres));
9190 else
9191 timeout = 0;
9192
9193 /* Title. */
9194 lres = Fplist_get (arg_plist, QCtitle);
9195 if (STRINGP (lres))
9196 title = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1));
9197 else
9198 title = "";
9199
9200 /* Notification body text. */
9201 lres = Fplist_get (arg_plist, QCbody);
9202 if (STRINGP (lres))
9203 msg = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1));
9204 else
9205 msg = "";
9206
9207 /* Do it! */
9208 retval = add_tray_notification (f, icon, tip, severity, timeout, title, msg);
9209 return (retval < 0 ? Qnil : make_number (retval));
9210}
9211
9212DEFUN ("w32-notification-close",
9213 Fw32_notification_close, Sw32_notification_close,
9214 1, 1, 0,
9215 doc: /* Remove the MS-Windows tray notification specified by its ID. */)
9216 (Lisp_Object id)
9217{
9218 struct frame *f = SELECTED_FRAME ();
9219
9220 if (INTEGERP (id))
9221 delete_tray_notification (f, XINT (id));
9222
9223 return Qnil;
9224}
9225
8758 9226
8759/*********************************************************************** 9227/***********************************************************************
8760 Initialization 9228 Initialization
@@ -8828,6 +9296,14 @@ syms_of_w32fns (void)
8828 DEFSYM (Qframes, "frames"); 9296 DEFSYM (Qframes, "frames");
8829 DEFSYM (Qtip_frame, "tip-frame"); 9297 DEFSYM (Qtip_frame, "tip-frame");
8830 DEFSYM (Qunicode_sip, "unicode-sip"); 9298 DEFSYM (Qunicode_sip, "unicode-sip");
9299 DEFSYM (QCicon, ":icon");
9300 DEFSYM (QCtip, ":tip");
9301 DEFSYM (QClevel, ":level");
9302 DEFSYM (Qinfo, "info");
9303 DEFSYM (Qwarning, "warning");
9304 DEFSYM (QCtimeout, ":timeout");
9305 DEFSYM (QCtitle, ":title");
9306 DEFSYM (QCbody, ":body");
8831 9307
8832 /* Symbols used elsewhere, but only in MS-Windows-specific code. */ 9308 /* Symbols used elsewhere, but only in MS-Windows-specific code. */
8833 DEFSYM (Qgnutls_dll, "gnutls"); 9309 DEFSYM (Qgnutls_dll, "gnutls");
@@ -9161,6 +9637,8 @@ This variable has effect only on Windows Vista and later. */);
9161 defsubr (&Sw32_window_exists_p); 9637 defsubr (&Sw32_window_exists_p);
9162 defsubr (&Sw32_battery_status); 9638 defsubr (&Sw32_battery_status);
9163 defsubr (&Sw32__menu_bar_in_use); 9639 defsubr (&Sw32__menu_bar_in_use);
9640 defsubr (&Sw32_notification_notify);
9641 defsubr (&Sw32_notification_close);
9164 9642
9165#ifdef WINDOWSNT 9643#ifdef WINDOWSNT
9166 defsubr (&Sfile_system_info); 9644 defsubr (&Sfile_system_info);