Skip to content

Instantly share code, notes, and snippets.

@neko-neko-nyan
Created May 18, 2022 09:06
Show Gist options
  • Save neko-neko-nyan/35d59d5c2cdbe3dea2548a541bb986b3 to your computer and use it in GitHub Desktop.
Save neko-neko-nyan/35d59d5c2cdbe3dea2548a541bb986b3 to your computer and use it in GitHub Desktop.
Desktop background changing daemon (for linux, using feh)
#!/usr/bin/python3
import json
import math
import os
import random
import subprocess
import sys
import threading
DEFAULT_BASEDIR = os.path.expanduser('~/Изображения/Saved backgrounds')
def sizes(cwd, imgs):
for line in subprocess.run(['feh', '-l', *imgs], cwd=cwd, stdout=subprocess.PIPE).stdout.decode().splitlines()[1:]:
i = line.split(maxsplit=7)
yield i[7], (int(i[2]), int(i[3]))
def index(directory, output):
files = {}
if os.path.isdir(directory):
f = os.listdir(directory)
end = len(f)
for i in range(0, end, 50):
print('\r{:>5} / {}'.format(i, end), end='')
files.update(dict(sizes(directory, f[i:i+50])))
else:
directory = os.path.split(directory)
files[directory[1]] = next(sizes(directory[0], [directory[1]]))[1]
directory = directory[0]
with open(output, 'w') as f:
json.dump({
'files': files,
'base_dir': directory
}, f)
def filter_sizes(inp, out, size):
size = size.split('@', 2)
limit = float(size[1])
size = size[0].split('x', 2)
q = float(size[0]) / float(size[1])
with open(inp) as f:
files = json.load(f)
files_out = {}
for path, (width, height) in files['files'].items():
if math.fabs(width/height - q) < limit:
files_out[path] = (width, height)
print(len(files_out), 'of', len(files['files']))
with open(out, 'w') as f:
json.dump({
'files': files_out,
'base_dir': files['base_dir']
}, f)
def save(file):
subprocess.Popen(['neko', 'gui', 'save-background', file])
class History:
def __init__(self):
self._data = []
self._curr = 0
def add(self, item):
self._data.append(item)
self._curr = len(self._data) - 1
def prev(self, c=2):
self._curr -= c
def next(self):
if len(self._data) - 1 == self._curr or not self._data:
return None
self._curr += 1
return self._data[self._curr]
def curr(self):
return self._data[self._curr]
def reset(self):
d = self._data
self._data = []
self._curr = 0
return d
class BackgroundChanger(threading.Thread):
def __init__(self, timeout=10.0, shuffle=True, scale_mode='max'):
super().__init__(name=self.__class__.__name__)
self.timeout = timeout
self.shuffle = shuffle
self.scale_mode = scale_mode
self.input = []
self.shown = History()
self.pause = False
self._waiting = threading.Condition(threading.Lock())
def run(self):
while True:
image = self._next_image()
self._show(image)
with self._waiting:
self._waiting.wait(None if self.pause else self.timeout)
@staticmethod
def _show(image):
subprocess.run(['xfconf-query', '-c', 'xfce4-desktop', '-p',
'/backdrop/screen0/monitoreDP/workspace0/last-image', '-s', image])
def _next_image(self):
image = self.shown.next()
if image:
return image
if not self.input:
self.input = self.shown.reset()
if self.shuffle:
i = random.randint(0, len(self.input) - 1)
image = self.input.pop(i)
else:
image = self.input.pop(0)
self.shown.add(image)
return image
def save(self):
save(self.shown.curr())
def next(self, prev=False):
if prev:
self.shown.prev()
with self._waiting:
self._waiting.notify_all()
def add_files(self, inp):
with open(inp) as f:
files = json.load(f)
base = files['base_dir']
self.input.extend((os.path.join(base, i) for i in files['files'].keys()))
def toggle_pause(self, pause=None):
if pause is None:
self.pause = not self.pause
else:
self.pause = pause
if self.pause:
self.shown.prev(1)
with self._waiting:
self._waiting.notify_all()
class InputDispatcher(threading.Thread):
def __init__(self, changer, pipe='/tmp/bg-sc.pipe'):
super().__init__(name=self.__class__.__name__)
self.changer = changer
self.pipe = pipe
def run(self):
if not os.path.exists(self.pipe):
os.mkfifo(self.pipe)
with open(self.pipe, 'r') as f:
while True:
line = f.readline().split(maxsplit=1)
cmd = line[0] if line else None
arg = line[1] if len(line) > 1 else None
if cmd == 'next':
self.changer.next()
elif cmd == 'prev':
self.changer.next(True)
elif cmd == 'timeout':
self.changer.timeout = float(arg)
elif cmd == 'scale_mode':
self.changer.scale_mode = arg
elif cmd == 'add_files':
self.changer.add_files(arg)
elif cmd == 'save':
self.changer.save()
elif cmd == 'shuffle':
self.changer.shuffle = True
elif cmd == 'no' and arg == 'shuffle':
self.changer.shuffle = False
elif cmd == 'toggle' and arg == 'shuffle':
self.changer.shuffle = not self.changer.shuffle
elif cmd == 'pause':
self.changer.toggle_pause(True)
elif cmd == 'no' and arg == 'pause':
self.changer.toggle_pause(False)
elif cmd == 'toggle' and arg == 'pause':
self.changer.toggle_pause()
def show(inp=None, timeout=10.0, shuffle=True, scale_mode='max', pipe='/tmp/bg-sc.pipe'):
c = BackgroundChanger(float(timeout), bool(shuffle), scale_mode)
d = InputDispatcher(c, pipe)
if inp:
c.add_files(inp)
c.start()
d.start()
def main():
if len(sys.argv) < 2:
print("Usage: %s COMMAND ARGS..." % sys.argv[0])
sys.exit(1)
cmd = sys.argv[1]
if cmd == 'index':
index(sys.argv[2], sys.argv[3])
elif cmd == 'filter':
filter_sizes(sys.argv[2], sys.argv[3], sys.argv[4])
elif cmd == 'show':
if len(sys.argv) == 2:
show()
else:
show(sys.argv[2], **dict((i.split('=', 1) for i in sys.argv[3:])))
elif cmd == 'exec':
s = '/tmp/bg-sc.pipe'
i = 2
if sys.argv[2][:2] == '-S':
s = sys.argv[2]
i = 3
with open(s, 'w') as f:
f.write(' '.join(sys.argv[i:]))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment