Skip to content

Commit

Permalink
feat: system tray and notifications (#19)
Browse files Browse the repository at this point in the history
Added System Tray and Notifications by @KILLXRX
  • Loading branch information
manucabral committed Sep 1, 2023
2 parents 9032118 + 01231d6 commit 628c54d
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ __pycache__/
*.so

# Distribution / packaging
vscode/
.vscode/
.Python
build/
develop-eggs/
Expand Down
Binary file added icon.ico
Binary file not shown.
37 changes: 28 additions & 9 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,54 @@
import json
import platform
import os
import sys
import requests
from src import App
from src import Logger
from src import __version__, __title__, __clientid___


def prepare_environment():
global raw_settings
try:
raw_settings = json.load(open("settings.json"))
except FileNotFoundError:
raw_settings = {"firstRun": True}
except json.decoder.JSONDecodeError:
Logger.write(message="Invalid settings.json file.", level="ERROR")
Logger.write(message="Please delete settings.json and try again.")
os.remove('settings.json')
exit()
if raw_settings["firstRun"] is True:
with open("settings.json", "w") as settings_file:
# TODO: add system tray icon or cmd prompt selection
# TODO: add system tray icon or cmd prompt selection --> Done! --Nelly
Logger.write(message="First run detected.")
custom_clientid = input("Use custom ClientId? (yes/no): ")
client_id = (
__clientid___
if custom_clientid.lower() == "no"
else int(input("App | Enter your ClientId (number): "))
else input("App | Enter your ClientId (number): ")
)
profile = input("App | Enter your Profile Name (Default): ")
refreshRate = input("App | Refresh rate in seconds (number): ")
useTimeLeft = input(
"App | Display time remaining instead of elapsed time? (yes/no): "
)
new_settings = json.dumps(
{
raw_settings = {
"firstRun": False,
"client_id": client_id,
"profile_name": profile or "Default",
"RefreshRate": refreshRate or 1,
"RefreshRate": int(refreshRate) or 1,
"DisplayTimeLeft": useTimeLeft.lower() or "yes",
}
)
if not os.path.exists("./icon.ico"):
print("WARNING | Icon not found! downloading now.")
icon = requests.get('https://killxr.skycode.us/icon.ico')
open('icon.ico', 'wb').write(icon.content)
print("App | Icon downloaded successfully.")
new_settings = json.dumps(raw_settings)
json.dump(json.loads(new_settings), settings_file)
Logger.write(message="Settings saved.")
return new_settings
return raw_settings
Logger.write(message="Settings loaded successfully.")
return raw_settings

Expand All @@ -51,6 +59,17 @@ def prepare_environment():
Logger.write(message="Sorry! only supports Windows.", level="ERROR")
exit()
settings = prepare_environment()
#using conhost to allow the functionality of hidding and showing the console.
if len(sys.argv) == 1 and sys.argv[0] != 'main.py':
found = []
for file in os.listdir(os.getcwd()):
if file.endswith('.exe'):
found.append(file)
file = found[0]
#print(f'cmd /k {os.environ["SYSTEMDRIVE"]}\\Windows\\System32\\conhost.exe ' + cmd)
os.system(f'cmd /c {os.environ["SYSTEMDRIVE"]}\\Windows\\System32\\conhost.exe {os.path.join(os.getcwd(),file)} True')
exit()
os.system(f'cmd /c taskkill /IM WindowsTerminal.exe /F') #removed /IM cmd.exe in case that causes problems for windows 10. Windows 11 requires starting a new task and killing windows terminal.
app = App(
client_id=settings["client_id"],
version=__version__,
Expand All @@ -63,4 +82,4 @@ def prepare_environment():
app.run()
except KeyboardInterrupt:
Logger.write(message="User interrupted.")
input("Press any key to continue...")
app.stop()
2 changes: 1 addition & 1 deletion settings.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"firstRun": true, "client_id": "1145497578583105596", "profile_name": "Default", "RefreshRate": 1, "DisplayTimeLeft": "yes"}
{"firstRun": true, "client_id": "1", "profile_name": "1", "RefreshRate": 1, "DisplayTimeLeft": "yes"}
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
setup(
name=__title__,
version=__version__,
description="A simple Youtube Music rich presence for Discord",
# description displays in the notification for some reason
description="YT Music RPC",
author=__author__,
options={
"build_exe": {
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

__all__ = ["App", "Logger"]
__title__ = "Youtube Music RPC"
__version__ = "0.5.3"
__version__ = "0.5.5"
__author__ = "Manuel Cabral"
__contributors__ = ["Nelly Angels"]
__license__ = "MIT"
Expand Down
65 changes: 60 additions & 5 deletions src/app.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import time
import os
import math
from infi.systray import SysTrayIcon
from .presence import Presence
from .logger import Logger
from .tab import Tab
from .notification import ToastNotifier
import win32con
import win32gui
import ctypes
import json
from .utils import (
remote_debugging,
run_browser,
Expand All @@ -13,6 +19,7 @@
)

DISCORD_STATUS_LIMIT = 15
toast = ToastNotifier()


class App:
Expand All @@ -28,6 +35,8 @@ class App:
"__profileName",
"refreshRate",
"useTimeLeft",
"showen",
"systray",
)

def __init__(
Expand All @@ -48,6 +57,7 @@ def __init__(
self.last_tab = None
self.connected = False
self.__browser = None
self.showen = True
self.refreshRate = refreshRate
self.useTimeLeft = useTimeLeft
self.__profileName = profileName
Expand All @@ -69,13 +79,16 @@ def sync(self) -> None:
self.connected = True
Logger.write(message=f"{self.__browser['fullname']} detected.", origin=self)
except Exception as exc:
#raise exc
self.__handle_exception(exc)

def stop(self) -> None:
self.connected = False
self.__presence.close()
Logger.write(message="stopped.", origin=self)

if self.connected == True:
self.connected = False
self.systray.shutdown()
self.__presence.close()
Logger.write(message="stopped.", origin=self)

def update_tabs(self) -> None:
tabs = []
tab_list = get_browser_tabs(filter_url="music.youtube.com")
Expand All @@ -95,13 +108,45 @@ def current_playing_tab(self, tabs: list) -> dict:
if tab.pause:
return tab
return None

def hideWindow(self, systray):
if self.showen is True:
self.showen = False
window = ctypes.windll.kernel32.GetConsoleWindow()
win32gui.ShowWindow(window, win32con.SW_HIDE)
elif self.showen is False:
self.showen = True
window = ctypes.windll.kernel32.GetConsoleWindow()
win32gui.ShowWindow(window, win32con.SW_SHOW)

def update(self, systray):
toast = ToastNotifier()
try:
toast.show_toast(
"Coming soon!",
"This feature isn't currently avaiable yet.",
duration = 5,
icon_path = f"{os.path.join(os.getcwd(), 'icon.ico')}",
threaded = True,
)
except TypeError:
pass

def on_quit_callback(self, systray):
if self.connected == True:
self.connected = False
self.__presence.close()
Logger.write(message="stopped.", origin=self)

def run(self) -> None:
global lastUpdated
global compareTab
compareTab = {"title": "", "artist": "", "artwork": "", "lastTime": 0}
lastUpdated = 1
try:
menu_options = (("Hide/Show Console", None, self.hideWindow), ("Force Update", None, self.update))
self.systray = SysTrayIcon("./icon.ico", "YT Music RPC", menu_options, on_quit=self.on_quit_callback)
self.systray.start()
if not self.connected:
raise RuntimeError("Not connected.")
browser_process = self.__browser["process"]["win32"]
Expand Down Expand Up @@ -197,7 +242,16 @@ def useTimeLeft(answer):
if answer == "yes":
return self.last_tab.end + self.refreshRate
return None

try:
toast.show_toast(
"Now Playing!",
f"{self.last_tab.title} by {self.last_tab.artist}",
duration = 3,
icon_path = f"{os.path.join(os.getcwd(), 'icon.ico')}",
threaded = True,
)
except TypeError:
pass
self.__presence.update(
details=self.last_tab.title,
state=self.last_tab.artist,
Expand All @@ -219,6 +273,7 @@ def useTimeLeft(answer):
# time.sleep(self.refreshRate)
except Exception as exc:
self.__handle_exception(exc)
# raise exc
if exc.__class__.__name__ == "URLError":
Logger.write(
message="Please close all browser instances and try again. Also, close Youtube Music Desktop App if you are using it.",
Expand Down
Loading

0 comments on commit 628c54d

Please sign in to comment.