Skip to content

Commit 921ea5a

Browse files
authored
Merge pull request #49 from Danil0v3s/feat/recording
[Feat] Add recording
2 parents 72c6354 + 74300af commit 921ea5a

File tree

7 files changed

+113
-25
lines changed

7 files changed

+113
-25
lines changed

core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ object HardwareMonitorReader {
4040
val buffer = getByteBuffer(packet.data, hardware * HARDWARE_SIZE + sensor * SENSOR_SIZE, HEADER_SIZE)
4141
val hardwares = readHardware(buffer, hardware)
4242
val sensors = readSensor(buffer, sensor)
43-
_currentData = _currentData.copy(Hardwares = hardwares, Sensors = sensors)
43+
_currentData = _currentData.copy(Hardwares = hardwares, Sensors = sensors, LastPollTime = System.currentTimeMillis())
4444
_currentData
4545
}
4646

4747
is Packet.PresentMonApps -> {
4848
val appsCount = getByteBuffer(packet.data, LENGTH_SIZE, 0).short
4949
val buffer = getByteBuffer(packet.data, appsCount.toInt() * NAME_SIZE, LENGTH_SIZE)
5050
val apps = listOf("Auto") + readPresentMonApps(buffer, appsCount)
51-
_currentData = _currentData.copy(PresentMonApps = apps)
51+
_currentData = _currentData.copy(PresentMonApps = apps, LastPollTime = System.currentTimeMillis())
5252
_currentData
5353
}
5454

target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@ import com.github.kwhat.jnativehook.GlobalScreen
44
import com.github.kwhat.jnativehook.keyboard.NativeKeyEvent
55
import com.github.kwhat.jnativehook.keyboard.NativeKeyListener
66
import kotlinx.coroutines.channels.Channel
7+
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
78
import kotlinx.coroutines.flow.filterIsInstance
89
import kotlinx.coroutines.flow.receiveAsFlow
910

1011
sealed class KeyboardEvent {
1112
data object ToggleOverlay : KeyboardEvent()
13+
data object ToggleRecording : KeyboardEvent()
1214
}
1315

1416
internal object KeyboardManager {
1517

16-
private val _channel = Channel<KeyboardEvent>()
18+
private val _channel = Channel<KeyboardEvent>(CONFLATED)
1719
val events = _channel.receiveAsFlow()
1820

1921
fun filter(event: KeyboardEvent) = events.filterIsInstance(event::class)
@@ -25,11 +27,15 @@ internal object KeyboardManager {
2527
override fun nativeKeyReleased(nativeEvent: NativeKeyEvent) {
2628
val isCtrl = nativeEvent.modifiers.and(NativeKeyEvent.CTRL_MASK) > 0
2729
val isAlt = nativeEvent.modifiers.and(NativeKeyEvent.VC_ALT) > 0
28-
val isF10 = nativeEvent.keyCode == NativeKeyEvent.VC_F10
2930

30-
if (isCtrl && isAlt && isF10) {
31-
_channel.trySend(KeyboardEvent.ToggleOverlay)
31+
if (!isCtrl && !isAlt) return
32+
33+
val event = when (nativeEvent.keyCode) {
34+
NativeKeyEvent.VC_F10 -> KeyboardEvent.ToggleOverlay
35+
NativeKeyEvent.VC_F11 -> KeyboardEvent.ToggleRecording
36+
else -> null
3237
}
38+
event?.let { _channel.trySend(it) }
3339
}
3440
})
3541
} catch (e: Throwable) {

target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ServerMain.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ fun main(vararg args: String) = singleInstance(args) {
1717
ProcessManager.stop()
1818
})
1919
} else {
20-
KeyboardManager.registerKeyboardHook()
2120
}
21+
KeyboardManager.registerKeyboardHook()
2222

