mirror of
https://github.com/Alexander-D-Karpov/netfetch.git
synced 2026-03-16 22:07:03 +03:00
384 lines
9.5 KiB
Go
384 lines
9.5 KiB
Go
package collector
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func (c *Collector) collectTerminal() {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
c.info.Terminal = detectTerminal()
|
|
}
|
|
|
|
func detectTerminal() string {
|
|
if term := detectTerminalFromEnv(); term != "" {
|
|
return term
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
return detectTerminalWindows()
|
|
}
|
|
|
|
if runtime.GOOS == "darwin" {
|
|
if term := detectTerminalFromEnv(); term != "" {
|
|
return term
|
|
}
|
|
}
|
|
|
|
if term := detectTerminalFromProcessTree(); term != "" {
|
|
return term
|
|
}
|
|
|
|
return "Unknown"
|
|
}
|
|
|
|
func detectTerminalFromEnv() string {
|
|
if term := os.Getenv("TERM_PROGRAM"); term != "" {
|
|
version := os.Getenv("TERM_PROGRAM_VERSION")
|
|
termName := normalizeTerminalName(term)
|
|
if version != "" {
|
|
return termName + " " + version
|
|
}
|
|
return termName
|
|
}
|
|
|
|
terminalEnvVars := map[string]string{
|
|
"KITTY_PID": "kitty",
|
|
"KITTY_WINDOW_ID": "kitty",
|
|
"ALACRITTY_SOCKET": "Alacritty",
|
|
"ALACRITTY_LOG": "Alacritty",
|
|
"ALACRITTY_WINDOW_ID": "Alacritty",
|
|
"WEZTERM_EXECUTABLE": "WezTerm",
|
|
"WEZTERM_PANE": "WezTerm",
|
|
"WT_SESSION": "Windows Terminal",
|
|
"WT_PROFILE_ID": "Windows Terminal",
|
|
"KONSOLE_VERSION": "Konsole",
|
|
"KONSOLE_DBUS_SESSION": "Konsole",
|
|
"GNOME_TERMINAL_SERVICE": "GNOME Terminal",
|
|
"GNOME_TERMINAL_SCREEN": "GNOME Terminal",
|
|
"TILIX_ID": "Tilix",
|
|
"TERMINATOR_UUID": "Terminator",
|
|
"TERMINATOR_DBUS_NAME": "Terminator",
|
|
"ITERM_SESSION_ID": "iTerm2",
|
|
"ITERM_PROFILE": "iTerm2",
|
|
"GHOSTTY_RESOURCES_DIR": "Ghostty",
|
|
"GHOSTTY_BIN_DIR": "Ghostty",
|
|
"TABBY_CONFIG_DIRECTORY": "Tabby",
|
|
"HYPER_CLI": "Hyper",
|
|
"TERMINUS_PLUGINS": "Terminus",
|
|
"VSCODE_INJECTION": "VS Code",
|
|
"VSCODE_GIT_IPC_HANDLE": "VS Code",
|
|
"TERMINAL_EMULATOR": "",
|
|
"COLORTERM": "",
|
|
}
|
|
|
|
for envVar, termName := range terminalEnvVars {
|
|
if value := os.Getenv(envVar); value != "" {
|
|
if termName != "" {
|
|
if envVar == "KONSOLE_VERSION" {
|
|
return "Konsole " + formatKonsoleVersion(value)
|
|
}
|
|
return termName
|
|
}
|
|
|
|
if envVar == "TERMINAL_EMULATOR" {
|
|
return normalizeTerminalName(value)
|
|
}
|
|
}
|
|
}
|
|
|
|
if colorterm := os.Getenv("COLORTERM"); colorterm != "" {
|
|
colortermMap := map[string]string{
|
|
"truecolor": "",
|
|
"24bit": "",
|
|
"gnome-terminal": "GNOME Terminal",
|
|
"xfce4-terminal": "Xfce Terminal",
|
|
"rxvt-xpm": "rxvt",
|
|
"rxvt": "rxvt",
|
|
}
|
|
|
|
if term, ok := colortermMap[colorterm]; ok && term != "" {
|
|
return term
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func formatKonsoleVersion(version string) string {
|
|
if len(version) == 6 {
|
|
year := version[0:2]
|
|
month := version[2:4]
|
|
patch := version[4:6]
|
|
return fmt.Sprintf("%s.%s.%s", year, month, patch)
|
|
}
|
|
return version
|
|
}
|
|
|
|
func normalizeTerminalName(name string) string {
|
|
terminalNames := map[string]string{
|
|
"apple_terminal": "Terminal.app",
|
|
"terminal.app": "Terminal.app",
|
|
"iterm.app": "iTerm2",
|
|
"iterm2": "iTerm2",
|
|
"hyper": "Hyper",
|
|
"vscode": "VS Code",
|
|
"code": "VS Code",
|
|
"visual studio code": "VS Code",
|
|
"gnome-terminal": "GNOME Terminal",
|
|
"gnome-terminal-server": "GNOME Terminal",
|
|
"xfce4-terminal": "Xfce Terminal",
|
|
"mate-terminal": "MATE Terminal",
|
|
"lxterminal": "LXTerminal",
|
|
"qterminal": "QTerminal",
|
|
"terminology": "Terminology",
|
|
"terminator": "Terminator",
|
|
"tilix": "Tilix",
|
|
"guake": "Guake",
|
|
"tilda": "Tilda",
|
|
"yakuake": "Yakuake",
|
|
"konsole": "Konsole",
|
|
"kitty": "kitty",
|
|
"alacritty": "Alacritty",
|
|
"wezterm": "WezTerm",
|
|
"wezterm-gui": "WezTerm",
|
|
"foot": "foot",
|
|
"st": "st",
|
|
"urxvt": "urxvt",
|
|
"rxvt-unicode": "urxvt",
|
|
"rxvt": "rxvt",
|
|
"xterm": "xterm",
|
|
"uxterm": "xterm",
|
|
"cool-retro-term": "cool-retro-term",
|
|
"termux": "Termux",
|
|
"contour": "Contour",
|
|
"rio": "Rio",
|
|
"warp": "Warp",
|
|
"extraterm": "Extraterm",
|
|
"black-box": "Black Box",
|
|
"ptyxis": "Ptyxis",
|
|
"cosmic-term": "COSMIC Terminal",
|
|
}
|
|
|
|
nameLower := strings.ToLower(name)
|
|
if normalized, ok := terminalNames[nameLower]; ok {
|
|
return normalized
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
func detectTerminalFromProcessTree() string {
|
|
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" && runtime.GOOS != "freebsd" {
|
|
return ""
|
|
}
|
|
|
|
skipProcesses := map[string]bool{
|
|
"login": true, "init": true, "systemd": true, "sshd": true, "ssh": true,
|
|
"tmux": true, "tmux: server": true, "screen": true, "zellij": true,
|
|
"sh": true, "bash": true, "zsh": true, "fish": true, "dash": true,
|
|
"ksh": true, "tcsh": true, "csh": true, "ash": true, "nu": true,
|
|
"elvish": true, "xonsh": true, "ion": true, "oil": true,
|
|
"su": true, "sudo": true, "doas": true, "pkexec": true,
|
|
"newgrp": true, "sg": true, "runuser": true,
|
|
"python": true, "python3": true, "node": true, "ruby": true, "perl": true,
|
|
}
|
|
|
|
terminalProcesses := map[string]string{
|
|
"alacritty": "Alacritty",
|
|
"kitty": "kitty",
|
|
"wezterm": "WezTerm",
|
|
"wezterm-gui": "WezTerm",
|
|
"gnome-terminal": "GNOME Terminal",
|
|
"gnome-terminal-": "GNOME Terminal",
|
|
"konsole": "Konsole",
|
|
"xfce4-terminal": "Xfce Terminal",
|
|
"xterm": "xterm",
|
|
"urxvt": "urxvt",
|
|
"rxvt": "rxvt",
|
|
"terminator": "Terminator",
|
|
"tilix": "Tilix",
|
|
"st": "st",
|
|
"cool-retro-term": "cool-retro-term",
|
|
"lxterminal": "LXTerminal",
|
|
"mate-terminal": "MATE Terminal",
|
|
"terminology": "Terminology",
|
|
"foot": "foot",
|
|
"footclient": "foot",
|
|
"contour": "Contour",
|
|
"qterminal": "QTerminal",
|
|
"guake": "Guake",
|
|
"tilda": "Tilda",
|
|
"yakuake": "Yakuake",
|
|
"hyper": "Hyper",
|
|
"Hyper": "Hyper",
|
|
"terminus": "Terminus",
|
|
"tabby": "Tabby",
|
|
"extraterm": "Extraterm",
|
|
"black-box": "Black Box",
|
|
"blackbox": "Black Box",
|
|
"ptyxis": "Ptyxis",
|
|
"cosmic-term": "COSMIC Terminal",
|
|
"rio": "Rio",
|
|
"ghostty": "Ghostty",
|
|
"code": "VS Code",
|
|
"code-oss": "VS Code",
|
|
"codium": "VSCodium",
|
|
"cursor": "Cursor",
|
|
"goland": "GoLand",
|
|
"idea": "IntelliJ IDEA",
|
|
"pycharm": "PyCharm",
|
|
"webstorm": "WebStorm",
|
|
"phpstorm": "PhpStorm",
|
|
"rubymine": "RubyMine",
|
|
"clion": "CLion",
|
|
"datagrip": "DataGrip",
|
|
"rider": "Rider",
|
|
"fleet": "Fleet",
|
|
"zed": "Zed",
|
|
"sublime_text": "Sublime Text",
|
|
"atom": "Atom",
|
|
"Terminal": "Terminal.app",
|
|
"iTerm2": "iTerm2",
|
|
"Apple_Terminal": "Terminal.app",
|
|
}
|
|
|
|
pid := os.Getppid()
|
|
visited := make(map[int]bool)
|
|
|
|
for i := 0; i < 30 && pid > 1; i++ {
|
|
if visited[pid] {
|
|
break
|
|
}
|
|
visited[pid] = true
|
|
|
|
procName := getProcessName(pid)
|
|
if procName == "" {
|
|
break
|
|
}
|
|
|
|
if skipProcesses[procName] {
|
|
pid = getParentPID(pid)
|
|
continue
|
|
}
|
|
|
|
if terminal, ok := terminalProcesses[procName]; ok {
|
|
return terminal
|
|
}
|
|
|
|
for prefix, terminal := range terminalProcesses {
|
|
if strings.HasPrefix(procName, prefix) {
|
|
return terminal
|
|
}
|
|
}
|
|
|
|
pid = getParentPID(pid)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func getProcessName(pid int) string {
|
|
if runtime.GOOS == "darwin" {
|
|
out, err := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "comm=").Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
|
|
commPath := fmt.Sprintf("/proc/%d/comm", pid)
|
|
data, err := os.ReadFile(commPath)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return strings.TrimSpace(string(data))
|
|
}
|
|
|
|
func getParentPID(pid int) int {
|
|
if runtime.GOOS == "darwin" {
|
|
out, err := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "ppid=").Output()
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
ppid, err := strconv.Atoi(strings.TrimSpace(string(out)))
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return ppid
|
|
}
|
|
|
|
statPath := fmt.Sprintf("/proc/%d/stat", pid)
|
|
data, err := os.ReadFile(statPath)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
statStr := string(data)
|
|
closeParen := strings.LastIndex(statStr, ")")
|
|
if closeParen == -1 || closeParen+2 >= len(statStr) {
|
|
return 0
|
|
}
|
|
|
|
fields := strings.Fields(statStr[closeParen+2:])
|
|
if len(fields) < 2 {
|
|
return 0
|
|
}
|
|
|
|
ppid, err := strconv.Atoi(fields[1])
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
return ppid
|
|
}
|
|
|
|
func detectTerminalWindows() string {
|
|
if os.Getenv("WT_SESSION") != "" {
|
|
return "Windows Terminal"
|
|
}
|
|
|
|
if os.Getenv("ConEmuPID") != "" {
|
|
return "ConEmu"
|
|
}
|
|
|
|
if os.Getenv("CMDER_ROOT") != "" {
|
|
return "Cmder"
|
|
}
|
|
|
|
if os.Getenv("ALACRITTY_SOCKET") != "" {
|
|
return "Alacritty"
|
|
}
|
|
|
|
if os.Getenv("WEZTERM_EXECUTABLE") != "" {
|
|
return "WezTerm"
|
|
}
|
|
|
|
if os.Getenv("TERMINUS_PLUGINS") != "" {
|
|
return "Terminus"
|
|
}
|
|
|
|
if os.Getenv("HYPER_CLI") != "" {
|
|
return "Hyper"
|
|
}
|
|
|
|
if comspec := os.Getenv("COMSPEC"); comspec != "" {
|
|
base := filepath.Base(comspec)
|
|
if strings.EqualFold(base, "powershell.exe") {
|
|
return "PowerShell"
|
|
}
|
|
if strings.EqualFold(base, "pwsh.exe") {
|
|
return "PowerShell Core"
|
|
}
|
|
}
|
|
|
|
return "cmd.exe"
|
|
}
|