Skip to content

Conversation

@Mambouna
Copy link

@Mambouna Mambouna commented Apr 1, 2025

Creates new functionality in storage.py to take screenshots.

  • Also rearranges some former Storage() functions to make working with multiple classes that need to work with files more ergonomic.
  • Fixed a bug in storage.py that would prevent saving manually from a game script.
  • Initialization of the screenshot instance under storage.py added to runner.py.
  • Added the function screen.screenshot() for manually controlled screenshots.
  • Added F12 as a default keybinding in game.py to take screenshots.

Fixes #290

@lordmauve lordmauve changed the title FIX #290 - Screenshot functionality Screenshot functionality Apr 1, 2025
@Mambouna
Copy link
Author

Mambouna commented Apr 6, 2025

Locally flake8 doesn't have any problems anymore and the documentation is updated. Want to give it another go?

@lordmauve
Copy link
Owner

There's a lot here, but I think it would be better if screenshots were saved to a standard "Pictures" directory for each platform, and I am hesitant about exposing screenshot() as an API and also giving it a default key binding. I think F12 should be sufficient.

I don't think this needs to be wired through the Storage classes, but what were you aiming to achieve with that?

@Mambouna
Copy link
Author

Mambouna commented Apr 7, 2025

We can also save to pictures directory of course. I used storage since it already had functionality useful for checking validity of directories and setting up proper paths per system. Since screenshots are files to be stored locally, I also thought it idiomatic to use the existing infrastructure and from my experience, games use different approaches. Some save screenshots to system picture folders, other do so in their own user data directories. Since there was no clear "standard" case, I went with this one.

EDIT: Also if there's more stuff in the future that has to deal with files, having reworked storage.py means that it's easier to integrate coming stuff too. But of course this would only be true if storage.py becomes sort of the central file where all file system stuff is done.

The systems can be separated of course, but then we'd likely still need to import or rewrite those utility functions as far as I can see. If you would prefer that though, I'm not opposed to it.

The user function being exposed was simply done to not restrict options. This way, if a user introduces a system of custom keybindings for example or just wants to give a different key as an easier to reach option where maybe you want to take a lot of screenshots, there's something provided already. I couldn't see any harm in introducing it, but again if you feel differently, I'm not inherently against removing it.

@lordmauve
Copy link
Owner

I think we do need to save to "user library" directories because "~/.config/pgzero" is not a place anyone will find a screenshot. We also need to print the path it was saved to.

@Mambouna
Copy link
Author

I think we do need to save to "user library" directories because "~/.config/pgzero" is not a place anyone will find a screenshot. We also need to print the path it was saved to.

You're correct. I've made the changes. Screenshots now saved in user pictures directory, a message is printed stating the file name and directory and I've updated the documentation to also state the default keybinding F12.

@lordmauve lordmauve added the enhancement New feature or request label Apr 14, 2025
@Mambouna Mambouna requested a review from lordmauve April 14, 2025 18:35
@Mambouna
Copy link
Author

Thanks for the review, I've implemented all your suggestions. I liked the idea of having a central spot for file system stuff at first, but you were right that this wasn't the time to start that by cobbling together individual parts.

Copy link
Owner

@lordmauve lordmauve left a comment

Choose a reason for hiding this comment

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

The implementation is pretty close, just a few style nits.

I'd like to have a few simple tests of the path computation and writing screenshots (maybe not to my real ~/Pictures, we can unittest.mock.patch() it to write to a TemporaryDirectory).

@Mambouna
Copy link
Author

Okay, I tried adding relevant tests for the screenshot functionality. This was my best effort, as I don't really know what I'm doing as soon as tests go beyond simple asserts.

If this is unsatisfactory, I'm not sure I can improve upon it without very explicit instruction. While I would appreciate that, it might be the case that it would be less work overall if you edited the PR yourself then.

@Mambouna
Copy link
Author

@lordmauve Any further feedback?

@lordmauve
Copy link
Owner

I think you got into a mess with merges and rebases as some of the commits on main were rebased onto your branch. I've interactively rebased to just keep your changes and force pushed. There's still a conflict, I'll try rebasing again.

@lordmauve
Copy link
Owner

Ok. Tests are passing but incorrectly - they fail on Windows. I guess we traditionally didn't do a whole lot of platform-specific stuff, mostly just relying on Pygame to handle portability for us.

I think the root errors are

Traceback (most recent call last):
  File "C:\Users\lordm\dev\pgzero\test\test_screen.py", line 272, in test_take_screenshot
    self.screen.screenshot()
    ~~~~~~~~~~~~~~~~~~~~~~^^
  File "C:\Users\lordm\dev\pgzero\src\pgzero\screen.py", line 241, in screenshot
    return screenshots.take(self.surface)
           ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  File "C:\Users\lordm\dev\pgzero\src\pgzero\screen.py", line 81, in take
    pygame.image.save(surface, filepath)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
pygame.error: Couldn't open C:\Users\lordm\AppData\Local\Temp\tmpj70qc5kqscreenshot_testdir\pgzero_screenshots\test_screen-2025-07-30_00:31:48.png

and

