From 59994ff66187e416460c964c11d9f881843c8a0f Mon Sep 17 00:00:00 2001 From: Alexandr Miloslavskiy Date: Mon, 22 May 2023 04:03:28 +0300 Subject: [PATCH 1/3] Issue #603: Test snippet Signed-off-by: Alexandr Miloslavskiy --- .../Issue0603_WrongTaskBarOverlays.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Issue0603_WrongTaskBarOverlays.java diff --git a/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Issue0603_WrongTaskBarOverlays.java b/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Issue0603_WrongTaskBarOverlays.java new file mode 100644 index 00000000000..af54efd05ce --- /dev/null +++ b/tests/org.eclipse.swt.tests/ManualTests/org/eclipse/swt/tests/manual/Issue0603_WrongTaskBarOverlays.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2023 Syntevo and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Syntevo - initial API and implementation + *******************************************************************************/ + +package org.eclipse.swt.tests.manual; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + +public final class Issue0603_WrongTaskBarOverlays { + public static void main(String[] args) { + final Display display = new Display(); + + final Shell shell = new Shell(display); + shell.setLayout(new GridLayout(1, true)); + + final Label hint = new Label(shell, 0); + hint.setText( + "1. Use Win11; Win10 is not enough\n" + + "2. Start the program and pin its icon on TaskBar\n" + + "3. Press the button below to open a few Shells with overlays\n" + + "4. Issue #603: when one of them is closed, it doesn't restore previous overlay correctly\n" + + " Note that Windows is documented to restore the overlay that was set last, contrary to\n" + + " overlay of currently active Shell\n" + + "5. Issue #603: when all of them is closed, there should be no overlay\n" + + "6. The checkboxes below are to test that patch didn't break setting text/image overlays" + ); + + Button button = new Button(shell, SWT.PUSH); + button.setText("Test"); + button.addListener(SWT.Selection, e -> { + for (int iShell = 0; iShell < 3; iShell++) { + Shell shell2 = new Shell(display); + shell2.setText("Shell #" + iShell); + shell2.setSize(250, 100); + shell2.open(); + + final TaskItem taskItem2 = display.getSystemTaskBar().getItem(shell2); + taskItem2.setOverlayText("#" + iShell); + + // Windows Explorer seems to get confused when multiple overlays are set quickly + // It will then sometimes put them in wrong order. + // The workaround is to wait and process display events after each shell + { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + } + + while (display.readAndDispatch()) { + } + } + } + }); + + final TaskItem taskItem = display.getSystemTaskBar().getItem(shell); + + Button chkText = new Button(shell, SWT.CHECK); + chkText.setText("Set Text overlay for me"); + chkText.addListener(SWT.Selection, e -> { + if (chkText.getSelection()) { + taskItem.setOverlayText("Main"); + } else { + taskItem.setOverlayText(""); + } + }); + + Image image = new Image(display, 16, 16); + { + GC gc = new GC(image); + gc.setBackground(display.getSystemColor(SWT.COLOR_RED)); + gc.setForeground(display.getSystemColor(SWT.COLOR_RED)); + gc.fillRectangle(0, 0, 16, 16); + gc.dispose(); + } + + Button chkImage = new Button(shell, SWT.CHECK); + chkImage.setText("Set Image overlay for me"); + chkImage.addListener(SWT.Selection, e -> { + if (chkImage.getSelection()) { + taskItem.setOverlayImage(image); + } else { + taskItem.setOverlayImage(null); + } + }); + + shell.pack(); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + + image.dispose(); + } +} From 23dc699eb46f8bf0dc005fffbb27f99289c46d57 Mon Sep 17 00:00:00 2001 From: Alexandr Miloslavskiy Date: Mon, 22 May 2023 04:05:49 +0300 Subject: [PATCH 2/3] Issue #603: Don't call ITaskbarList3 early MSDN for 'ITaskbarList3' says: TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method Do exactly this, don't call any methods until the button is created. This doesn't seem to fix any currently known problems, but still the right thing to do. Signed-off-by: Alexandr Miloslavskiy --- .../win32/org/eclipse/swt/widgets/Shell.java | 2 +- .../org/eclipse/swt/widgets/TaskItem.java | 75 ++++++++++++------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java index 89947ab92bc..2175d13b49a 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Shell.java @@ -2285,7 +2285,7 @@ long windowProc (long hwnd, int msg, long wParam, long lParam) { if (display.taskBar != null) { for (TaskItem item : display.taskBar.items) { if (item != null && item.shell == this) { - item.recreate (); + item.onTaskbarButtonCreated(); break; } } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java index e1c26b5a82d..c4ac3466fb0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java @@ -38,6 +38,7 @@ public class TaskItem extends Item { TaskBar parent; Shell shell; + boolean hasTaskbarButton = false; int progress, progressState = SWT.DEFAULT; Image overlayImage; String overlayText = ""; @@ -188,16 +189,27 @@ public int getProgressState () { return progressState; } -void recreate () { - if (showingText) { - if (overlayText.length () != 0) updateText (); +void updateImageAndText () { + if (showingText && (overlayText.length () != 0)) { + updateText (); + } else if (overlayImage != null) { + updateImage (); } else { - if (overlayImage != null) updateImage (); + parent.mTaskbarList3.SetOverlayIcon(shell.handle, 0, 0); } +} + +void updateAll () { + updateImageAndText (); if (progress != 0) updateProgress (); if (progressState != SWT.DEFAULT) updateProgressState (); } +void onTaskbarButtonCreated () { + this.hasTaskbarButton = true; + updateAll (); +} + @Override void releaseHandle () { super.releaseHandle (); @@ -287,15 +299,14 @@ public void setOverlayImage (Image overlayImage) { if (overlayImage != null && overlayImage.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT); if (shell == null) return; this.overlayImage = overlayImage; - if (overlayImage != null) { - updateImage (); - } else { - if (overlayText.length () != 0) { - updateText (); - } else { - parent.mTaskbarList3.SetOverlayIcon(shell.handle, 0, 0); - } - } + this.showingText = (this.overlayImage == null); + + // MSDN for 'ITaskbarList3' says: + // TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method + // #updateAll() will be called later when message is received. + if (!hasTaskbarButton) return; + + updateImageAndText (); } /** @@ -329,15 +340,14 @@ public void setOverlayText (String overlayText) { if (overlayText == null) error (SWT.ERROR_NULL_ARGUMENT); if (shell == null) return; this.overlayText = overlayText; - if (overlayText.length () != 0) { - updateText (); - } else { - if (overlayImage != null) { - updateImage (); - } else { - parent.mTaskbarList3.SetOverlayIcon(shell.handle, 0, 0); - } - } + this.showingText = (this.overlayText.length() != 0); + + // MSDN for 'ITaskbarList3' says: + // TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method + // #updateAll() will be called later when message is received. + if (!hasTaskbarButton) return; + + updateImageAndText (); } /** @@ -371,6 +381,12 @@ public void setProgress (int progress) { progress = Math.max (0, Math.min (progress, PROGRESS_MAX)); if (this.progress == progress) return; this.progress = progress; + + // MSDN for 'ITaskbarList3' says: + // TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method + // #updateAll() will be called later when message is received. + if (!hasTaskbarButton) return; + updateProgress (); } @@ -414,6 +430,12 @@ public void setProgressState (int progressState) { if (shell == null) return; if (this.progressState == progressState) return; this.progressState = progressState; + + // MSDN for 'ITaskbarList3' says: + // TaskbarButtonCreated ... message must be received by your application before it calls any ITaskbarList3 method + // #updateAll() will be called later when message is received. + if (!hasTaskbarButton) return; + updateProgressState (); } @@ -429,7 +451,6 @@ public void handleEvent (Event event) { } void updateImage () { - showingText = false; Image image2 = null; long hIcon = 0; switch (overlayImage.type) { @@ -463,8 +484,7 @@ void updateProgressState () { parent.mTaskbarList3.SetProgressState(shell.handle, tbpFlags); } -void updateText () { - showingText = true; +long renderTextIcon () { /* Create resources */ int width = 16, height = 16; long hdc = OS.GetDC (0); @@ -547,6 +567,11 @@ void updateText () { OS.DeleteObject (hBitmap); OS.DeleteObject (hMask); + return hIcon; +} + +void updateText () { + long hIcon = renderTextIcon(); parent.mTaskbarList3.SetOverlayIcon(shell.handle, hIcon, 0); OS.DestroyIcon (hIcon); } From 87fe7bc1ffee19d71c0b3fb9af9982926986431f Mon Sep 17 00:00:00 2001 From: Alexandr Miloslavskiy Date: Mon, 22 May 2023 04:12:41 +0300 Subject: [PATCH 3/3] Issue #603: [win32] Windows TaskBar shows wrong overlay after closing related Shell Signed-off-by: Alexandr Miloslavskiy --- .../org/eclipse/swt/widgets/TaskItem.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java index c4ac3466fb0..86e09819e59 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/TaskItem.java @@ -90,6 +90,28 @@ protected void checkSubclass () { @Override void destroyWidget () { + if (null != parent.mTaskbarList3) { // extra safety just in case + // MSDN for 'ITaskbarList3::SetOverlayIcon' says: + // Because a single overlay is applied to the taskbar button instead + // of to the individual window thumbnails, this is a per-group + // feature rather than per-window. Requests for overlay icons can + // be received from individual windows in a taskbar group, but they + // do not queue. The last overlay received is the overlay shown. + // If the last overlay received is removed, the overlay that it + // replaced is restored so long as it is still active. As an example, + // windows 1, 2, and 3 set, in order, overlays A, B, and C. + // Because overlay C was received last, it is shown on the taskbar button. + // Window 2 calls SetOverlayIcon with a NULL value to remove overlay B. + // Window 3 then does the same to remove overlay C. + // Because window 1's overlay A is still active, that overlay is then + // displayed on the taskbar button. + // This implies that Shell should pass NULL before closing. + // + // Issue #603: On Win11 the lack of this actually caused wrong overlay + // to show after closing Shell. + parent.mTaskbarList3.SetOverlayIcon(shell.handle, 0, 0); + } + parent.destroyItem (this); releaseHandle (); }