Skip to content

Commit

Permalink
Implement Linux support
Browse files Browse the repository at this point in the history
- disable building on Linux (TODO)
- add winreg compatibility layer to allow reusing existing code without having to write an new system
- replace ctypes usage available python replacement
- gate windows logic behind os.name == "nt" checks (TODO compare how this behaves on cygwin and msys)
- update hl2 linux executable name to use the script instead
- replace hardcoded usage of "*.exe" with indexing of the executables list
-
  • Loading branch information
Jan200101 committed Nov 15, 2021
1 parent ef6af0c commit e98af8b
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 39 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__
*.pyc
15 changes: 6 additions & 9 deletions TF2 Rich Presence/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,7 @@ def main(version_num='v2.1'):
python_target = os.path.abspath(Path(f'{new_build_folder_name}/resources/{interpreter_name}'))
print(f"Copying from {python_source}\n\tto {python_target}: ", end='')
assert os.path.isdir(python_source) and not os.path.isdir(python_target)
if sys.platform == 'win32':
subprocess.run(f'xcopy \"{python_source}\" \"{python_target}\\\" /E /Q')
else:
raise SyntaxError("Whatever the Linux/MacOS equivalent of xcopy is")
subprocess.run(f'xcopy \"{python_source}\" \"{python_target}\\\" /E /Q')
elif os.path.isfile(python_source_zip):
python_target = os.path.abspath(Path(f'{new_build_folder_name}/resources'))
print(f"Extracting from {python_source_zip}\n\tto {python_target}")
Expand Down Expand Up @@ -474,10 +471,7 @@ def copy_dir(source, target):
pass

print(f"Copying from {source} to {target}: ", end='')
if sys.platform == 'win32':
subprocess.run(f'xcopy \"{source}\" \"{target}{os.path.sep}\" /E /Q')
else:
raise SyntaxError("Whatever the Linux/MacOS equivalent of xcopy is")
subprocess.run(f'xcopy \"{source}\" \"{target}{os.path.sep}\" /E /Q')


# log all prints to a file
Expand All @@ -499,4 +493,7 @@ def finish(self):


if __name__ == '__main__':
main()
if os.name == 'nt':
main()
else:
print("Build is not available on non Windows systems")
14 changes: 11 additions & 3 deletions TF2 Rich Presence/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
# cython: language_level=3

import os
import winreg
try:
import winreg
except ImportError:
import unixreg as winreg
from typing import Callable, List, Optional, Tuple, Union

import vdf
Expand Down Expand Up @@ -136,7 +139,7 @@ def steam_config_file(self, exe_location: str, require_condebug: bool) -> Option

# given Steam's install, find a TF2 install
def find_tf2_exe(self, steam_location: str) -> Optional[str]:
extend_path: Callable[[str], str] = lambda path: os.path.join(path, 'steamapps', 'common', 'Team Fortress 2', 'hl2.exe')
extend_path: Callable[[str], str] = lambda path: os.path.join(path, 'steamapps', 'common', 'Team Fortress 2', 'hl2.' + ('exe' if os.name == "nt" else 'sh'))
default_path: str = extend_path(steam_location)

if is_tf2_install(self.log, default_path):
Expand Down Expand Up @@ -198,7 +201,12 @@ def is_tf2_install(log: logger.Log, exe_location: str) -> bool:
# Steam seems to update this often enough
def get_steam_username() -> str:
key: winreg.HKEYType = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"SOFTWARE\\Valve\\Steam\\")
username: str = winreg.QueryValueEx(key, 'LastGameNameUsed')[0]
username: str
try:
username = winreg.QueryValueEx(key, 'LastGameNameUsed')[0]
except FileNotFoundError:
# TODO Jan: implement username detection for Linux systems
username = ""
key.Close()
return username

Expand Down
6 changes: 5 additions & 1 deletion TF2 Rich Presence/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,13 @@ def launch_tf2(self):
self.launch_tf2_button['text'] = self.loc.text("Launching...")
self.safe_update()

kwargs = {}
if os.name == "nt":
kwargs["creationflags"] = 0x08000000

if self.tf2_launch_cmd:
self.launched_tf2_with_button = True
subprocess.Popen(f'"{self.tf2_launch_cmd[0]}" {self.tf2_launch_cmd[1]} -game tf -steam -secure -condebug -conclearlog', creationflags=0x08000000)
subprocess.Popen(list(self.tf2_launch_cmd) + ["-game", "tf", "-steam", "-secure", "-condebug", "-conclearlog"], **kwargs)
else:
messagebox.showerror(self.loc.text("TF2 Rich Presence"), self.loc.text("Couldn't find a Team Fortress 2 installation."))
self.launch_tf2_button['state'] = 'normal'
Expand Down
3 changes: 1 addition & 2 deletions TF2 Rich Presence/localization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# https://github.com/Kataiser/tf2-rich-presence/blob/master/LICENSE
# cython: language_level=3

