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