Skip to content

Commit

Permalink
Merge pull request #214 from thenaterhood/dev
Browse files Browse the repository at this point in the history
Next Release
  • Loading branch information
thenaterhood authored Dec 8, 2024
2 parents 7f1022a + 8dd8f8a commit d97194e
Show file tree
Hide file tree
Showing 26 changed files with 804 additions and 122 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ jobs:
sudo apt update
sudo apt install libgtk-3-dev libgtk-3-bin -y
python -m pip install -e .
cd src
coverage run --source gscreenshot -m pytest ../test
coverage run --source src/gscreenshot -m pytest test
coverage report -m
type-check:
Expand Down
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ gscreenshot - screenshot frontend (CLI and GUI) for a variety of screenshot back

# SYNOPSIS

gscreenshot [-cosnp] [-f FILENAME] [-d DELAY] [--help] [-V --version] [-g POINTER_GLYPH]
gscreenshot [-cosnpv] [-f FILENAME] [-d DELAY] [--help] [-V --version] [-g POINTER_GLYPH] [--select-color SELECT_COLOR] [--select-border-weight SELECT_BORDER_WEIGHT] [--gui]

gscreenshot-cli [-cosnp] [-f FILENAME] [-d DELAY] [--help] [-V --version] [-g POINTER_GLYPH]
gscreenshot-cli [-cosnpv] [-f FILENAME] [-d DELAY] [--help] [-V --version] [-g POINTER_GLYPH] [--select-color SELECT_COLOR] [--select-border-weight SELECT_BORDER_WEIGHT] [--gui]

# DESCRIPTION

Expand Down Expand Up @@ -78,7 +78,7 @@ Other than region selection, gscreenshot's CLI is non-interactive and is suitabl
\--gui
: Open the gscreenshot GUI. This is the default if no options are passed to gscreenshot.
This can be combined with other parameters to change, for example, the initial screenshot
taken when gscreenshot starts.s
taken when gscreenshot starts.

-c, \--clip
: Copy the resulting screenshot to the clipboard. Relies on xclip or wl-clipboard.
Expand All @@ -89,6 +89,14 @@ Other than region selection, gscreenshot's CLI is non-interactive and is suitabl
-s, \--selection
: Use a region selection utility to select a region of the screen to capture (if available).

--select-color
: RGB or RGBA hex value for the selection color box. Defaults to "#cccccc55". This can be used to
remove transparency from the selection box if a compositor is not in use. An empty string '' is
also accepted to use the underlying selection utility's defaults.

--select-border-weight
: Thickness of the border of the selection box. Defaults to 5.

-V, \--version
: Display the version, supported features, and additional relevant information.

Expand All @@ -104,6 +112,25 @@ Other than region selection, gscreenshot's CLI is non-interactive and is suitabl
: Use an alternate image when capturing the cursor. "adwaita", "prohibit", "allow" are built in, or pass
a file path to use a custom image.

-v
: Show more gscreenshot output

-vv
: Show all debugging output from gscreenshot

-vvv
: Show all debugging output from gscreenshot and supporting libraries (which provide logging)

# ENVIRONMENT

gscreenshot uses and respects the following environment variables:

GDK\_SCALE
: Scale factor of the display

QT\_SCALE\_FACTOR
: Scale factor of the display

# EXAMPLES

gscreenshot
Expand Down
41 changes: 39 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,54 @@ class LintCommand(Command):

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
command = ['pylint', 'src', f'--rcfile=pylintrc']
command = ['pylint', 'src', '--rcfile=pylintrc']
subprocess.check_call(command)


class TestCommand(Command):
description = 'run the tests'
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
command = ['coverage', 'run', '--source', 'src/gscreenshot', '-m', 'pytest', 'test']
subprocess.check_call(command)


class CoverageCommand(Command):
description = 'run the tests and produce a coverage report'
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
command = ['coverage', 'run', '--source', 'src/gscreenshot', '-m', 'pytest', 'test']
subprocess.check_call(command)
command = ['coverage', 'html']
subprocess.check_call(command)
command = ['xdg-open', 'htmlcov/index.html']
subprocess.check_call(command)


