settings defaults. logging. misc

This commit is contained in:
Joshua Perry 2024-10-25 20:52:47 +01:00
parent 6f94f60b15
commit 0b254654ef
18 changed files with 434 additions and 266 deletions

3
.gitignore vendored
View File

@ -46,3 +46,6 @@ bin/
### STT model ### ### STT model ###
/src/main/resources/STT-model/ /src/main/resources/STT-model/
/src/main/resources/Semantic-model/
/logs/
/user.properties

View File

@ -23,6 +23,9 @@ dependencies {
// Core // Core
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)
// ConstrainLayout
implementation("tech.annexflow.compose:constraintlayout-compose-multiplatform:0.4.0")
// Material Design // Material Design
implementation(compose.materialIconsExtended) implementation(compose.materialIconsExtended)
@ -37,15 +40,10 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
// Voyager // Voyager
// Navigator
implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion") implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")
// Screen Model
implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion") implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion")
// BottomSheetNavigator
implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion") implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion")
// TabNavigator
implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion") implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion")
// Transitions
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion") implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
// Deep Java Library // Deep Java Library
@ -57,8 +55,9 @@ dependencies {
// Semantic Kernel // Semantic Kernel
implementation("com.microsoft.semantic-kernel:semantickernel-api:1.3.0") implementation("com.microsoft.semantic-kernel:semantickernel-api:1.3.0")
// SLF4J // Logging
implementation("org.slf4j:slf4j-api:2.0.16") implementation ("io.github.oshai:kotlin-logging-jvm:7.0.0")
implementation ("ch.qos.logback:logback-classic:1.5.11")
} }
compose.desktop { compose.desktop {

View File

@ -1,92 +1,44 @@
import androidx.compose.foundation.layout.Row import androidx.compose.material.MaterialTheme
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Stop
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.Window import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application import androidx.compose.ui.window.application
import cafe.adriel.voyager.navigator.CurrentScreen import cafe.adriel.voyager.navigator.CurrentScreen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.DelicateCoroutinesApi import ui.screens.Home
import processing.SemanticSimilarity import ui.TopBar
import screens.Home import java.io.File
import screens.Settings import java.nio.file.Paths
import kotlin.io.path.absolutePathString
val logger = KotlinLogging.logger {}
fun main() = application { fun main() = application {
val testSentence = "This is a test sentence for semantic similarity" logger.info { "\n================== NEW APPLICATION SESSION ==================" }
val testCommands = listOf("This is a test", "This sentence is completely different", "Testing semantic similarity between sentence", "An unrelated statement") checkLogsDir()
val semanticTester = SemanticSimilarity()
semanticTester.compareSentenceToList(testSentence, testCommands)
Window(onCloseRequest = ::exitApplication) { Window(onCloseRequest = ::exitApplication) {
Navigator(Home()) { navigator -> MaterialTheme {
Scaffold( Navigator(Home()) { navigator ->
topBar = { topBar(navigator.size) }, Scaffold(
content = { CurrentScreen() }, topBar = { TopBar.topBar(navigator.size) },
bottomBar = { } content = { CurrentScreen() },
) bottomBar = { }
)
}
} }
} }
logger.info { "Application started" }
} }
@Composable fun checkLogsDir() {
fun topBar(screenCount: Int) { val dir = File(Paths.get("logs/").absolutePathString())
Row { if (!dir.exists()) {
if (screenCount > 1) { dir.createNewFile()
backButton() logger.info { "Folder Created: logs/" }
}
captureButton()
Settings.settingsButton()
}
}
@Composable
fun backButton() {
val navigator = LocalNavigator.currentOrThrow
Button(onClick = { navigator.pop() }) {
Icon(
imageVector = Icons.Filled.ArrowBackIosNew,
contentDescription = "Go Back"
)
}
}
val captureDuration = Settings.userProperties.getProperty("capture_duration").toInt()
@Composable
fun getCaptureIcon(capturing: Boolean) = if (!capturing) Icons.Filled.PlayArrow else Icons.Filled.Stop
@Composable
fun getCaptureDescription(capturing: Boolean) = if (!capturing) "Start" else "Stop"
fun getCaptureOnClick(mic: Microphone) = if (!mic.capturing) mic.startCapture(captureDuration) else mic.stopCapture()
@OptIn(DelicateCoroutinesApi::class)
@Composable
fun captureButton() {
val mic = Microphone()
var isCapturing by rememberSaveable { mutableStateOf(mic.capturing) }
Button(
onClick = {
getCaptureOnClick(mic)
isCapturing = !isCapturing
}
) {
Icon(
imageVector = getCaptureIcon(isCapturing),
contentDescription = getCaptureDescription(isCapturing) + " audio capture"
)
} }
} }
//TODO: If folders (Semantic-model/, STT-model/) don't exist on start-up install
// Install Semantic-model by downloading, using djl-converter (look into using pip module from Java), move to resources
// Install STT-model by downloading, move to resources

View File

@ -6,6 +6,7 @@ import javax.sound.sampled.AudioSystem
import javax.sound.sampled.DataLine import javax.sound.sampled.DataLine
import javax.sound.sampled.TargetDataLine import javax.sound.sampled.TargetDataLine
import processing.STT import processing.STT
import ui.Capture.Companion.captureLogger
/** /**
* TODO: Documentation * TODO: Documentation
@ -25,6 +26,7 @@ class Microphone {
*/ */
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
fun startCapture(captureDuration: Int) { fun startCapture(captureDuration: Int) {
captureLogger.info { "\n================== NEW CAPTURE SESSION ==================" }
capturing = true capturing = true
source.open(audioFormat, source.bufferSize) source.open(audioFormat, source.bufferSize)
source.start() source.start()
@ -36,8 +38,8 @@ class Microphone {
while (capturing) { while (capturing) {
val audioBytes = source.read(buffer, 0, buffer.size) val audioBytes = source.read(buffer, 0, buffer.size)
val result = stt.parseBuffer(buffer, audioBytes) val result = stt.parseBuffer(buffer, audioBytes)
println("Captured Speech: $result") captureLogger.info { ("Captured Speech: $result") }
//TODO: Pass onto semantic similarity //TODO: Pass onto processing
} }
stt.closeModel() stt.closeModel()
} }

View File

@ -0,0 +1,40 @@
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.file.Paths
import java.util.Properties
import kotlin.io.path.absolutePathString
class UserSettings: Properties() {
init {
if (!file.exists()) {
file.createNewFile()
defaults()
save()
logger.info { "File Created: user.settings initialized with defaults" }
}
else {
val stream = FileInputStream(file)
load(stream)
stream.close()
}
}
fun defaults() {
setProperty("capture_duration", "5")
setProperty("ai_name", "Mia")
}
fun save() {
val stream = FileOutputStream(file)
store(stream, null)
stream.flush()
stream.close()
}
companion object {
private val file = File(
Paths.get("user.settings").absolutePathString()
)
}
}

View File

@ -1,5 +1,6 @@
package processing package processing
import logger
import org.vosk.Model import org.vosk.Model
import org.vosk.Recognizer import org.vosk.Recognizer
import java.nio.file.Paths import java.nio.file.Paths
@ -12,33 +13,37 @@ class STT {
/** /**
* *
*/ */
private val model = Model(getModelPath()) fun parseBuffer(buffer: ByteArray, audioBytes: Int): String? = if (recognizer.acceptWaveForm(buffer, audioBytes)) recognizer.result else recognizer.partialResult
/**
*
*/
private val recognizer = Recognizer(model, 16000.0f)
/**
*
*/
fun parseBuffer(buffer: ByteArray, audioBytes: Int): String? {
return if (recognizer.acceptWaveForm(buffer, audioBytes)) {
recognizer.result
} else {
recognizer.partialResult
}
}
fun closeModel() { fun closeModel() {
recognizer.close() recognizer.close()
} }
/** companion object {
* /**
*/ *
private fun getModelPath() = Paths.get( */
javaClass.getResource("/STT-model")!!.toURI() private val model = getModel()
).absolutePathString() /**
*
*/
private val recognizer = getRecognizer()
/**
*
*/
private fun getModelPath() = Paths.get(
javaClass.getResource("/STT-model")!!.toURI()
).absolutePathString()
private fun getModel(): Model {
logger.info { "STT model initializing..." }
return Model(getModelPath())
}
private fun getRecognizer(): Recognizer {
logger.info { "STT model initialized" }
return Recognizer(model, 16000.0f)
}
}
} }

View File

@ -1,73 +1,77 @@
package processing package processing
import ai.djl.Application import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer
import ai.djl.Model import ai.djl.inference.Predictor
import ai.djl.huggingface.tokenizers.Encoding
import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
import ai.djl.huggingface.translator.TextEmbeddingTranslator
import ai.djl.huggingface.translator.TextEmbeddingTranslatorFactory
import ai.djl.huggingface.zoo.HfModelZoo
import ai.djl.ndarray.NDArray import ai.djl.ndarray.NDArray
import ai.djl.ndarray.NDList import ai.djl.ndarray.NDList
import ai.djl.ndarray.NDManager import ai.djl.ndarray.NDManager
import ai.djl.repository.zoo.Criteria import ai.djl.repository.zoo.Criteria
import ai.djl.training.util.ProgressBar import ai.djl.training.util.ProgressBar
import ai.djl.translate.DeferredTranslatorFactory import logger
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.math.sqrt
// Model generated using the djl-converter utility. Then moved to resources before build
class SemanticSimilarity { class SemanticSimilarity {
val manager = NDManager.newBaseManager()
val model = getCriteria().loadModel().newPredictor()
val tokenizer = HuggingFaceTokenizer.newInstance("sentence-transformers/all-mpnet-base-v2")
fun getCriteria(): Criteria<NDList, NDList> = Criteria.builder()
.setTypes(NDList::class.java, NDList::class.java)
.optModelPath(Paths.get(
javaClass.getResource("/Semantic-model")!!.toURI()
))
.optProgress(ProgressBar())
.build()
fun compareSentenceToList(sentence: String, list: List<String>) {
val sentenceEmbedding = generateEmbedding(sentence)
val similarities = list.map { string ->
Pair(
string,
cosineSimilarity(sentenceEmbedding, generateEmbedding(string))
)
}
similarities.forEach { similarity ->
println("Similarity between '$sentence' and '${similarity.first}': ${similarity.second}")
}
}
fun generateEmbedding(input: String): NDArray {
val inputList = tokenizer.encode(input).toNDList(manager, false)
val inputIds = inputList[0].expandDims(0)
val attentionMask = inputList[1].expandDims(0)
return model.predict(
NDList(inputIds, attentionMask)
)[0].mean(intArrayOf(1)).normalize(2.0, 1)
}
companion object { companion object {
val manager = NDManager.newBaseManager()
val model = getModel()
val tokenizer = getTokenizer()
fun getModel(): Predictor<NDList, NDList> {
logger.info { "Semantic similarity model initialized" }
return getCriteria().loadModel().newPredictor()
}
fun getTokenizer(): HuggingFaceTokenizer {
logger.info { "Semantic similarity tokenizer initialized" }
return HuggingFaceTokenizer.newInstance("sentence-transformers/all-mpnet-base-v2")
}
fun getCriteria(): Criteria<NDList, NDList> = Criteria.builder()
.setTypes(NDList::class.java, NDList::class.java)
.optModelPath(Paths.get(
javaClass.getResource("/Semantic-model")!!.toURI()
))
.optProgress(ProgressBar())
.build()
fun compareSentenceToList(sentence: String, list: List<String>): List<SimilarityScore> {
val sentenceEmbedding = generateEmbedding(sentence)
val similarities = list.map { string ->
SimilarityScore(
string,
cosineSimilarity(sentenceEmbedding, generateEmbedding(string))
)
}
return similarities
}
fun generateEmbedding(input: String): NDArray {
val inputList = tokenizer.encode(input).toNDList(manager, false)
val inputIds = inputList[0].expandDims(0)
val attentionMask = inputList[1].expandDims(0)
return model.predict(
NDList(inputIds, attentionMask)
)[0].mean(intArrayOf(1)).normalize(2.0, 1)
}
fun cosineSimilarity(input: NDArray, command: NDArray): Float { fun cosineSimilarity(input: NDArray, command: NDArray): Float {
val inputVec = input.squeeze(0) val inputVec = input.squeeze(0)
val commandVec = command.squeeze(0) val commandVec = command.squeeze(0)
val dotProduct = input.matMul(commandVec).getFloat() val dotProduct = input.matMul(commandVec).getFloat()
val normInput = calculateNorm(inputVec) val normInput = inputVec.norm().getFloat()
val normCommand = calculateNorm(commandVec) val normCommand = commandVec.norm().getFloat()
return dotProduct / (normInput * normCommand) return dotProduct / (normInput * normCommand)
} }
fun calculateNorm(array: NDArray) = sqrt(array.dot(array).getFloat()) data class SimilarityScore (
val sentence: String,
val score: Float,
)
} }
} }

View File

@ -1,33 +0,0 @@
package screens
import Microphone
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Stop
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.graphics.vector.ImageVector
import cafe.adriel.voyager.core.screen.Screen
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
class Home: Screen {
@Composable
@Preview
override fun Content() {
MaterialTheme {
Row {
}
}
}
}

View File

@ -1,4 +0,0 @@
package screens
class Profiles {
}

View File

@ -1,58 +0,0 @@
package screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import java.io.FileInputStream
import java.util.Properties
class Settings: Screen {
@Composable
override fun Content() {
Column {
userProperties.iterator().forEach { property ->
Row { //TODO: Make look nice
Text("${property.key}")
Text("${property.value}") //TODO: This should be a different input element dependent on expected dtype (maybe use prefixes to determine)
}
}
}
}
companion object {
val userProperties = getProperties()
@Composable
fun settingsButton() {
val navigator = LocalNavigator.currentOrThrow
Button(onClick = {
navigator.push(Settings())
}) {
Icon(
Icons.Filled.Settings,
contentDescription = "screens.Settings"
)
}
}
fun getProperties(): Properties {
val properties = Properties()
properties.load(
FileInputStream(
javaClass.getResource("/user.properties")!!.file
)
)
return properties
}
}
}

View File

@ -0,0 +1,86 @@
package ui
import Microphone
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.Slider
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Stop
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.DelicateCoroutinesApi
import logger
import ui.screens.Settings
import ui.screens.Settings.Companion.userSettings
import kotlin.math.roundToInt
class Capture {
companion object {
val captureLogger = KotlinLogging.logger("capture_logger")
fun getCaptureDuration() = userSettings.getProperty("capture_duration").toInt()
fun getCaptureOnClick(mic: Microphone) = if (!mic.capturing) mic.startCapture(getCaptureDuration()) else mic.stopCapture()
fun getCaptureIcon(capturing: Boolean) = if (!capturing) Icons.Filled.PlayArrow else Icons.Filled.Stop
fun getCaptureDescription(capturing: Boolean) = if (!capturing) "Start" else "Stop"
@Composable
fun captureContext(modifier: Modifier) {
Column(modifier) {
captureButton()
lastCapture()
}
}
@OptIn(DelicateCoroutinesApi::class)
@Composable
fun captureButton() {
val mic = Microphone()
var isCapturing by rememberSaveable { mutableStateOf(mic.capturing) }
Button(
onClick = {
getCaptureOnClick(mic)
isCapturing = !isCapturing
}
) {
Icon(
imageVector = getCaptureIcon(isCapturing),
contentDescription = getCaptureDescription(isCapturing) + " audio capture"
)
}
}
@Composable
fun captureDurationSlider() {
var sliderPosition by remember { mutableStateOf(getCaptureDuration().toFloat()) }
Text("Capture Duration")
Slider(
valueRange = 1.0f..20.0f,
value = sliderPosition,
onValueChange = {
sliderPosition = it.roundToInt().toFloat()
userSettings.setProperty("capture_duration", "${it.roundToInt()}")
userSettings.save()
logger.info { "User Setting Changed: capture_duration = ${it.roundToInt()}" }
}
)
Text("$sliderPosition seconds")
}
@Composable
fun captureLog() {} //TODO: Display capture.{date}.log to screen
@Composable
fun lastCapture() {} //TODO: Display capture.{date}.log last added line
}
}

View File

@ -0,0 +1,59 @@
package ui
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import ui.screens.Settings
class TopBar {
companion object {
@Composable
fun topBar(screenCount: Int) {
ConstraintLayout(
Modifier
.fillMaxWidth()
) {
val (back, capture, settings) = createRefs()
if (screenCount > 1) {
backButton(Modifier.constrainAs(back) {
top.linkTo(parent.top)
start.linkTo(parent.start, margin = 10.dp)
})
}
Capture.captureContext(Modifier.constrainAs(capture) {
top.linkTo(parent.top)
centerHorizontallyTo(parent)
})
Settings.settingsButton(Modifier.constrainAs(settings) {
top.linkTo(parent.top)
end.linkTo(parent.end, margin = 10.dp)
})
}
}
@Composable
fun backButton(modifier: Modifier) {
val navigator = LocalNavigator.currentOrThrow
Button(
modifier = modifier,
onClick = { navigator.pop()
}) {
Icon(
imageVector = Icons.Filled.ArrowBackIosNew,
contentDescription = "Go Back"
)
}
}
}
}

View File

@ -0,0 +1,15 @@
package ui.screens
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.screen.Screen
import ui.Capture
class Home: Screen {
@Composable
@Preview
override fun Content() {
Capture.captureLog()
}
}

View File

@ -0,0 +1,4 @@
package ui.screens
class Profiles {
}

View File

@ -0,0 +1,58 @@
package ui.screens
import UserSettings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import ui.Capture
class Settings: Screen {
@Composable
override fun Content() {
Column {
userSettings.forEach { setting ->
Spacer(Modifier.padding(10.dp))
setting(setting)
}
}
}
companion object {
val userSettings = UserSettings()
@Composable
fun settingsButton(modifier: Modifier) {
val navigator = LocalNavigator.currentOrThrow
Button(
modifier = modifier,
onClick = {
navigator.push(Settings())
}) {
Icon(
Icons.Filled.Settings,
contentDescription = "Settings"
)
}
}
@Composable
fun setting(setting: Map.Entry<Any?, Any?>) {
when(setting.key) {
"capture_duration" -> Capture.captureDurationSlider()
"ai_name" -> null //TODO: TextField
else -> null
}
}
}
}

View File

@ -6,7 +6,6 @@ import com.sun.jna.platform.win32.WinDef
import com.sun.jna.win32.W32APIOptions import com.sun.jna.win32.W32APIOptions
import java.awt.Point import java.awt.Point
import java.awt.Robot import java.awt.Robot
import java.awt.event.KeyEvent
class WindowController { //TODO: Class that controls program window class WindowController { //TODO: Class that controls program window
private val os = OS.valueOf(System.getProperty("os.name")) private val os = OS.valueOf(System.getProperty("os.name"))
@ -21,17 +20,17 @@ class WindowController { //TODO: Class that controls program window
} }
fun sendKeyPresses(keyCodes: Array<Int>) { fun sendKeyPresses(keyCodes: Array<Int>) {
keyCodes.iterator().forEach { keyCode -> keyCodes.forEach { keyCode ->
inputController.keyPress(keyCode) inputController.keyPress(keyCode)
} }
} }
fun sendKeyReleases(keyCodes: Array<Int>) { fun sendKeyReleases(keyCodes: Array<Int>) {
keyCodes.iterator().forEach { keyCode -> keyCodes.forEach { keyCode ->
inputController.keyRelease(keyCode) inputController.keyRelease(keyCode)
} }
} }
fun sendKeyStrokes(keyCodes: Array<Int>, delay: Int?) { //TODO: Create KeyList Class fun sendKeyStrokes(keyCodes: Array<Int>, delay: Int?) { //TODO: Create KeyList Class
keyCodes.iterator().forEach { keyCode -> keyCodes.forEach { keyCode ->
sendKeyPresses(arrayOf(keyCode)) sendKeyPresses(arrayOf(keyCode))
inputController.delay(delay!!) inputController.delay(delay!!)
sendKeyReleases(arrayOf(keyCode)) sendKeyReleases(arrayOf(keyCode))
@ -39,7 +38,7 @@ class WindowController { //TODO: Class that controls program window
} }
fun sendMultiKeyStrokes(commands: Array<Array<Int>>, delay: Int?) { //TODO: Create MultiKeyList Class fun sendMultiKeyStrokes(commands: Array<Array<Int>>, delay: Int?) { //TODO: Create MultiKeyList Class
commands.iterator().forEach { command -> commands.forEach { command ->
sendMousePresses(command) sendMousePresses(command)
inputController.delay(delay!!) inputController.delay(delay!!)
sendMouseReleases(command) sendMouseReleases(command)
@ -47,12 +46,12 @@ class WindowController { //TODO: Class that controls program window
} }
fun sendMousePresses(buttonCodes: Array<Int>) { //TODO: Create ButtonList Class fun sendMousePresses(buttonCodes: Array<Int>) { //TODO: Create ButtonList Class
buttonCodes.iterator().forEach { buttonCode -> buttonCodes.forEach { buttonCode ->
inputController.mousePress(buttonCode) inputController.mousePress(buttonCode)
} }
} }
fun sendMouseReleases(buttonCodes: Array<Int>) { fun sendMouseReleases(buttonCodes: Array<Int>) {
buttonCodes.iterator().forEach { buttonCode -> buttonCodes.forEach { buttonCode ->
inputController.mouseRelease(buttonCode) inputController.mouseRelease(buttonCode)
} }
} }
@ -60,7 +59,7 @@ class WindowController { //TODO: Class that controls program window
inputController.mouseMove(location.x, location.y) inputController.mouseMove(location.x, location.y)
} }
fun sendMouseStrokes(buttonCodes: Array<Int>, delay: Int?) { fun sendMouseStrokes(buttonCodes: Array<Int>, delay: Int?) {
buttonCodes.iterator().forEach { buttonCode -> buttonCodes.forEach { buttonCode ->
sendMousePresses(arrayOf(buttonCode)) sendMousePresses(arrayOf(buttonCode))
inputController.delay(delay!!) inputController.delay(delay!!)
sendMouseReleases(arrayOf(buttonCode)) sendMouseReleases(arrayOf(buttonCode))
@ -68,12 +67,12 @@ class WindowController { //TODO: Class that controls program window
} }
fun sendMouseStrokesAtLocation(buttonCodes: Array<Int>, delay: Int?, location: Point) { fun sendMouseStrokesAtLocation(buttonCodes: Array<Int>, delay: Int?, location: Point) {
sendMouseLocation(location) sendMouseLocation(location)
buttonCodes.iterator().forEach { buttonCode -> buttonCodes.forEach { buttonCode ->
sendMouseStrokes(arrayOf(buttonCode), delay) sendMouseStrokes(arrayOf(buttonCode), delay)
} }
} }
fun sendMouseStrokesAtLocations(commands: Array<Pair<Point,Array<Int>>>, delay: Int?) { fun sendMouseStrokesAtLocations(commands: Array<Pair<Point,Array<Int>>>, delay: Int?) {
commands.iterator().forEach { command -> commands.forEach { command ->
sendMouseLocation(command.first) sendMouseLocation(command.first)
sendMouseStrokes(command.second, delay) sendMouseStrokes(command.second, delay)
} }
@ -89,7 +88,7 @@ class WindowController { //TODO: Class that controls program window
} }
private fun activateLinuxWindow(windowName: String) { private fun activateLinuxWindow(windowName: String) {
val xorgFlag = System.getenv("DISPLAY") val xorgFlag = System.getenv("DISPLAY")
val hyprlandFlag = System.getenv("HYPRLAND_CMD") //TODO: User override of default checks, and add sway compatibility val hyprlandFlag = System.getenv("HYPRLAND_CMD") //TODO: User override of default checks (can specify what platform), and add sway compatibility
if (hyprlandFlag != null) { if (hyprlandFlag != null) {
val command = arrayOf("hyprctl", "dispatch", "focuswindow", windowName) //TODO: need to check both class regex and title regex (look at hyprland wiki) val command = arrayOf("hyprctl", "dispatch", "focuswindow", windowName) //TODO: need to check both class regex and title regex (look at hyprland wiki)

View File

@ -0,0 +1,38 @@
<configuration>
<!-- Define the log levels -->
<logger name="capture_logger" level="DEBUG">
<appender-ref ref="CAPTURE_LOGS_FILE" />
<appender-ref ref="STDOUT" />
</logger>
<root level="INFO">
<appender-ref ref="APPLICATION_LOGS_FILE" />
<appender-ref ref="STDOUT" />
</root>
<!-- Define the specific class appender -->
<appender name="CAPTURE_LOGS_FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/capture.log</file>
<append>true</append>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} - %msg%n</pattern>
</encoder>
</appender>
<!-- Define the appender for other logs -->
<appender name="APPLICATION_LOGS_FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/application.log</file>
<append>true</append>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Define the STDOUT appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</configuration>

View File

@ -1 +0,0 @@
capture_duration=5