]> git.eshelyaron.com Git - emacs.git/commitdiff
Support system dark mode on Windows 10 version 1809 and higher
authorVince Salvino <salvino@coderedcorp.com>
Wed, 27 Oct 2021 21:32:09 +0000 (17:32 -0400)
committerEli Zaretskii <eliz@gnu.org>
Sat, 30 Oct 2021 09:55:35 +0000 (12:55 +0300)
* src/w32fns.c (DARK_MODE_APP_NAME)
(DWMWA_USE_IMMERSIVE_DARK_MODE_OLD)
(DWMWA_USE_IMMERSIVE_DARK_MODE): Define.
(w32_applytheme): New function.
(w32_createvscrollbar, w32_createhscrollbar, w32_createwindow):
Call 'w32_applytheme'.
(globals_of_w32fns): Load 'DwmSetWindowAttribute' and
'SetWindowTheme' from their DLLs, and initialize 'w32_darkmode'.
* src/w32.c (w32_get_resource): Accept an additional argument
instead of hard-coding REG_ROOT; callers changed.  (Bug#51404)

* etc/NEWS:
* doc/emacs/msdos.texi (Windows Misc): Document the new feature.

doc/emacs/msdos.texi
etc/NEWS
src/w32.c
src/w32.h
src/w32fns.c

index 0f8f429b3f87672c04b2acc46204406d3b3ccd4b..3c6c61613e27c76277c8b43d94bf652e34ec7126 100644 (file)
@@ -1181,6 +1181,13 @@ The default is @code{t}, which fits well with the Windows default
 click-to-focus policy.
 @end ifnottex
 
+  On Windows 10 (version 1809 and higher) and Windows 11, Emacs title
+bars and scroll bars will follow the system Light or Dark mode,
+similar to other programs such as Explorer and Command Prompt. To
+change the color mode: Windows Settings > Personalization > Colors >
+Choose your color (or Choose your default app mode); then restart
+Emacs.
+
 @ifnottex
 @include msdos-xtra.texi
 @end ifnottex
index cc452211b68bb2dfb6fa958629a78ddffbd5440f..51ff53da184b462c7039b316b73ee52465b859ab 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -73,6 +73,14 @@ Image specifiers can now use ':type webp'.
 *** 'display-buffer' now can set up the body size of the chosen window.
 For example, an alist entry as '(window-width . (body-columns . 40))'
 will make the body of the chosen window 40 columns wide.
+
+** MS-Windows
+
++++
+*** Supports dark mode on Windows 10 (version 1809 and higher) and Windows 11.
+Graphical frames now use the appropriate light or dark title bar and
+scroll bars, based on the user's Windows color settings.
+
 \f
 * Editing Changes in Emacs 29.1
 
index 9fe698d28d7207bc00225c609009c49387ab781f..369e7ee4e1f041fe645d3e2e0d47f7a5507d5168 100644 (file)
--- a/src/w32.c
+++ b/src/w32.c
@@ -2820,8 +2820,15 @@ sys_putenv (char *str)
 
 #define REG_ROOT "SOFTWARE\\GNU\\Emacs"
 
+/* Query a value from the Windows Registry (under HKCU and HKLM),
+   where `key` is the registry key, `name` is the name, and `lpdwtype`
+   is a pointer to the return value's type. `lpwdtype` can be NULL if
+   you do not care about the type.
+
+   Returns: pointer to the value, or null pointer if the key/name does
+   not exist. */
 LPBYTE
-w32_get_resource (const char *key, LPDWORD lpdwtype)
+w32_get_resource (const char *key, const char *name, LPDWORD lpdwtype)
 {
   LPBYTE lpvalue;
   HKEY hrootkey = NULL;
@@ -2830,13 +2837,13 @@ w32_get_resource (const char *key, LPDWORD lpdwtype)
   /* Check both the current user and the local machine to see if
      we have any resources.  */
 
-  if (RegOpenKeyEx (HKEY_CURRENT_USER, REG_ROOT, 0, KEY_READ, &hrootkey) == ERROR_SUCCESS)
+  if (RegOpenKeyEx (HKEY_CURRENT_USER, key, 0, KEY_READ, &hrootkey) == ERROR_SUCCESS)
     {
       lpvalue = NULL;
 
-      if (RegQueryValueEx (hrootkey, key, NULL, NULL, NULL, &cbData) == ERROR_SUCCESS
+      if (RegQueryValueEx (hrootkey, name, NULL, NULL, NULL, &cbData) == ERROR_SUCCESS
          && (lpvalue = xmalloc (cbData)) != NULL
-         && RegQueryValueEx (hrootkey, key, NULL, lpdwtype, lpvalue, &cbData) == ERROR_SUCCESS)
+         && RegQueryValueEx (hrootkey, name, NULL, lpdwtype, lpvalue, &cbData) == ERROR_SUCCESS)
        {
           RegCloseKey (hrootkey);
          return (lpvalue);
@@ -2847,13 +2854,13 @@ w32_get_resource (const char *key, LPDWORD lpdwtype)
       RegCloseKey (hrootkey);
     }
 
-  if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, REG_ROOT, 0, KEY_READ, &hrootkey) == ERROR_SUCCESS)
+  if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hrootkey) == ERROR_SUCCESS)
     {
       lpvalue = NULL;
 
-      if (RegQueryValueEx (hrootkey, key, NULL, NULL, NULL, &cbData) == ERROR_SUCCESS
+      if (RegQueryValueEx (hrootkey, name, NULL, NULL, NULL, &cbData) == ERROR_SUCCESS
          && (lpvalue = xmalloc (cbData)) != NULL
-         && RegQueryValueEx (hrootkey, key, NULL, lpdwtype, lpvalue, &cbData) == ERROR_SUCCESS)
+         && RegQueryValueEx (hrootkey, name, NULL, lpdwtype, lpvalue, &cbData) == ERROR_SUCCESS)
        {
           RegCloseKey (hrootkey);
          return (lpvalue);
@@ -3077,7 +3084,7 @@ init_environment (char ** argv)
            int dont_free = 0;
            char bufc[SET_ENV_BUF_SIZE];
 
-           if ((lpval = w32_get_resource (env_vars[i].name, &dwType)) == NULL
+           if ((lpval = w32_get_resource (REG_ROOT, env_vars[i].name, &dwType)) == NULL
                /* Also ignore empty environment variables.  */
                || *lpval == 0)
              {
index ffa145b1484aaaff51d5bcea70f945cf4987f43f..ec0f37123e8311d0eead3733384d790c5d26b0df 100644 (file)
--- a/src/w32.h
+++ b/src/w32.h
@@ -161,8 +161,9 @@ extern void prepare_standard_handles (int in, int out,
 extern void reset_standard_handles (int in, int out,
                                    int err, HANDLE handles[4]);
 
-/* Return the string resource associated with KEY of type TYPE.  */
-extern LPBYTE w32_get_resource (const char * key, LPDWORD type);
+/* Query Windows Registry and return the resource associated
+   associated with KEY and NAME of type TYPE.  */
+extern LPBYTE w32_get_resource (const char * key, const char * name, LPDWORD type);
 
 extern void release_listen_threads (void);
 extern void init_ntproc (int);
index 14d1154a2bc507b085395e84d517658027ebf0f3..bcf0f50c6a6c0e5e15d6c21d5f54263c1d05091d 100644 (file)
@@ -73,6 +73,18 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include <imm.h>
 #include <windowsx.h>
 
+/*
+  Internal/undocumented constants for Windows Dark mode.
+  See: https://github.com/microsoft/WindowsAppSDK/issues/41
+*/
+#define DARK_MODE_APP_NAME L"DarkMode_Explorer"
+/* For Windows 10 version 1809, 1903, 1909. */
+#define DWMWA_USE_IMMERSIVE_DARK_MODE_OLD 19
+/* For Windows 10 version 2004 and higher, and Windows 11. */
+#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
+#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
+#endif
+
 #ifndef FOF_NO_CONNECTED_ELEMENTS
 #define FOF_NO_CONNECTED_ELEMENTS 0x2000
 #endif
@@ -185,6 +197,11 @@ typedef BOOL (WINAPI *IsDebuggerPresent_Proc) (void);
 typedef HRESULT (WINAPI *SetThreadDescription_Proc)
   (HANDLE hThread, PCWSTR lpThreadDescription);
 
+typedef HRESULT (WINAPI * SetWindowTheme_Proc)
+  (IN HWND hwnd, IN LPCWSTR pszSubAppName, IN LPCWSTR pszSubIdList);
+typedef HRESULT (WINAPI * DwmSetWindowAttribute_Proc)
+  (HWND hwnd, DWORD dwAttribute, IN LPCVOID pvAttribute, DWORD cbAttribute);
+
 TrackMouseEvent_Proc track_mouse_event_fn = NULL;
 ImmGetCompositionString_Proc get_composition_string_fn = NULL;
 ImmGetContext_Proc get_ime_context_fn = NULL;
@@ -199,6 +216,8 @@ EnumDisplayMonitors_Proc enum_display_monitors_fn = NULL;
 GetTitleBarInfo_Proc get_title_bar_info_fn = NULL;
 IsDebuggerPresent_Proc is_debugger_present = NULL;
 SetThreadDescription_Proc set_thread_description = NULL;
+SetWindowTheme_Proc SetWindowTheme_fn = NULL;
+DwmSetWindowAttribute_Proc DwmSetWindowAttribute_fn = NULL;
 
 extern AppendMenuW_Proc unicode_append_menu;
 
@@ -252,6 +271,9 @@ int w32_major_version;
 int w32_minor_version;
 int w32_build_number;
 
+/* If the OS is set to use dark mode. */
+BOOL w32_darkmode = FALSE;
+
 /* Distinguish between Windows NT and Windows 95.  */
 int os_subtype;
 
@@ -2279,10 +2301,36 @@ w32_init_class (HINSTANCE hinst)
     }
 }
 
+/* Applies the Windows system theme (light or dark) to a window handle. */
+static void
+w32_applytheme (HWND hwnd)
+{
+  if (w32_darkmode)
+    {
+      /* Set window theme to that of a built-in Windows app (Explorer)
+        because it has dark scroll bars and other UI elements. */
+      if (SetWindowTheme_fn)
+       {
+         SetWindowTheme_fn (hwnd, DARK_MODE_APP_NAME, NULL);
+       }
+      /* Set the titlebar to system dark mode. */
+      if (DwmSetWindowAttribute_fn)
+       {
+         /* Windows 10 version 2004 and up, Windows 11. */
+         DWORD attr = DWMWA_USE_IMMERSIVE_DARK_MODE;
+         /* Windows 10 older than 2004. */
+         if (w32_build_number < 19041)
+           attr = DWMWA_USE_IMMERSIVE_DARK_MODE_OLD;
+         DwmSetWindowAttribute_fn
+           (hwnd, attr, &w32_darkmode, sizeof(w32_darkmode));
+       }
+    }
+}
+
 static HWND
 w32_createvscrollbar (struct frame *f, struct scroll_bar * bar)
 {
-  return CreateWindow ("SCROLLBAR", "",
+  HWND hwnd = CreateWindow ("SCROLLBAR", "",
                       /* Clip siblings so we don't draw over child
                          frames.  Apparently this is not always
                          sufficient so we also try to make bar windows
@@ -2291,12 +2339,15 @@ w32_createvscrollbar (struct frame *f, struct scroll_bar * bar)
                       /* Position and size of scroll bar.  */
                       bar->left, bar->top, bar->width, bar->height,
                       FRAME_W32_WINDOW (f), NULL, hinst, NULL);
+  if (hwnd)
+    w32_applytheme (hwnd);
+  return hwnd;
 }
 
 static HWND
 w32_createhscrollbar (struct frame *f, struct scroll_bar * bar)
 {
-  return CreateWindow ("SCROLLBAR", "",
+  HWND hwnd = CreateWindow ("SCROLLBAR", "",
                       /* Clip siblings so we don't draw over child
                          frames.  Apparently this is not always
                          sufficient so we also try to make bar windows
@@ -2305,6 +2356,9 @@ w32_createhscrollbar (struct frame *f, struct scroll_bar * bar)
                       /* Position and size of scroll bar.  */
                       bar->left, bar->top, bar->width, bar->height,
                       FRAME_W32_WINDOW (f), NULL, hinst, NULL);
+  if (hwnd)
+    w32_applytheme (hwnd);
+  return hwnd;
 }
 
 static void
@@ -2390,6 +2444,9 @@ w32_createwindow (struct frame *f, int *coords)
       /* Enable drag-n-drop.  */
       DragAcceptFiles (hwnd, TRUE);
 
+      /* Enable system light/dark theme. */
+      w32_applytheme (hwnd);
+
       /* Do this to discard the default setting specified by our parent. */
       ShowWindow (hwnd, SW_HIDE);
 
@@ -11028,6 +11085,36 @@ globals_of_w32fns (void)
   set_thread_description = (SetThreadDescription_Proc)
     get_proc_addr (hm_kernel32, "SetThreadDescription");
 
+  /* Support OS dark mode on Windows 10 version 1809 and higher.
+     See `w32_applytheme` which uses appropriate APIs per version of Windows.
+     For future wretches who may need to understand Windows build numbers:
+     https://docs.microsoft.com/en-us/windows/release-health/release-information
+  */
+  if (w32_major_version >= 10 && w32_build_number >= 17763
+      && os_subtype == OS_SUBTYPE_NT)
+    {
+      /* Load dwmapi and uxtheme, which will be needed to set window themes. */
+      HMODULE dwmapi_lib = LoadLibrary("dwmapi.dll");
+      DwmSetWindowAttribute_fn = (DwmSetWindowAttribute_Proc)
+       get_proc_addr (dwmapi_lib, "DwmSetWindowAttribute");
+      HMODULE uxtheme_lib = LoadLibrary("uxtheme.dll");
+      SetWindowTheme_fn = (SetWindowTheme_Proc)
+       get_proc_addr (uxtheme_lib, "SetWindowTheme");
+
+      /* Check Windows Registry for system theme. DWORD set to 0 or 1.
+        TODO: "Nice to have" would be to create a lisp setting (which
+        defaults to this Windows Registry value), then read that lisp
+        value here instead. This would allow the user to forcibly
+        override the system theme (which is also user-configurable in
+        Windows settings; see MS-Windows section in Emacs manual). */
+      LPBYTE val = w32_get_resource
+       ("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
+        "AppsUseLightTheme",
+        NULL);
+      if (val && (DWORD)*val == 0)
+       w32_darkmode = TRUE;
+    }
+
   except_code = 0;
   except_addr = 0;
 #ifndef CYGWIN