File size: 5,911 Bytes
2695555 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | """openTARS 4.5 Endurance: onde o programa está rodando.
Parte pura (sem rede, sem tela): diz se é WSL, se há interface gráfica, e
traduz caminhos do Windows. Quem usa é o tars.py (modo sem interface, abrir
links pelo Windows, achar o Ollama do Windows a partir do WSL).
Três situações que importam:
* área de trabalho Linux (X11 ou Wayland): tudo como sempre;
* WSL com WSLg (o Windows 11 mostra janelas Linux): há DISPLAY, a janela
abre, mas não existe uma "área de trabalho" Linux pra controlar;
* sem interface (servidor, SSH, WSL sem WSLg, contêiner): só conversa no
terminal, comandos, arquivos e a IA; nada de mouse, teclado nem tela.
"""
import os
import re
from pathlib import Path
import tars_plataforma as plataforma
def _ler(caminho):
try:
return Path(caminho).read_text(encoding="utf-8", errors="replace")
except Exception:
return ""
def versao_wsl(env=None, ler=None):
"""1 ou 2 dentro do WSL; None fora dele."""
env = os.environ if env is None else env
ler = ler or _ler
release = ler("/proc/sys/kernel/osrelease").lower()
versao = ler("/proc/version").lower()
dentro = bool(env.get("WSL_DISTRO_NAME") or env.get("WSL_INTEROP")) or "microsoft" in release or "microsoft" in versao
if not dentro:
return None
return 2 if "wsl2" in release or "wsl2" in versao or env.get("WSL_INTEROP") else 1
def e_wsl(env=None, ler=None):
return versao_wsl(env, ler) is not None
def tem_tela(env=None):
env = os.environ if env is None else env
# Windows e macOS sempre têm a área de trabalho do sistema; DISPLAY/WAYLAND_DISPLAY são coisa do Linux
return plataforma.sistema() != "linux" or bool(env.get("DISPLAY") or env.get("WAYLAND_DISPLAY"))
def forcado_sem_interface(env=None):
"""TARS_SEM_INTERFACE=1: trata a máquina como sem tela mesmo com DISPLAY
(servidor com X remoto, quem quer só o terminal)."""
env = os.environ if env is None else env
return str(env.get("TARS_SEM_INTERFACE", "")).lower() in ("1", "on", "sim", "yes", "true")
def detectar(env=None, ler=None):
"""{'wsl': 1|2|None, 'wslg': bool, 'tela': bool, 'ssh': bool, 'sem_interface': bool,
'tipo': 'desktop'|'wslg'|'sem_tela', 'rotulo': str (em inglês)}"""
env = os.environ if env is None else env
wsl = versao_wsl(env, ler)
sem = forcado_sem_interface(env) or not tem_tela(env)
ssh = bool(env.get("SSH_CONNECTION") or env.get("SSH_TTY"))
wslg = bool(wsl) and not sem and (bool(env.get("WAYLAND_DISPLAY")) or bool(env.get("DISPLAY")))
tipo = "sem_tela" if sem else ("wslg" if wslg else "desktop")
if sem:
rotulo = f"WSL{wsl} (no graphical session)" if wsl else ("SSH (no graphical session)" if ssh else "no graphical session")
elif wslg:
rotulo = f"WSL{wsl} with WSLg"
else:
rotulo = "desktop"
return {"wsl": wsl, "wslg": wslg, "tela": not sem, "ssh": ssh, "sem_interface": sem, "tipo": tipo, "rotulo": rotulo}
# ------------------------------------------------------------
# Caminhos do Windows vistos de dentro do WSL
# ------------------------------------------------------------
_UNIDADE = re.compile(r"^([A-Za-z]):[\\/]*(.*)$", re.DOTALL)
_REDE_WSL = re.compile(r"^[\\/]{2}(?:wsl\$|wsl\.localhost)[\\/]+[^\\/]+[\\/]*(.*)$", re.IGNORECASE | re.DOTALL)
def parece_caminho_windows(texto):
t = (texto or "").strip()
return bool(_UNIDADE.match(t) or _REDE_WSL.match(t))
def caminho_do_windows(texto):
"""'C:\\Users\\ana\\a b.txt' -> '/mnt/c/Users/ana/a b.txt';
'\\\\wsl$\\Ubuntu\\home\\ana\\x' -> '/home/ana/x'. Texto que não parece
caminho do Windows volta como veio."""
t = (texto or "").strip()
achado = _REDE_WSL.match(t)
if achado:
return "/" + achado.group(1).replace("\\", "/").strip("/")
achado = _UNIDADE.match(t)
if achado:
resto = achado.group(2).replace("\\", "/").strip("/")
base = f"/mnt/{achado.group(1).lower()}"
return f"{base}/{resto}" if resto else base
return t
# ------------------------------------------------------------
# Ollama do Windows, visto de dentro do WSL2
# ------------------------------------------------------------
def gateway_padrao(texto_route):
"""IP do gateway padrão a partir do conteúdo de /proc/net/route."""
for linha in (texto_route or "").splitlines()[1:]:
campos = linha.split()
if len(campos) >= 3 and campos[1] == "00000000":
try:
n = int(campos[2], 16)
except ValueError:
continue
if n:
return ".".join(str((n >> (8 * i)) & 255) for i in range(4))
return None
def servidor_dns(texto_resolv):
for linha in (texto_resolv or "").splitlines():
partes = linha.split()
if len(partes) >= 2 and partes[0] == "nameserver" and re.fullmatch(r"\d+\.\d+\.\d+\.\d+", partes[1]):
return partes[1]
return None
def candidatos_ollama_wsl(porta=11434, ler=None):
"""Endereços onde o Ollama do Windows pode estar, vistos do WSL2 (além
do 127.0.0.1 de sempre): o IP do Windows aparece como servidor DNS e
como gateway. Sem repetição, na ordem em que vale tentar."""
ler = ler or _ler
achados = []
for ip in (servidor_dns(ler("/etc/resolv.conf")), gateway_padrao(ler("/proc/net/route"))):
if ip and ip not in achados and not ip.startswith("127."):
achados.append(ip)
return [f"http://{ip}:{porta}" for ip in achados]
def abridor_de_links_do_windows(existe=None):
"""Como abrir um link no navegador do Windows a partir do WSL sem
interface: ['wslview'] (pacote wslu) ou ['explorer.exe']. None se não houver."""
import shutil
existe = existe or shutil.which
for comando in (["wslview"], ["explorer.exe"]):
if existe(comando[0]):
return comando
return None
|