├── Scripts ├── __init__.py ├── run.py ├── utils.py └── ioreg.py ├── README.md ├── LICENSE ├── .gitignore ├── CheckGPU.py └── CheckGPU.command /Scripts/__init__.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname, basename, isfile 2 | import glob 3 | modules = glob.glob(dirname(__file__)+"/*.py") 4 | __all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CheckGPU 2 | Small py script to offer some debugging info on GPUs. 3 | 4 | *** 5 | 6 | ## To install: 7 | 8 | Do the following one line at a time in Terminal: 9 | 10 | git clone https://github.com/corpnewt/CheckGPU 11 | cd CheckGPU 12 | chmod +x CheckGPU.command 13 | 14 | Then run with either `./CheckGPU.command` or by double-clicking *CheckGPU.command* 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 CorpNewt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Scripts/run.py: -------------------------------------------------------------------------------- 1 | import sys, subprocess, time, threading, shlex 2 | try: 3 | from Queue import Queue, Empty 4 | except: 5 | from queue import Queue, Empty 6 | 7 | ON_POSIX = 'posix' in sys.builtin_module_names 8 | 9 | class Run: 10 | 11 | def __init__(self): 12 | return 13 | 14 | def _read_output(self, pipe, q): 15 | try: 16 | for line in iter(lambda: pipe.read(1), b''): 17 | q.put(line) 18 | except ValueError: 19 | pass 20 | pipe.close() 21 | 22 | def _create_thread(self, output): 23 | # Creates a new queue and thread object to watch based on the output pipe sent 24 | q = Queue() 25 | t = threading.Thread(target=self._read_output, args=(output, q)) 26 | t.daemon = True 27 | return (q,t) 28 | 29 | def _stream_output(self, comm, shell = False): 30 | output = error = "" 31 | p = None 32 | try: 33 | if shell and type(comm) is list: 34 | comm = " ".join(shlex.quote(x) for x in comm) 35 | if not shell and type(comm) is str: 36 | comm = shlex.split(comm) 37 | p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, universal_newlines=True, close_fds=ON_POSIX) 38 | # Setup the stdout thread/queue 39 | q,t = self._create_thread(p.stdout) 40 | qe,te = self._create_thread(p.stderr) 41 | # Start both threads 42 | t.start() 43 | te.start() 44 | 45 | while True: 46 | c = z = "" 47 | try: c = q.get_nowait() 48 | except Empty: pass 49 | else: 50 | sys.stdout.write(c) 51 | output += c 52 | sys.stdout.flush() 53 | try: z = qe.get_nowait() 54 | except Empty: pass 55 | else: 56 | sys.stderr.write(z) 57 | error += z 58 | sys.stderr.flush() 59 | if not c==z=="": continue # Keep going until empty 60 | # No output - see if still running 61 | p.poll() 62 | if p.returncode != None: 63 | # Subprocess ended 64 | break 65 | # No output, but subprocess still running - stall for 20ms 66 | time.sleep(0.02) 67 | 68 | o, e = p.communicate() 69 | return (output+o, error+e, p.returncode) 70 | except: 71 | if p: 72 | try: o, e = p.communicate() 73 | except: o = e = "" 74 | return (output+o, error+e, p.returncode) 75 | return ("", "Command not found!", 1) 76 | 77 | def _decode(self, value, encoding="utf-8", errors="ignore"): 78 | # Helper method to only decode if bytes type 79 | if sys.version_info >= (3,0) and isinstance(value, bytes): 80 | return value.decode(encoding,errors) 81 | return value 82 | 83 | def _run_command(self, comm, shell = False): 84 | c = None 85 | try: 86 | if shell and type(comm) is list: 87 | comm = " ".join(shlex.quote(x) for x in comm) 88 | if not shell and type(comm) is str: 89 | comm = shlex.split(comm) 90 | p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 91 | c = p.communicate() 92 | except: 93 | if c == None: 94 | return ("", "Command not found!", 1) 95 | return (self._decode(c[0]), self._decode(c[1]), p.returncode) 96 | 97 | def run(self, command_list, leave_on_fail = False): 98 | # Command list should be an array of dicts 99 | if type(command_list) is dict: 100 | # We only have one command 101 | command_list = [command_list] 102 | output_list = [] 103 | for comm in command_list: 104 | args = comm.get("args", []) 105 | shell = comm.get("shell", False) 106 | stream = comm.get("stream", False) 107 | sudo = comm.get("sudo", False) 108 | stdout = comm.get("stdout", False) 109 | stderr = comm.get("stderr", False) 110 | mess = comm.get("message", None) 111 | show = comm.get("show", False) 112 | 113 | if not mess == None: 114 | print(mess) 115 | 116 | if not len(args): 117 | # nothing to process 118 | continue 119 | if sudo: 120 | # Check if we have sudo 121 | out = self._run_command(["which", "sudo"]) 122 | if "sudo" in out[0]: 123 | # Can sudo 124 | if type(args) is list: 125 | args.insert(0, out[0].replace("\n", "")) # add to start of list 126 | elif type(args) is str: 127 | args = out[0].replace("\n", "") + " " + args # add to start of string 128 | 129 | if show: 130 | print(" ".join(args)) 131 | 132 | if stream: 133 | # Stream it! 134 | out = self._stream_output(args, shell) 135 | else: 136 | # Just run and gather output 137 | out = self._run_command(args, shell) 138 | if stdout and len(out[0]): 139 | print(out[0]) 140 | if stderr and len(out[1]): 141 | print(out[1]) 142 | # Append output 143 | output_list.append(out) 144 | # Check for errors 145 | if leave_on_fail and out[2] != 0: 146 | # Got an error - leave 147 | break 148 | if len(output_list) == 1: 149 | # We only ran one command - just return that output 150 | return output_list[0] 151 | return output_list 152 | -------------------------------------------------------------------------------- /CheckGPU.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os, sys, binascii 3 | from Scripts import utils, ioreg, run 4 | 5 | class CheckGPU: 6 | def __init__(self): 7 | self.u = utils.Utils("CheckGPU") 8 | # Verify running OS 9 | if not sys.platform.lower() == "darwin": 10 | self.u.head("Wrong OS!") 11 | print("") 12 | print("This script can only be run on macOS!") 13 | print("") 14 | self.u.grab("Press [enter] to exit...") 15 | exit(1) 16 | self.i = ioreg.IOReg() 17 | self.r = run.Run() 18 | self.kextstat = None 19 | self.log = "" 20 | self.conn_types = { 21 | "<00080000>":"HDMI", 22 | "<00040000>":"DisplayPort", 23 | "<04000000>":"DVI", 24 | "<02000000>":"LVDS", 25 | "<01000000>":"Dummy Port" 26 | } 27 | self.ioreg = None 28 | 29 | def get_kextstat(self, force = False): 30 | # Gets the kextstat list if needed 31 | if not self.kextstat or force: 32 | self.kextstat = self.r.run({"args":"kextstat"})[0] 33 | return self.kextstat 34 | 35 | def get_boot_args(self): 36 | # Attempts to pull the boot-args from nvram 37 | out = self.r.run({"args":["nvram","-p"]}) 38 | for l in out[0].split("\n"): 39 | if "boot-args" in l: 40 | return "\t".join(l.split("\t")[1:]) 41 | return None 42 | 43 | def get_os_version(self): 44 | # Scrape sw_vers 45 | prod_name = self.r.run({"args":["sw_vers","-productName"]})[0].strip() 46 | prod_vers = self.r.run({"args":["sw_vers","-productVersion"]})[0].strip() 47 | build_vers = self.r.run({"args":["sw_vers","-buildVersion"]})[0].strip() 48 | if build_vers: build_vers = "({})".format(build_vers) 49 | return " ".join([x for x in (prod_name,prod_vers,build_vers) if x]) 50 | 51 | def locate(self, kext): 52 | # Gathers the kextstat list - then parses for loaded kexts 53 | ks = self.get_kextstat() 54 | # Verifies that our name ends with a space 55 | if not kext[-1] == " ": 56 | kext += " " 57 | for x in ks.split("\n")[1:]: 58 | if kext.lower() in x.lower(): 59 | # We got the kext - return the version 60 | try: 61 | v = x.split("(")[1].split(")")[0] 62 | except: 63 | return "?.?" 64 | return v 65 | return None 66 | 67 | def lprint(self, message): 68 | print(message) 69 | self.log += message + "\n" 70 | 71 | def main(self): 72 | self.u.head() 73 | self.lprint("") 74 | self.lprint("Checking kexts:") 75 | self.lprint("") 76 | self.lprint("Locating Lilu...") 77 | lilu_vers = self.locate("Lilu") 78 | if not lilu_vers: 79 | self.lprint(" - Not loaded! AppleALC and WhateverGreen need this!") 80 | else: 81 | self.lprint(" - Found v{}".format(lilu_vers)) 82 | self.lprint("Checking for Lilu plugins...") 83 | self.lprint(" - Locating AppleALC...") 84 | alc_vers = self.locate("AppleALC") 85 | if not alc_vers: 86 | self.lprint(" --> Not loaded! Onboard and HDMI/DP audio may not work!") 87 | else: 88 | self.lprint(" --> Found v{}".format(alc_vers)) 89 | self.lprint(" - Locating WhateverGreen...") 90 | weg_vers = self.locate("WhateverGreen") 91 | if not weg_vers: 92 | self.lprint(" --> Not loaded! GFX and audio may not work!") 93 | else: 94 | self.lprint(" --> Found v{}".format(weg_vers)) 95 | self.lprint("") 96 | os_vers = self.get_os_version() 97 | self.lprint("Current OS Version: {}".format(os_vers or "Unknown!")) 98 | self.lprint("") 99 | boot_args = self.get_boot_args() 100 | self.lprint("Current boot-args: {}".format(boot_args or "None set!")) 101 | self.lprint("") 102 | self.lprint("Locating GPU devices...") 103 | all_devs = self.i.get_all_devices(plane="IOService") 104 | self.lprint("") 105 | self.lprint("Iterating for devices with matching class-code...") 106 | gpus = [x for x in all_devs.values() if x.get("info",{}).get("class-code","").endswith("0300>")] 107 | if not len(gpus): 108 | self.lprint(" - None found!") 109 | self.lprint("") 110 | else: 111 | self.lprint(" - Located {}".format(len(gpus))) 112 | self.lprint("") 113 | self.lprint("Iterating GPU devices:") 114 | self.lprint("") 115 | gather = ( 116 | "AAPL,ig-platform-id", 117 | "AAPL,snb-platform-id", 118 | "built-in", 119 | "device-id", 120 | "vendor-id", 121 | "hda-gfx", 122 | "model", 123 | "NVDAType", 124 | "NVArch", 125 | "AAPL,slot-name", 126 | "acpi-path" 127 | ) 128 | start = "framebuffer-" 129 | fb_checks = (" AppleIntelFramebuffer@", " NVDA,Display-", "class AtiFbStub") 130 | for g in sorted(gpus, key=lambda x:x.get("device_path","?")): 131 | g_dict = g.get("info",{}) 132 | pcidebug_check = g_dict.get("pcidebug","").replace("??:??.?","") 133 | loc = g.get("device_path") 134 | self.lprint(" - {} - {}".format(g["name"], loc or "Could Not Resolve Device Path")) 135 | for x in sorted(g_dict): 136 | if x in gather or x.startswith(start): 137 | val = g_dict[x] 138 | # Strip formatting from ioreg 139 | if x in ("device-id","vendor-id"): 140 | try: 141 | val = "0x"+binascii.hexlify(binascii.unhexlify(val[1:5])[::-1]).decode().upper() 142 | except: 143 | pass 144 | elif val.startswith('<"') and val.endswith('">'): 145 | try: 146 | val = val[2:-2] 147 | except: 148 | pass 149 | elif val.startswith("<") and val.endswith(">"): 150 | try: 151 | val = "0x"+binascii.hexlify(binascii.unhexlify(val[1:-1])[::-1]).decode().upper() 152 | except: 153 | pass 154 | elif val[0] == val[-1] == '"': 155 | try: 156 | val = val[1:-1] 157 | except: 158 | pass 159 | self.lprint(" --> {}: {}".format(x,val)) 160 | self.lprint("") 161 | self.lprint("Connectors:") 162 | self.lprint("") 163 | # Check for any framebuffers or connected displays here 164 | name_check = g["line"] # Use the line to prevent mismatching 165 | primed = False 166 | last_fb = None 167 | fb_list = [] 168 | connected = "Connected to Display" 169 | for line in self.i.get_ioreg(): 170 | if name_check in line: 171 | primed = len(line.split("+-o ")[0]) 172 | continue 173 | if primed is False: 174 | continue 175 | # Make sure se have the right device 176 | # by verifying the pcidebug value 177 | if "pcidebug" in line and not pcidebug_check in line: 178 | # Unprime - wrong device 179 | primed = False 180 | continue 181 | # We're primed check for any framebuffers 182 | # or if we left our scope 183 | if "+-o " in line and len(line.split("+-o ")[0]) <= primed: 184 | break 185 | if any(f in line for f in fb_checks): 186 | # Got a framebuffer - list it 187 | fb_list.append(" - "+line.split("+-o ")[1].split(" connector-type: {}".format( 192 | self.conn_types.get(conn,"Unknown Connector ({})".format(conn)) 193 | )) 194 | if any(c in line for c in (" ", 198 | # otherwise just use " --> " 199 | prefix = " --> " if fb_list[-1].startswith(" - ") else " ----> " 200 | fb_list.append(prefix+connected) 201 | if fb_list: 202 | self.lprint("\n".join(fb_list)) 203 | else: 204 | self.lprint(" - None found!") 205 | self.lprint("") 206 | 207 | print("Saving log...") 208 | print("") 209 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 210 | with open("GPU.log","w") as f: 211 | f.write(self.log) 212 | print("Done.") 213 | print("") 214 | 215 | 216 | if __name__ == '__main__': 217 | # os.chdir(os.path.dirname(os.path.realpath(__file__))) 218 | a = CheckGPU() 219 | a.main() 220 | -------------------------------------------------------------------------------- /Scripts/utils.py: -------------------------------------------------------------------------------- 1 | import sys, os, time, re, json, datetime, ctypes, subprocess 2 | 3 | if os.name == "nt": 4 | # Windows 5 | import msvcrt 6 | else: 7 | # Not Windows \o/ 8 | import select 9 | 10 | class Utils: 11 | 12 | def __init__(self, name = "Python Script"): 13 | self.name = name 14 | # Init our colors before we need to print anything 15 | cwd = os.getcwd() 16 | os.chdir(os.path.dirname(os.path.realpath(__file__))) 17 | if os.path.exists("colors.json"): 18 | self.colors_dict = json.load(open("colors.json")) 19 | else: 20 | self.colors_dict = {} 21 | os.chdir(cwd) 22 | 23 | def check_admin(self): 24 | # Returns whether or not we're admin 25 | try: 26 | is_admin = os.getuid() == 0 27 | except AttributeError: 28 | is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 29 | return is_admin 30 | 31 | def elevate(self, file): 32 | # Runs the passed file as admin 33 | if self.check_admin(): 34 | return 35 | if os.name == "nt": 36 | ctypes.windll.shell32.ShellExecuteW(None, "runas", '"{}"'.format(sys.executable), '"{}"'.format(file), None, 1) 37 | else: 38 | try: 39 | p = subprocess.Popen(["which", "sudo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 40 | c = p.communicate()[0].decode("utf-8", "ignore").replace("\n", "") 41 | os.execv(c, [ sys.executable, 'python'] + sys.argv) 42 | except: 43 | exit(1) 44 | 45 | def compare_versions(self, vers1, vers2, **kwargs): 46 | # Helper method to compare ##.## strings 47 | # 48 | # vers1 < vers2 = True 49 | # vers1 = vers2 = None 50 | # vers1 > vers2 = False 51 | 52 | # Sanitize the pads 53 | pad = str(kwargs.get("pad", "")) 54 | sep = str(kwargs.get("separator", ".")) 55 | 56 | ignore_case = kwargs.get("ignore_case", True) 57 | 58 | # Cast as strings 59 | vers1 = str(vers1) 60 | vers2 = str(vers2) 61 | 62 | if ignore_case: 63 | vers1 = vers1.lower() 64 | vers2 = vers2.lower() 65 | 66 | # Split and pad lists 67 | v1_parts, v2_parts = self.pad_length(vers1.split(sep), vers2.split(sep)) 68 | 69 | # Iterate and compare 70 | for i in range(len(v1_parts)): 71 | # Remove non-numeric 72 | v1 = ''.join(c.lower() for c in v1_parts[i] if c.isalnum()) 73 | v2 = ''.join(c.lower() for c in v2_parts[i] if c.isalnum()) 74 | # Equalize the lengths 75 | v1, v2 = self.pad_length(v1, v2) 76 | # Compare 77 | if str(v1) < str(v2): 78 | return True 79 | elif str(v1) > str(v2): 80 | return False 81 | # Never differed - return None, must be equal 82 | return None 83 | 84 | def pad_length(self, var1, var2, pad = "0"): 85 | # Pads the vars on the left side to make them equal length 86 | pad = "0" if len(str(pad)) < 1 else str(pad)[0] 87 | if not type(var1) == type(var2): 88 | # Type mismatch! Just return what we got 89 | return (var1, var2) 90 | if len(var1) < len(var2): 91 | if type(var1) is list: 92 | var1.extend([str(pad) for x in range(len(var2) - len(var1))]) 93 | else: 94 | var1 = "{}{}".format((pad*(len(var2)-len(var1))), var1) 95 | elif len(var2) < len(var1): 96 | if type(var2) is list: 97 | var2.extend([str(pad) for x in range(len(var1) - len(var2))]) 98 | else: 99 | var2 = "{}{}".format((pad*(len(var1)-len(var2))), var2) 100 | return (var1, var2) 101 | 102 | def check_path(self, path): 103 | # Let's loop until we either get a working path, or no changes 104 | test_path = path 105 | last_path = None 106 | while True: 107 | # Bail if we've looped at least once and the path didn't change 108 | if last_path != None and last_path == test_path: return None 109 | last_path = test_path 110 | # Check if we stripped everything out 111 | if not len(test_path): return None 112 | # Check if we have a valid path 113 | if os.path.exists(test_path): 114 | return os.path.abspath(test_path) 115 | # Check for quotes 116 | if test_path[0] == test_path[-1] and test_path[0] in ('"',"'"): 117 | test_path = test_path[1:-1] 118 | continue 119 | # Check for a tilde and expand if needed 120 | if test_path[0] == "~": 121 | tilde_expanded = os.path.expanduser(test_path) 122 | if tilde_expanded != test_path: 123 | # Got a change 124 | test_path = tilde_expanded 125 | continue 126 | # Let's check for spaces - strip from the left first, then the right 127 | if test_path[0] in (" ","\t"): 128 | test_path = test_path[1:] 129 | continue 130 | if test_path[-1] in (" ","\t"): 131 | test_path = test_path[:-1] 132 | continue 133 | # Maybe we have escapes to handle? 134 | test_path = "\\".join([x.replace("\\", "") for x in test_path.split("\\\\")]) 135 | 136 | def grab(self, prompt, **kwargs): 137 | # Takes a prompt, a default, and a timeout and shows it with that timeout 138 | # returning the result 139 | timeout = kwargs.get("timeout", 0) 140 | default = kwargs.get("default", None) 141 | # If we don't have a timeout - then skip the timed sections 142 | if timeout <= 0: 143 | if sys.version_info >= (3, 0): 144 | return input(prompt) 145 | else: 146 | return str(raw_input(prompt)) 147 | # Write our prompt 148 | sys.stdout.write(prompt) 149 | sys.stdout.flush() 150 | if os.name == "nt": 151 | start_time = time.time() 152 | i = '' 153 | while True: 154 | if msvcrt.kbhit(): 155 | c = msvcrt.getche() 156 | if ord(c) == 13: # enter_key 157 | break 158 | elif ord(c) >= 32: #space_char 159 | i += c 160 | if len(i) == 0 and (time.time() - start_time) > timeout: 161 | break 162 | else: 163 | i, o, e = select.select( [sys.stdin], [], [], timeout ) 164 | if i: 165 | i = sys.stdin.readline().strip() 166 | print('') # needed to move to next line 167 | if len(i) > 0: 168 | return i 169 | else: 170 | return default 171 | 172 | def cls(self): 173 | os.system('cls' if os.name=='nt' else 'clear') 174 | 175 | def cprint(self, message, **kwargs): 176 | strip_colors = kwargs.get("strip_colors", False) 177 | if os.name == "nt": 178 | strip_colors = True 179 | reset = u"\u001b[0m" 180 | # Requires sys import 181 | for c in self.colors: 182 | if strip_colors: 183 | message = message.replace(c["find"], "") 184 | else: 185 | message = message.replace(c["find"], c["replace"]) 186 | if strip_colors: 187 | return message 188 | sys.stdout.write(message) 189 | print(reset) 190 | 191 | # Needs work to resize the string if color chars exist 192 | '''# Header drawing method 193 | def head(self, text = None, width = 55): 194 | if text == None: 195 | text = self.name 196 | self.cls() 197 | print(" {}".format("#"*width)) 198 | len_text = self.cprint(text, strip_colors=True) 199 | mid_len = int(round(width/2-len(len_text)/2)-2) 200 | middle = " #{}{}{}#".format(" "*mid_len, len_text, " "*((width - mid_len - len(len_text))-2)) 201 | if len(middle) > width+1: 202 | # Get the difference 203 | di = len(middle) - width 204 | # Add the padding for the ...# 205 | di += 3 206 | # Trim the string 207 | middle = middle[:-di] 208 | newlen = len(middle) 209 | middle += "...#" 210 | find_list = [ c["find"] for c in self.colors ] 211 | 212 | # Translate colored string to len 213 | middle = middle.replace(len_text, text + self.rt_color) # always reset just in case 214 | self.cprint(middle) 215 | print("#"*width)''' 216 | 217 | # Header drawing method 218 | def head(self, text = None, width = 55): 219 | if text == None: 220 | text = self.name 221 | self.cls() 222 | print(" {}".format("#"*width)) 223 | mid_len = int(round(width/2-len(text)/2)-2) 224 | middle = " #{}{}{}#".format(" "*mid_len, text, " "*((width - mid_len - len(text))-2)) 225 | if len(middle) > width+1: 226 | # Get the difference 227 | di = len(middle) - width 228 | # Add the padding for the ...# 229 | di += 3 230 | # Trim the string 231 | middle = middle[:-di] + "...#" 232 | print(middle) 233 | print("#"*width) 234 | 235 | def resize(self, width, height): 236 | print('\033[8;{};{}t'.format(height, width)) 237 | 238 | def custom_quit(self): 239 | self.head() 240 | print("by CorpNewt\n") 241 | print("Thanks for testing it out, for bugs/comments/complaints") 242 | print("send me a message on Reddit, or check out my GitHub:\n") 243 | print("www.reddit.com/u/corpnewt") 244 | print("www.github.com/corpnewt\n") 245 | # Get the time and wish them a good morning, afternoon, evening, and night 246 | hr = datetime.datetime.now().time().hour 247 | if hr > 3 and hr < 12: 248 | print("Have a nice morning!\n\n") 249 | elif hr >= 12 and hr < 17: 250 | print("Have a nice afternoon!\n\n") 251 | elif hr >= 17 and hr < 21: 252 | print("Have a nice evening!\n\n") 253 | else: 254 | print("Have a nice night!\n\n") 255 | exit(0) 256 | -------------------------------------------------------------------------------- /CheckGPU.command: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get the curent directory, the script name 4 | # and the script name with "py" substituted for the extension. 5 | args=( "$@" ) 6 | dir="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" 7 | script="${0##*/}" 8 | target="${script%.*}.py" 9 | 10 | # use_py3: 11 | # TRUE = Use if found, use py2 otherwise 12 | # FALSE = Use py2 13 | # FORCE = Use py3 14 | use_py3="TRUE" 15 | 16 | # We'll parse if the first argument passed is 17 | # --install-python and if so, we'll just install 18 | # Can optionally take a version number as the 19 | # second arg - i.e. --install-python 3.13.1 20 | just_installing="FALSE" 21 | 22 | tempdir="" 23 | 24 | compare_to_version () { 25 | # Compares our OS version to the passed OS version, and 26 | # return a 1 if we match the passed compare type, or a 0 if we don't. 27 | # $1 = 0 (equal), 1 (greater), 2 (less), 3 (gequal), 4 (lequal) 28 | # $2 = OS version to compare ours to 29 | if [ -z "$1" ] || [ -z "$2" ]; then 30 | # Missing info - bail. 31 | return 32 | fi 33 | local current_os= comp= 34 | current_os="$(sw_vers -productVersion 2>/dev/null)" 35 | comp="$(vercomp "$current_os" "$2")" 36 | # Check gequal and lequal first 37 | if [[ "$1" == "3" && ("$comp" == "1" || "$comp" == "0") ]] || [[ "$1" == "4" && ("$comp" == "2" || "$comp" == "0") ]] || [[ "$comp" == "$1" ]]; then 38 | # Matched 39 | echo "1" 40 | else 41 | # No match 42 | echo "0" 43 | fi 44 | } 45 | 46 | set_use_py3_if () { 47 | # Auto sets the "use_py3" variable based on 48 | # conditions passed 49 | # $1 = 0 (equal), 1 (greater), 2 (less), 3 (gequal), 4 (lequal) 50 | # $2 = OS version to compare 51 | # $3 = TRUE/FALSE/FORCE in case of match 52 | if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then 53 | # Missing vars - bail with no changes. 54 | return 55 | fi 56 | if [ "$(compare_to_version "$1" "$2")" == "1" ]; then 57 | use_py3="$3" 58 | fi 59 | } 60 | 61 | get_remote_py_version () { 62 | local pyurl= py_html= py_vers= py_num="3" 63 | pyurl="https://www.python.org/downloads/macos/" 64 | py_html="$(curl -L $pyurl --compressed 2>&1)" 65 | if [ -z "$use_py3" ]; then 66 | use_py3="TRUE" 67 | fi 68 | if [ "$use_py3" == "FALSE" ]; then 69 | py_num="2" 70 | fi 71 | py_vers="$(echo "$py_html" | grep -i "Latest Python $py_num Release" | awk '{print $8}' | cut -d'<' -f1)" 72 | echo "$py_vers" 73 | } 74 | 75 | download_py () { 76 | local vers="$1" url= 77 | clear 78 | echo " ### ###" 79 | echo " # Downloading Python #" 80 | echo "### ###" 81 | echo 82 | if [ -z "$vers" ]; then 83 | echo "Gathering latest version..." 84 | vers="$(get_remote_py_version)" 85 | if [ -z "$vers" ]; then 86 | if [ "$just_installing" == "TRUE" ]; then 87 | echo " - Failed to get info!" 88 | exit 1 89 | else 90 | # Didn't get it still - bail 91 | print_error 92 | fi 93 | fi 94 | echo "Located Version: $vers" 95 | else 96 | # Got a version passed 97 | echo "User-Provided Version: $vers" 98 | fi 99 | echo "Building download url..." 100 | url="$(\ 101 | curl -L https://www.python.org/downloads/release/python-${vers//./}/ --compressed 2>&1 | \ 102 | grep -iE "python-$vers-macos.*.pkg\"" | \ 103 | grep -iE "a href=" | \ 104 | awk -F'"' '{ print $2 }' | \ 105 | head -n 1\ 106 | )" 107 | if [ -z "$url" ]; then 108 | if [ "$just_installing" == "TRUE" ]; then 109 | echo " - Failed to build download url!" 110 | exit 1 111 | else 112 | # Couldn't get the URL - bail 113 | print_error 114 | fi 115 | fi 116 | echo " - $url" 117 | echo "Downloading..." 118 | # Create a temp dir and download to it 119 | tempdir="$(mktemp -d 2>/dev/null || mktemp -d -t 'tempdir')" 120 | curl "$url" -o "$tempdir/python.pkg" 121 | if [ "$?" != "0" ]; then 122 | echo " - Failed to download python installer!" 123 | exit $? 124 | fi 125 | echo 126 | echo "Running python install package..." 127 | echo 128 | sudo installer -pkg "$tempdir/python.pkg" -target / 129 | if [ "$?" != "0" ]; then 130 | echo " - Failed to install python!" 131 | exit $? 132 | fi 133 | echo 134 | # Now we expand the package and look for a shell update script 135 | pkgutil --expand "$tempdir/python.pkg" "$tempdir/python" 136 | if [ -e "$tempdir/python/Python_Shell_Profile_Updater.pkg/Scripts/postinstall" ]; then 137 | # Run the script 138 | echo "Updating PATH..." 139 | echo 140 | "$tempdir/python/Python_Shell_Profile_Updater.pkg/Scripts/postinstall" 141 | echo 142 | fi 143 | vers_folder="Python $(echo "$vers" | cut -d'.' -f1 -f2)" 144 | if [ -f "/Applications/$vers_folder/Install Certificates.command" ]; then 145 | # Certs script exists - let's execute that to make sure our certificates are updated 146 | echo "Updating Certificates..." 147 | echo 148 | "/Applications/$vers_folder/Install Certificates.command" 149 | echo 150 | fi 151 | echo "Cleaning up..." 152 | cleanup 153 | if [ "$just_installing" == "TRUE" ]; then 154 | echo 155 | echo "Done." 156 | else 157 | # Now we check for py again 158 | downloaded="TRUE" 159 | clear 160 | main 161 | fi 162 | } 163 | 164 | cleanup () { 165 | if [ -d "$tempdir" ]; then 166 | rm -Rf "$tempdir" 167 | fi 168 | } 169 | 170 | print_error() { 171 | clear 172 | cleanup 173 | echo " ### ###" 174 | echo " # Python Not Found #" 175 | echo "### ###" 176 | echo 177 | echo "Python is not installed or not found in your PATH var." 178 | echo 179 | if [ "$kernel" == "Darwin" ]; then 180 | echo "Please go to https://www.python.org/downloads/macos/ to" 181 | echo "download and install the latest version, then try again." 182 | else 183 | echo "Please install python through your package manager and" 184 | echo "try again." 185 | fi 186 | echo 187 | exit 1 188 | } 189 | 190 | print_target_missing() { 191 | clear 192 | cleanup 193 | echo " ### ###" 194 | echo " # Target Not Found #" 195 | echo "### ###" 196 | echo 197 | echo "Could not locate $target!" 198 | echo 199 | exit 1 200 | } 201 | 202 | format_version () { 203 | local vers="$1" 204 | echo "$(echo "$1" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }')" 205 | } 206 | 207 | vercomp () { 208 | # Modified from: https://apple.stackexchange.com/a/123408/11374 209 | local ver1="$(format_version "$1")" ver2="$(format_version "$2")" 210 | if [ $ver1 -gt $ver2 ]; then 211 | echo "1" 212 | elif [ $ver1 -lt $ver2 ]; then 213 | echo "2" 214 | else 215 | echo "0" 216 | fi 217 | } 218 | 219 | get_local_python_version() { 220 | # $1 = Python bin name (defaults to python3) 221 | # Echoes the path to the highest version of the passed python bin if any 222 | local py_name="$1" max_version= python= python_version= python_path= 223 | if [ -z "$py_name" ]; then 224 | py_name="python3" 225 | fi 226 | py_list="$(which -a "$py_name" 2>/dev/null)" 227 | # Walk that newline separated list 228 | while read python; do 229 | if [ -z "$python" ]; then 230 | # Got a blank line - skip 231 | continue 232 | fi 233 | if [ "$check_py3_stub" == "1" ] && [ "$python" == "/usr/bin/python3" ]; then 234 | # See if we have a valid developer path 235 | xcode-select -p > /dev/null 2>&1 236 | if [ "$?" != "0" ]; then 237 | # /usr/bin/python3 path - but no valid developer dir 238 | continue 239 | fi 240 | fi 241 | python_version="$(get_python_version $python)" 242 | if [ -z "$python_version" ]; then 243 | # Didn't find a py version - skip 244 | continue 245 | fi 246 | # Got the py version - compare to our max 247 | if [ -z "$max_version" ] || [ "$(vercomp "$python_version" "$max_version")" == "1" ]; then 248 | # Max not set, or less than the current - update it 249 | max_version="$python_version" 250 | python_path="$python" 251 | fi 252 | done <<< "$py_list" 253 | echo "$python_path" 254 | } 255 | 256 | get_python_version() { 257 | local py_path="$1" py_version= 258 | # Get the python version by piping stderr into stdout (for py2), then grepping the output for 259 | # the word "python", getting the second element, and grepping for an alphanumeric version number 260 | py_version="$($py_path -V 2>&1 | grep -i python | cut -d' ' -f2 | grep -E "[A-Za-z\d\.]+")" 261 | if [ ! -z "$py_version" ]; then 262 | echo "$py_version" 263 | fi 264 | } 265 | 266 | prompt_and_download() { 267 | if [ "$downloaded" != "FALSE" ] || [ "$kernel" != "Darwin" ]; then 268 | # We already tried to download, or we're not on macOS - just bail 269 | print_error 270 | fi 271 | clear 272 | echo " ### ###" 273 | echo " # Python Not Found #" 274 | echo "### ###" 275 | echo 276 | target_py="Python 3" 277 | printed_py="Python 2 or 3" 278 | if [ "$use_py3" == "FORCE" ]; then 279 | printed_py="Python 3" 280 | elif [ "$use_py3" == "FALSE" ]; then 281 | target_py="Python 2" 282 | printed_py="Python 2" 283 | fi 284 | echo "Could not locate $printed_py!" 285 | echo 286 | echo "This script requires $printed_py to run." 287 | echo 288 | while true; do 289 | read -p "Would you like to install the latest $target_py now? (y/n): " yn 290 | case $yn in 291 | [Yy]* ) download_py;break;; 292 | [Nn]* ) print_error;; 293 | esac 294 | done 295 | } 296 | 297 | main() { 298 | local python= version= 299 | # Verify our target exists 300 | if [ ! -f "$dir/$target" ]; then 301 | # Doesn't exist 302 | print_target_missing 303 | fi 304 | if [ -z "$use_py3" ]; then 305 | use_py3="TRUE" 306 | fi 307 | if [ "$use_py3" != "FALSE" ]; then 308 | # Check for py3 first 309 | python="$(get_local_python_version python3)" 310 | fi 311 | if [ "$use_py3" != "FORCE" ] && [ -z "$python" ]; then 312 | # We aren't using py3 explicitly, and we don't already have a path 313 | python="$(get_local_python_version python2)" 314 | if [ -z "$python" ]; then 315 | # Try just looking for "python" 316 | python="$(get_local_python_version python)" 317 | fi 318 | fi 319 | if [ -z "$python" ]; then 320 | # Didn't ever find it - prompt 321 | prompt_and_download 322 | return 1 323 | fi 324 | # Found it - start our script and pass all args 325 | "$python" "$dir/$target" "${args[@]}" 326 | } 327 | 328 | # Keep track of whether or not we're on macOS to determine if 329 | # we can download and install python for the user as needed. 330 | kernel="$(uname -s)" 331 | # Check to see if we need to force based on 332 | # macOS version. 10.15 has a dummy python3 version 333 | # that can trip up some py3 detection in other scripts. 334 | # set_use_py3_if "3" "10.15" "FORCE" 335 | downloaded="FALSE" 336 | # Check for the aforementioned /usr/bin/python3 stub if 337 | # our OS version is 10.15 or greater. 338 | check_py3_stub="$(compare_to_version "3" "10.15")" 339 | trap cleanup EXIT 340 | if [ "$1" == "--install-python" ] && [ "$kernel" == "Darwin" ]; then 341 | just_installing="TRUE" 342 | download_py "$2" 343 | else 344 | main 345 | fi 346 | -------------------------------------------------------------------------------- /Scripts/ioreg.py: -------------------------------------------------------------------------------- 1 | import os, sys, binascii, json, gzip 2 | from . import run 3 | 4 | class IOReg: 5 | def __init__(self): 6 | self.ioreg = {} 7 | self.pci_devices = [] 8 | self.r = run.Run() 9 | self.d = None # Placeholder 10 | # Placeholder for a local pci.ids file. You can get it from: https://pci-ids.ucw.cz/ 11 | # and place it next to this file 12 | self.pci_ids_url = "https://pci-ids.ucw.cz" 13 | self.pci_ids = {} 14 | 15 | def _get_hex_addr(self,item): 16 | # Attempts to reformat an item from NAME@X,Y to NAME@X000000Y 17 | try: 18 | if not "@" in item: 19 | # If no address - assume 0 20 | item = "{}@0".format(item) 21 | name,addr = item.split("@") 22 | if "," in addr: 23 | cont,port = addr.split(",") 24 | elif len(addr) > 4: 25 | # Using XXXXYYYY formatting already 26 | return name+"@"+addr 27 | else: 28 | # No comma, and 4 or fewer digits 29 | cont,port = addr,"0" 30 | item = name+"@"+hex(int(port,16)+(int(cont,16)<<16))[2:].upper() 31 | except: 32 | pass 33 | return item 34 | 35 | def _get_dec_addr(self,item): 36 | # Attemps to reformat an item from NAME@X000000Y to NAME@X,Y 37 | try: 38 | if not "@" in item: 39 | # If no address - assume 0 40 | item = "{}@0".format(item) 41 | name,addr = item.split("@") 42 | if addr.count(",")==1: 43 | # Using NAME@X,Y formating already 44 | return name+"@"+addr 45 | if len(addr)<5: 46 | return "{}@{},0".format(name,addr) 47 | hexaddr = int(addr,16) 48 | port = hexaddr & 0xFFFF 49 | cont = (hexaddr >> 16) & 0xFFFF 50 | item = name+"@"+hex(cont)[2:].upper() 51 | if port: 52 | item += ","+hex(port)[2:].upper() 53 | except: 54 | pass 55 | return item 56 | 57 | def _get_pcix_uid(self,item,allow_fallback=True,fallback_uid=0,plane="IOService",force=False): 58 | # Helper to look for the passed item's _UID 59 | # Expects a XXXX@Y style string 60 | self.get_ioreg(plane=plane,force=force) 61 | # Ensure our item ends with 2 spaces 62 | item = item.rstrip()+" " 63 | item_uid = None 64 | found_device = False 65 | for line in self.ioreg[plane]: 66 | if item in line: 67 | found_device = True 68 | continue 69 | if not found_device: 70 | continue # Haven't found it yet 71 | # We have the device here - let's look for _UID or a closing 72 | # curly bracket 73 | if line.replace("|","").strip() == "}": 74 | break # Bail on the loop 75 | elif '"_UID" = "' in line: 76 | # Got a _UID - let's rip it 77 | try: 78 | item_uid = int(line.split('"_UID" = "')[1].split('"')[0]) 79 | except: 80 | # Some _UIDs are strings - but we won't accept that here 81 | # as we're ripping it specifically for PciRoot/Pci pathing 82 | break 83 | if item_uid is None and allow_fallback: 84 | return fallback_uid 85 | return item_uid 86 | 87 | def get_ioreg(self,plane="IOService",force=False): 88 | if force or not self.ioreg.get(plane,None): 89 | self.ioreg[plane] = self.r.run({"args":["ioreg", "-lw0", "-p", plane]})[0].split("\n") 90 | return self.ioreg[plane] 91 | 92 | def get_pci_devices(self, force=False): 93 | # Uses system_profiler to build a list of connected 94 | # PCI devices 95 | if force or not self.pci_devices: 96 | try: 97 | self.pci_devices = json.loads(self.r.run({"args":[ 98 | "system_profiler", 99 | "SPPCIDataType", 100 | "-json" 101 | ]})[0])["SPPCIDataType"] 102 | assert isinstance(self.pci_devices,list) 103 | except: 104 | # Failed - reset 105 | self.pci_devices = [] 106 | return self.pci_devices 107 | 108 | def _update_pci_ids_if_missing(self, quiet=True): 109 | # Checks for the existence of pci.ids or pci.ids.gz - and attempts 110 | # to download the latest if none is found. 111 | pci_ids_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"pci.ids") 112 | pci_ids_gz_path = pci_ids_path+".gz" 113 | found = next((x for x in (pci_ids_path,pci_ids_gz_path) if os.path.isfile(x)),None) 114 | if found: 115 | return found 116 | # Not found - try to update 117 | return self._update_pci_ids(quiet=quiet) 118 | 119 | def _update_pci_ids(self, quiet=True): 120 | if self.d is None: 121 | try: 122 | # Only initialize if we're actually using it 123 | from . import downloader 124 | self.d = downloader.Downloader() 125 | except: 126 | return None 127 | def qprint(text): 128 | if quiet: return 129 | print(text) 130 | qprint("Gathering latest info from {}...".format(self.pci_ids_url)) 131 | try: 132 | _html = self.d.get_string(self.pci_ids_url,progress=False) 133 | assert _html 134 | except: 135 | qprint(" - Something went wrong") 136 | return None 137 | # Try to scrape for the .gz compressed download link 138 | qprint("Locating download URL...") 139 | dl_url = None 140 | for line in _html.split("\n"): 141 | if ">pci.ids.gz" in line: 142 | # Got it - build the URL 143 | try: 144 | dl_url = "/".join([ 145 | self.pci_ids_url.rstrip("/"), 146 | line.split('"')[1].lstrip("/") 147 | ]) 148 | break 149 | except: 150 | continue 151 | if not dl_url: 152 | qprint(" - Not located") 153 | return None 154 | # Got a download URL - let's actually download it 155 | qprint(" - {}".format(dl_url)) 156 | qprint("Downloading {}...".format(os.path.basename(dl_url))) 157 | target_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),os.path.basename(dl_url)) 158 | try: 159 | saved_file = self.d.stream_to_file(dl_url,target_path,progress=not quiet) 160 | except: 161 | qprint(" - Something went wrong") 162 | return None 163 | if os.path.isfile(target_path): 164 | qprint("\nSaved to: {}".format(target_path)) 165 | return target_path 166 | qprint("Download failed.") 167 | return None 168 | 169 | def _get_pci_ids_dict(self, force=False): 170 | if self.pci_ids and not force: 171 | return self.pci_ids 172 | self.pci_ids = {} 173 | # Hasn't already been processed - see if it exists, and load it if so 174 | pci_ids_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"pci.ids") 175 | pci_ids_gz_path = pci_ids_path+".gz" 176 | pci_ids = None 177 | if os.path.isfile(pci_ids_gz_path): 178 | # Prioritize the gzip file if found 179 | try: 180 | pci_ids = gzip.open(pci_ids_gz_path) \ 181 | .read().decode(errors="ignore").replace("\r","") \ 182 | .split("\n") 183 | except: 184 | pass 185 | if not pci_ids and os.path.isfile(pci_ids_path): 186 | # Try loading the file 187 | try: 188 | with open(pci_ids_path,"rb") as f: 189 | pci_ids = f.read().decode(errors="ignore") \ 190 | .replace("\r","").split("\n") 191 | except: 192 | pass 193 | # Check again 194 | if not pci_ids: 195 | return self.pci_ids 196 | def get_id_name_from_line(line): 197 | # Helper to rip the id(s) out of the passed 198 | # line and convert to an int 199 | try: 200 | line = line.strip() 201 | if line.startswith("C "): 202 | line = line[2:] 203 | _id = int(line.split(" ")[0].replace(" ",""),16) 204 | name = " ".join(line.split(" ")[1:]) 205 | return (_id,name) 206 | except: 207 | return None 208 | # Walk our file and build out our dict 209 | _classes = False 210 | device = sub = None 211 | key = "devices" 212 | for line in pci_ids: 213 | if line.strip().startswith("# List of known device classes"): 214 | _classes = True 215 | key = "classes" 216 | device = sub = None 217 | continue 218 | if line.strip().startswith("#"): 219 | continue # Skip comments 220 | if line.startswith("\t\t"): 221 | if sub is None: continue 222 | # Got a subsystem/programming interface name 223 | try: 224 | _id,name = get_id_name_from_line(line) 225 | sub[_id] = name 226 | except: 227 | continue 228 | elif line.startswith("\t"): 229 | if device is None: continue 230 | # Got a device/subclass name 231 | try: 232 | _id,name = get_id_name_from_line(line) 233 | device[_id] = sub = {"name":name} 234 | except: 235 | sub = None 236 | continue 237 | else: 238 | # Got a vendor/class 239 | try: 240 | _id,name = get_id_name_from_line(line) 241 | if not key in self.pci_ids: 242 | self.pci_ids[key] = {} 243 | self.pci_ids[key][_id] = device = {"name":name} 244 | except: 245 | device = sub = None 246 | continue 247 | return self.pci_ids 248 | 249 | def get_device_info_from_pci_ids(self, device_dict): 250 | # Returns a dictionary containing the following info: 251 | # { 252 | # "vendor":ven, 253 | # "device":dev, 254 | # "subsystem":sub, 255 | # "class":cls, 256 | # "subclass":scls, 257 | # "programming_interface":pi 258 | # } 259 | info = {} 260 | pci_ids = self._get_pci_ids_dict() 261 | if not pci_ids: 262 | return info 263 | def normalize_id(_id): 264 | if not isinstance(_id,(int,str)): 265 | return None 266 | if isinstance(_id,str): 267 | if _id.startswith("<") and _id.endswith(">"): 268 | _id = _id.strip("<>") 269 | try: 270 | _id = binascii.hexlify(binascii.unhexlify(_id)[::-1]).decode() 271 | except: 272 | return None 273 | try: 274 | _id = int(_id,16) 275 | except: 276 | return None 277 | return _id 278 | device_info = {} 279 | # Get the vendor, device, subsystem ids 280 | v = normalize_id(device_dict.get("vendor-id")) 281 | d = normalize_id(device_dict.get("device-id")) 282 | sv = normalize_id(device_dict.get("subsystem-vendor-id")) 283 | si = normalize_id(device_dict.get("subsystem-id")) 284 | device_info["vendor"] = pci_ids.get("devices",{}).get(v,{}).get("name") 285 | device_info["device"] = pci_ids.get("devices",{}).get(v,{}).get(d,{}).get("name") 286 | if sv is not None and si is not None: 287 | sid = (sv << 16) + si 288 | device_info["subsystem"] = pci_ids.get("devices",{}).get(v,{}).get(d,{}).get(sid) 289 | # Resolve our class-code to sub ids if possible 290 | cc = normalize_id(device_dict.get("class-code")) 291 | if cc is not None: 292 | # 0xAAAABBCC 293 | c = cc >> 16 & 0xFFFF 294 | s = cc >> 8 & 0xFF 295 | p = cc & 0xFF 296 | device_info["class"] = pci_ids.get("classes",{}).get(c,{}).get("name") 297 | device_info["subclass"] = pci_ids.get("classes",{}).get(c,{}).get(s,{}).get("name") 298 | device_info["programming_interface"] = pci_ids.get("classes",{}).get(c,{}).get(s,{}).get(p) 299 | return device_info 300 | 301 | def get_pci_device_name(self, device_dict, pci_devices=None, force=False, use_unknown=True, use_pci_ids=True): 302 | device_name = "Unknown PCI Device" if use_unknown else None 303 | if not device_dict or not isinstance(device_dict,dict): 304 | return device_name 305 | if "info" in device_dict: 306 | # Expand the info 307 | device_dict = device_dict["info"] 308 | if use_pci_ids: 309 | pci_dict = self.get_device_info_from_pci_ids(device_dict) 310 | if pci_dict and pci_dict.get("device"): 311 | return pci_dict["device"] 312 | # Compare the vendor-id, device-id, revision-id, 313 | # subsystem-id, and subsystem-vendor-id if found 314 | # The system_profiler output prefixes those with "sppci-" 315 | def normalize_id(_id): 316 | if not _id: 317 | return None 318 | if _id.startswith("<") and _id.endswith(">"): 319 | _id = _id.strip("<>") 320 | try: 321 | _id = binascii.hexlify(binascii.unhexlify(_id)[::-1]).decode() 322 | except: 323 | return None 324 | try: 325 | return int(_id,16) 326 | except: 327 | return None 328 | key_list = ( 329 | "vendor-id", 330 | "device-id", 331 | "subsystem-vendor-id", 332 | "subsystem-id" 333 | ) 334 | # Normalize the ids 335 | d_keys = [normalize_id(device_dict.get(key)) for key in key_list] 336 | if any(k is None for k in d_keys[:2]): 337 | # vendor and device ids are required 338 | return device_name 339 | # - check our system_profiler info 340 | if not isinstance(pci_devices,list): 341 | pci_devices = self.get_pci_devices(force=force) 342 | for pci_device in pci_devices: 343 | p_keys = [normalize_id(pci_device.get("sppci_"+key)) for key in key_list] 344 | if p_keys == d_keys: 345 | # Got a match - save the name if present 346 | device_name = pci_device.get("_name",device_name) 347 | break 348 | return device_name 349 | 350 | def get_all_devices(self, plane=None, force=False): 351 | # Let's build a device dict - and retain any info for each 352 | if plane is None: 353 | # Try to use IODeviceTree if it's populated, or if 354 | # IOService is not populated 355 | if self.ioreg.get("IODeviceTree") or not self.ioreg.get("IOService"): 356 | plane = "IODeviceTree" 357 | else: 358 | plane = "IOService" 359 | self.get_ioreg(plane=plane,force=force) 360 | # We're only interested in these two classes 361 | class_match = ( 362 | "= pad: 385 | del _path[-1] 386 | else: 387 | break 388 | if class_match and not any(c in line for c in class_match): 389 | continue # Not the right class 390 | # We found a device of our class - let's 391 | # retain info about it 392 | name = parts[1].split(" ")[0] 393 | clss = parts[1].split("