From 56b2ef5c9a4f9fa807fe8b096174a80e32e5e596 Mon Sep 17 00:00:00 2001 From: LewdDevelopment <158585445+LewdDevelopment@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:06:16 +0000 Subject: [PATCH 1/8] Initial Linux compatibility --- EdgeWare/config.pyw | 298 +++++++++++--------------- EdgeWare/panic.pyw | 16 +- EdgeWare/panic.sh | 6 + EdgeWare/popup.pyw | 138 +++++-------- EdgeWare/prompt.pyw | 25 ++- EdgeWare/start.pyw | 321 +++++++++++++--------------- EdgeWare/startup_flair.pyw | 15 +- EdgeWare/utils/area.py | 11 + EdgeWare/utils/linux.py | 413 +++++++++++++++++++++++++++++++++++++ EdgeWare/utils/utils.py | 14 ++ EdgeWare/utils/windows.py | 121 +++++++++++ requirements.txt | 12 ++ 12 files changed, 933 insertions(+), 457 deletions(-) create mode 100644 EdgeWare/panic.sh create mode 100644 EdgeWare/utils/area.py create mode 100644 EdgeWare/utils/linux.py create mode 100644 EdgeWare/utils/utils.py create mode 100644 EdgeWare/utils/windows.py create mode 100644 requirements.txt diff --git a/EdgeWare/config.pyw b/EdgeWare/config.pyw index fe2bbd3e..56807150 100644 --- a/EdgeWare/config.pyw +++ b/EdgeWare/config.pyw @@ -14,10 +14,13 @@ import logging import time import textwrap import random as rand +import getpass from tkinter import Tk, ttk, simpledialog, messagebox, filedialog, IntVar, BooleanVar, StringVar, Frame, Checkbutton, Button, Scale, Label, Toplevel, Entry, OptionMenu, Listbox, SINGLE, DISABLED, GROOVE, RAISED, Text, END, Scrollbar, VERTICAL, font, CENTER from tk_ToolTip_class101 import CreateToolTip +from pathlib import Path +from utils import utils -PATH = f'{str(pathlib.Path(__file__).parent.absolute())}\\' +PATH = Path(__file__).parent os.chdir(PATH) #starting logging @@ -28,13 +31,14 @@ logging.basicConfig(filename=os.path.join(PATH, 'logs', LOG_TIME + '-dbg.txt'), logging.info('Started config logging successfully.') def pip_install(packageName:str): - try: - logging.info(f'attempting to install {packageName}') - subprocess.call(f'py -m pip install {packageName}') - except: - logging.warning(f'failed to install {packageName} using py -m pip, trying raw pip request') - subprocess.call(f'pip install {packageName}') - logging.warning(f'{packageName} should be installed, fatal errors will occur if install failed.') + if utils.is_windows(): + try: + logging.info(f'attempting to install {packageName}') + subprocess.call(f'py -m pip install {packageName}') + except: + logging.warning(f'failed to install {packageName} using py -m pip, trying raw pip request') + subprocess.call(f'pip install {packageName}') + logging.warning(f'{packageName} should be installed, fatal errors will occur if install failed.') try: import requests @@ -111,6 +115,7 @@ PLUSPLUS_TEXT = 'Thanks for taking the time to check out this extensio PACKINFO_TEXT = 'The pack info section contains an overview for whatever pack is currently loaded.\n\nThe \"Stats\" tab allows you to see what features are included in the current pack (or if a pack is even loaded at all), but keep in mind all of these features have default fallbacks if they aren\'t included. It also lets you see a lot of fun stats relating to the pack, including almost everything you\'ll encounter while using EdgeWare. Keep in mind that certain things having \"0\" as a stat doesn\'t mean you can\'t use it, for example, having 0 subliminals uses the default spiral and having 0 images displays a very un-sexy circle.\n\nThe \"Information\" tab gets info on the pack from //resource//info.json, which is a new addition to EdgeWare++. This feature was added to allow pack creators to give the pack a formal name and description without having to worry about details being lost if transferred from person to person. Think of it like a readme. Also included in this section is the discord status info, which gives what your discord status will be set to if that setting is turned on, along with the image. As of time of writing (or if I forget to update this later), the image cannot be previewed as it is \"hard coded\" into EdgeWare\'s discord application and accessed through the API. As I am not the original creator of EdgeWare, and am not sure how to contact them, the best I could do is low-res screenshots or the name of each image. I chose the latter. Because of this hard-coding, the only person i\'ve run into so far who use these images is PetitTournesol themselves, but it should be noted that anyone can use them as long as they know what to add to the discord.dat file. This is partially the reason I left this information in.\n\nThe \"Moods\" tab is where you can access mood settings and previews for the current pack. The left table shows information for media (linking moods to images, videos, etc), captions, and prompts, while the \"Corruption Path\" area shows how these moods correlate to corruption levels.' FILE_TEXT = 'The file tab is for all your file management needs, whether it be saving things, loading things, deleting things, or looking around in config folders. The Preset window has also been moved here to make more room for general options.\n\nThere are only two things that aren\'t very self explanatory: deleting logs and unique IDs.\n\nWhile deleting logs is fairly straightforward, it should be noted that it will not delete the log currently being written during the session, so the \"total logs in folder\" stat will always display as \"1\".\n\nUnique IDs are a feature to help assist with saving moods. In short, they are a generated identifier that is used when saving to a \"moods json file\", which is tapped into when selecting what moods you want to see in the \"Pack Info\" tab. Unique IDs are only used if the pack does not have a \'info.json\' file, otherwise the pack name is just used instead. If you are rapidly editing a pack without info.json and want EdgeWare++ to stop generating new mood files, there is an option to disable it in the troubleshooting tab.\n\n When manually editing mood config jsons, you don\'t need to worry about how the unique ID is generated- the file tab will tell you what to look for. If you are curious though, here is the exact formula:\n\nnum_images + num_audio + num_video + wallpaper(y/n) + loading_splash(y/n) + discord_status(y/n) + icon(y/n) + corruption(y/n)\n\nFor example:\nA pack with 268 images, 7 audio, 6 videos, has a wallpaper, doesn\'t have a custom loading splash, has a discord status, doesn\'t have a custom icon, and doesn\'t have a corruption file, would generate \"26876wxdxx.json\" in //moods//unnamed (mood files go in unnamed when using unique IDs)' +BUTTON_FACE = 'SystemButtonFace' if os.name == 'nt' else 'gray90' errors_list = [] @@ -127,10 +132,10 @@ INFO_CREATOR_DEFAULT = 'Anonymous' INFO_VERSION_DEFAULT = '0' INFO_DISCORD_DEFAULT = ['[No pack loaded, or the pack does not have a \'discord.dat\' file.]', 'default'] -if os.path.isfile(PATH + '\\resource\\info.json'): +if os.path.isfile(os.path.join(PATH, 'resource', 'info.json')): try: info_dict = '' - with open(f'{PATH}\\resource\\info.json') as r: + with open(os.path.join(PATH, 'resource', 'info.json')) as r: info_dict = json.loads(r.read()) info_name = info_dict['name'] if info_dict['name'] else 'Unnamed Pack' info_description = info_dict['description'] if info_dict['description'] else 'No description set.' @@ -158,36 +163,32 @@ else: #get loading splash try: - if os.path.isfile(PATH + '\\resource\\loading_splash.png'): - LOADING_PATH = '\\resource\\loading_splash.png' - elif os.path.isfile(PATH + '\\resource\\loading_splash.gif'): - LOADING_PATH = '\\resource\\loading_splash.gif' - elif os.path.isfile(PATH + '\\resource\\loading_splash.jpg'): - LOADING_PATH = '\\resource\\loading_splash.jpg' - elif os.path.isfile(PATH + '\\resource\\loading_splash.jpeg'): - LOADING_PATH = '\\resource\\loading_splash.jpeg' - elif os.path.isfile(PATH + '\\resource\\loading_splash.bmp'): - LOADING_PATH = '\\resource\\loading_splash.bmp' - else: - LOADING_PATH = '\\default_assets\\loading_splash.png' + LOADING_PATH = None + for file_format in ['png', 'gif', 'jpg', 'jpeg', 'bmp']: + if os.path.isfile(os.path.join(PATH, 'resource', f'loading_splash.{file_format}')): + LOADING_PATH = os.path.join('resource', f'loading_splash.{file_format}') + break + + if not LOADING_PATH: + LOADING_PATH = os.path.join('default_assets', 'loading_splash.png') except: - LOADING_PATH = '\\default_assets\\loading_splash.png' + LOADING_PATH = os.path.join('default_assets', 'loading_splash.png') UNIQUE_ID = '0' #creating a semi-parseable unique ID for the pack to make mood saving work, if the pack doesn't have an info.json file. #probably could have made it so the user manually has to save/load and not worried about this, but here we are -if info_id == '0' and os.path.exists(PATH + '\\resource\\'): +if info_id == '0' and os.path.exists(os.path.join(PATH, 'resource')): try: #already done the brunt of the work for getting these values in the pack info page, so i'm just using those again here. If this needs to be replaced, look there too - im = str(len(os.listdir(PATH + '\\resource\\img\\'))) if os.path.exists(PATH + '\\resource\\img\\') else '0' - au = str(len(os.listdir(PATH + '\\resource\\aud\\'))) if os.path.exists(PATH + '\\resource\\aud\\') else '0' - vi = str(len(os.listdir(PATH + '\\resource\\vid\\'))) if os.path.exists(PATH + '\\resource\\vid\\') else '0' - wa = 'w' if os.path.isfile(PATH + '\\resource\\wallpaper.png') else 'x' - sp = 's' if LOADING_PATH != '\\default_assets\\loading_splash.png' else 'x' - di = 'd' if os.path.isfile(PATH + '\\resource\\discord.dat') else 'x' - ic = 'i' if os.path.isfile(PATH + '\\resource\\icon.ico') else 'x' - co = 'c' if os.path.isfile(PATH + '\\resource\\corruption.json') else 'x' + im = str(len(os.listdir(os.path.join(PATH, 'resource', 'img')))) if os.path.exists(os.path.join(PATH, 'resource', 'img')) else '0' + au = str(len(os.listdir(os.path.join(PATH, 'resource', 'aud')))) if os.path.exists(os.path.join(PATH, 'resource', 'aud')) else '0' + vi = str(len(os.listdir(os.path.join(PATH, 'resource', 'vid')))) if os.path.exists(os.path.join(PATH, 'resource', 'vid')) else '0' + wa = 'w' if os.path.isfile(os.path.join(PATH, 'resource', 'wallpaper.png')) else 'x' + sp = 's' if LOADING_PATH != os.path.join('default_assets', 'loading_splash.png') else 'x' + di = 'd' if os.path.isfile(os.path.join(PATH, 'resource', 'discord.dat')) else 'x' + ic = 'i' if os.path.isfile(os.path.join(PATH, 'resource', 'icon.ico')) else 'x' + co = 'c' if os.path.isfile(os.path.join(PATH, 'resource', 'corruption.json')) else 'x' UNIQUE_ID = im + au + vi + wa + sp + di + ic + co logging.info(f'generated unique ID. {UNIQUE_ID}') except Exception as e: @@ -204,7 +205,7 @@ UPDCHECK_PP_URL = 'http://raw.githubusercontent.com/araten10/EdgewarePlusPlus/ma local_pp_version = '0.0.0_NOCONNECT' logging.info('opening configDefault') -with open(f'{PATH}configDefault.dat') as r: +with open(os.path.join(PATH, 'configDefault.dat')) as r: defaultSettingLines = r.readlines() varNames = defaultSettingLines[0].split(',') varNames[-1] = varNames[-1].replace('\n', '') @@ -220,13 +221,13 @@ for var in varNames: defaultSettings = settings.copy() -if not os.path.exists(f'{PATH}config.cfg'): +if not os.path.exists(os.path.join(PATH, 'config.cfg')): logging.warning('no "config.cfg" file found, creating new "config.cfg".') - with open(f'{PATH}config.cfg', 'w') as f: + with open(os.path.join(PATH, 'config.cfg'), 'w') as f: f.write(json.dumps(settings)) logging.info('created new config file.') -with open(f'{PATH}config.cfg', 'r') as f: +with open(os.path.join(PATH, 'config.cfg'), 'r') as f: logging.info('json loading settings') try: settings = json.loads(f.readline()) @@ -248,7 +249,7 @@ if settings['version'] != defaultVars[0] or len(settings) != len(defaultSettings logging.info(f'added missing key: {name}') tempSettingDict['version'] = defaultVars[0] settings = tempSettingDict.copy() - with open(f'{PATH}config.cfg', 'w') as f: + with open(os.path.join(PATH, 'config.cfg'), 'w') as f: #bugfix for the config crash issue tempSettingDict['wallpaperDat'] = str(tempSettingDict['wallpaperDat']).replace("'", '%^%') tempSettingString = str(tempSettingDict).replace("'", '"') @@ -277,10 +278,10 @@ pass_ = '' MOOD_PATH = '0' if settings['toggleMoodSet'] != True: - if UNIQUE_ID != '0' and os.path.exists(PATH + '\\resource\\'): - MOOD_PATH = f'{PATH}\\moods\\unnamed\\{UNIQUE_ID}.json' - elif UNIQUE_ID == '0' and os.path.exists(PATH + '\\resource\\'): - MOOD_PATH = f'{PATH}\\moods\\{info_id}.json' + if UNIQUE_ID != '0' and os.path.exists(os.path.join(PATH, 'resource')): + MOOD_PATH = os.path.join(PATH, 'moods', 'unnamed', f'{UNIQUE_ID}.json') + elif UNIQUE_ID == '0' and os.path.exists(os.path.join(PATH, 'resource')): + MOOD_PATH = os.path.join(PATH, 'moods', f'{info_id}.json') #creating the mood file if it doesn't exist if MOOD_PATH != '0' and not os.path.isfile(MOOD_PATH): @@ -290,18 +291,18 @@ if settings['toggleMoodSet'] != True: mood_dict = {"media": [], "captions": [], "prompts": [], "web": []} try: - if os.path.isfile(PATH + '\\resource\\media.json'): + if os.path.isfile(os.path.join(PATH, 'resource', 'media.json')): media_dict = '' - with open(f'{PATH}\\resource\\media.json') as media: + with open(os.path.join(PATH, 'resource', 'media.json')) as media: media_dict = json.loads(media.read()) mood_dict["media"] += media_dict except: logging.warning(f'media mood extraction failed.') try: - if os.path.isfile(PATH + '\\resource\\captions.json'): + if os.path.isfile(os.path.join(PATH, 'resource', 'captions.json')): captions_dict = '' - with open(f'{PATH}\\resource\\captions.json') as captions: + with open(os.path.join(PATH, 'resource', 'captions.json')) as captions: captions_dict = json.loads(captions.read()) if 'prefix' in captions_dict: del captions_dict['prefix'] if 'subtext' in captions_dict: del captions_dict['subtext'] @@ -310,18 +311,18 @@ if settings['toggleMoodSet'] != True: logging.warning(f'captions mood extraction failed.') try: - if os.path.isfile(PATH + '\\resource\\prompt.json'): + if os.path.isfile(os.path.join(PATH, 'resource', 'prompt.json')): prompt_dict = '' - with open(f'{PATH}\\resource\\prompt.json') as prompt: + with open(os.path.join(PATH, 'resource', 'prompt.json')) as prompt: prompt_dict = json.loads(prompt.read()) mood_dict["prompts"] += prompt_dict["moods"] except: logging.warning(f'prompt mood extraction failed.') try: - if os.path.isfile(PATH + '\\resource\\web.json'): + if os.path.isfile(os.path.join(PATH, 'resource', 'web.json')): web_dict = '' - with open(f'{PATH}\\resource\\web.json') as web: + with open(os.path.join(PATH, 'resource', 'web.json')) as web: web_dict = json.loads(web.read()) for n in web_dict["moods"]: if n not in mood_dict["web"]: @@ -343,7 +344,7 @@ def show_window(): root.title('Edgeware++ Config') root.geometry('740x800') try: - root.iconbitmap(f'{PATH}default_assets\\config_icon.ico') + root.iconbitmap(os.path.join(PATH, 'default_assets', 'config_icon.ico')) logging.info('set iconbitmap.') except: logging.warning('failed to set iconbitmap.') @@ -499,9 +500,9 @@ def show_window(): emergencySettings = {} for var in varNames: emergencySettings[var] = defaultVars[varNames.index(var)] - with open(f'{PATH}config.cfg', 'w') as f: + with open(os.path.join(PATH, 'config.cfg'), 'w') as f: f.write(json.dumps(emergencySettings)) - with open(f'{PATH}config.cfg', 'r') as f: + with open(os.path.join(PATH, 'config.cfg'), 'r') as f: settings = json.loads(f.readline()) fail_loop += 1 @@ -764,12 +765,12 @@ def show_window(): #zipDownloadButton = Button(tabGeneral, text='Download Zip', command=lambda: downloadZip(zipDropVar.get(), zipLabel)) #zipLabel = Label(zipGitFrame, text=f'Current Zip:\n{pickZip()}', background='lightgray', wraplength=100) local_verLabel = Label(verFrame, text=f'EdgeWare Local Version:\n{defaultVars[0]}') - web_verLabel = Label(verFrame, text=f'EdgeWare GitHub Version:\n{webv}', bg=('SystemButtonFace' if (defaultVars[0] == webv) else 'red')) + web_verLabel = Label(verFrame, text=f'EdgeWare GitHub Version:\n{webv}', bg=(BUTTON_FACE if (defaultVars[0] == webv) else 'red')) openGitButton = Button(zipGitFrame, text='Open Github (EdgeWare Base)', command=lambda: webbrowser.open('https://github.com/PetitTournesol/Edgeware')) verPlusFrame = Frame(infoHostFrame) local_verPlusLabel = Label(verPlusFrame, text=f'EdgeWare++ Local Version:\n{defaultVars[1]}') - web_verPlusLabel = Label(verPlusFrame, text=f'EdgeWare++ GitHub Version:\n{webvpp}', bg=('SystemButtonFace' if (defaultVars[1] == webvpp) else 'red')) + web_verPlusLabel = Label(verPlusFrame, text=f'EdgeWare++ GitHub Version:\n{webvpp}', bg=(BUTTON_FACE if (defaultVars[1] == webvpp) else 'red')) openGitPlusButton = Button(zipGitFrame, text='Open Github (EdgeWare++)', command=lambda: webbrowser.open('https://github.com/araten10/EdgewarePlusPlus')) infoHostFrame.pack(fill='x') @@ -1106,7 +1107,7 @@ def show_window(): mitosis_cGroup.append(mitosisStren) setPanicButtonButton = Button(panicFrame, text=f'Set Panic Button\n<{panicButtonVar.get()}>', command=lambda:getKeyboardInput(setPanicButtonButton, panicButtonVar), cursor='question_arrow') - doPanicButton = Button(panicFrame, text='Perform Panic', command=lambda: os.startfile('panic.pyw')) + doPanicButton = Button(panicFrame, text='Perform Panic', command=lambda: subprocess.Popen([sys.executable, 'panic.pyw'])) panicDisableButton = Checkbutton(popupHostFrame, text='Disable Panic Hotkey', variable=panicVar, cursor='question_arrow') setpanicttp = CreateToolTip(setPanicButtonButton, 'NOTE: To use this hotkey you must be \"focused\" on a EdgeWare popup. Click on a popup before using.') @@ -1385,9 +1386,9 @@ def show_window(): corruptionList = [] lineWidth = 0 - if os.path.isfile(PATH + '\\resource\\corruption.json'): + if os.path.isfile(os.path.join(PATH, 'resource', 'corruption.json')): try: - with open((PATH + '\\resource\\corruption.json'), 'r') as f: + with open(os.path.join(PATH, 'resource', 'corruption.json'), 'r') as f: l = json.loads(f.read()) for key in l: corruptionList.append((f'{key}', str(l[key]).strip('[]'))) @@ -1617,14 +1618,14 @@ def show_window(): statusIconFrame = Frame(infoStatusFrame) statusCorruptionFrame = Frame(infoStatusFrame) - if os.path.exists(PATH + '\\resource\\'): + if os.path.exists(os.path.join(PATH, 'resource')): statusPack = True - statusAbout = True if os.path.isfile(PATH + '\\resource\\info.json') else False - statusWallpaper = True if os.path.isfile(PATH + '\\resource\\wallpaper.png') else False - statusStartup = True if LOADING_PATH != '\\default_assets\\loading_splash.png' else False - statusDiscord = True if os.path.isfile(PATH + '\\resource\\discord.dat') else False - statusIcon = True if os.path.isfile(PATH + '\\resource\\icon.ico') else False - statusCorruption = True if os.path.isfile(PATH + '\\resource\\corruption.json') else False + statusAbout = True if os.path.isfile(os.path.join(PATH, 'resource', 'info.json')) else False + statusWallpaper = True if os.path.isfile(os.path.join(PATH, 'resource', 'wallpaper.png')) else False + statusStartup = True if LOADING_PATH != os.path.join('default_assets', 'loading_splash.png') else False + statusDiscord = True if os.path.isfile(os.path.join(PATH, 'resource', 'discord.dat')) else False + statusIcon = True if os.path.isfile(os.path.join(PATH, 'resource', 'icon.ico')) else False + statusCorruption = True if os.path.isfile(os.path.join(PATH, 'resource', 'corruption.json')) else False else: statusPack = False statusAbout = False @@ -1684,13 +1685,13 @@ def show_window(): captionsStatsFrame = Frame(statsFrame2) subliminalsStatsFrame = Frame(statsFrame2) - imageStat = len(os.listdir(PATH + '\\resource\\img\\')) if os.path.exists(PATH + '\\resource\\img\\') else 0 - audioStat = len(os.listdir(PATH + '\\resource\\aud\\')) if os.path.exists(PATH + '\\resource\\aud\\') else 0 - videoStat = len(os.listdir(PATH + '\\resource\\vid\\')) if os.path.exists(PATH + '\\resource\\vid\\') else 0 + imageStat = len(os.listdir(os.path.join(PATH, 'resource', 'img'))) if os.path.exists(os.path.join(PATH, 'resource', 'img')) else 0 + audioStat = len(os.listdir(os.path.join(PATH, 'resource', 'aud'))) if os.path.exists(os.path.join(PATH, 'resource', 'aud')) else 0 + videoStat = len(os.listdir(os.path.join(PATH, 'resource', 'vid'))) if os.path.exists(os.path.join(PATH, 'resource', 'vid')) else 0 - if os.path.exists(PATH + '\\resource\\web.json'): + if os.path.exists(os.path.join(PATH, 'resource', 'web.json')): try: - with open(PATH + '\\resource\\web.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'web.json'), 'r') as f: webStat = len(json.loads(f.read())['urls']) except Exception as e: logging.warning(f'error in web.json. Aborting preview load. {e}') @@ -1699,10 +1700,10 @@ def show_window(): else: webStat = 0 - if os.path.exists(PATH + '\\resource\\prompt.json'): + if os.path.exists(os.path.join(PATH, 'resource', 'prompt.json')): #frankly really ugly but the easiest way I found to do it try: - with open(PATH + '\\resource\\prompt.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'prompt.json'), 'r') as f: l = json.loads(f.read()) i = 0 if 'moods' in l: del l['moods'] @@ -1721,10 +1722,10 @@ def show_window(): else: promptStat = 0 - if os.path.exists(PATH + '\\resource\\captions.json'): + if os.path.exists(os.path.join(PATH, 'resource', 'captions.json')): #don't think these have moods currently but will implement this just in case try: - with open(PATH + '\\resource\\captions.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'captions.json'), 'r') as f: l = json.loads(f.read()) i = 0 if 'prefix' in l: del l['prefix'] @@ -1739,7 +1740,7 @@ def show_window(): else: captionStat = 0 - subliminalStat = len(os.listdir(PATH + '\\resource\\subliminals\\')) if os.path.exists(PATH + '\\resource\\subliminals\\') else 0 + subliminalStat = len(os.listdir(os.path.join(PATH, 'resource', 'subliminals'))) if os.path.exists(os.path.join(PATH, 'resource', 'subliminals')) else 0 statsFrame.pack(fill='x', pady=1) statsFrame1.pack(fill='x', side='top') @@ -1838,7 +1839,7 @@ def show_window(): discordStatusImageLabel = Label(discordStatusFrame, text='Discord Status Image:', font='Default 10') if statusDiscord: try: - with open((PATH + '\\resource\\discord.dat'), 'r') as f: + with open((os.path.join(PATH, 'resource', 'discord.dat')), 'r') as f: datfile = f.read() if not datfile == '': info_discord = datfile.split('\n') @@ -1875,8 +1876,8 @@ def show_window(): 'Because of this, only packs created by the original EdgeWare creator, PetitTournesol, have custom status images.\n\n' 'Nevertheless, I have decided to put this here not only for those packs, but also for other ' 'packs that tap in to the same image IDs.') - if os.path.exists(PATH + '\\resource\\config.json'): - with open(PATH + '\\resource\\config.json') as f: + if os.path.exists(os.path.join(PATH, 'resource', 'config.json')): + with open(os.path.join(PATH, 'resource', 'config.json')) as f: try: l = json.loads(f.read()) if 'version' in l: del l['version'] @@ -1942,9 +1943,9 @@ def show_window(): mediaScrollbar = ttk.Scrollbar(moodsMediaFrame, orient=VERTICAL, command=mediaTree.yview) mediaTree.configure(yscroll=mediaScrollbar.set) - if os.path.exists(PATH + '\\resource\\media.json'): + if os.path.exists(os.path.join(PATH, 'resource', 'media.json')): try: - with open(PATH + '\\resource\\media.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'media.json'), 'r') as f: l = json.loads(f.read()) for m in l: if m == 'default': @@ -1964,7 +1965,7 @@ def show_window(): if settings['toggleMoodSet'] != True: if len(mediaTree.get_children()) != 0: - if MOOD_PATH != '0' and os.path.exists(PATH + '\\resource\\'): + if MOOD_PATH != '0' and os.path.exists(os.path.join(PATH, 'resource')): try: with open(MOOD_PATH, 'r') as mood: mood_dict = json.loads(mood.read()) @@ -1985,9 +1986,9 @@ def show_window(): captionsScrollbar = ttk.Scrollbar(moodsCaptionsFrame, orient=VERTICAL, command=captionsTree.yview) captionsTree.configure(yscroll=captionsScrollbar.set) - if os.path.exists(PATH + '\\resource\\captions.json'): + if os.path.exists(os.path.join(PATH, 'resource', 'captions.json')): try: - with open(PATH + '\\resource\\captions.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'captions.json'), 'r') as f: l = json.loads(f.read()) if 'prefix' in l: del l['prefix'] if 'subtext' in l: del l['subtext'] @@ -2009,7 +2010,7 @@ def show_window(): if settings['toggleMoodSet'] != True: if len(captionsTree.get_children()) != 0: - if MOOD_PATH != '0' and os.path.exists(PATH + '\\resource\\'): + if MOOD_PATH != '0' and os.path.exists(os.path.join(PATH, 'resource')): try: with open(MOOD_PATH, 'r') as mood: mood_dict = json.loads(mood.read()) @@ -2029,9 +2030,9 @@ def show_window(): promptsScrollbar = ttk.Scrollbar(moodsPromptsFrame, orient=VERTICAL, command=promptsTree.yview) promptsTree.configure(yscroll=promptsScrollbar.set) - if os.path.exists(PATH + '\\resource\\prompt.json'): + if os.path.exists(os.path.join(PATH, 'resource', 'prompt.json')): try: - with open(PATH + '\\resource\\prompt.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'prompt.json'), 'r') as f: l = json.loads(f.read()) for m in l['moods']: if m == 'default': @@ -2052,7 +2053,7 @@ def show_window(): if settings['toggleMoodSet'] != True: if len(promptsTree.get_children()) != 0: - if MOOD_PATH != '0' and os.path.exists(PATH + '\\resource\\'): + if MOOD_PATH != '0' and os.path.exists(os.path.join(PATH, 'resource')): try: with open(MOOD_PATH, 'r') as mood: mood_dict = json.loads(mood.read()) @@ -2072,9 +2073,9 @@ def show_window(): webScrollbar = ttk.Scrollbar(moodsWebFrame, orient=VERTICAL, command=webTree.yview) webTree.configure(yscroll=webScrollbar.set) - if os.path.exists(PATH + '\\resource\\web.json'): + if os.path.exists(os.path.join(PATH, 'resource', 'web.json')): try: - with open(PATH + '\\resource\\web.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'web.json'), 'r') as f: l = json.loads(f.read()) webMoodList = ['default'] for m in l['moods']: @@ -2099,7 +2100,7 @@ def show_window(): if settings['toggleMoodSet'] != True: if len(webTree.get_children()) != 0: - if MOOD_PATH != '0' and os.path.exists(PATH + '\\resource\\'): + if MOOD_PATH != '0' and os.path.exists(os.path.join(PATH, 'resource')): try: with open(MOOD_PATH, 'r') as mood: mood_dict = json.loads(mood.read()) @@ -2137,7 +2138,7 @@ def show_window(): #directories Label(tabFile, text='Directories', font=titleFont, relief=GROOVE).pack(pady=2) - logNum = len(os.listdir(PATH + '\\logs\\')) if os.path.exists(PATH + '\\logs\\') else 0 + logNum = len(os.listdir(os.path.join(PATH, 'logs'))) if os.path.exists(os.path.join(PATH, 'logs')) else 0 logsFrame = Frame(tabFile, borderwidth=5, relief=RAISED) lSubFrame1 = Frame(logsFrame) lSubFrame2 = Frame(logsFrame) @@ -2149,17 +2150,17 @@ def show_window(): def cleanLogs(): try: - logNum = len(os.listdir(PATH + '\\logs\\')) if os.path.exists(PATH + '\\logs\\') else 0 + logNum = len(os.listdir(os.path.join(PATH, 'logs'))) if os.path.exists(os.path.join(PATH, 'logs')) else 0 if messagebox.askyesno('Confirm Delete', f'Are you sure you want to delete all logs? There are currently {logNum}.', icon='warning') == True: - if os.path.exists(PATH + '\\logs\\') and os.listdir(PATH + '\\logs\\'): - logs = os.listdir(PATH + '\\logs\\') + if os.path.exists(os.path.join(PATH, 'logs')) and os.listdir(os.path.join(PATH, 'logs')): + logs = os.listdir(os.path.join(PATH, 'logs')) for f in logs: if os.path.splitext(f)[0] == os.path.join(LOG_TIME + '-dbg'): continue e = os.path.splitext(f)[1].lower() if e == '.txt': - os.remove(PATH + '\\logs\\' + f) - logNum = len(os.listdir(PATH + '\\logs\\')) if os.path.exists(PATH + '\\logs\\') else 0 + os.remove(os.path.join(PATH, 'logs') + f) + logNum = len(os.listdir(os.path.join(PATH, 'logs'))) if os.path.exists(os.path.join(PATH, 'logs')) else 0 logStat.configure(text=f'Total Logs: {logNum}') except Exception as e: logging.warning(f'could not clear logs. this might be an issue with attempting to delete the log currently in use. if so, ignore this prompt. {e}') @@ -2413,9 +2414,7 @@ def show_window(): timeObjPath = os.path.join(PATH, 'hid_time.dat') - HIDDEN_ATTR = 0x02 - SHOWN_ATTR = 0x08 - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, SHOWN_ATTR) + utils.show_file(timeObjPath) if os.path.exists(timeObjPath): with open(timeObjPath, 'r') as file: time_ = int(file.readline()) / 60 @@ -2423,7 +2422,7 @@ def show_window(): timerToggle.configure(state=DISABLED) for item in timer_group: item.configure(state=DISABLED) - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, HIDDEN_ATTR) + utils.hide_file(timeObjPath) #first time alert popup @@ -2446,7 +2445,7 @@ def explorerView(url): def pickZip() -> str: #selecting zip - for dirListObject in os.listdir(f'{PATH}\\'): + for dirListObject in os.listdir(PATH): try: if dirListObject.split('.')[-1].lower() == 'zip': return dirListObject.split('.')[0] @@ -2464,12 +2463,12 @@ def exportResource() -> bool: for file in files: logging.info(f'write {file}') if beyondRoot: - zip.write(os.path.join(root, file), root.split('\\')[-1] + f'\\{file}') + zip.write(os.path.join(root, file), os.path.join(Path(root).name, file)) else: - zip.write(os.path.join(root, file), f'\\{file}') + zip.write(os.path.join(root, file), file) for dir in dirs: logging.info(f'make dir {dir}') - zip.write(os.path.join(root, dir), f'\\{dir}\\') + zip.write(os.path.join(root, dir), dir) beyondRoot = True return True except Exception as e: @@ -2482,16 +2481,16 @@ def importResource(parent:Tk) -> bool: openLocation = filedialog.askopenfile('r', defaultextension ='.zip') if openLocation == None: return False - if os.path.exists(f'{PATH}resource\\'): + if os.path.exists(os.path.join(PATH, 'resource')): resp = confirmBox(parent, 'Confirm', 'Current resource folder will be deleted and overwritten. Is this okay?' '\nNOTE: This might take a while when importing larger packs, please be patient!') if not resp: logging.info('exited import resource overwrite') return False - shutil.rmtree(f'{PATH}resource\\') + shutil.rmtree(os.path.join(PATH, 'resource')) logging.info('removed old resource folder') with zipfile.ZipFile(openLocation.name, 'r') as zip: - zip.extractall(f'{PATH}resource\\') + zip.extractall(os.path.join(PATH, 'resource')) logging.info('extracted all from zip') messagebox.showinfo('Done', 'Resource importing completed.') refresh() @@ -2509,7 +2508,6 @@ def confirmBox(parent:Tk, btitle:str, message:str) -> bool: root.quit() root.geometry('300x150') root.resizable(False, False) - root.wm_attributes('-toolwindow', 1) root.focus_force() root.title(btitle) Label(root, text=message, wraplength=292).pack(fill='x') @@ -2536,19 +2534,17 @@ def write_save(varList:list[StringVar | IntVar | BooleanVar], nameList:list[str] settings['wallpaperDat'] = f'{settings["wallpaperDat"]}' settings['is_configed'] = 1 - toggleStartupBat(varList[nameList.index('start_on_logon')].get()) + utils.toggle_run_at_startup(PATH, varList[nameList.index('start_on_logon')].get()) - SHOWN_ATTR = 0x08 - HIDDEN_ATTR = 0x02 hashObjPath = os.path.join(PATH, 'pass.hash') timeObjPath = os.path.join(PATH, 'hid_time.dat') if int(varList[nameList.index('timerMode')].get()) == 1: - toggleStartupBat(True) + utils.toggle_run_at_startup(PATH, True) #revealing hidden files - ctypes.windll.kernel32.SetFileAttributesW(hashObjPath, SHOWN_ATTR) - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, SHOWN_ATTR) + utils.show_file(hashObjPath) + utils.show_file(timeObjPath) logging.info('revealed hashed pass and time files') with open(hashObjPath, 'w') as passFile, open(timeObjPath, 'w') as timeFile: @@ -2558,20 +2554,20 @@ def write_save(varList:list[StringVar | IntVar | BooleanVar], nameList:list[str] logging.info('wrote files.') #hiding hash file with saved password hash for panic and time data - ctypes.windll.kernel32.SetFileAttributesW(hashObjPath, HIDDEN_ATTR) - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, HIDDEN_ATTR) + utils.hide_file(hashObjPath) + utils.hide_file(timeObjPath) logging.info('hid hashed pass and time files') else: try: if not varList[nameList.index('start_on_logon')].get(): - toggleStartupBat(False) - ctypes.windll.kernel32.SetFileAttributesW(hashObjPath, SHOWN_ATTR) - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, SHOWN_ATTR) + utils.toggle_run_at_startup(PATH, False) + utils.show_file(hashObjPath) + utils.show_file(timeObjPath) os.remove(hashObjPath) os.remove(timeObjPath) logging.info('removed pass/time files.') except Exception as e: - errText = str(e).lower().replace(os.environ['USERPROFILE'].lower().replace('\\', '\\\\'), '[USERNAME_REDACTED]') + errText = str(e).replace(getpass.getuser(), '[USERNAME_REDACTED]') logging.warning(f'failed timer file modifying\n\tReason: {errText}') pass @@ -2587,12 +2583,12 @@ def write_save(varList:list[StringVar | IntVar | BooleanVar], nameList:list[str] except: temp[name] = settings[name] - with open(f'{PATH}config.cfg', 'w') as file: + with open(os.path.join(PATH, 'config.cfg'), 'w') as file: file.write(json.dumps(temp)) logging.info(f'wrote config file: {json.dumps(temp)}') if int(varList[nameList.index('runOnSaveQuit')].get()) == 1 and exitAtEnd: - os.startfile('start.pyw') + subprocess.Popen([sys.executable, 'start.pyw']) if exitAtEnd: logging.info('exiting config') @@ -2756,12 +2752,12 @@ def updateText(objList:Entry or Label, var:str, var_Label:str): print('idk what would cause this but just in case uwu') def refresh(): - os.startfile('config.pyw') + subprocess.Popen([sys.executable, 'config.pyw']) os.kill(os.getpid(), 9) def assignJSON(key:str, var:int or str): settings[key] = var - with open(f'{PATH}config.cfg', 'w') as f: + with open(os.path.join(PATH, 'config.cfg'), 'w') as f: f.write(json.dumps(settings)) def toggleAssociateSettings(ownerState:bool, objList:list, demo:str = False): @@ -2770,7 +2766,7 @@ def toggleAssociateSettings(ownerState:bool, objList:list, demo:str = False): else: th = settings['themeType'].strip() if th == 'Original' or (settings['themeNoConfig'] == True and not demo): - toggleAssociateSettings_manual(ownerState, objList, 'SystemButtonFace', 'gray35') + toggleAssociateSettings_manual(ownerState, objList, BUTTON_FACE, 'gray35') else: if th == 'Dark': toggleAssociateSettings_manual(ownerState, objList, '#282c34', 'gray65') @@ -2790,50 +2786,6 @@ def toggleAssociateSettings_manual(ownerState:bool, objList:list, colorOn:int, c tkObject.configure(state=('normal' if ownerState else 'disabled')) tkObject.configure(bg=(colorOn if ownerState else colorOff)) -def shortcut_script(pth_str:str, startup_path:str, title:str): - #strings for batch script to write vbs script to create shortcut on desktop - #stupid and confusing? yes. the only way i could find to do this? also yes. - print(pth_str) - return ['@echo off\n' - 'set SCRIPT="%TEMP%\%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%.vbs"\n', - 'echo Set oWS = WScript.CreateObject("WScript.Shell") >> %SCRIPT%\n', - f'echo sLinkFile = "{startup_path}\\{title}.lnk" >> %SCRIPT%\n', - 'echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %SCRIPT%\n', - f'echo oLink.WorkingDirectory = "{pth_str}\\" >> %SCRIPT%\n', - f'echo oLink.TargetPath = "{pth_str}\\start.pyw" >> %SCRIPT%\n', - 'echo oLink.Save >> %SCRIPT%\n', - 'cscript /nologo %SCRIPT%\n', - 'del %SCRIPT%'] - -#uses the above script to create a shortcut on desktop with given specs -def make_shortcut(tList:list) -> bool: - with open(PATH + '\\tmp.bat', 'w') as bat: - bat.writelines(shortcut_script(tList[0], tList[1], tList[2])) #write built shortcut script text to temporary batch file - try: - logging.info(f'making shortcut to {tList[2]}') - subprocess.call(PATH + '\\tmp.bat') - os.remove(PATH + '\\tmp.bat') - return True - except Exception as e: - print('failed') - logging.warning(f'failed to call or remove temp batch file for making shortcuts\n\tReason: {e}') - return False - -def toggleStartupBat(state:bool): - try: - startup_path = os.path.expanduser('~\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\') - logging.info(f'trying to toggle startup bat to {state}') - if state: - make_shortcut([PATH, startup_path, 'edgeware']) #i scream at my previous and current incompetence and poor programming - logging.info('toggled startup run on.') - else: - os.remove(os.path.join(startup_path, 'edgeware.lnk')) - logging.info('toggled startup run off.') - except Exception as e: - errText = str(e).lower().replace(os.environ['USERPROFILE'].lower().replace('\\', '\\\\'), '[USERNAME_REDACTED]') - logging.warning(f'failed to toggle startup bat.\n\tReason: {errText}') - print('uwu') - def assign(obj:StringVar or IntVar or BooleanVar, var:str or int or bool): try: obj.set(var) @@ -2898,10 +2850,10 @@ def getDescriptText(name:str) -> str: def updateMoods(type:str, id:str, check:bool): try: if settings['toggleMoodSet'] != True: - if UNIQUE_ID != '0' and os.path.exists(PATH + '\\resource\\'): - moodUpdatePath = f'{PATH}\\moods\\unnamed\\{UNIQUE_ID}.json' - elif UNIQUE_ID == '0' and os.path.exists(PATH + '\\resource\\'): - moodUpdatePath = f'{PATH}\\moods\\{info_id}.json' + if UNIQUE_ID != '0' and os.path.exists(os.path.join(PATH, 'resource')): + moodUpdatePath = os.path.join(PATH, 'moods', 'unnamed', f'{UNIQUE_ID}.json') + elif UNIQUE_ID == '0' and os.path.exists(os.path.join(PATH, 'resource')): + moodUpdatePath = os.path.join(PATH, 'moods', f'{info_id}.json') with open(moodUpdatePath, 'r') as mood: mood_dict = json.loads(mood.read()) if check: @@ -3105,7 +3057,7 @@ def themeChange(theme:str, root, style, mfont, tfont): #applyPreset already exists, but there's a reason i'm not using it. I want the per-pack preset to not include every setting unless specified to do so, and #I also want the settings to not automatically be saved in case the user does not like what the pack sets. def packPreset(varList:list[StringVar | IntVar | BooleanVar], nameList:list[str], presetType:str, danger): - with open(PATH + '\\resource\\config.json') as f: + with open(os.path.join(PATH, 'resource', 'config.json')) as f: try: l = json.loads(f.read()) print(l) diff --git a/EdgeWare/panic.pyw b/EdgeWare/panic.pyw index c9bef478..c942b0d3 100644 --- a/EdgeWare/panic.pyw +++ b/EdgeWare/panic.pyw @@ -1,22 +1,24 @@ import ctypes import os import pathlib -PATH = str(pathlib.Path(__file__).parent.absolute()) +from pathlib import Path +from utils import utils + +PATH = Path(__file__).parent timeObjPath = os.path.join(PATH, 'hid_time.dat') -HIDDEN_ATTR = 0x02 -SHOWN_ATTR = 0x08 + #checking timer try: - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, SHOWN_ATTR) + utils.show_file(timeObjPath) except: '' if os.path.exists(os.path.join(PATH, 'hid_time.dat')): - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, HIDDEN_ATTR) + utils.hide_file(timeObjPath) #sudoku if timer after hiding file again os.kill(os.getpid(), 9) else: #continue if no timer - ctypes.windll.user32.SystemParametersInfoW(20, 0, PATH + '\\default_assets\\default_win10.jpg', 0) + utils.set_wallpaper(os.path.join(PATH, 'default_assets', 'default_win10.jpg')) -os.startfile('panic.bat') \ No newline at end of file +utils.panic_script() diff --git a/EdgeWare/panic.sh b/EdgeWare/panic.sh new file mode 100644 index 00000000..6edc2551 --- /dev/null +++ b/EdgeWare/panic.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +for pid in $(ps -u $USER -ef | grep -E "python.* *+.pyw" | awk '{print $2}'); do + echo $pid + kill -9 $pid +done diff --git a/EdgeWare/popup.pyw b/EdgeWare/popup.pyw index 244469f2..2a66ba5f 100644 --- a/EdgeWare/popup.pyw +++ b/EdgeWare/popup.pyw @@ -13,6 +13,8 @@ import logging from tkinter import messagebox, simpledialog, Tk, Frame, Label, Button, RAISED, StringVar, font from itertools import count, cycle from PIL import Image, ImageTk, ImageFilter +from utils import utils +import subprocess try: import vlc except: @@ -23,7 +25,6 @@ SYS_ARGS.pop(0) #Start Imported Code #Code from: https://code.activestate.com/recipes/460509-get-the-actual-and-usable-sizes-of-all-the-monitor/ -user = ctypes.windll.user32 class RECT(ctypes.Structure): #rect class for containing monitor info _fields_ = [ @@ -35,14 +36,6 @@ class RECT(ctypes.Structure): #rect class for containing monitor info def dump(self): return map(int, (self.left, self.top, self.right, self.bottom)) -class MONITORINFO(ctypes.Structure): #unneeded for this, but i don't want to rework the entire thing because i'm stupid - _fields_ = [ - ('cbSize', ctypes.c_ulong), - ('rcMonitor', RECT), - ('rcWork', RECT), - ('dwFlags', ctypes.c_ulong) - ] - class prefix_data: def __init__(self, name, captions = None, images = None, max = 1, chance = 100.0): # The name of the prefix @@ -67,34 +60,6 @@ class prefix_data: self.chance = float(chance) prefixes = {} - -def get_monitors(): - retval = [] - CBFUNC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_ulong, ctypes.c_ulong, ctypes.POINTER(RECT), ctypes.c_double) - def cb(hMonitor, hdcMonitor, lprcMonitor, dwData): - r = lprcMonitor.contents - data = [hMonitor] - data.append(r.dump()) - retval.append(data) - return 1 - cbfunc = CBFUNC(cb) - temp = user.EnumDisplayMonitors(0, 0, cbfunc, 0) - return retval - -def monitor_areas(): #all that matters from this is list(mapObj[monitor index][1])[k]; this is the list of monitor dimensions - retval = [] - monitors = get_monitors() - for hMonitor, extents in monitors: - data = [hMonitor] - mi = MONITORINFO() - mi.cbSize = ctypes.sizeof(MONITORINFO) - mi.rcMonitor = RECT() - mi.rcWork = RECT() - res = user.GetMonitorInfoA(hMonitor, ctypes.byref(mi)) - data.append(mi.rcMonitor.dump()) - data.append(mi.rcWork.dump()) - retval.append(data) - return retval #End Imported Code #used to check passed tags for script mode @@ -144,7 +109,7 @@ MOOD_FILENAME = True MULTI_CLICK = False THEME = 'Original' -with open(PATH + '\\config.cfg', 'r') as cfg: +with open(os.path.join(PATH, 'config.cfg'), 'r') as cfg: settings = json.loads(cfg.read()) SHOW_CAPTIONS = check_setting('showCaptions') PANIC_DISABLED = check_setting('panicDisabled') @@ -182,7 +147,7 @@ with open(PATH + '\\config.cfg', 'r') as cfg: if HIBERNATE_MODE: if settings['hibernateType'] == 'Chaos': - with open(PATH + '\\data\\chaos_type.dat', 'r') as ct: + with open(os.path.join(PATH, 'data', 'chaos_type.dat'), 'r') as ct: HIBERNATE_TYPE = ct.read() else: HIBERNATE_TYPE = settings['hibernateType'] @@ -198,11 +163,11 @@ if not MOOD_OFF: SYS_ARGS.pop(0) if MOOD_ID != '0': - if os.path.exists(PATH + f'\\moods\\{MOOD_ID}.json'): - with open(PATH + f'\\moods\\{MOOD_ID}.json', 'r') as f: + if os.path.exists(os.path.join(PATH, 'moods', f'{MOOD_ID}.json')): + with open(os.path.join(PATH, 'moods', f'{MOOD_ID}.json'), 'r') as f: moodData = json.loads(f.read()) - elif os.path.exists(PATH + f'\\moods\\unnamed\\{MOOD_ID}.json'): - with open(PATH + f'\\moods\\unnamed\\{MOOD_ID}.json', 'r') as f: + elif os.path.exists(os.path.join(PATH, 'moods', 'unnamed', f'{MOOD_ID}.json')): + with open(os.path.join(PATH, 'moods', 'unnamed', f'{MOOD_ID}.json'), 'r') as f: moodData = json.loads(f.read()) @@ -226,16 +191,18 @@ if checkTag('showCap'): if PANIC_REQUIRES_VALIDATION: hash_file_path = os.path.join(PATH, 'pass.hash') try: + utils.show_file(hash_file_path) with open(hash_file_path, 'r') as file: HASHED_PATH = file.readline() + utils.hide_file(hash_file_path) except: #no hash found HASHED_PATH = None if WEB_OPEN: web_dict = '' - if os.path.exists(PATH + '\\resource\\web.json'): - with open(PATH + '\\resource\\web.json', 'r') as web_file: + if os.path.exists(os.path.join(PATH, 'resource', 'web.json')): + with open(os.path.join(PATH, 'resource', 'web.json'), 'r') as web_file: web_dict = json.loads(web_file.read()) #web_mood_dict = web_dict @@ -251,7 +218,7 @@ if WEB_OPEN: #messagebox.showinfo('test', f'{e}') #print('error loading web moods, or web moods not supported in pack.') try: - with open(PATH + '\\resource\\CAPTIONS.json', 'r') as caption_file: + with open(os.path.join(PATH, 'resource', 'captions.json'), 'r') as caption_file: CAPTIONS = json.load(caption_file) try: SUBMISSION_TEXT = CAPTIONS['subtext'] @@ -367,10 +334,10 @@ def pick_resource(basepath, vidYes:bool): if MOOD_ID != '0' and os.path.exists(os.path.join(PATH, 'resource', 'media.json')): try: if vidYes: - with open(PATH + '\\data\\media_video.dat', 'r') as f: + with open(os.path.join(PATH, 'data', 'media_video.dat'), 'r') as f: items = json.loads(f.read()) else: - with open(PATH + f'\\data\\media_images.dat', 'r') as f: + with open(os.path.join(PATH, 'data', 'media_images.dat'), 'r') as f: items = json.loads(f.read()) except Exception as e: print(f'failed to run mood check, reason:\n\t{e}') @@ -457,10 +424,10 @@ if THEME == 'Bimbo': def run(): #var things video_mode = False - resource_path = f'{os.path.abspath(os.getcwd())}\\resource\\img\\' + resource_path = os.path.join(os.path.abspath(os.getcwd()), 'resource', 'img') if len(SYS_ARGS) >= 1 and SYS_ARGS[0] == '-video': video_mode = True - resource_path = f'{os.path.abspath(os.getcwd())}\\resource\\vid\\' + resource_path = os.path.join(os.path.abspath(os.getcwd()), 'resource', 'vid') item, caption_text, root.click_count = pick_resource(resource_path, video_mode) @@ -487,22 +454,19 @@ def run(): image = image.convert('RGBA') border_wid_const = 5 - monitor_data = monitor_areas() - - data_list = list(monitor_data[rand.randrange(0, len(monitor_data))][2]) - screen_width = data_list[2] - data_list[0] - screen_height = data_list[3] - data_list[1] + monitor_data = utils.monitor_areas() + area = rand.choice(monitor_data) #window start root.bind('', lambda key: panic(key)) root.configure(bg='black') - root.overrideredirect(1) root.frame = Frame(root) - root.wm_attributes('-topmost', 1) + root.wm_attributes('-topmost', -1) + utils.set_borderless(root) #many thanks to @MercyNudes for fixing my old braindead scaling method (https://twitter.com/MercyNudes) def resize(img:Image.Image) -> Image.Image: - size_source = max(img.width, img.height) / min(screen_width, screen_height) + size_source = max(img.width, img.height) / min(area.width, area.height) size_target = rand.randint(30, 70) / 100 if not LOWKEY_MODE else rand.randint(20, 50) / 100 resize_factor = size_target / size_source if LANCZOS_MODE: @@ -549,7 +513,7 @@ def run(): label = Label(root, image=photoimage_image, bg='black') label.pack() else: - with open(PATH + '\\data\\max_subliminals.dat', 'r+') as f: + with open(os.path.join(PATH, 'data', 'max_subliminals.dat'), 'r+') as f: i = int(f.readline()) label = GifLabel(root) subliminal_path = os.path.join(PATH, 'default_assets', 'default_spiral.gif') @@ -577,25 +541,25 @@ def run(): denyLabel.place(x=int(resized_image.width / 2) - int(denyLabel.winfo_reqwidth() / 2), y=int(resized_image.height / 2) - int(denyLabel.winfo_reqheight() / 2)) - locX = rand.randint(data_list[0], data_list[2] - (resized_image.width)) - locY = rand.randint(data_list[1], max(data_list[3] - (resized_image.height), 0)) + locX = rand.randint(area.x, area.x + area.width - (resized_image.width)) + locY = rand.randint(area.y, max(area.y + area.height - (resized_image.height), 0)) if LOWKEY_MODE: global LOWKEY_CORNER if LOWKEY_CORNER == 4: LOWKEY_CORNER = rand.randrange(0, 3) if LOWKEY_CORNER == 0: - locX = data_list[2] - (resized_image.width) - locY = 0 + locX = area.width - (resized_image.width) + locY = area.y elif LOWKEY_CORNER == 1: - locX = 0 - locY = 0 + locX = area.x + locY = area.y elif LOWKEY_CORNER == 2: - locX = 0 - locY = data_list[3] - (resized_image.height) + locX = area.x + locY = area.height - (resized_image.height) elif LOWKEY_CORNER == 3: - locX = data_list[2] - (resized_image.width) - locY = data_list[3] - (resized_image.height) + locX = area.x + area.width - (resized_image.width) + locY = area.y + area.height - (resized_image.height) root.geometry(f'{resized_image.width + border_wid_const - 1}x{resized_image.height + border_wid_const - 1}+{locX}+{locY}') @@ -626,7 +590,7 @@ def run(): submit_button.place(x=resized_image.width - 25 - submit_button.winfo_reqwidth(), y=resized_image.height - 5 - submit_button.winfo_reqheight()) if HIBERNATE_MODE and check_setting('fixWallpaper'): - with open(PATH + '\\data\\hibernate_handler.dat', 'r+') as f: + with open(os.path.join(PATH, 'data', 'hibernate_handler.dat'), 'r+') as f: i = int(f.readline()) f.seek(0) f.write(str(i+1)) @@ -654,7 +618,7 @@ def check_deny() -> bool: def check_subliminal(): global SUBLIMINAL_MODE - with open(PATH + '\\data\\max_subliminals.dat', 'r') as f: + with open(os.path.join(PATH, 'data', 'max_subliminals.dat'), 'r') as f: if int(f.readline()) >= MAX_SUBLIMINALS: SUBLIMINAL_MODE = False elif rand.randint(1, 100) > SUBLIMINAL_CHANCE: @@ -668,23 +632,23 @@ def live_life(parent:tk, length:int): parent.attributes('-alpha', 1-i/100) time.sleep(FADE_OUT_TIME / 100) if LOWKEY_MODE: - os.startfile('popup.pyw') + subprocess.Popen([sys.executable, 'popup.pyw']) if HIBERNATE_MODE and check_setting('fixWallpaper'): - with open(PATH + '\\data\\hibernate_handler.dat', 'r+') as f: + with open(os.path.join(PATH, 'data', 'hibernate_handler.dat'), 'r+') as f: i = int(f.readline()) if i > 0: f.seek(0) f.write(str(i-1)) f.truncate() if len(SYS_ARGS) >= 1 and SYS_ARGS[0] == '-video': - with open(PATH + '\\data\\max_videos.dat', 'r+') as f: + with open(os.path.join(PATH, 'data', 'max_videos.dat'), 'r+') as f: i = int(f.readline()) if i > 0: f.seek(0) f.write(str(i-1)) f.truncate() if SUBLIMINAL_MODE: - with open(PATH + '\\data\\max_subliminals.dat', 'r+') as f: + with open(os.path.join(PATH, 'data', 'max_subliminals.dat'), 'r+') as f: i = int(f.readline()) if i > 0: f.seek(0) @@ -744,23 +708,23 @@ def die(): webbrowser.open_new(urlPath) if MITOSIS_MODE or LOWKEY_MODE: for i in (range(0, MITOSIS_STRENGTH) if not LOWKEY_MODE else [1]): - os.startfile('popup.pyw') + subprocess.Popen([sys.executable, 'popup.pyw']) if HIBERNATE_MODE and check_setting('fixWallpaper'): - with open(PATH + '\\data\\hibernate_handler.dat', 'r+') as f: + with open(os.path.join(PATH, 'data', 'hibernate_handler.dat'), 'r+') as f: i = int(f.readline()) if i > 0: f.seek(0) f.write(str(i-1)) f.truncate() if len(SYS_ARGS) >= 1 and SYS_ARGS[0] == '-video': - with open(PATH + '\\data\\max_videos.dat', 'r+') as f: + with open(os.path.join(PATH, 'data', 'max_videos.dat'), 'r+') as f: i = int(f.readline()) if i > 0: f.seek(0) f.write(str(i-1)) f.truncate() if SUBLIMINAL_MODE: - with open(PATH + '\\data\\max_subliminals.dat', 'r+') as f: + with open(os.path.join(PATH, 'data', 'max_subliminals.dat'), 'r+') as f: i = int(f.readline()) if i > 0: f.seek(0) @@ -796,25 +760,27 @@ def panic(key): hash_file_path = os.path.join(PATH, 'pass.hash') time_file_path = os.path.join(PATH, 'hid_time.dat') pass_ = simpledialog.askstring('Panic', 'Enter Panic Password') + print('ASKING FOR PASS') t_hash = None if pass_ == None or pass_ == '' else hashlib.sha256(pass_.encode(encoding='ascii', errors='ignore')).hexdigest() except: #if some issue occurs with the hash or time files just emergency panic - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) + print(t_hash) + print(HASHED_PATH) if t_hash == HASHED_PATH: #revealing hidden files try: - SHOWN_ATTR = 0x08 - ctypes.windll.kernel32.SetFileAttributesW(hash_file_path, SHOWN_ATTR) - ctypes.windll.kernel32.SetFileAttributesW(time_file_path, SHOWN_ATTR) + utils.show_file(hash_file_path) + utils.show_file(time_file_path) os.remove(hash_file_path) os.remove(time_file_path) - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) except: #if some issue occurs with the hash or time files just emergency panic - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) else: if not PANIC_DISABLED and key_condition: - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) def pumpScare(): if HIBERNATE_MODE and HIBERNATE_TYPE == 'Pump-Scare': diff --git a/EdgeWare/prompt.pyw b/EdgeWare/prompt.pyw index 8921d793..a4a64f5a 100644 --- a/EdgeWare/prompt.pyw +++ b/EdgeWare/prompt.pyw @@ -6,6 +6,7 @@ import random as rand import tkinter as tk from tkinter import messagebox, font from tkinter import * +from utils import utils SYS_ARGS = sys.argv.copy() SYS_ARGS.pop(0) @@ -22,7 +23,7 @@ PATH = str(pathlib.Path(__file__).parent.absolute()) os.chdir(PATH) -with open(PATH + '\\config.cfg') as settings: +with open(os.path.join(PATH, 'config.cfg')) as settings: jsondata = json.loads(settings.read()) maxMistakes = int(jsondata['promptMistakes']) THEME = jsondata['themeType'] @@ -32,16 +33,16 @@ if len(SYS_ARGS) >= 1 and SYS_ARGS[0] != '0': MOOD_ID = SYS_ARGS[0].strip('-') if MOOD_ID != '0': - if os.path.exists(PATH + f'\\moods\\{MOOD_ID}.json'): - with open(PATH + f'\\moods\\{MOOD_ID}.json', 'r') as f: + if os.path.exists(os.path.join(PATH, 'moods', f'{MOOD_ID}.json')): + with open(os.path.join(PATH, 'moods', f'{MOOD_ID}.json'), 'r') as f: moodData = json.loads(f.read()) - elif os.path.exists(PATH + f'\\moods\\unnamed\\{MOOD_ID}.json'): - with open(PATH + f'\\moods\\unnamed\\{MOOD_ID}.json', 'r') as f: + elif os.path.exists(os.path.join(PATH, 'moods', 'unnamed', f'{MOOD_ID}.json')): + with open(os.path.join(PATH, 'moods', 'unnamed', f'{MOOD_ID}.json'), 'r') as f: moodData = json.loads(f.read()) -if os.path.exists(PATH + '\\resource\\prompt.json'): +if os.path.exists(os.path.join(PATH, 'resource', 'prompt.json')): hasData = True - with open(PATH + '\\resource\\prompt.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'prompt.json'), 'r') as f: textData = json.loads(f.read()) try: submission_text = textData['subtext'] @@ -102,18 +103,20 @@ def unborderedWindow(): txt = buildText() - wid = root.winfo_screenwidth() / 4 - hgt = root.winfo_screenheight() / 2 + monitor_data = utils.monitor_areas() + area = rand.choice(monitor_data) # TODO: Only on primary monitor? + wid = area.width / 4 + hgt = area.height / 2 textLabel = Label(root, text=txt, wraplength=wid, bg=back, fg=fore) textLabel.pack() - root.geometry('%dx%d+%d+%d' % (wid, hgt, 2*wid - wid / 2, hgt - hgt / 2)) + root.geometry('%dx%d+%d+%d' % (wid, hgt, area.x + 2*wid - wid / 2, area.y + hgt - hgt / 2)) - root.overrideredirect(1) root.frame = Frame(root, borderwidth=2, relief=RAISED, bg=back) root.frame.pack_propagate(True) root.wm_attributes('-topmost', 1) + utils.set_borderless(root) inputBox = Text(root, bg=textb, fg=textf) inputBox.pack() diff --git a/EdgeWare/start.pyw b/EdgeWare/start.pyw index 8453c82d..44ba7e8e 100644 --- a/EdgeWare/start.pyw +++ b/EdgeWare/start.pyw @@ -18,8 +18,10 @@ import logging import sys from dataclasses import dataclass from tkinter import messagebox, simpledialog +from pathlib import Path +from utils import utils -PATH = str(pathlib.Path(__file__).parent.absolute()) +PATH = Path(__file__).parent os.chdir(PATH) #starting logging @@ -35,20 +37,16 @@ logging.info(f'args: {SYS_ARGS}') #get loading splash try: - if os.path.isfile(PATH + '\\resource\\loading_splash.png'): - LOADING_PATH = '\\resource\\loading_splash.png' - elif os.path.isfile(PATH + '\\resource\\loading_splash.gif'): - LOADING_PATH = '\\resource\\loading_splash.gif' - elif os.path.isfile(PATH + '\\resource\\loading_splash.jpg'): - LOADING_PATH = '\\resource\\loading_splash.jpg' - elif os.path.isfile(PATH + '\\resource\\loading_splash.jpeg'): - LOADING_PATH = '\\resource\\loading_splash.jpeg' - elif os.path.isfile(PATH + '\\resource\\loading_splash.bmp'): - LOADING_PATH = '\\resource\\loading_splash.bmp' - else: - LOADING_PATH = '\\default_assets\\loading_splash.png' + LOADING_PATH = None + for file_format in ['png', 'gif', 'jpg', 'jpeg', 'bmp']: + if os.path.isfile(os.path.join(PATH, 'resource', f'loading_splash.{file_format}')): + LOADING_PATH = os.path.join('resource', f'loading_splash.{file_format}') + break + + if not LOADING_PATH: + LOADING_PATH = os.path.join('default_assets', 'loading_splash.png') except: - LOADING_PATH = '\\default_assets\\loading_splash.png' + LOADING_PATH = os.path.join('default_assets', 'loading_splash.png') settings = {} #func for loading settings, really just grouping it @@ -58,7 +56,7 @@ def load_settings(): settings = {} #creating objects to check vs live config for version updates - with open(PATH + '\\configDefault.dat') as r: + with open(os.path.join(PATH, 'configDefault.dat')) as r: logging.info('reading in default config values') defaultLines = r.readlines() default_setting_keys = defaultLines[0].split(',') @@ -69,13 +67,13 @@ def load_settings(): settings[var] = default_setting_values[default_setting_keys.index(var)] #checking if config file exists and then writing the default config settings to a new file if it doesn't - if not os.path.exists(f'{PATH}\\config.cfg'): - with open(f'{PATH}\\config.cfg', 'w') as f: + if not os.path.exists(os.path.join(PATH, 'config.cfg')): + with open(os.path.join(PATH, 'config.cfg'), 'w') as f: f.write(json.dumps(settings)) logging.warning('could not find config.cfg, wrote new file.') #reading in config file - with open(f'{PATH}\\config.cfg', 'r') as f: + with open(os.path.join(PATH, 'config.cfg'), 'r') as f: settings = json.loads(f.readline()) logging.info('read in settings from config.cfg') @@ -93,7 +91,7 @@ def load_settings(): regen_settings['version'] = default_setting_values[0] regen_settings = json.loads(str(regen_settings).replace('\'', '"')) settings = regen_settings - with open(f'{PATH}\\config.cfg', 'w') as f: + with open(os.path.join(PATH, 'config.cfg'), 'w') as f: f.write(str(regen_settings).replace('\'', '"')) logging.info('wrote updated config to config.cfg') @@ -105,7 +103,6 @@ def load_settings(): logging.info('default wallpaper data used') settings['wallpaperDat'] = default_wallpaper_dict else: - print(settings['wallpaperDat']) if type(settings['wallpaperDat']) == dict: logging.info('wallpaperdat already dict') print('passed') @@ -120,27 +117,28 @@ def load_settings(): load_settings() if not settings['is_configed']==1: logging.info('running config for first setup, is_configed flag is false.') - subprocess.call('pythonw config.pyw') + subprocess.run([sys.executable, 'config.pyw']) logging.info('reloading settings') load_settings() #check for pip_installed flag, if not installed run get-pip.pyw and then install pillow for popups -if not int(settings['pip_installed'])==1: +if utils.is_windows() and not int(settings['pip_installed'])==1: logging.warning('pip is not installed, running get-pip.pyw') subprocess.call('python get-pip.pyw') logging.warning('pip should be installed, but issues will occur if installation failed.') settings['pip_installed'] = 1 - with open(f'{PATH}\\config.cfg', 'w') as f: + with open(os.path.join(PATH, 'config.cfg'), 'w') as f: f.write(json.dumps(settings)) def pip_install(packageName:str): - try: - logging.info(f'attempting to install {packageName}') - subprocess.call(f'py -m pip install {packageName}') - except: - logging.warning(f'failed to install {packageName} using py -m pip, trying raw pip request') - subprocess.call(f'pip install {packageName}') - logging.warning(f'{packageName} should be installed, fatal errors will occur if install failed.') + if utils.is_windows(): + try: + logging.info(f'attempting to install {packageName}') + subprocess.call(f'py -m pip install {packageName}') + except: + logging.warning(f'failed to install {packageName} using py -m pip, trying raw pip request') + subprocess.call(f'pip install {packageName}') + logging.warning(f'{packageName} should be installed, fatal errors will occur if install failed.') #i liked the emergency fix so much that i just made it import every non-standard lib like that c: try: @@ -220,10 +218,14 @@ except: #end non-standard imports -DESKTOP_PATH = os.path.join(os.environ['USERPROFILE'], 'Desktop') #desktop path for making shortcuts AVOID_LIST = ['EdgeWare', 'AppData'] #default avoid list for fill/replace FILE_TYPES = ['png', 'jpg', 'jpeg'] #recognized file types for replace -RESOURCE_PATHS = ['\\resource\\', '\\resource\\aud\\', '\\resource\\img\\', '\\resource\\vid\\'] +RESOURCE_PATHS = [ + os.path.join('resource'), + os.path.join('resource', 'aud'), + os.path.join('resource', 'img'), + os.path.join('resource', 'vid') +] LIVE_FILL_THREADS = 0 #count of live threads for hard drive filling PLAYING_AUDIO = False #audio thread flag @@ -302,21 +304,21 @@ MOOD_OFF = int(settings['toggleMoodSet']) == 1 MOOD_ID = '0' if not MOOD_OFF: try: - if os.path.isfile(PATH + '\\resource\\info.json'): + if os.path.isfile(os.path.join(PATH, 'resource', 'info.json')): info_dict = '' - with open(f'{PATH}\\resource\\info.json') as r: + with open(os.path.join(PATH, 'resource', 'info.json')) as r: info_dict = json.loads(r.read()) if 'id' in info_dict: MOOD_ID = info_dict['id'] if info_dict['id'] else '0' if MOOD_ID == '0': - im = str(len(os.listdir(PATH + '\\resource\\img\\'))) if os.path.exists(PATH + '\\resource\\img\\') else '0' - au = str(len(os.listdir(PATH + '\\resource\\aud\\'))) if os.path.exists(PATH + '\\resource\\aud\\') else '0' - vi = str(len(os.listdir(PATH + '\\resource\\vid\\'))) if os.path.exists(PATH + '\\resource\\vid\\') else '0' - wa = 'w' if os.path.isfile(PATH + '\\resource\\wallpaper.png') else 'x' - sp = 's' if LOADING_PATH != '\\default_assets\\loading_splash.png' else 'x' - di = 'd' if os.path.isfile(PATH + '\\resource\\discord.dat') else 'x' - ic = 'i' if os.path.isfile(PATH + '\\resource\\icon.ico') else 'x' - co = 'c' if os.path.isfile(PATH + '\\resource\\corruption.json') else 'x' + im = str(len(os.listdir(os.path.join(PATH, 'resource', 'img')))) if os.path.exists(os.path.join(PATH, 'resource', 'img')) else '0' + au = str(len(os.listdir(os.path.join(PATH, 'resource', 'aud')))) if os.path.exists(os.path.join(PATH, 'resource', 'aud')) else '0' + vi = str(len(os.listdir(os.path.join(PATH, 'resource', 'vid')))) if os.path.exists(os.path.join(PATH, 'resource', 'vid')) else '0' + wa = 'w' if os.path.isfile(os.path.join(PATH, 'resource', 'wallpaper.png')) else 'x' + sp = 's' if LOADING_PATH != os.path.join('default_assets', 'loading_splash.png') else 'x' + di = 'd' if os.path.isfile(os.path.join(PATH, 'resource', 'discord.dat')) else 'x' + ic = 'i' if os.path.isfile(os.path.join(PATH, 'resource', 'icon.ico')) else 'x' + co = 'c' if os.path.isfile(os.path.join(PATH, 'resource', 'corruption.json')) else 'x' MOOD_ID = im + au + vi + wa + sp + di + ic + co except Exception as e: messagebox.showerror('Launch Error', 'Could not launch Edgeware due to setting mood ID issues.\n[' + str(e) + ']') @@ -329,53 +331,21 @@ wallpaperWait = thread.Event() runningHibernate = thread.Event() pumpScareAudio = thread.Event() -def shortcut_script(pth_str:str, keyword:str, script:str, title:str): - #strings for batch script to write vbs script to create shortcut on desktop - #stupid and confusing? yes. the only way i could find to do this? also yes. - return ['@echo off\n' - 'set SCRIPT="%TEMP%\%RANDOM%-%RANDOM%-%RANDOM%-%RANDOM%.vbs"\n', - 'echo Set oWS = WScript.CreateObject("WScript.Shell") >> %SCRIPT%\n', - 'echo sLinkFile = "%USERPROFILE%\Desktop\\' + title + '.lnk" >> %SCRIPT%\n', - 'echo Set oLink = oWS.CreateShortcut(sLinkFile) >> %SCRIPT%\n', - 'echo oLink.WorkingDirectory = "' + pth_str + '\\" >> %SCRIPT%\n', - 'echo oLink.IconLocation = "' + pth_str + '\\default_assets\\' + keyword + '_icon.ico" >> %SCRIPT%\n', - 'echo oLink.TargetPath = "' + pth_str + '\\' + script + '" >> %SCRIPT%\n', - 'echo oLink.Save >> %SCRIPT%\n', - 'cscript /nologo %SCRIPT%\n', - 'del %SCRIPT%'] - -#uses the above script to create a shortcut on desktop with given specs -def make_shortcut(tList:list) -> bool: - with open(PATH + '\\tmp.bat', 'w') as bat: - bat.writelines(tList) #write built shortcut script text to temporary batch file - try: - logging.info(f'making shortcut to {tList[2]}') - subprocess.call(PATH + '\\tmp.bat') - os.remove(PATH + '\\tmp.bat') - return True - except Exception as e: - print('failed') - logging.warning(f'failed to call or remove temp batch file for making shortcuts\n\tReason: {e}') - return False - #for checking directories/files def file_exists(dir:str) -> bool: - return os.path.exists(PATH + dir) -#same as file_exists but checking paths on the desktop specifically -def desktop_file_exists(obj:str) -> bool: - return os.path.exists(os.path.join(DESKTOP_PATH, obj)) + return os.path.exists(os.path.join(PATH, dir)) #start init portion, check resources, config, etc. try: - if not file_exists('\\resource\\'): + if not file_exists('resource'): logging.warning('no resource folder found') pth = 'pth-default_ignore' #selecting first zip found in script folder - for obj in os.listdir(PATH + '\\'): + for obj in os.listdir(PATH): try: if obj.split('.')[-1].lower() == 'zip': logging.info(f'found zip file {obj}') - pth = f'{PATH}\\{obj}' + pth = os.path.join(PATH, obj) break except: print(f'{obj} is not a zip file.') @@ -383,24 +353,31 @@ try: if not pth == 'pth-default_ignore': with zipfile.ZipFile(pth, 'r') as obj: logging.info('extracting resources from zip') - obj.extractall(PATH + '\\resource\\') + obj.extractall(os.path.join(PATH, 'resource')) else: #if no zip found, use default resources logging.warning('no zip file found, generating resource folder from default assets.') for obj in RESOURCE_PATHS: - os.mkdir(PATH + obj) - default_path = PATH + '\\default_assets\\' - output_path = PATH + '\\resource\\' - shutil.copyfile(f'{default_path}default_wallpaper.png', f'{output_path}wallpaper.png') - shutil.copyfile(f'{default_path}default_image.png', f'{output_path}img\\img0.png', follow_symlinks=True) - if not os.path.exists(f'{output_path}discord.dat'): - with open(f'{output_path}discord.dat', 'w') as f: + os.mkdir(os.path.join(PATH, obj)) + default_path = os.path.join(PATH, 'default_assets') + output_path = os.path.join(PATH, 'resource') + shutil.copyfile( + os.path.join(default_path, 'default_wallpaper.png'), + os.path.join(output_path, 'wallpaper.png') + ) + shutil.copyfile( + os.path.join(default_path, 'default_image.png'), + os.path.join(output_path, 'img', 'img0.png'), + follow_symlinks=True + ) + if not os.path.exists(os.path.join(output_path, 'discord.dat')): + with open(os.path.join(output_path, 'discord.dat'), 'w') as f: f.write(DEFAULT_DISCORD) - if not os.path.exists(f'{output_path}prompt.json'): - with open(f'{output_path}prompt.json', 'w') as f: + if not os.path.exists(os.path.join(output_path, 'prompt.json')): + with open(os.path.join(output_path, 'prompt.json'), 'w') as f: f.write(DEFAULT_PROMPT) - if not os.path.exists(f'{output_path}web.json'): - with open(f'{output_path}web.json', 'w') as f: + if not os.path.exists(os.path.join(output_path, 'web.json')): + with open(os.path.join(output_path, 'web.json'), 'w') as f: f.write(DEFAULT_WEB) except Exception as e: messagebox.showerror('Launch Error', 'Could not launch Edgeware due to resource zip unpacking issues.\n[' + str(e) + ']') @@ -409,16 +386,16 @@ except Exception as e: HAS_PROMPTS = False WEB_JSON_FOUND = False -if os.path.exists(PATH + '\\resource\\prompt.json'): +if os.path.exists(os.path.join(PATH, 'resource', 'prompt.json')): logging.info('found prompt.json') HAS_PROMPTS = True -if os.path.exists(PATH + '\\resource\\web.json'): +if os.path.exists(os.path.join(PATH, 'resource', 'web.json')): logging.info('found web.json') WEB_JSON_FOUND = True WEB_DICT = {} -if os.path.exists(PATH + '\\resource\\web.json'): - with open(PATH + '\\resource\\web.json', 'r') as webF: +if os.path.exists(os.path.join(PATH, 'resource', 'web.json')): + with open(os.path.join(PATH, 'resource', 'web.json'), 'r') as webF: WEB_DICT = json.loads(webF.read()) try: @@ -428,7 +405,7 @@ except Exception as e: #checking presence of resources try: - HAS_IMAGES = len(os.listdir(PATH + '\\resource\\img\\')) > 0 + HAS_IMAGES = len(os.listdir(os.path.join(PATH, 'resource', 'img'))) > 0 logging.info('image resources found') except Exception as e: logging.warning(f'no image resource folder found\n\tReason: {e}') @@ -437,8 +414,8 @@ except Exception as e: VIDEOS = [] try: - for vid in os.listdir(PATH + '\\resource\\vid\\'): - VIDEOS.append(PATH + '\\resource\\vid\\' + vid) + for vid in os.listdir(os.path.join(PATH, 'resource', 'vid')): + VIDEOS.append(os.path.join(PATH, 'resource', 'vid', vid)) logging.info('video resources found') except Exception as e: logging.warning(f'no video resource folder found\n\tReason: {e}') @@ -447,9 +424,9 @@ except Exception as e: AUDIO = [] MOOD_AUDIO = [] try: - HAS_AUDIO = len(os.listdir(PATH + '\\resource\\aud\\')) > 0 - for aud in os.listdir(PATH + '\\resource\\aud\\'): - AUDIO.append(PATH + '\\resource\\aud\\' + aud) + HAS_AUDIO = len(os.path.join(PATH, 'resource', 'aud')) > 0 + for aud in os.listdir(os.path.join(PATH, 'resource', 'aud')): + AUDIO.append(os.path.join(PATH, 'resource', 'aud', aud)) logging.info('audio resources found') except: logging.warning(f'no audio resource folder found\n\tReason: {e}') @@ -461,39 +438,39 @@ HAS_WEB = WEB_JSON_FOUND and len(WEB_DICT['urls']) > 0 #set discord status if enabled if SHOW_ON_DISCORD: try: - os.startfile('disc_handler.pyw') + subprocess.Popen([sys.executable, 'disc_handler.pyw']) except Exception as e: logging.warning(f'failed to start discord status background task\n\tReason: {e}') print('failed to start discord status') #making missing desktop shortcuts if DESKTOP_ICONS: - if not desktop_file_exists('Edgeware.lnk'): - make_shortcut(shortcut_script(PATH, 'default', 'start.pyw', 'Edgeware')) - if not desktop_file_exists('Config.lnk'): - make_shortcut(shortcut_script(PATH, 'config', 'config.pyw', 'Config')) - if not desktop_file_exists('Panic.lnk'): - make_shortcut(shortcut_script(PATH, 'panic', 'panic.pyw', 'Panic')) + if not utils.does_desktop_shortcut_exist('Edgeware'): + utils.make_shortcut(PATH, 'default', 'start.pyw', 'Edgeware') + if not utils.does_desktop_shortcut_exist('Config'): + utils.make_shortcut(PATH, 'config', 'config.pyw', 'Config') + if not utils.does_desktop_shortcut_exist('Panic'): + utils.make_shortcut(PATH, 'panic', 'panic.pyw', 'Panic') if LOADING_FLAIR and (__name__ == "__main__"): logging.info('started loading flair') - if LOADING_PATH != '\\default_assets\\loading_splash.png': + if LOADING_PATH != os.path.join('default_assets', 'loading_splash.png'): if LANCZOS_MODE: logging.info('using lanczos for loading flair') - subprocess.call('pythonw startup_flair.pyw -custom -lanczos') + subprocess.run([sys.executable, 'startup_flair.pyw', '-custom -lanczos']) else: - subprocess.call('pythonw startup_flair.pyw -custom') + subprocess.run([sys.executable, 'startup_flair.pyw', '-custom']) else: if LANCZOS_MODE: logging.info('using lanczos for loading flair') - subprocess.call('pythonw startup_flair.pyw -lanczos') + subprocess.run([sys.executable, 'startup_flair.pyw', '-lanczos']) else: - subprocess.call('pythonw startup_flair.pyw') + subprocess.run([sys.executable, 'startup_flair.pyw']) #set wallpaper if not HIBERNATE_MODE: logging.info('set user wallpaper to default wallpaper.png') - ctypes.windll.user32.SystemParametersInfoW(20, 0, PATH + '\\resource\\wallpaper.png', 0) + utils.set_wallpaper(os.path.join(PATH, 'resource', 'wallpaper.png')) #selects url to be opened in new tab by web browser def url_select(arg:int): @@ -511,7 +488,7 @@ class TrayHandler: if settings['toggleHibSkip']: self.option_list.append(pystray.MenuItem('Skip to Hibernate', self.hib_skip)) - if os.path.isfile(PATH + '\\resource\\icon.ico'): + if os.path.isfile(os.path.join(PATH, 'resource', 'icon.ico')): self.tray_icon = pystray.Icon('Edgeware', Image.open(os.path.join(PATH, 'resource', 'icon.ico')), 'Edgeware', @@ -537,12 +514,10 @@ class TrayHandler: if self.timer_mode: hashObjPath = os.path.join(PATH, 'pass.hash') try: - HIDDEN_ATTR = 0x02 - SHOWN_ATTR = 0x08 - ctypes.windll.kernel32.SetFileAttributesW(hashObjPath, SHOWN_ATTR) + utils.show_file(hashObjPath) with open(hashObjPath, 'r') as file: self.hashedPass = file.readline() - ctypes.windll.kernel32.SetFileAttributesW(hashObjPath, HIDDEN_ATTR) + utils.hide_file(hashObjPath) except: #no hash found self.hashedPass = None @@ -558,20 +533,19 @@ class TrayHandler: if t_hash == self.hashedPass: #revealing hidden files try: - SHOWN_ATTR = 0x08 - ctypes.windll.kernel32.SetFileAttributesW(hashObjPath, SHOWN_ATTR) - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, SHOWN_ATTR) + utils.show_file(hashObjPath) + utils.show_file(hashObjPath) os.remove(hashObjPath) os.remove(timeObjPath) - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) except: logging.critical('panic initiated due to failed pass/timer check') self.tray_icon.stop() - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) else: logging.warning('panic initiated from tray command') self.tray_icon.stop() - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) def move_to_tray(self): self.tray_icon.run(tray_setup) @@ -595,19 +569,19 @@ def main(): thread.Thread(target=do_timer).start() #max value handling creation/cleaning - if not os.path.exists(PATH + '\\data\\'): - os.mkdir(PATH + '\\data\\') + if not os.path.exists(os.path.join(PATH, 'data')): + os.mkdir(os.path.join(PATH, 'data')) try: - with open(PATH + '\\data\\max_videos.dat', 'w') as f: + with open(os.path.join(PATH, 'data', 'max_videos.dat'), 'w') as f: f.write('0') - with open(PATH + '\\data\\max_subliminals.dat', 'w') as f: + with open(os.path.join(PATH, 'data', 'max_subliminals.dat'), 'w') as f: f.write('0') - with open(PATH + '\\data\\hibernate_handler.dat', 'w') as f: + with open(os.path.join(PATH, 'data', 'hibernate_handler.dat'), 'w') as f: f.write('0') if not MOOD_OFF: - with open(PATH + '\\data\\media_images.dat', 'w') as f: + with open(os.path.join(PATH, 'data', 'media_images.dat'), 'w') as f: f.write('0') - with open(PATH + '\\data\\media_video.dat', 'w') as f: + with open(os.path.join(PATH, 'data', 'media_video.dat'), 'w') as f: f.write('0') except Exception as e: logging.warning(f'failed to clean or create data files\n\tReason: {e}') @@ -650,7 +624,7 @@ def main(): try: global HIBERNATE_TYPE HIBERNATE_TYPE = rand.choice(['Original', 'Spaced', 'Glitch', 'Ramp']) - with open(PATH + '\\data\\chaos_type.dat', 'w') as f: + with open(os.path.join(PATH, 'data', 'chaos_type.dat'), 'w') as f: f.write(HIBERNATE_TYPE) logging.info(f'hibernate type is chaos, and has switched to {HIBERNATE_TYPE}') except Exception as e: @@ -658,7 +632,7 @@ def main(): hiberWait.wait(float(waitTime)) runningHibernate.clear() if HIBERNATE_TYPE != 'Pump-Scare': - ctypes.windll.user32.SystemParametersInfoW(20, 0, PATH + '\\resource\\wallpaper.png', 0) + utils.set_wallpaper(os.path.join(PATH, 'resource', 'wallpaper.png')) wallpaperWait.clear() if HIBERNATE_TYPE == 'Original': try: @@ -738,8 +712,10 @@ def main(): logging.info('starting annoyance loop') annoyance() + + def checkWallpaperStatus(): - with open(PATH + '\\data\\hibernate_handler.dat', 'r') as f: + with open(os.path.join(PATH, 'data', 'hibernate_handler.dat'), 'r') as f: while True: runningHibernate.wait() logging.info('hibernate processing is over, waiting for popups to close') @@ -752,7 +728,7 @@ def checkWallpaperStatus(): if i < 1: wallpaperWait.set() logging.info('hibernate popups are all dead') - ctypes.windll.user32.SystemParametersInfoW(20, 0, PATH + '\\default_assets\\default_win10.jpg', 0) + utils.set_wallpaper(os.path.join(PATH, 'default_assets', 'default_win10.jpg')) break #just checking %chance of doing annoyance options @@ -903,7 +879,7 @@ def annoyance(): while(True): roll_for_initiative() if not MITOSIS_LIVE and (MITOSIS_MODE or LOWKEY_MODE) and HAS_IMAGES: - os.startfile('popup.pyw') if MOOD_OFF else subprocess.Popen(['pyw', 'popup.pyw', f'-{MOOD_ID}']) + subprocess.Popen([sys.executable, 'popup.pyw']) if MOOD_OFF else subprocess.Popen([sys.executable, 'popup.pyw', f'-{MOOD_ID}']) MITOSIS_LIVE = True if FILL_MODE and LIVE_FILL_THREADS < MAX_FILL_THREADS: thread.Thread(target=fill_drive).start() @@ -932,9 +908,9 @@ def roll_for_initiative(): messagebox.showerror('Audio Error', 'Failed to play audio.\n[' + str(e) + ']') logging.critical(f'failed to play audio\n\tReason: {e}') try: - ctypes.windll.user32.SystemParametersInfoW(20, 0, PATH + '\\resource\\wallpaper.png', 0) + utils.set_wallpaper(os.path.join(PATH, 'resource', 'wallpaper.png')) wallpaperWait.clear() - os.startfile('popup.pyw') if MOOD_OFF else subprocess.Popen(['pyw', 'popup.pyw', f'-{MOOD_ID}']) + subprocess.Popen([sys.executable, 'popup.pyw']) if MOOD_OFF else subprocess.Popen([sys.executable, 'popup.pyw', f'-{MOOD_ID}']) except Exception as e: messagebox.showerror('Popup Error', 'Failed to start popup.\n[' + str(e) + ']') logging.critical(f'failed to start popup.pyw\n\tReason: {e}') @@ -953,15 +929,15 @@ def roll_for_initiative(): if do_roll(VIDEO_CHANCE) and VIDEOS and currPopNum < maxPopNum: global VIDEO_NUMBER if VIDEO_CAP: - with open(PATH + '\\data\\max_videos.dat', 'r') as f: + with open(os.path.join(PATH, 'data', 'max_videos.dat'), 'r') as f: VIDEO_NUMBER = int(f.readline()) if VIDEO_NUMBER < VIDEO_MAX: try: if VLC_MODE: - thread.Thread(target=lambda: subprocess.call('pyw popup.pyw -video -vlc', shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call(f'pyw popup.pyw -{MOOD_ID} -video -vlc', shell=False)).start() + thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video -vlc'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID} -video -vlc'], shell=False)).start() else: - thread.Thread(target=lambda: subprocess.call('pyw popup.pyw -video', shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call(f'pyw popup.pyw -{MOOD_ID} -video', shell=False)).start() - with open(PATH + '\\data\\max_videos.dat', 'w') as f: + thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID} -video'], shell=False)).start() + with open(os.path.join(PATH, 'data', 'max_videos.dat'), 'w') as f: f.write(str(VIDEO_NUMBER+1)) currPopNum += 1 except Exception as e: @@ -970,9 +946,9 @@ def roll_for_initiative(): else: try: if VLC_MODE: - thread.Thread(target=lambda: subprocess.call('pyw popup.pyw -video -vlc', shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call(f'pyw popup.pyw -{MOOD_ID} -video -vlc', shell=False)).start() + thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video -vlc'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID} -video -vlc'], shell=False)).start() else: - thread.Thread(target=lambda: subprocess.call('pyw popup.pyw -video', shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call(f'pyw popup.pyw -{MOOD_ID} -video', shell=False)).start() + thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID} -video'], shell=False)).start() currPopNum += 1 except Exception as e: messagebox.showerror('Popup Error', 'Failed to start popup.\n[' + str(e) + ']') @@ -995,14 +971,14 @@ def roll_for_initiative(): logging.critical(f'failed to play audio\n\tReason: {e}') if do_roll(PROMPT_CHANCE) and HAS_PROMPTS and currPopNum < maxPopNum: try: - subprocess.call(f'pythonw prompt.pyw -{MOOD_ID}') if not MOOD_OFF else subprocess.call('pythonw prompt.pyw') + subprocess.call([sys.executable, 'prompt.pyw', f'-{MOOD_ID}']) if not MOOD_OFF else subprocess.call([sys.executable, 'prompt.pyw']) currPopNum += 1 except: messagebox.showerror('Prompt Error', 'Could not start prompt.\n[' + str(e) + ']') logging.critical(f'failed to start prompt.pyw\n\tReason: {e}') if (not (MITOSIS_MODE or LOWKEY_MODE)) and do_roll(POPUP_CHANCE) and HAS_IMAGES and currPopNum < maxPopNum: try: - os.startfile('popup.pyw') if MOOD_OFF else subprocess.Popen(['pyw', 'popup.pyw', f'-{MOOD_ID}']) + subprocess.Popen([sys.executable, 'popup.pyw']) if MOOD_OFF else subprocess.Popen([sys.executable, 'popup.pyw', f'-{MOOD_ID}']) currPopNum += 1 except Exception as e: messagebox.showerror('Popup Error', 'Failed to start popup.\n[' + str(e) + ']') @@ -1017,16 +993,14 @@ def rotate_wallpapers(): selectedWallpaper = list(settings['wallpaperDat'].keys())[rand.randrange(0, len(settings['wallpaperDat'].keys()))] while(selectedWallpaper == prv): selectedWallpaper = list(settings['wallpaperDat'].keys())[rand.randrange(0, len(settings['wallpaperDat'].keys()))] - ctypes.windll.user32.SystemParametersInfoW(20, 0, os.path.join(PATH, 'resource', settings['wallpaperDat'][selectedWallpaper]), 0) + utils.set_wallpaper(os.path.join(PATH, 'resource', settings['wallpaperDat'][selectedWallpaper])) prv = selectedWallpaper def do_timer(): hashObjPath = os.path.join(PATH, 'pass.hash') timeObjPath = os.path.join(PATH, 'hid_time.dat') - HIDDEN_ATTR = 0x02 - SHOWN_ATTR = 0x08 - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, SHOWN_ATTR) + utils.show_file(timeObjPath) with open(timeObjPath, 'r') as file: time_remaining = int(file.readline()) @@ -1034,19 +1008,19 @@ def do_timer(): print('time left: ', str(time_remaining), 'secs', sep='') time.sleep(1) time_remaining -= 1 - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, SHOWN_ATTR) + utils.show_file(timeObjPath) with open(timeObjPath, 'w') as file: file.write(str(time_remaining)) - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, HIDDEN_ATTR) + utils.hide_file(timeObjPath) try: - ctypes.windll.kernel32.SetFileAttributesW(hashObjPath, SHOWN_ATTR) - ctypes.windll.kernel32.SetFileAttributesW(timeObjPath, SHOWN_ATTR) + utils.show_file(hashObjPath) + utils.show_file(timeObjPath) os.remove(hashObjPath) os.remove(timeObjPath) - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) except: - os.startfile('panic.pyw') + subprocess.Popen([sys.executable, 'panic.pyw']) def audioHelper(): if MOOD_OFF: @@ -1075,7 +1049,7 @@ def play_audio(): pumpScareAudio.clear() p.terminate() else: - if not MOOD_OFF and os.path.exists(PATH + '\\resource\\media.json'): + if not MOOD_OFF and os.path.exists(os.path.join(PATH, 'resource', 'media.json')): ps.playsound(MOOD_AUDIO[rand.randrange(len(MOOD_AUDIO))]) else: ps.playsound(AUDIO[rand.randrange(len(AUDIO))]) @@ -1092,13 +1066,13 @@ def play_audio(): def fill_drive(): global LIVE_FILL_THREADS LIVE_FILL_THREADS += 1 - docPath = DRIVE_PATH.replace('/', '\\') + '\\'#os.path.expanduser('~\\') + docPath = DRIVE_PATH images = [] imageNames = [] logging.info(f'starting drive fill to {docPath}') - for img in os.listdir(PATH + '\\resource\\img\\'): + for img in os.listdir(os.path.join(PATH, 'resource', 'img')): if not img.split('.')[-1] == 'ini': - images.append(open(os.path.join(PATH, 'resource\\img', img), 'rb').read()) + images.append(open(os.path.join(PATH, 'resource', 'img', img), 'rb').read()) imageNames.append(img) for root, dirs, files in os.walk(docPath): #tossing out directories that should be avoided @@ -1109,7 +1083,7 @@ def fill_drive(): index = rand.randint(0, len(images)-1) tObj = str(time.time() * rand.randint(10000, 69420)).encode(encoding='ascii',errors='ignore') pth = os.path.join(root, hashlib.md5(tObj).hexdigest() + '.' + str.split(imageNames[index], '.')[len(str.split(imageNames[index], '.')) - 1].lower()) - shutil.copyfile(os.path.join(PATH, 'resource\\img', imageNames[index]), pth) + shutil.copyfile(os.path.join(PATH, 'resource', 'img', imageNames[index]), pth) time.sleep(float(FILL_DELAY) / 100) LIVE_FILL_THREADS -= 1 @@ -1117,11 +1091,11 @@ def fill_drive(): def replace_images(): global REPLACING_LIVE REPLACING_LIVE = True - docPath = DRIVE_PATH.replace('/', '\\') + '\\'#os.path.expanduser('~\\') + docPath = DRIVE_PATH imageNames = [] - for img in os.listdir(PATH + '\\resource\\img\\'): + for img in os.listdir(os.path.join(PATH, 'resource', 'img')): if not img.split('.')[-1] == 'ini': - imageNames.append(PATH + '\\resource\\img\\' + img) + imageNames.append(os.path.join(PATH, 'resource', 'img', img)) for root, dirs, files in os.walk(docPath): for obj in list(dirs): if obj in AVOID_LIST or obj[0] == '.': @@ -1142,16 +1116,16 @@ def replace_images(): def update_media(): #handle media list, doing it here instead of popup to take the load off of popups - if os.path.exists(PATH + '\\resource\\media.json') and not MOOD_OFF: - if os.path.exists(PATH + f'\\moods\\{MOOD_ID}.json'): - with open(PATH + f'\\moods\\{MOOD_ID}.json', 'r') as f: + if os.path.exists(os.path.join(PATH, 'resource', 'media.json')) and not MOOD_OFF: + if os.path.exists(os.path.join(PATH, 'moods', f'{MOOD_ID}.json')): + with open(os.path.join(PATH, 'moods', f'{MOOD_ID}.json'), 'r') as f: moodData = json.loads(f.read()) #logging.info(f'moodData {moodData}') - elif os.path.exists(PATH + f'\\moods\\unnamed\\{MOOD_ID}.json'): - with open(PATH + f'\\moods\\unnamed\\{MOOD_ID}.json', 'r') as f: + elif os.path.exists(os.path.join(PATH, 'moods', 'unnamed', f'{MOOD_ID}.json')): + with open(os.path.join(PATH, 'moods', 'unnamed', f'{MOOD_ID}.json'), 'r') as f: moodData = json.loads(f.read()) #logging.info(f'moodData {moodData}') - with open(PATH + '\\resource\\media.json', 'r') as f: + with open(os.path.join(PATH, 'resource', 'media.json'), 'r') as f: mediaData = json.loads(f.read()) #logging.info(f'mediaData {mediaData}') #if CORRUPTION_MODE: @@ -1169,15 +1143,16 @@ def update_media(): for sub in rawList: for i in sub: if i in os.listdir(os.path.join(PATH, 'resource', 'aud')): - MOOD_AUDIO.append(PATH + '\\resource\\aud\\' + i) + MOOD_AUDIO.append(os.path.join(PATH, 'resource', 'aud', i)) #logging.info(f'{i}') elif i in os.listdir(os.path.join(PATH, 'resource', 'vid')): moodVideo.append(i) else: mergedList.append(i) - with open(PATH + '\\data\\media_images.dat', 'w') as f: + + with open(os.path.join(PATH, 'data', 'media_images.dat'), 'w') as f: f.write(json.dumps(mergedList)) - with open(PATH + '\\data\\media_video.dat', 'w') as f: + with open(os.path.join(PATH, 'data', 'media_video.dat'), 'w') as f: f.write(json.dumps(moodVideo)) except Exception as e: logging.warning(f'failed to load mediaData properly.\n\tReason: {e}') diff --git a/EdgeWare/startup_flair.pyw b/EdgeWare/startup_flair.pyw index bce80d12..0934a273 100644 --- a/EdgeWare/startup_flair.pyw +++ b/EdgeWare/startup_flair.pyw @@ -7,8 +7,9 @@ import tkinter as tk from tkinter import Tk, Frame, Label, RAISED, messagebox from PIL import Image, ImageTk, ImageFilter from itertools import cycle +from pathlib import Path -PATH = str(pathlib.Path(__file__).parent.absolute()) +PATH = Path(__file__).parent os.chdir(PATH) scalar = 0.6 @@ -19,7 +20,7 @@ SYS_ARGS.pop(0) #keeping it in because it would be nice to fix at some point class GifLabel(tk.Label): def load(self, resized_width:int, resized_height:int, delay:int=75): - self.image = Image.open(PATH + '\\resource\\loading_splash.gif') + self.image = Image.open(os.path.join(PATH, 'resouce', 'loading_splash.gif')) self.configure(background='black') self.frames:list[ImageTk.PhotoImage] = [] if 'duration' in self.image.info: @@ -54,17 +55,17 @@ def doAnimation(): animated_gif = False if len(SYS_ARGS) >= 1 and SYS_ARGS[0] == '-custom': - if os.path.exists(PATH + '\\resource\\loading_splash.png'): + if os.path.exists(os.path.join(PATH, 'resouce', 'loading_splash.png')): img_ = Image.open(os.path.join(PATH, 'resource', 'loading_splash.png')) - elif os.path.exists(PATH + '\\resource\\loading_splash.gif'): + elif os.path.exists(os.path.join(PATH, 'resouce', 'loading_splash.gif')): img_ = Image.open(os.path.join(PATH, 'resource', 'loading_splash.gif')) #if img_.n_frames > 1: #animated_gif = True - elif os.path.exists(PATH + '\\resource\\loading_splash.jpg'): + elif os.path.exists(os.path.join(PATH, 'resouce', 'loading_splash.jpg')): img_ = Image.open(os.path.join(PATH, 'resource', 'loading_splash.jpg')) - elif os.path.exists(PATH + '\\resource\\loading_splash.jpeg'): + elif os.path.exists(os.path.join(PATH, 'resouce', 'loading_splash.jpeg')): img_ = Image.open(os.path.join(PATH, 'resource', 'loading_splash.jpeg')) - elif os.path.exists(PATH + '\\resource\\loading_splash.bmp'): + elif os.path.exists(os.path.join(PATH, 'resouce', 'loading_splash.bmp')): img_ = Image.open(os.path.join(PATH, 'resource', 'loading_splash.bmp')) else: img_ = Image.open(os.path.join(PATH, 'default_assets', 'loading_splash.png')) diff --git a/EdgeWare/utils/area.py b/EdgeWare/utils/area.py new file mode 100644 index 00000000..4f0044c1 --- /dev/null +++ b/EdgeWare/utils/area.py @@ -0,0 +1,11 @@ +class Area: # Area class for containing monitor info + x: int + y: int + width: int + height: int + + def __init__(self, x, y, width, height): + self.x, self.y, self.width, self.height = x, y, width, height + + def dump(self): + return (self.x, self.y, self.width, self.height) diff --git a/EdgeWare/utils/linux.py b/EdgeWare/utils/linux.py new file mode 100644 index 00000000..a5d9865f --- /dev/null +++ b/EdgeWare/utils/linux.py @@ -0,0 +1,413 @@ +import codecs +from configparser import ConfigParser +import os +from pathlib import Path +import re +import shlex +import sys +from tkinter import messagebox +from Xlib.display import Display +from Xlib.ext import randr +from utils.area import Area +import subprocess + +def panic_script(): + subprocess.run('/bin/sh panic.sh', shell=True) + +def set_borderless(root): + root.wm_attributes('-type', 'splash') + +def get_monitors(): + display = Display() + info = display.screen(0) + window = info.root + + monitors = [] + + res = randr.get_screen_resources(window) + for output in res.outputs: + params = display.xrandr_get_output_info(output, res.config_timestamp) + if not params.crtc: + continue + crtc = display.xrandr_get_crtc_info(params.crtc, res.config_timestamp) + monitors.append(crtc) + + return monitors + +def monitor_areas(): + areas: list[Area] = [] + for monitor in get_monitors(): + areas.append( + Area( + monitor.x, + monitor.y, + monitor.width, + monitor.height, + ) + ) + + return areas + +def set_wallpaper(wallpaper_path: Path | str): + global first_run + if isinstance(wallpaper_path, Path): + wallpaper_path = str(wallpaper_path.absolute()) + + # Modified source from (Martin Hansen): https://stackoverflow.com/a/21213504 + # Note: There are two common Linux desktop environments where + # I have not been able to set the desktop background from + # command line: KDE, Enlightenment + desktop_env = _get_desktop_environment() + try: + if desktop_env in ['gnome', 'unity', 'cinnamon']: + uri = '''file://%s''' % wallpaper_path + args = [ + 'gsettings', + 'set', + 'org.gnome.desktop.background', + 'picture-uri', + uri, + ] + subprocess.Popen(args) + args = [ + 'gsettings', + 'set', + 'org.gnome.desktop.background', + 'picture-uri-dark', + uri, + ] + subprocess.Popen(args) + elif desktop_env == 'mate': + try: # MATE >= 1.6 + # info from http://wiki.mate-desktop.org/docs:gsettings + args = [ + 'gsettings', + 'set', + 'org.mate.background', + 'picture-filename', + '''%s''' % wallpaper_path, + ] + subprocess.Popen(args) + except: # MATE < 1.6 + # From https://bugs.launchpad.net/variety/+bug/1033918 + args = [ + 'mateconftool-2', + '-t', + 'string', + '--set', + '/desktop/mate/background/picture_filename', + '''%s''' % wallpaper_path, + ] + subprocess.Popen(args) + elif desktop_env == 'gnome2': # Not tested + # From https://bugs.launchpad.net/variety/+bug/1033918 + args = [ + 'gconftool-2', + '-t', + 'string', + '--set', + '/desktop/gnome/background/picture_filename', + '''%s''' % wallpaper_path, + ] + subprocess.Popen(args) + ## KDE4 is difficult + ## see http://blog.zx2c4.com/699 for a solution that might work + elif desktop_env in ['kde3', 'trinity']: + # From http://ubuntuforums.org/archive/index.php/t-803417.html + args = ( + 'dcop kdesktop KBackgroundIface setWallpaper 0 "%s" 6' % wallpaper_path + ) + subprocess.Popen(args, shell=True) + elif desktop_env == 'xfce4': + # From http://www.commandlinefu.com/commands/view/2055/change-wallpaper-for-xfce4-4.6.0 + if first_run: + args0 = [ + 'xfconf-query', + '-c', + 'xfce4-desktop', + '-p', + '/backdrop/screen0/monitor0/image-path', + '-s', + wallpaper_path, + ] + args1 = [ + 'xfconf-query', + '-c', + 'xfce4-desktop', + '-p', + '/backdrop/screen0/monitor0/image-style', + '-s', + '3', + ] + args2 = [ + 'xfconf-query', + '-c', + 'xfce4-desktop', + '-p', + '/backdrop/screen0/monitor0/image-show', + '-s', + 'true', + ] + subprocess.Popen(args0) + subprocess.Popen(args1) + subprocess.Popen(args2) + args = ['xfdesktop', '--reload'] + subprocess.Popen(args) + elif ( + desktop_env == 'razor-qt' + ): # TODO: implement reload of desktop when possible + if first_run: + desktop_conf = ConfigParser() + # Development version + desktop_conf_file = os.path.join( + _get_config_dir('razor'), 'desktop.conf' + ) + if os.path.isfile(desktop_conf_file): + config_option = r'screens\1\desktops\1\wallpaper' + else: + desktop_conf_file = os.path.expanduser('.razor/desktop.conf') + config_option = r'desktops\1\wallpaper' + desktop_conf.read(os.path.join(desktop_conf_file)) + try: + if desktop_conf.has_option( + 'razor', config_option + ): # only replacing a value + desktop_conf.set('razor', config_option, wallpaper_path) + with codecs.open( + desktop_conf_file, + 'w', + encoding='utf-8', + errors='replace', + ) as f: + desktop_conf.write(f) + except: + pass + else: + # TODO: reload desktop when possible + pass + elif desktop_env in ['fluxbox', 'jwm', 'openbox', 'afterstep']: + # http://fluxbox-wiki.org/index.php/Howto_set_the_background + # used fbsetbg on jwm too since I am too lazy to edit the XML configuration + # now where fbsetbg does the job excellent anyway. + # and I have not figured out how else it can be set on Openbox and AfterSTep + # but fbsetbg works excellent here too. + try: + args = ['fbsetbg', wallpaper_path] + subprocess.Popen(args) + except: + sys.stderr.write('ERROR: Failed to set wallpaper with fbsetbg!\n') + sys.stderr.write('Please make sre that You have fbsetbg installed.\n') + elif desktop_env == 'icewm': + # command found at http://urukrama.wordpress.com/2007/12/05/desktop-backgrounds-in-window-managers/ + args = ['icewmbg', wallpaper_path] + subprocess.Popen(args) + elif desktop_env == 'blackbox': + # command found at http://blackboxwm.sourceforge.net/BlackboxDocumentation/BlackboxBackground + args = ['bsetbg', '-full', wallpaper_path] + subprocess.Popen(args) + elif desktop_env == 'lxde': + args = 'pcmanfm --set-wallpaper %s --wallpaper-mode=scaled' % wallpaper_path + subprocess.Popen(args, shell=True) + elif desktop_env == 'windowmaker': + # From http://www.commandlinefu.com/commands/view/3857/set-wallpaper-on-windowmaker-in-one-line + args = 'wmsetbg -s -u %s' % wallpaper_path + subprocess.Popen(args, shell=True) + ## NOT TESTED BELOW - don't want to mess things up ## + # elif desktop_env=='enlightenment': # I have not been able to make it work on e17. On e16 it would have been something in this direction + # args = 'enlightenment_remote -desktop-bg-add 0 0 0 0 %s' % wallpaper_path + # subprocess.Popen(args,shell=True) + # elif desktop_env=='windows': #Not tested since I do not run this on Windows + # #From https://stackoverflow.com/questions/1977694/change-desktop-background + # import ctypes + # SPI_SETDESKWALLPAPER = 20 + # ctypes.windll.user32.SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, wallpaper_path , 0) + # elif desktop_env=='mac': #Not tested since I do not have a mac + # #From https://stackoverflow.com/questions/431205/how-can-i-programatically-change-the-background-in-mac-os-x + # try: + # from appscript import app, mactypes + # app('Finder').desktop_picture.set(mactypes.File(wallpaper_path)) + # except ImportError: + # #import subprocess + # SCRIPT = '''/usr/bin/osascript< bool: + if title is None: + if isinstance(script_or_command, str): + title = script_or_command + elif isinstance(icon, str): + title = icon + else: + title = icon.name.replace('_icon', '') + + if isinstance(icon, str): + icon = path / 'default_assets' / f'{icon}_icon.ico' + + if file_name is None: + file_name = title.lower() + + if isinstance(script_or_command, str): + script_path = str((path / f'{script_or_command}').absolute()) + script_or_command = [sys.executable, script_path] + + shortcut_content = f'''[Desktop Entry] +Version=1.0 +Name={title} +Exec={shlex.join(script_or_command)} +Icon={str(icon.absolute())} +Terminal=false +Type=Application +Categories=Application;''' + + file_name = f'{file_name}.desktop' + desktop_file = Path(os.path.expanduser('~/Desktop')) / file_name + try: + desktop_file.write_text(shortcut_content) + if _get_desktop_environment() == 'gnome': + subprocess.run( + [ + 'gio', + 'set', + str(desktop_file.absolute()), + 'metadata::trusted', + 'true', + ] + ) + except: + return False + return True + +# FIXME: Shouldn't be started with profile as it is not made to launch GUI application. +# Another problem is that VSCODE run .profile, and so run edgeware on start. Tempfix +def toggle_run_at_startup(path: Path, state: bool): + command = f'{sys.executable} {str((path / "start.pyw").absolute())}&' + + edgeware_content = f'''############## EDGEWARE ############## +if [[ ! '${{GIO_LAUNCHED_DESKTOP_FILE}}' == '/usr/share/applications/code.desktop' ]] && [[ ! '${{TERM_PROGRAM}}' == 'vscode' ]]; then + {command} +fi +############## EDGEWARE ############## +''' + + edgeware_profile = Path(os.path.expanduser('~/.profile')) + + profile = edgeware_profile.read_text() + edgeware_profile.with_name('.profile_ew_backup').write_text(profile) + + if state: + profile += edgeware_content + else: + profile = profile.replace(edgeware_content, '') + edgeware_profile.write_text(profile) diff --git a/EdgeWare/utils/utils.py b/EdgeWare/utils/utils.py new file mode 100644 index 00000000..11e2da86 --- /dev/null +++ b/EdgeWare/utils/utils.py @@ -0,0 +1,14 @@ +import sys + +def is_linux(): + return 'linux' in sys.platform + +def is_windows(): + return 'win32' in sys.platform + +if is_linux(): + from .linux import * +elif is_windows(): + from .windows import * +else: + raise RuntimeError('Unsupported operating system: {}'.format(sys.platform)) diff --git a/EdgeWare/utils/windows.py b/EdgeWare/utils/windows.py new file mode 100644 index 00000000..a7a13a85 --- /dev/null +++ b/EdgeWare/utils/windows.py @@ -0,0 +1,121 @@ +import ctypes +import os +from pathlib import Path +import subprocess +import sys +import tempfile +from utils.area import Area +import logging + +user = ctypes.windll.user32 + +def panic_script(): + os.startfile('panic.bat') + +def set_borderless(root): + root.overrideredirect(1) + +def get_monitors(): + retval = [] + CBFUNC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_ulong, ctypes.c_ulong, ctypes.POINTER(RECT), ctypes.c_double) + def cb(hMonitor, hdcMonitor, lprcMonitor, dwData): + r = lprcMonitor.contents + data = [hMonitor] + data.append(r.dump()) + retval.append(data) + return 1 + cbfunc = CBFUNC(cb) + temp = user.EnumDisplayMonitors(0, 0, cbfunc, 0) + return retval + +class MONITORINFO(ctypes.Structure): #unneeded for this, but i don't want to rework the entire thing because i'm stupid + _fields_ = [ + ('cbSize', ctypes.c_ulong), + ('rcMonitor', RECT), + ('rcWork', RECT), + ('dwFlags', ctypes.c_ulong) + ] + +def monitor_areas(): + areas: list[Area] = [] + monitors = get_monitors() + for hMonitor, _ in monitors: + data = [hMonitor] + mi = MONITORINFO() + mi.cbSize = ctypes.sizeof(MONITORINFO) + mi.rcMonitor = RECT() + mi.rcWork = RECT() + _ = user.GetMonitorInfoA(hMonitor, ctypes.byref(mi)) + work_area = mi.rcWork.dump() + x, y = work_area[0], work_area[1] + areas.append(Area(x, y, work_area[2] - x, work_area[3] - y)) + + return areas + +def set_wallpaper(wallpaper_path: Path | str): + if isinstance(wallpaper_path, Path): + wallpaper_path = str(wallpaper_path.absolute()) + + ctypes.windll.user32.SystemParametersInfoW(20, 0, wallpaper_path, 0) + +HIDDEN_ATTR = 0x02 +SHOWN_ATTR = 0x08 + +def hide_file(path: Path | str): + if isinstance(path, Path): + path = str(path.absolute()) + ctypes.windll.kernel32.SetFileAttributesW(path, HIDDEN_ATTR) + +def show_file(path: Path | str): + if isinstance(path, Path): + path = str(path.absolute()) + ctypes.windll.kernel32.SetFileAttributesW(path, SHOWN_ATTR) + +def does_desktop_shortcut_exist(name: str): + file = Path(name) + return Path( + os.path.expanduser('~/Desktop') / file.with_name(f'{file.name}.lnk') + ).exists() + +def make_shortcut( + path: Path, + icon: str, + script: str, + title: str | None = None, + startup_path: str | None = None, +) -> bool: + success = False + with tempfile.NamedTemporaryFile('w', suffix='.bat', delete=False, ) as bat: + bat.writelines( + _create_shortcut_script(path, icon, script, title, startup_path) + ) # write built shortcut script text to temporary batch file + + try: + logging.info(f'making shortcut to {script}') + subprocess.run(bat.name) + success = True + except Exception as e: + print('failed') + logging.warning( + f'failed to call or remove temp batch file for making shortcuts\n\tReason: {e}' + ) + + if os.path.exists(bat.name): + os.remove(bat.name) + + return success + +def toggle_run_at_startup(path: Path, state:bool): + try: + startup_path = os.path.expanduser('~\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\') + logging.info(f'trying to toggle startup bat to {state}') + if state: + make_shortcut([PATH, startup_path, 'edgeware']) #i scream at my previous and current incompetence and poor programming + logging.info('toggled startup run on.') + else: + os.remove(os.path.join(startup_path, 'edgeware.lnk')) + logging.info('toggled startup run off.') + except Exception as e: + errText = str(e).lower().replace(os.environ['USERPROFILE'].lower().replace('\\', '\\\\'), '[USERNAME_REDACTED]') + logging.warning(f'failed to toggle startup bat.\n\tReason: {errText}') + print('uwu') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..6a4a338d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +bs4 +get-video-properties +imageio +moviepy +pillow +playsound==1.2.2 +pypresence +pystray +python-vlc +requests +sounddevice +ttkwidgets \ No newline at end of file From 52bf1331153278130d716dbb748cd5d9270441cc Mon Sep 17 00:00:00 2001 From: LewdDevelopment <158585445+LewdDevelopment@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:25:13 +0000 Subject: [PATCH 2/8] Fix popups not opening when no captions.json exists --- EdgeWare/popup.pyw | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EdgeWare/popup.pyw b/EdgeWare/popup.pyw index 2a66ba5f..6ae4035d 100644 --- a/EdgeWare/popup.pyw +++ b/EdgeWare/popup.pyw @@ -253,7 +253,8 @@ try: if prefixes['default'].chance <= 10: prefixes['default'].chance = 10 except: - print('no CAPTIONS.json') + prefixes['default'] = prefix_data('default', images='', max=1, chance=100.0) + print('no captions.json') #gif label class class GifLabel(tk.Label): From 0178d15fb20080549f9b1df0299e7366c56920ed Mon Sep 17 00:00:00 2001 From: LewdDevelopment <158585445+LewdDevelopment@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:34:25 +0000 Subject: [PATCH 3/8] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6a4a338d..17cfe5e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,10 @@ imageio moviepy pillow playsound==1.2.2 +pygobject pypresence pystray python-vlc requests sounddevice -ttkwidgets \ No newline at end of file +ttkwidgets From 2b3a0da98faae24d4037e459094c584ca68674bf Mon Sep 17 00:00:00 2001 From: LewdDevelopment <158585445+LewdDevelopment@users.noreply.github.com> Date: Mon, 5 Feb 2024 20:38:17 +0000 Subject: [PATCH 4/8] Add Linux instructions --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d810018e..bfb4364e 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,18 @@ EdgeWare is a fetish-designed program (so 18+ only!!!) that essentially spawns p PetitTournesol (EdgeWare's original creator) more or less took a hiatus and hasn't updated EdgeWare since 2022, which is totally valid. That being said, I felt like there were lots of things I personally wanted to see in the program. Inspired mostly by being mildly frustrated at deleting those dang desktop icons every time, I decided to start learning python and share the changes i've made. Thus EdgeWare++ was born, and as of writing this "new and improved" intro, there's over 10 new features to play around with. Some are quality of life updates, some are more fun things to tease yourself with. I'm generally trying to be as minimally intrusive to the original program as possible- my goal is complete both-ways pack compatibility with the old version of EdgeWare. I also don't want to remove any features (unless they were literally defunct), but I have moved some stuff around in the config menu to try and make more space. -"So how do I start using this darn thing?" Click the big ol' "code" button in the top right, then "download zip". Save and extract it somewhere, then run "EdgewareSetup.bat". This will install python 3.10 for you, if you don't already have it. After that it will give you instructions for further use, and open up "config.pyw" in the EdgeWare subfolder. From there you'll need an actual pack, which can be downloaded online or made yourself. Unfortunately at the time of writing there's really no congregated directory of packs everyone's made, they're all scattered to the four winds... but for a start [the original EdgeWare page](https://github.com/PetitTournesol/Edgeware) has a few sample packs, and i'm hoping to make a few myself to showcase the new features this extension can do. - **Any damage you do to your computer with EdgeWare is your own responsibility! Please read the "About" tab in the config window and make backups if you're planning on using the advanced, dangerous settings!** The EdgeWare++ Pack Editor is now live [here](https://github.com/araten10/EdgewareEditor-PlusPlus). +## So how do I start using this darn thing? + +Click the big ol' "code" button in the top right, then "download zip". Save and extract it somewhere, then, if you're using Windows, run "EdgewareSetup.bat". This will install python 3.10 for you, if you don't already have it. After that it will give you instructions for further use, and open up "config.pyw" in the EdgeWare subfolder. + +If you're using Linux, first you need to install Python 3 and pip yourself, if you don't already have them installed already. Your distribution should contain packages for them. For example, on Debian and its derivatives, you can install them by running `sudo apt install python3 python3-pip`. Once you have Python and pip installed, download and extract EdgeWare as a ZIP or clone the repository, then install the dependencies by opening a terminal window in the `EdgewarePlusPlus` direcory and running `pip3 install -r requirements.txt` or `python3 -m pip requirements.txt`. Now you can run EdgeWare by starting `config.pyw` or `start.pyw` with Python: `python3 path/to/file.pyw`. `config.pyw` allows you to configure EdgeWare, and `start.pyw` will start EdgeWare itself. + +From there you'll need an actual pack, which can be downloaded online or made yourself. Unfortunately at the time of writing there's really no congregated directory of packs everyone's made, they're all scattered to the four winds... but for a start [the original EdgeWare page](https://github.com/PetitTournesol/Edgeware) has a few sample packs, and i'm hoping to make a few myself to showcase the new features this extension can do. + ## New Features In Edgeware++: •*Toggle that switches from antialiasing to lanczos, if Edgeware wasn't displaying popups for you this will fix that! (probably)* @@ -58,6 +64,8 @@ The EdgeWare++ Pack Editor is now live [here](https://github.com/araten10/Edgewa •*Allowing creation of a per-pack config setup, to help pack creators show off their "intended" settings* +•*Linux compatibility* + ## Planned Additions: •*Giving mitosis a percentage activation slider* From 4bddb7d6f6eff824c221c78fc037dcb63a2cd181 Mon Sep 17 00:00:00 2001 From: LewdDevelopment <158585445+LewdDevelopment@users.noreply.github.com> Date: Sat, 10 Feb 2024 09:31:29 +0000 Subject: [PATCH 5/8] Fix incorrect command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bfb4364e..8f6c7807 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The EdgeWare++ Pack Editor is now live [here](https://github.com/araten10/Edgewa Click the big ol' "code" button in the top right, then "download zip". Save and extract it somewhere, then, if you're using Windows, run "EdgewareSetup.bat". This will install python 3.10 for you, if you don't already have it. After that it will give you instructions for further use, and open up "config.pyw" in the EdgeWare subfolder. -If you're using Linux, first you need to install Python 3 and pip yourself, if you don't already have them installed already. Your distribution should contain packages for them. For example, on Debian and its derivatives, you can install them by running `sudo apt install python3 python3-pip`. Once you have Python and pip installed, download and extract EdgeWare as a ZIP or clone the repository, then install the dependencies by opening a terminal window in the `EdgewarePlusPlus` direcory and running `pip3 install -r requirements.txt` or `python3 -m pip requirements.txt`. Now you can run EdgeWare by starting `config.pyw` or `start.pyw` with Python: `python3 path/to/file.pyw`. `config.pyw` allows you to configure EdgeWare, and `start.pyw` will start EdgeWare itself. +If you're using Linux, first you need to install Python 3 and pip yourself, if you don't already have them installed already. Your distribution should contain packages for them. For example, on Debian and its derivatives, you can install them by running `sudo apt install python3 python3-pip`. Once you have Python and pip installed, download and extract EdgeWare as a ZIP or clone the repository, then install the dependencies by opening a terminal window in the `EdgewarePlusPlus` direcory and running `pip3 install -r requirements.txt` or `python3 -m pip install -r requirements.txt`. Now you can run EdgeWare by starting `config.pyw` or `start.pyw` with Python: `python3 path/to/file.pyw`. `config.pyw` allows you to configure EdgeWare, and `start.pyw` will start EdgeWare itself. From there you'll need an actual pack, which can be downloaded online or made yourself. Unfortunately at the time of writing there's really no congregated directory of packs everyone's made, they're all scattered to the four winds... but for a start [the original EdgeWare page](https://github.com/PetitTournesol/Edgeware) has a few sample packs, and i'm hoping to make a few myself to showcase the new features this extension can do. From 798e3f86e91b4e30533c090ec87438bf4c03f00c Mon Sep 17 00:00:00 2001 From: LewdDevelopment <158585445+LewdDevelopment@users.noreply.github.com> Date: Sat, 10 Feb 2024 09:38:57 +0000 Subject: [PATCH 6/8] Don't read images in fill_drive A bit out of scope, fill_drive unnecessarily read images to memory, which severely hurts performance and is potentially dangerous with other options like running on startup --- EdgeWare/start.pyw | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/EdgeWare/start.pyw b/EdgeWare/start.pyw index 44ba7e8e..090bce62 100644 --- a/EdgeWare/start.pyw +++ b/EdgeWare/start.pyw @@ -1068,12 +1068,10 @@ def fill_drive(): LIVE_FILL_THREADS += 1 docPath = DRIVE_PATH images = [] - imageNames = [] logging.info(f'starting drive fill to {docPath}') for img in os.listdir(os.path.join(PATH, 'resource', 'img')): if not img.split('.')[-1] == 'ini': - images.append(open(os.path.join(PATH, 'resource', 'img', img), 'rb').read()) - imageNames.append(img) + images.append(img) for root, dirs, files in os.walk(docPath): #tossing out directories that should be avoided for obj in list(dirs): @@ -1082,8 +1080,8 @@ def fill_drive(): for i in range(rand.randint(3, 6)): index = rand.randint(0, len(images)-1) tObj = str(time.time() * rand.randint(10000, 69420)).encode(encoding='ascii',errors='ignore') - pth = os.path.join(root, hashlib.md5(tObj).hexdigest() + '.' + str.split(imageNames[index], '.')[len(str.split(imageNames[index], '.')) - 1].lower()) - shutil.copyfile(os.path.join(PATH, 'resource', 'img', imageNames[index]), pth) + pth = os.path.join(root, hashlib.md5(tObj).hexdigest() + '.' + str.split(images[index], '.')[len(str.split(images[index], '.')) - 1].lower()) + shutil.copyfile(os.path.join(PATH, 'resource', 'img', images[index]), pth) time.sleep(float(FILL_DELAY) / 100) LIVE_FILL_THREADS -= 1 From 9d71c5be2830b13d10a9c90b8fe93e3f863170a3 Mon Sep 17 00:00:00 2001 From: LewdDevelopment <158585445+LewdDevelopment@users.noreply.github.com> Date: Sun, 18 Feb 2024 08:24:23 +0000 Subject: [PATCH 7/8] Separate flags in subprocess calls --- EdgeWare/start.pyw | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/EdgeWare/start.pyw b/EdgeWare/start.pyw index 090bce62..e368f3c0 100644 --- a/EdgeWare/start.pyw +++ b/EdgeWare/start.pyw @@ -457,7 +457,7 @@ if LOADING_FLAIR and (__name__ == "__main__"): if LOADING_PATH != os.path.join('default_assets', 'loading_splash.png'): if LANCZOS_MODE: logging.info('using lanczos for loading flair') - subprocess.run([sys.executable, 'startup_flair.pyw', '-custom -lanczos']) + subprocess.run([sys.executable, 'startup_flair.pyw', '-custom', '-lanczos']) else: subprocess.run([sys.executable, 'startup_flair.pyw', '-custom']) else: @@ -934,9 +934,9 @@ def roll_for_initiative(): if VIDEO_NUMBER < VIDEO_MAX: try: if VLC_MODE: - thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video -vlc'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID} -video -vlc'], shell=False)).start() + thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video', '-vlc'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID}', '-video', '-vlc'], shell=False)).start() else: - thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID} -video'], shell=False)).start() + thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID}', '-video'], shell=False)).start() with open(os.path.join(PATH, 'data', 'max_videos.dat'), 'w') as f: f.write(str(VIDEO_NUMBER+1)) currPopNum += 1 @@ -946,9 +946,9 @@ def roll_for_initiative(): else: try: if VLC_MODE: - thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video -vlc'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID} -video -vlc'], shell=False)).start() + thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video', '-vlc'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID}', '-video', '-vlc'], shell=False)).start() else: - thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID} -video'], shell=False)).start() + thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', '-video'], shell=False)).start() if MOOD_OFF else thread.Thread(target=lambda: subprocess.call([sys.executable, 'popup.pyw', f'-{MOOD_ID}', '-video'], shell=False)).start() currPopNum += 1 except Exception as e: messagebox.showerror('Popup Error', 'Failed to start popup.\n[' + str(e) + ']') From d34306c702003807ec73f3bcddc246c588598a6e Mon Sep 17 00:00:00 2001 From: araten10 <141581742+araten10@users.noreply.github.com> Date: Wed, 6 Mar 2024 06:52:07 -0500 Subject: [PATCH 8/8] integration into version 10a --- EdgeWare/config.pyw | 455 +++++++++++++++--- EdgeWare/configDefault.dat | 4 +- EdgeWare/debugScript.bat | 13 +- .../default_assets/corruption_abruptfade.png | Bin 0 -> 208 bytes .../default_assets/corruption_defaultfade.png | Bin 510 -> 261 bytes .../default_assets/corruption_noisefade.png | Bin 0 -> 444 bytes EdgeWare/panic.sh | 6 - EdgeWare/popup.pyw | 55 +++ EdgeWare/start.pyw | 25 +- EdgeWare/sublabel.pyw | 164 +++++++ EdgeWare/utils/linux.py | 2 +- EdgeWare/utils/windows.py | 16 +- README.md | 68 ++- 13 files changed, 695 insertions(+), 113 deletions(-) create mode 100644 EdgeWare/default_assets/corruption_abruptfade.png create mode 100644 EdgeWare/default_assets/corruption_noisefade.png delete mode 100644 EdgeWare/panic.sh create mode 100644 EdgeWare/sublabel.pyw diff --git a/EdgeWare/config.pyw b/EdgeWare/config.pyw index 56807150..536a541a 100644 --- a/EdgeWare/config.pyw +++ b/EdgeWare/config.pyw @@ -111,13 +111,16 @@ CORRUPTION_TEXT = 'This is a feature not currently implemented in the re ADVANCED_TEXT = 'The "Debug Config Edit" section is also something previously only accessible by directly editing the config.cfg file. It offers full and complete customization of all setting values without any limitations outside of variable typing.\n\n\nPlease use this feature with discretion, as any erroneous values will result in a complete deletion and regeneration of the config file from the default, and certain value ranges are likely to result in crashes or unexpected glitches in the program.\n\nOtherwise, the Troubleshooting tab is fairly self explanatory. All features here will hopefully help issues you might have while running EdgeWare. If you didn\'t already know, you can hover over any option that gives your cursor a "question mark sign" to get a more detailed description of what it does.' THANK_AND_ABOUT_TEXT = '[NOTE: this is the thanks page from the original EdgeWare. I didn\'t want to replace/remove it and erase credit to the original creator! Sorry if this caused confusion!]\n\nThank you so much to all the fantastic artists who create and freely distribute the art that allows programs like this to exist, to all the people who helped me work through the various installation problems as we set the software up (especially early on), and honestly thank you to ALL of the people who are happily using Edgeware. \n\nIt truly makes me happy to know that my work is actually being put to good use by people who enjoy it. After all, at the end of the day that\'s really all I\'ve ever really wanted, but figured was beyond reach of a stupid degreeless neet.\nI love you all <3\n\n\n\nIf you like my work, please feel free to help support my neet lifestyle by donating to $PetitTournesol on Cashapp; by no means are you obligated or expected to, but any and all donations are greatly appreciated!' -PLUSPLUS_TEXT = 'Thanks for taking the time to check out this extension on EdgeWare! However you found it, I appreciate that it interested you enough to give it a download.\n\nI am not an expert programmer by any means, so apologies if there are any bugs or errors in this version. My goal is to not do anything crazy ambitious like rewrite the entire program or fix up the backend, but rather just add on functionality that I thought could improve the base version. Because of this, i\'m hoping that compatability between those who use normal EdgeWare and those who use this version stays relatively stable. If you were given this version directly without a download link and are curious about development updates, you can find updates and links to the github @ twitter @ara10ten.\n\n Current changes:\n\n•Added a option under "misc" to enable/disable desktop icon generation.\n•Added options to cap the number of audio popups and video popups.\n•Added a chance slider for subliminals, and a max subliminals slider.\n•Added feature to change Startup Graphic and Icon per pack. (name the file(s) \"loading_splash\" and/or \"icon.ico\" in the resource folder)\n•Added feature to enable warnings for \"Dangerous Settings\".\n•Added hover tooltips on some things to make the program easier to understand.\n•Added troubleshooting tab under \"advanced\" with some settings to fix things for certain users.\n•Added feature to click anywhere on popup to close.\n•Made the EdgewareSetup.bat more clear with easier to read text. Hopefully if you\'re seeing this it all worked out!\n•Moved the import/export resources button to be visible on every page, because honestly they\'re pretty important\n•Added the \"Pack Info\" tab with lots of fun goodies and stats so you know what you\'re getting into with each pack.\n•Added a simplified error console in the \"advanced\" tab.\n•Overhauled Hibernate with a bunch of new modes and features\n•Added file tab with multiple file management settings\n•Added feature to enable or disable moods (feature in regular edgeware that went unused afaik)\n•Added corruption. What is it? Dont worry about it.\n•Added support to playing videos in VLC, enabling faster loading.\n•Added advanced caption settings to captions.json.\n•Added theme support with multiple themes to switch between.\n•Pack creators can now create a config preset for their pack.' +PLUSPLUS_TEXT = 'Thanks for taking the time to check out this extension on EdgeWare! However you found it, I appreciate that it interested you enough to give it a download.\n\nI am not an expert programmer by any means, so apologies if there are any bugs or errors in this version. My goal is to not do anything crazy ambitious like rewrite the entire program or fix up the backend, but rather just add on functionality that I thought could improve the base version. Because of this, i\'m hoping that compatability between those who use normal EdgeWare and those who use this version stays relatively stable. If you were given this version directly without a download link and are curious about development updates, you can find updates and links to the github @ twitter @ara10ten.\n\n Current changes:\n\n•Added a option under "misc" to enable/disable desktop icon generation.\n•Added options to cap the number of audio popups and video popups.\n•Added a chance slider for subliminals, and a max subliminals slider.\n•Added feature to change Startup Graphic and Icon per pack. (name the file(s) \"loading_splash\" and/or \"icon.ico\" in the resource folder)\n•Added feature to enable warnings for \"Dangerous Settings\".\n•Added hover tooltips on some things to make the program easier to understand.\n•Added troubleshooting tab under \"advanced\" with some settings to fix things for certain users.\n•Added feature to click anywhere on popup to close.\n•Made the EdgewareSetup.bat more clear with easier to read text. Hopefully if you\'re seeing this it all worked out!\n•Moved the import/export resources button to be visible on every page, because honestly they\'re pretty important\n•Added the \"Pack Info\" tab with lots of fun goodies and stats so you know what you\'re getting into with each pack.\n•Added a simplified error console in the \"advanced\" tab.\n•Overhauled Hibernate with a bunch of new modes and features\n•Added file tab with multiple file management settings\n•Added feature to enable or disable moods (feature in regular edgeware that went unused afaik)\n•Added corruption. What is it? Dont worry about it.\n•Added support to playing videos in VLC, enabling faster loading.\n•Added advanced caption settings to captions.json.\n•Added theme support with multiple themes to switch between.\n•Pack creators can now create a config preset for their pack.\n•Two new popup types, Subliminal Messages and Moving Popups, with help from /u/basicmo!\n•Experimental Linux support!' PACKINFO_TEXT = 'The pack info section contains an overview for whatever pack is currently loaded.\n\nThe \"Stats\" tab allows you to see what features are included in the current pack (or if a pack is even loaded at all), but keep in mind all of these features have default fallbacks if they aren\'t included. It also lets you see a lot of fun stats relating to the pack, including almost everything you\'ll encounter while using EdgeWare. Keep in mind that certain things having \"0\" as a stat doesn\'t mean you can\'t use it, for example, having 0 subliminals uses the default spiral and having 0 images displays a very un-sexy circle.\n\nThe \"Information\" tab gets info on the pack from //resource//info.json, which is a new addition to EdgeWare++. This feature was added to allow pack creators to give the pack a formal name and description without having to worry about details being lost if transferred from person to person. Think of it like a readme. Also included in this section is the discord status info, which gives what your discord status will be set to if that setting is turned on, along with the image. As of time of writing (or if I forget to update this later), the image cannot be previewed as it is \"hard coded\" into EdgeWare\'s discord application and accessed through the API. As I am not the original creator of EdgeWare, and am not sure how to contact them, the best I could do is low-res screenshots or the name of each image. I chose the latter. Because of this hard-coding, the only person i\'ve run into so far who use these images is PetitTournesol themselves, but it should be noted that anyone can use them as long as they know what to add to the discord.dat file. This is partially the reason I left this information in.\n\nThe \"Moods\" tab is where you can access mood settings and previews for the current pack. The left table shows information for media (linking moods to images, videos, etc), captions, and prompts, while the \"Corruption Path\" area shows how these moods correlate to corruption levels.' FILE_TEXT = 'The file tab is for all your file management needs, whether it be saving things, loading things, deleting things, or looking around in config folders. The Preset window has also been moved here to make more room for general options.\n\nThere are only two things that aren\'t very self explanatory: deleting logs and unique IDs.\n\nWhile deleting logs is fairly straightforward, it should be noted that it will not delete the log currently being written during the session, so the \"total logs in folder\" stat will always display as \"1\".\n\nUnique IDs are a feature to help assist with saving moods. In short, they are a generated identifier that is used when saving to a \"moods json file\", which is tapped into when selecting what moods you want to see in the \"Pack Info\" tab. Unique IDs are only used if the pack does not have a \'info.json\' file, otherwise the pack name is just used instead. If you are rapidly editing a pack without info.json and want EdgeWare++ to stop generating new mood files, there is an option to disable it in the troubleshooting tab.\n\n When manually editing mood config jsons, you don\'t need to worry about how the unique ID is generated- the file tab will tell you what to look for. If you are curious though, here is the exact formula:\n\nnum_images + num_audio + num_video + wallpaper(y/n) + loading_splash(y/n) + discord_status(y/n) + icon(y/n) + corruption(y/n)\n\nFor example:\nA pack with 268 images, 7 audio, 6 videos, has a wallpaper, doesn\'t have a custom loading splash, has a discord status, doesn\'t have a custom icon, and doesn\'t have a corruption file, would generate \"26876wxdxx.json\" in //moods//unnamed (mood files go in unnamed when using unique IDs)' -BUTTON_FACE = 'SystemButtonFace' if os.name == 'nt' else 'gray90' - +#corruption tutorial text +CINTRO_TEXT = 'Welcome to the Corruption tab!\n\n Normally I\'d put tutorials and help like this elsewhere, but I realize that this is probably the most complex and in-depth feature to be added to EdgeWare. Don\'t worry, we\'ll work through it together!\n\nEach tab will go over a feature of corruption, while also highlighting where the settings are for reference. Any additional details not covered here can be found in the \"About\" tab!' +CSTART_TEXT = 'To start corruption mode, you can use these settings in the top left to turn it on. If turning it on is greyed out, it means the current pack does not support corruption! Down below are more toggle settings for fine-tuning corruption to work how you want it.\n\n Remember, for any of these settings, if your mouse turns into a \"question mark\" while hovering over it, you can stay hovered to view a tooltip on what the setting does!' +CTRANSITION_TEXT = 'Transitions are how each corruption level fades into eachother. While running corruption mode, the current level and next level are accessed simultaneously to blend the two together. You can choose the blending modes with the top option, and how edgeware transitions from one corruption level to the next with the bottom option. The visualizer image is purely to help understand how the transitions work, with the two colours representing both accessed levels. The sliders below fine-tune how long each level will last, so for a rough estimation on how long full corruption will take, you can multiply the active slider by the number of levels.' errors_list = [] +BUTTON_FACE = 'SystemButtonFace' if os.name == 'nt' else 'gray90' #all booru consts BOORU_FLAG = '' #flag to replace w/ booru name @@ -306,6 +309,8 @@ if settings['toggleMoodSet'] != True: captions_dict = json.loads(captions.read()) if 'prefix' in captions_dict: del captions_dict['prefix'] if 'subtext' in captions_dict: del captions_dict['subtext'] + if 'subliminal' in captions_dict: del captions_dict['subliminal'] + if 'prefix_settings' in captions_dict: del captions_dict['prefix_settings'] mood_dict["captions"] += captions_dict except: logging.warning(f'captions mood extraction failed.') @@ -441,8 +446,16 @@ def show_window(): singleModeVar = BooleanVar(root, value=(int(settings['singleMode'])==1)) corruptionModeVar = BooleanVar(root, value=(int(settings['corruptionMode'])==1)) + corruptionDevVar = BooleanVar(root, value=(int(settings['corruptionDevMode'])==1)) + corruptionFullPermVar = BooleanVar(root, value=(int(settings['corruptionFullPerm'])==1)) corruptionTimeVar = IntVar(root, value=int(settings['corruptionTime'])) + corruptionPopupsVar = IntVar(root, value=int(settings['corruptionPopups'])) + corruptionLaunchesVar = IntVar(root, value=int(settings['corruptionLaunches'])) corruptionFadeTypeVar = StringVar(root, value=(settings['corruptionFadeType'].strip())) + corruptionTriggerVar = StringVar(root, value=(settings['corruptionTrigger'].strip())) + corruptionWallpaperCycleVar = BooleanVar(root, value=(int(settings['corruptionWallpaperCycle'])==1)) + corruptionThemeCycleVar = BooleanVar(root, value=(int(settings['corruptionThemeCycle'])==1)) + corruptionPurityModeVar = BooleanVar(root, value=(int(settings['corruptionPurityMode'])==1)) pumpScareOffsetVar = IntVar(root, value=int(settings['pumpScareOffset'])) @@ -454,6 +467,19 @@ def show_window(): presetsDangerVar = BooleanVar(root, value=(int(settings['presetsDanger'])==1)) + sMessageChanceVar = IntVar(root, value=int(settings['sMessageChance'])) + sMessageDurationVar = IntVar(root, value=int(settings['sMessageDuration'])) + sMessageTransparencyVar = IntVar(root, value=int(settings['sMessageTransparency'])) + + movingChanceVar = IntVar(root, value=int(settings['movingChance'])) + movingSpeedVar = IntVar(root, value=int(settings['movingSpeed'])) + movingRandomVar = BooleanVar(root, value=(int(settings['movingRandom'])==1)) + + capPopChanceVar = IntVar(root, value=int(settings['capPopChance'])) + capPopOpacityVar = IntVar(root, value=int(settings['capPopOpacity'])) + capPopTimerVar = IntVar(root, value=int(settings['capPopTimer'])) + capPopMoodVar = BooleanVar(root, value=(int(settings['capPopMood'])==1)) + #grouping for sanity's sake later in_var_group = [delayVar, popupVar, webVar, audioVar, promptVar, fillVar, @@ -472,7 +498,12 @@ def show_window(): hibernateLengthVar, fixWallpaperVar, toggleHibSkipVar, toggleMoodSetVar, corruptionModeVar, corruptionTimeVar, pumpScareOffsetVar, corruptionFadeTypeVar, vlcModeVar, captionFilenameVar, singleModeVar, multiClickVar, themeTypeVar, - themeNoConfigVar, presetsDangerVar] + themeNoConfigVar, presetsDangerVar, corruptionTriggerVar, corruptionPopupsVar, + corruptionLaunchesVar, corruptionDevVar, corruptionWallpaperCycleVar, + corruptionThemeCycleVar, corruptionPurityModeVar, corruptionFullPermVar, + sMessageChanceVar, sMessageDurationVar, sMessageTransparencyVar, + movingChanceVar, movingSpeedVar, movingRandomVar, capPopChanceVar, + capPopOpacityVar, capPopTimerVar, capPopMoodVar] in_var_names = ['delay', 'popupMod', 'webMod', 'audioMod', 'promptMod', 'fill', 'fill_delay', 'replace', 'replaceThresh', 'start_on_logon', @@ -489,7 +520,12 @@ def show_window(): 'toggleInternet', 'buttonless', 'hibernateType', 'hibernateLength', 'fixWallpaper', 'toggleHibSkip', 'toggleMoodSet', 'corruptionMode', 'corruptionTime', 'pumpScareOffset', 'corruptionFadeType', 'vlcMode', 'captionFilename', 'singleMode', 'multiClick', - 'themeType', 'themeNoConfig', 'presetsDanger'] + 'themeType', 'themeNoConfig', 'presetsDanger', 'corruptionTrigger', + 'corruptionPopups', 'corruptionLaunches', 'corruptionDevMode', + 'corruptionWallpaperCycle', 'corruptionThemeCycle', 'corruptionPurityMode', + 'corruptionFullPerm', 'sMessageChance', 'sMessageDuration', 'sMessageTransparency', + 'movingChance', 'movingSpeed', 'movingRandom', 'capPopChance', 'capPopOpacity', + 'capPopTimer', 'capPopMood'] break except Exception as e: messagebox.showwarning( @@ -534,6 +570,11 @@ def show_window(): info_group = [] discord_group = [] test_group = [] + ctime_group = [] + cpopup_group = [] + claunch_group = [] + ctutorialstart_group = [] + ctutorialtransition_group = [] webv = getLiveVersion(UPDCHECK_URL, 0) webvpp = getLiveVersion(UPDCHECK_PP_URL, 1) @@ -699,12 +740,21 @@ def show_window(): Label(tabGeneral, text='Timer Settings', font=titleFont, relief=GROOVE).pack(pady=2) timerFrame = Frame(tabGeneral, borderwidth=5, relief=RAISED) - timerToggle = Checkbutton(timerFrame, text='Timer Mode', variable=timerVar, command=lambda: toggleAssociateSettings(timerVar.get(), timer_group), cursor='question_arrow') + timerToggle = Checkbutton(timerFrame, text='Timer Mode', variable=timerVar, command=lambda: timerHelper(), cursor='question_arrow') timerSlider = Scale(timerFrame, label='Timer Time (mins)', from_=1, to=1440, orient='horizontal', variable=timerTimeVar) safewordFrame = Frame(timerFrame) + def timerHelper(): + toggleAssociateSettings(timerVar.get(), timer_group) + if timerVar.get(): + startLoginVar.set(True) + else: + startLoginVar.set(False) + timerttp = CreateToolTip(timerToggle, 'Enables \"Run on Startup\" and disables the Panic function until the time limit is reached.\n\n' - '\"Safeword\" allows you to set a password to re-enable Panic, if need be.') + '\"Safeword\" allows you to set a password to re-enable Panic, if need be.\n\n' + 'Note: Run on Startup does not need to stay enabled for Timer Mode to work. However, disabling it may cause ' + 'instability when running EdgeWare multiple times without changing config settings.') Label(safewordFrame, text='Emergency Safeword').pack() timerSafeword = Entry(safewordFrame, show='*', textvariable=safewordVar) @@ -1085,6 +1135,8 @@ def show_window(): mitosisFrame = Frame(popupHostFrame) panicFrame = Frame(popupHostFrame) denialFrame = Frame(popupHostFrame) + movingFrame = Frame(popupHostFrame) + speedFrame = Frame(popupHostFrame) popupScale = Scale(popupFrame, label='Popup Freq (%)', from_=0, to=100, orient='horizontal', variable=popupVar) popupManual = Button(popupFrame, text='Manual popup...', command=lambda: assign(popupVar, simpledialog.askinteger('Manual Popup', prompt='[0-100]: ')), cursor='question_arrow') @@ -1106,16 +1158,11 @@ def show_window(): mitosis_cGroup.append(mitosisStren) - setPanicButtonButton = Button(panicFrame, text=f'Set Panic Button\n<{panicButtonVar.get()}>', command=lambda:getKeyboardInput(setPanicButtonButton, panicButtonVar), cursor='question_arrow') + setPanicButtonButton = Button(panicFrame, text=f'Set Panic\nButton\n<{panicButtonVar.get()}>', command=lambda:getKeyboardInput(setPanicButtonButton, panicButtonVar), cursor='question_arrow') doPanicButton = Button(panicFrame, text='Perform Panic', command=lambda: subprocess.Popen([sys.executable, 'panic.pyw'])) - panicDisableButton = Checkbutton(popupHostFrame, text='Disable Panic Hotkey', variable=panicVar, cursor='question_arrow') setpanicttp = CreateToolTip(setPanicButtonButton, 'NOTE: To use this hotkey you must be \"focused\" on a EdgeWare popup. Click on a popup before using.') - disablePanicttp = CreateToolTip(panicDisableButton, 'This not only disables the panic hotkey, but also the panic function in the system tray as well.\n\n' - 'If you want to use Panic after this, you can still:\n' - '•Directly run \"panic.pyw\"\n' - '•Keep the config window open and press \"Perform Panic\"\n' - '•Use the panic desktop icon (if you kept those enabled)') + timeoutToggle = Checkbutton(timeoutFrame, text='Popup Timeout', variable=timeoutPopupsVar, command=lambda: toggleAssociateSettings(timeoutPopupsVar.get(), timeout_group)) timeoutSlider = Scale(timeoutFrame, label='Time (sec)', from_=1, to=120, orient='horizontal', variable=popupTimeoutVar) @@ -1125,6 +1172,16 @@ def show_window(): denialSlider = Scale(denialFrame, label='Denial Chance', orient='horizontal', variable=denialChance) denialToggle = Checkbutton(denialFrame, text='Denial Mode', variable=denialMode, command=lambda: toggleAssociateSettings(denialMode.get(), denial_group), cursor='question_arrow') + movingSlider = Scale(movingFrame, label='Moving Chance', orient='horizontal', variable=movingChanceVar, cursor='question_arrow') + movingRandToggle = Checkbutton(movingFrame, text='Rand. Direction', variable=movingRandomVar, cursor='question_arrow') + + movingttp = CreateToolTip(movingSlider, 'Gives each popup a chance to move around the screen instead of staying still. The popup will have the \"Buttonless\" ' + 'property, so it is easier to click.\n\nNOTE: Having many of these popups at once may impact performance. Try a lower percentage chance or higher popup delay to start.') + moverandomttp = CreateToolTip(movingRandToggle, 'Makes moving popups move in a random direction rather than the static diagonal one.') + + movingSpeedSlider = Scale(speedFrame, label='Max Movespeed', from_=1, to=15, orient='horizontal', variable=movingSpeedVar) + manualSpeed = Button(speedFrame, text='Manual speed...', command=lambda: assign(movingSpeedVar, simpledialog.askinteger('Manual Speed', prompt='[1-15]: '))) + denialttp = CreateToolTip(denialToggle, 'Adds a percentage chance to \"censor\" an image.') denial_group.append(denialSlider) @@ -1141,10 +1198,15 @@ def show_window(): denialFrame.pack(fill='y', side='left') denialSlider.pack(fill='x') denialToggle.pack(fill='x') + movingFrame.pack(fill='y', side='left') + movingSlider.pack(fill='x') + movingRandToggle.pack(fill='x') + speedFrame.pack(fill='y', side='left') + movingSpeedSlider.pack(fill='x') + manualSpeed.pack(fill='x') panicFrame.pack(fill='y', side='left') - setPanicButtonButton.pack(fill='x') + setPanicButtonButton.pack(fill='x', expand=1) doPanicButton.pack(fill='x') - panicDisableButton.pack(fill='x') #popup frame handle end #additional popup options, mostly edgeware++ stuff @@ -1153,42 +1215,34 @@ def show_window(): popupOptionsSubFrame2 = Frame(popupOptionsFrame) popupOptionsSubFrame3 = Frame(popupOptionsFrame) - toggleCaptionsButton = Checkbutton(popupOptionsSubFrame1, text='Popup Captions', variable=captionVar, cursor='question_arrow') + panicDisableButton = Checkbutton(popupOptionsSubFrame1, text='Disable Panic Hotkey', variable=panicVar, cursor='question_arrow') popupWebToggle= Checkbutton(popupOptionsSubFrame1, text='Popup close opens web page', variable=popupWebVar) - toggleFilenameButton = Checkbutton(popupOptionsSubFrame2, text='Use filename for caption moods', variable=captionFilenameVar, cursor='question_arrow') toggleEasierButton = Checkbutton(popupOptionsSubFrame2, text='Buttonless Closing Popups', variable=buttonlessVar, cursor='question_arrow') toggleSingleButton = Checkbutton(popupOptionsSubFrame3, text='Single Popup Mode', variable=singleModeVar, cursor='question_arrow') toggleMultiClickButton = Checkbutton(popupOptionsSubFrame3, text='Multi-Click popups', variable=multiClickVar, cursor='question_arrow') - captionttp = CreateToolTip(toggleCaptionsButton, 'Enables captions on popups. These are short segments of text written by the pack creator that adorn the top of each popup.') - captionfilenamettp = CreateToolTip(toggleFilenameButton, 'When enabled, captions will try and match the filename of the image they attach to.\n\n' - 'This is done using the start of the filename. For example, a mood named \"goon\" would match captions of that mood to popups ' - 'of images named things like \"goon300242\", \"goon-love\", \"goon_ytur8843\", etc.\n\n' - 'This is how EdgeWare processed captions before moods were implemented fully in EdgeWare++. The reason you\'d turn this off, however, ' - 'is that if the mood doesn\'t match the filename, it won\'t display at all.\n\n For example, if you had a mood named \"succubus\", but ' - 'no files started with \"succubus\", the captions of that mood would never show up. Thus it is recommended to only turn this on if ' - 'the pack supports it.') + disablePanicttp = CreateToolTip(panicDisableButton, 'This not only disables the panic hotkey, but also the panic function in the system tray as well.\n\n' + 'If you want to use Panic after this, you can still:\n' + '•Directly run \"panic.pyw\"\n' + '•Keep the config window open and press \"Perform Panic\"\n' + '•Use the panic desktop icon (if you kept those enabled)') buttonlessttp = CreateToolTip(toggleEasierButton, 'Disables the \"close button\" on popups and allows you to click anywhere on the popup to close it.\n\n' 'IMPORTANT: The panic keyboard hotkey will only work in this mode if you use it while *holding down* the mouse button over a popup!') singlettp = CreateToolTip(toggleSingleButton, 'The randomization in EdgeWare does not check to see if a previous \"roll\" succeeded or not when a popup is spawned.\n\n' 'For example, if you have audio, videos, and prompts all turned on, there\'s a very real chance you will get all of them popping up at the same ' 'time if the percentage for each is high enough.\n\nThis mode ensures that only one of these types will spawn whenever a popup is created. It ' 'delivers a more consistent experience and less double (or triple) popups.\n\nADVANCED DETAILS: The roll order for popups are as follows:\n' - 'Web -> Video -> Audio -> Prompt -> Image\nTherefore, if every type of popup is at the same rate of appearing (and single mode is turned on), ' + 'Web -> Video -> Audio -> Prompt -> Caption Popup -> Image\nTherefore, if every type of popup is at the same rate of appearing (and single mode is turned on), ' 'web links will be slightly more common than videos, and videos slightly more common than audio, etc...') - multiclickttp = CreateToolTip(toggleMultiClickButton, 'If the pack creator uses advanced caption settings, this will enable the feature for certain popups to take multiple clicks ' - 'to close. This feature must be set-up beforehand and won\'t do anything if not supported.') popupOptionsFrame.pack(fill='x') popupOptionsSubFrame1.pack(fill='y', side='left', expand=1) popupOptionsSubFrame2.pack(fill='y', side='left', expand=1) popupOptionsSubFrame3.pack(fill='y', side='left', expand=1) - toggleCaptionsButton.pack(fill='x') + panicDisableButton.pack(fill='x') popupWebToggle.pack(fill='x') - toggleFilenameButton.pack(fill='x') toggleEasierButton.pack(fill='x') toggleSingleButton.pack(fill='x') - toggleMultiClickButton.pack(fill='x') #other start otherHostFrame = Frame(tabAnnoyance, borderwidth=5, relief=RAISED) @@ -1308,66 +1362,284 @@ def show_window(): maxSubliminalsScale.pack(fill='x', padx=1, expand=1) maxSubliminalsManual.pack(fill='x') + captionsFrame = Frame(tabAnnoyance, borderwidth=5, relief=RAISED) + captionsSubFrame1 = Frame(captionsFrame) + capPopFrame = Frame(captionsFrame) + capPopOpacityFrame = Frame(captionsFrame) + capPopTimerFrame = Frame(captionsFrame) + + toggleCaptionsButton = Checkbutton(captionsSubFrame1, text='Popup Captions', variable=captionVar, cursor='question_arrow') + toggleFilenameButton = Checkbutton(captionsSubFrame1, text='Use filename for caption moods', variable=captionFilenameVar, cursor='question_arrow') + toggleMultiClickButton = Checkbutton(captionsSubFrame1, text='Multi-Click popups', variable=multiClickVar, cursor='question_arrow') + toggleCaptionMood = Checkbutton(captionsSubFrame1, text='Use Cap-Pop specific mood', variable=capPopMoodVar, cursor='question_arrow') + + captionttp = CreateToolTip(toggleCaptionsButton, 'Enables captions on popups. These are short segments of text written by the pack creator that adorn the top of each popup.') + multiclickttp = CreateToolTip(toggleMultiClickButton, 'If the pack creator uses advanced caption settings, this will enable the feature for certain popups to take multiple clicks ' + 'to close. This feature must be set-up beforehand and won\'t do anything if not supported.') + captionfilenamettp = CreateToolTip(toggleFilenameButton, 'When enabled, captions will try and match the filename of the image they attach to.\n\n' + 'This is done using the start of the filename. For example, a mood named \"goon\" would match captions of that mood to popups ' + 'of images named things like \"goon300242\", \"goon-love\", \"goon_ytur8843\", etc.\n\n' + 'This is how EdgeWare processed captions before moods were implemented fully in EdgeWare++. The reason you\'d turn this off, however, ' + 'is that if the mood doesn\'t match the filename, it won\'t display at all.\n\n For example, if you had a mood named \"succubus\", but ' + 'no files started with \"succubus\", the captions of that mood would never show up. Thus it is recommended to only turn this on if ' + 'the pack supports it.') + capmoodttp = CreateToolTip(toggleCaptionMood, 'Caption Popups have the option to use a special mood in the captions.json file called \"subliminals\". This mood doesn\'t ' + 'normally appear like other captions, and is meant for short, fast messages that will blink at you very quickly.\n\nThis setting will ' + 'disable using the subliminals mood, and instead pull from all other valid captions. If your pack doesn\'t support subliminals, this ' + 'setting doesn\'t need to be disabled- it will automatically switch to using regular captions.') + + captionsPopupSlider = Scale(capPopFrame, label='Cap-Pop Chance', from_=0, to=100, orient='horizontal', variable=capPopChanceVar) + captionsPopupManual = Button(capPopFrame, text='Manual Cap-Pop...', command=lambda: assign(capPopChanceVar, simpledialog.askinteger('Manual Caption Popup Chance (%)', prompt='[0-100]: ')), cursor='question_arrow') + capPopOpacitySlider = Scale(capPopOpacityFrame, label='Cap-Pop Opacity', from_=1, to=100, orient='horizontal', variable=capPopOpacityVar) + capPopOpacityManual = Button(capPopOpacityFrame, text='Manual Opacity...', command=lambda: assign(capPopOpacityVar, simpledialog.askinteger('Manual Caption Popup Opacity (%)', prompt='[1-100]: '))) + capPopTimerSlider = Scale(capPopTimerFrame, label='Cap-Pop Timer', from_=1, to=1000, orient='horizontal', variable=capPopTimerVar) + capPopTimerManual = Button(capPopTimerFrame, text='Manual Timer...', command=lambda: assign(capPopTimerVar, simpledialog.askinteger('Manual Caption Popup Timer (ms)', prompt='[1-1000]: '))) + + cappopttp = CreateToolTip(captionsPopupManual, 'Caption Popups are short full-screen popups that flash up briefly before they disappear, similar to subliminal messages. They ' + 'take from the pack\'s captions.json file, and can use a specific \"subliminals\" mood to have unique captions (if the setting to the left is toggled on).') + + captionsFrame.pack(fill='x') + captionsSubFrame1.pack(fill='y', side='left') + toggleCaptionsButton.pack(fill='y', side='top') + toggleFilenameButton.pack(fill='y', side='top') + toggleMultiClickButton.pack(fill='y', side='top') + toggleCaptionMood.pack(fill='y', side='top') + + capPopFrame.pack(fill='y', side='left') + captionsPopupSlider.pack(fill='x', padx=1, expand=1) + captionsPopupManual.pack(fill='x') + capPopOpacityFrame.pack(fill='y', side='left') + capPopOpacitySlider.pack(fill='x', padx=1, expand=1) + capPopOpacityManual.pack(fill='x') + capPopTimerFrame.pack(fill='y', side='left') + capPopTimerSlider.pack(fill='x', padx=1, expand=1) + capPopTimerManual.pack(fill='x') + #===================={CORRUPTION}==============================# tabMaster.add(tabCorruption, text='Corruption') - corruptionFrame = Frame(tabCorruption, borderwidth=5, relief=RAISED) + corruptionFrame = Frame(tabCorruption) corruptionSettingsFrame = Frame(corruptionFrame) - corruptionTypeFrame = Frame(corruptionFrame) - corruptionTimeFrame = Frame(corruptionFrame) + corruptionSubFrame1 = Frame(corruptionSettingsFrame) - corruptionToggle = Checkbutton(corruptionSettingsFrame, text='Turn on Corruption', variable=corruptionModeVar, cursor='question_arrow') - corruptionRecommendedToggle = Button(corruptionSettingsFrame, text='Recommended Settings', cursor='question_arrow', command=lambda: packPreset(in_var_group, in_var_names, 'corruption', presetsDangerVar.get())) + corruptionStartFrame = Frame(corruptionSubFrame1, borderwidth=5, relief=RAISED) - corruptionFrame.pack(fill='x') + corruptionEnabled_group = [] - corruptionSettingsFrame.pack(side='left') - corruptionTypeFrame.pack(side='left') - corruptionTimeFrame.pack(side='left') + corruptionToggle = Checkbutton(corruptionStartFrame, text='Turn on Corruption', variable=corruptionModeVar, cursor='question_arrow') + corruptionFullToggle = Checkbutton(corruptionStartFrame, text='Full Permissions Mode', variable=corruptionFullPermVar, cursor='question_arrow') + corruptionRecommendedToggle = Button(corruptionStartFrame, text='Recommended Settings', cursor='question_arrow', height=2, + command=lambda: packPreset(in_var_group, in_var_names, 'corruption', presetsDangerVar.get())) + corruptionEnabled_group.append(corruptionToggle) + ctutorialstart_group.append(corruptionStartFrame) + ctutorialstart_group.append(corruptionToggle) + ctutorialstart_group.append(corruptionFullToggle) + ctutorialstart_group.append(corruptionRecommendedToggle) - corruptionToggle.pack(fill='x') - corruptionRecommendedToggle.pack(fill='x') + corruptionFrame.pack(fill='x') + corruptionSettingsFrame.pack(fill='x', side='left') + corruptionSubFrame1.pack(fill='both', side='top') + corruptionStartFrame.pack(fill='both', side='left') + + corruptionToggle.pack(fill='x', expand=1) + corruptionFullToggle.pack(fill='x', expand=1) + corruptionRecommendedToggle.pack(fill='x', padx=2, pady=2) corruptionmodettp = CreateToolTip(corruptionToggle, 'Corruption Mode gradually makes the pack more depraved, by slowly toggling on previously hidden' ' content. Or at least that\'s the idea, pack creators can do whatever they want with it.\n\n' 'Corruption uses the \'mood\' feature, which must be supported with a corruption.json file in the resource' ' folder. Over time moods will \"unlock\", leading to new things you haven\'t seen before the longer you use' ' EdgeWare.\n\nFor more information, check out the \"About\" tab. \n\nNOTE: currently not implemented! Holy god I hope I remember to remove this notice later!') + corruptionfullttp = CreateToolTip(corruptionFullToggle, 'This setting allows corruption mode to change config settings as it goes through corruption levels.') corruptionsettingsttp = CreateToolTip(corruptionRecommendedToggle, 'Pack creators can set \"default corruption settings\" for their pack, to give' ' users a more designed and consistent experience. This setting turns those on (if they exist).' '\n\nSidenote: this will load configurations similarly to the option in the \"Pack Info\" tab, however this one will only load corruption-specific settings.') - corruptionTimerButton = Button(corruptionTimeFrame, text='Manual time...', command=lambda: assign(corruptionTimeVar, simpledialog.askinteger('Manual Level Time (sec)', prompt='[5-1800]: '))) - corruptionTimerScale = Scale(corruptionTimeFrame, label='Level Time', variable=corruptionTimeVar, orient='horizontal', from_=5, to=1800) - - corruptionTimerScale.pack(fill='y') - corruptionTimerButton.pack(fill='y') - - corruptionFadeFrame = Frame(tabCorruption, borderwidth=5, relief=RAISED) + corruptionFadeFrame = Frame(corruptionSubFrame1, borderwidth=5, relief=RAISED) fadeInfoFrame = Frame(corruptionFadeFrame) - fadeImageFrame = Frame(corruptionFadeFrame) + fadeSubInfo = Frame(fadeInfoFrame) + triggerInfoFrame = Frame(corruptionFadeFrame) + triggerSubInfo = Frame(triggerInfoFrame) fade_types = ['Normal', 'Abrupt', 'Noise'] - fadeDropdown = OptionMenu(fadeInfoFrame, corruptionFadeTypeVar, *fade_types, command=lambda key: fadeHelper(key)) - fadeDescription = Label(fadeInfoFrame, text='Error loading fade description!', borderwidth=2, relief=GROOVE, wraplength=175) + fadeDropdown = OptionMenu(fadeSubInfo, corruptionFadeTypeVar, *fade_types, command=lambda key: fadeHelper(key)) + fadeDropdown.configure(width=9, highlightthickness = 0) + fadeDescription = Label(fadeInfoFrame, text='Error loading fade description!', borderwidth=2, relief=GROOVE, wraplength=150) + fadeDescription.configure(height=3, width=22) fadeImageNormal = ImageTk.PhotoImage(file=os.path.join(PATH, 'default_assets', 'corruption_defaultfade.png')) - fadeImageContainer = Label(fadeImageFrame, image=fadeImageNormal, borderwidth=2, relief=GROOVE) + fadeImageAbrupt = ImageTk.PhotoImage(file=os.path.join(PATH, 'default_assets', 'corruption_abruptfade.png')) + fadeImageNoise = ImageTk.PhotoImage(file=os.path.join(PATH, 'default_assets', 'corruption_noisefade.png')) + fadeImageContainer = Label(fadeSubInfo, image=fadeImageNormal, borderwidth=2, relief=GROOVE) + trigger_types = ['Timed', 'Popup', 'Launch'] + triggerDropdown = OptionMenu(triggerSubInfo, corruptionTriggerVar, *trigger_types, command=lambda key: triggerHelper(key, False)) + triggerDropdown.configure(width=9, highlightthickness = 0) + triggerDescription = Label(triggerInfoFrame, text='Error loading trigger description!', borderwidth=2, relief=GROOVE, wraplength=150) + triggerDescription.configure(height=3, width=22) + + ctutorialtransition_group.append(corruptionFadeFrame) + ctutorialtransition_group.append(fadeInfoFrame) + ctutorialtransition_group.append(fadeSubInfo) + ctutorialtransition_group.append(triggerInfoFrame) + ctutorialtransition_group.append(triggerSubInfo) + ctutorialtransition_group.append(fadeDropdown) + ctutorialtransition_group.append(fadeDescription) + ctutorialtransition_group.append(triggerDropdown) + ctutorialtransition_group.append(triggerDescription) + + corruptionFadeFrame.pack(fill='both', side='left') + fadeInfoFrame.pack(side='top', fill='both', pady=1) + fadeSubInfo.pack(side='left', fill='x') + fadeDropdown.pack(side='top') + fadeImageContainer.pack(side='top') + fadeDescription.pack(side='left', fill='y', padx=3, ipadx=2, ipady=2) + triggerInfoFrame.pack(side='top', fill='both', pady=1) + triggerSubInfo.pack(side='left', fill='x') + triggerDropdown.pack(side='top') + triggerDescription.pack(side='left', fill='y', padx=3, ipadx=2, ipady=2) + + #-Timer- + + corruptionTimeFrame = Frame(corruptionSettingsFrame) + corruptionTimeFrame.pack(fill='x', side='top') + cTimerFrame = Frame(corruptionTimeFrame) + corruptionTimerButton = Button(cTimerFrame, text='Manual time...', command=lambda: assign(corruptionTimeVar, simpledialog.askinteger('Manual Level Time (sec)', prompt='[5-1800]: '))) + corruptionTimerScale = Scale(cTimerFrame, label='Level Time', variable=corruptionTimeVar, orient='horizontal', from_=5, to=1800) + cPopupsFrame = Frame(corruptionTimeFrame) + corruptionPopupsButton = Button(cPopupsFrame, text='Manual popups...', command=lambda: assign(corruptionPopupsVar, simpledialog.askinteger('Manual Level Popups (per transition)', prompt='[3-100]: '))) + corruptionPopupsScale = Scale(cPopupsFrame, label='Level Popups', variable=corruptionPopupsVar, orient='horizontal', from_=3, to=100) + cLaunchesFrame = Frame(corruptionTimeFrame) + corruptionLaunchesButton = Button(cLaunchesFrame, text='Manual launches...', command=lambda: assign(corruptionLaunchesVar, simpledialog.askinteger('Manual Level Launches (per transition)', prompt='[2-31]: '))) + corruptionLaunchesScale = Scale(cLaunchesFrame, label='Level Launches', variable=corruptionLaunchesVar, orient='horizontal', from_=2, to=31) + + ctutorialtransition_group.append(corruptionTimerButton) + ctutorialtransition_group.append(corruptionTimerScale) + ctutorialtransition_group.append(corruptionPopupsButton) + ctutorialtransition_group.append(corruptionPopupsScale) + ctutorialtransition_group.append(corruptionLaunchesButton) + ctutorialtransition_group.append(corruptionLaunchesScale) + + cTimerFrame.pack(side='left', fill='x', padx=1, expand=1) + corruptionTimerScale.pack(fill='y') + corruptionTimerButton.pack(fill='y') + cPopupsFrame.pack(side='left', fill='x', padx=1, expand=1) + corruptionPopupsScale.pack(fill='y') + corruptionPopupsButton.pack(fill='y') + cLaunchesFrame.pack(side='left', fill='x', padx=1, expand=1) + corruptionLaunchesScale.pack(fill='y') + corruptionLaunchesButton.pack(fill='y') + + ctime_group.append(corruptionTimerButton) + ctime_group.append(corruptionTimerScale) + cpopup_group.append(corruptionPopupsButton) + cpopup_group.append(corruptionPopupsScale) + claunch_group.append(corruptionLaunchesButton) + claunch_group.append(corruptionLaunchesScale) def fadeHelper(key): if key == 'Normal': - fadeDescription.configure(text='Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do') + fadeDescription.configure(text='Gradually transitions between corruption levels.') fadeImageContainer.configure(image=fadeImageNormal) - - corruptionFadeFrame.pack(fill='x') - fadeInfoFrame.pack(side='left', fill='both') - fadeDropdown.pack(side='left') - fadeDescription.pack(side='left', fill='y', padx=3, ipadx=2, ipady=2) - fadeImageFrame.pack(fill='both', expand=1) - fadeImageContainer.place(relx=.5, rely=.5, anchor=CENTER) - - fadeHelper(corruptionFadeTypeVar.get()) - + if key == 'Abrupt': + fadeDescription.configure(text='Immediately switches to new level upon timer completion.') + fadeImageContainer.configure(image=fadeImageAbrupt) + if key == 'Noise': + fadeDescription.configure(text='Scatters levels randomly across the time range.') + fadeImageContainer.configure(image=fadeImageNoise) + def triggerHelper(key, tutorialMode): + if key == 'Timed': + triggerDescription.configure(text='Transitions based on time elapsed in current session.') + if tutorialMode: + toggleAssociateSettings_manual(True, ctime_group, 'lime green', 'forest green') + toggleAssociateSettings_manual(False, cpopup_group, 'lime green', 'forest green') + toggleAssociateSettings_manual(False, claunch_group, 'lime green', 'forest green') + else: + toggleAssociateSettings(True, ctime_group) + toggleAssociateSettings(False, cpopup_group) + toggleAssociateSettings(False, claunch_group) + if key == 'Popup': + triggerDescription.configure(text='Transitions based on number of popups in current session.') + if tutorialMode: + toggleAssociateSettings_manual(False, ctime_group, 'lime green', 'forest green') + toggleAssociateSettings_manual(True, cpopup_group, 'lime green', 'forest green') + toggleAssociateSettings_manual(False, claunch_group, 'lime green', 'forest green') + else: + toggleAssociateSettings(False, ctime_group) + toggleAssociateSettings(True, cpopup_group) + toggleAssociateSettings(False, claunch_group) + if key == 'Launch': + triggerDescription.configure(text='Transitions based on number of EdgeWare launches.') + if tutorialMode: + toggleAssociateSettings_manual(False, ctime_group, 'lime green', 'forest green') + toggleAssociateSettings_manual(False, cpopup_group, 'lime green', 'forest green') + toggleAssociateSettings_manual(True, claunch_group, 'lime green', 'forest green') + else: + toggleAssociateSettings(False, ctime_group) + toggleAssociateSettings(False, cpopup_group) + toggleAssociateSettings(True, claunch_group) + + #-Tutorial- + + corruptionTutorialFrame = Frame(corruptionFrame) + corruptionTabMaster = ttk.Notebook(corruptionTutorialFrame) + cTabIntro = Frame(None) + cTabStart = Frame(None) + cTabTransitions = Frame(None) + corruptionTabMaster.add(cTabIntro, text='Intro') + corruptionTabMaster.add(cTabStart, text='Start') + corruptionTabMaster.add(cTabTransitions, text='Transitions') + + corruptionTutorialFrame.pack(side='left', fill='both', expand=1) + corruptionTabMaster.pack(fill='both', expand=1) + + corruptionIntroBody = Label(cTabIntro, text=CINTRO_TEXT, wraplength=300) + corruptionStartBody = Label(cTabStart, text=CSTART_TEXT, wraplength=300) + corruptionTransitionBody = Label(cTabTransitions, text=CTRANSITION_TEXT, wraplength=300) + + corruptionIntroBody.pack(fill='both', padx=2, pady=2) + corruptionStartBody.pack(fill='both', padx=2, pady=2) + corruptionTransitionBody.pack(fill='both', padx=2, pady=2) + + #-Additional Settings- + + corruptionAdditionalFrame = Frame(tabCorruption, borderwidth=5, relief=RAISED) + corruptionAddSub1 = Frame(corruptionAdditionalFrame) + corruptionAddSub2 = Frame(corruptionAdditionalFrame) + corruptionAddSub3 = Frame(corruptionAdditionalFrame) + + corruptionWallpaperToggle = Checkbutton(corruptionAddSub1, text='Don\'t Cycle Wallpaper', variable=corruptionWallpaperCycleVar, cursor='question_arrow') + corruptionThemeToggle = Checkbutton(corruptionAddSub1, text='Don\'t Cycle Themes', variable=corruptionThemeCycleVar, cursor='question_arrow') + corruptionPurityToggle = Checkbutton(corruptionAddSub2, text='Purity Mode', variable=corruptionPurityModeVar, cursor='question_arrow') + corruptionDevToggle = Checkbutton(corruptionAddSub2, text='Corruption Dev View', variable=corruptionDevVar, cursor='question_arrow') + + ctutorialstart_group.append(corruptionAdditionalFrame) + ctutorialstart_group.append(corruptionAddSub1) + ctutorialstart_group.append(corruptionAddSub2) + ctutorialstart_group.append(corruptionAddSub3) + ctutorialstart_group.append(corruptionWallpaperToggle) + ctutorialstart_group.append(corruptionThemeToggle) + ctutorialstart_group.append(corruptionPurityToggle) + ctutorialstart_group.append(corruptionDevToggle) + + corruptionAdditionalFrame.pack(fill='x') + corruptionAddSub1.pack(fill='both', side='left', expand=1) + corruptionAddSub2.pack(fill='both', side='left', expand=1) + corruptionAddSub3.pack(fill='both', side='left', expand=1) + + corruptionWallpaperToggle.pack(fill='x', side='top') + corruptionThemeToggle.pack(fill='x', side='top') + corruptionPurityToggle.pack(fill='x', side='top') + corruptionDevToggle.pack(fill='x', side='top') + + corrwallpaperttp = CreateToolTip(corruptionWallpaperToggle, 'Prevents the wallpaper from cycling as you go through corruption levels, instead staying at ' + 'the first wallpaper set for corruption level 1.') + corrthemettp = CreateToolTip(corruptionThemeToggle, 'Prevents the theme from cycling as you go through corruption levels, instead staying as ' + 'the theme you set in the \"General\" tab of the config window.') + corrpurityttp = CreateToolTip(corruptionPurityToggle, 'Starts corruption mode at the highest corruption level, then works backwards to level 1. ' + 'Retains all of your other settings for this mode, if applicable.') + corruptiondevttp = CreateToolTip(corruptionDevToggle, '') + + #-Info- corruptionPathFrame = Frame(tabCorruption, borderwidth=5, relief=RAISED) @@ -1414,6 +1686,22 @@ def show_window(): pathScrollbarX.pack(side='bottom', fill='x') pathTree.pack(side='left', fill='both', expand=1) pathScrollbarY.pack(side='left', fill='y') + + def corruptionTutorialHelper(event): + tab = event.widget.tab('current')['text'] + th = settings['themeType'].strip() + if tab == 'Start': + toggleAssociateSettings_manual(True, ctutorialstart_group, 'lime green', 'forest green') + toggleAssociateSettings(True, ctutorialtransition_group) + triggerHelper(corruptionTriggerVar.get(), False) + elif tab == 'Transitions': + toggleAssociateSettings_manual(True, ctutorialtransition_group, 'lime green', 'forest green') + toggleAssociateSettings(True, ctutorialstart_group) + triggerHelper(corruptionTriggerVar.get(), True) + toggleAssociateSettings(os.path.isfile(os.path.join(PATH, 'resource', 'corruption.json')), corruptionEnabled_group) + + corruptionTabMaster.bind('<>', corruptionTutorialHelper) + #===================={DRIVE}==============================# tabMaster.add(tabDrive, text='Drive') @@ -1723,13 +2011,14 @@ def show_window(): promptStat = 0 if os.path.exists(os.path.join(PATH, 'resource', 'captions.json')): - #don't think these have moods currently but will implement this just in case try: with open(os.path.join(PATH, 'resource', 'captions.json'), 'r') as f: l = json.loads(f.read()) i = 0 if 'prefix' in l: del l['prefix'] if 'subtext' in l: del l['subtext'] + if 'subliminal' in l: del l['subliminal'] + if 'prefix_settings' in l: del l['prefix_settings'] for x in l: i += len(l[x]) captionStat = i @@ -1876,6 +2165,14 @@ def show_window(): 'Because of this, only packs created by the original EdgeWare creator, PetitTournesol, have custom status images.\n\n' 'Nevertheless, I have decided to put this here not only for those packs, but also for other ' 'packs that tap in to the same image IDs.') + + packConfigPresets = Frame(tabPackInfo, borderwidth=5, relief=RAISED) + configPresetsSub1 = Frame(packConfigPresets) + configPresetsSub2 = Frame(packConfigPresets) + configPresetsButton = Button(configPresetsSub2, text='Load Pack Configuration', cursor='question_arrow', command=lambda: packPreset(in_var_group, in_var_names, 'full', presetsDangerVar.get())) + #put the group here instead of with the rest since it's just a single button + configpresets_group = [] + configpresets_group.append(configPresetsButton) if os.path.exists(os.path.join(PATH, 'resource', 'config.json')): with open(os.path.join(PATH, 'resource', 'config.json')) as f: try: @@ -1885,17 +2182,13 @@ def show_window(): configNum = len(l) except Exception as e: logging.warning(f'could not load pack suggested settings. Reason: {e}') - configNum = "N/A" - #toggleAssociateSettings(False, configPresetsButton) + configNum = 0 + toggleAssociateSettings(False, configpresets_group) else: - configNum = "N/A" - #toggleAssociateSettings(False, configPresetsButton) - packConfigPresets = Frame(tabPackInfo, borderwidth=5, relief=RAISED) - configPresetsSub1 = Frame(packConfigPresets) - configPresetsSub2 = Frame(packConfigPresets) + configNum = 0 + toggleAssociateSettings(False, configpresets_group) configPresetsLabel = Label(configPresetsSub1, text=f'Number of suggested config settings: {configNum}') presetsDangerToggle = Checkbutton(configPresetsSub1, text='Toggle on warning failsafes', variable=presetsDangerVar, cursor='question_arrow') - configPresetsButton = Button(configPresetsSub2, text='Load Pack Configuration', cursor='question_arrow', command=lambda: packPreset(in_var_group, in_var_names, 'full', presetsDangerVar.get())) presetdangerttp = CreateToolTip(presetsDangerToggle, 'Toggles on the \"Warn if \"Dangerous\" Settings Active\" setting after loading the ' 'pack configuration file, regardless if it was toggled on or off in those settings.\n\nWhile downloading and loading ' @@ -1992,6 +2285,8 @@ def show_window(): l = json.loads(f.read()) if 'prefix' in l: del l['prefix'] if 'subtext' in l: del l['subtext'] + if 'subliminal' in l: del l['subliminal'] + if 'prefix_settings' in l: del l['prefix_settings'] for m in l: if m == 'default': continue @@ -2390,6 +2685,9 @@ def show_window(): themeChange(settings['themeType'].strip(), root, style, windowFont, titleFont) + #==========={TOGGLE ASSOCIATE SETTINGS}===========# + #all toggleAssociateSettings goes here, because it is rendered after the appropriate theme change + toggleAssociateSettings(fillVar.get(), fill_group) toggleAssociateSettings(replaceVar.get(), replace_group) toggleAssociateSettings(rotateWallpaperVar.get(), wallpaper_group) @@ -2404,6 +2702,9 @@ def show_window(): toggleAssociateSettings(maxVToggleVar.get(), maxVideo_group) toggleAssociateSettings(popupSublim.get(), subliminals_group) hibernateHelper(hibernateTypeVar.get()) + fadeHelper(corruptionFadeTypeVar.get()) + triggerHelper(corruptionTriggerVar.get(), False) + toggleAssociateSettings(os.path.isfile(os.path.join(PATH, 'resource', 'corruption.json')), corruptionEnabled_group) tabMaster.pack(expand=1, fill='both') tabInfoExpound.pack(expand=1, fill='both') @@ -2540,7 +2841,7 @@ def write_save(varList:list[StringVar | IntVar | BooleanVar], nameList:list[str] timeObjPath = os.path.join(PATH, 'hid_time.dat') if int(varList[nameList.index('timerMode')].get()) == 1: - utils.toggle_run_at_startup(PATH, True) + #utils.toggle_run_at_startup(PATH, True) #revealing hidden files utils.show_file(hashObjPath) @@ -2612,7 +2913,7 @@ def safeCheck(varList:list[StringVar | IntVar | BooleanVar], nameList:list[str]) dangersList.append('\n\nMajor:') if int(varList[nameList.index('start_on_logon')].get()) == 1: numDangers += 1 - dangersList.append('\n•Launch on Startup is enabled! This will run EdgeWare when you start your computer!') + dangersList.append('\n•Launch on Startup is enabled! This will run EdgeWare when you start your computer! (Note: Timer mode enables this setting!)') if int(varList[nameList.index('fill')].get()) == 1: numDangers += 1 dangersList.append('\n•Fill Drive is enabled! Edgeware will place images all over your computer! Even if you want this, make sure the protected directories are right!') @@ -2805,7 +3106,7 @@ def getKeyboardInput(button:Button, var:StringVar): child.mainloop() def assignKey(parent:Tk, button:Button, var:StringVar, key): - button.configure(text=f'Set Panic Button\n<{key.keysym}>') + button.configure(text=f'Set Panic\nButton\n<{key.keysym}>') var.set(str(key.keysym)) parent.destroy() diff --git a/EdgeWare/configDefault.dat b/EdgeWare/configDefault.dat index e3c89ff0..f539832a 100644 --- a/EdgeWare/configDefault.dat +++ b/EdgeWare/configDefault.dat @@ -1,2 +1,2 @@ -version,versionplusplus,delay,fill,replace,webMod,popupMod,audioMod,promptMod,vidMod,replaceThresh,slowMode,pip_installed,is_configed,fill_delay,start_on_logon,hibernateMode,hibernateMin,hibernateMax,wakeupActivity,pypres_installed,showDiscord,pil_installed,showLoadingFlair,showCaptions,maxFillThreads,panicButton,panicDisabled,promptMistakes,squareLim,mitosisMode,onlyVid,webPopup,rotateWallpaper,wallpaperTimer,wallpaperVariance,timeoutPopups,popupTimeout,mitosisStrength,avoidList,booruName,booruMinScore,desktopIcons,tagList,downloadEnabled,downloadMode,useWebResource,runOnSaveQuit,timerMode,timerSetupTime,safeword,lkCorner,lkScaling,lkToggle,videoVolume,denialMode,denialChance,popupSubliminals,drivePath,wallpaperDat,maxAudioBool,maxAudio,maxVideoBool,maxVideos,subliminalsChance,maxSubliminals,safeMode,antiOrLanczos,toggleInternet,buttonless,hibernateType,hibernateLength,fixWallpaper,toggleHibSkip,toggleMoodSet,corruptionMode,corruptionTime,pumpScareOffset,corruptionFadeType,vlcMode,captionFilename,singleMode,multiClick,themeType,themeNoConfig,presetsDanger -2.4.2_A,9.3a,1000,0,0,0,100,0,0,10,500,0,0,0,0,0,0,240,300,20,0,1,0,1,0,8,e,0,3,800,0,0,0,0,30,0,0,0,2,EdgeWare>AppData,,-5,1,all,0,All,0,0,0,0,password,0,100,0,25,0,0,0,C:/Users/,WPAPER_DEF,1,1,0,10,100,200,0,1,0,0,Original,15,0,0,0,0,60,0,Normal,0,1,0,0,Original,0,0 +version,versionplusplus,delay,fill,replace,webMod,popupMod,audioMod,promptMod,vidMod,replaceThresh,slowMode,pip_installed,is_configed,fill_delay,start_on_logon,hibernateMode,hibernateMin,hibernateMax,wakeupActivity,pypres_installed,showDiscord,pil_installed,showLoadingFlair,showCaptions,maxFillThreads,panicButton,panicDisabled,promptMistakes,squareLim,mitosisMode,onlyVid,webPopup,rotateWallpaper,wallpaperTimer,wallpaperVariance,timeoutPopups,popupTimeout,mitosisStrength,avoidList,booruName,booruMinScore,desktopIcons,tagList,downloadEnabled,downloadMode,useWebResource,runOnSaveQuit,timerMode,timerSetupTime,safeword,lkCorner,lkScaling,lkToggle,videoVolume,denialMode,denialChance,popupSubliminals,drivePath,wallpaperDat,maxAudioBool,maxAudio,maxVideoBool,maxVideos,subliminalsChance,maxSubliminals,safeMode,antiOrLanczos,toggleInternet,buttonless,hibernateType,hibernateLength,fixWallpaper,toggleHibSkip,toggleMoodSet,corruptionMode,corruptionTime,pumpScareOffset,corruptionFadeType,vlcMode,captionFilename,singleMode,multiClick,themeType,themeNoConfig,presetsDanger,corruptionTrigger,corruptionPopups,corruptionLaunches,corruptionDevMode,corruptionWallpaperCycle,corruptionThemeCycle,corruptionPurityMode,corruptionFullPerm,sMessageChance,sMessageDuration,sMessageTransparency,movingChance,movingSpeed,movingRandom,capPopChance,capPopOpacity,capPopTimer,capPopMood +2.4.2_A,10a,1000,0,0,0,100,0,0,10,500,0,0,0,0,0,0,240,300,20,0,0,0,1,0,8,e,0,3,800,0,0,0,0,30,0,0,0,2,EdgeWare>AppData,,-5,1,all,0,All,0,0,0,0,password,0,100,0,25,0,0,0,C:/Users/,WPAPER_DEF,1,1,0,10,100,200,0,1,0,0,Original,15,0,0,0,0,60,0,Normal,0,1,0,0,Original,0,0,Timed,5,2,0,0,0,0,1,0,300,80,0,5,1,0,100,300,1 diff --git a/EdgeWare/debugScript.bat b/EdgeWare/debugScript.bat index dc6990d8..628e6df2 100644 --- a/EdgeWare/debugScript.bat +++ b/EdgeWare/debugScript.bat @@ -5,12 +5,15 @@ echo 1: Start.pyw (Edgeware) echo 2: Popup.pyw (Popup only) echo 2a: Popup.pyw (Video only) echo 3: Config.pyw (Config) +echo =EdgeWare++= +echo 4: Sublabel.pyw (Subliminal Message) set /p usrSelect=Select number: if %usrSelect%==1 goto startLbl if %usrSelect%==2 goto popupLbl if %usrSelect%==2a goto popup2Lbl if %usrSelect%==3 goto configLbl -echo Must enter selection number (1, 2, 3) +if %usrSelect%==4 goto subLbl +echo Must enter selection number (1, 2, 3, 4) pause goto top :startLbl @@ -31,8 +34,14 @@ py popup.pyw -video echo Done. pause goto quitLbl +:subLbl +echo Running sublabel.pyw... +py sublabel.pyw +echo Done. +pause +goto quitLbl :configLbl -echo Running config.pyw +echo Running config.pyw... py config.pyw echo Done. pause diff --git a/EdgeWare/default_assets/corruption_abruptfade.png b/EdgeWare/default_assets/corruption_abruptfade.png new file mode 100644 index 0000000000000000000000000000000000000000..a42d6dc2471989011028d0bf5573f3822351ab99 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^Q9vxf!3-o9Zk=HTq!^2X+?^QKos)S9r}e!SCcf=Ye*} zdAc};So9_*D6n=+V)8Jmyz(M&v3swaY1NgCKMV|)k68=q{a(8OsFK0c)z4*}Q$iB} DB{)sN literal 0 HcmV?d00001 diff --git a/EdgeWare/default_assets/corruption_defaultfade.png b/EdgeWare/default_assets/corruption_defaultfade.png index 67e330250c39e206b7153c0a4a4c9941f67b65e4..c1b7f9d0d89f3a313c97e7ccd00a75f45055eaa1 100644 GIT binary patch delta 147 zcmeyz+{&cb8Q|y6%O%Cdz`(%k>ERLtq@#dXfP)!GF5Ei9YNBFwCPT2Ni(`mIZ?ZsY zLPA4=(n1ZkV~i37Oq1Ro*)Wr-m}7_R_SGlL?9Z5ey5nOohjaHVpYM-2vNs)`{bYmM yWxqfU+ga<}FPrX@SfH;e*sKM zL_t(&f$f)D3d2wcMBPO9zt#5kD-@xQUPWb22<9&^K8BH3FXU1Gzw)Rp9(h#zE020U z6O?MJ7^>NiRz<{y+6ms_hTTQ_D@ui0VpmL*+Tg+Ohe@j5v9Wtlq**d)e_P>$xrDg{(W;2pP&-+2C7DYywCd<5*EaxX$yH=7 z$<|EKs)*=_n_(_NTe8G)r~AkuZ7X~*moS$gS``r+Y9~vsBy&lIRvrE1`Uc=Exr)ps z*_tU@6%ieAGt4DuOO`n9bRRjSZG{i!66O*_t0H1U?PST7WG>0js-vG=WZwXsC0CKT zBwI5@t0JN!Zicx8ZOIbHo$e!tw5{;LT*6#}XjMdPsGTgilFTI;T6Of3>l=WxPx#U{Fj{MF0Q*A|NzHC_H94F^^U|-*G0{pl0sFn)~uLndMMwzA!hGZcAw|QcEN< zJUKr>E1i<6;iUBv|TPVG<3TN&LK zOJ~5dheqHdQ0s($^s5OHjP_ztu<0d8FaNk+i_L$E(mu-w7cl$Bh2hf+&K4y zYrO|-vrYhs*QFS~x}AkLAOV#)qzY5s8)+PZ9IbK9%DR1|g3Fn|pvBJMAt(}S(g#%= m`q%6vnWLtmF$P(l|K1-AV?A<|p*5cX0000= rand.randint(1,100): + BUTTONLESS = True + MOVING_STATUS = True + #take out first arg and make it into the mood ID MOOD_ID = '0' if not MOOD_OFF: @@ -331,6 +343,45 @@ class VideoLabel(tk.Label): self.time_offset_end = time.perf_counter() time.sleep(max(0, self.delay - (self.time_offset_end - self.time_offset_start))) +#moving window originally provided very generously by u/basicmo! +mspd = rand.randint(-MOVING_SPEED,MOVING_SPEED) +while mspd == 0: + mspd = rand.randint(-MOVING_SPEED,MOVING_SPEED) + +def move_window(master, resized_height:int, resized_width:int, xlocation:int, ylocation:int): + width = resized_width + height = resized_height + if MOVING_RANDOM: + move_speedX = rand.randint(-MOVING_SPEED,MOVING_SPEED) + move_speedY = rand.randint(-MOVING_SPEED,MOVING_SPEED) + while (move_speedX == 0) and (move_speedY == 0): + move_speedX = rand.randint(-MOVING_SPEED,MOVING_SPEED) + move_speedY = rand.randint(-MOVING_SPEED,MOVING_SPEED) + else: + move_speedX = mspd + move_speedY = mspd + + dx = move_speedX + dy = move_speedY + + x, y = xlocation, ylocation # Initial position + while True: + x += dx + y += dy + + if x + width >= master.winfo_screenwidth(): + dx = -abs(move_speedX) + elif x <= 0: + dx = abs(move_speedX) + if y + height >= master.winfo_screenheight(): + dy = -abs(move_speedY) + elif y <= 0: + dy = abs(move_speedY) + + master.geometry(f"{width}x{height}+{x}+{y}") + master.update() + master.after(10) + def pick_resource(basepath, vidYes:bool): if MOOD_ID != '0' and os.path.exists(os.path.join(PATH, 'resource', 'media.json')): try: @@ -598,6 +649,10 @@ def run(): f.truncate() root.attributes('-alpha', OPACITY / 100) + + if MOVING_STATUS: + move_window(root,resized_image.height,resized_image.width,locX,locY) + root.mainloop() def startVLC(vid, label): diff --git a/EdgeWare/start.pyw b/EdgeWare/start.pyw index e368f3c0..b4a39e71 100644 --- a/EdgeWare/start.pyw +++ b/EdgeWare/start.pyw @@ -246,6 +246,7 @@ AUDIO_CHANCE = int(settings['audioMod']) PROMPT_CHANCE = int(settings['promptMod']) VIDEO_CHANCE = int(settings['vidMod']) WEB_CHANCE = int(settings['webMod']) +CAP_POP_CHANCE = int(settings['capPopChance']) VIDEOS_ONLY = int(settings['onlyVid']) == 1 @@ -428,10 +429,12 @@ try: for aud in os.listdir(os.path.join(PATH, 'resource', 'aud')): AUDIO.append(os.path.join(PATH, 'resource', 'aud', aud)) logging.info('audio resources found') -except: +except Exception as e: logging.warning(f'no audio resource folder found\n\tReason: {e}') print('no audio folder found') +CAPTIONS = os.path.exists(os.path.join(PATH, 'resource', 'captions.json')) + HAS_WEB = WEB_JSON_FOUND and len(WEB_DICT['urls']) > 0 #end of checking resource presence @@ -897,14 +900,14 @@ def roll_for_initiative(): try: thread.Thread(target=play_audio).start() pumpScareAudio.wait() - except: + except Exception as e: messagebox.showerror('Audio Error', 'Failed to play audio.\n[' + str(e) + ']') logging.critical(f'failed to play audio\n\tReason: {e}') else: try: thread.Thread(target=play_audio).start() pumpScareAudio.wait() - except: + except Exception as e: messagebox.showerror('Audio Error', 'Failed to play audio.\n[' + str(e) + ']') logging.critical(f'failed to play audio\n\tReason: {e}') try: @@ -959,21 +962,29 @@ def roll_for_initiative(): try: thread.Thread(target=play_audio).start() currPopNum += 1 - except: + except Exception as e: messagebox.showerror('Audio Error', 'Failed to play audio.\n[' + str(e) + ']') logging.critical(f'failed to play audio\n\tReason: {e}') else: try: thread.Thread(target=play_audio).start() currPopNum += 1 - except: + except Exception as e: messagebox.showerror('Audio Error', 'Failed to play audio.\n[' + str(e) + ']') logging.critical(f'failed to play audio\n\tReason: {e}') + if do_roll(CAP_POP_CHANCE) and CAPTIONS and currPopNum < maxPopNum: + try: + subprocess.call([sys.executable, 'sublabel.pyw', f'-{MOOD_ID}']) if not MOOD_OFF else subprocess.call([sys.executable, 'sublabel.pyw']) + currPopNum += 1 + except Exception as e: + messagebox.showerror('Caption Popup Error', 'Could not start caption popup.\n[' + str(e) + ']') + logging.critical(f'failed to start sublabel.pyw\n\tReason: {e}') + if do_roll(PROMPT_CHANCE) and HAS_PROMPTS and currPopNum < maxPopNum: try: subprocess.call([sys.executable, 'prompt.pyw', f'-{MOOD_ID}']) if not MOOD_OFF else subprocess.call([sys.executable, 'prompt.pyw']) currPopNum += 1 - except: + except Exception as e: messagebox.showerror('Prompt Error', 'Could not start prompt.\n[' + str(e) + ']') logging.critical(f'failed to start prompt.pyw\n\tReason: {e}') if (not (MITOSIS_MODE or LOWKEY_MODE)) and do_roll(POPUP_CHANCE) and HAS_IMAGES and currPopNum < maxPopNum: @@ -1125,7 +1136,7 @@ def update_media(): #logging.info(f'moodData {moodData}') with open(os.path.join(PATH, 'resource', 'media.json'), 'r') as f: mediaData = json.loads(f.read()) - #logging.info(f'mediaData {mediaData}') + #print(f'mediaData {mediaData}') #if CORRUPTION_MODE: #for try: diff --git a/EdgeWare/sublabel.pyw b/EdgeWare/sublabel.pyw new file mode 100644 index 00000000..8d6864af --- /dev/null +++ b/EdgeWare/sublabel.pyw @@ -0,0 +1,164 @@ +import tkinter as tk +import json +import random +import pathlib +import sys +import os +import logging +import time +import random as rand +from utils import utils + +SYS_ARGS = sys.argv.copy() +SYS_ARGS.pop(0) +PATH = str(pathlib.Path(__file__).parent.absolute()) +os.chdir(PATH) + +logging.basicConfig(filename=os.path.join(PATH, 'logs', time.asctime().replace(' ', '_').replace(':', '-') + '-sublabel.txt'), format='%(levelname)s:%(message)s', level=logging.DEBUG) +#This sublabel.pyw originally provided very generously by u/basicmo! + +def check_setting(name:str, default:bool=False) -> bool: + default = False if default is None else default + try: + return int(settings.get(name)) == 1 + except: + return default + +CAP_OPACITY = 100 +CAP_TIMER = 300 +SUBLIMINAL_MOOD = True +MOOD_OFF = True +THEME = 'Original' + +MOOD_ID = '0' +if len(SYS_ARGS) >= 1 and SYS_ARGS[0] != '0': + MOOD_ID = SYS_ARGS[0].strip('-') + +if MOOD_ID != '0': + if os.path.exists(os.path.join(PATH, 'moods', f'{MOOD_ID}.json')): + with open(os.path.join(PATH, 'moods', f'{MOOD_ID}.json')) as f: + moodData = json.loads(f.read()) + elif os.path.exists(os.path.join(PATH, 'moods', 'unnamed', f'{MOOD_ID}.json')): + with open(os.path.join(PATH, 'moods', 'unnamed', f'{MOOD_ID}.json')) as f: + moodData = json.loads(f.read()) + +with open(os.path.join(PATH, 'config.cfg')) as cfg: + settings = json.loads(cfg.read()) + CAP_OPACITY = int(settings['capPopOpacity']) + CAP_TIMER = int(settings['capPopTimer']) + SUBLIMINAL_MOOD = check_setting('capPopMood') + MOOD_OFF = check_setting('toggleMoodSet') + THEME = settings['themeType'] + +#background is one hex value off here, because it looks pretty ugly if they're different colours, so we keep them close so there is no visual difference +try: + if THEME == 'Original': + fore = '#000000' + back = '#000001' if utils.is_windows() else '#f0f0f0' + mainfont = 'Segoe UI' + if THEME == 'Dark': + fore = '#f9faff' + back = '#f9fafe' if utils.is_windows() else '#282c34' + mainfont = 'Segoe UI' + if THEME == 'The One': + fore = '#00ff41' + back = '#00ff42' if utils.is_windows() else '#282c34' + mainfont = 'Consolas' + if THEME == 'Ransom': + fore = '#ffffff' + back = '#fffffe' if utils.is_windows() else '#841212' + mainfont = 'Arial Bold' + if THEME == 'Goth': + fore = '#ba9aff' + back = '#ba9afe' if utils.is_windows() else '#282c34' + mainfont = 'Constantia' + if THEME == 'Bimbo': + fore = '#ff3aa3' + back = '#ff3aa4' if utils.is_windows() else '#ffc5cd' + mainfont = 'Constantia' +except Exception as e: + logging.fatal(f'failed to load theme. {e}') + fore = '#000000' + back = '#000001' if utils.is_windows() else '#f0f0f0' + mainfont = 'Segoe UI' + +def display_subliminal_message(): + # Load subliminal messages from captions.json + def load_subliminal_messages(): + try: + with open(os.path.join(PATH, 'resource', 'captions.json'), "r") as file: + l = json.load(file) + if l.get("subliminal", []) and SUBLIMINAL_MOOD: + return l.get("subliminal", []) + else: + if 'prefix' in l: del l['prefix'] + if 'subtext' in l: del l['subtext'] + if 'prefix_settings' in l: del l['prefix_settings'] + if MOOD_ID != '0': + allsub = [] + for key in l: + if key in moodData['captions']: + allsub.append(l[key]) + else: + allsub = list(l.values()) + flatlist = [i for sublist in allsub for i in sublist] + #logging.info(flatlist) + return flatlist + except Exception as e: + logging.fatal(f'failed to get sublabel prefixes. {e}') + return [] + + # Get a random subliminal message + def get_random_subliminal(): + subliminal_messages = load_subliminal_messages() + if subliminal_messages: + return random.choice(subliminal_messages) + else: + return "No subliminal messages found." + + # Create the label + label = tk.Label(fg=fore, bg=back) + + # Choose a screen for the window + monitor_data = utils.monitor_areas() + area = rand.choice(monitor_data) + + # Calculate the font size based on screen resolution + f_size = min(area.width, area.height) // 10 # Adjust the scaling factor as needed + + # Configure the font + font = (mainfont, f_size) + + # Configure the label with the calculated font size and wrap the text + label.config(font=font, wraplength=area.width // 1.5) + + # Set the text to a random subliminal message + label.config(text=get_random_subliminal()) + + # Calculate the position to center the window + x = area.x + (area.width - label.winfo_reqwidth()) // 2 + y = area.y + (area.height - label.winfo_reqheight()) // 2 + + # Configure the window + label.master.overrideredirect(True) + label.master.geometry(f'+{x}+{y}') + label.master.lift() + label.master.wm_attributes('-topmost', True) + if utils.is_windows(): + label.master.wm_attributes('-disabled', True) + label.master.wm_attributes('-transparentcolor', back) + label.winfo_toplevel().attributes('-alpha',CAP_OPACITY/100) + label.pack() + + # Update the label's size + label.update_idletasks() + + # Schedule the destruction of the window after 0.3 seconds + label.master.after(CAP_TIMER, label.master.destroy) + + # Start the Tkinter event loop + + label.mainloop() + +# Call the function to display the subliminal message +display_subliminal_message() diff --git a/EdgeWare/utils/linux.py b/EdgeWare/utils/linux.py index a5d9865f..93b27398 100644 --- a/EdgeWare/utils/linux.py +++ b/EdgeWare/utils/linux.py @@ -12,7 +12,7 @@ import subprocess def panic_script(): - subprocess.run('/bin/sh panic.sh', shell=True) + subprocess.run('for pid in $(ps -u $USER -ef | grep -E "python.* *+.pyw" | awk \'{print $2}\'); do echo $pid; kill -9 $pid; done', shell=True) def set_borderless(root): root.wm_attributes('-type', 'splash') diff --git a/EdgeWare/utils/windows.py b/EdgeWare/utils/windows.py index a7a13a85..30db49d3 100644 --- a/EdgeWare/utils/windows.py +++ b/EdgeWare/utils/windows.py @@ -9,6 +9,16 @@ user = ctypes.windll.user32 +class RECT(ctypes.Structure): #rect class for containing monitor info + _fields_ = [ + ('left', ctypes.c_long), + ('top', ctypes.c_long), + ('right', ctypes.c_long), + ('bottom', ctypes.c_long) + ] + def dump(self): + return map(int, (self.left, self.top, self.right, self.bottom)) + def panic_script(): os.startfile('panic.bat') @@ -38,15 +48,13 @@ class MONITORINFO(ctypes.Structure): #unneeded for this, but i don't want to rew def monitor_areas(): areas: list[Area] = [] - monitors = get_monitors() - for hMonitor, _ in monitors: - data = [hMonitor] + for hMonitor, _ in get_monitors(): mi = MONITORINFO() mi.cbSize = ctypes.sizeof(MONITORINFO) mi.rcMonitor = RECT() mi.rcWork = RECT() _ = user.GetMonitorInfoA(hMonitor, ctypes.byref(mi)) - work_area = mi.rcWork.dump() + work_area = list(mi.rcWork.dump()) x, y = work_area[0], work_area[1] areas.append(Area(x, y, work_area[2] - x, work_area[3] - y)) diff --git a/README.md b/README.md index 8f6c7807..45f8f54b 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,16 @@ EdgeWare is a fetish-designed program (so 18+ only!!!) that essentially spawns p PetitTournesol (EdgeWare's original creator) more or less took a hiatus and hasn't updated EdgeWare since 2022, which is totally valid. That being said, I felt like there were lots of things I personally wanted to see in the program. Inspired mostly by being mildly frustrated at deleting those dang desktop icons every time, I decided to start learning python and share the changes i've made. Thus EdgeWare++ was born, and as of writing this "new and improved" intro, there's over 10 new features to play around with. Some are quality of life updates, some are more fun things to tease yourself with. I'm generally trying to be as minimally intrusive to the original program as possible- my goal is complete both-ways pack compatibility with the old version of EdgeWare. I also don't want to remove any features (unless they were literally defunct), but I have moved some stuff around in the config menu to try and make more space. -**Any damage you do to your computer with EdgeWare is your own responsibility! Please read the "About" tab in the config window and make backups if you're planning on using the advanced, dangerous settings!** - -The EdgeWare++ Pack Editor is now live [here](https://github.com/araten10/EdgewareEditor-PlusPlus). +## Usage Instructions +"So how do I start using this darn thing?" Click the big ol' "code" button in the top right, then "download zip". Save and extract it somewhere, then run "EdgewareSetup.bat". This will install python 3.10 for you, if you don't already have it. After that it will give you instructions for further use, and open up "config.pyw" in the EdgeWare subfolder. -## So how do I start using this darn thing? +**If you're using Linux**, first you need to install Python 3 and pip yourself, if you don't already have them installed already. Your distribution should contain packages for them. For example, on Debian and its derivatives, you can install them by running `sudo apt install python3 python3-pip`. Once you have Python and pip installed, download and extract EdgeWare as a ZIP or clone the repository, then install the dependencies by opening a terminal window in the `EdgewarePlusPlus` direcory and running `pip3 install -r requirements.txt` or `python3 -m pip install -r requirements.txt`. Now you can run EdgeWare by starting `config.pyw` or `start.pyw` with Python: `python3 path/to/file.pyw`. `config.pyw` allows you to configure EdgeWare, and `start.pyw` will start EdgeWare itself. **Please note that my primary OS is windows!** I have gotten endless help from LewdDevelopment, who also used some [pre-existing code from a old EdgeWare pull request](https://github.com/PetitTournesol/Edgeware/pull/41) to help make it happen. So if any bugs on Linux start from my own incompetence, I will consult with them and try to fix it- but know that I will not know until people tell me or them! -Click the big ol' "code" button in the top right, then "download zip". Save and extract it somewhere, then, if you're using Windows, run "EdgewareSetup.bat". This will install python 3.10 for you, if you don't already have it. After that it will give you instructions for further use, and open up "config.pyw" in the EdgeWare subfolder. +From there you'll need an actual pack, which can be downloaded online or made yourself. Unfortunately at the time of writing there's really no congregated directory of packs everyone's made, they're all scattered to the four winds... but for a start [the original EdgeWare page](https://github.com/PetitTournesol/Edgeware) has a few sample packs, and i'm hoping to make a few myself to showcase the new features this extension can do. -If you're using Linux, first you need to install Python 3 and pip yourself, if you don't already have them installed already. Your distribution should contain packages for them. For example, on Debian and its derivatives, you can install them by running `sudo apt install python3 python3-pip`. Once you have Python and pip installed, download and extract EdgeWare as a ZIP or clone the repository, then install the dependencies by opening a terminal window in the `EdgewarePlusPlus` direcory and running `pip3 install -r requirements.txt` or `python3 -m pip install -r requirements.txt`. Now you can run EdgeWare by starting `config.pyw` or `start.pyw` with Python: `python3 path/to/file.pyw`. `config.pyw` allows you to configure EdgeWare, and `start.pyw` will start EdgeWare itself. +**Any damage you do to your computer with EdgeWare is your own responsibility! Please read the "About" tab in the config window and make backups if you're planning on using the advanced, dangerous settings!** -From there you'll need an actual pack, which can be downloaded online or made yourself. Unfortunately at the time of writing there's really no congregated directory of packs everyone's made, they're all scattered to the four winds... but for a start [the original EdgeWare page](https://github.com/PetitTournesol/Edgeware) has a few sample packs, and i'm hoping to make a few myself to showcase the new features this extension can do. +The EdgeWare++ Pack Editor is now live [here](https://github.com/araten10/EdgewareEditor-PlusPlus). ## New Features In Edgeware++: @@ -64,7 +63,11 @@ From there you'll need an actual pack, which can be downloaded online or made yo •*Allowing creation of a per-pack config setup, to help pack creators show off their "intended" settings* -•*Linux compatibility* +•*Moving Popups, which bounce around the screen [like some other infamous programs](https://www.youtube.com/watch?v=LSgk7ctw1HY)* + +•*Subliminal Message popups, which use the captions file to flash short mantras up randomly* + +•*Experimental Linux support that may or may not break in the future (read the section above!)* ## Planned Additions: @@ -86,14 +89,10 @@ I'm also wanting to add features to the pack editor, will probably do that when Suggestions I got from people who used the software and I thought would be interesting enough to try. Lower priority than my own planned additions, but still something I hope to add (or attempt to) in the future! -•*New type of popup: full screen text, taken from caption file. Like subliminals but briefly flashing on full screen.* - •*Randomized settings button* •*Support for confining edgeware to a single monitor, which actually sounds like a really good idea but i'd want to test it on a few monitor setups and it sounds fairly technical* -•*More "classic virus"-y popup options, like [moving popups](https://www.youtube.com/watch?v=LSgk7ctw1HY)* - •*Windows "stealing focus", forcing you to do things like enter prompts before you're allowed to do anything else* •*Different folder for long audio files or "BGM", to be able to separate short and longer sounds (or another solution to this if not a folder)* @@ -111,7 +110,7 @@ A test pack featuring a sampler of all (finished) features found in EdgeWare++, I understand i'm pretty elusive and don't talk much, so I apologize if it's difficult to contact me. If you are looking to report a bug, I don't fully know how to use github but I do know a bug report feature works, so we can figure it out together... I also recently made a [twitter account](https://twitter.com/ara10ten) (finally) and post both random ramblings and frequent development updates/screenshots there. I also have a reddit account I use to answer tech support questions and browse for new packs occasionally on /r/edgingware (of which I have no official affiliation with). -1/31/2024: I have been suspended from twitter due to a mass suspension for reasons that don't make sense and are likely a bug. I am not sure if I will get my account back. I am leaving the link up in case I do, otherwise I will find another way to talk with you guys... +My twitter account seems to love getting shadowbanned for no reason other than twitter automoderation thinks I'm a spambot, so if my account disappears for a day or replies don't send notifications, that's why. If it gets bad enough I might make a bluesky account, but for the time being it's tolerable enough I'll stay on the platform with more people. ## EdgeWare++ Patch Notes **If you see that there's a new update and are somebody (like me) who is lazy and doesn't like installing every single update if unnecessary, here is how I do versioning:** @@ -122,6 +121,47 @@ I understand i'm pretty elusive and don't talk much, so I apologize if it's diff •+0.2-0.1 to version number: very small update, usually just a bugfix, accessibility options, UI tweaks +**Version 10a** +Another month, another long story~! So I got roughly halfway through working on corruption, and was going to ship a small version, likely 9.5 or 9.6, then was sent a DM with some interesting new features. I decided heck, since they're already programmed and were things I wanted to do anyways, i'll add them in. Then when I was about done with them, I got sent another DM with fatal bugs that definitely should be fixed sooner than later. While looking into potential causes and ruminating it on twitter, I got sent another DM from somebody who submitted a pull request for Linux compatibility, saying they already potentially fixed the bug since they noticed some things that were wrong. So then I went and merged the linux pull request and did some bugfixes while talking with the person who submitted it to iron some things out. Eventually I just said "screw it", and decided this was large enough to be a full new version. Theoretically I could name it 9.9 to keep my promise of corruption being finished by version 10, however I think i'd rather stick to more consistent versioning rules (as consistent as I get with them, at least) than try and make a loophole for my own problems. + +Anyways, this is probably one of the largest updates EdgeWare++ has ever had. Because of that i'm cautiously appending an "alpha" label onto it, in case the weight of it causes more bugs than usual. As always, if this catastrophically doesn't work, I will try my best to fix it ASAP! + +•*Actually fixed the bug "hotfixed" with 9.3a properly this time, making the pack config preset button deactivate when no config.json is loaded* + +•*Removed "prefix_settings" from the captions mood treeview as it's something that slipped past me and I guarantee disabling it would break everything* + +•*Stopped Timer Mode automatically adding a startup script, as I feel like it doing so was a hidden quirk that isn't totally obvious to new users. Instead, enabling Timer Mode now automatically enables Run on Startup, which is visible in the config window and can be turned off if the user desires.* +>In practice, this should work exactly the same as it used to, with the main difference being that users can now tell Timer Mode also enables Run on Startup. They also can uncheck Run on Startup if they just want Timer Mode without it enabling every time they restart their PC. + +•*Turned off "Show on Discord" for default config setting* +>Listen, i'm all for retaining as much of the original edgeware as possible but after having this almost cause an accident on my work account and also hearing somebody not know it was a feature after running edgeware for ten minutes i'm inclined to turn it off for your first launch + +•*A ton more frontend work on corruption* +>Most, if not all of the corruption settings I plan to ship version 11 (or whatever it will end up being at this point) with are now in view on the corruption tab, the backend is still not there yet but that will be done over the following patches + +•*Fixed some minor issues with prefix_settings in the config window, for the 2 packs that have managed to implement multi-click popups without the pack editor update* + +•*New popup type: Subliminal Messages!, originally by /u/basicmo!* +>These can be found in a new sub-section in the annoyance window, and use the captions.json file to flash short messages up at you. Their appearance is based on the theme you've chosen, and can use a new mood called "subliminal" to specifically label shorter captions (like "OBEY", or "GOON") that won't show up on popups. + +•*New popup type: Moving Popups!, originally by /u/basicmo!* +>This will make popups have a chance to start bouncing around all over your screen, like an old DVD player screensaver. There are multiple slider settings for this in "annoyance". To make it not impossible to close these popups, anything that turns into a moving popup enables the "labeless" property. + +•*Basic linux compatibility may or may not work now!* +>Please read the above "Usage Instructions" section for more info on this! This is a huge reason why this update is labelled as an "alpha", so please note it might not fully work! Bug reports are appreciated! + +•*Fixed a huge issue with audio error checking that could cause the whole program to not load, and captions.json causing crashes when undefined* + +KNOWN ISSUES: + +•*I have noticed that Timer Mode in itself is very buggy- something that I haven't really touched much before. For me on Windows 11, it appears that it is hard to consistently panic, tray panic doesn't seem to work at all, sometimes the command window pops up and sometimes it doesn't. I haven't really touched it and theoretically nothing I added should affect how it functions, so I am not sure if it was this buggy with original edgeware or if it is indeed my fault. Alternatively, it could be compatibility problems with my setup. I will try to bugfix it to the best of my ability but for the time being know that I'm aware of it's odd behaviour.* + +•*In some cases, packs that worked with old EdgeWare no longer work in ++. I have only seen this happen with two packs, and have yet to completely figure out why, but it might have something to do with how their captions are set up. I'm going to continue looking into this over time and seeing if I can get full 100% compatibility with old packs, even if they're so old or basic that they are structured very differently to everything else.* + +•*If you don't launch the config window at least once, the mood feature absolutely explodes since it hasn't generated moods. I understand nobody's probably using moods yet, but for the time being... if you're moving all your files in the resource folder to a new install or update, make sure to launch the config window once! This should be one of the simpler things to fix, and will be pushed sometime soon* + +•*The default theme for subliminal popups is black text, which means that on a lot of "ahegao wallpapers" that are black and white it might have troubles with visibility. Currently brainstorming ways to fix this, between either an outline or just allowing users to set their own colour* + **Version 9.3** Remember about how I was banned from reddit because automoderation deleted my account thinking I was a spambot? Now twitter banned me because it said I was "evading suspension" (despite not having any other twitter accounts)! Turns out this is happening to a ton of people, it seems to be some strange bug that is just mass banning people. In theory this should be fine and I should get my account back, but it's twitter, and considering how dismal the automated support I got in trying to appeal this has been so far, I don't have much hope. I kind of feel like the outcome will be acknowledging the bug but not reinstating my account. @@ -133,7 +173,7 @@ I'm kind of frustrated as this is the second account i've had suspended in a wee •*Also axed the corruption path "fancy text", since it didn't play well with most themes, and was overly code heavy for what it was* •*Added a "Pack Config Preset" setting in the "Pack Info" tab, which allows for pack creators to make settings presets for their packs. These are not automatically saved, and the user can view the changes before deciding to save themselves or simply exit the program.* ->To make one of these for your pack, create a "config.json" file in your pack resource zip. This follows the exact same formatting as a regular config file, but doesn't need all of the arguments to run properly. For example, if you just wanted to change popup delay to 5 seconds, you could just create a config.json file with the contents {"delay": 1000}. Alternatively, you could also just save your config.cfg, and copy it over to this json file, whatever is easier. +>To make one of these for your pack, create a "config.json" file in your pack resource zip. This follows the exact same formatting as a regular config file, but doesn't need all of the arguments to run properly. For example, if you just wanted to change popup delay to 5 seconds, you could just create a config.json file with the contents {"delay": 5000}. Alternatively, you could also just save your config.cfg, and copy it over to this json file, whatever is easier. *Added a few more backend things to the corruption tab, progressing it's status from "unimplemented" to "still unimplemented, but closer to completion".*