Initial commit

The following components and features have been implemented per this initial commit:
- Python library wrapping warframestat.us API endpoint w/ decorated functions
- Full transparency for overlay window (click through and alpha)
- Added status for open worlds
- Added support for variable window sizes (does not resize automatically)
This commit is contained in:
雲華
2021-05-01 18:28:02 -04:00
commit 04b4b96cae
2 changed files with 283 additions and 0 deletions

123
src/overlay.py Normal file
View File

@@ -0,0 +1,123 @@
import tkinter as tk
import warframe_api as warpy
import win32gui
import win32api
import win32con
import requests
from ctypes import windll
GWL_EXSTYLE = -20
WS_EX_APPWINDOW = 0x00040000
WS_EX_TOOLWINDOW = 0x00000080
X_POS_WIN_OFFSET = 8
Y_POS_WIN_OFFSET = 31
# DEFINE BASE TK WINDOW SETTINGS
root = tk.Tk()
root.title("TennoUI")
root.wm_attributes("-topmost", True) # Keep at the top most layer (needs change)
root.wm_attributes("-disabled", True) # Disable interactions with the window
root.wm_attributes("-transparent", '#2e3440') # Used to make window transparent
root.overrideredirect(True) # Remove the title bar
def enable_clickthrough(_root):
"""
Sets the app window to be click through.
:param _root: Tkinter parent
:return:
"""
_hwnd = windll.user32.GetParent(_root.winfo_id())
style = windll.user32.GetWindowLongPtrW(_hwnd, GWL_EXSTYLE)
style |= win32con.WS_EX_TRANSPARENT | win32con.WS_EX_LAYERED
win32gui.SetWindowLong(_hwnd, win32con.GWL_EXSTYLE, style)
def disable_clickthrough(_root):
"""
TODO: Properly implement this so it can undo click-through transparency
:param _root: Tkinter parent
:return:
"""
_hwnd = windll.user32.GetParent(_root.winfo_id())
style = windll.user32.GetWindowLongPtrW(_hwnd, GWL_EXSTYLE)
style |= win32con.WS_EX_COMPOSITED | win32con.WS_EX_LAYERED
win32gui.SetWindowLong(_hwnd, win32con.GWL_EXSTYLE, style)
root.after(10, lambda: enable_clickthrough(root)) # Make overlay click through.
# Define the root's geometry based on the geometry of specified application.
# List of Warframe class names:
# - WarframePublicEvolutionGfxD3D12
# - WarframePublicEvolutionGfxD3D11 (NEED TO TEST THIS VALUE)
hwnd = win32gui.FindWindow("WarframePublicEvolutionGfxD3D12", None)
fullscreen = borderless = False
if hwnd:
# Window rectangle includes title bar which results in a mismatch when applying the overlay.
# Client rectangle provides the correct dimensions but does not provide the x and y positions
# of the window relative to the screen space, hence their retrieval with GetWindowRect().
x_pos, y_pos, _, _ = win32gui.GetWindowRect(hwnd)
_, _, width, height = win32gui.GetClientRect(hwnd)
if win32gui.GetWindowPlacement(hwnd)[1] == win32con.SW_SHOWMAXIMIZED:
# Checks to see if the window is fullscreen.
fullscreen = True
if width == win32api.GetSystemMetrics(0) and height == win32api.GetSystemMetrics(1):
# Compares screen space to client dimensions. The window is likely borderless if
# the width and height match the screen space and fullscreen is not detected.
borderless = True
else:
# Assume fullscreen
x_pos, y_pos, width, height, fullscreen = 0, 0, win32api.GetSystemMetrics(0), win32api.GetSystemMetrics(1), True
if fullscreen:
# root.wm_attributes("-fullscreen", True)
print("Fullscreen is not supported") # Currently unable to display above fullscreen apps
exit()
elif borderless:
root.geometry('%dx%d+%d+%d' % (width, height, x_pos, y_pos))
print("Borderless")
else:
# Offset required to properly place the window on-top of client rectangle
root.geometry('%dx%d+%d+%d' % (width, height, x_pos + X_POS_WIN_OFFSET, y_pos + Y_POS_WIN_OFFSET))
print("Windowed")
timers = tk.Frame(master=root, bg="#FFFFFF", width=340, height=160, cursor="none")
warframe = warpy.WarframeAPI("pc", requests.session())
timers.grid(row=0, column=0)
tk.Label(timers, text="Cetus", fg="#FFFFFF").grid(row=0, column=0)
tk.Label(timers, text="Deimos", fg="#FFFFFF").grid(row=0, column=2)
tk.Label(timers, text="Vallis", fg="#FFFFFF").grid(row=0, column=4)
cetus_timer = tk.Label(timers)
vallis_timer = tk.Label(timers)
cambion_timer = tk.Label(timers)
cetus_timer.grid(row=0, column=1)
cambion_timer.grid(row=0, column=3)
vallis_timer.grid(row=0, column=5)
def current_cycles():
cetus_status = warframe.cetus_status()
vallis_status = warframe.vallis_status()
cambion_status = warframe.cambion_status()
if cetus_status["isDay"]:
cetus_timer.config(text="Day")
else:
cetus_timer.config(text="Night")
if vallis_status["isWarm"]:
vallis_timer.config(text="Warm")
else:
vallis_timer.config(text="Cold")
cambion_timer.config(text=cambion_status["active"].capitalize())
root.after(300000, current_cycles)
current_cycles()
root.mainloop()

