164 lines
5.9 KiB
Python
Executable file
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()
|