import ctypes
import functools
import json
import locale
Expand Down Expand Up @@ -119,7 +118,7 @@ def detect_system_language(log: logger.Log):

if not db['has_asked_language']:
language_codes: dict = {read_localization_files()[lang]['code']: lang for lang in langs[1:]}
system_locale: str = locale.windows_locale[ctypes.windll.kernel32.GetUserDefaultUILanguage()]
system_locale: str = locale.getlocale()[0]
system_language_code: str = system_locale.split('_')[0]
is_brazilian_port: bool = system_locale == 'pt_BR'

Expand Down
10 changes: 7 additions & 3 deletions TF2 Rich Presence/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
import utils


if os.name == "nt":
APPDATA = os.getenv('APPDATA')
else:
APPDATA = os.path.join(os.getenv("HOME"), ".config")

# TODO: replace this whole thing with a real logger
class Log:
def __init__(self, path: Optional[str] = None):
Expand All @@ -30,11 +35,10 @@ def __init__(self, path: Optional[str] = None):
self.logs_path: str = 'logs'
created_logs_dir: bool = False
else:
self.logs_path = os.path.join(os.getenv('APPDATA'), 'TF2 Rich Presence', 'logs')
self.logs_path = os.path.join(APPDATA, 'TF2 Rich Presence', 'logs')

if not os.path.isdir(self.logs_path):
os.mkdir(os.path.join(os.getenv('APPDATA'), 'TF2 Rich Presence'))
os.mkdir(self.logs_path)
os.makedirs(self.logs_path)
time.sleep(0.1) # ensure it gets created
created_logs_dir = True
else:
Expand Down
10 changes: 7 additions & 3 deletions TF2 Rich Presence/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,9 @@ def loop_body(self):
if not self.has_set_process_priority:
self_process: psutil.Process = psutil.Process()
priorities_before: tuple = (self_process.nice(), self_process.ionice())
self_process.nice(psutil.BELOW_NORMAL_PRIORITY_CLASS)
self_process.ionice(psutil.IOPRIO_LOW)
if os.name == "nt":
self_process.nice(psutil.BELOW_NORMAL_PRIORITY_CLASS)
self_process.ionice(psutil.IOPRIO_LOW)
priorities_after: tuple = (self_process.nice(), self_process.ionice())
self.log.debug(f"Set process priorities from {priorities_before} to {priorities_after}")
self.has_set_process_priority = True
Expand Down Expand Up @@ -478,4 +479,7 @@ def find_tf2_exe(self, *args, **kwargs) -> str:


