1212
1313import static org .junit .jupiter .api .Assertions .assertEquals ;
1414import static org .junit .jupiter .api .Assertions .assertNull ;
15+ import static org .junit .jupiter .api .Assumptions .assumeFalse ;
1516import static org .junit .jupiter .api .Assumptions .assumeTrue ;
1617
1718import java .util .List ;
1819import java .util .concurrent .CompletableFuture ;
20+ import java .util .concurrent .atomic .AtomicBoolean ;
1921
22+ import org .eclipse .swt .SWT ;
2023import org .eclipse .swt .dnd .Clipboard ;
2124import org .eclipse .swt .dnd .DND ;
2225import org .eclipse .swt .dnd .RTFTransfer ;
2326import org .eclipse .swt .dnd .TextTransfer ;
2427import org .eclipse .swt .dnd .Transfer ;
28+ import org .eclipse .swt .layout .RowLayout ;
29+ import org .eclipse .swt .widgets .Button ;
2530import org .eclipse .swt .widgets .Display ;
31+ import org .eclipse .swt .widgets .Label ;
2632import org .eclipse .swt .widgets .Shell ;
2733import org .junit .jupiter .api .AfterEach ;
2834import org .junit .jupiter .api .BeforeEach ;
35+ import org .junit .jupiter .api .MethodOrderer .OrderAnnotation ;
36+ import org .junit .jupiter .api .Order ;
2937import org .junit .jupiter .api .RepeatedTest ;
3038import org .junit .jupiter .api .Tag ;
3139import org .junit .jupiter .api .Test ;
40+ import org .junit .jupiter .api .TestMethodOrder ;
3241import org .junit .jupiter .params .ParameterizedTest ;
3342import org .junit .jupiter .params .provider .MethodSource ;
3443
4049 * some clipboard tests
4150 */
4251@ Tag ("clipboard" )
52+ @ TestMethodOrder (OrderAnnotation .class ) // run tests needing button presses first
4353public class Test_org_eclipse_swt_dnd_Clipboard {
4454
55+ /**
56+ * See {@link #openAndFocusShell(boolean)} - some tests require user to actually
57+ * interact with the shell.
58+ *
59+ * Default to skipping tests requiring "real" activation on GHA and Jenkins.
60+ *
61+ * <code>true</code>: skip tests <code>false</code>: don't skip tests
62+ * <code>null</code>: unknown whether to skip tests yet
63+ */
64+ private static Boolean skipTestsRequiringButtonPress = (Boolean .parseBoolean (System .getenv ("GITHUB_ACTIONS" ))
65+ || System .getenv ("JOB_NAME" ) != null ) ? true : null ;
4566 private static int uniqueId = 1 ;
4667 private Display display ;
4768 private Shell shell ;
@@ -65,21 +86,86 @@ public void setUp() {
6586 * Note: Wayland backend does not allow access to system clipboard from
6687 * non-focussed windows. So we have to create/open and focus a window here so
6788 * that clipboard operations work.
89+ *
90+ * Additionally, if we want to provide data to the clipboard, we require user
91+ * interaction on the created shell. Therefore if forSetContents is true the
92+ * tester needs to press a button for test to work.
93+ *
94+ * If there is no user interaction (button not pressed) then the test is
95+ * skipped, rather than failed, and subsequent tests requiring user interaction
96+ * as skipped too. See {@link #skipTestsRequiringButtonPress}
6897 */
69- private void openAndFocusShell () {
98+ private void openAndFocusShell (boolean forSetContents ) throws InterruptedException {
99+ assertNull (shell );
70100 shell = new Shell (display );
71- SwtTestUtil .openShell (shell );
101+
102+ boolean requireUserPress = forSetContents && SwtTestUtil .isWayland ();
103+ if (requireUserPress ) {
104+ assumeFalse (skipTestsRequiringButtonPress != null && skipTestsRequiringButtonPress ,
105+ "Skipping tests that require user input" );
106+
107+ AtomicBoolean pressed = new AtomicBoolean (false );
108+ shell .setLayout (new RowLayout (SWT .VERTICAL ));
109+ Button button = new Button (shell , SWT .PUSH );
110+ button .setText ("Press me!" );
111+ button .addListener (SWT .Selection , (e ) -> pressed .set (true ));
112+ button .setSize (200 , 50 );
113+ Label label = new Label (shell , SWT .NONE );
114+ label .setText ("""
115+ Press the button to tell Wayland that you really want this window to have access to clipboard.
116+ This is needed on Wayland because only really focussed programs are allowed to write to the
117+ global keyboard.
118+
119+ If you don't press this button soon, the test will be skipped and you won't be asked again.
120+ """ );
121+ Label timeleft = new Label (shell , SWT .NONE );
122+ timeleft .setText ("Time left to press button: XXXXXXXXXXXXXXXXXXXX seconds" );
123+
124+ SwtTestUtil .openShell (shell );
125+
126+ // If we know there is a tester pressing the buttons, allow them
127+ // a little grace on the timeout. If we don't know if there is a
128+ // tester around, skip tests fairly quickly and don't
129+ // ask again.
130+ int timeout = skipTestsRequiringButtonPress == null ? 1500 : 10000 ;
131+ long startTime = System .nanoTime ();
132+ SwtTestUtil .processEvents (timeout , () -> {
133+ long nowTime = System .nanoTime ();
134+ long timeLeft = nowTime - startTime ;
135+ long timeLeftMs = timeout - (timeLeft / 1_000_000 );
136+ double timeLeftS = timeLeftMs / 1_000.0d ;
137+ timeleft .setText ("Time left to press button: " + timeLeftS + " seconds" );
138+ return pressed .get ();
139+ });
140+ boolean userPressedButton = pressed .get ();
141+ if (userPressedButton ) {
142+ skipTestsRequiringButtonPress = false ;
143+ } else {
144+ skipTestsRequiringButtonPress = true ;
145+ assumeTrue (false , "Skipping tests that require user input" );
146+ }
147+ } else {
148+ SwtTestUtil .openShell (shell );
149+ }
150+
72151 }
73152
74153 /**
75154 * Note: Wayland backend does not allow access to system clipboard from
76155 * non-focussed windows. So we have to open and focus remote here so that
77156 * clipboard operations work.
78157 */
79- public void openAndFocusRemote () throws Exception {
158+ private void openAndFocusRemote () throws Exception {
80159 assertNull (remote );
81160 remote = new RemoteClipboard ();
82161 remote .start ();
162+
163+ /*
164+ * If/when OpenJDK Project Wakefield gets merged then we may need to wait for
165+ * button pressed on the swing app just like the SWT app. This may also be
166+ * needed if Wayland implementations get more restrictive on X apps too.
167+ */
168+ // remote.waitForButtonPress();
83169 }
84170
85171 @ AfterEach
@@ -138,8 +224,8 @@ public void test_Remote(int clipboardId) throws Exception {
138224 */
139225 @ ParameterizedTest
140226 @ MethodSource ("supportedClipboardIds" )
141- public void test_LocalClipboard (int clipboardId ) {
142- openAndFocusShell ();
227+ public void test_LocalClipboard (int clipboardId ) throws InterruptedException {
228+ openAndFocusShell (false );
143229
144230 String helloWorld = getUniqueTestString ();
145231 clipboard .setContents (new Object [] { helloWorld }, new Transfer [] { textTransfer }, clipboardId );
@@ -179,10 +265,11 @@ public void test_LocalClipboard(int clipboardId) {
179265 assertEquals (helloWorldRtf , clipboard .getContents (rtfTransfer , clipboardId ));
180266 }
181267
268+ @ Order (2 )
182269 @ ParameterizedTest
183270 @ MethodSource ("supportedClipboardIds" )
184271 public void test_setContents (int clipboardId ) throws Exception {
185- openAndFocusShell ();
272+ openAndFocusShell (true );
186273 String helloWorld = getUniqueTestString ();
187274
188275 clipboard .setContents (new Object [] { helloWorld }, new Transfer [] { textTransfer }, clipboardId );
@@ -199,11 +286,10 @@ public void test_getContents(int clipboardId) throws Exception {
199286 String helloWorld = getUniqueTestString ();
200287 remote .setContents (helloWorld , clipboardId );
201288
202- openAndFocusShell ();
289+ openAndFocusShell (false );
203290 assertEquals (helloWorld , clipboard .getContents (textTransfer , clipboardId ));
204291 }
205292
206-
207293 @ Test
208294 public void test_getContentsBothClipboards () throws Exception {
209295 assumeTrue (SwtTestUtil .isGTK );
@@ -214,16 +300,17 @@ public void test_getContentsBothClipboards() throws Exception {
214300 String helloWorldSelection = getUniqueTestString ();
215301 remote .setContents (helloWorldSelection , DND .SELECTION_CLIPBOARD );
216302
217- openAndFocusShell ();
303+ openAndFocusShell (false );
218304 assertEquals (helloWorldClipboard , clipboard .getContents (textTransfer , DND .CLIPBOARD ));
219305 assertEquals (helloWorldSelection , clipboard .getContents (textTransfer , DND .SELECTION_CLIPBOARD ));
220306 }
221307
308+ @ Order (1 )
222309 @ Test
223310 public void test_setContentsBothClipboards () throws Exception {
224311 assumeTrue (SwtTestUtil .isGTK );
225312
226- openAndFocusShell ();
313+ openAndFocusShell (true );
227314 String helloWorldClipboard = getUniqueTestString ();
228315 clipboard .setContents (new Object [] { helloWorldClipboard }, new Transfer [] { textTransfer }, DND .CLIPBOARD );
229316 String helloWorldSelection = getUniqueTestString ();
@@ -244,7 +331,7 @@ public void test_getContentsAsync(int clipboardId) throws Exception {
244331 String helloWorld = getUniqueTestString ();
245332 remote .setContents (helloWorld , clipboardId );
246333
247- openAndFocusShell ();
334+ openAndFocusShell (false );
248335
249336 // Multiple ways of using the API
250337 // 1: Spin the event loop manually waiting for future to complete
0 commit comments