emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] master 9d43941: Implement tray notifications for MS-Window


From: Eli Zaretskii
Subject: [Emacs-diffs] master 9d43941: Implement tray notifications for MS-Windows
Date: Wed, 11 Nov 2015 16:31:20 +0000

branch: master
commit 9d43941569fc3840fa9306d149461a8439a54d68
Author: Eli Zaretskii <address@hidden>
Commit: Eli Zaretskii <address@hidden>

    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.
---
 doc/lispref/os.texi |   88 +++++++++-
 src/w32fns.c        |  478 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 562 insertions(+), 4 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 7050df8..1345ed2 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -2323,10 +2323,11 @@ Emacs is restarted by the session manager.
 @cindex notifications, on desktop
 
 Emacs is able to send @dfn{notifications} on systems that support the
-freedesktop.org Desktop Notifications Specification.  In order to use
-this functionality, Emacs must have been compiled with D-Bus support,
-and the @code{notifications} library must be loaded.  @xref{Top, ,
-D-Bus,dbus,D-Bus integration in Emacs}.
+freedesktop.org Desktop Notifications Specification and on MS-Windows.
+In order to use this functionality on Posix hosts, Emacs must have
+been compiled with D-Bus support, and the @code{notifications} library
+must be loaded.  @xref{Top, , D-Bus,dbus,D-Bus integration in Emacs}.
+The following function is supported when D-Bus support is available:
 
 @defun notifications-notify &rest params
 This function sends a notification to the desktop via D-Bus,
@@ -2559,6 +2560,85 @@ If @var{spec_version} is @code{nil}, the server supports 
a
 specification prior to @samp{"1.0"}.
 @end defun
 
address@hidden tray notifications, MS-Windows
+When Emacs runs on MS-Windows as a GUI session, it supports a small
+subset of the D-Bus notifications functionality via a native
+primitive:
+
address@hidden w32-notification-notify &rest params
+This function displays an MS-Windows tray notification as specified by
address@hidden  MS-Windows tray notifications are displayed in a
+balloon from an icon in the notification area of the taskbar.
+
+Value is the integer unique ID of the notification that can be used to
+remove the notification using @code{w32-notification-close}, described
+below.  If the function fails, the return value is @code{nil}.
+
+The arguments @var{params} are specified as keyword/value pairs.  All the
+parameters are optional, but if no parameters are specified, the
+function will do nothing and return @code{nil}.
+
+The following parameters are supported:
+
address@hidden @code
address@hidden :icon @var{icon}
+Display @var{icon} in the system tray.  If @var{icon} is a string, it
+should specify a file name from which to load the icon; the specified
+file should be a @file{.ico} Windows icon file.  If @var{icon} is not
+a string, or if this parameter is not specified, the standard Emacs
+icon will be used.
+
address@hidden :tip @var{tip}
+Use @var{tip} as the tooltip for the notification.  If @var{tip} is a
+string, this is the text of a tooltip that will be shown when the
+mouse pointer hovers over the tray icon added by the notification.  If
address@hidden is not a string, or if this parameter is not specified, the
+default tooltip text is @samp{Emacs notification}.  The tooltip text can
+be up to 127 characters long (63 on Windows versions before W2K).
+Longer strings will be truncated.
+
address@hidden :level @var{level}
+Notification severity level, one of @code{info}, @code{warning}, or
address@hidden  If given, the value determines the icon displayed to the
+left of the notification title, but only if the @code{:title} parameter
+(see below) is also specified and is a string.
+
address@hidden :timeout @var{timeout}
address@hidden is the time in seconds after which the notification
+disappears.  The value can be integer or floating-point.  This is
+ignored on Vista and later systems, where the duration is fixed at 9
+sec and can only be customized via system-wide Accessibility settings.
+
address@hidden :title @var{title}
+The title of the notification.  If @var{title} is a string, it is
+displayed in a larger font immediately above the body text.  The title
+text can be up to 63 characters long; longer text will be truncated.
+
address@hidden :body @var{body}
+The body of the notification.  If @var{body} is a string, it specifies
+the text of the notification message.  Use embedded newlines to
+control how the text is broken into lines.  The body text can be up to
+255 characters long, and will be truncated if it's longer.  Unlike
+with D-Bus, the body text should be plain text, with no markup.
address@hidden table
+
+Note that versions of Windows before W2K support only @code{:icon} and
address@hidden:tip}.  The other parameters can be passed, but they will be
+ignored on those old systems.
+
+There can be at most one active notification at any given time.  An
+active notification must be removed by calling
address@hidden before a new one can be shown.
address@hidden defun
+
+To remove the notification and its icon from the taskbar, use the
+following function:
+
address@hidden w32-notification-close id
+This function removes the tray notification given by its unique
address@hidden
address@hidden defun
+
 @node File Notifications
 @section Notifications on File Changes
 @cindex file notifications