2323
if (!ApplicationParams.isAutostart) {
2424
ProcessManager.start()

target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/ObserveHardwareReadings.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.

target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package app.cleanmeter.target.desktop.ui.overlay
22

33
import androidx.lifecycle.ViewModel
44
import app.cleanmeter.core.common.hardwaremonitor.HardwareMonitorData
5-
import app.cleanmeter.target.desktop.data.ObserveHardwareReadings
5+
import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorReader
66
import app.cleanmeter.target.desktop.data.OverlaySettingsRepository
77
import app.cleanmeter.target.desktop.model.OverlaySettings
88
import kotlinx.coroutines.CoroutineScope
@@ -40,9 +40,11 @@ class OverlayViewModel : ViewModel() {
4040

4141
private fun observeHwInfo() {
4242
CoroutineScope(Dispatchers.IO).launch {
43-
ObserveHardwareReadings.data.collectLatest { hwInfoData ->
44-
_state.update { it.copy(hardwareData = hwInfoData) }
45-
}
43+
HardwareMonitorReader
44+
.currentData
45+
.collectLatest { hwInfoData ->
46+
_state.update { it.copy(hardwareData = hwInfoData) }
47+
}
4648
}
4749
}
4850
}

target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,33 @@ package app.cleanmeter.target.desktop.ui.settings
33
import androidx.compose.ui.unit.IntOffset
44
import androidx.lifecycle.ViewModel
55
import app.cleanmeter.core.common.hardwaremonitor.HardwareMonitorData
6+
import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorReader
67
import app.cleanmeter.core.os.hardwaremonitor.Packet
78
import app.cleanmeter.core.os.hardwaremonitor.SocketClient
8-
import app.cleanmeter.target.desktop.data.ObserveHardwareReadings
9+
import app.cleanmeter.target.desktop.KeyboardEvent
10+
import app.cleanmeter.target.desktop.KeyboardManager
911
import app.cleanmeter.target.desktop.data.OverlaySettingsRepository
1012
import app.cleanmeter.target.desktop.model.OverlaySettings
1113
import kotlinx.coroutines.CoroutineScope
1214
import kotlinx.coroutines.Dispatchers
1315
import kotlinx.coroutines.flow.Flow
1416
import kotlinx.coroutines.flow.MutableStateFlow
1517
import kotlinx.coroutines.flow.collectLatest
18+
import kotlinx.coroutines.flow.distinctUntilChangedBy
19+
import kotlinx.coroutines.flow.filter
20+
import kotlinx.coroutines.flow.flowOn
21+
import kotlinx.coroutines.flow.onEach
1622
import kotlinx.coroutines.flow.update
1723
import kotlinx.coroutines.launch
24+
import kotlinx.serialization.encodeToString
25+
import kotlinx.serialization.json.Json
26+
import java.io.File
27+
import java.io.Writer
1828

1929
data class SettingsState(
2030
val overlaySettings: OverlaySettings? = null,
21-
val hardwareData: HardwareMonitorData? = null
31+
val hardwareData: HardwareMonitorData? = null,
32+
val isRecording: Boolean = false,
2233
)
2334

2435
sealed class SettingsEvent {
@@ -43,9 +54,13 @@ class SettingsViewModel : ViewModel() {
4354
val state: Flow<SettingsState>
4455
get() = _state
4556

57+
private val dataHistory = emptyList<HardwareMonitorData>().toMutableList()
58+
4659
init {
4760
observeOverlaySettings()
48-
observeHwInfo()
61+
observeData()
62+
observeRecordingHotkey()
63+
observeRecordingState()
4964
}
5065

5166
private fun observeOverlaySettings() {
@@ -58,11 +73,46 @@ class SettingsViewModel : ViewModel() {
5873
}
5974
}
6075

61-
private fun observeHwInfo() {
76+
private fun observeData() {
6277
CoroutineScope(Dispatchers.IO).launch {
63-
ObserveHardwareReadings.data.collectLatest { hwInfoData ->
64-
_state.update { it.copy(hardwareData = hwInfoData) }
65-
}
78+
HardwareMonitorReader
79+
.currentData
80+
.collectLatest { hwInfoData ->
81+
_state.update { it.copy(hardwareData = hwInfoData) }
82+
}
83+
}
84+
}
85+
86+
private fun observeRecordingHotkey() {
87+
CoroutineScope(Dispatchers.Default).launch {
88+
KeyboardManager
89+
.filter(KeyboardEvent.ToggleRecording)
90+
.collectLatest {
91+
println("Toggle recording ${_state.value.isRecording}")
92+
_state.update { it.copy(isRecording = !it.isRecording) }
93+
}
94+
}
95+
}
96+
97+
private fun observeRecordingState() {
98+
CoroutineScope(Dispatchers.Default).launch {
99+
_state
100+
.collectLatest { state ->
101+
when {
102+
state.isRecording && state.hardwareData != null -> {
103+
dataHistory.add(state.hardwareData)
104+
return@collectLatest
105+
}
106+
!state.isRecording && dataHistory.isNotEmpty() -> {
107+
File("cleanmeter.recording.${System.currentTimeMillis()}.json").printWriter().use {
108+
it.append(Json.encodeToString(dataHistory))
109+
dataHistory.clear()
110+
}
111+
}
112+
dataHistory.isNotEmpty() -> dataHistory.clear()
113+
else -> Unit
114+
}
115+
}
66116
}
67117
}
68118

@@ -145,6 +195,7 @@ class SettingsViewModel : ViewModel() {
145195
)
146196
)
147197
}
198+
148199
SensorType.Framerate -> settingsState.overlaySettings
149200
SensorType.Frametime -> settingsState.overlaySettings
150201
SensorType.TotalVramUsed -> settingsState.overlaySettings

target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
package app.cleanmeter.target.desktop.ui.settings.tabs
22

33
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.border
45
import androidx.compose.foundation.layout.Arrangement
56
import androidx.compose.foundation.layout.Column
67
import androidx.compose.foundation.layout.Row
78
import androidx.compose.foundation.layout.Spacer
9+
import androidx.compose.foundation.layout.fillMaxWidth
810
import androidx.compose.foundation.layout.padding
911
import androidx.compose.foundation.layout.size
1012
import androidx.compose.foundation.layout.width
1113
import androidx.compose.foundation.layout.wrapContentHeight
1214
import androidx.compose.foundation.rememberScrollState
1315
import androidx.compose.foundation.shape.CircleShape
16+
import androidx.compose.foundation.shape.RoundedCornerShape
1417
import androidx.compose.foundation.text.ClickableText
1518
import androidx.compose.foundation.verticalScroll
19+
import androidx.compose.material.Icon
1620
import androidx.compose.material.Text
21+
import androidx.compose.material.icons.Icons
22+
import androidx.compose.material.icons.outlined.Info
1723
import androidx.compose.runtime.Composable
1824
import androidx.compose.ui.Alignment
1925
import androidx.compose.ui.Modifier
26+
import androidx.compose.ui.graphics.Color
2027
import androidx.compose.ui.platform.LocalUriHandler
28+
import androidx.compose.ui.res.painterResource
2129
import androidx.compose.ui.text.AnnotatedString
2230
import androidx.compose.ui.text.ParagraphStyle
2331
import androidx.compose.ui.text.SpanStyle
@@ -30,6 +38,7 @@ import androidx.compose.ui.unit.dp
3038
import androidx.compose.ui.unit.sp
3139
import app.cleanmeter.core.designsystem.LocalColorScheme
3240
import app.cleanmeter.core.designsystem.LocalTypography
41+
import app.cleanmeter.target.desktop.ui.components.HotKeySymbol
3342
import app.cleanmeter.target.desktop.ui.components.section.CollapsibleSection
3443

3544
@Composable
@@ -101,6 +110,33 @@ internal fun HelpSettingsUi() {
101110
}
102111
)
103112
}
113+
114+
CollapsibleSection(title = "HOTKEYS") {
115+
Hotkey(label = "Toggle the overlay", "F10")
116+
Hotkey(label = "Toggle data recording", "F11")
117+
}
118+
}
119+
}
120+
121+
@Composable
122+
private fun Hotkey(label: String, key: String) {
123+
Row(
124+
modifier = Modifier.fillMaxWidth(),
125+
verticalAlignment = Alignment.CenterVertically,
126+
horizontalArrangement = Arrangement.SpaceBetween
127+
) {
128+
Row(
129+
verticalAlignment = Alignment.CenterVertically,
130+
horizontalArrangement = Arrangement.spacedBy(8.dp),
131+
) {
132+
Text(
133+
text = label,
134+
color = LocalColorScheme.current.text.heading,
135+
style = LocalTypography.current.labelLMedium,
136+
modifier = Modifier.wrapContentHeight(),
137+
)
138+
}
139+
HotKeySymbol(listOf("Ctrl", "Alt", key))
104140
}
105141
}
106142

0 commit comments

Comments
 (0)