├── .gitignore ├── README.md ├── main.py └── src ├── __init__.py ├── basic_system_info.py ├── default_result_list.py ├── global_vars.py ├── sysinfo ├── __init__.py ├── board_name │ ├── __init__.py │ ├── linux.py │ ├── macos.py │ └── windows.py ├── cpu_info │ ├── __init__.py │ ├── linux.py │ ├── macos.py │ ├── unwanted_list.py │ └── windows.py ├── desktop_environment │ ├── __init__.py │ └── plasma.py ├── gpu_info │ ├── __init__.py │ ├── linux.py │ ├── macos.py │ └── windows.py ├── kernel.py ├── memory │ ├── __init__.py │ ├── linux.py │ ├── macos.py │ └── windows.py ├── os_info │ ├── __init__.py │ ├── linux.py │ ├── macos.py │ └── windows.py ├── package_count │ ├── __init__.py │ └── pm_list.py ├── resolution │ ├── __init__.py │ ├── linux.py │ ├── macos.py │ └── windows.py ├── shell │ ├── __init__.py │ ├── posix.py │ └── windows.py ├── system_theme │ ├── __init__.py │ ├── base.py │ ├── gtk2.py │ ├── gtk3.py │ └── qt.py ├── terminal │ ├── __init__.py │ ├── corrections.py │ └── font │ │ ├── __init__.py │ │ ├── appleterminal.py │ │ ├── cutefish_termin.py │ │ ├── gnome_terminal.py │ │ ├── konsole.py │ │ ├── windowsterminal.py │ │ └── xfce4_terminal.py ├── uptime │ ├── __init__.py │ ├── macos.py │ ├── posix.py │ └── windows.py └── username.py └── tools ├── __init__.py ├── command.py ├── get_parents ├── __init__.py ├── linux.py ├── macos.py └── windows.py ├── get_wmic_value.py ├── parse_info.py └── parse_proc.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # pip log 7 | pip-log.txt 8 | pip-delete-this-directory.txt 9 | 10 | # Unit test / coverage reports 11 | htmlcov/ 12 | .tox/ 13 | .nox/ 14 | .coverage 15 | .coverage.* 16 | .cache 17 | nosetests.xml 18 | coverage.xml 19 | *.cover 20 | *.py,cover 21 | .hypothesis/ 22 | .pytest_cache/ 23 | cover/ 24 | 25 | # Translations 26 | *.mo 27 | *.pot 28 | 29 | # Django stuff: 30 | *.log 31 | local_settings.py 32 | db.sqlite3 33 | db.sqlite3-journal 34 | 35 | # Flask stuff: 36 | instance/ 37 | .webassets-cache 38 | 39 | # Scrapy stuff: 40 | .scrapy 41 | 42 | # Sphinx documentation 43 | docs/_build/ 44 | 45 | # PyBuilder 46 | .pybuilder/ 47 | target/ 48 | 49 | # Jupyter Notebook 50 | .ipynb_checkpoints 51 | 52 | # IPython 53 | profile_default/ 54 | ipython_config.py 55 | 56 | # pyenv 57 | # For a library or package, you might want to ignore these files since the code is 58 | # intended to run in multiple environments; otherwise, check them in: 59 | # .python-version 60 | 61 | # pipenv 62 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 63 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 64 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 65 | # install all needed dependencies. 66 | #Pipfile.lock 67 | 68 | # poetry 69 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 70 | # This is especially recommended for binary packages to ensure reproducibility, and is more 71 | # commonly ignored for libraries. 72 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 73 | #poetry.lock 74 | 75 | # pdm 76 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 77 | #pdm.lock 78 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 79 | # in version control. 80 | # https://pdm.fming.dev/#use-with-ide 81 | .pdm.toml 82 | 83 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 84 | __pypackages__/ 85 | 86 | # Celery stuff 87 | celerybeat-schedule 88 | celerybeat.pid 89 | 90 | # SageMath parsed files 91 | *.sage.py 92 | 93 | # Environments 94 | .env 95 | .venv 96 | env/ 97 | venv/ 98 | ENV/ 99 | env.bak/ 100 | venv.bak/ 101 | 102 | # Spyder project settings 103 | .spyderproject 104 | .spyproject 105 | 106 | # Rope project settings 107 | .ropeproject 108 | 109 | # mkdocs documentation 110 | /site 111 | 112 | # mypy 113 | .mypy_cache/ 114 | .dmypy.json 115 | dmypy.json 116 | 117 | # Pyre type checker 118 | .pyre/ 119 | 120 | # pytype static type analyzer 121 | .pytype/ 122 | 123 | # Cython debug symbols 124 | cython_debug/ 125 | 126 | /ptime.exe 127 | 128 | *.backup 129 | 130 | .vscode/ 131 | .idea 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # qwqfetch 3 | 4 | Hi! 5 | 6 | This is a replacement of neofetch in [hyfetch](https://github.com/hykilpikonna/hyfetch), written in python. It will serve as a module for output system information. 7 | 8 | Now it is still in its early state, several items has not been worked on, and it may (very possibly) display weird information on your system, or even simply throw an exception. 9 | 10 | This is the output on my Arch Linux: 11 | ``` 12 | aleksana@Aleksana-laptop 13 | ------------------------ 14 | OS: Arch Linux x86_64 15 | Host: LENOVO LNVNB161216 16 | Kernel: 5.19.1-zen1-1.1-zen 17 | Uptime: 1 day, 7 hours, 21 mins 18 | Packages: 1120 (pacman), 11 (flatpak) 19 | Shell: zsh 5.9 20 | Resolution: 2240x1400 21 | DE: Plasma 5.25.4 [KF5 5.97.0] [Qt 5.15.5] 22 | Theme: Breeze [GTK2/3/Qt] 23 | Icons: Papirus-Dark [GTK2/3/Qt] 24 | Cursor: breeze_cursors 25 | Terminal: konsole 26 | Terminal Font: SFMono Nerd Font Mono 11 27 | CPU: AMD Ryzen 7 5800H (16) @ 3.200GHz 28 | GPU: AMD ATI Cezanne 29 | Memory: 6124MiB / 13816MiB 30 | ``` 31 | 32 | And also windows (without the need of MinGW): 33 | ``` 34 | Aleksana@DESKTOP-8D84B2M 35 | ------------------------ 36 | OS: Microsoft Windows 10 Pro AMD64 37 | Host: Intel Corporation 440BX Desktop Reference Platform 38 | Kernel: NT 10.0.19044 39 | Uptime: 5 hours, 1 min 40 | Shell: cmd 10.0.19044.1889 41 | Resolution: 2226x1205 42 | DE: Windows Shell 43 | Terminal: WindowsTerminal 44 | CPU: AMD Ryzen 7 5800H (4) @ 3.19 GHz 45 | GPU: VMware SVGA 3D 46 | Memory: 2.5 GiB / 4.0 GiB 47 | ``` 48 | 49 | The platforms we plan to support first are GNU/Linux (popular distributions that adheres to a set of specifications), Windows and MacOS. But if you need it, feel free to ask, and we'll try our best.(Note that we don't plan to support python<3.7) 50 | 51 | Any kind of contributions is welcomed. For faster test & feedback, join our telegram group [@hyfetch](https://t.me/hyfetch). -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | def main(): 5 | from sys import version_info, exit 6 | 7 | if not (version_info[0] == 3 and version_info[1] >= 7): 8 | exit("Sorry, Please use Python3 > 3.7") 9 | 10 | import src 11 | 12 | print(src.get_result()) 13 | 14 | 15 | if __name__ == "__main__": 16 | main() 17 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from . import global_vars 3 | from .basic_system_info import * 4 | from .default_result_list import default_result 5 | 6 | 7 | def get_result_dict() -> dict[str, str]: 8 | global_vars._init() 9 | 10 | global_vars.set( 11 | { 12 | "platform": {"name": sys_name, "type": sys_type, "arch": sys_arch}, 13 | "result": {result: "" for result in default_result}, 14 | "args": {}, 15 | } 16 | ) 17 | 18 | from . import sysinfo 19 | 20 | return { 21 | key: value 22 | for key, value in dict( 23 | sorted( 24 | sysinfo.run().items(), key=lambda pair: default_result.index(pair[0]) 25 | ) 26 | ).items() 27 | if value 28 | } 29 | 30 | 31 | def get_result() -> str: 32 | result_dict = get_result_dict() 33 | result = f"{result_dict.pop('USERNAME')}@{result_dict.pop('HOSTNAME')}\n" 34 | result += "-" * (len(result) - 1) + "\n" 35 | for key, val in result_dict.items(): 36 | result += f"{key}: {val}\n" 37 | return result 38 | -------------------------------------------------------------------------------- /src/basic_system_info.py: -------------------------------------------------------------------------------- 1 | from platform import system, machine 2 | from os import name 3 | 4 | sys_name = system() 5 | sys_type = name 6 | sys_arch = machine() 7 | -------------------------------------------------------------------------------- /src/default_result_list.py: -------------------------------------------------------------------------------- 1 | default_result = [ 2 | "USERNAME", 3 | "HOSTNAME", 4 | "OS", 5 | "Host", 6 | "Kernel", 7 | "Uptime", 8 | "Packages", 9 | "Shell", 10 | "Resolution", 11 | "DE", 12 | "WM", 13 | "Theme", 14 | "Icons", 15 | "Cursor", 16 | "Terminal", 17 | "Terminal Font", 18 | "CPU", 19 | "GPU", 20 | "Memory", 21 | ] 22 | -------------------------------------------------------------------------------- /src/global_vars.py: -------------------------------------------------------------------------------- 1 | def _init(): 2 | global _global_dict 3 | _global_dict = {} 4 | 5 | 6 | def set(new_dict: dict) -> None: 7 | merge(new_dict, _global_dict) 8 | 9 | 10 | def merge(new_dict: dict, old_dict: dict): 11 | for key in new_dict.keys(): 12 | if isinstance(key, str): 13 | if ( 14 | isinstance(new_dict[key], dict) 15 | and key in old_dict.keys() 16 | and isinstance(old_dict[key], dict) 17 | ): 18 | merge(new_dict[key], old_dict[key]) 19 | elif new_dict[key]: 20 | old_dict[key] = new_dict[key] 21 | 22 | 23 | def get(keys: list): 24 | result = [] 25 | for key in keys: 26 | if isinstance(key, str): 27 | result.append(_global_dict[key]) 28 | return result 29 | 30 | 31 | def delete(keys: list): 32 | for key in keys: 33 | if isinstance(key, str): 34 | del _global_dict[key] 35 | -------------------------------------------------------------------------------- /src/sysinfo/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from os import cpu_count 3 | from multiprocessing.pool import ThreadPool 4 | from importlib import import_module 5 | 6 | debug = False 7 | threading = True 8 | 9 | modules_name_list = [ 10 | "board_name", 11 | "cpu_info", 12 | "desktop_environment", 13 | "gpu_info", 14 | "kernel", 15 | "memory", 16 | "os_info", 17 | "package_count", 18 | "resolution", 19 | "shell", 20 | "system_theme", 21 | "terminal", 22 | "uptime", 23 | "username", 24 | ] 25 | 26 | 27 | functions_list = [ 28 | getattr(import_module(f".{module_name}", package=__name__), "get") 29 | for module_name in modules_name_list 30 | ] 31 | 32 | 33 | def run_func(func): 34 | if debug: 35 | return func() 36 | else: 37 | try: 38 | return func() 39 | except: 40 | return {} 41 | 42 | 43 | def run() -> dict[str, str]: 44 | 45 | return_list: list[dict[str, str]] = ( 46 | ThreadPool(cpu_count()).map(run_func, functions_list) 47 | if threading 48 | else [run_func(f) for f in functions_list] 49 | ) 50 | return {k: v for d in return_list for k, v in d.items()} 51 | -------------------------------------------------------------------------------- /src/sysinfo/board_name/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | 4 | sys_name = global_vars.get(["platform"])[0]["name"] 5 | 6 | 7 | def get() -> dict[str, str]: 8 | if sys_name == "Linux": 9 | from .linux import info 10 | elif sys_name == "Windows": 11 | from .windows import info 12 | elif sys_name == "Darwin": 13 | from .macos import info 14 | else: 15 | info = "" 16 | return {"Host": info} 17 | -------------------------------------------------------------------------------- /src/sysinfo/board_name/linux.py: -------------------------------------------------------------------------------- 1 | def get_dmi_info(): 2 | dmi_path = "/sys/class/dmi/id/" 3 | 4 | try: 5 | board_name = open(dmi_path + "board_name").read().strip() 6 | except: 7 | board_name = "" 8 | 9 | try: 10 | board_vendor = open(dmi_path + "board_vendor").read().strip() 11 | except: 12 | board_vendor = "" 13 | 14 | return f"{board_vendor} {board_name}" 15 | 16 | 17 | info = get_dmi_info().strip() 18 | -------------------------------------------------------------------------------- /src/sysinfo/board_name/macos.py: -------------------------------------------------------------------------------- 1 | from ...tools.command import RunCommand 2 | from ...tools.parse_info import parser 3 | 4 | 5 | model_name = parser( 6 | RunCommand("system_profiler SPHardwareDataType").read(), 7 | {"Model Identifier": "m"}, 8 | ":", 9 | )["m"] 10 | 11 | 12 | info = f"Apple {model_name}" 13 | -------------------------------------------------------------------------------- /src/sysinfo/board_name/windows.py: -------------------------------------------------------------------------------- 1 | from ...tools import get_wmic 2 | 3 | 4 | def get_board_info(): 5 | try: 6 | board_name = get_wmic('baseboard get product') 7 | except: 8 | board_name = "" 9 | 10 | try: 11 | board_vendor = get_wmic('baseboard get Manufacturer') 12 | except: 13 | board_vendor = "" 14 | 15 | return f"{board_vendor} {board_name}" 16 | 17 | 18 | info = get_board_info().strip() 19 | -------------------------------------------------------------------------------- /src/sysinfo/cpu_info/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | from .unwanted_list import unwanted 4 | import re 5 | 6 | platform_info = global_vars.get(["platform"])[0] 7 | sys_name = platform_info["name"] 8 | sys_arch = platform_info["arch"] 9 | 10 | 11 | def strip_name(name: str) -> str: 12 | for info in unwanted: 13 | name = re.sub(info,'',name).strip() 14 | while " " in name: 15 | name = name.replace(" ", " ") 16 | return name 17 | 18 | 19 | def get() -> dict[str, str]: 20 | if sys_name == "Linux": 21 | from .linux import get_cpu_info 22 | elif sys_name == "Windows": 23 | from .windows import get_cpu_info 24 | elif sys_name == "Darwin": 25 | from .macos import get_cpu_info 26 | else: 27 | get_cpu_info = lambda: {} 28 | 29 | info = get_cpu_info() 30 | output = [] 31 | # if you can prove to me you have more than one different processors, 32 | # and actually using it and have python >= 3.7 installed 33 | # I'll change this as soon as possible. 34 | if "count" in info and info["count"] > 1: 35 | output.append(f"{info['count']}x") 36 | if info.get("name"): 37 | output.append(strip_name(info["name"])) 38 | if "core" in info and info["core"] > 1: 39 | output.append(f"({info['core']})") 40 | if info.get("freq"): 41 | output.append(f"@ {info['freq'] / 1000_000:.2f} GHz") 42 | 43 | return {"CPU": " ".join(output)} 44 | -------------------------------------------------------------------------------- /src/sysinfo/cpu_info/linux.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | from ...global_vars import merge 6 | 7 | 8 | def get_cpu_freq_sys(index: int) -> int | None: 9 | freq_path = Path(f"/sys/devices/system/cpu/cpu{index}/cpufreq/") 10 | try: 11 | freq_max = int((freq_path / "cpuinfo_max_freq").read_text()) 12 | try: 13 | freq_bios_max = int((freq_path / "bios_limit").read_text()) 14 | return min(freq_bios_max, freq_max) 15 | except: 16 | return freq_max 17 | except: 18 | return None 19 | 20 | 21 | def get_from_proc() -> dict: 22 | from ...tools import parse_proc 23 | 24 | def strip_cpu_info(cpu_list): 25 | iterator = 0 26 | while iterator < len(cpu_list): 27 | iterator += 1 28 | del cpu_list[iterator: int(cpu_list[iterator - 1]["siblings"])] 29 | return cpu_list 30 | 31 | cpu_raw_info = strip_cpu_info(parse_proc("/proc/cpuinfo")) 32 | return { 33 | "name": cpu_raw_info[0]["model name"], 34 | "core": int(cpu_raw_info[0]["siblings"]), 35 | "freq": get_cpu_freq_sys(cpu_raw_info[0]["processor"]), 36 | "count": len(cpu_raw_info), 37 | } 38 | 39 | 40 | def get_from_lscpu() -> dict: 41 | from ...tools.command import RunCommand 42 | 43 | mapping = { 44 | "Socket(s)": "count", 45 | "CPU(s)": "core", # is wrong but will correct later 46 | "CPU MHz": "freq", 47 | "CPU max MHz": "freq", 48 | "Model name": "name", 49 | } 50 | 51 | lscpu: dict[str, str | int] = dict(ln.split(':', 1) for ln in RunCommand("lscpu").readlines() if ':' in ln) 52 | lscpu = {mapping[k.strip()]: v.strip() for k, v in lscpu.items() if k in mapping and v.strip()} 53 | 54 | if 'freq' in lscpu: 55 | lscpu['freq'] = int(lscpu['freq']) * 1000 56 | 57 | if 'core' in lscpu and 'count' in lscpu: 58 | lscpu['count'] = int(lscpu['count']) 59 | lscpu['core'] = int(lscpu['core']) // lscpu['count'] 60 | 61 | return lscpu 62 | 63 | 64 | def get_cpu_info() -> dict: 65 | cpu_info_dict = {} 66 | for method in [get_from_proc, get_from_lscpu]: 67 | cpu_info_dict.update({k: v for k, v in method().items() if v}) 68 | if {"count", "core", "freq", "name"} == set(cpu_info_dict.keys()): 69 | break 70 | return cpu_info_dict 71 | -------------------------------------------------------------------------------- /src/sysinfo/cpu_info/macos.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...tools.command import RunCommand 3 | 4 | 5 | def run_sysctl(key: str) -> str: 6 | return RunCommand(f"/usr/sbin/sysctl -n {key}").readline() 7 | 8 | 9 | def get_cpu_info() -> dict[str, str]: 10 | total_cores = run_sysctl("machdep.cpu.core_count") 11 | per_cores = run_sysctl("machdep.cpu.cores_per_package") 12 | info = { 13 | "name": run_sysctl("machdep.cpu.brand_string"), 14 | "core": int(per_cores), 15 | "count": int(total_cores) / int(per_cores), 16 | "freq": int(run_sysctl("hw.cpufrequency")) / 1000, 17 | } 18 | return info 19 | -------------------------------------------------------------------------------- /src/sysinfo/cpu_info/unwanted_list.py: -------------------------------------------------------------------------------- 1 | # Here is regex! 2 | 3 | unwanted = [ 4 | "with Radeon.*Graphics", 5 | "\((TM|tm|R|r)\)", 6 | "CPU", 7 | "Processor", 8 | "(Dual|Quad|Six|Eight)-Core", 9 | "([1-9])?[0-9]-Core", 10 | "@( )?^\d+(\.\d+)?( )?(G|M)?H(Z|z)", 11 | ", .* Compute Cores", 12 | ", altivec supported", 13 | "Technologies, Inc", 14 | "FPU.*", 15 | '\("AuthenticAMD".*\)' 16 | ] 17 | -------------------------------------------------------------------------------- /src/sysinfo/cpu_info/windows.py: -------------------------------------------------------------------------------- 1 | from ...tools import get_wmic 2 | 3 | 4 | def get_cpu_info() -> dict: 5 | return { 6 | "name": get_wmic("cpu get Name").strip(), 7 | "core": int(get_wmic("cpu get NumberOfCores").strip()), 8 | "freq": int(get_wmic("cpu get MaxClockSpeed")) * 1000, 9 | "count": 1, 10 | } 11 | -------------------------------------------------------------------------------- /src/sysinfo/desktop_environment/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | 4 | sys_name = global_vars.get(["platform"])[0]["name"] 5 | 6 | 7 | def get() -> dict[str, str]: 8 | if sys_name == "Windows": 9 | de_name = "Windows Shell" 10 | elif sys_name == "Darwin": 11 | de_name = "Aqua" 12 | elif sys_name == "Linux": 13 | from os import getenv 14 | 15 | de_name = getenv("DESKTOP_SESSION") 16 | if not de_name: 17 | de_name = "" 18 | else: 19 | de_name = "" 20 | 21 | if de_name == "plasma": 22 | from .plasma import get as getplasma 23 | 24 | de_name = getplasma() 25 | return {"DE": de_name} 26 | -------------------------------------------------------------------------------- /src/sysinfo/desktop_environment/plasma.py: -------------------------------------------------------------------------------- 1 | from ...tools.command import RunCommand 2 | 3 | 4 | def read_version(command: str, location: int): 5 | try: 6 | return RunCommand(command).read().split()[location].strip() 7 | except: 8 | return "" 9 | 10 | 11 | def get(): 12 | plasma_version = read_version("plasmashell --version", 1) 13 | kde_version = read_version("kded5 --version", 1) 14 | qt_version = read_version("qtpaths-qt5 --qt-version", 0) 15 | result = f"Plasma {plasma_version.strip()}" 16 | if kde_version: 17 | result += f" [KF5 {kde_version}]" 18 | if qt_version: 19 | result += f" [Qt {qt_version}]" 20 | return result 21 | -------------------------------------------------------------------------------- /src/sysinfo/gpu_info/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | 4 | platform_info = global_vars.get(["platform"])[0] 5 | sys_name = platform_info["name"] 6 | 7 | 8 | def get() -> dict[str, str]: 9 | # if sys_name == "Linux": 10 | # from .linux import gpu_info 11 | if sys_name == "Windows": 12 | from .windows import gpu_info 13 | elif sys_name == "Linux": 14 | from .linux import gpu_info 15 | elif sys_name == "Darwin": 16 | from .macos import gpu_info 17 | else: 18 | gpu_info = "" 19 | 20 | # going to change to regex 21 | # def strip(name): 22 | # for info in unwanted: 23 | # name = name.replace(info, "") 24 | # name = name.strip() 25 | # return name 26 | 27 | return {"GPU": gpu_info.strip()} 28 | -------------------------------------------------------------------------------- /src/sysinfo/gpu_info/linux.py: -------------------------------------------------------------------------------- 1 | def get_from_lspci(): 2 | try: 3 | from ...tools import RunCommand 4 | 5 | lspci = RunCommand("lspci -mm").readlines() 6 | for line in lspci: 7 | if '"VGA compatible controller"' in line: 8 | line_list = line.split('" "') 9 | if "[" and "]" in line_list[1]: 10 | manufacturer = ( 11 | line_list[1].strip('"').split("[", 1)[1].split("]")[0] 12 | ) 13 | manufacturer = manufacturer.replace("/", " ") 14 | else: 15 | manufacturer = line_list[1] 16 | name = line_list[2].split('"')[0] 17 | return f"{manufacturer} {name}".strip() 18 | return "" 19 | except IndexError: 20 | return "" 21 | 22 | 23 | def get_from_glxinfo(): 24 | try: 25 | from ...tools.command import RunCommand 26 | 27 | glxinfo = RunCommand("glxinfo -B").readlines() 28 | for line in glxinfo: 29 | if "Device: " in line: 30 | name = line.split(": ", 1)[1].split(" (", 1)[0] 31 | return name 32 | return "" 33 | except IndexError: 34 | return "" 35 | 36 | 37 | gpu_info = "" 38 | for method in [get_from_lspci, get_from_glxinfo]: 39 | result = method() 40 | if result: 41 | gpu_info = result 42 | break 43 | -------------------------------------------------------------------------------- /src/sysinfo/gpu_info/macos.py: -------------------------------------------------------------------------------- 1 | from ...tools.command import RunCommand 2 | from ...tools.parse_info import parser 3 | 4 | gpu_info = parser( 5 | RunCommand("system_profiler SPDisplaysDataType").read(), 6 | {"Chipset Model": "name"}, 7 | ":", 8 | )["name"] 9 | -------------------------------------------------------------------------------- /src/sysinfo/gpu_info/windows.py: -------------------------------------------------------------------------------- 1 | from ...tools import get_wmic 2 | 3 | gpu_info = get_wmic('path win32_VideoController get name') -------------------------------------------------------------------------------- /src/sysinfo/kernel.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from platform import release, uname 3 | from .. import global_vars 4 | 5 | 6 | def get() -> dict[str, str]: 7 | sys_name = global_vars.get(["platform"])[0]["name"] 8 | if sys_name == "Linux": 9 | kernel = release() 10 | elif sys_name == "Darwin": 11 | kernel = f"Darwin {uname().release}" 12 | elif sys_name == "Windows": 13 | from ..tools import get_wmic 14 | 15 | kernel = f"NT {get_wmic('os get version')}" 16 | else: 17 | kernel = "" 18 | return {"Kernel": kernel} 19 | -------------------------------------------------------------------------------- /src/sysinfo/memory/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | 4 | 5 | def get() -> dict[str, str]: 6 | 7 | sys_name = global_vars.get(["platform"])[0]["name"] 8 | 9 | if sys_name == "Linux": 10 | from .linux import memory_used, memory_all 11 | elif sys_name == "Windows": 12 | from .windows import memory_used, memory_all 13 | elif sys_name == "Darwin": 14 | from .macos import memory_used, memory_all 15 | else: 16 | return {} 17 | 18 | return { 19 | "Memory": f"{memory_used / 1024 ** 2:.2f} GiB / {memory_all / 1024 ** 2:.2f} GiB" 20 | } 21 | -------------------------------------------------------------------------------- /src/sysinfo/memory/linux.py: -------------------------------------------------------------------------------- 1 | from ...tools import parse_proc 2 | 3 | info_dict = parse_proc("/proc/meminfo")[0] 4 | 5 | memory_all = int(info_dict["MemTotal"].split()[0]) 6 | memory_available = ( 7 | int(info_dict["MemAvailable"].split()[0]) 8 | if "MemAvailable" in info_dict 9 | else int(info_dict["MemFree"].split()[0]) 10 | ) 11 | 12 | # MemFree is actually better,but following neofetch 13 | # can avoid someone complaining the value not the same. 14 | # When MemAvailable does not exist use MemFree instead. 15 | 16 | memory_used = memory_all - memory_available 17 | -------------------------------------------------------------------------------- /src/sysinfo/memory/macos.py: -------------------------------------------------------------------------------- 1 | from ...tools.command import RunCommand 2 | from ...tools.parse_info import parser 3 | 4 | memory_all = int(RunCommand("sysctl -n hw.memsize").read()) / 1024 5 | memory_info = parser( 6 | RunCommand("vm_stat").read(), 7 | {"Pages active": "active", "Pages wired down": "wired_down"}, 8 | ":", 9 | ) 10 | memory_used = ( 11 | int(memory_info["active"].rstrip(".")) + int(memory_info["wired_down"].rstrip(".")) 12 | ) * 4 13 | -------------------------------------------------------------------------------- /src/sysinfo/memory/windows.py: -------------------------------------------------------------------------------- 1 | from ...tools import get_wmic 2 | 3 | memory_all = int(get_wmic('ComputerSystem get TotalPhysicalMemory')) / 1024 4 | memory_free = int(get_wmic('OS get FreePhysicalMemory')) 5 | 6 | memory_used = memory_all - memory_free -------------------------------------------------------------------------------- /src/sysinfo/os_info/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | 4 | 5 | def get() -> dict[str, str]: 6 | platform_info = global_vars.get(["platform"])[0] 7 | sys_name = platform_info["name"] 8 | sys_arch = platform_info["arch"] 9 | 10 | if sys_name == "Linux": 11 | from .linux import info 12 | elif sys_name == "Darwin": 13 | from .macos import info 14 | elif sys_name == "Windows": 15 | from .windows import info 16 | else: 17 | info = "" 18 | 19 | return {"OS": f"{info} {sys_arch}"} 20 | -------------------------------------------------------------------------------- /src/sysinfo/os_info/linux.py: -------------------------------------------------------------------------------- 1 | from ...tools import parse_info 2 | 3 | info_dict = { 4 | "name": "", 5 | "version": "", 6 | "codename": "", 7 | "full_name": "", 8 | # As you can see this is for Arch Linux! 9 | "no_version": False, 10 | } 11 | 12 | key_order = ["name", "version", "codename"] 13 | 14 | 15 | def get_from_distro(): 16 | try: 17 | import distro 18 | 19 | info_dict.update( 20 | { 21 | "name": distro.name(), 22 | "version": distro.version(), 23 | "codename": distro.codename(), 24 | } 25 | ) 26 | if info_dict["version"] == "rolling" or "": 27 | info_dict["no_version"] = True 28 | info_dict["version"] = "" 29 | if info_dict["codename"] == "n/a" or "": 30 | info_dict["codename"] = "" 31 | except ImportError: 32 | pass 33 | 34 | 35 | def get_from_os_release(): 36 | try: 37 | os_release = open("/etc/os-release").read() 38 | requirements = { 39 | "PRETTY_NAME": "full_name", 40 | "NAME": "name", 41 | "VERSION_ID": "version", 42 | "VERSION_CODENAME": "codename", 43 | "BUILD_ID": "build_id", 44 | } 45 | outputs: dict[str, str] = parse_info.parser(os_release, requirements, "=") 46 | if outputs.pop("build_id") == "rolling": 47 | info_dict["no_version"] = True 48 | for key in outputs.keys(): 49 | if outputs[key] != "": 50 | info_dict[key] = outputs[key].strip('"') 51 | 52 | except (FileNotFoundError, IndexError): 53 | pass 54 | 55 | 56 | def get_from_lsb_release(): 57 | try: 58 | from ...tools.command import RunCommand 59 | 60 | requirements = {"Description": "full_name", "Release": "version"} 61 | lsb_release = RunCommand("lsb_release -a").read() 62 | lsb_release: dict[str, str] = parse_info.parser(lsb_release, requirements, ":") 63 | 64 | if lsb_release["full_name"] != "n/a" or "": 65 | info_dict["full_name"] = lsb_release["full_name"] 66 | if lsb_release["version"] == "rolling": 67 | info_dict["no_version"] = True 68 | elif lsb_release["version"]: 69 | info_dict["version"] = lsb_release["version"] 70 | 71 | except IndexError: 72 | pass 73 | 74 | 75 | # Because /etc/issue is highly modified in many distros 76 | # I don't consider it appropriate to detect it. 77 | 78 | for method in [get_from_distro, get_from_os_release, get_from_lsb_release]: 79 | if ( 80 | (info_dict["full_name"] != "") 81 | or (info_dict["no_version"] and info_dict["name"] != "") 82 | or ("" not in [info_dict[key] for key in key_order]) 83 | ): 84 | break 85 | method() 86 | 87 | if not info_dict["name"]: 88 | info_dict["name"] = "Unknown Linux" 89 | 90 | if info_dict["full_name"]: 91 | info = info_dict["full_name"] 92 | else: 93 | info = "" 94 | for key in key_order: # get correct output order 95 | info = f"{info} {info_dict[key]}" 96 | info = info.strip() 97 | -------------------------------------------------------------------------------- /src/sysinfo/os_info/macos.py: -------------------------------------------------------------------------------- 1 | from platform import mac_ver 2 | 3 | info = f"macOS {mac_ver()[0]}" 4 | -------------------------------------------------------------------------------- /src/sysinfo/os_info/windows.py: -------------------------------------------------------------------------------- 1 | from ...tools import get_wmic 2 | 3 | info = get_wmic('os get name').split('|')[0] -------------------------------------------------------------------------------- /src/sysinfo/package_count/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...tools.command import RunCommand 3 | from .pm_list import package_managers 4 | 5 | 6 | def get() -> dict[str, str]: 7 | packages_list = {} 8 | 9 | for pm in package_managers: 10 | if "command" in pm.keys(): 11 | output = RunCommand(pm["command"]).read() 12 | count = output.count("\n") 13 | if count: 14 | packages_list[pm["name"]] = count 15 | 16 | packages = [f"{count} ({pm})" for pm, count in packages_list.items()] 17 | return {"Packages": ", ".join(packages)} 18 | -------------------------------------------------------------------------------- /src/sysinfo/package_count/pm_list.py: -------------------------------------------------------------------------------- 1 | # Do not use unix pipe!!! 2 | 3 | package_managers = [ 4 | {"name": "pacman", "command": "pacman -Qq --color never"}, 5 | { 6 | "name": "dpkg", 7 | "command": r"dpkg-query -f '.\n' -W", 8 | }, 9 | { 10 | "name": "flatpak", 11 | "command": "flatpak list --all", 12 | }, 13 | { 14 | "name": "pm", 15 | "command": "pm list packages", 16 | }, 17 | {"name": "swupd", "command": "swupd bundle-list --quiet"}, 18 | {"name": "bulge", "command": "bulge list"}, 19 | {"name": "pacinstall", "command": "pacinstall -l"}, 20 | {"name": "butch", "command": "butch list"}, 21 | {"name": "lvu", "command": "lvu installed"}, 22 | {"name": "pkgin", "command": "pkgin list"}, 23 | {"name": "xbps", "command": "xbps-query -l"}, 24 | {"name": "tce", "command": "tce-status -i"}, 25 | {"name": "socery", "command": "gaze installed"}, 26 | {"name": "alps", "command": "alps showinstalled"}, 27 | {"name": "opkg", "command": "opkg list-installed"}, 28 | {"name": "cpt", "command": "cpt list"}, 29 | {"name": "pisi", "command": "pisi li"}, 30 | {"name": "apk", "command": "apk info"}, 31 | {"name": "pkgin", "command": "pkgin list"}, 32 | {"name": "cygwin", "command": "cygcheck -cd"}, 33 | ] 34 | -------------------------------------------------------------------------------- /src/sysinfo/resolution/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | 4 | platform_info = global_vars.get(["platform"])[0] 5 | sys_name = platform_info["name"] 6 | 7 | 8 | def get() -> dict[str, str]: 9 | # if sys_name == "Linux": 10 | # from .linux import resolution 11 | if sys_name == "Windows": 12 | from .windows import resolution 13 | elif sys_name == "Linux": 14 | from .linux import resolution 15 | elif sys_name == "Darwin": 16 | from .macos import resolution 17 | else: 18 | resolution = "" 19 | 20 | # going to change to regex 21 | # def strip(name): 22 | # for info in unwanted: 23 | # name = name.replace(info, "") 24 | # name = name.strip() 25 | # return name 26 | 27 | return {"Resolution": resolution.strip()} 28 | -------------------------------------------------------------------------------- /src/sysinfo/resolution/linux.py: -------------------------------------------------------------------------------- 1 | from ...tools.command import RunCommand 2 | 3 | 4 | def get_from_xrandr() -> str: 5 | try: 6 | xrandr = RunCommand("xrandr --nograb --current").readlines() 7 | for line in xrandr: 8 | if "minimum" and "current" and "maximum" in line: 9 | resolution = line.split("current", 1)[1].split(",", 1)[0] 10 | return resolution.replace(" ", "") 11 | except (AttributeError, TypeError): 12 | pass 13 | return "" 14 | 15 | 16 | def get_from_xwininfo() -> str: 17 | from ...tools import parse_info 18 | 19 | try: 20 | xwininfo: str = RunCommand("xwininfo -root").read() 21 | xwininfo: dict["str", "str"] = parse_info( 22 | xwininfo, {"Width": "width", "Height": "height"}, ":" 23 | ) 24 | if xwininfo["width"] and xwininfo["height"] != "": 25 | return f"{xwininfo['width']}x{xwininfo['height']}" 26 | except (AttributeError, TypeError): 27 | pass 28 | return "" 29 | 30 | 31 | def get_from_drm() -> str: 32 | try: 33 | drm_dir = "/sys/class/drm" 34 | from os import scandir 35 | 36 | for dir in scandir(drm_dir): 37 | if dir.is_dir(): 38 | resolution = open(f"{dir.path}/modes").readline() 39 | return resolution 40 | except FileNotFoundError: 41 | pass 42 | return "" 43 | 44 | 45 | for method in [get_from_xrandr, get_from_xwininfo, get_from_drm]: 46 | resolution = method() 47 | if resolution: 48 | break 49 | -------------------------------------------------------------------------------- /src/sysinfo/resolution/macos.py: -------------------------------------------------------------------------------- 1 | from ...tools.command import RunCommand 2 | from ...tools.parse_info import parser 3 | 4 | resolution = parser( 5 | RunCommand("system_profiler SPDisplaysDataType").read(), {"Resolution": "r"}, ":" 6 | )["r"] 7 | horizontal = resolution.split()[0] 8 | vertical = resolution.split()[2] 9 | resolution = f"{horizontal}x{vertical}" 10 | -------------------------------------------------------------------------------- /src/sysinfo/resolution/windows.py: -------------------------------------------------------------------------------- 1 | from ...tools import get_wmic 2 | 3 | resolution_horizontal = get_wmic( 4 | "path Win32_VideoController get CurrentHorizontalResolution" 5 | ).strip() 6 | resolution_vertical = get_wmic( 7 | "path Win32_VideoController get CurrentVerticalResolution" 8 | ).strip() 9 | 10 | resolution = f"{resolution_horizontal}x{resolution_vertical}" 11 | -------------------------------------------------------------------------------- /src/sysinfo/shell/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | 4 | sys_type = global_vars.get(["platform"])[0]["type"] 5 | 6 | 7 | def get() -> dict[str, str]: 8 | if sys_type == "posix": 9 | from .posix import shell_name, shell_ver 10 | elif sys_type == "nt": 11 | from .windows import shell_name, shell_ver 12 | else: 13 | shell_name, shell_ver = "", "" 14 | 15 | return {'Shell': f"{shell_name} {shell_ver}".strip()} 16 | -------------------------------------------------------------------------------- /src/sysinfo/shell/posix.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ...tools.command import RunCommand 3 | 4 | shell_name = os.getenv("SHELL").split("/")[-1].strip() 5 | 6 | if shell_name in ["bash", "zsh"]: 7 | shell_ver = RunCommand(f"{shell_name} -c 'echo ${shell_name.upper()}_VERSION'").read() 8 | else: 9 | shell_ver = "" 10 | -------------------------------------------------------------------------------- /src/sysinfo/shell/windows.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...tools import get_parents, RunCommand 3 | 4 | shell_name, shell_ver = "", "" 5 | 6 | for name in get_parents(): 7 | if name != "python.exe": 8 | shell_name = name.replace(".exe", "") 9 | break 10 | 11 | if shell_name == "cmd": 12 | shell_ver = ( 13 | RunCommand("cmd -ver").readline().split("[")[1].split()[-1].strip("]").strip() 14 | ) 15 | 16 | elif shell_name == ("powershell" or "pwsh"): 17 | command = "pwsh -Command" if shell_name == "pwsh" else "powershell" 18 | shell_ver = ".".join( 19 | [ 20 | s 21 | for s in ( 22 | RunCommand(f"{command} $PSVersionTable.PSVersion") 23 | .read() 24 | .split("\n")[2] 25 | .split() 26 | ) 27 | if s 28 | ] 29 | ) 30 | shell_ver = shell_ver.replace(".preview", "-preview") 31 | 32 | elif shell_name == "bash": 33 | bash_path = "C:\\Program Files\\Git\\bin\\bash.exe" 34 | from os import getenv 35 | 36 | for path in getenv("PATH").split(";"): 37 | if path.endswith("Git\\cmd"): 38 | bash_path = path.replace("cmd", "bin\\bash.exe") 39 | break 40 | shell_ver = RunCommand(f"'{bash_path}' -c 'echo $BASH_VERSION'").read().strip() 41 | -------------------------------------------------------------------------------- /src/sysinfo/system_theme/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import os 3 | from .gtk2 import gtk2 4 | from .gtk3 import gtk3 5 | from .qt import qt 6 | 7 | 8 | def read_cursor(theme_dict: dict[str, str]) -> str: 9 | env = os.getenv("XCURSOR_THEME") 10 | if env and len(env) != 0: 11 | return env 12 | for gtk in ["GTK2", "GTK3"]: 13 | try: 14 | result = theme_dict[gtk]["cursor"] 15 | if result: 16 | return result 17 | except KeyError: 18 | pass 19 | return "" 20 | 21 | 22 | def read(theme_type: str, theme_dict: dict[str, str]) -> str: 23 | result = "" 24 | result_dict = {} 25 | for t in theme_dict.keys(): 26 | if theme_type in theme_dict[t] and theme_dict[t][theme_type].strip() != "": 27 | result_dict[t] = theme_dict[t][theme_type].strip() 28 | # sort to prepare for pretty() and make it more pleasant 29 | result_dict = dict(sorted(result_dict.items())) 30 | while result_dict != {}: 31 | next_value = result_dict.get(next(iter(result_dict))) 32 | same_list = [i for i in result_dict if result_dict[i] == next_value] 33 | tag = "/".join(same_list) 34 | [result_dict.pop(key) for key in same_list] 35 | result += f"{next_value} [{tag}] " 36 | return pretty(result.strip()) 37 | 38 | 39 | def pretty(result: str) -> str: 40 | replace_dict = {"GTK2/GTK3": "GTK2/3"} 41 | for key, val in replace_dict.items(): 42 | result = result.replace(key, val) 43 | return result.strip() 44 | 45 | 46 | theme_dict = {"GTK2": gtk2, "GTK3": gtk3, "Qt": qt} 47 | 48 | 49 | def get() -> dict[str, str]: 50 | return { 51 | "Theme": read("theme", theme_dict), 52 | "Icons": read("icons", theme_dict), 53 | "Cursor": read_cursor(theme_dict), 54 | } 55 | -------------------------------------------------------------------------------- /src/sysinfo/system_theme/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import configparser 3 | 4 | config = configparser.ConfigParser(allow_no_value=True, strict=False) 5 | home = os.path.expanduser("~") -------------------------------------------------------------------------------- /src/sysinfo/system_theme/gtk2.py: -------------------------------------------------------------------------------- 1 | import os 2 | from .base import config, home 3 | 4 | 5 | def read_gtk2() -> str: 6 | gtk2 = {} 7 | env = os.getenv("GTK2_RC_FILES") 8 | if env and len(env) != 0: 9 | files = env.split(":") 10 | else: 11 | files = [f"{home}/.gtkrc-2.0"] 12 | for file in files: 13 | if os.path.exists(file): 14 | try: 15 | # add a [top] to make it readable 16 | config.read_string("[top]\n" + open(file).read()) 17 | gtk2["theme"] = config["top"]["gtk-theme-name"].strip('"') 18 | gtk2["icons"] = config["top"]["gtk-icon-theme-name"].strip('"') 19 | gtk2["cursor"] = config["top"]["gtk-cursor-theme-name"].strip('"') 20 | except: 21 | pass 22 | return gtk2 23 | 24 | 25 | gtk2 = read_gtk2() 26 | -------------------------------------------------------------------------------- /src/sysinfo/system_theme/gtk3.py: -------------------------------------------------------------------------------- 1 | import os 2 | from .base import config, home 3 | from ...global_vars import merge 4 | from ...tools.command import RunCommand 5 | 6 | gtk3 = {} 7 | 8 | 9 | def read_from_config() -> None: 10 | 11 | file = f"{home}/.config/gtk-3.0/settings.ini" 12 | if os.path.exists(file): 13 | try: 14 | config.read(file) 15 | merge( 16 | { 17 | "theme": config["Settings"]["gtk-theme-name"], 18 | "icons": config["Settings"]["gtk-icon-theme-name"], 19 | "cursor": config["Settings"]["gtk-cursor-theme-name"], 20 | }, 21 | gtk3, 22 | ) 23 | except: 24 | pass 25 | 26 | 27 | def get_gsettings_function(): 28 | de_list = ["cinnamon", "gnome", "mate"] 29 | failed_list = [] 30 | 31 | def get_gsettings_on_de(key): 32 | for de in de_list: 33 | if de in failed_list: 34 | continue 35 | value = RunCommand(f"gsettings get org.{de}.desktop.interface {key}").read() 36 | if value != "": 37 | return value.strip("' \n") 38 | else: 39 | failed_list.append(de) 40 | 41 | return get_gsettings_on_de 42 | 43 | 44 | def read_from_gsettings(): 45 | get_gsettings = get_gsettings_function() 46 | merge( 47 | { 48 | "theme": get_gsettings("gtk-theme"), 49 | "icons": get_gsettings("icon-theme"), 50 | "cursor": get_gsettings("cursor-theme"), 51 | }, 52 | gtk3, 53 | ) 54 | 55 | 56 | for method in [read_from_config, read_from_gsettings]: 57 | if gtk3 != {} and "" not in gtk3.values(): 58 | break 59 | method() 60 | -------------------------------------------------------------------------------- /src/sysinfo/system_theme/qt.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from os import path 3 | from .base import config, home 4 | from ...tools.command import RunCommand 5 | 6 | 7 | def read_kde_qt() -> dict[str, str]: 8 | qt = {} 9 | filepaths = [f"{home}/.config/"] 10 | filename = "kdeglobals" 11 | commands = ["kf5-config", "kde4-config", "kde-config"] 12 | for command in commands: 13 | filepaths_raw = RunCommand(f"{command} --path config").read() 14 | if filepaths_raw: 15 | filepaths = filepaths_raw.strip().split(":") 16 | break 17 | filepaths.reverse() 18 | for filepath in filepaths: 19 | file = filepath + filename 20 | if path.exists(file): 21 | try: 22 | config.read(file) 23 | qt["theme"] = config["KDE"]["widgetStyle"] 24 | qt["icons"] = config["Icons"]["Theme"] 25 | except: 26 | pass 27 | return qt 28 | 29 | 30 | qt = read_kde_qt() 31 | -------------------------------------------------------------------------------- /src/sysinfo/terminal/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .font import get_font_all 3 | from ... import global_vars 4 | 5 | 6 | def get() -> dict[str, str]: 7 | from ...tools import get_parents 8 | from .corrections import correction_dict 9 | 10 | sys_name = global_vars.get(["platform"])[0]["name"] 11 | 12 | result = {} 13 | 14 | for name in get_parents(): 15 | name = name.replace(".exe", "") 16 | if not ( 17 | name.endswith("sh") 18 | or name.endswith("shell") 19 | or name.startswith("python") 20 | or name in ["nu", "su", "sudo", "doas", "screen", "hyfetch", "tmux", "cmd"] 21 | ): 22 | if sys_name == "Darwin" and name == "Terminal": 23 | name = "Apple Terminal" 24 | if name in correction_dict.keys(): 25 | result["Terminal"] = correction_dict[name] 26 | elif name == "login": 27 | # macos's default terminal starts a progress called login 28 | if sys_name == "Darwin": 29 | continue 30 | from os import getenv 31 | 32 | result["Terminal"] = f"tty{getenv('XDG_VTNR')}" 33 | else: 34 | result["Terminal"] = name 35 | break 36 | 37 | result["Terminal Font"] = get_font_all(name) 38 | 39 | return result 40 | -------------------------------------------------------------------------------- /src/sysinfo/terminal/corrections.py: -------------------------------------------------------------------------------- 1 | correction_dict = { 2 | "gnome-terminal-": "gnome-terminal", 3 | "cutefish-termin": "cutefish-terminal", 4 | "urxvtd": "urxvt", 5 | "nvim": "Neovim Terminal", 6 | "NeoVimServer": "VimR Terminal", 7 | "explorer": "Windows Console", 8 | } 9 | -------------------------------------------------------------------------------- /src/sysinfo/terminal/font/__init__.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | 3 | 4 | def get_font_all(name: str) -> str: 5 | try: 6 | name = name.strip("-").replace("-", "_").replace(" ", "").lower() 7 | get_font = getattr(import_module(f".{name}", package=__name__), "get_font") 8 | 9 | return get_font().strip() 10 | except (ModuleNotFoundError, AttributeError): 11 | return "" 12 | -------------------------------------------------------------------------------- /src/sysinfo/terminal/font/appleterminal.py: -------------------------------------------------------------------------------- 1 | from plistlib import load, loads 2 | from os.path import expanduser 3 | 4 | 5 | def get_font() -> str: 6 | config_path = f"{expanduser('~')}/Library/Preferences/com.apple.Terminal.plist" 7 | with open(config_path, "rb") as config_file: 8 | settings: dict = load(config_file) 9 | default_config = settings["Startup Window Settings"] 10 | font_config = loads(settings["Window Settings"][default_config]["Font"]) 11 | font_name = font_config["$objects"][2] 12 | font_size = int(font_config["$objects"][1]["NSSize"]) 13 | return f"{font_name} {font_size}" 14 | -------------------------------------------------------------------------------- /src/sysinfo/terminal/font/cutefish_termin.py: -------------------------------------------------------------------------------- 1 | from os.path import expanduser 2 | from ....tools import parse_info 3 | 4 | 5 | def get_font() -> str: 6 | home_path = expanduser("~") 7 | try: 8 | config = open(f"{home_path}/.config/cutefishos/cutefish-terminal.conf").read() 9 | config: dict[str, str] = parse_info.parser( 10 | config, {"fontName": "font_name", "fontPointSize": "font_size"}, "=" 11 | ) 12 | if config["font_name"]: 13 | return f"{config['font_name']} {config['font_size']}" 14 | except (FileNotFoundError, AttributeError): 15 | pass 16 | return "" 17 | -------------------------------------------------------------------------------- /src/sysinfo/terminal/font/gnome_terminal.py: -------------------------------------------------------------------------------- 1 | from ....tools.command import RunCommand 2 | 3 | 4 | def get_font() -> str: 5 | default_profile = ( 6 | RunCommand("gsettings get org.gnome.Terminal.ProfilesList default") 7 | .read().strip("' \n") 8 | ) 9 | font = ( 10 | RunCommand(f"gsettings get org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:{default_profile}/ font") 11 | .read().strip("' \n") 12 | ) 13 | return font 14 | -------------------------------------------------------------------------------- /src/sysinfo/terminal/font/konsole.py: -------------------------------------------------------------------------------- 1 | from os.path import expanduser 2 | from ....tools import parse_info 3 | 4 | 5 | def get_font() -> str: 6 | home_path = expanduser("~") 7 | try: 8 | konsolerc = open(f"{home_path}/.config/konsolerc").read() 9 | default_profile_name = parse_info.parser(konsolerc, {"DefaultProfile": "f"}, "=")["f"] 10 | default_profile = open( 11 | f"{home_path}/.local/share/konsole/{default_profile_name}" 12 | ).read() 13 | font_attr = parse_info.parser(default_profile, {"Font": "font"}, "=")["font"].split( 14 | "," 15 | ) 16 | return f"{font_attr[0]} {font_attr[1]}" 17 | 18 | except (FileNotFoundError, AttributeError): 19 | pass 20 | return "" -------------------------------------------------------------------------------- /src/sysinfo/terminal/font/windowsterminal.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | from pathlib import Path 3 | from json import loads 4 | from ....tools import get_parents 5 | 6 | 7 | def get_font() -> str: 8 | font_name = "Cascadia Mono" 9 | font_size = "12" 10 | try: 11 | file = ( 12 | list( 13 | Path(f"{getenv('LocalAppData')}\\Packages").glob( 14 | "Microsoft.WindowsTerminal_*\\LocalState\\settings.json" 15 | ) 16 | )[0] 17 | .open() 18 | .read() 19 | ) 20 | config = loads(file) 21 | 22 | parents_list = get_parents() 23 | for current_name, next_name in zip(parents_list, parents_list[1:]): 24 | if next_name == "WindowsTerminal.exe": 25 | start_shell_name = current_name 26 | break 27 | 28 | for section in config["profiles"]["list"]: 29 | if "commandline" in section.keys() and section["commandline"].endswith( 30 | start_shell_name 31 | ): 32 | if "font" in section.keys(): 33 | font = section["font"] 34 | if "face" in font: 35 | font_name = font["face"] 36 | if "size" in font: 37 | font_size = font["size"] 38 | break 39 | except: 40 | pass 41 | return f"{font_name} {font_size}" 42 | -------------------------------------------------------------------------------- /src/sysinfo/terminal/font/xfce4_terminal.py: -------------------------------------------------------------------------------- 1 | from os.path import expanduser 2 | from ....tools import parse_info 3 | 4 | 5 | def get_font() -> str: 6 | xfce4_config = f"{expanduser('~')}/.config/xfce4/terminal/terminalrc" 7 | needs = {"FontName": "font_name", "FontUseSystem": "use_system"} 8 | try: 9 | results = parse_info.parser(open(xfce4_config).read(), needs, "=") 10 | if results["use_system"] == "TRUE": 11 | from ....tools.command import run_command 12 | 13 | font_name = ( 14 | run_command("gsettings get org.gnome.desktop.interface monospace-font-name") 15 | .read() 16 | .strip("'") 17 | ) 18 | if font_name: 19 | return font_name 20 | 21 | else: 22 | return results["font_name"] 23 | except FileNotFoundError: 24 | pass -------------------------------------------------------------------------------- /src/sysinfo/uptime/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ... import global_vars 3 | 4 | sys_name = global_vars.get(["platform"])[0]["name"] 5 | 6 | 7 | def process_time(seconds: int) -> str: 8 | time_str = "" 9 | time_format = {"day": 86400, "hour": 3600, "min": 60} 10 | for time_type in time_format.keys(): 11 | count = str(int(seconds / time_format[time_type])) 12 | if count == "1": 13 | time_str += f"{count} {time_type}, " 14 | elif count != "0": 15 | time_str += f"{count} {time_type}s, " 16 | seconds = seconds % time_format[time_type] 17 | return time_str.strip(", ") 18 | 19 | 20 | def get() -> dict[str, str]: 21 | if sys_name == "Linux": 22 | from .posix import uptime_seconds 23 | elif sys_name == "Windows": 24 | from .windows import uptime_seconds 25 | elif sys_name == "Darwin": 26 | from .macos import uptime_seconds 27 | else: 28 | uptime_seconds = 0 29 | return {"Uptime": process_time(uptime_seconds)} 30 | -------------------------------------------------------------------------------- /src/sysinfo/uptime/macos.py: -------------------------------------------------------------------------------- 1 | from ...tools.command import RunCommand 2 | from time import time 3 | 4 | uptime_seconds = int(time()) - int( 5 | RunCommand("/usr/sbin/sysctl -n kern.boottime").readline().split()[3].strip(",") 6 | ) 7 | -------------------------------------------------------------------------------- /src/sysinfo/uptime/posix.py: -------------------------------------------------------------------------------- 1 | uptime_seconds = int(float(open("/proc/uptime").read().split()[0])) 2 | -------------------------------------------------------------------------------- /src/sysinfo/uptime/windows.py: -------------------------------------------------------------------------------- 1 | from ctypes import windll 2 | 3 | uptime = windll.kernel32.GetTickCount64() 4 | uptime_seconds = int(str(uptime)[:-3]) 5 | -------------------------------------------------------------------------------- /src/sysinfo/username.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from os import getlogin 3 | from platform import node 4 | 5 | 6 | def get() -> dict[str, str]: 7 | try: 8 | username = getlogin() 9 | except FileNotFoundError: 10 | from getpass import getuser 11 | 12 | username = getuser() 13 | return {"USERNAME": username, "HOSTNAME": node()} 14 | -------------------------------------------------------------------------------- /src/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__ file 2 | from .parse_proc import parse_proc_info as parse_proc 3 | from .get_parents import get as get_parents 4 | from .get_wmic_value import process as get_wmic 5 | from .command import RunCommand 6 | 7 | __all__ = [parse_proc, get_parents, get_wmic, RunCommand] 8 | -------------------------------------------------------------------------------- /src/tools/command.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from locale import getpreferredencoding 3 | import subprocess 4 | from shlex import split 5 | 6 | 7 | class RunCommand: 8 | def __init__(self, command: str, error=False) -> None: 9 | encoding = getpreferredencoding() 10 | try: 11 | result_object = subprocess.run( 12 | split(command), 13 | stdin=subprocess.PIPE, 14 | stdout=subprocess.PIPE, 15 | stderr=subprocess.STDOUT, 16 | ) 17 | if (result_object.returncode == 0) is not error: 18 | self.result = result_object.stdout.decode(encoding).strip() 19 | else: 20 | self.result = "" 21 | except (FileNotFoundError, AttributeError): 22 | self.result = "" 23 | 24 | def read(self) -> str: 25 | return self.result 26 | 27 | def readline(self) -> str: 28 | return self.result.split("\n", 1)[0] 29 | 30 | def readlines(self) -> list[str]: 31 | return self.result.split("\n") 32 | -------------------------------------------------------------------------------- /src/tools/get_parents/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from os import getpid, getppid 3 | from ... import global_vars 4 | 5 | 6 | def get() -> list[str]: 7 | sys_name = global_vars.get(["platform"])[0]["name"] 8 | if sys_name == "Linux": 9 | from .linux import get_info 10 | elif sys_name == "Windows": 11 | from .windows import get_info 12 | elif sys_name == "Darwin": 13 | from .macos import get_info 14 | try: 15 | return global_vars.get(["parent_list"])[0] 16 | except: 17 | pass 18 | try: 19 | pid = getppid() 20 | except: 21 | pid = getpid() 22 | parent_list = [] 23 | while pid != "0": 24 | name, parent = get_info(pid) 25 | if not name: 26 | break 27 | parent_list.append(name) 28 | pid = parent 29 | global_vars.set({"parent_list": parent_list}) 30 | return parent_list 31 | -------------------------------------------------------------------------------- /src/tools/get_parents/linux.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .. import parse_proc 3 | 4 | def get_info(id: str) -> tuple[str]: 5 | info_dict = parse_proc(f"/proc/{id}/status")[0] 6 | name = info_dict["Name"] 7 | parent = info_dict["PPid"] 8 | return name, parent -------------------------------------------------------------------------------- /src/tools/get_parents/macos.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..command import RunCommand 3 | 4 | 5 | def get_info(id: str) -> tuple[str]: 6 | line = RunCommand(f"ps -o ppid,command {id}").readlines()[1].strip() 7 | parent = line.split()[0] 8 | name = line.split()[1] 9 | if "/" in name: 10 | name = name.split("/")[-1] 11 | return name, parent 12 | -------------------------------------------------------------------------------- /src/tools/get_parents/windows.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..parse_info import parser 3 | from ..command import RunCommand 4 | 5 | 6 | def get_info(id: str) -> tuple[str]: 7 | requirements = {"ParentProcessId": "parent", "Name": "name"} 8 | outputs: str = RunCommand( 9 | f"powershell Get-WmiObject Win32_Process -Filter ProcessId={id}" 10 | ).read() 11 | outputs: dict[str, str] = parser(outputs, requirements, ":") 12 | return outputs["name"], outputs["parent"] 13 | -------------------------------------------------------------------------------- /src/tools/get_wmic_value.py: -------------------------------------------------------------------------------- 1 | from .command import RunCommand 2 | 3 | 4 | def process(command: str) -> str: 5 | result_raw = RunCommand(f"wmic {command}").read() 6 | result = "\n".join( 7 | [line.rstrip() for line in result_raw.splitlines() if line.strip()] 8 | ) 9 | return result.splitlines()[1] -------------------------------------------------------------------------------- /src/tools/parse_info.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | # It is used to deal with this kind of strings: 4 | 5 | # Name: Arch Linux 6 | # Arch: x86_64 7 | # Version: Rolling 8 | 9 | # or 10 | 11 | # Name=Arch Linux 12 | # Arch=x86_64 13 | # Version=Rolling 14 | 15 | # And if you need Name and Arch: 16 | # needs = {"Name":"name","Arch":"arch"} 17 | # It will output {"name":"Arch Linux","arch":"x86_64"} 18 | 19 | 20 | def parser(inputs: str, needs: dict[str, str], separator: str) -> dict[str, str]: 21 | satisfied = {key: "" for key in needs.values()} 22 | for line in inputs.splitlines(): 23 | for key, val in needs.items(): 24 | if line.strip().startswith(key): 25 | line_list = line.split(separator, 1) 26 | if line_list[0].strip() == key: 27 | satisfied[val] = line_list[1].strip() 28 | needs.pop(key) 29 | break 30 | return satisfied 31 | -------------------------------------------------------------------------------- /src/tools/parse_proc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | 6 | def parse_proc_info(filepath: str | Path) -> list[dict[str, str]]: 7 | info_list = [] 8 | for section in Path(filepath).read_text().split('\n\n'): 9 | info: list[list[str]] = [ln.split(':', 1) for ln in section.splitlines()] 10 | info: dict[str, str] = {s[0].strip('\t '): s[1].strip() for s in info if len(s) == 2} 11 | if info: 12 | info_list.append(info) 13 | return info_list 14 | --------------------------------------------------------------------------------