Restore VRTW_Tool title; note discord mute regression

This commit is contained in:
every_holiday
2026-06-24 03:08:30 +09:00
parent ab9551c241
commit 63700d9d29
7 changed files with 202 additions and 42 deletions

View File

@@ -4,6 +4,8 @@ import (
"flag"
"log"
"os"
"os/exec"
"path/filepath"
"vrc_osc_go/internal/app"
)
@@ -11,12 +13,30 @@ import (
func main() {
var configPath string
var mode string
var noGUI bool
flag.StringVar(&configPath, "config", "config/config.toml", "path to config.toml")
flag.StringVar(&mode, "mode", "", "generate-json or extract-log")
flag.BoolVar(&noGUI, "no-gui", false, "do not launch the GUI companion")
flag.Parse()
if mode == "" && !noGUI {
launchGUI()
}
if err := app.Run(configPath, mode); err != nil {
log.SetOutput(os.Stderr)
log.Fatalf("vrc_osc_go: %v", err)
}
}
func launchGUI() {
exe, err := os.Executable()
if err != nil {
return
}
gui := filepath.Join(filepath.Dir(exe), "vrc_osc_gui.exe")
if _, err := os.Stat(gui); err != nil {
return
}
_ = exec.Command(gui).Start()
}

View File

@@ -5,20 +5,21 @@ import (
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
"vrc_osc_go/internal/app"
)
func main() {
go func() {
if err := app.Run("config/config.toml", ""); err != nil {
log.Printf("backend stopped: %v", err)
}
}()
func runtimeDir() string {
exe, err := os.Executable()
if err != nil {
return "runtime"
}
return filepath.Join(filepath.Dir(exe), "runtime")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handleIndex)
mux.HandleFunc("/api/state", handleState)
@@ -36,7 +37,7 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="3">
<title>VRC_OSC GUI</title>
<title>VRTW_Tool</title>
<style>
body{font-family:sans-serif;background:#111;color:#eee;margin:20px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@@ -49,7 +50,7 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
</style>
</head>
<body>
<h1>VRC_OSC GUI</h1>
<h1>VRTW_Tool</h1>
<div id="root">loading...</div>
<script>
const sortKey = 'vrc_osc_gui_sort';
@@ -149,19 +150,42 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
}
func handleState(w http.ResponseWriter, r *http.Request) {
s := app.SnapshotRuntime()
guests := []app.GuestStatus{}
if t := app.GetGuestTracker(); t != nil {
guests = t.Snapshot(time.Now())
statePath := filepath.Join(runtimeDir(), "state.json")
body, err := os.ReadFile(statePath)
if err != nil {
_ = json.NewEncoder(w).Encode(map[string]any{
"world": "",
"discord_muted": false,
"ocr": "",
"translate": "",
"guests": []any{},
"guest_count": 0,
"error": err.Error(),
})
return
}
_ = json.NewEncoder(w).Encode(map[string]any{
"world": s.CurrentWorld,
"discord_muted": s.DiscordMuted,
"ocr": s.LastOCRText,
"translate": s.LastTranslate,
"guests": guests,
"guest_count": len(guests),
})
var state map[string]any
if err := json.Unmarshal(body, &state); err != nil {
_ = json.NewEncoder(w).Encode(map[string]any{"error": err.Error()})
return
}
guests := loadGuestSnapshot()
state["guests"] = guests
state["guest_count"] = len(guests)
_ = json.NewEncoder(w).Encode(state)
}
func loadGuestSnapshot() []map[string]any {
statePath := filepath.Join(runtimeDir(), "guest_snapshot.json")
body, err := os.ReadFile(statePath)
if err != nil {
return []map[string]any{}
}
var guests []map[string]any
if err := json.Unmarshal(body, &guests); err != nil {
return []map[string]any{}
}
return guests
}
func openBrowser(url string) {

View File

@@ -29,7 +29,7 @@ func Run(configPath string, mode string) error {
if err != nil {
return err
}
SetGuestTracker(NewGuestTracker(cfg.VrcLog.GuestNames))
InitGuestTracker(NewGuestTracker(cfg.VrcLog.GuestNames))
log.Printf("vrc_osc_go starting osc=%s:%d config=%s", cfg.OSC.Host, cfg.OSC.Port, configPath)
server := osc.NewServer(cfg.OSC.Host, cfg.OSC.Port)

View File

@@ -1,7 +1,6 @@
package app
import (
"bytes"
"fmt"
"log"
"os/exec"
@@ -9,13 +8,68 @@ import (
func pressDiscordMuteHotkey() error {
script := `
$p = Get-Process | Where-Object { $_.MainWindowTitle -match 'Discord' } | Select-Object -First 1
if ($null -eq $p) { exit 2 }
[void][System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
$ErrorActionPreference = "Stop"
function Get-DiscordWindows {
$windows = foreach ($p in Get-Process) {
$title = ($p.MainWindowTitle | Out-String).Trim()
if (-not $title) { continue }
if (-not $title.ToLower().Contains('discord')) { continue }
$p
}
if (-not $windows) { return @() }
return $windows
}
function Get-BestDiscordWindow {
$windows = Get-DiscordWindows
if (-not $windows -or $windows.Count -eq 0) { return $null }
$browserKeywords = @('google chrome', 'microsoft edge', 'mozilla firefox', 'brave', 'opera')
$ranked = $windows | Sort-Object {
$title = ($_.MainWindowTitle | Out-String).Trim().ToLower()
$score = 0
foreach ($keyword in $browserKeywords) {
if ($title.Contains($keyword)) { $score += 10 }
}
if ($title -match '^\(\d+\)\s*discord') { $score += 20 }
if ($title.Contains('discord')) { $score += 5 }
$score
} -Descending
return $ranked | Select-Object -First 1
}
function Get-VRChatWindow {
$windows = Get-Process | Where-Object {
$_.MainWindowTitle -and (
$_.MainWindowTitle -eq 'VRChat' -or $_.MainWindowTitle.ToLower().StartsWith('vrchat ')
)
}
if (-not $windows) { return $null }
return $windows | Sort-Object { $_.MainWindowHandle } -Descending | Select-Object -First 1
}
function Activate-Window($p) {
if ($null -eq $p) { return }
$wshell = New-Object -ComObject WScript.Shell
try { $null = $wshell.AppActivate($p.Id) } catch {}
Start-Sleep -Milliseconds 200
}
$discord = Get-BestDiscordWindow
if ($null -eq $discord) { exit 2 }
Activate-Window $discord
$wshell = New-Object -ComObject WScript.Shell
$null = $wshell.AppActivate($p.Id)
Start-Sleep -Milliseconds 150
Start-Sleep -Milliseconds 200
$wshell.SendKeys('^+m')
Start-Sleep -Milliseconds 200
$vrchat = Get-VRChatWindow
if ($null -ne $vrchat) {
Activate-Window $vrchat
}
`
cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script)
@@ -28,9 +82,3 @@ $wshell.SendKeys('^+m')
}
return nil
}
func pressDiscordMuteHotkeyLegacy() error {
var buf bytes.Buffer
_ = buf
return pressDiscordMuteHotkey()
}

View File

@@ -57,7 +57,6 @@ func handleDiscordSend(state *discordMuteState, args []osc.Value) error {
} else {
log.Printf("INFO already muted")
}
return nil
}
return nil

View File

@@ -2,9 +2,14 @@ package app
import (
"fmt"
"encoding/json"
"os"
"path/filepath"
"sort"
"sync"
"time"
"vrc_osc_go/internal/common"
)
type GuestStatus struct {
@@ -48,6 +53,7 @@ func (t *GuestTracker) MarkJoin(name string, at time.Time) {
s.Present = true
s.LastJoin = at
s.AbsentFor = ""
_ = t.persistLocked()
}
func (t *GuestTracker) MarkLeave(name string, at time.Time) {
@@ -59,6 +65,7 @@ func (t *GuestTracker) MarkLeave(name string, at time.Time) {
if !s.LastJoin.IsZero() {
s.AbsentFor = humanSince(at)
}
_ = t.persistLocked()
}
func (t *GuestTracker) Snapshot(now time.Time) []GuestStatus {
@@ -76,6 +83,26 @@ func (t *GuestTracker) Snapshot(now time.Time) []GuestStatus {
return out
}
func (t *GuestTracker) persistLocked() error {
dir := filepath.Join(common.RootDir(), "runtime")
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
snapshot := make([]GuestStatus, 0, len(t.items))
for _, s := range t.items {
cp := *s
snapshot = append(snapshot, cp)
}
f, err := os.Create(filepath.Join(dir, "guest_snapshot.json"))
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
return enc.Encode(snapshot)
}
func (t *GuestTracker) ensure(name string) *GuestStatus {
if s, ok := t.items[name]; ok {
return s

View File

@@ -1,6 +1,14 @@
package app
import "sync"
import (
"encoding/json"
"os"
"path/filepath"
"sync"
"time"
"vrc_osc_go/internal/common"
)
type RuntimeState struct {
mu sync.RWMutex
@@ -14,30 +22,42 @@ var runtimeState = &RuntimeState{}
var guestTracker *GuestTracker
func SetGuestTracker(t *GuestTracker) { guestTracker = t }
func InitGuestTracker(t *GuestTracker) {
guestTracker = t
if guestTracker != nil {
guestTracker.mu.Lock()
_ = guestTracker.persistLocked()
guestTracker.mu.Unlock()
}
}
func GetGuestTracker() *GuestTracker { return guestTracker }
func SetDiscordMuted(v bool) {
runtimeState.mu.Lock()
defer runtimeState.mu.Unlock()
runtimeState.DiscordMuted = v
runtimeState.mu.Unlock()
_ = persistRuntimeState()
}
func SetOCRText(v string) {
runtimeState.mu.Lock()
defer runtimeState.mu.Unlock()
runtimeState.LastOCRText = v
runtimeState.mu.Unlock()
_ = persistRuntimeState()
}
func SetTranslateText(v string) {
runtimeState.mu.Lock()
defer runtimeState.mu.Unlock()
runtimeState.LastTranslate = v
runtimeState.mu.Unlock()
_ = persistRuntimeState()
}
func SetCurrentWorld(v string) {
runtimeState.mu.Lock()
defer runtimeState.mu.Unlock()
runtimeState.CurrentWorld = v
runtimeState.mu.Unlock()
_ = persistRuntimeState()
}
func SnapshotRuntime() RuntimeState {
@@ -45,3 +65,25 @@ func SnapshotRuntime() RuntimeState {
defer runtimeState.mu.RUnlock()
return *runtimeState
}
func persistRuntimeState() error {
s := SnapshotRuntime()
dir := filepath.Join(common.RootDir(), "runtime")
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
f, err := os.Create(filepath.Join(dir, "state.json"))
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
return enc.Encode(map[string]any{
"discord_muted": s.DiscordMuted,
"ocr": s.LastOCRText,
"translate": s.LastTranslate,
"world": s.CurrentWorld,
"updated_at": time.Now().Format(time.RFC3339),
})
}