dot/outerheaven.init3.home/.config/sway/scripts/screenshot.py

164 lines
5.9 KiB
Python
Executable file

#!/usr/bin/env python3
"""
This script provides screenshot handling for Sway
Determines the mode (region or window), and then takes a capture
Expects window manager keybinds like below:
bindsym --no-repeat Print exec "~/.config/sway/scripts/screenshot.py region"
bindsym --no-repeat Shift+Print exec "~/.config/sway/scripts/screenshot.py window"
If mode is 'region', the tool 'slurp' is used to select the region to capture.
If mode is 'window', the WM is asked which display is active (to capture).
In both cases, the 'grim' utility does the image capture. Captures go here:
~/Pictures/screenshots/Screenshot_*.png
Fedora dependencies:
- sudo dnf in python-{i3ipc,pillow}
Note: while i3ipc aspects of this will work with i3...
slurp/grim likely will not
"""
import argparse
import os
import subprocess
from time import strftime
from i3ipc import Connection, Event
from PIL import Image
from PIL.PngImagePlugin import PngInfo
# setup argparse
# take one arg, m/mode: selection/window
parser = argparse.ArgumentParser(
description='Take some screenshots for Sway using IPC and slurp/grim')
# add main arg, screenshot mode -- region (selection area) or [focused] window
parser.add_argument('mode', type=str, choices=['region', 'window', 'w', 'r'],
help='''Screenshot "mode", either:
A selected area ('region') or
the focused display ('window')"''')
# instantiate args
args = parser.parse_args()
# env prep
# get the current time
now = strftime("%Y-%m-%dT-%H%M%z")
# use strftime - similar iso format as 'datetime', with 1 minor fix
# no ':' - in the off chance they are uploaded to Jira
# ex: 2022-11-21T-2337-0600
# set a var for the homedir using os.environ - arguably not portable? /shrug
homedir = os.environ['HOME']
screenshot_dir = f'{homedir}/Pictures/screenshots'
screenshot_path = f'{screenshot_dir}/Screenshot_{now}.png'
preview_command = f"imv -d -s none '{screenshot_path}'"
# preview_command = f"xdg-open '{screenshot_path}'"
if not os.path.isdir(screenshot_dir):
print(f"Screenshot dir doesn't exist, creating {screenshot_dir}")
os.mkdir(screenshot_dir)
else:
print(f'Screenshot dir ({screenshot_dir}) exists, continuing')
# misc functions
def determine_ss_cmd():
'''based on mode, determine the screenshot command'''
# screenshot command handling (based on mode)
# grim uses compression level 1 in both cases
# neglible quality difference while saving space
if args.mode in ['window', 'w']:
# use wm connection to determine the active output
outputs = _wm.get_outputs()
for output in outputs:
if output.focused:
active_output = output.name
print(f'determined active output: {active_output}')
command = f"grim -l 1 -c -o {active_output} '{screenshot_path}'"
elif args.mode in ['region', 'r']:
# omits -c to leave out cursors
command = f"slurp -d | grim -l 1 -g - '{screenshot_path}'"
return command
def preview_focus(_wm, _event):
'''function called by window manager new_window events
checks if new window is preview, if so: give it focus'''
if _event.container.app_id == 'imv':
# give the preview focus
# for Sway we use 'app_id', for i3 this is probably 'class'
_wm.command('[app_id=imv] focus')
# once the preview window is focused, close our connection to the wm
_wm.main_quit()
def wm_connect():
'''get the party started, create a connection to the window manager'''
conn = Connection(auto_reconnect=True)
# on new window events check if screenshot preview window gets focus
conn.on(Event.WINDOW_NEW, preview_focus)
return conn
def _run_command(command):
print(f'Command: {command}')
_r = subprocess.run(command, shell=True, capture_output=True, check=False)
if _r.stderr:
raise subprocess.CalledProcessError(
returncode = _r.returncode,
cmd = _r.args,
stderr = _r.stderr
)
if _r.stdout:
print(f"Command Result: {_r.stdout.decode('utf-8')}")
return _r
# begin screenshot/preview process
# connect to the window manager -- Sway
# (i3 could work, may need grim/slurp replacements)
_wm = wm_connect()
# determine screenshot command - differs if window or region mode
screenshot_command = determine_ss_cmd()
# run the screenshot/preview commands
# previewing/sending focus only if screenshot is taken
SS_RC = -1
try:
ss_result = _run_command(screenshot_command)
SS_RC = ss_result.returncode
except subprocess.CalledProcessError as error:
print('screenshot failed/aborted')
# clean up after ourselves, close the wm loop
_wm.main_quit()
# if the screenshot succeeded, place a comment on the image
# and then preview/focus the window
if SS_RC == 0:
#
# construct the comment for the screenshot
# immediately after it's taken, find the focused window details
wm_tree = _wm.get_tree()
wm_focused = wm_tree.find_focused()
app_name = wm_focused.name
app_id = wm_focused.app_id
COMMENT = f"Screenshot of '{app_name} (app_id: {app_id}) at {now}'"
print(f'storing comment: {COMMENT}')
#
# open the screenshot for (metadata) modification
# adding the application title/window class/date as a comment
# visible using 'exiftool', easier sorting through command line
ss_obj = Image.open(screenshot_path)
ss_metadata = PngInfo()
ss_metadata.add_text("Comment", COMMENT)
#
# write the (commented) image back out
ss_obj.save(screenshot_path, pnginfo=ss_metadata)
#
# open the preview with 'imv'
print(f"exec preview: {preview_command}")
_wm.command(f'exec {preview_command}')
#
# start the main loop for the window manager
# basically wait for the preview listener to fire
# when the preview window opens, a message is sent to give it focus
# afterwards we exit
_wm.main()