diff --git a/src/w32fns.c b/src/w32fns.c
index d92352a..eed849f 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/>.  */
 #include <commctrl.h>
 #include <commdlg.h>
 #include <shellapi.h>
+#include <shlwapi.h>
 #include <ctype.h>
 #include <winspool.h>
 #include <objbase.h>
@@ -8755,6 +8756,473 @@ Internal use only.  */)
   return menubar_in_use ? Qt : Qnil;
 }
 
+/***********************************************************************
+                         Tray notifications
+ ***********************************************************************/
+/* A private struct declaration to avoid compile-time limits.  */
+typedef struct MY_NOTIFYICONDATAW {
+  DWORD cbSize;
+  HWND hWnd;
+  UINT uID;
+  UINT uFlags;
+  UINT uCallbackMessage;
+  HICON hIcon;
+  WCHAR szTip[128];
+  DWORD dwState;
+  DWORD dwStateMask;
+  WCHAR szInfo[256];
+  _ANONYMOUS_UNION union {
+    UINT uTimeout;
+    UINT uVersion;
+  } DUMMYUNIONNAME;
+  WCHAR szInfoTitle[64];
+  DWORD dwInfoFlags;
+  GUID guidItem;
+  HICON hBalloonIcon;
+} MY_NOTIFYICONDATAW;
+
+#ifndef NOTIFYICONDATAW_V1_SIZE
+# define NOTIFYICONDATAW_V1_SIZE offsetof (MY_NOTIFYICONDATAW, szTip[64])
+#endif
+#ifndef NOTIFYICONDATAW_V2_SIZE
+# define NOTIFYICONDATAW_V2_SIZE offsetof (MY_NOTIFYICONDATAW, guidItem)
+#endif
+#ifndef NOTIFYICONDATAW_V3_SIZE
+# define NOTIFYICONDATAW_V3_SIZE offsetof (MY_NOTIFYICONDATAW, hBalloonIcon)
+#endif
+#ifndef NIF_INFO
+# define NIF_INFO     0x00000010
+#endif
+#ifndef NIIF_NONE
+# define NIIF_NONE    0x00000000
+#endif
+#ifndef NIIF_INFO
+# define NIIF_INFO    0x00000001
+#endif
+#ifndef NIIF_WARNING
+# define NIIF_WARNING 0x00000002
+#endif
+#ifndef NIIF_ERROR
+# define NIIF_ERROR   0x00000003
+#endif
+
+
+#define EMACS_TRAY_NOTIFICATION_ID  42 /* arbitrary */
+#define EMACS_NOTIFICATION_MSG      (WM_APP + 1)
+
+enum NI_Severity {
+  Ni_None,
+  Ni_Info,
+  Ni_Warn,
+  Ni_Err
+};
+
+/* Report the version of a DLL given by its name.  The return value is
+   constructed using MAKEDLLVERULL.  */
+static ULONGLONG
+get_dll_version (const char *dll_name)
+{
+  ULONGLONG version = 0;
+  HINSTANCE hdll = LoadLibrary (dll_name);
+
+  if (hdll)
+    {
+      DLLGETVERSIONPROC pDllGetVersion
+       = (DLLGETVERSIONPROC) GetProcAddress (hdll, "DllGetVersion");
+
+      if (pDllGetVersion)
+       {
+         DLLVERSIONINFO dvi;
+         HRESULT result;
+
+         memset (&dvi, 0, sizeof(dvi));
+         dvi.cbSize = sizeof(dvi);
+         result = pDllGetVersion (&dvi);
+         if (SUCCEEDED (result))
+           version = MAKEDLLVERULL (dvi.dwMajorVersion, dvi.dwMinorVersion,
+                                    0, 0);
+       }
+      FreeLibrary (hdll);
+    }
+
+  return version;
+}
+
+/* Return the number of bytes in UTF-8 encoded string STR that
+   corresponds to at most LIM characters.  If STR ends before LIM
+   characters, return the number of bytes in STR including the
+   terminating null byte.  */
+static int
+utf8_mbslen_lim (const char *str, int lim)
+{
+  const char *p = str;
+  int mblen = 0, nchars = 0;
+
+  while (*p && nchars < lim)
+    {
+      int nbytes = CHAR_BYTES (*p);
+
+      mblen += nbytes;
+      nchars++;
+      p += nbytes;
+    }
+
+  if (!*p && nchars < lim)
+    mblen++;
+
+  return mblen;
+}
+
+/* Low-level subroutine to show tray notifications.  All strings are
+   supposed to be unibyte UTF-8 encoded by the caller.  */
+static EMACS_INT
+add_tray_notification (struct frame *f, const char *icon, const char *tip,
+                      enum NI_Severity severity, unsigned timeout,
+                      const char *title, const char *msg)
+{
+  EMACS_INT retval = EMACS_TRAY_NOTIFICATION_ID;
+
+  if (FRAME_W32_P (f))
+    {
+      MY_NOTIFYICONDATAW nidw;
+      ULONGLONG shell_dll_version = get_dll_version ("Shell32.dll");
+      wchar_t tipw[128], msgw[256], titlew[64];
+      int tiplen;
+
+      memset (&nidw, 0, sizeof(nidw));
+
+      /* MSDN says the full struct is supported since Vista, whose
+        Shell32.dll version is said to be 6.0.6.  But DllGetVersion
+        cannot report the 3rd field value, it reports "build number"
+        instead, which is something else.  So we use the Windows 7's
+        version 6.1 as cutoff, and Vista loses.  (Actually, the loss
+        is not a real one, since we don't expose the hBalloonIcon
+        member of the struct to Lisp.)  */
+      if (shell_dll_version >= MAKEDLLVERULL (6, 1, 0, 0)) /* >= Windows 7 */
+       nidw.cbSize = sizeof (nidw);
+      else if (shell_dll_version >= MAKEDLLVERULL (6, 0, 0, 0)) /* XP */
+       nidw.cbSize = NOTIFYICONDATAW_V3_SIZE;
+      else if (shell_dll_version >= MAKEDLLVERULL (5, 0, 0, 0)) /* W2K */
+       nidw.cbSize = NOTIFYICONDATAW_V2_SIZE;
+      else
+       nidw.cbSize = NOTIFYICONDATAW_V1_SIZE;                  /* < W2K */
+      nidw.hWnd = FRAME_W32_WINDOW (f);
+      nidw.uID = EMACS_TRAY_NOTIFICATION_ID;
+      nidw.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_INFO;
+      nidw.uCallbackMessage = EMACS_NOTIFICATION_MSG;
+      if (!*icon)
+       nidw.hIcon = LoadIcon (hinst, EMACS_CLASS);
+      else
+       {
+         if (w32_unicode_filenames)
+           {
+             wchar_t icon_w[MAX_PATH];
+
+             if (filename_to_utf16 (icon, icon_w) != 0)
+               {
+                 errno = ENOENT;
+                 return -1;
+               }
+             nidw.hIcon = LoadImageW (NULL, icon_w, IMAGE_ICON, 0, 0,
+                                      LR_DEFAULTSIZE | LR_LOADFROMFILE);
+           }
+         else
+           {
+             char icon_a[MAX_PATH];
+
+             if (filename_to_ansi (icon, icon_a) != 0)
+               {
+                 errno = ENOENT;
+                 return -1;
+               }
+             nidw.hIcon = LoadImageA (NULL, icon_a, IMAGE_ICON, 0, 0,
+                                      LR_DEFAULTSIZE | LR_LOADFROMFILE);
+           }
+       }
+      if (!nidw.hIcon)
+       {
+         switch (GetLastError ())
+           {
+           case ERROR_FILE_NOT_FOUND:
+             errno = ENOENT;
+             break;
+           default:
+             errno = ENOMEM;
+             break;
+           }
+         return -1;
+       }
+
+      /* Windows 9X and NT4 support only 64 characters in the Tip,
+        later versions support up to 128.  */
+      if (nidw.cbSize == NOTIFYICONDATAW_V1_SIZE)
+       {
+         tiplen = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
+                                        tip, utf8_mbslen_lim (tip, 63),
+                                        tipw, 64);
+         if (tiplen >= 63)
+           tipw[63] = 0;
+       }
+      else
+       {
+         tiplen = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
+                                        tip, utf8_mbslen_lim (tip, 127),
+                                        tipw, 128);
+         if (tiplen >= 127)
+           tipw[127] = 0;
+       }
+      if (tiplen == 0)
+       {
+         errno = EINVAL;
+         retval = -1;
+         goto done;
+       }
+      wcscpy (nidw.szTip, tipw);
+
+      /* The rest of the structure is only supported since Windows 2000.  */
+      if (nidw.cbSize > NOTIFYICONDATAW_V1_SIZE)
+       {
+         int slen;
+
+         slen = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
+                                            msg, utf8_mbslen_lim (msg, 255),
+                                            msgw, 256);
+         if (slen >= 255)
+           msgw[255] = 0;
+         else if (slen == 0)
+           {
+             errno = EINVAL;
+             retval = -1;
+             goto done;
+           }
+         wcscpy (nidw.szInfo, msgw);
+         nidw.uTimeout = timeout;
+         slen = pMultiByteToWideChar (CP_UTF8, MB_ERR_INVALID_CHARS,
+                                      title, utf8_mbslen_lim (title, 63),
+                                      titlew, 64);
+         if (slen >= 63)
+           titlew[63] = 0;
+         else if (slen == 0)
+           {
+             errno = EINVAL;
+             retval = -1;
+             goto done;
+           }
+         wcscpy (nidw.szInfoTitle, titlew);
+
+         switch (severity)
+           {
+           case Ni_None:
+             nidw.dwInfoFlags = NIIF_NONE;
+             break;
+           case Ni_Info:
+           default:
+             nidw.dwInfoFlags = NIIF_INFO;
+             break;
+           case Ni_Warn:
+             nidw.dwInfoFlags = NIIF_WARNING;
+             break;
+           case Ni_Err:
+             nidw.dwInfoFlags = NIIF_ERROR;
+             break;
+           }
+       }
+
+      if (!Shell_NotifyIconW (NIM_ADD, (PNOTIFYICONDATAW)&nidw))
+       {
+         /* GetLastError returns meaningless results when
+            Shell_NotifyIcon fails.  */
+         DebPrint (("Shell_NotifyIcon ADD failed (err=%d)\n",
+                    GetLastError ()));
+         errno = EINVAL;
+         retval = -1;
+       }
+    done:
+      if (*icon && !DestroyIcon (nidw.hIcon))
+       DebPrint (("DestroyIcon failed (err=%d)\n", GetLastError ()));
+    }
+  return retval;
+}
+
+/* Low-level subroutine to remove a tray notification.  Note: we only
+   pass the minimum data about the notification: its ID and the handle
+   of the window to which it sends messages.  MSDN doesn't say this is
+   enough, but it works in practice.  This allows us to avoid keeping
+   the notification data around after we show the notification.  */
+static void
+delete_tray_notification (struct frame *f, int id)
+{
+  if (FRAME_W32_P (f))
+    {
+      MY_NOTIFYICONDATAW nidw;
+
+      memset (&nidw, 0, sizeof(nidw));
+      nidw.hWnd = FRAME_W32_WINDOW (f);
+      nidw.uID = id;
+
+      if (!Shell_NotifyIconW (NIM_DELETE, (PNOTIFYICONDATAW)&nidw))
+       {
+         /* GetLastError returns meaningless results when
+            Shell_NotifyIcon fails.  */
+         DebPrint (("Shell_NotifyIcon DELETE failed\n"));
+         errno = EINVAL;
+         return;
+       }
+    }
+  return;
+}
+
+DEFUN ("w32-notification-notify",
+       Fw32_notification_notify, Sw32_notification_notify,
+       0, MANY, 0,
+       doc: /* Display an MS-Windows tray notification as specified by PARAMS.
+
+Value is the integer unique ID of the notification that can be used
+to remove the notification using `w32-notification-close', which see.
+If the function fails, the return value is nil.
+
+Tray notifications, a.k.a. \"taskbar messages\", are messages that
+inform the user about events unrelated to the current user activity,
+such as a significant system event, by briefly displaying informative
+text in a balloon from an icon in the notification area of the taskbar.
+
+Parameters in PARAMS are specified as keyword/value pairs.  All the
+parameters are optional, but if no parameters are specified, the
+function will do nothing and return nil.
+
+The following parameters are supported:
+
+:icon ICON       -- Display ICON in the system tray.  If ICON is a string,
+                    it should specify a file name from which to load the
+                    icon; the specified file should be a .ico Windows icon
+                    file.  If ICON is not a string, or if this parameter
+                    is not specified, the standard Emacs icon will be used.
+
+:tip TIP         -- Use TIP as the tooltip for the notification.  If TIP
+                    is a string, this is the text of a tooltip that will
+                    be shown when the mouse pointer hovers over the tray
+                    icon added by the notification.  If TIP is not a
+                    string, or if this parameter is not specified, the
+                    default tooltip text is \"Emacs notification\".  The
+                    tooltip text can be up to 127 characters long (63
+                    on Windows versions before W2K).  Longer strings
+                    will be truncated.
+
+:level LEVEL     -- Notification severity level, one of `info',
+                    `warning', or `error'.  If given, the value
+                    determines the icon displayed to the left of the
+                    notification title, but only if the `:title'
+                    parameter (see below) is also specified and is a
+                    string.
+
+:timeout TIMEOUT -- TIMEOUT is the time in seconds after which the
+                    notification disappears.  The value can be integer
+                    or floating-point.  This is ignored on Vista and
+                    later systems, where the duration is fixed at 9 sec
+                    and can only be customized via system-wide
+                    Accessibility settings.
+
+:title TITLE     -- The title of the notification.  If TITLE is a string,
+                    it is displayed in a larger font immediately above
+                    the body text.  The title text can be up to 63
+                    characters long; longer text will be truncated.
+
+:body BODY       -- The body of the notification.  If BODY is a string,
+                    it specifies the text of the notification message.
+                    Use embedded newlines to control how the text is
+                    broken into lines.  The body text can be up to 255
+                    characters long, and will be truncated if it's longer.
+
+Note that versions of Windows before W2K support only `:icon' and `:tip'.
+You can pass the other parameters, but they will be ignored on those
+old systems.
+
+There can be at most one active notification at any given time.  An
+active notification must be removed by calling `w32-notification-close'
+before a new one can be shown.
+
+usage: (w32-notification-notify &rest PARAMS)  */)
+  (ptrdiff_t nargs, Lisp_Object *args)
+{
+  struct frame *f = SELECTED_FRAME ();
+  Lisp_Object arg_plist, lres;
+  EMACS_INT retval;
+  char *icon, *tip, *title, *msg;
+  enum NI_Severity severity;
+  unsigned timeout;
+
+  if (nargs == 0)
+    return Qnil;
+
+  arg_plist = Flist (nargs, args);
+
+  /* Icon.  */
+  lres = Fplist_get (arg_plist, QCicon);
+  if (STRINGP (lres))
+    icon = SSDATA (ENCODE_FILE (Fexpand_file_name (lres, Qnil)));
+  else
+    icon = "";
+
+  /* Tip.  */
+  lres = Fplist_get (arg_plist, QCtip);
+  if (STRINGP (lres))
+    tip = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1));
+  else
+    tip = "Emacs notification";
+
+  /* Severity.  */
+  lres = Fplist_get (arg_plist, QClevel);
+  if (NILP (lres))
+    severity = Ni_None;
+  else if (EQ (lres, Qinfo))
+    severity = Ni_Info;
+  else if (EQ (lres, Qwarning))
+    severity = Ni_Warn;
+  else if (EQ (lres, Qerror))
+    severity = Ni_Err;
+  else
+    severity = Ni_Info;
+
+  /* Timeout.  */
+  lres = Fplist_get (arg_plist, QCtimeout);
+  if (NUMBERP (lres))
+    timeout = 1000 * (INTEGERP (lres) ? XINT (lres) : XFLOAT_DATA (lres));
+  else
+    timeout = 0;
+
+  /* Title.  */
+  lres = Fplist_get (arg_plist, QCtitle);
+  if (STRINGP (lres))
+    title = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1));
+  else
+    title = "";
+
+  /* Notification body text.  */
+  lres = Fplist_get (arg_plist, QCbody);
+  if (STRINGP (lres))
+    msg = SSDATA (code_convert_string_norecord (lres, Qutf_8, 1));
+  else
+    msg = "";
+
+  /* Do it!  */
+  retval = add_tray_notification (f, icon, tip, severity, timeout, title, msg);
+  return (retval < 0 ? Qnil : make_number (retval));
+}
+
+DEFUN ("w32-notification-close",
+       Fw32_notification_close, Sw32_notification_close,
+       1, 1, 0,
+       doc: /* Remove the MS-Windows tray notification specified by its ID.  
*/)
+  (Lisp_Object id)
+{
+  struct frame *f = SELECTED_FRAME ();
+
+  if (INTEGERP (id))
+    delete_tray_notification (f, XINT (id));
+
+  return Qnil;
+}
+
 
 /***********************************************************************
                            Initialization
@@ -8828,6 +9296,14 @@ syms_of_w32fns (void)
   DEFSYM (Qframes, "frames");
   DEFSYM (Qtip_frame, "tip-frame");
   DEFSYM (Qunicode_sip, "unicode-sip");
+  DEFSYM (QCicon, ":icon");
+  DEFSYM (QCtip, ":tip");
+  DEFSYM (QClevel, ":level");
+  DEFSYM (Qinfo, "info");
+  DEFSYM (Qwarning, "warning");
+  DEFSYM (QCtimeout, ":timeout");
+  DEFSYM (QCtitle, ":title");
+  DEFSYM (QCbody, ":body");
 
   /* Symbols used elsewhere, but only in MS-Windows-specific code.  */
   DEFSYM (Qgnutls_dll, "gnutls");
@@ -9161,6 +9637,8 @@ This variable has effect only on Windows Vista and later. 
 */);
   defsubr (&Sw32_window_exists_p);
   defsubr (&Sw32_battery_status);
   defsubr (&Sw32__menu_bar_in_use);
+  defsubr (&Sw32_notification_notify);
+  defsubr (&Sw32_notification_close);
 
 #ifdef WINDOWSNT
   defsubr (&Sfile_system_info);



reply via email to

[Prev in Thread] Current Thread [Next in Thread]