summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md34
-rw-r--r--newTheme.py220
-rw-r--r--src/color_engine.py49
-rw-r--r--src/get_args.py10
-rw-r--r--src/instant_rice.py26
-rw-r--r--src/paths.py (renamed from paths.py)0
-rw-r--r--src/update_i3.py58
-rw-r--r--src/update_polybar.py24
-rw-r--r--src/update_rofi.py23
-rw-r--r--src/user_interface.py51
10 files changed, 269 insertions, 226 deletions
diff --git a/README.md b/README.md
index da24a09..cf53e7c 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,19 @@
-# Dynamic Theme Switcher
+```
+ _ _ _ _
+(_)_ __ ___| |_ __ _ _ __ | |_ _ __(_) ___ ___
+| | '_ \/ __| __/ _` | '_ \| __| | '__| |/ __/ _ \
+| | | | \__ \ || (_| | | | | |_ | | | | (_| __/
+|_|_| |_|___/\__\__,_|_| |_|\__| |_| |_|\___\___|
+```
+
![desktop with theme switcher](demo.png)
This program will take in an image and generate a color palette based on the image. This palette will be determined by first doing a KMeans analysis on the image to find the most prominent colors, and then generating a complimentary color for each of the most prominent colors. After determining the colors, the program will automatically update the colors for i3 and Polybar to the new color scheme. Next, the program will update the background displayed to the image passed in and prompt the user to re-load i3.
## Usage
-`python3 newTheme.py [image]`
+`python3 instant_rice.py [image_path] [-r] [--nolock]`
+ - `-r`: pick a random image from your `Paths['wallpapers']` directory
+ - `--nolock`: bypass generating an i3 lock screen
## Requirements
@@ -17,9 +26,22 @@ This program will take in an image and generate a color palette based on the ima
- Clone this repo to a safe place
- Modify this script to adapt to your configuration
- - With polybar, it is hard to uniquely identify the lines for the colors without knowing the line number, for this reason, the script will look for a specific line number to modify, and depending on your configuration you might have to change these line numbers. I use modified version of the stock config files for `i3` and `polybar`, so if your config is based on the stock config file, no changes *should* be necessary.
- - Update `paths.py` with the appropiate paths to your config files
+ - With Polybar, it is hard to uniquely identify the lines for the colors without knowing the line number, for this reason, the script will look for a specific line number to modify, and depending on your configuration you might have to change these line numbers. I use modified version of the stock config files for `i3` and `polybar`, so if your config is based on the stock config file, no changes *should* be necessary.
+ - Update `paths.py` with the appropriate paths to your config files
- In your `.bashrc`, create an alias to the python script
- - IE, `alias newtheme='python3 /home/chandler/Documents/newColors/newTheme.py'`
+ - IE, `alias rice='python3 /home/chandler/Documents/newColors/newTheme.py'`
- reload your bash config/open & close your terminal
- - apply a new theme to your system!
+ - apply a new theme to your system (Ex: `rice -r`)!
+
+ ## Configuration
+
+ Instant Rice stores the locations of your configuration files alongside other settings in the `paths.py` folder within a python dictionary. Before using the program, verify the directories and settings are configured to your system.
+ below is the default paths directory configured for my system:
+ ```
+ Paths = {
+ 'i3': '/home/chandler/.config/i3/config',
+ 'polybar': '/home/chandler/.config/polybar/config.ini',
+ 'wallpapers': '/home/chandler/Pictures/papes/',
+ 'lockscreen': '/home/chandler/.config/i3/'
+ }
+ ```
diff --git a/newTheme.py b/newTheme.py
deleted file mode 100644
index 35d75fc..0000000
--- a/newTheme.py
+++ /dev/null
@@ -1,220 +0,0 @@
-import sys, os
-import random
-import numpy as np
-import cv2 as cv
-import subprocess
-from PIL import Image
-from sklearn.cluster import KMeans
-from paths import Paths
-from rich import print
-
-
-def grabColors(img_path: str, num_colors: int) -> list():
- """
- Takes in an image, and Number of colors, then returns a list of those colors.
- The list of colors will contain the most prominent colors present in the image.
- img_path - the path where your image lives (IE, /home/chandler/Pictures/moss.png)
- num_colors - the number of colors you need back from the image
- """
- img = cv.imread(img_path)
- img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
-
- # scale image down by factor of 10 to decrease computation time
- dim = (int(len(img[0])/10), int(len(img)/10))
- img = cv.resize(img, dim, interpolation= cv.INTER_AREA)
- clt = KMeans(n_clusters=num_colors, n_init='auto')
- clt.fit(img.reshape(-1, 3))
- return clt.cluster_centers_
-
-def rgbToHex(input_values):
- """
- Takes in a list of RBG color values and returns a list of those same colors as hex values
- """
- hex_list=[]
- for color in input_values:
- red = int(color[0])
- green = int(color[1])
- blue = int(color[2])
- hex_list.append('#{:02x}{:02x}{:02x}'.format(red, green, blue))
- return hex_list
-
-def hexToRGB(hex_value: str):
- hex_value = hex_value.lstrip('#')
- return tuple(int(hex_value[i:i+2], 16) for i in (0, 2, 4))
-
-def compColors(color_list):
- """
- given a list of colors, generate complimentary colors to contrast the prominent colors.
- return a list of these colors.
- """
- compliments = []
- for color in color_list:
- curr_hex = color[1:] # slice off the # from the hex code
- rgb = (curr_hex[0:2], curr_hex[2:4], curr_hex[4:6])
- comp = ['%02X' % (255 - int(a, 16)) for a in rgb]
- compliments.append('#' + ''.join(comp))
- return compliments
-
-def getScreenResolution():
- output = subprocess.Popen('xrandr | grep "\*" | cut -d" " -f4',shell=True, stdout=subprocess.PIPE).communicate()[0]
- resolution = output.split()[0].split(b'x')
- width = int(resolution[0].decode('UTF-8'))
- height = int(resolution[1].decode('UTF-8'))
- return width, height
-
-def updatei3Theme(config_path: str, img_path: str, colors: list, compliments: list, lock: bool, dmenu: bool):
- print('[bold red]Updating i3 color scheme')
- data = ''
- with open(config_path, 'r') as file:
- data = file.readlines()
-
- for i, line in enumerate(data):
- # update colors
- if "set $bgcolor" in line:
- data[i] = 'set $bgcolor ' + colors[0] + '\n'
- if "set $in-bgcolor" in line:
- data[i] = 'set $in-bgcolor ' + colors[1] + '\n'
- if "set $text" in line:
- data[i] = 'set $text ' + compliments[0] + '\n'
- if "set $indicator" in line:
- data[i] = 'set $indicator ' + colors[2] + '\n'
- if "set $in-text" in line:
- data[i] = 'set $in-text ' + compliments[1] + '\n'
- #update background image
- if "set $bgimage" in line:
- data[i] = 'set $bgimage ' + img_path + '\n'
- if "bindsym $mod+d exec --no-startup-id dmenu_run" in line:
- print('[bold red]Updating Dmenu color scheme')
- data[i] = f"bindsym $mod+d exec --no-startup-id dmenu_run -nb '{colors[0]}' -sf '{compliments[0]}' -sb '{colors[1]}' -nf '{compliments[1]}'\n"
- # update i3lock image, convert to png so it plays nice w i3lock
- if lock:
- img = cv.imread(img_path)
- imgHeight, imgWidth, _ = img.shape
- screenWidth, screenHeight = getScreenResolution()
- lock_scale = screenWidth / imgWidth
- print('[bold red]Creating lock screen')
- dim = (int(imgWidth * lock_scale), int(imgHeight * lock_scale))
- img = cv.resize(img, dim, interpolation= cv.INTER_AREA)
- cv.imwrite('lock.png', img)
- os.rename('lock.png', Paths['lockscreen'] + 'lock.png')
- with open(config_path, 'w') as file:
- file.writelines(data)
-
-
-def updatePolybarTheme(config_path: str, colors: list, compliments: list):
- print('[bold red]Updating polybar color scheme')
- data = ''
- with open(config_path, 'r') as file:
- data = file.readlines()
- for i,line in enumerate(data):
- #update colors
- if "background =" in line and i == 19:
- data[i] = 'background = ' + colors[0] + '\n'
- if "background-alt =" in line and i == 20:
- data[i] = 'background-alt = ' + colors[1] + '\n'
- if "foreground =" in line and i == 21:
- data[i] = 'foreground = ' + compliments[0] + '\n'
- if "primary =" in line and i == 22:
- data[i] = 'primary = ' + compliments[1] + '\n'
- if "secondary =" in line and i == 23:
- data[i] = 'secondary = ' + compliments[2] + '\n'
- if "disabled =" in line and i == 25:
- data[i] = 'disabled = ' + colors[2] + '\n'
- with open(config_path, 'w') as file:
- file.writelines(data)
-
-
-def updateRofiTheme(config_path: str, colors: list, compliments: list):
- print('[bold red]Updating Rofi color scheme')
- data = ''
- with open(config_path, 'r') as file:
- data = file.readlines()
- bg = hexToRGB(colors[1])
- fg = hexToRGB(compliments[1])
- lbg = hexToRGB(colors[0])
- lfg = hexToRGB(colors[0])
- for i,line in enumerate(data):
- if 'background: ' in line and i == 23:
- data[i] = ' background: rgba({}, {}, {}, 70%);\n'.format(bg[0], bg[1], bg[2])
- if 'foreground: ' in line and i == 28:
- data[i] = ' foreground: rgba({}, {}, {}, 100%);\n'.format(fg[0], fg[1], fg[2])
- if 'lightbg: ' in line and i == 12:
- data[i] = ' lightbg: rgba({}, {}, {}, 100%);\n'.format(lbg[0], lbg[1], lgb[2])
- if 'lightfg: ' in line and i == 7:
- data[i] = ' lightfg: rgba({}, {}, {}, 100%);\n'.format(lfg[0], lfg[1], lfg[2])
- with open(config_path, 'w') as file:
- file.writelines(data)
-
-def pickRandomWallpaper():
- confirmed = False
- while not confirmed:
- wallpaper = Paths['wallpapers'] + random.choice(os.listdir(Paths['wallpapers']))
- os.system(f'viu {wallpaper}')
- print(f'picked wallpaper: {wallpaper}')
- print('[bold](a)ccept (r)etry')
- response = input('>')
-
- if response == 'a':
- confirmed = True
-
- return wallpaper
-
-
-def colorPickerUI(img_path: str):
- #display the selected color scheme and ask user if they like it or want to generate a new color scheme
- confirmed = False
- while not confirmed:
- print()
- popularColors = grabColors(img_path, 3)
- hex_colors = rgbToHex(popularColors)
- hex_compliments = compColors(hex_colors)
-
- main_colors = ''
- complimentary_colors = ''
-
- for color in hex_colors:
- main_colors += f'[on {color}] [/on {color}]'
- print(main_colors)
- for color in hex_compliments:
- complimentary_colors += f'[on {color}] [/on {color}]'
- print(complimentary_colors)
- print()
- count = 0
- for i in range(len(hex_colors)):
- print(f'[{hex_compliments[i]} on {hex_colors[i]}]\tGenerated Color Scheme\t\t ({count})')
- count += 1
-# for i in range(len(hex_colors)):
-# print(f'[{hex_colors[i]} on {hex_compliments[i]}]\tGenerated Color Scheme\t\t ({count})')
- count += 1
- print('[bold](a)ccept (r)etry')
- response = input('> ')
- if response == 'r':
- continue
- else:
- confirmed = True
- return hex_colors, hex_compliments
-def main():
- if '-r' in sys.argv:
- img_path = pickRandomWallpaper()
- else:
- img_path = sys.argv[1]
-
- hex_colors, hex_compliments = colorPickerUI(img_path)
- if 'polybar' in Paths:
- updatePolybarTheme(Paths['polybar'], hex_colors, hex_compliments)
- if 'rofi' in Paths:
- updateRofiTheme(Paths['rofi'], hex_colors, hex_compliments)
- if 'i3' in Paths:
- update_dmenu = True if ('-dmenu' in sys.argv) else False
- if '--nolock' in sys.argv:
- updatei3Theme(Paths['i3'], img_path, hex_colors, hex_compliments, False, update_dmenu)
- else:
- updatei3Theme(Paths['i3'], img_path, hex_colors, hex_compliments, True, update_dmenu)
-
-
-
- # print("Theme changed successfully, please reload i3")
- print("[bold red]Restarting i3")
- os.system("i3 restart")
-
-main()
diff --git a/src/color_engine.py b/src/color_engine.py
new file mode 100644
index 0000000..f86b2e7
--- /dev/null
+++ b/src/color_engine.py
@@ -0,0 +1,49 @@
+import cv2 as cv
+from sklearn.cluster import KMeans
+
+def grabColors(img_path: str, num_colors: int) -> list():
+ """
+ Takes in an image, and Number of colors, then returns a list of those colors.
+ The list of colors will contain the most prominent colors present in the image.
+ img_path - the path where your image lives (IE, /home/chandler/Pictures/moss.png)
+ num_colors - the number of colors you need back from the image
+ """
+ img = cv.imread(img_path)
+ img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
+
+ # scale image down by factor of 10 to decrease computation time
+ dim = (int(len(img[0])/10), int(len(img)/10))
+ img = cv.resize(img, dim, interpolation= cv.INTER_AREA)
+ clt = KMeans(n_clusters=num_colors, n_init='auto')
+ clt.fit(img.reshape(-1, 3))
+ return clt.cluster_centers_
+
+def rgbToHex(input_values: list):
+ """
+ Takes in a list of RBG color values and returns a list of those same colors as hex values
+ """
+ hex_list=[]
+ for color in input_values:
+ red = int(color[0])
+ green = int(color[1])
+ blue = int(color[2])
+ hex_list.append('#{:02x}{:02x}{:02x}'.format(red, green, blue))
+ return hex_list
+
+def hexToRGB(hex_value: str):
+ hex_value = hex_value.lstrip('#')
+ return tuple(int(hex_value[i:i+2], 16) for i in (0, 2, 4))
+
+def compColors(color_list: list):
+ """
+ given a list of colors, generate complimentary colors to contrast the prominent colors.
+ return a list of these colors.
+ """
+ compliments = []
+ for color in color_list:
+ curr_hex = color[1:] # slice off the # from the hex code
+ rgb = (curr_hex[0:2], curr_hex[2:4], curr_hex[4:6])
+ comp = ['%02X' % (255 - int(a, 16)) for a in rgb]
+ compliments.append('#' + ''.join(comp))
+ return compliments
+
diff --git a/src/get_args.py b/src/get_args.py
new file mode 100644
index 0000000..0001abb
--- /dev/null
+++ b/src/get_args.py
@@ -0,0 +1,10 @@
+import user_interface
+
+def get_args(args):
+ if len(args) != 2:
+ pass
+ elif '-r' in args:
+ img_path = user_interface.pickRandomWallpaper()
+ else:
+ img_path = args[1]
+ return img_path
diff --git a/src/instant_rice.py b/src/instant_rice.py
new file mode 100644
index 0000000..6d02be6
--- /dev/null
+++ b/src/instant_rice.py
@@ -0,0 +1,26 @@
+import sys
+import color_engine
+import user_interface
+import update_rofi
+import update_i3
+import update_polybar
+from get_args import get_args
+from paths import Paths
+
+def main():
+ img_path = get_args(sys.argv)
+ hex_colors, hex_compliments = user_interface.colorPickerUI(img_path)
+
+ if 'polybar' in Paths:
+ update_polybar.updatePolybarTheme(Paths['polybar'], hex_colors, hex_compliments)
+ if 'rofi' in Paths:
+ update_rofi.updateRofiTheme(Paths['rofi'], hex_colors, hex_compliments)
+ if 'i3' in Paths:
+ update_dmenu = True if ('-dmenu' in sys.argv) else False
+ generate_i3lock = False if ('--nolock' in sys.argv) else True
+ update_i3.updatei3Theme(Paths['i3'], img_path, hex_colors, hex_compliments, generate_i3lock, update_dmenu)
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/paths.py b/src/paths.py
index 7ad287b..7ad287b 100644
--- a/paths.py
+++ b/src/paths.py
diff --git a/src/update_i3.py b/src/update_i3.py
new file mode 100644
index 0000000..6430f6e
--- /dev/null
+++ b/src/update_i3.py
@@ -0,0 +1,58 @@
+import subprocess
+import cv2 as cv
+import os
+
+from rich import print
+from paths import Paths
+
+def updatei3Theme(config_path: str, img_path: str, colors: list, compliments: list, lock: bool, dmenu: bool):
+ print('[bold red]Updating i3 color scheme')
+ data = ''
+ with open(config_path, 'r') as file:
+ data = file.readlines()
+
+ for i, line in enumerate(data):
+ # update colors
+ if "set $bgcolor" in line:
+ data[i] = 'set $bgcolor ' + colors[0] + '\n'
+ if "set $in-bgcolor" in line:
+ data[i] = 'set $in-bgcolor ' + colors[1] + '\n'
+ if "set $text" in line:
+ data[i] = 'set $text ' + compliments[0] + '\n'
+ if "set $indicator" in line:
+ data[i] = 'set $indicator ' + colors[2] + '\n'
+ if "set $in-text" in line:
+ data[i] = 'set $in-text ' + compliments[1] + '\n'
+ #update background image
+ if "set $bgimage" in line:
+ data[i] = 'set $bgimage ' + img_path + '\n'
+
+ if "bindsym $mod+d exec --no-startup-id dmenu_run" in line:
+ if dmenu:
+ print('[bold red]Updating Dmenu color scheme')
+ data[i] = f"bindsym $mod+d exec --no-startup-id dmenu_run -nb '{colors[0]}' -sf '{compliments[0]}' -sb '{colors[1]}' -nf '{compliments[1]}'\n"
+ # update i3lock image, convert to png so it plays nice w i3lock
+ if lock:
+ img = cv.imread(img_path)
+ imgHeight, imgWidth, _ = img.shape
+ screenWidth, screenHeight = getScreenResolution()
+ lock_scale = screenWidth / imgWidth
+ print('[bold red]Creating lock screen')
+ dim = (int(imgWidth * lock_scale), int(imgHeight * lock_scale))
+ img = cv.resize(img, dim, interpolation= cv.INTER_AREA)
+ cv.imwrite('lock.png', img)
+ os.rename('lock.png', Paths['lockscreen'] + 'lock.png')
+ with open(config_path, 'w') as file:
+ file.writelines(data)
+
+ print("[bold red]Restarting i3")
+ os.system("i3 restart")
+
+
+def getScreenResolution():
+ output = subprocess.Popen('xrandr | grep "\*" | cut -d" " -f4',shell=True, stdout=subprocess.PIPE).communicate()[0]
+ resolution = output.split()[0].split(b'x')
+ width = int(resolution[0].decode('UTF-8'))
+ height = int(resolution[1].decode('UTF-8'))
+ return width, height
+
diff --git a/src/update_polybar.py b/src/update_polybar.py
new file mode 100644
index 0000000..ff395c6
--- /dev/null
+++ b/src/update_polybar.py
@@ -0,0 +1,24 @@
+
+
+def updatePolybarTheme(config_path: str, colors: list, compliments: list):
+ print('[bold red]Updating polybar color scheme')
+ data = ''
+ with open(config_path, 'r') as file:
+ data = file.readlines()
+ for i,line in enumerate(data):
+ #update colors
+ if "background =" in line and i == 19:
+ data[i] = 'background = ' + colors[0] + '\n'
+ if "background-alt =" in line and i == 20:
+ data[i] = 'background-alt = ' + colors[1] + '\n'
+ if "foreground =" in line and i == 21:
+ data[i] = 'foreground = ' + compliments[0] + '\n'
+ if "primary =" in line and i == 22:
+ data[i] = 'primary = ' + compliments[1] + '\n'
+ if "secondary =" in line and i == 23:
+ data[i] = 'secondary = ' + compliments[2] + '\n'
+ if "disabled =" in line and i == 25:
+ data[i] = 'disabled = ' + colors[2] + '\n'
+ with open(config_path, 'w') as file:
+ file.writelines(data)
+
diff --git a/src/update_rofi.py b/src/update_rofi.py
new file mode 100644
index 0000000..a7d3aa6
--- /dev/null
+++ b/src/update_rofi.py
@@ -0,0 +1,23 @@
+import color_engine
+
+def updateRofiTheme(config_path: str, colors: list, compliments: list):
+ print('[bold red]Updating Rofi color scheme')
+ data = ''
+ with open(config_path, 'r') as file:
+ data = file.readlines()
+ bg = color_engine.hexToRGB(colors[1])
+ fg = color_engine.hexToRGB(compliments[1])
+ lbg = color_engine.hexToRGB(colors[0])
+ lfg = color_engine.hexToRGB(colors[0])
+ for i,line in enumerate(data):
+ if 'background: ' in line and i == 23:
+ data[i] = ' background: rgba({}, {}, {}, 70%);\n'.format(bg[0], bg[1], bg[2])
+ if 'foreground: ' in line and i == 28:
+ data[i] = ' foreground: rgba({}, {}, {}, 100%);\n'.format(fg[0], fg[1], fg[2])
+ if 'lightbg: ' in line and i == 12:
+ data[i] = ' lightbg: rgba({}, {}, {}, 100%);\n'.format(lbg[0], lbg[1], lgb[2])
+ if 'lightfg: ' in line and i == 7:
+ data[i] = ' lightfg: rgba({}, {}, {}, 100%);\n'.format(lfg[0], lfg[1], lfg[2])
+ with open(config_path, 'w') as file:
+ file.writelines(data)
+
diff --git a/src/user_interface.py b/src/user_interface.py
new file mode 100644
index 0000000..32abf60
--- /dev/null
+++ b/src/user_interface.py
@@ -0,0 +1,51 @@
+import os
+import random
+from paths import Paths
+import color_engine
+from rich import print
+
+
+def colorPickerUI(img_path: str):
+#display the selected color scheme and ask user if they like it or want to generate a new color scheme
+ confirmed = False
+ while not confirmed:
+ print()
+ popularColors = color_engine.grabColors(img_path, 3)
+ hex_colors = color_engine.rgbToHex(popularColors)
+ hex_compliments = color_engine.compColors(hex_colors)
+
+ main_colors = ''
+ complimentary_colors = ''
+
+ for color in hex_colors:
+ main_colors += f'[on {color}] [/on {color}]'
+ print(main_colors)
+ for color in hex_compliments:
+ complimentary_colors += f'[on {color}] [/on {color}]'
+ print(complimentary_colors)
+ print()
+ count = 0
+ for i in range(len(hex_colors)):
+ print(f'[{hex_compliments[i]} on {hex_colors[i]}]\tGenerated Color Scheme\t\t ({count})')
+ count += 1
+ print('[bold](a)ccept (r)etry')
+ response = input('> ')
+ if response == 'r':
+ continue
+ else:
+ confirmed = True
+ return hex_colors, hex_compliments
+
+def pickRandomWallpaper():
+ confirmed = False
+ while not confirmed:
+ wallpaper = Paths['wallpapers'] + random.choice(os.listdir(Paths['wallpapers']))
+ os.system(f'viu {wallpaper}')
+ print(f'picked wallpaper: {wallpaper}')
+ print('[bold](a)ccept (r)etry')
+ response = input('>')
+
+ if response == 'a':
+ confirmed = True
+
+ return wallpaper