Traceback (most recent call last):
  File "C:\Users\lordm\AppData\Roaming\uv\python\cpython-3.13.3-windows-x86_64-none\Lib\unittest\case.py", line 58, in testPartExecutor
    yield
  File "C:\Users\lordm\AppData\Roaming\uv\python\cpython-3.13.3-windows-x86_64-none\Lib\unittest\case.py", line 651, in run
    self._callTestMethod(testMethod)
    ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "C:\Users\lordm\AppData\Roaming\uv\python\cpython-3.13.3-windows-x86_64-none\Lib\unittest\case.py", line 606, in _callTestMethod
    if method() is not None:
       ~~~~~~^^
  File "C:\Users\lordm\AppData\Roaming\uv\python\cpython-3.13.3-windows-x86_64-none\Lib\unittest\mock.py", line 1426, in patched
    return func(*newargs, **newkeywargs)
  File "C:\Users\lordm\AppData\Roaming\uv\python\cpython-3.13.3-windows-x86_64-none\Lib\unittest\mock.py", line 1891, in _inner
    return f(*args, **kw)
  File "C:\Users\lordm\dev\pgzero\test\test_screen.py", line 256, in test_get_screenshot_path_linux
    self.assertEqual(result_path,
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
                     os.path.join("/home/user", "Pictures", "pgzero"))
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\lordm\AppData\Roaming\uv\python\cpython-3.13.3-windows-x86_64-none\Lib\unittest\case.py", line 907, in assertEqual
    assertion_func(first, second, msg=msg)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\lordm\AppData\Roaming\uv\python\cpython-3.13.3-windows-x86_64-none\Lib\unittest\case.py", line 1273, in assertMultiLineEqual
    self.fail(self._formatMessage(msg, standardMsg))
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\lordm\AppData\Roaming\uv\python\cpython-3.13.3-windows-x86_64-none\Lib\unittest\case.py", line 732, in fail
    raise self.failureException(msg)
AssertionError: 'C:\\Users\\lordm\\Pictures\\pgzero' != '/home/user\\Pictures\\pgzero'
- C:\Users\lordm\Pictures\pgzero
+ /home/user\Pictures\pgzero

Mambouna added 8 commits July 30, 2025 00:57
Creates new functionality in `storage.py` to take screenshots.
- Also rearranges some former `Storage()` functions to make working with multiple classes that need to work with files more ergonomic.
- Fixed a bug in `storage.py` that would prevent saving manually from a
  game script.
- Initialization of the `screenshot` instance under `storage.py` added
  to `runner.py`.
- Added the function `screen.screenshot()` for manually controlled
  screenshots.
- Added F12 as a default keybinding in `game.py` to take screenshots.
Based on review by lordmauve, screenshots are now saved in the user home
directory under the subfolder `Pictures`. Because this negates some of
the former justification to make `storage.py` functionality more
generalized, these changes were reverted unless still required with this
change.
- Screenshots saved as `.png`.
- Timestamp printed in ISO format.
- All functionality decoupled from `storage.py` and moved to `screen.py`.
- Using `sys.platform` instead of `platform.sys`.
- Only recognized OSes use user folder for screenshot, otherwise warns
  and uses CWD.
- Using `exist_ok` on `os.makedirs()` instead of passing errors
  manually.
- Using F12 to take screenshots catches errors and prints a traceback.
- User defined screenshots propagate the error instead.
- Only prints info about screenshots when using F12, otherwise returns
  filepath to user.
All suggestions from review have been incorporated, except for writing
the tests.
All suggestions from review have been incorporated.
@Mambouna
Copy link
Author

@lordmauve Thanks for the heads-up. I'll take a look at it probably the day after tomorrow. I don't have a windows system myself but the error looks like the actual platform is being misidentified so if that's the case, the fix shouldn't be too complex.

@Mambouna
Copy link
Author

Mambouna commented Aug 1, 2025

@lordmauve Okay so turns out os.path itself uses sys.builtin_module_names to check the platform. I tried adding a patch for that as well to the tests. This still runs fine on my end, could you try if this fixes the second error on Windows?

I've also tried to fix the permission problem, but that might be more tricky. Permissions in Windows for temporary files and directories from Python seem to be pretty wonky and whether a file can be opened seems to depend on whether it was opened at least once already. I tried reading a bit into the Pygame code but I don't really know C, so I'm not sure if pygame.image.save() opens, closes and opens the file. If yes, that seems to be the most likely culprit to me as it fits with the knows permission issues with tempfile from python.

The easiest fix in that case would be to simply have the test write to the actual user directory where all the permission weirdness isn't a problem, but that seems really icky for a testing suite. What do you think?

@lordmauve
Copy link
Owner

The easiest fix in that case would be to simply have the test write to the actual user directory where all the permission weirdness isn't a problem, but that seems really icky for a testing suite. What do you think?

I agree, that's icky. Might be Ok in GitHub Actions though.

@lordmauve
Copy link
Owner

The tests are still failing on Windows :-(

@Mambouna
Copy link
Author

Mambouna commented Aug 2, 2025

Hi, thanks for trying again, shame the last change didn't help...

Here's a last attempt that I don't think it actually very good practise but I think it shouldn't produce side effects. Instead of patching the values that should inform which library python uses, I directly patch os.path itself with the given library.
Again, this runs fine for me and debugging suggests on Linux the switch of libraries occurs correctly (although that was also the case before). If this doesn't work, I'm not sure I have more ideas ...

Also the tests should still fail from the permission error, since I haven't found any solution for that yet. But if the other error is gone, that would be a good step forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Screenshot game

2 participants