Кэш опознания. _identities и _snapshots в AccountQuotaService живут в памяти по ключу «провайдер:слот» и при удалении ключа не чистились. Владелец удалил аккаунт Antigravity, завёл victor.trushenko@gmail.com, а в списке остался прежний trushenko.semya@gmail.com. Добавлен forget_profile, вызывается при удалении учётных данных и при перезаписи слота другим аккаунтом. Проверено: после сброса запись исчезает. NVIDIA. Проверка подключения получала каталог моделей успешно — то есть ключ рабочий и аккаунт опознан, — а затем делала пробный запрос к первой чат-модели каталога. Каталог NVIDIA общий, доступ к конкретной модели даётся по аккаунту, и ответ «Function ... Not found for account <id>» объявлялся провалом подключения. Теперь успешный список моделей считается доказательством работоспособности ключа, а неудачная проба возвращается примечанием с предложением выбрать доступную модель. 401 и 403 при этом по-прежнему означают отказ: ключ отвергнут. Различие поймал тест A54 на неверном ключе. Выход из программы. Неудачная остановка процессов отменяла выход целиком: владелец нажимал «закрыть» и оставался в работающем приложении. Теперь показывается предупреждение, а программа закрывается. 599 passed, ruff clean, релизный гейт 10/10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
456 lines
24 KiB
C#
456 lines
24 KiB
C#
using System;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Net;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Windows.Forms;
|
||
using Microsoft.Win32;
|
||
|
||
namespace HermesHub
|
||
{
|
||
public static class WebLauncher
|
||
{
|
||
private static Mutex instanceMutex;
|
||
|
||
[STAThread]
|
||
public static void Main(string[] args)
|
||
{
|
||
Application.EnableVisualStyles();
|
||
Application.SetCompatibleTextRenderingDefault(false);
|
||
|
||
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||
string hermesHome = Environment.GetEnvironmentVariable("HERMES_HOME");
|
||
if (string.IsNullOrEmpty(hermesHome))
|
||
{
|
||
hermesHome = Path.Combine(localAppData, "hermes");
|
||
}
|
||
|
||
// 1. Determine Web API Host and Port from hub_settings.json or default
|
||
string host = "127.0.0.1";
|
||
int port = 5800;
|
||
string settingsFile = Path.Combine(hermesHome, "hub_settings.json");
|
||
if (File.Exists(settingsFile))
|
||
{
|
||
try
|
||
{
|
||
string json = File.ReadAllText(settingsFile, Encoding.UTF8);
|
||
int pIdx = json.IndexOf("\"web_api_port\":", StringComparison.OrdinalIgnoreCase);
|
||
if (pIdx >= 0)
|
||
{
|
||
int colon = json.IndexOf(':', pIdx);
|
||
int comma = json.IndexOfAny(new char[] { ',', '}', '\r', '\n' }, colon + 1);
|
||
if (colon >= 0 && comma > colon)
|
||
{
|
||
string pStr = json.Substring(colon + 1, comma - colon - 1).Trim();
|
||
int parsedPort;
|
||
if (int.TryParse(pStr, out parsedPort) && parsedPort > 0 && parsedPort < 65536)
|
||
{
|
||
port = parsedPort;
|
||
}
|
||
}
|
||
}
|
||
int hIdx = json.IndexOf("\"web_api_host\":", StringComparison.OrdinalIgnoreCase);
|
||
if (hIdx >= 0)
|
||
{
|
||
int q1 = json.IndexOf('"', hIdx + 15);
|
||
int q2 = json.IndexOf('"', q1 + 1);
|
||
if (q1 >= 0 && q2 > q1)
|
||
{
|
||
string hStr = json.Substring(q1 + 1, q2 - q1 - 1).Trim();
|
||
if (!string.IsNullOrEmpty(hStr) && hStr != "0.0.0.0")
|
||
{
|
||
host = hStr;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
string targetUrl = string.Format("http://{0}:{1}/", host, port);
|
||
string healthUrl = string.Format("http://{0}:{1}/api/health", host, port);
|
||
|
||
bool firstInstance;
|
||
instanceMutex = new Mutex(true, "Local\\HermesHubWeb", out firstInstance);
|
||
if (!firstInstance) { Process.Start(targetUrl); return; }
|
||
// Adopt no unknown server: stop only a verified process from our installation.
|
||
try { StopOwnedRuntime(hermesHome, false); }
|
||
catch (Exception ex) { MessageBox.Show(ex.Message, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }
|
||
|
||
// 2. Check if server is already running and healthy
|
||
bool serverWasAlreadyRunning = IsServerHealthy(healthUrl);
|
||
Process serverProcess = null;
|
||
StringBuilder serverLog = new StringBuilder();
|
||
|
||
if (!serverWasAlreadyRunning)
|
||
{
|
||
// Find Python
|
||
string hermesPythonW = Path.Combine(hermesHome, @"hermes-agent\venv\Scripts\pythonw.exe");
|
||
string hermesPython = Path.Combine(hermesHome, @"hermes-agent\venv\Scripts\python.exe");
|
||
string exe = File.Exists(hermesPythonW) ? hermesPythonW : hermesPython;
|
||
|
||
if (!File.Exists(exe))
|
||
{
|
||
MessageBox.Show(
|
||
"Hermes Python not found:\n" + exe + "\n\nPlease install Hermes Agent first.",
|
||
"Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
return;
|
||
}
|
||
|
||
string pluginSrc = Path.Combine(hermesHome, @"plugins\antigravity-provider\src");
|
||
string agentDir = Path.Combine(hermesHome, "hermes-agent");
|
||
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||
string hubSrc = Path.GetFullPath(Path.Combine(baseDir, @"..\src"));
|
||
|
||
StringBuilder script = new StringBuilder();
|
||
script.AppendLine("import sys");
|
||
if (Directory.Exists(pluginSrc))
|
||
script.AppendLine("sys.path.insert(0, r'" + pluginSrc.Replace('\\', '/') + "')");
|
||
if (Directory.Exists(agentDir))
|
||
script.AppendLine("sys.path.insert(0, r'" + agentDir.Replace('\\', '/') + "')");
|
||
if (Directory.Exists(hubSrc))
|
||
script.AppendLine("sys.path.insert(0, r'" + hubSrc.Replace('\\', '/') + "')");
|
||
script.AppendLine("from antigravity_provider.router.web.server import run_server");
|
||
script.AppendLine("run_server()");
|
||
|
||
string entryScript = Path.Combine(hermesHome, "hermes_hub_web_entry.py");
|
||
try
|
||
{
|
||
File.WriteAllText(entryScript, script.ToString(), Encoding.UTF8);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show("Cannot write entry script:\n" + ex.Message, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
return;
|
||
}
|
||
|
||
ProcessStartInfo serverPsi = new ProcessStartInfo();
|
||
serverPsi.FileName = exe;
|
||
serverPsi.Arguments = "\"" + entryScript + "\"";
|
||
serverPsi.WorkingDirectory = hermesHome;
|
||
serverPsi.UseShellExecute = false;
|
||
serverPsi.CreateNoWindow = true;
|
||
serverPsi.WindowStyle = ProcessWindowStyle.Hidden;
|
||
// Вывод перехватываем, чтобы при падении показать причину, а не
|
||
// голое «terminated unexpectedly». Без этого владелец видит факт
|
||
// отказа и ни слова о том, чего не хватает.
|
||
serverPsi.RedirectStandardError = true;
|
||
serverPsi.RedirectStandardOutput = true;
|
||
|
||
try
|
||
{
|
||
serverProcess = Process.Start(serverPsi);
|
||
DataReceivedEventHandler collect = delegate(object sender, DataReceivedEventArgs item) {
|
||
if (item.Data == null) return;
|
||
lock (serverLog) { serverLog.AppendLine(item.Data); if (serverLog.Length > 4000) serverLog.Remove(0, serverLog.Length - 4000); }
|
||
};
|
||
serverProcess.ErrorDataReceived += collect;
|
||
serverProcess.OutputDataReceived += collect;
|
||
serverProcess.BeginErrorReadLine();
|
||
serverProcess.BeginOutputReadLine();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show("Failed to start Hermes Hub web server:\n" + ex.Message, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
return;
|
||
}
|
||
|
||
// 3. Poll /api/health with timeout (up to 15s)
|
||
bool ready = false;
|
||
for (int i = 0; i < 75; i++)
|
||
{
|
||
if (IsServerHealthy(healthUrl))
|
||
{
|
||
ready = true;
|
||
break;
|
||
}
|
||
if (serverProcess.HasExited)
|
||
{
|
||
string why;
|
||
lock (serverLog) { why = serverLog.ToString(); }
|
||
if (why.Length > 1500) why = why.Substring(why.Length - 1500);
|
||
string msg = "Веб-сервер Hermes Hub завершился с ошибкой.";
|
||
if (!string.IsNullOrEmpty(why)) msg += Environment.NewLine + Environment.NewLine + why.Trim();
|
||
MessageBox.Show(msg, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
return;
|
||
}
|
||
Thread.Sleep(200);
|
||
}
|
||
|
||
if (!ready)
|
||
{
|
||
MessageBox.Show("Hermes Hub web server failed to respond within 15 seconds.", "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
if (serverProcess != null && !serverProcess.HasExited)
|
||
{
|
||
try { serverProcess.Kill(); } catch { }
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 4. Locate browser in strict priority: Edge -> Chrome -> Chromium registry -> Fallback
|
||
string browserPath = FindChromiumBrowser();
|
||
Process browserProc = null;
|
||
|
||
|
||
if (!string.IsNullOrEmpty(browserPath))
|
||
{
|
||
ProcessStartInfo browserPsi = new ProcessStartInfo();
|
||
browserPsi.FileName = browserPath;
|
||
// Отдельный профиль браузера обязателен. Без него запущенный
|
||
// msedge.exe передаёт задачу УЖЕ РАБОТАЮЩЕМУ экземпляру и тут же
|
||
// завершается: WaitForExit возвращается мгновенно, лаунчер
|
||
// считает окно закрытым и убивает сервер, пока страница ещё
|
||
// грузится. Владелец видел ERR_CONNECTION_REFUSED.
|
||
string browserProfile = Path.Combine(hermesHome, "web_browser_profile");
|
||
try { Directory.CreateDirectory(browserProfile); } catch { }
|
||
browserPsi.Arguments = string.Format(
|
||
"--app=\"{0}\" --window-size=1400,900 --user-data-dir=\"{1}\" --no-first-run --no-default-browser-check",
|
||
targetUrl, browserProfile);
|
||
browserPsi.UseShellExecute = false;
|
||
|
||
try
|
||
{
|
||
browserProc = Process.Start(browserPsi);
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show(
|
||
"Не удалось запустить браузер в режиме приложения:\n" + ex.Message + "\n\nОткрываю стандартный браузер.",
|
||
"Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
Process.Start(targetUrl);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// No Chromium browser found
|
||
MessageBox.Show(
|
||
"Браузер с поддержкой режима приложения (Microsoft Edge или Google Chrome) не найден.\n\n" +
|
||
"Интерфейс будет открыт в стандартном браузере с адресной строкой.",
|
||
"Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
try
|
||
{
|
||
Process.Start(targetUrl);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show("Не удалось открыть браузер: " + ex.Message, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
|
||
Application.Run(new HubContext(hermesHome, targetUrl, browserPath, browserProc));
|
||
instanceMutex.ReleaseMutex();
|
||
}
|
||
|
||
// Restrict cleanup to this installation. Never kill arbitrary Python/browser processes.
|
||
public static void StopOwnedRuntime(string home, bool includeLauncher)
|
||
{
|
||
string escaped = Path.GetFullPath(home).TrimEnd('\\').Replace("'", "''");
|
||
string script = "$ErrorActionPreference='Stop'; $root='" + escaped + "'; " +
|
||
"$py=@((Join-Path $root 'hermes-agent\\venv\\Scripts\\python.exe'),(Join-Path $root 'hermes-agent\\venv\\Scripts\\pythonw.exe')); " +
|
||
"$all=@(Get-CimInstance Win32_Process); $protected=@($PID); $cursor=$PID; " +
|
||
"while ($cursor) { $node=$all | Where-Object ProcessId -eq $cursor | Select-Object -First 1; if (!$node) { break }; $cursor=$node.ParentProcessId; if ($cursor -in $protected) { break }; $protected+= $cursor }; " +
|
||
"function Stop-HubBranch([int]$processId) { foreach ($child in @($all | Where-Object ParentProcessId -eq $processId)) { if ($child.ProcessId -notin $protected) { Stop-HubBranch $child.ProcessId } }; " +
|
||
"if (Get-Process -Id $processId -ErrorAction SilentlyContinue) { Stop-Process -Id $processId -Force -ErrorAction Stop } }; " +
|
||
"$targets=@($all | Where-Object { " +
|
||
"($_.ExecutablePath -in $py -and ($_.CommandLine -match 'hermes_hub_web_entry\\.py|antigravity_provider\\.router\\.web'))" +
|
||
" -or ($_.Name -in @('msedge.exe','chrome.exe','chromium.exe') -and $_.CommandLine -match ('--user-data-dir=[\\x22]?'+[regex]::Escape((Join-Path $root 'web_browser_profile'))+'[\\x22]?(?:\\s|$)'))" +
|
||
(includeLauncher ? " -or ($_.Name -eq 'HermesHubWeb.exe' -and ($_.ExecutablePath -eq (Join-Path $root 'HermesHubWeb.exe') -or $_.ExecutablePath -eq (Join-Path $env:LOCALAPPDATA 'Programs\\HermesHub\\HermesHubWeb.exe')))" : "") +
|
||
" }); foreach ($target in $targets) { Stop-HubBranch $target.ProcessId; " +
|
||
"if (Get-Process -Id $target.ProcessId -ErrorAction SilentlyContinue) { throw 'Не удалось остановить прежний процесс Hermes Hub' } }";
|
||
ProcessStartInfo info = new ProcessStartInfo("powershell.exe", "-NoProfile -NonInteractive -EncodedCommand " + Convert.ToBase64String(Encoding.Unicode.GetBytes(script)));
|
||
info.UseShellExecute = false;
|
||
info.CreateNoWindow = true;
|
||
info.WindowStyle = ProcessWindowStyle.Hidden;
|
||
using (Process process = Process.Start(info))
|
||
{
|
||
if (!process.WaitForExit(20000)) { process.Kill(); throw new IOException("Остановка прежнего сервера превысила 20 секунд"); }
|
||
if (process.ExitCode != 0) throw new IOException("Не удалось остановить прежний сервер. Обновление отменено.");
|
||
}
|
||
}
|
||
|
||
private sealed class HubContext : ApplicationContext
|
||
{
|
||
private readonly NotifyIcon tray;
|
||
private readonly System.Windows.Forms.Timer timer;
|
||
private readonly string home, url, browserPath;
|
||
private Process browser;
|
||
private bool watching, hadWindow, closing;
|
||
|
||
public HubContext(string homePath, string targetUrl, string browserExe, Process browserProcess)
|
||
{
|
||
home = homePath; url = targetUrl; browserPath = browserExe; browser = browserProcess;
|
||
watching = browser != null;
|
||
tray = new NotifyIcon();
|
||
tray.Icon = System.Drawing.SystemIcons.Application;
|
||
tray.Text = "Hermes Hub — работает в фоне";
|
||
ContextMenuStrip menu = new ContextMenuStrip();
|
||
menu.Items.Add("Открыть", null, delegate { Open(); });
|
||
menu.Items.Add("Выход", null, delegate { ExitCompletely(); });
|
||
tray.ContextMenuStrip = menu;
|
||
tray.DoubleClick += delegate { Open(); };
|
||
tray.Visible = true;
|
||
timer = new System.Windows.Forms.Timer(); timer.Interval = 500;
|
||
timer.Tick += delegate { WatchWindow(); }; timer.Start();
|
||
}
|
||
|
||
private void WatchWindow()
|
||
{
|
||
if (!watching || closing || browser == null) return;
|
||
bool closed;
|
||
try {
|
||
browser.Refresh();
|
||
if (!browser.HasExited && browser.MainWindowHandle != IntPtr.Zero) hadWindow = true;
|
||
closed = browser.HasExited || (hadWindow && browser.MainWindowHandle == IntPtr.Zero);
|
||
} catch { closed = true; }
|
||
if (!closed) return;
|
||
watching = false;
|
||
DialogResult answer = MessageBox.Show("Закрыть Hermes Hub полностью?\n\nДа — остановить сервер и фоновые опросы.\nНет — оставить в фоне (значок в области уведомлений).", "Hermes Hub", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
||
if (answer == DialogResult.Yes) ExitCompletely();
|
||
}
|
||
|
||
private void Open()
|
||
{
|
||
if (closing) return;
|
||
try {
|
||
if (browser != null && !browser.HasExited && browser.MainWindowHandle != IntPtr.Zero) {
|
||
ShowWindow(browser.MainWindowHandle, 9); SetForegroundWindow(browser.MainWindowHandle); return;
|
||
}
|
||
if (string.IsNullOrEmpty(browserPath)) { Process.Start(url); return; }
|
||
ProcessStartInfo info = new ProcessStartInfo(browserPath,
|
||
"--app=\"" + url + "\" --window-size=1400,900 --user-data-dir=\"" + Path.Combine(home, "web_browser_profile") + "\" --no-first-run --no-default-browser-check");
|
||
info.UseShellExecute = false;
|
||
browser = Process.Start(info); watching = true; hadWindow = false;
|
||
} catch (Exception ex) { MessageBox.Show(ex.Message, "Hermes Hub"); }
|
||
}
|
||
|
||
private void ExitCompletely()
|
||
{
|
||
if (closing) return;
|
||
closing = true; timer.Stop();
|
||
try {
|
||
if (browser != null && !browser.HasExited) {
|
||
ProcessStartInfo kill = new ProcessStartInfo("taskkill.exe", "/PID " + browser.Id + " /T /F");
|
||
kill.UseShellExecute = false; kill.CreateNoWindow = true;
|
||
using (Process process = Process.Start(kill)) { process.WaitForExit(5000); }
|
||
}
|
||
// Неудачная остановка не повод отменять выход: владелец нажал
|
||
// «закрыть», и программа обязана закрыться. Прежде окно с
|
||
// ошибкой возвращало его обратно в работающее приложение.
|
||
try { StopOwnedRuntime(home, false); }
|
||
catch (Exception stopEx) {
|
||
MessageBox.Show("Часть процессов остановить не удалось: " + stopEx.Message + " Hermes Hub закроется; при необходимости снимите их в диспетчере задач.", "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||
}
|
||
tray.Visible = false; tray.Dispose(); timer.Dispose(); ExitThread();
|
||
} catch (Exception ex) {
|
||
closing = false; timer.Start();
|
||
MessageBox.Show("Не удалось завершить всё: " + ex.Message, "Hermes Hub", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
}
|
||
}
|
||
[System.Runtime.InteropServices.DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr handle);
|
||
[System.Runtime.InteropServices.DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr handle, int command);
|
||
}
|
||
|
||
public static bool IsServerHealthy(string url)
|
||
{
|
||
try
|
||
{
|
||
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
|
||
req.Timeout = 800;
|
||
req.Method = "GET";
|
||
using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
|
||
{
|
||
if (resp.StatusCode == HttpStatusCode.OK)
|
||
{
|
||
using (StreamReader r = new StreamReader(resp.GetResponseStream(), Encoding.UTF8))
|
||
{
|
||
string txt = r.ReadToEnd();
|
||
return txt.Contains("\"ok\":true") || txt.Contains("\"ok\": true");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
return false;
|
||
}
|
||
|
||
public static string FindChromiumBrowser()
|
||
{
|
||
// 1. Microsoft Edge
|
||
string[] edgePaths = new string[]
|
||
{
|
||
@"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
||
@"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
|
||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\Edge\Application\msedge.exe")
|
||
};
|
||
foreach (string p in edgePaths)
|
||
{
|
||
if (File.Exists(p)) return p;
|
||
}
|
||
|
||
string edgeReg = GetAppPathFromRegistry("msedge.exe");
|
||
if (!string.IsNullOrEmpty(edgeReg) && File.Exists(edgeReg)) return edgeReg;
|
||
|
||
// 2. Google Chrome
|
||
string[] chromePaths = new string[]
|
||
{
|
||
@"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||
@"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
|
||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\Application\chrome.exe")
|
||
};
|
||
foreach (string p in chromePaths)
|
||
{
|
||
if (File.Exists(p)) return p;
|
||
}
|
||
|
||
string chromeReg = GetAppPathFromRegistry("chrome.exe");
|
||
if (!string.IsNullOrEmpty(chromeReg) && File.Exists(chromeReg)) return chromeReg;
|
||
|
||
// 3. Brave / Vivaldi / Chromium
|
||
string[] otherPaths = new string[]
|
||
{
|
||
@"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe",
|
||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"BraveSoftware\Brave-Browser\Application\brave.exe"),
|
||
@"C:\Program Files\Vivaldi\Application\vivaldi.exe",
|
||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Vivaldi\Application\vivaldi.exe")
|
||
};
|
||
foreach (string p in otherPaths)
|
||
{
|
||
if (File.Exists(p)) return p;
|
||
}
|
||
|
||
string braveReg = GetAppPathFromRegistry("brave.exe");
|
||
if (!string.IsNullOrEmpty(braveReg) && File.Exists(braveReg)) return braveReg;
|
||
|
||
return null;
|
||
}
|
||
|
||
private static string GetAppPathFromRegistry(string exeName)
|
||
{
|
||
try
|
||
{
|
||
string key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\" + exeName;
|
||
using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(key))
|
||
{
|
||
if (rk != null)
|
||
{
|
||
object val = rk.GetValue(null);
|
||
if (val != null) return val.ToString();
|
||
}
|
||
}
|
||
using (RegistryKey rk = Registry.CurrentUser.OpenSubKey(key))
|
||
{
|
||
if (rk != null)
|
||
{
|
||
object val = rk.GetValue(null);
|
||
if (val != null) return val.ToString();
|
||
}
|
||
}
|
||
}
|
||
catch { }
|
||
return null;
|
||
}
|
||
}
|
||
}
|