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 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 { func decodeHistoryEventsSnapshot(b []byte) []historyEventRecord {
type snapshot struct { type snapshot struct {
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
@@ -408,7 +447,7 @@ func historyVisitsForDay(app *guiApp, day time.Time) []historyDetailRow {
if !historyInQueryRange(day, q) { if !historyInQueryRange(day, q) {
return nil return nil
} }
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
visits := historyVisitSource() visits := historyVisitSource()
return historyVisitsForDayFromData(app, day, visits, events, q) return historyVisitsForDayFromData(app, day, visits, events, q)
} }
@@ -502,7 +541,7 @@ func historyCalendarCellsForMonth(app *guiApp) []historyCalendarCell {
monthStart = time.Now() monthStart = time.Now()
} }
monthStart = time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location()) monthStart = time.Date(monthStart.Year(), monthStart.Month(), 1, 0, 0, 0, 0, monthStart.Location())
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
visits := historyVisitSource() visits := historyVisitSource()
return historyCalendarCellsForMonthFromData(app, monthStart, visits, events, q) 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) { func exportHistoryDayFromRequest(req historyExportRequest) (string, error) {
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
visits := historyVisitSource() visits := historyVisitSource()
rows := append([]historyDetailRow(nil), req.Rows...) rows := append([]historyDetailRow(nil), req.Rows...)
if len(rows) == 0 { if len(rows) == 0 {
@@ -795,7 +834,7 @@ func appendExportGuestSnapshot(b *strings.Builder, day time.Time, q historyQuery
if b == nil { if b == nil {
return return
} }
guests := readGuestSnapshot() guests := guestSnapshotForExport()
dayStart, dayEnd := historyDayBounds(day) dayStart, dayEnd := historyDayBounds(day)
type guestLine struct { type guestLine struct {
name string 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 { func cloneHistorySelection(selected map[string]bool) map[string]bool {
if len(selected) == 0 { if len(selected) == 0 {
return nil return nil
@@ -887,7 +944,7 @@ func refreshHistoryCache(app *guiApp, force bool) bool {
if !force && app.historyCacheLoaded && !app.historyReloadPending { if !force && app.historyCacheLoaded && !app.historyReloadPending {
return false return false
} }
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
visits := historyVisitSource() visits := historyVisitSource()
q := historyQueryFromApp(app) q := historyQueryFromApp(app)
app.historyRows = historyRows() app.historyRows = historyRows()

View File

@@ -102,7 +102,7 @@ const (
leftPaneHeight = 560 leftPaneHeight = 560
rightPaneWidth = windowWidth - leftPaneWidth rightPaneWidth = windowWidth - leftPaneWidth
settingsPaneWidth = rightPaneWidth settingsPaneWidth = rightPaneWidth
settingsPaneHeight = leftPaneHeight settingsPaneHeight = 700
clrMainBg = 0x00311c0b clrMainBg = 0x00311c0b
clrHeaderBg = 0x004d2c08 clrHeaderBg = 0x004d2c08
clrNavBg = 0x0024150b clrNavBg = 0x0024150b
@@ -228,8 +228,6 @@ type guiApp struct {
tabSettings uintptr tabSettings uintptr
btnTopMost uintptr btnTopMost uintptr
btnAutoUnmute uintptr btnAutoUnmute uintptr
btnFontDown uintptr
btnFontUp uintptr
activeTab string activeTab string
topMost bool topMost bool
autoUnmuteOnLeave bool autoUnmuteOnLeave bool
@@ -586,8 +584,6 @@ func runNativeGUI(initialTab string) error {
rightTitle, _ := syscall.UTF16PtrFromString("") rightTitle, _ := syscall.UTF16PtrFromString("")
topMostTitle, _ := syscall.UTF16PtrFromString("Top most") topMostTitle, _ := syscall.UTF16PtrFromString("Top most")
autoUnmuteTitle, _ := syscall.UTF16PtrFromString("Leave unmute") autoUnmuteTitle, _ := syscall.UTF16PtrFromString("Leave unmute")
fontDownTitle, _ := syscall.UTF16PtrFromString("A-")
fontUpTitle, _ := syscall.UTF16PtrFromString("A+")
browseTitle, _ := syscall.UTF16PtrFromString("参照") browseTitle, _ := syscall.UTF16PtrFromString("参照")
saveTitle, _ := syscall.UTF16PtrFromString("保存") saveTitle, _ := syscall.UTF16PtrFromString("保存")
toolbarTitle, _ := 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.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.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.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("") exportDirTitle, _ := syscall.UTF16PtrFromString("")
exportDirRect := settingsHistoryExportDirRect() exportDirRect := settingsHistoryExportDirRect()
app.settingsExportDirEdit, _, _ = createWindowEx.Call( app.settingsExportDirEdit, _, _ = createWindowEx.Call(
@@ -1216,7 +1210,7 @@ func refreshGUI(setWindowText *syscall.LazyProc, app *guiApp, reloadData bool) {
if app.settingsPaneHwnd != 0 { if app.settingsPaneHwnd != 0 {
showWindowProc.Call(app.settingsPaneHwnd, swHideControl) 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 { if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl) showWindowProc.Call(hwnd, swHideControl)
} }
@@ -1257,7 +1251,7 @@ func refreshSettingsControls(showWindowProc, setWindowPosProc *syscall.LazyProc,
return return
} }
app.lastSettingsVisible = true 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 { if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl) showWindowProc.Call(hwnd, swHideControl)
} }
@@ -1281,7 +1275,7 @@ func refreshSettingsControls(showWindowProc, setWindowPosProc *syscall.LazyProc,
if app.settingsPaneHwnd != 0 { if app.settingsPaneHwnd != 0 {
showWindowProc.Call(app.settingsPaneHwnd, swHideControl) 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 { if hwnd != 0 {
showWindowProc.Call(hwnd, swHideControl) showWindowProc.Call(hwnd, swHideControl)
} }
@@ -1639,10 +1633,10 @@ func resizeGUIForSettings(app *guiApp) {
return return
} }
resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate resizeFlags := hwndNoMove | hwndNoZOrder | hwndNoActivate
windowHeight := contentTop + leftPaneHeight + bottomBarHeight windowHeight := contentTop + settingsPaneHeight + bottomBarHeight
setWindowPosProc.Call(app.hwnd, 0, 0, 0, windowWidth, uintptr(windowHeight), resizeFlags) setWindowPosProc.Call(app.hwnd, 0, 0, 0, windowWidth, uintptr(windowHeight), resizeFlags)
if app.settingsPaneHwnd != 0 { 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) drawPaneText(hdc, 24, 68, "表示", app.hFont, clrMutedText)
drawSettingsWideButton(hdc, settingsTopMostRect(), "Top most", onOffBool(app.topMost), app.hFont, app.topMost) drawSettingsWideButton(hdc, settingsTopMostRect(), "Top most", onOffBool(app.topMost), app.hFont, app.topMost)
drawSettingsWideButton(hdc, settingsAutoUnmuteRect(), "Leave unmute", onOffBool(app.autoUnmuteOnLeave), app.hFont, app.autoUnmuteOnLeave) 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) drawSettingsRefreshControl(hdc, app)
drawPaneText(hdc, 24, 314, "文字サイズ", app.hFont, clrMutedText)
drawSettingsFontControl(hdc, app) drawSettingsFontControl(hdc, app)
drawPaneText(hdc, 24, 374, "履歴 Export", app.hFont, clrMutedText) drawPaneText(hdc, 24, 400, "履歴 Export", app.hFont, clrMutedText)
drawPaneText(hdc, 24, 396, "出力先フォルダ", app.hFont, clrMutedText) drawPaneText(hdc, 24, 430, "出力先フォルダ", app.hFont, clrMutedText)
drawSettingsWideButton(hdc, settingsHistoryExportTimeRect(), "Time", onOffBool(app.historyExportIncludeTime), app.hFont, app.historyExportIncludeTime) drawSettingsWideButton(hdc, settingsHistoryExportTimeRect(), "Time", onOffBool(app.historyExportIncludeTime), app.hFont, app.historyExportIncludeTime)
drawSettingsWideButton(hdc, settingsHistoryExportWorldRect(), "World", onOffBool(app.historyExportIncludeWorld), app.hFont, app.historyExportIncludeWorld) 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, settingsHistoryExportJoinLeaveRect(), "Join/Leave", onOffBool(app.historyExportIncludeJoinLeave), app.hFont, app.historyExportIncludeJoinLeave)
drawSettingsWideButton(hdc, settingsHistoryExportCustomToggleRect(), "Custom", onOffBool(app.historyExportCustomEnabled), app.hFont, app.historyExportCustomEnabled) 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 return 0
} }
@@ -1923,35 +1918,35 @@ func settingsFontUpRect() winRect {
} }
func settingsHistoryExportDirRect() 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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, settingsRefreshValueRect(), uint32(0x0038281a), border)
drawSettingsBox(hdc, settingsRefreshUpRect(), fill, border) drawSettingsBox(hdc, settingsRefreshUpRect(), fill, border)
drawSettingsWideButton(hdc, settingsRefreshUnitRect(), "\u5358\u4f4d", refreshIntervalUnitLabel(app.refreshIntervalUnit), font, true) 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, 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 { 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, settingsFontValueRect(), uint32(0x0038281a), border)
drawSettingsBox(hdc, settingsFontUpRect(), fill, border) drawSettingsBox(hdc, settingsFontUpRect(), fill, border)
drawCenteredPaneText(hdc, settingsFontDownRect(), "A-", font, clrText) 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) drawCenteredPaneText(hdc, settingsFontUpRect(), "A+", font, clrText)
} }
@@ -2951,7 +2946,7 @@ func eventRows(users []userState) []guiEventRow {
} }
func joinLeaveRowsForCurrentWorld(app *guiApp) []guiEventRow { func joinLeaveRowsForCurrentWorld(app *guiApp) []guiEventRow {
events := historyEventsFromSnapshot() events := historyEventsWithFallback()
if len(events) == 0 { if len(events) == 0 {
users, _ := currentUsersFromJoinLeave() users, _ := currentUsersFromJoinLeave()
return eventRows(users) return eventRows(users)