if __name__ == '__main__':
launch()
try:
launch()
except KeyboardInterrupt:
pass
32 changes: 18 additions & 14 deletions TF2 Rich Presence/processes.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, log: logger.Log):
self.used_tasklist: bool = False
self.tf2_without_condebug: bool = False
self.parsed_tasklist: Dict[str, int] = {}
self.executables: Dict[str, list] = {'posix': ['hl2_linux', 'steam', 'Discord'],
self.executables: Dict[str, list] = {'posix': ['hl2.sh', 'steam', 'Discord'],
'nt': ['hl2.exe', 'steam.exe', 'discord'],
'order': ['TF2', 'Steam', 'Discord']}
self.process_data: Dict[str, dict] = {'TF2': {'running': False, 'pid': None, 'path': None, 'time': None},
Expand Down Expand Up @@ -65,9 +65,9 @@ def scan_windows(self):
if len(self.parsed_tasklist) == 3:
self.all_pids_cached = True

self.process_data['TF2']['pid'] = self.parsed_tasklist['hl2.exe'] if 'hl2.exe' in self.parsed_tasklist else None
self.process_data['Steam']['pid'] = self.parsed_tasklist['steam.exe'] if 'steam.exe' in self.parsed_tasklist else None
self.process_data['Discord']['pid'] = self.parsed_tasklist['discord'] if 'discord' in self.parsed_tasklist else None
self.process_data['TF2']['pid'] = self.parsed_tasklist[self.executables[os.name][0]] if self.executables[os.name][0] in self.parsed_tasklist else None
self.process_data['Steam']['pid'] = self.parsed_tasklist[self.executables[os.name][1]] if self.executables[os.name][1] in self.parsed_tasklist else None
self.process_data['Discord']['pid'] = self.parsed_tasklist[self.executables[os.name][2]] if self.executables[os.name][2] in self.parsed_tasklist else None

self.get_all_extended_info()
else:
Expand All @@ -88,6 +88,7 @@ def scan_posix(self):
except psutil.NoSuchProcess:
pass


self.get_all_extended_info()

# get only the needed info (exe path and process start time) for each, and then apply it to self.p_data
Expand Down Expand Up @@ -124,7 +125,7 @@ def get_process_info(self, process: Union[str, int], return_data: Tuple[str, ...

try:
process: psutil.Process = psutil.Process(pid=pid)
running: bool = [name for name in self.executables[os.name] if name in process.name().lower()] != []
running: bool = [name for name in self.executables[os.name] if name in process.name()] != []
p_info['running'] = running

if not running:
Expand All @@ -138,7 +139,10 @@ def get_process_info(self, process: Union[str, int], return_data: Tuple[str, ...
p_info['path'] = os.path.dirname(process.cwd()) + '/Steam'
else:
cmdline: List[str] = process.cmdline()
p_info['path'] = os.path.dirname(cmdline[0])
try:
p_info['path'] = os.path.dirname(cmdline[0])
except IndexError:
pass
else:
cmdline = process.cmdline()
p_info['path'] = os.path.dirname(cmdline[0])
Expand Down Expand Up @@ -195,29 +199,29 @@ def parse_tasklist(self):
for process_line in processes:
process: list = process_line.split()

for ref_name in ('hl2.exe', 'Steam.exe', 'steam.exe', 'Discord'):
for ref_name in self.executables[os.name]:
if ref_name in process[0]:
try:
self.parsed_tasklist[ref_name.lower()] = int(process[1])
except ValueError:
self.log.error(f"Couldn't parse PID from process {process}")

self.process_data['TF2']['running'] = 'hl2.exe' in self.parsed_tasklist
self.process_data['Steam']['running'] = 'steam.exe' in self.parsed_tasklist
self.process_data['Discord']['running'] = 'discord' in self.parsed_tasklist
self.process_data['TF2']['running'] = self.executables[os.name][0] in self.parsed_tasklist
self.process_data['Steam']['running'] = self.executables[os.name][1] in self.parsed_tasklist
self.process_data['Discord']['running'] = self.executables[os.name][2] in self.parsed_tasklist

# don't detect gmod (or any other program named hl2.exe)
if self.process_data['TF2']['running']:
if not self.hl2_exe_is_tf2(self.parsed_tasklist['hl2.exe']):
self.log.debug(f"Found running non-TF2 hl2.exe with PID {self.parsed_tasklist['hl2.exe']}")
if not self.hl2_exe_is_tf2(self.parsed_tasklist[self.executables[os.name][0]]):
self.log.debug(f"Found running non-TF2 hl2.exe with PID {self.parsed_tasklist[self.executables[os.name][0]]}")
self.process_data['TF2'] = copy.deepcopy(self.p_data_default['TF2'])
del self.parsed_tasklist['hl2.exe']
del self.parsed_tasklist[self.executables[os.name][0]]

# makes sure a process's path is a TF2 install, not some other game
@functools.cache
def hl2_exe_is_tf2(self, hl2_exe_pid: int) -> bool:
hl2_exe_dir: str = self.get_process_info(hl2_exe_pid, ('path',))['path']
return configs.is_tf2_install(self.log, os.path.join(hl2_exe_dir, 'hl2.exe'))
return configs.is_tf2_install(self.log, os.path.join(hl2_exe_dir, self.executables[os.name][0]))


if __name__ == '__main__':
Expand Down
5 changes: 4 additions & 1 deletion TF2 Rich Presence/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

import functools
import json
import winreg
try:
import winreg
except ImportError:
import unixreg as winreg
from typing import Optional, Union

import logger
Expand Down
8 changes: 6 additions & 2 deletions TF2 Rich Presence/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import os
from typing import Dict, Optional, Union

if os.name == "nt":
APPDATA = os.getenv('APPDATA')
else:
APPDATA = os.path.join(os.getenv("HOME"), ".config")

# read from or write to DB.json (intentionally uncached)
def access_db(write: dict = None, pass_permission_error: bool = True) -> Optional[Dict[str, Union[bool, list, str]]]:
Expand Down Expand Up @@ -50,8 +54,8 @@ def access_db(write: dict = None, pass_permission_error: bool = True) -> Optiona

@functools.cache
def db_json_path() -> str:
if os.path.isdir(os.path.join(os.getenv('APPDATA'), 'TF2 Rich Presence')):
return os.path.join(os.getenv('APPDATA'), 'TF2 Rich Presence', 'DB.json')
if os.path.isdir(os.path.join(APPDATA, 'TF2 Rich Presence')):
return os.path.join(APPDATA, 'TF2 Rich Presence', 'DB.json')
else:
return 'DB.json'

Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ requests-futures==1.0.0
requests==2.26.0
sentry_sdk==1.4.3
urllib3==1.26.7
vdf==3.4
vdf==3.4
unixreg; sys_platform != 'win32'

0 comments on commit e98af8b

Please sign in to comment.