-
Notifications
You must be signed in to change notification settings - Fork 5.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8280982: [Wayland] [XWayland] java.awt.Robot taking screenshots #13803
Conversation
/csr |
👋 Welcome back azvegint! A progress list of the required criteria for merging this PR into |
@azvegint has indicated that a compatibility and specification (CSR) request is needed for this pull request. @azvegint please create a CSR request for issue JDK-8280982 with the correct fix version. This pull request cannot be integrated until the CSR request is approved. |
Webrevs
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to understand why we need to add third party headers to our source tree. Are these not generally available on Linux systems?
You say that the functionality isn't enabled by default, is that a runtime thing? It loos like it will be built unconditionally.
They are not available on anything except very new Linux distros. Ubuntu 22.04 is however known to be OK.
Yes, a runtime thing. An implementation System Property needs to be set. |
Do we really need to add this new API? probably we can implement the feature w/o this new method? |
private static volatile Boolean isOnWayland = null; | ||
|
||
@SuppressWarnings("removal") | ||
public static boolean isOnWayland() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that this method doesn't have to part of a public interface of UNIXToolkit
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. Furthermore, Wayland detection is no longer used in this change, so I am removing it.
It is now in #13830, where it belongs.
src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java
Outdated
Show resolved
Hide resolved
src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java
Outdated
Show resolved
Hide resolved
return TRUE; | ||
} | ||
|
||
static inline void convertRGBxToBGRx(int* in) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this work correctly on little-endian ARM systems?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tested it, but I'll try to find a machine to test it on.
src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c
Outdated
Show resolved
Hide resolved
src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c
Outdated
Show resolved
Hide resolved
(*env)->SetIntArrayRegion( | ||
env, pixelArray, | ||
start, len, | ||
((jint *) screenProps->captureData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if endianness of captureData
matches the expected endianness of pixelArray
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, it is going to be supported on x86-64 only, but it is a good candidate for investigation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this enforced somehow? I mean what happens if this code is built on risc-v or arm?
I couldn't think of anything better. |
Can we just reset the token when we create a new instance of Robot? |
This will be a big problem for automated testing, as each test with the robot will require user confirmation(or even more than one, as some tests instantiate it several times). On the other hand, we can introduce a new system property(e.g. |
I'm thinking about the re-set API "If the user wishes to change their mind about the screens allowed to be captured, the user should use the new Robot#resetScreenCapturePermission method" Well the "user" is hosed unless the app calls this every time ... or provides UI for it .. I want to work through the scenarios and how much of it is specific to the behaviours of the API you are using and so forth. And we presumably can store alongside the token, info about the screens attached at the time we obtained the token ? Can you point (here in the implementation review) to the exact native functions that popup up the dialog ? I can't imagine why we would need to reset "we have all permissions" to "we have no permissions" so I'm supposing The first time we need a permission for the screencast API there is a dialog. Next time, what happens if for some reaon the token is no longer valid ? If the user denied screen 2, or added screen 2 later, would that not result in an invalid token and we'd know |
Right now it can be read with a simple call: Moving to Robot doesn't change anything in this regard. So anyone can delete it.
We can try to do that and switch it depending on the area requested, but I tried to keep it simple.
(It is collapsed by the github in the review, so links are to the branch) basically if you didn't provide a correct token or no token at all to SelectSources call it will prompt with dialog right after the Start call
It is not for the "we have all permissions" case, it is for the case when we only have a permission to some screens. So it won't hurt.
No we don't. We receive the response callbackScreenCastStart as a separate streams for each allowed display. Based on this response we building a list of available displays. At this point we can compare this result with the screens got from X11 API(won't be compatible with Wayland, where we also want to reuse it later) or with Wayland(+1 dependency at this point).
The DENY case propagated to java and we are throwing the security exception.
It just prompts a permission request again.
Adding or removing a display for example.
Let me elaborate how this We may or may not provide the If the token provided is not valid (we have no control over this, it's checked on the system side), the permission prompt will appear. Later, after granting permissions, the When we are calling the So whenever we don't provide a valid token, we get a confirmation prompt. |
I was thinking it better to make it an API class, but we can discuss later when we are sure we want the API at all.
If it can avoid the need for dialogs in some additional cases, its worth it, but
But how does the app know when to call it ?
OK, so to make sure I understand, you use the token to see if you can obtain a stream for all connected displays. If any "fail" you know the user didn't approve that. So we can do this every time right ? And even if the screens have changed if we already have the token we need for THIS screen capture we know whether we already have the permission we need. And if we do then we just use it and don't cause a user re-prompt. At this point I think we've done everything that "revoke" was needed for. Then [if I have everything right] the only open question is whether when BTW "the app" means "any app that can find this token, doesn't it" ?
I think most people reading the API would interpret it as "rm , not
|
Right now it doesn't know. If the user is not satisfied with the result, he can "reset" it. But it seems to be a weak position.
The problem is how to relate the tokens to the screens. We don't have some universal id that says that the display we saved earlier is this current display. There is an opaque id of stream:
But it didn't help much, as it local to the session(each token - different session), token looks like We can match the stream layout to the screen layout in the system, but it has its flaws: Let's say we have following layout of screens in system, displays have the same resolution(e.g. 1920*1080): Screen capture permission is granted for
Yes, it is currently shared between Java applications for the same user.
Currently we only overwrite tokens when we get a new one from the system. In the full DENY case, we do not receive any token. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm getting pretty consistent testing results now - meaning the ones that have known
issues are the ones that fail, and I've looked at the code sufficiently.
The restore token seems to be behaving properly now.
I did have one case I've not understood where a test system got very confused about the display resolution of the monitor and needed a full power cycle.
I don't know if a specific test caused that or it was something else, but it isn't repeatable (at least not yet) so shouldn't block integration.
I am quite sure that SOME of the test failures are unstable tests that we've been lucky with all these years and xwayland is now showing up the instability. Things un-related to robot and more about timing for the most part. Although there's one test that seriously over-stresses robot with > 10,000 calls in a loop to getPixelColor() I'll see if I can push test update fixes to at least some of these to reduce noise.
But I'm OK to approve now.
@azvegint This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 245 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java
Outdated
Show resolved
Hide resolved
src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java
Outdated
Show resolved
Hide resolved
src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java
Outdated
Show resolved
Hide resolved
I spoke too soon :-( import java.awt.Frame; public class FSW {
} % java FSW and the capture fails every time. And again with debugging turned on ScreencastWatcher: started cropTo:163 Unexpected stride / 4: 0 srcW: 1920 rebuildScreenData:134 #---------------------# callbackScreenCastStart:617 rebuildScreenData result |0| connectStream:373 @@@ using screen 0 startStream:355 screenId#35: stream connecting 0x7f04548e3600 onStreamProcess:272 screenId#35[loc(0,0) size(1920x1200)] got a frame of size 0 offset 0 stride 0 flags 0 FD 31 captureDataReady 0 onStreamParamChanged:197 screenId#35[loc(0,0) size(1920x1200)] param event id 4 |
src/java.desktop/unix/classes/sun/awt/screencast/TokenItem.java
Outdated
Show resolved
Hide resolved
Regarding the failed capture
I've confirmed that on my system we DO enter FSEM using Xrandr So something about this code, or pipewire or XWayland isn't handling this properly. I found this but it was supposed to be fixed a while ago .. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, seems to be working reasonably well on Ubuntu 20 and 22.
….java in headless environment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly alignment issue with couple of code comments..
AccessController.doPrivileged( | ||
new GetPropertyAction("awt.robot.gtk", "true") | ||
)); | ||
AccessController.doPrivileged( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code formatting..better to align AccessController
below Boolean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved a little, but with indent.
Compared to strictly under the Boolean, for my taste it is easier to quickly parse with the eyes, and it does not visually merge.
"awt.robot.screenshotMethod", | ||
isOnWayland | ||
? METHOD_SCREENCAST | ||
: METHOD_X11 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
code alignment issue...will look better if all starts in same column
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java
Outdated
Show resolved
Hide resolved
src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java
Outdated
Show resolved
Hide resolved
src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c
Outdated
Show resolved
Hide resolved
|
||
if ((*env)->ExceptionCheck(env)) { | ||
(*env)->ExceptionDescribe(env); | ||
(*env)->ExceptionClear(env); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, these checks are not needed here at all, this is an oversight, there was a Java function call before.
The check after ReleaseIntArrayElements is also unnecessary.
/integrate |
Going to push as commit 9d7bf53.
Your commit was automatically rebased without conflicts. |
Modern Linux systems often come with Wayland by default.
This comes with some difficulties, and one of them is the inability to get screenshots from the system.
This is because we now use the X Window System API to capture screenshots and it cannot access data outside the XWayland server
But this functionality is a very important part of automated testing.
At the moment there are two obvious solutions to this problem, and both use xdg-desktop-portal:
It has several drawbacks though:
Since we would like Ubuntu 22.04 LTS which comes with Gnome 42 this option is not acceptable for us because it would require user confirmation for each screenshot.
But we still can consider this option as a fallback.
It typically used by applications that need to capture the contents of the user's screen or a specific window for the purpose of sharing, recording, or streaming.
This might be a bit of overkill, but it avoids several of the problems mentioned in the Screenshot API.
restore_token
So this PR adds the ability to take screenshots using the ScreenCast API. This functionality is currently disabled by default.
This change also introduces some new behavior for the robot:
A system window now appears asking for confirmation from the user to capture the screen.
Robot#resetScreenCapturePermission
methodAlso added several system properties:
awt.robot.screencastEnabled
false by default, uses the ScreenCast API if trueawt.robot.screencastDebug
false by default, prints some debug information if trueFor convenience, this change is divided into two commits:
Changes in the documentation are in a separate draft PR (#13809) also for convenience.
At the moment, the planned supported systems are Ubuntu 22.04+, the latest Oracle Linux 9.1 and RHEL 9.1 has some issues with restore_token support.
Progress
Issues
"3"
)Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/13803/head:pull/13803
$ git checkout pull/13803
Update a local copy of the PR:
$ git checkout pull/13803
$ git pull https://git.openjdk.org/jdk.git pull/13803/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 13803
View PR using the GUI difftool:
$ git pr show -t 13803
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/13803.diff
Webrev
Link to Webrev Comment