160
src/warframe_api.py Normal file
View File

@@ -0,0 +1,160 @@
from requests import request
class NonPlatformError(Exception):
pass
def catch_status_code(f):
def func(*args, **kwargs):
response = f(*args, **kwargs)
if response.status_code != 200:
response.raise_for_status()
return response.json()
return func
class WarframeAPI:
_platforms = ['pc', 'ps4', 'xb1', 'swi']
def __init__(self, platform: str, session: request, language: str = 'en'):
if platform not in self._platforms:
raise NonPlatformError(platform)
self.platform = platform
self.language = language
self.api = 'https://api.warframestat.us/{platform}'.format(platform=self.platform)
self.session = session
@catch_status_code
def worldstate(self):
response = self.session.get(self.api)
return response
@catch_status_code
def alerts(self):
response = self.session.get(self.api + "/alerts")
return response
@catch_status_code
def arbitration(self):
response = self.session.get(self.api + "/arbitration")
return response
@catch_status_code
def cambion_status(self):
response = self.session.get(self.api + "/cambionCycle")
return response
@catch_status_code
def cetus_status(self):
response = self.session.get(self.api + "/cetusCycle")
return response
@catch_status_code
def conclave_challenges(self):
response = self.session.get(self.api + "/cetusCycle")
return response
@catch_status_code
def construction_progress(self):
response = self.session.get(self.api + "/constructionProgress")
return response
@catch_status_code
def darvo_deal(self):
response = self.session.get(self.api + "/dailyDeals")
return response
@catch_status_code
def earth_cycle(self):
response = self.session.get(self.api + "/earthCycle")
return response
@catch_status_code
def ongoing_events(self):
response = self.session.get(self.api + "/events")
return response
@catch_status_code
def fissures(self):
response = self.session.get(self.api + "/fissures")
return response
@catch_status_code
def darvo_flash_sale(self):
response = self.session.get(self.api + "/flashSales")
return response
@catch_status_code
def global_upgrades(self):
response = self.session.get(self.api + "/globalUpgrades")
return response
@catch_status_code
def invasions(self):
response = self.session.get(self.api + "/invasions")
return response
@catch_status_code
def kuva_nodes(self):
response = self.session.get(self.api + "/kuva")
return response
@catch_status_code
def news(self):
response = self.session.get(self.api + "/news")
return response
@catch_status_code
def nightwave(self):
response = self.session.get(self.api + "/nightwave")
return response
@catch_status_code
def persistent_enemy_data(self):
response = self.session.get(self.api + "/persistentEnemies")
return response
@catch_status_code
def riven_stats(self, query: str = None):
if query:
response = self.session.get(self.api + "/rivens/search/{query}".format(query=query))
else:
response = self.session.get(self.api + "/rivens")
return response
@catch_status_code
def sentient_outpost(self):
response = self.session.get(self.api + "/sentientOutposts")
return response
@catch_status_code
def sanctuary_status(self):
response = self.session.get(self.api + "/simaris")
return response
@catch_status_code
def sortie(self):
response = self.session.get(self.api + "/sortie")
return response
@catch_status_code
def syndicate_nodes(self):
response = self.session.get(self.api + "/syndicateMissions")
return response
@catch_status_code
def worldstate_timestamp(self):
response = self.session.get(self.api + "/timestamp")
return response
@catch_status_code
def vallis_status(self):
response = self.session.get(self.api + "/vallisCycle")
return response
@catch_status_code
def void_trader(self):
response = self.session.get(self.api + "/voidTrader")
return response