diff options
| author | Stéphane Marks | 2025-11-20 12:54:40 -0500 |
|---|---|---|
| committer | Michael Albinus | 2025-12-21 12:55:10 +0100 |
| commit | f5f2306fc1d4370730fdcdd91c8acdf0d7930487 (patch) | |
| tree | 01f1ed23cf987c424dc3eb54948a19deef7ee81b /src | |
| parent | 28a2a7d811a9d99de7103a3be4e1dd3e3a59c813 (diff) | |
| download | emacs-f5f2306fc1d4370730fdcdd91c8acdf0d7930487.tar.gz emacs-f5f2306fc1d4370730fdcdd91c8acdf0d7930487.zip | |
System GUI taskbar and progress reporter hooks (bug#79859)
Implement system GUI taskbar/dock/launcher icon badge, icon
progress indicator, icon attention alert features for D-Bus
platforms (tested on KDE and GNOME), NS (macOS/GNUstep),
MS-Windows.
Add 'progress-reporter-update-functions' abnormal hook to facilitate
taskbar progress display, and other custom progress reporters.
The default function list is 'progress-reporter-echo-area' which
is backward compatible.
* lisp/subr.el (progress-reporter-update-functions):
New defvar.
(progress-reporter-echo-area): New defun.
(progress-reporter-do-update): Run
progress-reporter-update-functions for both numerical and
pulsing reporters.
(progress-reporter-done): Run progress-reporter-done-functions.
* lisp/system-taskbar.el: New file.
* src/nsfns.m (Fns_badge, Fns_progress_indicator)
(Fns_request_user_attention): New function.
(syms_of_nsfns): Add defsubr Sns_badge,
Sns_request_user_attention, Sns_progress_indicator. Add DEFSYM
Qinformational, Qcritical.
* src/w32fns.c (rgb_list_to_colorref, Fw32_badge)
(Fw32_request_user_attention, Fw32_progress_indicator): New
function.
(syms_of_w32fns): Add defsubr Sw32_badge,
Sw32_progress_indicator, Sw32_request_user_attention. Add DEFSYM
Qinformational, Qcritical.
* doc/emacs/frames.texi: User documentation.
* doc/lispref/os.texi: Programmer documentation.
* etc/NEWS: Announce system-taskbar-mode. Announce progress
reporter callback enhancements.
Diffstat (limited to 'src')
| -rw-r--r-- | src/nsfns.m | 128 | ||||
| -rw-r--r-- | src/w32fns.c | 327 |
2 files changed, 455 insertions, 0 deletions
diff --git a/src/nsfns.m b/src/nsfns.m index 3528c4acd50..2b94b32e59c 100644 --- a/src/nsfns.m +++ b/src/nsfns.m | |||
| @@ -3674,6 +3674,129 @@ DEFUN ("ns-show-character-palette", | |||
| 3674 | return Qnil; | 3674 | return Qnil; |
| 3675 | } | 3675 | } |
| 3676 | 3676 | ||
| 3677 | DEFUN ("ns-badge", Fns_badge, Sns_badge, 1, 1, 0, | ||
| 3678 | doc: /* Set the app icon badge to BADGE. | ||
| 3679 | BADGE should be a string short enough to display nicely in the short | ||
| 3680 | space intended for badges. | ||
| 3681 | If BADGE is nil, clear the app badge. */) | ||
| 3682 | (Lisp_Object badge) | ||
| 3683 | { | ||
| 3684 | block_input (); | ||
| 3685 | if (NILP (badge)) | ||
| 3686 | [[NSApp dockTile] setBadgeLabel: nil]; | ||
| 3687 | else | ||
| 3688 | { | ||
| 3689 | CHECK_STRING (badge); | ||
| 3690 | [[NSApp dockTile] setBadgeLabel: | ||
| 3691 | [NSString stringWithUTF8String: SSDATA (badge)]]; | ||
| 3692 | } | ||
| 3693 | unblock_input (); | ||
| 3694 | return Qnil; | ||
| 3695 | } | ||
| 3696 | |||
| 3697 | /* Use -1 to indicate no active request. */ | ||
| 3698 | static NSInteger ns_request_user_attention_id = -1; | ||
| 3699 | |||
| 3700 | DEFUN ("ns-request-user-attention", | ||
| 3701 | Fns_request_user_attention, | ||
| 3702 | Sns_request_user_attention, | ||
| 3703 | 1, 1, 0, | ||
| 3704 | doc: /* Bounce the app dock icon to request user attention. | ||
| 3705 | If URGENCY nil, cancel the outstanding request, if any. | ||
| 3706 | If URGENCY is the symbol `informational', bouncing lasts a few seconds. | ||
| 3707 | If URGENCY is the symbol `critical', bouncing lasts until Emacs is | ||
| 3708 | focused. */) | ||
| 3709 | (Lisp_Object urgency) | ||
| 3710 | { | ||
| 3711 | block_input (); | ||
| 3712 | if (ns_request_user_attention_id != -1) | ||
| 3713 | { | ||
| 3714 | [NSApp cancelUserAttentionRequest: ns_request_user_attention_id]; | ||
| 3715 | ns_request_user_attention_id = -1; | ||
| 3716 | } | ||
| 3717 | if (!NILP (urgency) && SYMBOLP (urgency)) | ||
| 3718 | { | ||
| 3719 | if (EQ (urgency, Qinformational)) | ||
| 3720 | ns_request_user_attention_id = [NSApp requestUserAttention: | ||
| 3721 | NSInformationalRequest]; | ||
| 3722 | else if (EQ (urgency, Qcritical)) | ||
| 3723 | ns_request_user_attention_id = [NSApp requestUserAttention: | ||
| 3724 | NSCriticalRequest]; | ||
| 3725 | } | ||
| 3726 | unblock_input (); | ||
| 3727 | return Qnil; | ||
| 3728 | } | ||
| 3729 | |||
| 3730 | DEFUN ("ns-progress-indicator", | ||
| 3731 | Fns_progress_indicator, | ||
| 3732 | Sns_progress_indicator, | ||
| 3733 | 1, 1, 0, | ||
| 3734 | doc: /* Bounce the app dock icon to request user attention. | ||
| 3735 | PROGRESS is a float between 0.0 and 1.0. | ||
| 3736 | If PROGRESS is nil, remove the progress indicator. */) | ||
| 3737 | (Lisp_Object progress) | ||
| 3738 | { | ||
| 3739 | block_input (); | ||
| 3740 | NSDockTile *dock_tile = [NSApp dockTile]; | ||
| 3741 | /* Use NSLevelIndicator with reliable redraws, not NSProgressIndicator. */ | ||
| 3742 | NSLevelIndicator *level_indicator; | ||
| 3743 | /* Reuse the indicator subview or create one. */ | ||
| 3744 | if (dock_tile.contentView | ||
| 3745 | && [[dock_tile.contentView subviews] count] > 0 | ||
| 3746 | && [[[dock_tile.contentView subviews] lastObject] | ||
| 3747 | isKindOfClass:[NSLevelIndicator class]]) | ||
| 3748 | level_indicator = | ||
| 3749 | (NSLevelIndicator *)[[[dock_tile contentView] subviews] lastObject]; | ||
| 3750 | else | ||
| 3751 | { | ||
| 3752 | if (!dock_tile.contentView) | ||
| 3753 | { | ||
| 3754 | NSImageView* image_view = [[NSImageView alloc] init]; | ||
| 3755 | [image_view setImage: [NSApp applicationIconImage]]; | ||
| 3756 | [dock_tile setContentView: image_view]; | ||
| 3757 | } | ||
| 3758 | /* Set width to the width of the application icon, and height to | ||
| 3759 | % of the icon height to respect scaled icons. */ | ||
| 3760 | float width = [[NSApp applicationIconImage] size].width; | ||
| 3761 | float height = 0.10 * [[NSApp applicationIconImage] size].height; | ||
| 3762 | level_indicator = | ||
| 3763 | [[NSLevelIndicator alloc] initWithFrame: | ||
| 3764 | NSMakeRect (0.0, 0.0, | ||
| 3765 | width, height)]; | ||
| 3766 | [level_indicator setWantsLayer: YES]; /* Performance. */ | ||
| 3767 | [level_indicator setEnabled: NO]; /* Ignore mouse input. */ | ||
| 3768 | [level_indicator setLevelIndicatorStyle: | ||
| 3769 | NSLevelIndicatorStyleContinuousCapacity]; | ||
| 3770 | /* Match NSProgressIndicator color. */ | ||
| 3771 | [level_indicator setFillColor: [NSColor controlAccentColor]]; | ||
| 3772 | [level_indicator setMinValue: 0.0]; | ||
| 3773 | [level_indicator setMaxValue: 1.0]; | ||
| 3774 | /* The contentView takes ownership. */ | ||
| 3775 | [dock_tile.contentView addSubview: level_indicator]; | ||
| 3776 | } | ||
| 3777 | double progress_value; | ||
| 3778 | BOOL hide = (NILP (progress) | ||
| 3779 | || (!NILP (progress) && !(FLOATP (progress)))); | ||
| 3780 | if (!hide) | ||
| 3781 | { | ||
| 3782 | progress_value = XFLOAT_DATA (progress); | ||
| 3783 | hide = (progress_value < 0.0 || progress_value > 1.0); | ||
| 3784 | } | ||
| 3785 | if (hide) | ||
| 3786 | { | ||
| 3787 | [level_indicator setDoubleValue: 0.0]; | ||
| 3788 | [level_indicator setHidden: YES]; | ||
| 3789 | } | ||
| 3790 | else | ||
| 3791 | { | ||
| 3792 | [level_indicator setDoubleValue: progress_value]; | ||
| 3793 | [level_indicator setHidden: NO]; | ||
| 3794 | } | ||
| 3795 | [dock_tile display]; | ||
| 3796 | unblock_input (); | ||
| 3797 | return Qnil; | ||
| 3798 | } | ||
| 3799 | |||
| 3677 | #ifdef NS_IMPL_COCOA | 3800 | #ifdef NS_IMPL_COCOA |
| 3678 | 3801 | ||
| 3679 | DEFUN ("ns-send-items", | 3802 | DEFUN ("ns-send-items", |
| @@ -3957,6 +4080,9 @@ The default value is t. */); | |||
| 3957 | defsubr (&Sns_set_mouse_absolute_pixel_position); | 4080 | defsubr (&Sns_set_mouse_absolute_pixel_position); |
| 3958 | defsubr (&Sns_mouse_absolute_pixel_position); | 4081 | defsubr (&Sns_mouse_absolute_pixel_position); |
| 3959 | defsubr (&Sns_show_character_palette); | 4082 | defsubr (&Sns_show_character_palette); |
| 4083 | defsubr (&Sns_badge); | ||
| 4084 | defsubr (&Sns_request_user_attention); | ||
| 4085 | defsubr (&Sns_progress_indicator); | ||
| 3960 | #ifdef NS_IMPL_COCOA | 4086 | #ifdef NS_IMPL_COCOA |
| 3961 | defsubr (&Sns_send_items); | 4087 | defsubr (&Sns_send_items); |
| 3962 | #endif | 4088 | #endif |
| @@ -4023,4 +4149,6 @@ The default value is t. */); | |||
| 4023 | DEFSYM (Qassq_delete_all, "assq-delete-all"); | 4149 | DEFSYM (Qassq_delete_all, "assq-delete-all"); |
| 4024 | DEFSYM (Qrun_at_time, "run-at-time"); | 4150 | DEFSYM (Qrun_at_time, "run-at-time"); |
| 4025 | DEFSYM (Qx_hide_tip, "x-hide-tip"); | 4151 | DEFSYM (Qx_hide_tip, "x-hide-tip"); |
| 4152 | DEFSYM (Qinformational, "informational"); | ||
| 4153 | DEFSYM (Qcritical, "critical"); | ||
| 4026 | } | 4154 | } |
diff --git a/src/w32fns.c b/src/w32fns.c index f7bf6110991..b1f5799d1c5 100644 --- a/src/w32fns.c +++ b/src/w32fns.c | |||
| @@ -35,7 +35,12 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 35 | #include <c-ctype.h> | 35 | #include <c-ctype.h> |
| 36 | 36 | ||
| 37 | #define COBJMACROS /* Ask for C definitions for COM. */ | 37 | #define COBJMACROS /* Ask for C definitions for COM. */ |
| 38 | #if !defined MINGW_W64 && !defined CYGWIN | ||
| 39 | # define INITGUID | ||
| 40 | #endif | ||
| 41 | #include <initguid.h> | ||
| 38 | #include <shlobj.h> | 42 | #include <shlobj.h> |
| 43 | #include <shobjidl.h> | ||
| 39 | #include <oleidl.h> | 44 | #include <oleidl.h> |
| 40 | #include <objidl.h> | 45 | #include <objidl.h> |
| 41 | #include <ole2.h> | 46 | #include <ole2.h> |
| @@ -232,6 +237,8 @@ typedef struct Emacs_GESTURECONFIG | |||
| 232 | typedef BOOL (WINAPI * SetGestureConfig_proc) (HWND, DWORD, UINT, | 237 | typedef BOOL (WINAPI * SetGestureConfig_proc) (HWND, DWORD, UINT, |
| 233 | Emacs_PGESTURECONFIG, UINT); | 238 | Emacs_PGESTURECONFIG, UINT); |
| 234 | 239 | ||
| 240 | typedef BOOL (WINAPI * FlashWindowEx_Proc) (PFLASHWINFO pfwi); | ||
| 241 | |||
| 235 | static TrackMouseEvent_Proc track_mouse_event_fn = NULL; | 242 | static TrackMouseEvent_Proc track_mouse_event_fn = NULL; |
| 236 | static ImmGetCompositionString_Proc get_composition_string_fn = NULL; | 243 | static ImmGetCompositionString_Proc get_composition_string_fn = NULL; |
| 237 | static ImmGetContext_Proc get_ime_context_fn = NULL; | 244 | static ImmGetContext_Proc get_ime_context_fn = NULL; |
| @@ -254,6 +261,7 @@ static WTSUnRegisterSessionNotification_Proc WTSUnRegisterSessionNotification_fn | |||
| 254 | static WTSRegisterSessionNotification_Proc WTSRegisterSessionNotification_fn = NULL; | 261 | static WTSRegisterSessionNotification_Proc WTSRegisterSessionNotification_fn = NULL; |
| 255 | static RegisterTouchWindow_proc RegisterTouchWindow_fn = NULL; | 262 | static RegisterTouchWindow_proc RegisterTouchWindow_fn = NULL; |
| 256 | static SetGestureConfig_proc SetGestureConfig_fn = NULL; | 263 | static SetGestureConfig_proc SetGestureConfig_fn = NULL; |
| 264 | static FlashWindowEx_Proc flash_window_ex_fn = NULL; | ||
| 257 | 265 | ||
| 258 | extern AppendMenuW_Proc unicode_append_menu; | 266 | extern AppendMenuW_Proc unicode_append_menu; |
| 259 | 267 | ||
| @@ -11008,6 +11016,313 @@ Return -1 if the required system API is not available or fails. */) | |||
| 11008 | 11016 | ||
| 11009 | #endif | 11017 | #endif |
| 11010 | 11018 | ||
| 11019 | |||
| 11020 | #ifdef WINDOWSNT | ||
| 11021 | |||
| 11022 | /*********************************************************************** | ||
| 11023 | Taskbar Indicators | ||
| 11024 | ***********************************************************************/ | ||
| 11025 | |||
| 11026 | #ifndef MINGW_W64 | ||
| 11027 | /* mingw.org's MinGW doesn't have this stuff. */ | ||
| 11028 | DEFINE_GUID(CLSID_TaskbarList, 0x56fdf344, 0xfd6d, 0x11d0, 0x95,0x8a, 0x00,0x60,0x97,0xc9,0xa0,0x90); | ||
| 11029 | DEFINE_GUID(IID_ITaskbarList3, 0xea1afb91, 0x9e28, 0x4b86, 0x90,0xe9, 0x9e,0x9f,0x8a,0x5e,0xef,0xaf); | ||
| 11030 | #endif | ||
| 11031 | |||
| 11032 | DEFUN ("w32-badge", | ||
| 11033 | Fw32_badge, | ||
| 11034 | Sw32_badge, | ||
| 11035 | 3, 3, 0, | ||
| 11036 | doc: /* Display a taskbar icon overlay image on the selected frame. | ||
| 11037 | BADGE is a string. If BADGE is nil, remove the overlay. Do nothing if | ||
| 11038 | Windows does not support the ITaskbarList3 interface and return nil, | ||
| 11039 | otherwise return t. Do nothing if the selected frame is not (yet) | ||
| 11040 | associated with a window handle. BACKGROUND and FOREGROUND are RGB | ||
| 11041 | triplet strings of the form \"#RRGGBB\". */) | ||
| 11042 | (Lisp_Object badge, Lisp_Object background, Lisp_Object foreground) | ||
| 11043 | { | ||
| 11044 | struct frame *sf = SELECTED_FRAME (); | ||
| 11045 | HWND hwnd = NULL; | ||
| 11046 | |||
| 11047 | if (FRAME_W32_P (sf) && FRAME_LIVE_P (sf)) | ||
| 11048 | hwnd = FRAME_W32_WINDOW (sf); | ||
| 11049 | |||
| 11050 | if (hwnd == NULL) | ||
| 11051 | return Qnil; | ||
| 11052 | |||
| 11053 | CoInitialize (NULL); | ||
| 11054 | ITaskbarList3 *task_bar_list = NULL; | ||
| 11055 | HRESULT r = CoCreateInstance(&CLSID_TaskbarList, | ||
| 11056 | NULL, | ||
| 11057 | CLSCTX_INPROC_SERVER, | ||
| 11058 | &IID_ITaskbarList3, | ||
| 11059 | (void **)&task_bar_list); | ||
| 11060 | if (r != S_OK) | ||
| 11061 | return Qnil; | ||
| 11062 | |||
| 11063 | if (!NILP (badge) && STRINGP (badge) | ||
| 11064 | && STRINGP (background) && STRINGP (foreground)) | ||
| 11065 | { | ||
| 11066 | COLORREF bg_rgb; | ||
| 11067 | COLORREF fg_rgb; | ||
| 11068 | unsigned short r, g, b; | ||
| 11069 | if (parse_color_spec (SSDATA (background), &r, &g, &b)) | ||
| 11070 | bg_rgb = RGB (r, b, b); | ||
| 11071 | else | ||
| 11072 | return Qnil; | ||
| 11073 | if (parse_color_spec (SSDATA (foreground), &r, &g, &b)) | ||
| 11074 | fg_rgb = RGB (r, b, b); | ||
| 11075 | else | ||
| 11076 | return Qnil; | ||
| 11077 | |||
| 11078 | /* Prepare a string for drawing and as alt-text. */ | ||
| 11079 | Lisp_Object badge_utf8 = ENCODE_UTF_8 (badge); | ||
| 11080 | int wide_len = pMultiByteToWideChar (CP_UTF8, 0, | ||
| 11081 | SSDATA (badge_utf8), | ||
| 11082 | -1, NULL, 0); | ||
| 11083 | wchar_t *badge_w = alloca ((wide_len + 1) * sizeof (wchar_t)); | ||
| 11084 | pMultiByteToWideChar (CP_UTF8, 0, SSDATA (badge_utf8), -1, | ||
| 11085 | (LPWSTR) badge_w, | ||
| 11086 | wide_len); | ||
| 11087 | |||
| 11088 | /* Use the small icon size Windows suggests to not hard code 16x16. */ | ||
| 11089 | int icon_width = GetSystemMetrics (SM_CXSMICON); | ||
| 11090 | int icon_height = GetSystemMetrics (SM_CXSMICON); | ||
| 11091 | |||
| 11092 | HDC hwnd_dc = GetDC (hwnd); | ||
| 11093 | HDC dc = CreateCompatibleDC (hwnd_dc); | ||
| 11094 | |||
| 11095 | BITMAPV5HEADER bi; | ||
| 11096 | memset (&bi, 0, sizeof (bi)); | ||
| 11097 | bi.bV5Size = sizeof (bi); | ||
| 11098 | bi.bV5Width = icon_width; | ||
| 11099 | bi.bV5Height = -icon_height; /* Negative for a top-down DIB. */ | ||
| 11100 | bi.bV5Planes = 1; | ||
| 11101 | bi.bV5BitCount = 32; | ||
| 11102 | bi.bV5Compression = BI_BITFIELDS; /* Enable the masks below. */ | ||
| 11103 | bi.bV5RedMask = 0x00FF0000; | ||
| 11104 | bi.bV5GreenMask = 0x0000FF00; | ||
| 11105 | bi.bV5BlueMask = 0x000000FF; | ||
| 11106 | bi.bV5AlphaMask = 0xFF000000; | ||
| 11107 | |||
| 11108 | DWORD *bitmap_pixels; | ||
| 11109 | HBITMAP bitmap = CreateDIBSection (dc, (BITMAPINFO *) &bi, | ||
| 11110 | DIB_RGB_COLORS, | ||
| 11111 | (void **) &bitmap_pixels, | ||
| 11112 | NULL, 0); | ||
| 11113 | HGDIOBJ old_bitmap = SelectObject(dc, bitmap); | ||
| 11114 | |||
| 11115 | /* Draw a circle filled with bg. */ | ||
| 11116 | HBRUSH bg_brush = CreateSolidBrush (bg_rgb); | ||
| 11117 | HGDIOBJ old_brush = SelectObject (dc, bg_brush); | ||
| 11118 | Ellipse (dc, 0, 0, icon_width, icon_height); | ||
| 11119 | SelectObject (dc, old_brush); | ||
| 11120 | DeleteObject (bg_brush); | ||
| 11121 | |||
| 11122 | /* Derive a font scaled to fit the icon. First find the system's | ||
| 11123 | base font. Then scale it to fit icon_height. */ | ||
| 11124 | HFONT base_font; | ||
| 11125 | BOOL clean_up_base_font = FALSE; | ||
| 11126 | if (system_parameters_info_w_fn) | ||
| 11127 | { | ||
| 11128 | NONCLIENTMETRICS ncm; | ||
| 11129 | memset (&ncm, 0, sizeof (ncm)); | ||
| 11130 | ncm.cbSize = sizeof (ncm); | ||
| 11131 | SystemParametersInfo (SPI_GETNONCLIENTMETRICS, sizeof (ncm), &ncm, 0); | ||
| 11132 | base_font = CreateFontIndirect (&ncm.lfSmCaptionFont); | ||
| 11133 | clean_up_base_font = TRUE; | ||
| 11134 | } | ||
| 11135 | else | ||
| 11136 | base_font = (HFONT) GetStockObject (DEFAULT_GUI_FONT); | ||
| 11137 | if (clean_up_base_font) | ||
| 11138 | DeleteObject (base_font); | ||
| 11139 | |||
| 11140 | LOGFONT lf; | ||
| 11141 | GetObject (base_font, sizeof (lf), &lf); | ||
| 11142 | lf.lfWeight = FW_BOLD; | ||
| 11143 | lf.lfOutPrecision = OUT_OUTLINE_PRECIS; | ||
| 11144 | /* ClearType quality needs opqaue, but we draw transparent. */ | ||
| 11145 | lf.lfQuality = ANTIALIASED_QUALITY; | ||
| 11146 | /* Negative lfHeight indicates pixel units vs. positive in points. | ||
| 11147 | Use the LOGPIXELSY px/in of the primary monitor. */ | ||
| 11148 | lf.lfHeight = -MulDiv(icon_height / 2, /* Fit ~3 chars. */ | ||
| 11149 | 72, | ||
| 11150 | GetDeviceCaps (GetDC (NULL), LOGPIXELSY)); | ||
| 11151 | /* Ensure lfHeight pixel interpretation. */ | ||
| 11152 | int old_map_mode = SetMapMode (dc, MM_TEXT); | ||
| 11153 | HFONT scaled_font = CreateFontIndirect (&lf); | ||
| 11154 | HGDIOBJ old_font = SelectObject (dc, scaled_font); | ||
| 11155 | SetMapMode (dc, old_map_mode); | ||
| 11156 | |||
| 11157 | /* Draw badge text. */ | ||
| 11158 | SetBkMode (dc, TRANSPARENT); | ||
| 11159 | SetTextColor (dc, fg_rgb); | ||
| 11160 | RECT rect; | ||
| 11161 | rect.left = rect.top = 0; | ||
| 11162 | rect.right = icon_width; | ||
| 11163 | rect.bottom = icon_height; | ||
| 11164 | DrawText (dc, SSDATA (badge_utf8), | ||
| 11165 | -1, /* Indicate null-terminated string. */ | ||
| 11166 | &rect, | ||
| 11167 | DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP); | ||
| 11168 | SelectObject (dc, old_font); | ||
| 11169 | DeleteObject (scaled_font); | ||
| 11170 | |||
| 11171 | /* Make the circle and its text opaque by setting the alpha | ||
| 11172 | channel on each pixel falling within the circle. */ | ||
| 11173 | int circle_center_x = icon_width / 2; | ||
| 11174 | int circle_center_y = icon_height / 2; | ||
| 11175 | int circle_radius = (icon_width < icon_height | ||
| 11176 | ? icon_width | ||
| 11177 | : icon_height) / 2 - 2; | ||
| 11178 | int circle_radius_sq = circle_radius * circle_radius; | ||
| 11179 | DWORD *pixel; | ||
| 11180 | for (int y = 0; y < icon_height; ++y) | ||
| 11181 | for (int x = 0; x < icon_width; ++x) | ||
| 11182 | { | ||
| 11183 | int dx = x - circle_center_x; | ||
| 11184 | int dy = y - circle_center_y; | ||
| 11185 | if (dx * dx + dy * dy <= circle_radius_sq) | ||
| 11186 | { | ||
| 11187 | pixel = bitmap_pixels + (y * icon_width + x); | ||
| 11188 | *pixel |= 0xff000000; /* Flip the 0xAARRGGBB alpha channel. */ | ||
| 11189 | } | ||
| 11190 | } | ||
| 11191 | |||
| 11192 | /* Dummy monochrome bitmap mask, ignored when the color bitmap has | ||
| 11193 | an alpha channel, but needed to satisfy CreateIconIndirect. */ | ||
| 11194 | HBITMAP mask_bitmap = CreateBitmap (icon_width, icon_height, 1, 1, NULL); | ||
| 11195 | |||
| 11196 | /* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createiconindirect | ||
| 11197 | hbmMask and hbmColor members of the ICONINFO structure should | ||
| 11198 | not already be selected into a device context. */ | ||
| 11199 | SelectObject (dc, old_bitmap); | ||
| 11200 | |||
| 11201 | ICONINFO icon_info; | ||
| 11202 | memset (&icon_info, 0, sizeof (icon_info)); | ||
| 11203 | icon_info.fIcon = TRUE; | ||
| 11204 | icon_info.hbmMask = mask_bitmap; | ||
| 11205 | icon_info.hbmColor = bitmap; | ||
| 11206 | |||
| 11207 | HICON icon = CreateIconIndirect (&icon_info); | ||
| 11208 | task_bar_list->lpVtbl->SetOverlayIcon (task_bar_list, hwnd, icon, badge_w); | ||
| 11209 | |||
| 11210 | DestroyIcon (icon); | ||
| 11211 | DeleteObject (mask_bitmap); | ||
| 11212 | DeleteObject (bitmap); | ||
| 11213 | DeleteDC (dc); | ||
| 11214 | ReleaseDC (hwnd, hwnd_dc); | ||
| 11215 | } | ||
| 11216 | else | ||
| 11217 | task_bar_list->lpVtbl->SetOverlayIcon (task_bar_list, hwnd, NULL, NULL); | ||
| 11218 | |||
| 11219 | task_bar_list->lpVtbl->Release(task_bar_list); | ||
| 11220 | return Qt; | ||
| 11221 | } | ||
| 11222 | |||
| 11223 | DEFUN ("w32-request-user-attention", | ||
| 11224 | Fw32_request_user_attention, | ||
| 11225 | Sw32_request_user_attention, | ||
| 11226 | 1, 1, 0, | ||
| 11227 | doc: /* Flash the selected frame's taskbar icon and/or its window. | ||
| 11228 | If URGENCY is nil, cancel the request, if any. If URGENCY is the symbol | ||
| 11229 | `informational', flash the taskbar icon. If URGENCY is the symbol | ||
| 11230 | `critical', flash the taskbar icon and the frame. Windows stops | ||
| 11231 | flashing if the user focuses the frame. Do nothing if Windows does not | ||
| 11232 | support FlashWindowEx and return nil, otherwise return t. Do nothing if | ||
| 11233 | the frame is not (yet) associated with a window handle. */) | ||
| 11234 | (Lisp_Object urgency) | ||
| 11235 | { | ||
| 11236 | if (flash_window_ex_fn == NULL) | ||
| 11237 | return Qnil; | ||
| 11238 | |||
| 11239 | struct frame *sf = SELECTED_FRAME (); | ||
| 11240 | HWND hwnd = NULL; | ||
| 11241 | |||
| 11242 | if (FRAME_W32_P (sf) && FRAME_LIVE_P (sf)) | ||
| 11243 | hwnd = FRAME_W32_WINDOW (sf); | ||
| 11244 | |||
| 11245 | if (hwnd == NULL) | ||
| 11246 | return Qnil; | ||
| 11247 | |||
| 11248 | FLASHWINFO flash_info; | ||
| 11249 | flash_info.cbSize = sizeof(flash_info); | ||
| 11250 | flash_info.uCount = 0; | ||
| 11251 | flash_info.dwTimeout = 0; | ||
| 11252 | flash_info.hwnd = hwnd; | ||
| 11253 | if (!NILP (urgency) && SYMBOLP (urgency)) | ||
| 11254 | { | ||
| 11255 | /* The intended caller, 'system-taskbar-attention', has an | ||
| 11256 | optional timer to clear the attention indicator so this will | ||
| 11257 | flash until cleared via the timer, or the window comes to the | ||
| 11258 | foreground. For informational attention, flash the tray icon. | ||
| 11259 | For critical attention, flash the tray icon and the window. */ | ||
| 11260 | if (EQ (urgency, Qinformational)) | ||
| 11261 | flash_info.dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG; | ||
| 11262 | else if (EQ (urgency, Qcritical)) | ||
| 11263 | flash_info.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG; | ||
| 11264 | } | ||
| 11265 | else | ||
| 11266 | flash_info.dwFlags = FLASHW_STOP; | ||
| 11267 | |||
| 11268 | flash_window_ex_fn (&flash_info); | ||
| 11269 | return Qt; | ||
| 11270 | } | ||
| 11271 | |||
| 11272 | DEFUN ("w32-progress-indicator", | ||
| 11273 | Fw32_progress_indicator, | ||
| 11274 | Sw32_progress_indicator, | ||
| 11275 | 1, 1, 0, | ||
| 11276 | doc: /* Show a progress bar on the selected frame's taskbar icon. | ||
| 11277 | PROGRESS is a float in the range 0.0 to 1.0. If PROGRESS is nil, remove | ||
| 11278 | the progress indicator. Do nothing if Windows does not support the | ||
| 11279 | ITaskbarList3 interface and return nil, otherwise return t. Do nothing | ||
| 11280 | if the selected frame is not (yet) associated with a window handle */) | ||
| 11281 | (Lisp_Object progress) | ||
| 11282 | { | ||
| 11283 | struct frame *sf = SELECTED_FRAME (); | ||
| 11284 | HWND hwnd = NULL; | ||
| 11285 | |||
| 11286 | if (FRAME_W32_P (sf) && FRAME_LIVE_P (sf)) | ||
| 11287 | hwnd = FRAME_W32_WINDOW (sf); | ||
| 11288 | |||
| 11289 | if (hwnd == NULL) | ||
| 11290 | return Qnil; | ||
| 11291 | |||
| 11292 | CoInitialize (NULL); | ||
| 11293 | ITaskbarList3 *task_bar_list = NULL; | ||
| 11294 | HRESULT r = CoCreateInstance(&CLSID_TaskbarList, | ||
| 11295 | NULL, | ||
| 11296 | CLSCTX_INPROC_SERVER, | ||
| 11297 | &IID_ITaskbarList3, | ||
| 11298 | (void **)&task_bar_list); | ||
| 11299 | if (r != S_OK) | ||
| 11300 | return Qnil; | ||
| 11301 | |||
| 11302 | /* Scale task bar progress from 0.0-1.0 to 0-100. */ | ||
| 11303 | ULONGLONG adj_progress = 0; | ||
| 11304 | if (!NILP (progress) && FLOATP (progress)) | ||
| 11305 | adj_progress = (ULONGLONG) (100.0 * | ||
| 11306 | XFLOAT_DATA (progress)); | ||
| 11307 | if (adj_progress > 0) | ||
| 11308 | { | ||
| 11309 | task_bar_list->lpVtbl->SetProgressState (task_bar_list, | ||
| 11310 | hwnd, TBPF_NORMAL); | ||
| 11311 | task_bar_list->lpVtbl->SetProgressValue (task_bar_list, | ||
| 11312 | hwnd, adj_progress, 100); | ||
| 11313 | } | ||
| 11314 | else | ||
| 11315 | { | ||
| 11316 | task_bar_list->lpVtbl->SetProgressState (task_bar_list, | ||
| 11317 | hwnd, TBPF_NOPROGRESS); | ||
| 11318 | } | ||
| 11319 | |||
| 11320 | task_bar_list->lpVtbl->Release(task_bar_list); | ||
| 11321 | return Qt; | ||
| 11322 | } | ||
| 11323 | |||
| 11324 | #endif /* WINDOWSNT */ | ||
| 11325 | |||
| 11011 | /*********************************************************************** | 11326 | /*********************************************************************** |
| 11012 | Initialization | 11327 | Initialization |
| 11013 | ***********************************************************************/ | 11328 | ***********************************************************************/ |
| @@ -11509,6 +11824,15 @@ keys when IME input is received. */); | |||
| 11509 | DEFSYM (Qcapslock, "capslock"); | 11824 | DEFSYM (Qcapslock, "capslock"); |
| 11510 | DEFSYM (Qkp_numlock, "kp-numlock"); | 11825 | DEFSYM (Qkp_numlock, "kp-numlock"); |
| 11511 | DEFSYM (Qscroll, "scroll"); | 11826 | DEFSYM (Qscroll, "scroll"); |
| 11827 | |||
| 11828 | #ifdef WINDOWSNT | ||
| 11829 | /* Taskbar indicators support. */ | ||
| 11830 | defsubr (&Sw32_badge); | ||
| 11831 | defsubr (&Sw32_progress_indicator); | ||
| 11832 | defsubr (&Sw32_request_user_attention); | ||
| 11833 | DEFSYM (Qinformational, "informational"); | ||
| 11834 | DEFSYM (Qcritical, "critical"); | ||
| 11835 | #endif | ||
| 11512 | } | 11836 | } |
| 11513 | 11837 | ||
| 11514 | 11838 | ||
| @@ -11797,6 +12121,9 @@ globals_of_w32fns (void) | |||
| 11797 | SetGestureConfig_fn | 12121 | SetGestureConfig_fn |
| 11798 | = (SetGestureConfig_proc) get_proc_addr (user32_lib, | 12122 | = (SetGestureConfig_proc) get_proc_addr (user32_lib, |
| 11799 | "SetGestureConfig"); | 12123 | "SetGestureConfig"); |
| 12124 | flash_window_ex_fn | ||
| 12125 | = (FlashWindowEx_Proc) get_proc_addr (user32_lib, | ||
| 12126 | "FlashWindowEx"); | ||
| 11800 | 12127 | ||
| 11801 | { | 12128 | { |
| 11802 | HMODULE imm32_lib = GetModuleHandle ("imm32.dll"); | 12129 | HMODULE imm32_lib = GetModuleHandle ("imm32.dll"); |