Article Outline
Python pil example 'wallpaperchanger'
Functions in program:
def main():
def parse_argument():
Modules used in program:
import random
import argparse
import shlex
import subprocess
import sys
import os
python wallpaperchanger
Python pil example: wallpaperchanger
#!/usr/bin/env python
import os
import sys
import subprocess
import shlex
import argparse
import random
CONFIG_PATH = os.path.expanduser('~/.config/wallpaperchanger.conf')
if sys.version_info.major == 2:
import ConfigParser as configparser
import TKinter as tk
else: # major version == 3
import configparser
import tkinter as tk
try:
import PIL.Image
import PIL.ImageTk
except ImportError:
PIL = None
class WallpaperChanger(object):
"""
Main class
"""
def __init__(self):
self.wrap_config = WrapConfig()
self.wrap_config.load()
self.config = self.wrap_config.config
self.base_path = os.path.expanduser(self.config.get('Main', 'path'))
def call(self, filename, is_abspath=False):
if is_abspath:
path = filename
else:
path = os.path.join(self.base_path, filename)
replace_dic = {'file': path}
command = []
# avoid to split filename which includes spaces.
for line in shlex.split(self.config.get('Main', 'command')):
command.append(line.format(**replace_dic))
res = subprocess.call(command)
if res == 0:
self.config.set('Wallpaper', 'current', filename)
self.wrap_config.write()
def get_filenames(self):
return sorted(os.listdir(self.base_path))
def get_abspath(self, filename):
return os.path.join(self.base_path, filename)
class WrapConfig(object):
"""
Wrap ConfigParser,
"""
DEFAULT = {
'Main': {
'path': '~/picture/wallpaper',
'command': 'feh --bg-fill {file}',
},
'Wallpaper': {
'current': '',
'default': '',
}
}
def __init__(self):
self.config = configparser.ConfigParser()
def load(self):
"""load config file
if not exists, make file.
"""
if self.is_exists():
self.config.read(CONFIG_PATH)
else:
self.set_default()
self.write()
def write(self):
"""save config file,
automatically make directory.
"""
if not self.is_exists_parent_directory():
parent_path = self._get_parent_path()
os.makedirs(parent_path)
with open(CONFIG_PATH, 'w') as fp:
self.config.write(fp)
def set_default(self, overwrite=False):
"""set default, referring self.DEFAULT dictionary
if overwrite flag is True,
all config is overwrite.
if the flag is False and not exists the option, append.
"""
for section in self.DEFAULT.keys():
if not self.config.has_section(section):
self.config.add_section(section)
for option in self.DEFAULT.get(section, {}).keys():
if overwrite or not self.config.has_option(section, option):
self.config.set(section, option, self.DEFAULT[section][option])
def is_exists_parent_directory(self):
parent_path = self._get_parent_path()
return os.path.isdir(parent_path)
def is_exists(self):
return os.path.exists(CONFIG_PATH)
def _get_parent_path(self):
return os.path.abspath(os.path.dirname(CONFIG_PATH))
class Gui(tk.Frame):
"""
Graphical interface for wallpaper selecting.
"""
THUMBNAIL_SIZE = (400, 400)
def __init__(self, master, changer):
self._changer = changer
tk.Frame.__init__(self, master)
self.pack()
self.create_widgets()
self.init_binds()
self.set_listbox_filenames()
self.set_thumbnail()
self.filename = None
self.key = ''
def create_widgets(self):
"""init widgets
"""
f_left = tk.Frame(self)
f_left.pack({'fill': tk.BOTH, 'side': 'left'})
self.elem_listbox = tk.Listbox(f_left)
self.elem_listbox.pack({'side': 'top', 'fill': tk.BOTH})
self.elem_entry = tk.Entry(f_left, textvariable=self.gen_entry_callback())
self.elem_entry.pack({'side': 'bottom'})
self.elem_entry.focus_set()
if PIL is not None:
f_right = tk.Frame(self)
f_right.pack({'fill': tk.BOTH})
self.elem_thumbnail = tk.Label(f_right)
self.elem_thumbnail.pack({
'side': 'right',
})
def init_binds(self):
"""init binds
"""
self.master.bind('<Escape>', self.action_destroy)
self.master.bind('<Return>', self.action_finish)
self.master.bind('<Tab>', self.action_completion)
self.elem_listbox.bind('<<ListboxSelect>>', self.action_select)
self.elem_listbox.bind('<Double-Button-1>', self.action_finish)
def action_destroy(self, *args):
"""destroy gui
callback function
"""
self.master.destroy()
def action_select(self, event=None):
"""set thumbnail
when select item in listbox, called
callback function
"""
if event is not None:
idx = int(self.elem_listbox.curselection()[0])
self.filename = self.elem_listbox.get(idx)
self.set_thumbnail(self.filename)
def action_finish(self, *args):
"""apply new wallpaper by calling Changer.call
"""
if self.filename is not None:
self._changer.call(self.filename)
self.action_destroy()
def action_completion(self, *args):
"""Completion in textbox(Entry).
hooked Tab key, and disable default tab action by returning "break".
"""
names = self.get_filtered_filenames(self.key)
base = names[0]
others = names[1:]
for idx in (len(base) - x for x in range(len(base))):
flag = True
for line in others:
if not base[:idx] in line:
flag = False
if flag:
self.elem_entry.delete(0, tk.END)
self.elem_entry.insert(0, base[:idx])
break
return 'break'
def gen_entry_callback(self):
def callback(sv):
self.key = sv.get()
names = self.get_filtered_filenames(self.key)
self.set_listbox_filenames(names)
if len(names) == 1:
self.filename = names[0]
self.set_thumbnail(names[0])
else:
self.filename = None
self.set_thumbnail()
string_var = tk.StringVar()
string_var.trace('w', lambda name, index, mode, sv=string_var: callback(sv))
return string_var
def set_listbox_filenames(self, filenames=None):
self.elem_listbox.delete(0, self.elem_listbox.size() - 1)
if filenames is None:
filenames = self._changer.get_filenames()
for name in filenames:
self.elem_listbox.insert(tk.END, name)
def set_thumbnail(self, ifilename=None):
if PIL is not None:
size = self.THUMBNAIL_SIZE
thumbnail = PIL.Image.new('RGBA', size, (0, 0, 0, 0))
if ifilename is not None:
filename = self._changer.get_abspath(ifilename)
image = PIL.Image.open(filename)
image.thumbnail(size, PIL.Image.ANTIALIAS)
offset_x = int(max((size[0] - image.size[0]) / 2, 0))
offset_y = int(max((size[1] - image.size[1]) / 2, 0))
thumbnail.paste(image, (offset_x, offset_y))
self.thumbnail = PIL.ImageTk.PhotoImage(thumbnail)
self.elem_thumbnail.configure(image=self.thumbnail)
def get_filtered_filenames(self, keyword):
return [x for x in self._changer.get_filenames() if x.find(keyword) == 0]
def parse_argument():
parser = argparse.ArgumentParser(
description='Wallpaper Changer on Python.',
epilog='''Change ~/.config/wallpaperchanger.conf if you need.'''
)
parser.add_argument('filename',
nargs='?',
default=None,
help='set the picture, ')
parser.add_argument('-d', '--default',
action='store_true',
help='set default picture as wallpaper, if you set config'
)
parser.add_argument('-r', '--random',
action='store_true',
help='set random picture',
)
parser.add_argument('-n', '--next',
action='store_true',
help='set next picture, alphabetical order.',
)
parser.add_argument('--init',
action='store_true',
help='regen config file.')
return parser.parse_args()
def main():
arguments = parse_argument()
changer = WallpaperChanger()
if arguments.filename is not None:
is_abspath = False
filename = ''
if arguments.filename in changer.get_filenames():
filename = arguments.filename
elif os.path.exists(os.path.abspath(arguments.filename)):
filename = os.path.abspath(arguments.filename)
is_abspath = True
else:
print("'{file}' not found".format(file=arguments.filename))
exit(1)
changer.call(filename, is_abspath)
return
if arguments.default:
filename = changer.config.get('Wallpaper', 'default')
changer.call(filename)
return
if arguments.random:
filenames = changer.get_filenames()
idx = random.randint(0, len(filenames) - 1)
filename = filenames[idx]
changer.call(filename)
return
if arguments.next:
filenames = changer.get_filenames()
current = changer.config.get('Wallpaper', 'current')
idx = filenames.find(current) # -1 or 0<=idx<len
filename = filenames[(idx + 1) % len(filenames)]
changer.call(filename)
return
if arguments.init:
changer.wrap_config.set_default(overwrite=True)
changer.wrap_config.write()
w = tk.Tk()
gui = Gui(w, changer)
gui.mainloop()
if __name__ == '__main__':
main()
Python links
- Learn Python: https://pythonbasics.org/
- Python Tutorial: https://pythonprogramminglanguage.com