#!/usr/bin/python # This file is /etc/pm/sleep.d/00compiz-fglrx import os import sys import time import cPickle as pickle import tempfile import re from math import ceil from subprocess import Popen, PIPE, check_call, CalledProcessError # get the stdout from executing the command made up of elements of cmd_args def backquote(cmd_args): if type(cmd_args) is str: cmd_args = cmd_args.split() process = Popen(cmd_args, stdout=PIPE) out = process.communicate()[0].rstrip() if process.returncode != 0: raise CalledProcessError(process.returncode, cmd_args) return out def psinfo(pid, field): return backquote(('ps --no-headers -ww -o %s %s' % (field, pid)).split()) def proc(pid, part): return open('/proc/%s/%s' % (pid,part)).read().rstrip('\0').split('\0') def psenviron(pid): return dict( x.split('=',1) for x in proc(pid,'environ') if '=' in x) # Change the following to something like # # LOG_FILE_NAME='/home/dave/suspend.log' # # in order to see some debug output LOG_FILE_NAME=None logfile=None def log(*args): if not LOG_FILE_NAME: return global logfile if not logfile: logfile = open(LOG_FILE_NAME,'a') logfile.write(' '.join([str(a) for a in args]) + '\n') logfile.flush() class TimeOutError(Exception): pass def try_kill(pid, signal, timeout = 1.0, step = .01): os.kill(pid, signal) for x in range(int(ceil(timeout/step))): if not os.path.exists('/proc/%d' % pid): return time.sleep(step) raise TimeOutError, 'failed to kill process: %d in %d second(s)' % (pid,timeout) number = re.compile(r'\d*$') stat_fields = re.compile( r'\s+'.join([ r'(?P\d+)', r'\((?P.+)\)', r'(?P[RSDZTW])', r'(?P\d+)' ])) class NotCompiz(Exception): pass def compiz_real_info(pid): stat = stat_fields.match(open('/proc/%d/stat' % pid).read()) if stat.group('comm') != 'compiz.real': raise NotCompiz, 'continue' # we'll just move on to the next process ppid = int(stat.group('ppid')) return ppid, (psenviron(ppid), proc(ppid, 'cmdline'), psinfo(ppid,'user')) # Given recorded information about the parent processes of killed # compiz.real processes, "un-kill" them def restart_compiz(compiz_info): if compiz_info: log('restarting compiz') for pid, (env,cmd,user) in compiz_info.items(): log('user:', user, 'command:', cmd) # Ensure we have the necessary environment to re-launch the parent process if 'DISPLAY' in env and 'XAUTHORITY' in env and os.path.isfile(env['XAUTHORITY']): # If the parent didn't die when compiz.real was killed, kill the parent now try: try_kill(pid,15) except: pass try: check_call(['sudo', '-E', '-b', '-u', user] + cmd, env=env) except Exception, e: log('failed to relaunch') log(traceback.format_exc()) print >>sys.stderr, traceback.format_exc() else: log('required environment missing', env) def suspend(): # locate all parent processes of compiz.real commands all_pids = [int(f) for f in os.listdir('/proc') if number.match(f)] compiz_info = {} try: # Gather up all the info about the processes that launch compiz.real for pid in all_pids: try: ppid, info = compiz_real_info(pid) except: continue # Skip if not compiz.real or if already dead try_kill(pid, 15) # Kill off the compiz.real process compiz_info[ppid] = info # Remember how to restart its parent # write out everything we need to restart the compiz # processes and restore the current virtual terminal fd, pickle_file_name = tempfile.mkstemp('.pck', 'compiz-fglrx', '/var/run') pickle.dump((backquote('fgconsole'), compiz_info), open(pickle_file_name, 'w'),2) log('pickle saved in', pickle_file_name) # Keep a record of the name of the pickle file using # facilities from pm-utils. Using repr below ensures that # the filename is quoted in a way that should be # appropriate for the shell check_call([ 'sh', '-c', '. ${PM_FUNCTIONS} && savestate compiz-fglrx ' + repr(pickle_file_name)]) except: log('failure to suspend!') restart_compiz(compiz_info) raise # Now switch the virtual terminal as would have been done by the # 00clear hook. If done with compiz running, this kills resume log('switching to vt 63') check_call(['chvt', '63']) def resume(): # Get all the information we need to re-start pickle_file_name = backquote([ 'sh', '-c', '. ${PM_FUNCTIONS} && restorestate compiz-fglrx']) saved_console,compiz_info = pickle.load(open(pickle_file_name)) restart_compiz(compiz_info) # This is the functionality from the 00clear hook log('switching back to vt', saved_console) check_call(['chvt', str(saved_console)]) check_call(['deallocvt', '63']) # Try to make sure the screen wakes up and actually shows us the # password dialog. Without this we might need to move the mouse # to see it. Doesn't work, though; probably I need more # environment setup in order to be able to do this. # check_call(['xset','dpms','force','on']) # if we can't get rid of this file for any reason, it's not a # serious problem, so do it last. os.unlink(pickle_file_name) # The check for an argument in the next line is a convenience for # development, so we can import this file and not have it try to do # anything. if __name__ == '__main__' and len(sys.argv) > 1: log('=====================', backquote('date'), '==================') log('in ', ' '.join(sys.argv)) try: if sys.argv[1] in ('suspend','hibernate'): suspend() elif sys.argv[1] in ('resume','thaw'): resume() elif sys.argv[1] == 'help': # Nothing to say pass else: raise AssertionError, 'unknown argument %s' % sys.argv[1] except Exception, e: import traceback log(sys.argv[0]+':', traceback.format_exc()) # if anything failed, try to inhibit suspension. if sys.argv[1] in ('suspend','hibernate'): inhibit_file = os.environ.get('INHIBIT') if inhibit_file: log('inhibiting with', inhibit_file) open(inhibit_file,'a') raise