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:
123
src/overlay.py
Normal file
123
src/overlay.py
Normal 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
160
src/warframe_api.py
Normal 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
|
||||||
Reference in New Issue
Block a user