Skip to content
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

Windows: capture cursor #272

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Windows: capture cursor #272

wants to merge 5 commits into from

Conversation

toxicrecker
Copy link

@toxicrecker toxicrecker commented Jan 25, 2024

Changes proposed in this PR

  • Capture cursor implemented for windows

It is very important to keep up to date tests and documentation.

  • Tests added/updated
  • Documentation updated

Is your code right?

  • PEP8 compliant
  • flake8 passed

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Type: Enhancement

PR Summary: The pull request introduces functionality to capture the cursor on Windows systems. It adds new structures to hold cursor and icon information, updates function mappings for cursor-related API calls, and implements methods to capture cursor data and handle monochrome cursors.

Decision: Comment

📝 Type: 'Enhancement' - not supported yet.
  • Sourcery currently only approves 'Typo fix' PRs.
✅ Issue addressed: this change correctly addresses the issue or implements the desired feature.
No details provided.
✅ Small diff: the diff is small enough to approve with confidence.
No details provided.

General suggestions:

  • Ensure that the new cursor capture functionality is robustly integrated with the existing codebase, including proper error handling and validation of API call results.
  • Clarify the necessity of casting 'memdc' to 'HDC' in the 'DrawIcon' call and document the reasoning within the code to aid future maintainability.
  • Consider the implications of the change in the return type of '_grab_impl' to 'Optional[ScreenShot]' and verify that all calling code is updated to handle potential 'None' values.

Thanks for using Sourcery. We offer it for free for open source projects and would be very grateful if you could help us grow. If you like it, would you consider sharing Sourcery on your favourite social media? ✨

Share Sourcery

Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.

self._handles.ci = ci

iconinfo = ICONINFO() # 'ii' felt uncomfortable
self._handles.iconinfo = iconinfo
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (llm): The initialization of 'iconinfo' without checking the return value of 'GetIconInfo' could lead to issues if 'GetIconInfo' fails. It would be safer to check the return value before assuming that 'iconinfo' has been properly initialized.

self._handles.bmp = gdi.CreateCompatibleBitmap(srcdc, width, height)
gdi.SelectObject(memdc, self._handles.bmp)

user32.DrawIcon(HDC(memdc), 0, 0, hcursor) # Why HDC? [1]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (llm): The comment '[1]' questions the need for casting 'memdc' to 'HDC'. It's important to clarify whether this cast is indeed necessary and to document the reason in the code to avoid confusion for future maintainers.

Copy link
Owner

@BoboTiG BoboTiG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great PR, thank you !

Would you mind adding tests like I did in https://github.com/BoboTiG/python-mss/blob/e66199adcdad737ef4d66183083bd26c827090da/src/tests/test_gnu_linux.py 🙏🏻 ?

@@ -121,6 +155,13 @@ def __init__(self, /, **kwargs: Any) -> None:
bmi.bmiHeader.biClrImportant = 0 # See grab.__doc__ [3]
self._handles.bmi = bmi

ci = CURSORINFO()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use more explicit name? cusor_info seems a good one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will do

ci.cbSize = ctypes.sizeof(CURSORINFO)
self._handles.ci = ci

iconinfo = ICONINFO() # 'ii' felt uncomfortable
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
iconinfo = ICONINFO() # 'ii' felt uncomfortable
icon_info = ICONINFO()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea i will fix that too

@@ -200,7 +241,7 @@ def _callback(monitor: int, data: HDC, rect: LPRECT, dc_: LPARAM) -> int:
callback = MONITORNUMPROC(_callback)
user32.EnumDisplayMonitors(0, 0, callback, 0)

def _grab_impl(self, monitor: Monitor, /) -> ScreenShot:
def _grab_impl(self, monitor: Monitor, /) -> Optional[ScreenShot]:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to not change that part. If the process fails, then an exception will be raised.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was changed for the linux implementation so i did it but i can see why you would like to avoid that

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cursors the alpha value of every pixel is 0 for some reason. Therefore, the alpha
value of every non black pixel has to be manually set to 255.
"""
srcdc, memdc = self._handles.srcdc, self._handles.memdc
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the documentation 💪🏻

For the implementation, do we need to copy all from the grab() method? Can it be simplified to only the cursor itself?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i did not care to look into it but now that you said it i will see if something can be done about simplifying it to just the cursor
i will update you once im done

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UPDATE: im afraid it would not be possible since the cursor is a part of the screen's device context, it should not be a huge issue as im making a small 32x32 bitmap and capturing only the cursor's image
im sorry if im wrong, im not an expert at windows api and i would love to learn something new

@toxicrecker
Copy link
Author

Great PR, thank you !

Would you mind adding tests like I did in https://github.com/BoboTiG/python-mss/blob/e66199adcdad737ef4d66183083bd26c827090da/src/tests/test_gnu_linux.py 🙏🏻 ?

I have absolutely no experience in writing tests, sorry
Best I could do is copy paste what you did with linux tests and remove the hasattr(sct, "xfixes") assertation, I will do that if you want

@toxicrecker
Copy link
Author

I was trying to make sense of how your linux tests worked, and I assume that you try taking screenshots of a black screen one with cursor and one without cursor and then check if you find an RGB value other than 0, am I right? And if I'm right how do you run this test? I can't make sense of how you are actually going to run this test in a way it does it's job because the screen is mostly never pitch black, do you have a second completely blank monitor that you use for this test? Sorry if I'm just being dumb I don't have any experience with testing.

@BoboTiG
Copy link
Owner

BoboTiG commented Jan 25, 2024

Windows tests will be more tricky, for sure.
I will be able to help on that side 👍🏻

@toxicrecker
Copy link
Author

Windows tests will be more tricky, for sure. I will be able to help on that side 👍🏻

Thank you, is there anything else I could help with?

@BoboTiG
Copy link
Owner

BoboTiG commented Jan 25, 2024

Thank you, is there anything else I could help with?

It's all good for now. I'll check the code, test it on a VM, and get back to the PR when I'll have some time.

@BoboTiG BoboTiG mentioned this pull request Jan 25, 2024
Comment on lines +348 to +349
"left": round(pos_screen.x * ratio - self._handles.icon_info.xHotspot),
"top": round(pos_screen.y * ratio - self._handles.icon_info.yHotspot),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I used this PR in my project and from my testing multiplying by ratio actually messes things up. When i removed it everything worked fine even on strange DPI scalings in multiple different positions of multi monitor setup. For testing I used https://github.com/pavlobu/deskreen with https://www.amyuni.com/forum/viewtopic.php?t=3030

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants