#!/usr/bin/env python import sys, os import argparse, json import random, re, subprocess, shutil, time import datetime """ " CLI """ parser = argparse.ArgumentParser( prog='wallpaper', description='Cycle through wallpapers' ) parser.add_argument( '--config', required=True, help='{"": { "find": "", "exclude": "" }, "":...}' ) parser.add_argument( '--interval', default=600 ) parser.add_argument( '--unique', default=False ) parser.add_argument( '--consider_daylight', default=False ) parser.add_argument( '--brightness_threshold', default=0.35 ) parser.add_argument( '--window_manager', default='sway' ) args = parser.parse_args() config = json.loads(args.config) if args.window_manager == 'sway': if shutil.which('swaybg') is None: sys.exit('swaybg not found') elif args.window_manager == 'i3': if shutil.which('feh') is None: sys.exit('feh not found') if args.consider_daylight and shutil.which('sunwait') is None: sys.exit('sunwait not found') if args.consider_daylight and shutil.which('identify') is None: sys.exit('identify not found') """ " Program """ # set variables for --unique flag already_selected_file_paths = {} if args.unique: for weight, config_item in config.items(): config[weight]['id'] = hash(json.dumps(config_item, sort_keys=True)) already_selected_file_paths[config_item['id']] = [] # set variables for --consider_daylight flag location = {} if args.consider_daylight: location = { 'latitude': float(subprocess.run(['pass', 'latitude'], capture_output=True, text=True).stdout), 'longitude': float(subprocess.run(['pass', 'longitude'], capture_output=True, text=True).stdout), } # run the loop running_bg_process = None while True: probability_number = random.randrange(start=1, stop=100, step=1) # select config to use based on args.config # see https://stackoverflow.com/questions/16489449/select-element-from-array-with-probability-proportional-to-its-value/16490300#16490300 selected_config = {} accumulative = 0 for weight, config_item in config.items(): lower_bound = accumulative accumulative += int(weight) upper_bound = accumulative if lower_bound <= probability_number and probability_number <= upper_bound: selected_config = config_item break # build list of file paths to choose from found_file_paths = [] for dirpath, dirnames, filenames in os.walk(os.path.expandvars(selected_config['find'])): for filename in filenames: found_file_paths.append(f'{dirpath}/{filename}') if 'exclude' in selected_config.keys(): found_file_paths = list(filter( lambda file_path: (not re.match(os.path.expandvars(selected_config['exclude']), file_path)), found_file_paths )) if args.unique: # filter file paths from already selected paths filtered_file_paths = [ item for item in found_file_paths if item not in already_selected_file_paths[selected_config['id']] ] # if we cycled through all of them, repeat if not filtered_file_paths: filtered_file_paths = found_file_paths already_selected_file_paths[selected_config['id']] = [] found_file_paths = filtered_file_paths # select file selected_file_path = random.choice(found_file_paths) if args.consider_daylight: daytime_process = subprocess.run(['sunwait', 'poll', '{0}N'.format(location['latitude']), '{0}E'.format(location['longitude'])], capture_output=True, text=True) daytime = daytime_process.stdout.strip() if daytime == 'NIGHT': tries = 0 while True: selected_file_path = random.choice(found_file_paths) identify_process = subprocess.run(['identify', '-format', '%[fx:mean]', selected_file_path], capture_output=True, text=True) brightness = float(identify_process.stdout) if brightness < float(args.brightness_threshold): # non-bright image break else: # remove identified bright image from possible selections found_file_paths.remove(selected_file_path) if args.unique: already_selected_file_paths[selected_config['id']].append(selected_file_path) # can't find suitable image tries += 1 if tries >= len(found_file_paths) - 1: if args.unique: already_selected_file_paths[selected_config['id']] = [] break if args.unique: already_selected_file_paths[selected_config['id']].append(selected_file_path) # run bg command if args.window_manager == 'sway': bg_cmd = ['swaybg', '-i', selected_file_path, '-m', 'fill'] elif args.window_manager == 'i3': bg_cmd = ['feh', '--bg-fill', selected_file_path] replacing_process = subprocess.Popen(bg_cmd) # wait until new instance of swaybg has booted up properly time.sleep(5) # kill the previous process # the new swaybg process takes its place automatically if running_bg_process: running_bg_process.terminate() # set running process for next iteration running_bg_process = replacing_process # wait $INTERVAL seconds time.sleep(int(args.interval))