13
parser = argparse.ArgumentParser()
14
parser.add_argument('--keyword', default='',
15
help=('A keyword to distinguish the screenshots '
16
'taken in this run of the script'))
17
parser.add_argument('--screenshot-dir',
18
default=os.environ['HOME'],
19
help=('Specify a directory to store screenshots in. '
20
'Default is %(default)s'))
21
args = parser.parse_args()
24
device_context = '' # track what device's modes we are looking at
25
modes = [] # keep track of all the devices and modes discovered
26
current_modes = [] # remember the user's current settings for cleanup later
27
failures = 0 # count the number of failed modesets
28
failure_messages = [] # remember which modes failed
29
success_messages = [] # remember which modes succeeded
31
# Run xrandr and ask it what devices and modes are supported
32
xrandrinfo = subprocess.Popen('xrandr -q', shell=True, stdout=subprocess.PIPE)
33
output = xrandrinfo.communicate()[0].decode().split('\n')
36
# The results from xrandr are given in terms of the available display devices.
37
# One device can have zero or more associated modes. Unfortunately xrandr
38
# indicates this through indentation and is kinda wordy, so we have to keep
39
# track of the context we see mode names in as we parse the results.
42
# I haven't seen any blank lines in xrandr's output in my tests, but meh
46
# luckily the various data from xrandr are separated by whitespace...
49
# Check to see if the second word in the line indicates a new context
50
# -- if so, keep track of the context of the device we're seeing
51
if len(foo) >= 2: # throw out any weirdly formatted lines
52
if foo[1] == 'disconnected':
53
# we have a new context, but it should be ignored
55
if foo[1] == 'connected':
56
# we have a new context that we want to test
57
device_context = foo[0]
58
elif device_context != '': # we've previously seen a 'connected' dev
59
# mode names seem to always be of the format [horiz]x[vert]
60
# (there can be non-mode information inside of a device context!)
61
if foo[0].find('x') != -1:
62
modes.append((device_context, foo[0]))
63
# we also want to remember what the current mode is, which xrandr
64
# marks with a '*' character, so we can set things back the way
65
# we found them at the end:
66
if foo[1].find('*') != -1:
67
current_modes.append((device_context, foo[0]))
69
# Now we have a list of the modes we need to test. So let's do just that.
70
profile_path = os.environ['HOME'] + '/.shutter/profiles/'
71
screenshot_path = os.path.join(args.screenshot_dir, 'xrandr_screens')
73
# Where to find the shutter.xml template? Two possible locations.
74
shutter_xml_template = None
76
if 'PLAINBOX_PROVIDER_DATA' in os.environ:
77
shutter_xml_template = os.path.join(os.environ['PLAINBOX_PROVIDER_DATA'],
78
"settings", "shutter.xml")
80
shutter_xml_template = os.path.join(os.path.split(os.path.dirname(
81
os.path.realpath(__file__)))[0],
87
screenshot_path = screenshot_path + '_' + args.keyword
89
regex = re.compile(r'filename="[^"\r\n]*"')
91
# Keep the shutter profile in place before starting
93
# Any errors creating the directories or copying the template is fatal,
94
# since things won't work if we fail.
96
os.makedirs(profile_path, exist_ok=True)
97
os.makedirs(screenshot_path, exist_ok=True)
98
except OSError as excp:
99
raise SystemExit("ERROR: Unable to create "
100
"required directories: {}".format(excp))
103
shutil.copy(shutter_xml_template, profile_path)
104
except (IOError, OSError) as excp:
105
print("ERROR: Unable to copy {} to {}: {}".format(shutter_xml_template,
108
if excp.errno == errno.ENOENT:
109
print("Try setting PLAINBOX_PROVIDER_DATA to the the data path of a")
110
print("provider shipping the 'shutter.xml' template file, usually ")
111
print("found under /usr/share.")
115
old_profile = open(profile_path + 'shutter.xml', 'r')
116
content = old_profile.read()
117
new_profile = open(profile_path + 'shutter.xml', 'w')
118
# Replace the folder name with the desired one
119
new_profile.write(re.sub(r'folder="[^"\r\n]*"',
120
'folder="%s"' % screenshot_path, content))
124
raise SystemExit("ERROR: While updating folder name "
125
"in shutter profile: {}".format(sys.exc_info()))
128
cmd = 'xrandr --output ' + mode[0] + ' --mode ' + mode[1]
129
retval = subprocess.call(cmd, shell=True)
131
failures = failures + 1
132
message = 'Failed to set mode ' + mode[1] + ' for output ' + mode[0]
133
failure_messages.append(message)
135
# Update shutter profile to save the image as the right name
136
mode_string = mode[0] + '_' + mode[1]
139
old_profile = open(profile_path + 'shutter.xml', 'r')
140
content = old_profile.read()
141
new_profile = open(profile_path + 'shutter.xml', 'w')
142
new_profile.write(regex.sub('filename="%s"' % mode_string,
147
shuttercmd = ['shutter', '--profile=shutter', '--full', '-e']
148
retval = subprocess.call(shuttercmd, shell=False)
151
print("""Could not capture screenshot -
152
you may need to install the package 'shutter'.""")
155
print("""Could not configure screenshot tool -
156
you may need to install the package 'shutter',
157
or check that {}/{} exists and is writable.""".format(
161
message = 'Set mode ' + mode[1] + ' for output ' + mode[0]
162
success_messages.append(message)
163
time.sleep(3) # let the hardware recover a bit
165
# Put things back the way we found them
167
for mode in current_modes:
168
cmd = 'xrandr --output ' + mode[0] + ' --mode ' + mode[1]
169
subprocess.call(cmd, shell=True)
171
# Tar up the screenshots for uploading
173
with tarfile.open(screenshot_path + '.tgz', 'w:gz') as screen_tar:
174
for screen in os.listdir(screenshot_path):
175
screen_tar.add(screenshot_path + '/' + screen, screen)
179
# Output some fun facts and knock off for the day
181
for message in failure_messages:
182
print(message, file=sys.stderr)
184
for message in success_messages: