diff options
Diffstat (limited to 'src/nightshift.py')
-rwxr-xr-x | src/nightshift.py | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/src/nightshift.py b/src/nightshift.py new file mode 100755 index 0000000..72aebbb --- /dev/null +++ b/src/nightshift.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +# -*- python -*- +copyright=''' +Copyright © 2014 Mattias Andrée (maandree@member.fsf.org) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +''' + +import os +import sys +import socket +import threading +from subprocess import Popen, PIPE + + +PROGRAM_NAME = 'nightshift' +''' +:str The name of the program +''' + +PROGRAM_VERSION = '1.0' +''' +:str The version of the program +''' + + +## Set process title +def setproctitle(title): + ''' + Set process title + + @param title:str The title of the process + ''' + import ctypes + try: + # Remove path, keep only the file, + # otherwise we get really bad effects, namely + # the name title is truncates by the number + # of slashes in the title. At least that is + # the observed behaviour when using procps-ng. + title = title.split('/')[-1] + # Create strng buffer with title + title = title.encode(sys.getdefaultencoding(), 'replace') + title = ctypes.create_string_buffer(title) + if 'linux' in sys.platform: + # Set process title on Linux + libc = ctypes.cdll.LoadLibrary('libc.so.6') + libc.prctl(15, ctypes.byref(title), 0, 0, 0) + elif 'bsd' in sys.platform: + # Set process title on at least FreeBSD + libc = ctypes.cdll.LoadLibrary('libc.so.7') + libc.setproctitle(ctypes.create_string_buffer(b'-%s'), title) + except: + pass +setproctitle(sys.argv[0]) + + +## Parse options +for arg in sys.argv[1:]: + if arg in ('-V', '--version'): + ## Print the version of nightshift and of redshift + print('%s %s' % (PROGRAM_NAME, PROGRAM_VERSION)) + Popen(['redshift', '-V'], stdout = sys.stdout).wait() + sys.exit(0) + elif arg in ('-C', '--copyright'): + ## Print copyright information + print(copyright[1 : -1]) + sys.exit(0) + elif arg in ('-W', '--warranty'): + ## Print warranty disclaimer + print(copyright.split('\n\n')[-2]) + sys.exit(0) + elif arg in ('-h', '--help'): + ## Display help message + text = '''USAGE: nightshift [OPTIONS...] [-- REDSHIFT-OPTIONS...] + + Terminal user interface for redshift, a program for setting the colour + temperature of the display according to the time of day. + + -h --help Display this help message + -V --version Show program version + -C --copyright Show program copyright information + -W --warranty Show program warrantly disclaimer + + -d --daemon Start as daemon + -x --reset Reset mode (remove adjustment from screen) + +x --toggle Temporarily disable or enable adjustments + -s --status Print status information + + -c --config FILE Load settings from specified configuration file + -b --brightness DAY:NIGHT Screen brightness to set at daytime/night + -b --brightness BRIGHTNESS Screen brightness to apply + -t --temperature DAY:NIGHT Colour temperature to set at daytime/night + -t --temperature TEMP Colour temperature to apply + -l --location LAT:LON Your current location + -l --location PROVIDER Select provider for automatic location updates + (Type `list' to see available providers) + -m --method METHOD Method to use to set colour temperature + (Type `list' to see available methods) + -r --no-transition Disable temperature transitions + + Please report bugs to <https://github.com/maandree/nightshift/issues> + ''' + text = text.split('\n')[:-1] + indent = min([len(line) - len(line.lstrip()) for line in text if line.rstrip().startswith(' ')]) + print('\n'.join([line[indent:] if line.startswith(' ') else line for line in text])) + sys.exit(0) + else: + ## Unrecognised option + sys.stderr.write('%s: error: unrecognised option: %s\n' % (sys.argv[0], arg)) + sys.exit(1) + + +socket_path = '%s.%s-%s' % ('/dev/shm/', PROGRAM_NAME, os.environ['USER']) +sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +try: + # Connect to the server + sock.connect(socket_path) +except: + # The process need separate sockets, lets close it + # and let both process recreate it + sock.close() + sock = None + +if sock is None: + ## Server is not running + # Create pipe for interprocess signal + (r_end, w_end) = os.pipe() + + # Duplicate process. + pid = os.fork() + + if pid == 0: + ## Daemon (child) + # Close stdin + os.close(sys.stdin.fileno()) + + # Create server socket + os.unlink(socket_path) + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(socket_path) + sock.listen(5) + + # Send signal + with os.fdopen(w_end, 'wb') as file: + file.write(b'\n') + file.flush() + + # Close the pipe + os.close(r_end) + + ## TODO (for testing) + import signal + while True: + signal.pause() + + # Close socket + sock.close() + # Close process + sys.exit(0) + else: + ## Front-end (parent) + # Wait for a signal + rc = None + with os.fdopen(r_end, 'rb') as file: + file.read(1) + + # Close the pipe + os.close(w_end) + + # Connect to the server + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + print('connecting') + sock.connect(socket_path) + print('connected') + + +try: + proc = Popen(['redshift', '-v'], stdout = PIPE, stderr = open(os.devnull)) + red_brightness, red_period, red_temperature, red_running = 1, 1, 6500, True + red_condition = threading.Condition() + + def read_status(): + global red_brightness, red_period, red_temperature, red_running + while True: + got = proc.stdout.readline() + if (got is None) or (len(got) == 0): + break + got = got.decode('utf-8', 'replace')[:-1] + (key, value) = got.split(': ') + red_condition.acquire() + try: + if key == 'Brightness': + red_brightness = float(value) + elif key == 'Period': + if value == 'Night': + red_period = 0 + elif value == 'Daytime': + red_period = 1 + else: + red_period = float(value.split(' ')[1][1 : -1]) / 100 + elif key == 'Color temperature': + red_temperature = float(value[:-1]) + except: + pass + red_condition.notify() + red_condition.release() + red_running = False + + thread = threading.Thread(target = read_status) + thread.setDaemon(True) + thread.start() + + while red_running: + red_condition.acquire() + red_condition.wait() + print('%f: %f, %f' % (red_period, red_temperature, red_brightness)) + red_condition.release() + +finally: + # Close socket + sock.close() + |