├── .gitignore ├── README.md ├── main.py ├── requirements.txt ├── resources ├── cache │ └── settings.json ├── fonts │ ├── font.otf │ └── font.ttf └── tmp │ └── temp.png └── start.bat /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Кликабельные обои на питоне ? Почему нет! 2 | ![image](https://user-images.githubusercontent.com/69964743/159045067-4506f223-5d18-4f96-93f0-e80f512541cd.png) 3 | 4 | Возможность изменения темы по нажатия "кнопок" на рабочем столе. 5 | Выбор город, для которого будет отображаться погода. 6 | Курсы валют. 7 | День недели 8 | 9 | Сделать можно многое, нужна лишь фантазия. 10 | 11 | 12 | ##### Установка библиотек: 13 | 14 | ```shell script 15 | pip3 install -r requirements.txt 16 | ``` 17 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import ctypes, os, time, datetime, requests, json, win32gui, sys 2 | from PIL import Image, ImageDraw, ImageFont 3 | 4 | class POINT(ctypes.Structure): 5 | _fields_ = [('x', ctypes.c_ulong), ('y', ctypes.c_ulong)] 6 | 7 | def queryMousePosition(): 8 | pt = POINT() 9 | ctypes.windll.user32.GetCursorPos(ctypes.byref(pt)) 10 | return { 'x': pt.x, 'y': pt.y} 11 | 12 | class Cache: 13 | def __init__(self): 14 | self.filename = 'resources/cache/settings.json' 15 | self.checkExists() 16 | self.content = False 17 | self.cache = False 18 | self.read() 19 | 20 | def checkExists(self): 21 | if not os.path.exists(self.filename): 22 | self.createCashe() 23 | self.save() 24 | 25 | def toString(self): 26 | return json.dumps( self.cache, indent = 4, ensure_ascii = False ) 27 | 28 | def writeToFile(self, content): 29 | f = open( self.filename, 'w', encoding = 'utf8' ) 30 | f.write( content ) 31 | f.close() 32 | 33 | 34 | def save(self): 35 | self.writeToFile( self.toString() ) 36 | 37 | def readFile(self): 38 | f = open(self.filename, 'r', encoding = 'utf8') 39 | self.content = f.read() 40 | f.close() 41 | 42 | def read(self, i = 0): 43 | if i > 1: 44 | print('Error read cache file.') 45 | sys.exit() 46 | self.readFile() 47 | try: 48 | self.cache = json.loads( self.content ) 49 | except: 50 | self.createCashe() 51 | self.save() 52 | return self.read(i + 1) 53 | 54 | def createCashe(self): 55 | self.cache = { 56 | 'version': 'v1.0' 57 | } 58 | 59 | def get(self, index, default): 60 | if index in self.cache: 61 | return self.cache[index] 62 | self.put( index, default ) 63 | return default 64 | 65 | def put(self, index, body): 66 | self.cache[index] = body 67 | self.save() 68 | 69 | cache = Cache() 70 | 71 | class Course: 72 | 73 | def getJsonData(): 74 | url = 'https://www.cbr-xml-daily.ru/daily_json.js' 75 | return requests.get(url).json() 76 | 77 | def getCourse(args): 78 | try: 79 | data = Course.getJsonData() 80 | tbl = [] 81 | for i in args: 82 | tbl.append( (i.upper(), round(data['Valute'][ i.upper() ]['Value'], 2))) 83 | return tbl 84 | except: 85 | return False 86 | 87 | class Weather: 88 | 89 | def getCitys(): 90 | return cache.get('weather', [ 91 | ['Самара', 'https://www.gismeteo.ru/weather-samara-4618/'], 92 | ['Тольятти', 'https://www.gismeteo.ru/weather-tolyatti-4429/'], 93 | ['Москва', 'https://www.gismeteo.ru/weather-moscow-4368/'] 94 | ]) 95 | 96 | city = getCitys()[ cache.get('weatherIndex', 0) ] 97 | 98 | def setCity(index): 99 | Weather.city = Weather.getCitys()[index] 100 | 101 | def getJsonData(): 102 | try: 103 | data = requests.get(Weather.city[1], headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}).text 104 | i = str.find(data, 'M.state.weather.cw = {') 105 | if i == -1: 106 | return False 107 | end = str.find(data[i:], '\n') 108 | if end == -1: 109 | return False 110 | return json.loads( data[i + 21: i + end] ) 111 | except: 112 | return False 113 | 114 | def getData(): 115 | try: 116 | info = Weather.getJsonData() 117 | return (Weather.city[0], info['temperatureAir'][0], info['description'][0]) 118 | except: 119 | return False 120 | 121 | class Date: 122 | 123 | def getDay(): 124 | return ([ 125 | 'Понедельник', 126 | 'Вторник', 127 | 'Среда', 128 | 'Четверг', 129 | 'Пятница', 130 | 'Суббота', 131 | 'Воскресенье' 132 | ])[datetime.datetime.today().isoweekday() - 1] 133 | 134 | def get(*formats): 135 | date = datetime.datetime.now() 136 | if len(formats) == 1: 137 | return date.strftime(formats[0]) 138 | back = [] 139 | for i in formats: 140 | back.append( date.strftime(i) ) 141 | return tuple(back) 142 | 143 | class Color: 144 | def hex(s): 145 | n = int(s.lstrip('#'), 16) 146 | return (n >> 16, (n >> 8) & 0xff, n & 0xff) 147 | 148 | def getThemeList(): 149 | return [ 150 | { 'bg': Color.hex('#131313'), 'fg': Color.hex('#980002'), 'text': Color.hex('#ffbf00') }, 151 | { 'bg': Color.hex('#ebdcb2'), 'fg': Color.hex('#af4425'), 'text': Color.hex('#552e1c') }, 152 | { 'bg': Color.hex('#1e0000'), 'fg': Color.hex('#bc6d4f'), 'text': Color.hex('#9d331f') }, 153 | { 'bg': Color.hex('#ddc5a2'), 'fg': Color.hex('#523634'), 'text': Color.hex('#b6452c') }, 154 | { 'bg': Color.hex('#003b46'), 'fg': Color.hex('#c3dfe6'), 'text': Color.hex('#66a5ad') } 155 | ] 156 | 157 | def getTheme( mode ): 158 | return Color.getThemeList()[mode] 159 | 160 | class Cord: 161 | 162 | def __init__(self, x, y, w, h): 163 | self.x = x 164 | self.y = y 165 | self.w = w 166 | self.h = h 167 | self.x2 = x + w 168 | self.y2 = y + h 169 | 170 | def isInside(self, x, y): 171 | return self.x <= x <= self.x2 and self.y <= y <= self.y2 172 | 173 | class Main(): 174 | 175 | def __init__(self): 176 | 177 | user32 = ctypes.windll.user32 178 | 179 | self.width = user32.GetSystemMetrics(0) 180 | self.height = user32.GetSystemMetrics(1) 181 | 182 | self.mBuffer = user32.GetKeyState(1) 183 | self.mPos = False 184 | self.mDown = False 185 | 186 | self.path = os.getcwd() 187 | self.indexTheme = cache.get('themeIndex', 0) 188 | self.theme = Color.getTheme(self.indexTheme) 189 | 190 | self.font = ImageFont.load_default() 191 | self.cashFonts = {} 192 | 193 | self.bg = False 194 | self.bgLastColor = False 195 | 196 | self.openCitys = False 197 | 198 | self.buttons = [] 199 | 200 | def addButton(self, cord, function, update): 201 | self.buttons.append({'cord': cord, 'function': function, 'update': update}) 202 | 203 | def genEmpty(self): 204 | if not self.bg or self.theme['bg'] != self.bgLastColor: 205 | color = self.theme['bg'] 206 | img = Image.new('RGB', (self.width, self.height), color) 207 | 208 | self.orig = img 209 | 210 | 211 | img = self.orig.copy() 212 | 213 | self.object = img 214 | self.draw = ImageDraw.Draw(img) 215 | 216 | 217 | def getTextSize(self, text): 218 | return self.draw.textsize(text, self.font) 219 | 220 | def setFont(self, name, size): 221 | id = f'{name}x{size}' 222 | if not id in self.cashFonts: 223 | self.cashFonts[id] = ImageFont.truetype(f'resources/fonts/{name}', size) 224 | self.font = self.cashFonts[id] 225 | 226 | def setText(self, x, y, text, color = (255, 255, 255),): 227 | self.draw.text((x, y), text, font = self.font, fill = color) 228 | w, h = self.getTextSize( text ) 229 | return Cord(x, y, w, h) 230 | 231 | def setMindText(self, x, y, text, color = (255, 255, 255)): 232 | w, h = self.getTextSize( text ) 233 | return self.setText( int(x - w / 2), y, text, color ) 234 | 235 | def setWallpaper(self, filename): 236 | ctypes.windll.user32.SystemParametersInfoW(0x0014 , 0, self.path + f'\\{filename}', 2) 237 | 238 | def onUpdate(self, i = 0): 239 | self.pos = queryMousePosition() 240 | ctypes.windll.user32.GetKeyState.restype = ctypes.c_ushort 241 | if ctypes.windll.user32.GetKeyState(1) != self.mBuffer: 242 | self.mBuffer = bool(abs(int(self.mBuffer) - 1)) 243 | self.mDown = True 244 | 245 | back = False 246 | if self.mDown: 247 | self.mDown = False 248 | focus = win32gui.GetWindowText( win32gui.GetForegroundWindow() ) 249 | if focus in {'Program Manager', ''} : 250 | x, y = self.pos['x'], self.pos['y'] 251 | for butt in self.buttons: 252 | if butt['cord'].isInside(x, y): 253 | butt['function']() 254 | if butt['update']: 255 | back = True 256 | return back 257 | 258 | 259 | def update(self): 260 | def cityBtn(): 261 | self.openCitys = True 262 | 263 | def setCity(index): 264 | Weather.setCity( index ) 265 | self.openCitys = False 266 | cache.put('weatherIndex', index) 267 | self.weather = Weather.getData() 268 | 269 | def newTheme(): 270 | self.indexTheme += 1 271 | if self.indexTheme == len(Color.getThemeList()): 272 | self.indexTheme = 0 273 | self.theme = Color.getTheme(self.indexTheme) 274 | cache.put('themeIndex', self.indexTheme) 275 | 276 | self.buttons = [] 277 | c2 = False 278 | self.genEmpty() 279 | 280 | self.minutes, H_M, d_m_Y = Date.get('%M', '%H:%M', '%d.%m.%Y' ) 281 | 282 | w = self.width / 2 283 | theme = self.theme 284 | 285 | self.setFont( 'font.otf', 100 ) 286 | 287 | c = self.setMindText( w , self.height / 3 - 100, Date.getDay(), theme['fg']) 288 | 289 | if self.courses: 290 | self.setFont( 'font.otf', 50 ) 291 | c2 = self.setMindText( w , c.y2 + 15, ' '.join([ f'{i[0]}: {str(i[1])}' for i in self.courses ]), theme['fg']) 292 | 293 | self.draw.line(( min(c.x, c2.x) - 10, c2.y2 + 15, max(c.x2, c2.x2) + 10, c2.y2 + 15), fill = theme['fg'], width=5) 294 | else: 295 | self.draw.line(( c.x - 10, c.y2 + 15, c.x2 + 10, c.y2 + 15), fill = theme['fg'], width=5) 296 | 297 | 298 | self.setFont( 'font.otf', 100 ) 299 | c = self.setMindText( w , (c2.y2 if c2 else c.y2 ) + 30, H_M, theme['fg']) 300 | 301 | self.setFont( 'font.otf', 45 ) 302 | c = self.setMindText( w , c.y2 + 20, d_m_Y, theme['fg']) 303 | 304 | self.setFont( 'font.ttf', 20 ) 305 | w1, h1 = self.getTextSize('Сменить тему') 306 | s = self.setText( self.width - 5 - w1, self.height - 48 - h1, 'Сменить тему', theme['fg']) 307 | self.addButton(s, lambda: newTheme(), True) 308 | 309 | if not self.openCitys: 310 | self.setFont( 'font.otf', 40 ) 311 | if self.weather: 312 | s = self.setText(5, 5, str(self.weather[0]), theme['text']) 313 | self.addButton(s, cityBtn, True) 314 | self.setText(5, s.y2 + 5, f'{str(self.weather[1])}° {str(self.weather[2])}', theme['text']) 315 | else: 316 | self.setFont( 'font.ttf', 25 ) 317 | s = self.setText(5, 5, 'Выберите населённый пункт', theme['fg']) 318 | tbl = Weather.getCitys() 319 | for i in range(len(tbl)): 320 | s = self.setText(10, s.y2 + 5, tbl[i][0], theme['text']) 321 | self.addButton(s, lambda index = i: setCity(index), True) 322 | 323 | try: 324 | self.object.save('resources/tmp/temp.png') 325 | self.setWallpaper('resources/tmp/temp.png') 326 | except: 327 | if i > 2: 328 | return 329 | self.update(i + 1) 330 | 331 | 332 | def start(self): 333 | while True: 334 | self.courses = Course.getCourse(cache.get('courses', ['USD', 'EUR'])) 335 | self.weather = Weather.getData() 336 | self.update( ) 337 | 338 | while self.minutes == Date.get('%M'): 339 | if self.onUpdate(): 340 | self.update( ) 341 | time.sleep(0.1) 342 | 343 | 344 | if __name__ == "__main__": 345 | main = Main() 346 | main.start() 347 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pillow 2 | pywin32 3 | requests 4 | win32gui 5 | -------------------------------------------------------------------------------- /resources/cache/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v1.0", 3 | "weather": [ 4 | [ 5 | "Самара", 6 | "https://www.gismeteo.ru/weather-samara-4618/" 7 | ], 8 | [ 9 | "Тольятти", 10 | "https://www.gismeteo.ru/weather-tolyatti-4429/" 11 | ], 12 | [ 13 | "Москва", 14 | "https://www.gismeteo.ru/weather-moscow-4368/" 15 | ] 16 | ], 17 | "weatherIndex": 0, 18 | "themeIndex": 0, 19 | "courses": [ 20 | "USD", 21 | "EUR" 22 | ] 23 | } -------------------------------------------------------------------------------- /resources/fonts/font.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kislorod4ik/ClickableWallpapersOnPython/335b9c7ddca222bc7a84441d8b8ed199170f9c41/resources/fonts/font.otf -------------------------------------------------------------------------------- /resources/fonts/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kislorod4ik/ClickableWallpapersOnPython/335b9c7ddca222bc7a84441d8b8ed199170f9c41/resources/fonts/font.ttf -------------------------------------------------------------------------------- /resources/tmp/temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kislorod4ik/ClickableWallpapersOnPython/335b9c7ddca222bc7a84441d8b8ed199170f9c41/resources/tmp/temp.png -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | cd C:\Users\Kislorod4ik\СlickableWallpapersOnPython\ 2 | start C:\Users\Kislorod4ik\AppData\Local\Programs\Python\Python38\pythonw.exe main.py --------------------------------------------------------------------------------