From 74300afbe789b4409a87424f21a5453928820772 Mon Sep 17 00:00:00 2001 From: Danilo Lemes Date: Sun, 29 Dec 2024 20:54:59 +0000 Subject: [PATCH] add recording --- .../hardwaremonitor/HardwareMonitorReader.kt | 4 +- .../cleanmeter/target/desktop/KeyboardHook.kt | 14 ++-- .../cleanmeter/target/desktop/ServerMain.kt | 2 +- .../desktop/data/ObserveHardwareReadings.kt | 7 -- .../desktop/ui/overlay/OverlayViewModel.kt | 10 +-- .../desktop/ui/settings/SettingsViewModel.kt | 65 +++++++++++++++++-- .../ui/settings/tabs/HelpSettingsUi.kt | 36 ++++++++++ 7 files changed, 113 insertions(+), 25 deletions(-) delete mode 100644 target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/ObserveHardwareReadings.kt diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt index fc9e1d1..51ae695 100644 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt +++ b/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt @@ -40,7 +40,7 @@ object HardwareMonitorReader { val buffer = getByteBuffer(packet.data, hardware * HARDWARE_SIZE + sensor * SENSOR_SIZE, HEADER_SIZE) val hardwares = readHardware(buffer, hardware) val sensors = readSensor(buffer, sensor) - _currentData = _currentData.copy(Hardwares = hardwares, Sensors = sensors) + _currentData = _currentData.copy(Hardwares = hardwares, Sensors = sensors, LastPollTime = System.currentTimeMillis()) _currentData } @@ -48,7 +48,7 @@ object HardwareMonitorReader { val appsCount = getByteBuffer(packet.data, LENGTH_SIZE, 0).short val buffer = getByteBuffer(packet.data, appsCount.toInt() * NAME_SIZE, LENGTH_SIZE) val apps = listOf("Auto") + readPresentMonApps(buffer, appsCount) - _currentData = _currentData.copy(PresentMonApps = apps) + _currentData = _currentData.copy(PresentMonApps = apps, LastPollTime = System.currentTimeMillis()) _currentData } diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt index f0e71fe..876e10f 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt +++ b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt @@ -4,16 +4,18 @@ import com.github.kwhat.jnativehook.GlobalScreen import com.github.kwhat.jnativehook.keyboard.NativeKeyEvent import com.github.kwhat.jnativehook.keyboard.NativeKeyListener import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.Channel.Factory.CONFLATED import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.receiveAsFlow sealed class KeyboardEvent { data object ToggleOverlay : KeyboardEvent() + data object ToggleRecording : KeyboardEvent() } internal object KeyboardManager { - private val _channel = Channel() + private val _channel = Channel(CONFLATED) val events = _channel.receiveAsFlow() fun filter(event: KeyboardEvent) = events.filterIsInstance(event::class) @@ -25,11 +27,15 @@ internal object KeyboardManager { override fun nativeKeyReleased(nativeEvent: NativeKeyEvent) { val isCtrl = nativeEvent.modifiers.and(NativeKeyEvent.CTRL_MASK) > 0 val isAlt = nativeEvent.modifiers.and(NativeKeyEvent.VC_ALT) > 0 - val isF10 = nativeEvent.keyCode == NativeKeyEvent.VC_F10 - if (isCtrl && isAlt && isF10) { - _channel.trySend(KeyboardEvent.ToggleOverlay) + if (!isCtrl && !isAlt) return + + val event = when (nativeEvent.keyCode) { + NativeKeyEvent.VC_F10 -> KeyboardEvent.ToggleOverlay + NativeKeyEvent.VC_F11 -> KeyboardEvent.ToggleRecording + else -> null } + event?.let { _channel.trySend(it) } } }) } catch (e: Throwable) { diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ServerMain.kt b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ServerMain.kt index 2380088..56aba7a 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ServerMain.kt +++ b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ServerMain.kt @@ -17,8 +17,8 @@ fun main(vararg args: String) = singleInstance(args) { ProcessManager.stop() }) } else { - KeyboardManager.registerKeyboardHook() } + KeyboardManager.registerKeyboardHook() if (!ApplicationParams.isAutostart) { ProcessManager.start() diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/ObserveHardwareReadings.kt b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/ObserveHardwareReadings.kt deleted file mode 100644 index 7f5cbdf..0000000 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/ObserveHardwareReadings.kt +++ /dev/null @@ -1,7 +0,0 @@ -package app.cleanmeter.target.desktop.data - -import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorReader - -object ObserveHardwareReadings { - val data = HardwareMonitorReader.currentData -} \ No newline at end of file diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt index 6943c86..acca7ef 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt +++ b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt @@ -2,7 +2,7 @@ package app.cleanmeter.target.desktop.ui.overlay import androidx.lifecycle.ViewModel import app.cleanmeter.core.common.hardwaremonitor.HardwareMonitorData -import app.cleanmeter.target.desktop.data.ObserveHardwareReadings +import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorReader import app.cleanmeter.target.desktop.data.OverlaySettingsRepository import app.cleanmeter.target.desktop.model.OverlaySettings import kotlinx.coroutines.CoroutineScope @@ -40,9 +40,11 @@ class OverlayViewModel : ViewModel() { private fun observeHwInfo() { CoroutineScope(Dispatchers.IO).launch { - ObserveHardwareReadings.data.collectLatest { hwInfoData -> - _state.update { it.copy(hardwareData = hwInfoData) } - } + HardwareMonitorReader + .currentData + .collectLatest { hwInfoData -> + _state.update { it.copy(hardwareData = hwInfoData) } + } } } } \ No newline at end of file diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt index 67eadb5..192baf6 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt +++ b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt @@ -3,9 +3,11 @@ package app.cleanmeter.target.desktop.ui.settings import androidx.compose.ui.unit.IntOffset import androidx.lifecycle.ViewModel import app.cleanmeter.core.common.hardwaremonitor.HardwareMonitorData +import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorReader import app.cleanmeter.core.os.hardwaremonitor.Packet import app.cleanmeter.core.os.hardwaremonitor.SocketClient -import app.cleanmeter.target.desktop.data.ObserveHardwareReadings +import app.cleanmeter.target.desktop.KeyboardEvent +import app.cleanmeter.target.desktop.KeyboardManager import app.cleanmeter.target.desktop.data.OverlaySettingsRepository import app.cleanmeter.target.desktop.model.OverlaySettings import kotlinx.coroutines.CoroutineScope @@ -13,12 +15,21 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File +import java.io.Writer data class SettingsState( val overlaySettings: OverlaySettings? = null, - val hardwareData: HardwareMonitorData? = null + val hardwareData: HardwareMonitorData? = null, + val isRecording: Boolean = false, ) sealed class SettingsEvent { @@ -43,9 +54,13 @@ class SettingsViewModel : ViewModel() { val state: Flow get() = _state + private val dataHistory = emptyList().toMutableList() + init { observeOverlaySettings() - observeHwInfo() + observeData() + observeRecordingHotkey() + observeRecordingState() } private fun observeOverlaySettings() { @@ -58,11 +73,46 @@ class SettingsViewModel : ViewModel() { } } - private fun observeHwInfo() { + private fun observeData() { CoroutineScope(Dispatchers.IO).launch { - ObserveHardwareReadings.data.collectLatest { hwInfoData -> - _state.update { it.copy(hardwareData = hwInfoData) } - } + HardwareMonitorReader + .currentData + .collectLatest { hwInfoData -> + _state.update { it.copy(hardwareData = hwInfoData) } + } + } + } + + private fun observeRecordingHotkey() { + CoroutineScope(Dispatchers.Default).launch { + KeyboardManager + .filter(KeyboardEvent.ToggleRecording) + .collectLatest { + println("Toggle recording ${_state.value.isRecording}") + _state.update { it.copy(isRecording = !it.isRecording) } + } + } + } + + private fun observeRecordingState() { + CoroutineScope(Dispatchers.Default).launch { + _state + .collectLatest { state -> + when { + state.isRecording && state.hardwareData != null -> { + dataHistory.add(state.hardwareData) + return@collectLatest + } + !state.isRecording && dataHistory.isNotEmpty() -> { + File("cleanmeter.recording.${System.currentTimeMillis()}.json").printWriter().use { + it.append(Json.encodeToString(dataHistory)) + dataHistory.clear() + } + } + dataHistory.isNotEmpty() -> dataHistory.clear() + else -> Unit + } + } } } @@ -145,6 +195,7 @@ class SettingsViewModel : ViewModel() { ) ) } + SensorType.Framerate -> settingsState.overlaySettings SensorType.Frametime -> settingsState.overlaySettings SensorType.TotalVramUsed -> settingsState.overlaySettings diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt index 0d2d2e3..45802e5 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt +++ b/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt @@ -1,23 +1,31 @@ package app.cleanmeter.target.desktop.ui.settings.tabs import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.ParagraphStyle import androidx.compose.ui.text.SpanStyle @@ -30,6 +38,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.cleanmeter.core.designsystem.LocalColorScheme import app.cleanmeter.core.designsystem.LocalTypography +import app.cleanmeter.target.desktop.ui.components.HotKeySymbol import app.cleanmeter.target.desktop.ui.components.section.CollapsibleSection @Composable @@ -101,6 +110,33 @@ internal fun HelpSettingsUi() { } ) } + + CollapsibleSection(title = "HOTKEYS") { + Hotkey(label = "Toggle the overlay", "F10") + Hotkey(label = "Toggle data recording", "F11") + } + } +} + +@Composable +private fun Hotkey(label: String, key: String) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = label, + color = LocalColorScheme.current.text.heading, + style = LocalTypography.current.labelLMedium, + modifier = Modifier.wrapContentHeight(), + ) + } + HotKeySymbol(listOf("Ctrl", "Alt", key)) } }