4 Commits

Author SHA1 Message Date
messypy
51c00f2a75 Fallback history export to VRChat logs
All checks were successful
build-windows-exe / build (push) Successful in 1m40s
2026-07-28 00:37:56 +09:00
messypy
6d318af329 Give settings export controls more space
All checks were successful
build-windows-exe / build (push) Successful in 1m46s
2026-07-28 00:31:42 +09:00
messypy
a017170c71 Align settings controls
All checks were successful
build-windows-exe / build (push) Successful in 2m9s
2026-07-28 00:20:57 +09:00
messypy
d38d2edee3 Remove duplicate font size buttons
All checks were successful
build-windows-exe / build (push) Successful in 2m5s
2026-07-28 00:14:04 +09:00
2 changed files with 85 additions and 33 deletions

View File

@@ -342,6 +342,45 @@ func historyEventsFromSnapshot() []historyEventRecord {
return out
}
func historyEventsWithFallback() []historyEventRecord {
events := historyEventsFromSnapshot()
if len(events) > 0 {
return events
}
users, ok := currentUsersFromVRChatLog()
if !ok || len(users) == 0 {
return nil
}
out := make([]historyEventRecord, 0, len(users))
for _, user := range users {
name := strings.TrimSpace(user.Name)
if name == "" {
continue
}
if !user.LastJoin.IsZero() {
out = append(out, historyEventRecord{
At: user.LastJoin,
Kind: "join",
Name: name,
})
}
if !user.Present && !user.LastLeave.IsZero() {
out = append(out, historyEventRecord{
At: user.LastLeave,
Kind: "leave",
Name: name,
})
}
}
sort.SliceStable(out, func(i, j int) bool {
if out[i].At.Equal(out[j].At) {
return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name)
}
return out[i].At.Before(out[j].At)
})
return out
}
func decodeHistoryEventsSnapshot(b []byte) []historyEventRecord {
type snapshot struct {
UpdatedAt time.Time `json:"updated_at"`
@@ -408,7 +447,7 @@ func historyVisitsForDay(app *guiApp, day time.Time) []historyDetailRow {
if !historyInQueryRange(day, q) {
return nil
}
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
visits := historyVisitSource()
return historyVisitsForDayFromData(app, day, visits, events, q)
}
@@ -502,7 +541,7 @@ func historyCalendarCellsForMonth(app *guiApp) []historyCalendarCell {
monthStart = time.Now()
}
monthStart = time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location())
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
visits := historyVisitSource()
return historyCalendarCellsForMonthFromData(app, monthStart, visits, events, q)
}
@@ -591,7 +630,7 @@ func exportHistoryDay(app *guiApp, day time.Time, selected map[string]bool) (str
}
func exportHistoryDayFromRequest(req historyExportRequest) (string, error) {
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
visits := historyVisitSource()
rows := append([]historyDetailRow(nil), req.Rows...)
if len(rows) == 0 {
@@ -795,7 +834,7 @@ func appendExportGuestSnapshot(b *strings.Builder, day time.Time, q historyQuery
if b == nil {
return
}
guests := readGuestSnapshot()
guests := guestSnapshotForExport()
dayStart, dayEnd := historyDayBounds(day)
type guestLine struct {
name string
@@ -848,6 +887,24 @@ func appendExportGuestSnapshot(b *strings.Builder, day time.Time, q historyQuery
}
}
func guestSnapshotForExport() []userState {
guests := readGuestSnapshot()
if len(guests) > 0 {
out := make([]userState, 0, len(guests))
for _, guest := range guests {
out = append(out, userState{
Name: guest.Name,
Present: guest.Present,
LastJoin: guest.LastJoin,
LastLeave: guest.LastLeave,
})
}
return out
}
users, _ := currentUsersFromJoinLeave()
return users
}
func cloneHistorySelection(selected map[string]bool) map[string]bool {
if len(selected) == 0 {
return nil
@@ -887,7 +944,7 @@ func refreshHistoryCache(app *guiApp, force bool) bool {
if !force && app.historyCacheLoaded && !app.historyReloadPending {
return false
}
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
visits := historyVisitSource()
q := historyQueryFromApp(app)
app.historyRows = historyRows()

View File

@@ -102,7 +102,7 @@ const (
leftPaneHeight = 560
rightPaneWidth = windowWidth - leftPaneWidth
settingsPaneWidth = rightPaneWidth
settingsPaneHeight = leftPaneHeight
settingsPaneHeight = 700
clrMainBg = 0x00311c0b
clrHeaderBg = 0x004d2c08
clrNavBg = 0x0024150b
@@ -228,8 +228,6 @@ type guiApp struct {
tabSettings uintptr
btnTopMost uintptr
btnAutoUnmute uintptr
btnFontDown uintptr
btnFontUp uintptr
activeTab string
topMost bool
autoUnmuteOnLeave bool
@@ -586,8 +584,6 @@ func runNativeGUI(initialTab string) error {
rightTitle, _ := syscall.UTF16PtrFromString("")
topMostTitle, _ := syscall.UTF16PtrFromString("Top most")
autoUnmuteTitle, _ := syscall.UTF16PtrFromString("Leave unmute")
fontDownTitle, _ := syscall.UTF16PtrFromString("A-")
fontUpTitle, _ := syscall.UTF16PtrFromString("A+")
browseTitle, _ := syscall.UTF16PtrFromString("参照")
saveTitle, _ := syscall.UTF16PtrFromString("保存")
toolbarTitle, _ := syscall.UTF16PtrFromString("")
@@ -635,8 +631,6 @@ func runNativeGUI(initialTab string) error {
app.settingsPaneHwnd, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(paneClassName)), uintptr(unsafe.Pointer(settingsPaneTitle)), paneStyle, 0, contentTop, windowWidth, settingsPaneHeight, hwnd, idLog+1, hInstance, 0)
app.btnTopMost, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(buttonClass)), uintptr(unsafe.Pointer(topMostTitle)), buttonStyle, leftPaneWidth+32, contentTop+86, 140, 30, hwnd, 3005, hInstance, 0)
app.btnAutoUnmute, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(buttonClass)), uintptr(unsafe.Pointer(autoUnmuteTitle)), buttonStyle, leftPaneWidth+32, contentTop+132, 180, 30, hwnd, 3008, hInstance, 0)
app.btnFontDown, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(buttonClass)), uintptr(unsafe.Pointer(fontDownTitle)), buttonStyle, leftPaneWidth+32, contentTop+178, 52, 30, hwnd, 3006, hInstance, 0)
app.btnFontUp, _, _ = createWindowEx.Call(0, uintptr(unsafe.Pointer(buttonClass)), uintptr(unsafe.Pointer(fontUpTitle)), buttonStyle, leftPaneWidth+92, contentTop+178, 52, 30, hwnd, 3007, hInstance, 0)
exportDirTitle, _ := syscall.UTF16PtrFromString("")
exportDirRect := settingsHistoryExportDirRect()
app.settingsExportDirEdit, _, _ = createWindowEx.Call(
@@ -1216,7 +1210,7 @@ func refreshGUI(setWindowText *syscall.LazyProc, app *guiApp, reloadData bool) {
if app.settingsPaneHwnd != 0 {
showWindowProc.Call(app.settingsPaneHwnd, swHideControl)
}
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute, app.btnFontDown, app.btnFontUp} {
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute} {
if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl)
}
@@ -1257,7 +1251,7 @@ func refreshSettingsControls(showWindowProc, setWindowPosProc *syscall.LazyProc,
return
}
app.lastSettingsVisible = true
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute, app.btnFontDown, app.btnFontUp} {
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute} {
if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl)
}
@@ -1281,7 +1275,7 @@ func refreshSettingsControls(showWindowProc, setWindowPosProc *syscall.LazyProc,
if app.settingsPaneHwnd != 0 {
showWindowProc.Call(app.settingsPaneHwnd, swHideControl)
}
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute, app.btnFontDown, app.btnFontUp, app.settingsExportDirEdit, app.settingsExportCustomEdit, app.settingsExportBrowseBtn, app.settingsExportCustomSaveBtn} {
for _, hwnd := range []uintptr{app.btnTopMost, app.btnAutoUnmute, app.settingsExportDirEdit, app.settingsExportCustomEdit, app.settingsExportBrowseBtn, app.settingsExportCustomSaveBtn} {
if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl)
}
@@ -1639,10 +1633,10 @@ func resizeGUIForSettings(app *guiApp) {
return
}
resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate
windowHeight := contentTop + leftPaneHeight + bottomBarHeight
windowHeight := contentTop + settingsPaneHeight + bottomBarHeight
setWindowPosProc.Call(app.hwnd, 0, 0, 0, windowWidth, uintptr(windowHeight), resizeFlags)
if app.settingsPaneHwnd != 0 {
setWindowPosProc.Call(app.settingsPaneHwnd, 0, 0, uintptr(contentTop), windowWidth, uintptr(leftPaneHeight), resizeFlags)
setWindowPosProc.Call(app.settingsPaneHwnd, 0, 0, uintptr(contentTop), windowWidth, uintptr(settingsPaneHeight), resizeFlags)
}
}
@@ -1873,16 +1867,17 @@ func paintSettingsPane(hwnd uintptr, app *guiApp) uintptr {
drawPaneText(hdc, 24, 68, "表示", app.hFont, clrMutedText)
drawSettingsWideButton(hdc, settingsTopMostRect(), "Top most", onOffBool(app.topMost), app.hFont, app.topMost)
drawSettingsWideButton(hdc, settingsAutoUnmuteRect(), "Leave unmute", onOffBool(app.autoUnmuteOnLeave), app.hFont, app.autoUnmuteOnLeave)
drawPaneText(hdc, 24, 190, "文字サイズ", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 190, "更新間隔", app.hFont, clrMutedText)
drawSettingsRefreshControl(hdc, app)
drawPaneText(hdc, 24, 314, "文字サイズ", app.hFont, clrMutedText)
drawSettingsFontControl(hdc, app)
drawPaneText(hdc, 24, 374, "履歴 Export", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 396, "出力先フォルダ", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 400, "履歴 Export", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 430, "出力先フォルダ", app.hFont, clrMutedText)
drawSettingsWideButton(hdc, settingsHistoryExportTimeRect(), "Time", onOffBool(app.historyExportIncludeTime), app.hFont, app.historyExportIncludeTime)
drawSettingsWideButton(hdc, settingsHistoryExportWorldRect(), "World", onOffBool(app.historyExportIncludeWorld), app.hFont, app.historyExportIncludeWorld)
drawSettingsWideButton(hdc, settingsHistoryExportJoinLeaveRect(), "Join/Leave", onOffBool(app.historyExportIncludeJoinLeave), app.hFont, app.historyExportIncludeJoinLeave)
drawSettingsWideButton(hdc, settingsHistoryExportCustomToggleRect(), "Custom", onOffBool(app.historyExportCustomEnabled), app.hFont, app.historyExportCustomEnabled)
drawPaneText(hdc, 24, 516, "Custom 条件", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 618, "Custom 条件", app.hFont, clrMutedText)
return 0
}
@@ -1923,35 +1918,35 @@ func settingsFontUpRect() winRect {
}
func settingsHistoryExportDirRect() winRect {
return winRect{Left: 116, Top: 420, Right: 636, Bottom: 444}
return winRect{Left: 24, Top: 458, Right: 636, Bottom: 490}
}
func settingsHistoryExportBrowseRect() winRect {
return winRect{Left: 644, Top: 420, Right: 736, Bottom: 444}
return winRect{Left: 644, Top: 458, Right: 736, Bottom: 490}
}
func settingsHistoryExportTimeRect() winRect {
return winRect{Left: 24, Top: 452, Right: 360, Bottom: 482}
return winRect{Left: 24, Top: 510, Right: 360, Bottom: 546}
}
func settingsHistoryExportWorldRect() winRect {
return winRect{Left: 376, Top: 452, Right: 736, Bottom: 482}
return winRect{Left: 376, Top: 510, Right: 736, Bottom: 546}
}
func settingsHistoryExportJoinLeaveRect() winRect {
return winRect{Left: 24, Top: 488, Right: 360, Bottom: 518}
return winRect{Left: 24, Top: 558, Right: 360, Bottom: 594}
}
func settingsHistoryExportCustomToggleRect() winRect {
return winRect{Left: 376, Top: 488, Right: 736, Bottom: 518}
return winRect{Left: 376, Top: 558, Right: 736, Bottom: 594}
}
func settingsHistoryExportCustomRect() winRect {
return winRect{Left: 116, Top: 536, Right: 636, Bottom: 558}
return winRect{Left: 24, Top: 646, Right: 636, Bottom: 678}
}
func settingsHistoryExportCustomSaveRect() winRect {
return winRect{Left: 644, Top: 536, Right: 736, Bottom: 558}
return winRect{Left: 644, Top: 646, Right: 736, Bottom: 678}
}
func handleSettingsPaneClick(app *guiApp, x, y int32, getWindowText, setWindowText *syscall.LazyProc) bool {
@@ -2041,9 +2036,9 @@ func drawSettingsRefreshControl(hdc uintptr, app *guiApp) {
drawSettingsBox(hdc, settingsRefreshValueRect(), uint32(0x0038281a), border)
drawSettingsBox(hdc, settingsRefreshUpRect(), fill, border)
drawSettingsWideButton(hdc, settingsRefreshUnitRect(), "\u5358\u4f4d", refreshIntervalUnitLabel(app.refreshIntervalUnit), font, true)
drawCenteredPaneText(hdc, settingsRefreshDownRect(), "A-", font, clrText)
drawCenteredPaneText(hdc, settingsRefreshDownRect(), "-", font, clrText)
drawCenteredPaneText(hdc, settingsRefreshValueRect(), refreshIntervalValueLabel(app.refreshIntervalValue, app.refreshIntervalUnit), font, clrMutedText)
drawCenteredPaneText(hdc, settingsRefreshUpRect(), "A+", font, clrText)
drawCenteredPaneText(hdc, settingsRefreshUpRect(), "+", font, clrText)
}
func refreshIntervalValueLabel(value int, unit string) string {
@@ -2668,7 +2663,7 @@ func drawSettingsFontControl(hdc uintptr, app *guiApp) {
drawSettingsBox(hdc, settingsFontValueRect(), uint32(0x0038281a), border)
drawSettingsBox(hdc, settingsFontUpRect(), fill, border)
drawCenteredPaneText(hdc, settingsFontDownRect(), "A-", font, clrText)
drawCenteredPaneText(hdc, settingsFontValueRect(), strconv.Itoa(app.fontSize), font, clrMutedText)
drawCenteredPaneText(hdc, settingsFontValueRect(), strconv.Itoa(app.fontSize)+" pt", font, clrMutedText)
drawCenteredPaneText(hdc, settingsFontUpRect(), "A+", font, clrText)
}
@@ -2951,7 +2946,7 @@ func eventRows(users []userState) []guiEventRow {
}
func joinLeaveRowsForCurrentWorld(app *guiApp) []guiEventRow {
events := historyEventsFromSnapshot()
events := historyEventsWithFallback()
if len(events) == 0 {
users, _ := currentUsersFromJoinLeave()
return eventRows(users)