setup(name='gscreenshot',
cmdclass={
'lint': LintCommand,
'test': TestCommand,
'coverage': CoverageCommand,
},
version=pkg_version,
description='Lightweight GTK frontend to scrot',
Expand Down
4 changes: 2 additions & 2 deletions specs/gscreenshot.spec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
%define name gscreenshot
%define version 3.7.0
%define unmangled_version 3.7.0
%define version 3.8.0
%define unmangled_version 3.8.0
%define release 1

Summary: A simple screenshot tool
Expand Down
82 changes: 68 additions & 14 deletions src/gscreenshot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io
import json
import locale
import logging
import os
import platform
import sys
Expand All @@ -28,7 +29,9 @@
from gscreenshot.screenshooter import Screenshooter, get_screenshooter
from gscreenshot.util import session_is_wayland


_ = gettext.gettext
log = logging.getLogger(__name__)


#pylint: disable=missing-class-docstring
Expand All @@ -41,7 +44,15 @@ class Gscreenshot(object):
Gscreenshot application
"""

__slots__ = ['screenshooter', 'cache', 'session', '_screenshots', '_stamps']
__slots__ = [
'screenshooter',
'cache',
'session',
'_screenshots',
'_stamps',
'_select_color',
'_select_border_weight',
]

screenshooter: Screenshooter
cache: typing.Dict[str, str]
Expand Down Expand Up @@ -75,6 +86,8 @@ def __init__(self, screenshooter=None):
self._screenshots = ScreenshotCollection()

self._stamps = {}
self._select_color = None
self._select_border_weight = None

self.cache = {"last_save_dir": os.path.expanduser("~")}
if os.path.isfile(self.get_cache_file()):
Expand All @@ -93,13 +106,27 @@ def get_capabilities(self) -> typing.Dict[str, str]:
'''
return self.screenshooter.get_capabilities_()

def set_select_color(self, select_color_rgba: str):
'''
Set the selection color for region selection
This accepts an RGBA hexadecimal string.
'''
log.debug("set select color to '%s'", select_color_rgba)
self._select_color = select_color_rgba

def set_select_border_weight(self, select_border_weight: int):
'''Set the border weight for region selection'''
log.debug("set select border weight to '%s'", select_border_weight)
self._select_border_weight = select_border_weight

def register_stamp_image(self, fname: str,
name: typing.Optional[str]=None
) -> typing.Optional[str]:
'''
Adds a new stamp image from a file path
'''
if not os.path.exists(fname):
log.info("cursor glyph path '%s' does not exist", fname)
return None

glyph = Image.open(fname).convert("RGBA")
Expand All @@ -116,6 +143,7 @@ def register_stamp_image(self, fname: str,
name = f"{name[0:8]}..."

self._stamps[name] = glyph
log.debug("added cursor path = '%s', name = '%s'", fname, name)

return name

Expand Down Expand Up @@ -162,6 +190,7 @@ def get_cursor_by_name(self, name: typing.Optional[str]):
if name and name in cursors.keys():
return cursors[name]

log.info("cursor glyph name = '%s' does not exist, using default", name)
return cursors[default]

def show_screenshot_notification(self) -> bool:
Expand All @@ -181,7 +210,8 @@ def show_screenshot_notification(self) -> bool:
'gscreenshot'
], check=True, timeout=2)
return True
except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired):
except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc:
log.info("notify-send failed: %s", exc)
return False

def run_display_mismatch_warning(self):
Expand Down Expand Up @@ -214,8 +244,9 @@ def save_cache(self):
try:
with open(self.get_cache_file(), "w", encoding="UTF-8") as cachefile:
json.dump(self.cache, cachefile)
log.debug("wrote cache file '%s'", self.get_cache_file())
except FileNotFoundError:
print(_("unable to save cache file"))
log.warning(_("unable to save cache file - file not found"))

def get_screenshooter_name(self) -> str:
"""Gets the name of the current screenshooter"""
Expand Down Expand Up @@ -288,7 +319,9 @@ def screenshot_selected(self, delay: int=0, capture_cursor: bool=False,
delay,
capture_cursor,
use_cursor=use_cursor,
region=region
region=region,
select_color_rgba=self._select_color,
select_border_weight=self._select_border_weight,
)

if self.screenshooter.screenshot is not None:
Expand Down Expand Up @@ -325,7 +358,9 @@ def screenshot_window(self, delay: int=0, capture_cursor: bool=False,
self.screenshooter.grab_window_(
delay,
capture_cursor,
use_cursor=use_cursor
use_cursor=use_cursor,
select_color_rgba=self._select_color,
select_border_weight=self._select_border_weight,
)

if self.screenshooter.screenshot is not None:
Expand Down Expand Up @@ -412,8 +447,13 @@ def interpolate_filename(self, filename:str) -> str:
run through strftime.
'''
if "$" not in filename and "%" not in filename:
log.debug(
"filename '%s' did not contain templating, not interpolating", filename
)
return filename

interpolated = f"{filename}"

general_replacements:typing.Dict[str, str] = {
'$$': '$',
'$a': platform.node()
Expand All @@ -428,12 +468,18 @@ def interpolate_filename(self, filename:str) -> str:
})

for fmt, replacement in general_replacements.items():
filename = filename.replace(fmt, replacement)
interpolated = interpolated.replace(fmt, replacement)

now = datetime.now()
filename = now.strftime(filename)
interpolated = now.strftime(interpolated)

return filename
log.debug(
"interpolated filename - received template '%s', converted to '%s'",
filename,
interpolated
)

return interpolated

def get_time_foldername(self) -> str:
'''Generates a time-based folder name'''
Expand Down Expand Up @@ -487,12 +533,13 @@ def _save_image(self, image: Image.Image, filename: typing.Optional[str]=None,
# there with a time-based filename.
try:
os.makedirs(filename)
except (IOError, OSError):
log.debug("created directory tree for '%s'", filename)
except (IOError, OSError) as exc:
# Likely the directory already exists, so
# we'll throw the exception away.
# If we fail to save, we'll return a status
# saying so, so we'll be okay.
pass
log.info("failed to create tree for '%s': %s", filename, exc)

filename = os.path.join(
filename,
Expand All @@ -513,6 +560,7 @@ def _save_image(self, image: Image.Image, filename: typing.Optional[str]=None,
file_type = 'jpeg'

if file_type not in self.get_supported_formats():
log.info("unrecognized image format '%s'", file_type)
return None

try:
Expand All @@ -535,7 +583,8 @@ def _save_image(self, image: Image.Image, filename: typing.Optional[str]=None,
with open(filename, "wb") as file_pointer:
image.save(file_pointer, file_type.upper(), exif=exif_data)

except IOError:
except IOError as exc:
log.info("failed to save screenshot: %s", exc)
filename = None

screenshot = self._screenshots.cursor_current()
Expand All @@ -555,7 +604,10 @@ def save_screenshot_collection(self, foldername: typing.Optional[str]=None) -> b
foldername = self.interpolate_filename(foldername)

if not os.path.exists(foldername):
os.makedirs(foldername)
try:
os.makedirs(foldername)
except (IOError, OSError) as exc:
log.info("failed to make tree '%s': %s", foldername, exc)

i = 0
for screenshot in self._screenshots:
Expand Down Expand Up @@ -610,7 +662,8 @@ def open_last_screenshot(self) -> bool:
try:
subprocess.run(['xdg-open', screenshot_fname], check=True)
return True
except (subprocess.CalledProcessError, IOError, OSError):
except (subprocess.CalledProcessError, IOError, OSError) as exc:
log.warning("failed to open screenshot with xdg-open: %s", exc)
return False

def copy_last_screenshot_to_clipboard(self) -> bool:
Expand Down Expand Up @@ -657,8 +710,9 @@ def copy_last_screenshot_to_clipboard(self) -> bool:

xclip.communicate(input=png_data.getvalue())
return True
except (OSError, subprocess.CalledProcessError):
except (OSError, subprocess.CalledProcessError) as exc:
#pylint: disable=raise-missing-from
log.warning("failed to clip screenshot with clipper = '%s': %s", clipper_name, exc)
raise GscreenshotClipboardException(clipper_name)

def get_last_save_directory(self) -> str:
Expand Down
Loading

0 comments on commit d97194e

Please sign in to comment.