Advertisement
Guest User

GeForceNOW_Setup_Helper

a guest
Aug 8th, 2024
583
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.53 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. """
  4. SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
  5. SPDX-License-Identifier: MIT
  6.  
  7. Permission is hereby granted, free of charge, to any person obtaining a
  8. copy of this software and associated documentation files (the "Software"),
  9. to deal in the Software without restriction, including without limitation
  10. the rights to use, copy, modify, merge, publish, distribute, sublicense,
  11. and/or sell copies of the Software, and to permit persons to whom the
  12. Software is furnished to do so, subject to the following conditions:
  13.  
  14. The above copyright notice and this permission notice shall be included in
  15. all copies or substantial portions of the Software.
  16.  
  17. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  20. THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  23. DEALINGS IN THE SOFTWARE.
  24.  
  25. Trademark Addendum to the License for Use of NVIDIA Software
  26.  
  27. This trademark addendum to the license (“License”) is a legal agreement between
  28. you, whether an individual or entity, (“you”) and NVIDIA Corporation (“NVIDIA”)
  29. and governs the use of the NVIDIA logos and icons provided (“Trademarks”).
  30. Subject to the terms of this Addendum and the License,
  31. NVIDIA grants you a non-exclusive, non-transferable, limited license to use the
  32. Trademarks with the NVIDIA software and in accordance with NVIDIA’s trademark
  33. guidelines (Logos & Brand Guidelines | NVIDIA -
  34. https://www.nvidia.com/en-us/about-nvidia/legal-info/logo-brand-usage/).
  35. You will not: (a) materially modify or alter the Trademarks in any way;
  36. (b) use the Trademarks in such proximity to any of your own trademarks or
  37. third party trademarks so as to create a combination or
  38. composite mark; (c) or display the Trademarks in any way that implies that
  39. other goods or services are provided by NVIDIA or with NVIDIA’s supervision.
  40. NVIDIA retains all right, title, and interest in and to the Trademarks and
  41. that use shall inure to the benefit of NVIDIA.
  42. NVIDIA may terminate the license granted above immediately upon a material breach
  43. of the terms of this Addendum or the License.
  44. THE TRADEMARKS ARE LICENSED “AS IS” AND NVIDIA DISCLAIMS ALL WARRANTIES AND
  45. REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING,
  46. WITHOUT LIMITATION, THE WARRANTIES OF TITLE, NONINFRINGEMENT, MERCHANTABILITY,
  47. FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND COURSE OF DEALING.
  48. """
  49.  
  50. import os
  51. import sys
  52. import platform
  53. import stat
  54. import re
  55. import shutil
  56. import logging
  57. import time
  58. import subprocess
  59.  
  60. ##########################################################################################
  61. # App Name and Version
  62. ##########################################################################################
  63.  
  64. VERSION = '0.0.1.1'
  65. APP_NAME = 'NVIDIA GeForce NOW'
  66.  
  67. ##########################################################################################
  68. # Message Displayed to User
  69. ##########################################################################################
  70.  
  71. MSG_UNSUPPORTED = 'FATAL ERROR: Unsupported system!'
  72. MSG_GENERIC_ERROR = 'There was a problem setting up NVIDIA GeForce NOW on your Steam Deck.'
  73. MSG_CHROME_NOT_FOUND = 'FATAL ERROR: Google Chrome not found!'
  74. MSG_ADD_LIB_FAILED = 'FATAL ERROR: Add to Steam Library failed!'
  75. MSG_CHROME_INSTALL = 'Google Chrome not found. Do you want to install?'
  76. MSG_SETUP_COMPLETE_CONSOLE = 'NVIDIA GeForce NOW setup finished!'
  77. MSG_SETUP_COMPLETE = """NVIDIA GeForce NOW is set up and ready to go! To start playing:
  78.  
  79. 1. Return to Gaming Mode
  80. 2. Navigate to \'Non-Steam\' games in Steam library
  81. 3. Launch GeForce NOW
  82. """
  83. MSG_USR_CONSENT = """This script sets up NVIDIA GeForce NOW on your Steam Deck by:
  84.  
  85. ◦ Installing Google Chrome (browser supporting GeForce NOW), if it\'s not already installed
  86. ◦ Adjusting Google Chrome\'s Flatpak settings to allow for gamepad use
  87. ◦ Adding a GeForce NOW shortcut to Steam
  88.  
  89. Trademark Addendum to the License for Use of NVIDIA Software
  90.  
  91. This trademark addendum to the license ("License") is a legal agreement between you, whether an individual or entity, ("you") and NVIDIA Corporation ("NVIDIA") and governs the use of the NVIDIA logos and icons provided ("Trademarks"). Subject to the terms of this Addendum and the License, NVIDIA grants you a non-exclusive, non-transferable, limited license to use the Trademarks with the NVIDIA software and in accordance with NVIDIA's trademark guidelines (Logos & Brand Guidelines | NVIDIA - https://www.nvidia.com/en-us/about-nvidia/legal-info/logo-brand-usage/). You will not: (a) materially modify or alter the Trademarks in any way; (b) use the Trademarks in such proximity to any of your own trademarks or third party trademarks so as to create a combination or composite mark; (c) or display the Trademarks in any way that implies that other goods or services are provided by NVIDIA or with NVIDIA's supervision. NVIDIA retains all right, title, and interest in and to the Trademarks and that use shall inure to the benefit of NVIDIA. NVIDIA may terminate the license granted above immediately upon a material breach of the terms of this Addendum or the License. THE TRADEMARKS ARE LICENSED "AS IS" AND NVIDIA DISCLAIMS ALL WARRANTIES AND REPRESENTATIONS OF ANY KIND, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE WARRANTIES OF TITLE, NONINFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, USAGE OF TRADE AND COURSE OF DEALING. By clicking yes, you agree to the terms of this Addendum
  92.  
  93. Do you wish to continue?
  94. """
  95.  
  96. ##########################################################################################
  97. # Config Section
  98. ##########################################################################################
  99.  
  100. LOG_FILE = 'GeForceNOW_Setup.log'
  101. log_format = '%(asctime)s - %(levelname)s - %(message)s'
  102. try:
  103. logging.basicConfig(filename=LOG_FILE, level=logging.INFO, format=log_format)
  104. logging.info(f'Version - {VERSION}')
  105. except Exception as e:
  106. logging.error(f'Log config failed. {str(e)}')
  107.  
  108. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  109. EXE = os.path.join(SCRIPT_DIR, APP_NAME)
  110. ICON_DIR = os.path.join(SCRIPT_DIR, 'assets')
  111. USERS_DATA_DIR = os.path.join(os.getenv('HOME'), '.local/share/Steam/userdata')
  112. CONTROLLER_CONFIG_DIR = os.path.join(
  113. os.getenv('HOME'), '.local/share/Steam/steamapps/common/Steam Controller Configs')
  114. STEAM_CMD = 'steamos-add-to-steam'
  115.  
  116. GAMEPAD_LAYOUT_FILE_CONTENT = '''"controller_config"
  117. {
  118. "nvidia geforce now"
  119. {
  120. "template" "controller_neptune_gamepad+mouse.vdf"
  121. }
  122. }
  123. '''
  124. ADDITIONAL_GAMEPAD_LAYOUT_FILE_CONTENT = ''' "nvidia geforce now"
  125. {
  126. "template" "controller_neptune_gamepad+mouse.vdf"
  127. }
  128. }
  129. '''
  130.  
  131. ##########################################################################################
  132. # Script
  133. ##########################################################################################
  134.  
  135.  
  136. def yesno_kdialog(msg=MSG_CHROME_INSTALL):
  137. try:
  138. result = subprocess.run(['kdialog', '--title', APP_NAME, '--yesno',
  139. msg], capture_output=True, text=True, check=True)
  140. if result.returncode == 0:
  141. return True
  142. except Exception as e:
  143. logging.error(f'Kdialog failed. {str(e)}')
  144. return False
  145.  
  146.  
  147. def find_chrome():
  148. cmd = shutil.which('com.google.Chrome')
  149. if (not cmd) or ('/flatpak/' not in cmd):
  150. logging.info('Google Chrome not found. Installing...')
  151. print('Installing Chrome\n')
  152. if os.system('flatpak install -y flathub com.google.Chrome'):
  153. logging.error('Google Chrome installation failed.')
  154. return False
  155. else:
  156. logging.info('Google Chrome installation complete.')
  157. return True
  158. return True
  159.  
  160.  
  161. def sys_compatibility_check():
  162. return shutil.which(STEAM_CMD) and os.path.exists(USERS_DATA_DIR) and os.path.exists(CONTROLLER_CONFIG_DIR)
  163.  
  164.  
  165. def get_users():
  166. return [item for item in os.listdir(USERS_DATA_DIR) if os.path.isdir(os.path.join(USERS_DATA_DIR, item))]
  167.  
  168.  
  169. def add_exec_permission(file_path):
  170. try:
  171. st = os.stat(file_path)
  172. os.chmod(file_path, st.st_mode | stat.S_IEXEC)
  173. except Exception as e:
  174. logging.error(f'Add executable permission failed. {str(e)}')
  175.  
  176.  
  177. def get_file_mod_time(file_path):
  178. try:
  179. return os.path.getmtime(file_path)
  180. except FileNotFoundError:
  181. logging.error(f'File mod tine not found - {file_path}')
  182. return None
  183.  
  184.  
  185. class Login_watcher:
  186. def __init__(self, trackShortcut = True, maxWait = 3600):
  187. self.shortcut_files = {}
  188. self.file_mod_times = {}
  189. self.users = get_users()
  190. self.maxWait = maxWait
  191.  
  192. for user in self.users:
  193. dir = os.path.join(USERS_DATA_DIR, user, 'config')
  194. file = os.path.join(dir, 'shortcuts.vdf')
  195. if trackShortcut and os.path.exists(file):
  196. self.shortcut_files[file] = user
  197. elif os.path.exists(dir):
  198. self.shortcut_files[dir] = user
  199.  
  200. self.file_mod_times = {file_path: get_file_mod_time(
  201. file_path) for file_path in self.shortcut_files.keys()}
  202.  
  203. logging.info(f'Modify Time - {self.file_mod_times}' )
  204. def watch(self):
  205. count = 0
  206. while True:
  207. for file_path in self.shortcut_files.keys():
  208. current_mod_time = get_file_mod_time(file_path)
  209. if current_mod_time is not None and current_mod_time != self.file_mod_times[file_path]:
  210. self.file_mod_times[file_path] = current_mod_time
  211. return self.shortcut_files[file_path]
  212. # Check for new user login
  213. new_users_list = get_users()
  214. if len(new_users_list) != len(self.users):
  215. return list(set(new_users_list) - set(self.users))[0]
  216.  
  217. count += 1
  218. if count > self.maxWait:
  219. return ''
  220.  
  221. time.sleep(1)
  222.  
  223.  
  224. def uninstall_app():
  225. logging.info('START - uninstall_app')
  226.  
  227. for user in get_users():
  228. app_id = get_app_id(user)
  229. if app_id:
  230. os.system(f'xdg-open steam://uninstall/{app_id}')
  231. #Remove assets
  232. assets = [
  233. os.path.join(USERS_DATA_DIR, user, 'config',
  234. 'grid', f'{app_id}_hero.png'),
  235. os.path.join(USERS_DATA_DIR, user, 'config',
  236. 'grid', f'{app_id}p.png'),
  237. os.path.join(USERS_DATA_DIR, user, 'config',
  238. 'grid', f'{app_id}.png')
  239. ]
  240. try:
  241. for item in assets:
  242. if os.path.exists(item):
  243. os.remove(item)
  244. except Exception as e:
  245. logging.error(f'Failed to remove file : {str(e)}')
  246.  
  247. logging.info('END - uninstall_app')
  248.  
  249.  
  250. def add_app_to_steam():
  251. logging.info('START - add_app_to_steam')
  252. print('Adding to Steam\n')
  253. # Uninstall App
  254. uninstall_app()
  255.  
  256. # Set gamepad layout for all users
  257. set_gamepad_layout()
  258.  
  259. # Init login watcher
  260. login_watcher = Login_watcher()
  261.  
  262. # Add App to Steam
  263. cmd = f'{STEAM_CMD} -ui "{EXE}" > /dev/null 2>&1 &'
  264. logging.info(f'cmd = {cmd}')
  265. if os.system(cmd):
  266. logging.error('Add to Steam failed.')
  267. return False
  268.  
  269. # Watch for user login
  270. assets_changed_for_user = login_watcher.watch()
  271.  
  272. # Update Assets
  273. update_app_assets(assets_changed_for_user)
  274.  
  275. logging.info('END - add_app_to_steam')
  276.  
  277. return True
  278.  
  279.  
  280. def set_gamepad_layout():
  281. logging.info('START - set_gamepad_layout')
  282.  
  283. for user in get_users():
  284. string_to_insert = ADDITIONAL_GAMEPAD_LAYOUT_FILE_CONTENT
  285. dir = os.path.join(CONTROLLER_CONFIG_DIR, user, 'config')
  286. file = os.path.join(dir, 'configset_controller_neptune.vdf')
  287. try:
  288. logging.info(f'Processing : {file}')
  289. if not os.path.exists(dir):
  290. logging.info(
  291. f'Skipping setting up GamePad Layout, config dir not found : {dir}')
  292. continue
  293. elif not os.path.exists(file):
  294. with open(file, 'w') as fp:
  295. fp.write(GAMEPAD_LAYOUT_FILE_CONTENT)
  296. else:
  297. with open(file, 'r+') as fp:
  298. content = fp.read()
  299. if APP_NAME.lower() in content:
  300. logging.info(f'{APP_NAME} shortcut found in : {file}')
  301. else:
  302. position_to_insert = content.rfind('}')
  303. fp.seek(position_to_insert)
  304. fp.write(string_to_insert)
  305. except Exception as e:
  306. logging.error(f'Failed to GamePad Layout : {str(e)}')
  307.  
  308. logging.info('END - set_gamepad_layout')
  309.  
  310.  
  311. def get_app_id(user):
  312. app_id = 0
  313. file = os.path.join(USERS_DATA_DIR, user, 'config', 'shortcuts.vdf')
  314. if os.path.exists(file):
  315. try:
  316. with open(file, 'r', encoding='iso-8859-1') as fp:
  317. content = fp.read()
  318. words = re.split('\x00|\x01|\x02', content)
  319. for i in range(len(words) - 2):
  320. if words[i] == 'AppName' and words[i + 1] == 'NVIDIA GeForce NOW':
  321. target_word = words[i - 1]
  322. hex_string = ''.join(['{:02x}'.format(ord(item))
  323. for item in target_word[::-1]])
  324. if (hex_string):
  325. app_id = int(hex_string, 16)
  326. except Exception as e:
  327. logging.error(f'get_app_id failed for {file} - ERROR: {str(e)}')
  328. return app_id
  329.  
  330.  
  331. def update_library_icon(user):
  332. logging.info('START - update_library_icon')
  333.  
  334. file = os.path.join(USERS_DATA_DIR, user, 'config', 'shortcuts.vdf')
  335. if os.path.exists(file):
  336. try:
  337. with open(file, 'r+', encoding='iso-8859-1') as fp:
  338. icon_file = os.path.join(ICON_DIR, 'GFN-Logo.png')
  339. content = fp.read()
  340. pos1 = content.find(APP_NAME)
  341. pos2 = content.find("icon", pos1)
  342. if pos2 > 0:
  343. position_to_insert = pos2 + 5
  344. fp.seek(position_to_insert)
  345. fp.write(icon_file)
  346. fp.write(content[position_to_insert:])
  347. else:
  348. logging.error(f'App shortcut not found!')
  349. except Exception as e:
  350. logging.error(f'Library Icon not updated. {str(e)}')
  351. logging.info('END - update_library_icon')
  352.  
  353.  
  354.  
  355. def restart_steam():
  356. logging.info('START - restart_steam')
  357.  
  358. if not os.system('steam -shutdown > /dev/null 2>&1'):
  359. time.sleep(10)
  360. logging.info('Launch Steam again')
  361. login_watcher = Login_watcher(False, 15)
  362. os.system('nohup steam > /dev/null 2>&1 &')
  363. login_watcher.watch()
  364. logging.info('User logged in Steam')
  365. else:
  366. logging.error(f'Steam Shutdown failed')
  367.  
  368. logging.info('END - restart_steam')
  369.  
  370.  
  371. def update_app_assets(user):
  372. logging.info('START - update_app_assets')
  373.  
  374. time.sleep(2)
  375. update_library_icon(user)
  376.  
  377. app_id = get_app_id(user)
  378. if app_id:
  379. logging.info(f'Updating assets for AppID - {app_id}')
  380.  
  381. dst_dir = os.path.join(USERS_DATA_DIR, user, 'config', 'grid')
  382. if not os.path.exists(dst_dir):
  383. logging.info(f'App assets dir - {dst_dir} not found.')
  384. try:
  385. os.mkdir(dst_dir)
  386. except Exception as e:
  387. logging.error(f'mkdir failed for - {dst_dir}, ERROR: {str(e)}')
  388. logging.error(f'App assets not updated.')
  389. return
  390.  
  391. src = os.path.join(ICON_DIR, 'GFN-Hero.png')
  392. dst = os.path.join(dst_dir, f'{app_id}_hero.png')
  393. try:
  394. shutil.copyfile(src, dst)
  395. except Exception as e:
  396. logging.error(f'App assets not updated. {str(e)}')
  397.  
  398. src = os.path.join(ICON_DIR, 'GFN-Tile.png')
  399. dst = os.path.join(dst_dir, f'{app_id}p.png')
  400. try:
  401. shutil.copyfile(src, dst)
  402. except Exception as e:
  403. logging.error(f'App assets not updated. {str(e)}')
  404.  
  405. src = os.path.join(ICON_DIR, 'GFN-Recent-Tile.png')
  406. dst = os.path.join(dst_dir, f'{app_id}.png')
  407. try:
  408. shutil.copyfile(src, dst)
  409. except Exception as e:
  410. logging.error(f'App assets not updated. {str(e)}')
  411.  
  412. src = os.path.join(ICON_DIR, 'GFN-Hero-logo.png')
  413. dst = os.path.join(dst_dir, f'{app_id}_logo.png')
  414. try:
  415. shutil.copyfile(src, dst)
  416. except Exception as e:
  417. logging.error(f'App assets not updated. {str(e)}')
  418. else:
  419. logging.error(f'AppID - {app_id} not found.')
  420. print('Restarting Steam\n')
  421. restart_steam()
  422.  
  423. logging.info('END - update_app_assets')
  424.  
  425. if __name__ == "__main__":
  426. print('***************************')
  427. print('Running GeForceNOW Setup...')
  428. r_code = 0
  429. try:
  430. if not (yesno_kdialog(MSG_USR_CONSENT)):
  431. logging.info('No consent, exiting.')
  432. sys.exit(0)
  433.  
  434. add_exec_permission(EXE)
  435. if not sys_compatibility_check():
  436. r_code = 1
  437. logging.error('FATAL ERROR: System compatibility check failed!')
  438. os.system(
  439. f'kdialog --title "{APP_NAME}" --error "{MSG_GENERIC_ERROR}"')
  440. elif not find_chrome():
  441. r_code = 1
  442. logging.error('FATAL ERROR: Chrome check failed!')
  443. os.system(
  444. f'kdialog --title "{APP_NAME}" --error "{MSG_GENERIC_ERROR}"')
  445. elif not add_app_to_steam():
  446. r_code = 1
  447. logging.error('FATAL ERROR: Add App to Steam failed!')
  448. os.system(
  449. f'kdialog --title "{APP_NAME}" --error "{MSG_GENERIC_ERROR}"')
  450. else:
  451. time.sleep(5)
  452. print(f'{MSG_SETUP_COMPLETE_CONSOLE}')
  453. os.system(
  454. f'kdialog --title "{APP_NAME}" --msgbox "{MSG_SETUP_COMPLETE}"')
  455. except Exception as e:
  456. r_code = 1
  457. logging.error(f'FATAL ERROR - {str(e)}')
  458.  
  459. sys.exit(r_code)
  460.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement