diff --git a/.gitignore b/.gitignore index 8c6bf45..3bad499 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ bin/ ### STT model ### /src/main/resources/STT-model/ +/src/main/resources/Semantic-model/ +/logs/ +/user.properties diff --git a/build.gradle.kts b/build.gradle.kts index 2e31ea6..7eda8c8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,9 @@ dependencies { // Core implementation(compose.desktop.currentOs) + // ConstrainLayout + implementation("tech.annexflow.compose:constraintlayout-compose-multiplatform:0.4.0") + // Material Design implementation(compose.materialIconsExtended) @@ -37,15 +40,10 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") // Voyager - // Navigator implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion") - // Screen Model implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion") - // BottomSheetNavigator implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion") - // TabNavigator implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion") - // Transitions implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion") // Deep Java Library @@ -57,8 +55,9 @@ dependencies { // Semantic Kernel implementation("com.microsoft.semantic-kernel:semantickernel-api:1.3.0") - // SLF4J - implementation("org.slf4j:slf4j-api:2.0.16") + // Logging + implementation ("io.github.oshai:kotlin-logging-jvm:7.0.0") + implementation ("ch.qos.logback:logback-classic:1.5.11") } compose.desktop { diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 357531d..016ec07 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,92 +1,44 @@ -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.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.application import cafe.adriel.voyager.navigator.CurrentScreen -import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.navigator.currentOrThrow -import kotlinx.coroutines.DelicateCoroutinesApi -import processing.SemanticSimilarity -import screens.Home -import screens.Settings +import io.github.oshai.kotlinlogging.KotlinLogging +import ui.screens.Home +import ui.TopBar +import java.io.File +import java.nio.file.Paths +import kotlin.io.path.absolutePathString + +val logger = KotlinLogging.logger {} fun main() = application { - val testSentence = "This is a test sentence for semantic similarity" - val testCommands = listOf("This is a test", "This sentence is completely different", "Testing semantic similarity between sentence", "An unrelated statement") - - val semanticTester = SemanticSimilarity() - semanticTester.compareSentenceToList(testSentence, testCommands) - + logger.info { "\n================== NEW APPLICATION SESSION ==================" } + checkLogsDir() Window(onCloseRequest = ::exitApplication) { - Navigator(Home()) { navigator -> - Scaffold( - topBar = { topBar(navigator.size) }, - content = { CurrentScreen() }, - bottomBar = { } - ) + MaterialTheme { + Navigator(Home()) { navigator -> + Scaffold( + topBar = { TopBar.topBar(navigator.size) }, + content = { CurrentScreen() }, + bottomBar = { } + ) + } } } + logger.info { "Application started" } } -@Composable -fun topBar(screenCount: Int) { - Row { - if (screenCount > 1) { - backButton() - } - 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" - ) +fun checkLogsDir() { + val dir = File(Paths.get("logs/").absolutePathString()) + if (!dir.exists()) { + dir.createNewFile() + logger.info { "Folder Created: logs/" } } } +//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 diff --git a/src/main/kotlin/Microphone.kt b/src/main/kotlin/Microphone.kt index e02a713..7ed34d7 100644 --- a/src/main/kotlin/Microphone.kt +++ b/src/main/kotlin/Microphone.kt @@ -6,6 +6,7 @@ import javax.sound.sampled.AudioSystem import javax.sound.sampled.DataLine import javax.sound.sampled.TargetDataLine import processing.STT +import ui.Capture.Companion.captureLogger /** * TODO: Documentation @@ -25,6 +26,7 @@ class Microphone { */ @OptIn(DelicateCoroutinesApi::class) fun startCapture(captureDuration: Int) { + captureLogger.info { "\n================== NEW CAPTURE SESSION ==================" } capturing = true source.open(audioFormat, source.bufferSize) source.start() @@ -36,8 +38,8 @@ class Microphone { while (capturing) { val audioBytes = source.read(buffer, 0, buffer.size) val result = stt.parseBuffer(buffer, audioBytes) - println("Captured Speech: $result") - //TODO: Pass onto semantic similarity + captureLogger.info { ("Captured Speech: $result") } + //TODO: Pass onto processing } stt.closeModel() } diff --git a/src/main/kotlin/UserSettings.kt b/src/main/kotlin/UserSettings.kt new file mode 100644 index 0000000..38978a1 --- /dev/null +++ b/src/main/kotlin/UserSettings.kt @@ -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() + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/processing/STT.kt b/src/main/kotlin/processing/STT.kt index f4b9ad3..4b7a68b 100644 --- a/src/main/kotlin/processing/STT.kt +++ b/src/main/kotlin/processing/STT.kt @@ -1,5 +1,6 @@ package processing +import logger import org.vosk.Model import org.vosk.Recognizer import java.nio.file.Paths @@ -12,33 +13,37 @@ class STT { /** * */ - private val model = Model(getModelPath()) - - /** - * - */ - 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 parseBuffer(buffer: ByteArray, audioBytes: Int): String? = if (recognizer.acceptWaveForm(buffer, audioBytes)) recognizer.result else recognizer.partialResult fun closeModel() { recognizer.close() } - /** - * - */ - private fun getModelPath() = Paths.get( - javaClass.getResource("/STT-model")!!.toURI() - ).absolutePathString() + companion object { + /** + * + */ + private val model = getModel() + /** + * + */ + 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) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/processing/SemanticSimilarity.kt b/src/main/kotlin/processing/SemanticSimilarity.kt index ab08408..035555c 100644 --- a/src/main/kotlin/processing/SemanticSimilarity.kt +++ b/src/main/kotlin/processing/SemanticSimilarity.kt @@ -1,73 +1,77 @@ package processing -import ai.djl.Application -import ai.djl.Model -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.huggingface.tokenizers.HuggingFaceTokenizer +import ai.djl.inference.Predictor import ai.djl.ndarray.NDArray import ai.djl.ndarray.NDList import ai.djl.ndarray.NDManager import ai.djl.repository.zoo.Criteria import ai.djl.training.util.ProgressBar -import ai.djl.translate.DeferredTranslatorFactory +import logger import java.nio.file.Paths -import kotlin.math.sqrt + +// Model generated using the djl-converter utility. Then moved to resources before build class SemanticSimilarity { - val manager = NDManager.newBaseManager() - val model = getCriteria().loadModel().newPredictor() - val tokenizer = HuggingFaceTokenizer.newInstance("sentence-transformers/all-mpnet-base-v2") - - fun getCriteria(): Criteria = 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) { - 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 { + val manager = NDManager.newBaseManager() + val model = getModel() + val tokenizer = getTokenizer() + + fun getModel(): Predictor { + 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 = 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): List { + 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 { val inputVec = input.squeeze(0) val commandVec = command.squeeze(0) val dotProduct = input.matMul(commandVec).getFloat() - val normInput = calculateNorm(inputVec) - val normCommand = calculateNorm(commandVec) + val normInput = inputVec.norm().getFloat() + val normCommand = commandVec.norm().getFloat() return dotProduct / (normInput * normCommand) } - fun calculateNorm(array: NDArray) = sqrt(array.dot(array).getFloat()) + data class SimilarityScore ( + val sentence: String, + val score: Float, + ) } } \ No newline at end of file diff --git a/src/main/kotlin/screens/Home.kt b/src/main/kotlin/screens/Home.kt deleted file mode 100644 index 95973c3..0000000 --- a/src/main/kotlin/screens/Home.kt +++ /dev/null @@ -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 { - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/screens/Profiles.kt b/src/main/kotlin/screens/Profiles.kt deleted file mode 100644 index 3f39faa..0000000 --- a/src/main/kotlin/screens/Profiles.kt +++ /dev/null @@ -1,4 +0,0 @@ -package screens - -class Profiles { -} \ No newline at end of file diff --git a/src/main/kotlin/screens/Settings.kt b/src/main/kotlin/screens/Settings.kt deleted file mode 100644 index 085437f..0000000 --- a/src/main/kotlin/screens/Settings.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/ui/Capture.kt b/src/main/kotlin/ui/Capture.kt new file mode 100644 index 0000000..7d48d66 --- /dev/null +++ b/src/main/kotlin/ui/Capture.kt @@ -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 + + } +} \ No newline at end of file diff --git a/src/main/kotlin/ui/TopBar.kt b/src/main/kotlin/ui/TopBar.kt new file mode 100644 index 0000000..837df22 --- /dev/null +++ b/src/main/kotlin/ui/TopBar.kt @@ -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" + ) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ui/screens/Home.kt b/src/main/kotlin/ui/screens/Home.kt new file mode 100644 index 0000000..c7927f1 --- /dev/null +++ b/src/main/kotlin/ui/screens/Home.kt @@ -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() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/ui/screens/Profiles.kt b/src/main/kotlin/ui/screens/Profiles.kt new file mode 100644 index 0000000..8899786 --- /dev/null +++ b/src/main/kotlin/ui/screens/Profiles.kt @@ -0,0 +1,4 @@ +package ui.screens + +class Profiles { +} \ No newline at end of file diff --git a/src/main/kotlin/ui/screens/Settings.kt b/src/main/kotlin/ui/screens/Settings.kt new file mode 100644 index 0000000..50c18e1 --- /dev/null +++ b/src/main/kotlin/ui/screens/Settings.kt @@ -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) { + when(setting.key) { + "capture_duration" -> Capture.captureDurationSlider() + "ai_name" -> null //TODO: TextField + else -> null + } + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/window_control/WindowController.kt b/src/main/kotlin/window_control/WindowController.kt index 2971d55..acdc5b7 100644 --- a/src/main/kotlin/window_control/WindowController.kt +++ b/src/main/kotlin/window_control/WindowController.kt @@ -6,7 +6,6 @@ import com.sun.jna.platform.win32.WinDef import com.sun.jna.win32.W32APIOptions import java.awt.Point import java.awt.Robot -import java.awt.event.KeyEvent class WindowController { //TODO: Class that controls program window 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) { - keyCodes.iterator().forEach { keyCode -> + keyCodes.forEach { keyCode -> inputController.keyPress(keyCode) } } fun sendKeyReleases(keyCodes: Array) { - keyCodes.iterator().forEach { keyCode -> + keyCodes.forEach { keyCode -> inputController.keyRelease(keyCode) } } fun sendKeyStrokes(keyCodes: Array, delay: Int?) { //TODO: Create KeyList Class - keyCodes.iterator().forEach { keyCode -> + keyCodes.forEach { keyCode -> sendKeyPresses(arrayOf(keyCode)) inputController.delay(delay!!) sendKeyReleases(arrayOf(keyCode)) @@ -39,7 +38,7 @@ class WindowController { //TODO: Class that controls program window } fun sendMultiKeyStrokes(commands: Array>, delay: Int?) { //TODO: Create MultiKeyList Class - commands.iterator().forEach { command -> + commands.forEach { command -> sendMousePresses(command) inputController.delay(delay!!) sendMouseReleases(command) @@ -47,12 +46,12 @@ class WindowController { //TODO: Class that controls program window } fun sendMousePresses(buttonCodes: Array) { //TODO: Create ButtonList Class - buttonCodes.iterator().forEach { buttonCode -> + buttonCodes.forEach { buttonCode -> inputController.mousePress(buttonCode) } } fun sendMouseReleases(buttonCodes: Array) { - buttonCodes.iterator().forEach { buttonCode -> + buttonCodes.forEach { buttonCode -> inputController.mouseRelease(buttonCode) } } @@ -60,7 +59,7 @@ class WindowController { //TODO: Class that controls program window inputController.mouseMove(location.x, location.y) } fun sendMouseStrokes(buttonCodes: Array, delay: Int?) { - buttonCodes.iterator().forEach { buttonCode -> + buttonCodes.forEach { buttonCode -> sendMousePresses(arrayOf(buttonCode)) inputController.delay(delay!!) sendMouseReleases(arrayOf(buttonCode)) @@ -68,12 +67,12 @@ class WindowController { //TODO: Class that controls program window } fun sendMouseStrokesAtLocation(buttonCodes: Array, delay: Int?, location: Point) { sendMouseLocation(location) - buttonCodes.iterator().forEach { buttonCode -> + buttonCodes.forEach { buttonCode -> sendMouseStrokes(arrayOf(buttonCode), delay) } } fun sendMouseStrokesAtLocations(commands: Array>>, delay: Int?) { - commands.iterator().forEach { command -> + commands.forEach { command -> sendMouseLocation(command.first) sendMouseStrokes(command.second, delay) } @@ -89,7 +88,7 @@ class WindowController { //TODO: Class that controls program window } private fun activateLinuxWindow(windowName: String) { 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) { val command = arrayOf("hyprctl", "dispatch", "focuswindow", windowName) //TODO: need to check both class regex and title regex (look at hyprland wiki) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..74f4620 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + logs/capture.log + true + + %d{yyyy-MM-dd HH:mm:ss} - %msg%n + + + + + + logs/application.log + true + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + diff --git a/src/main/resources/user.properties b/src/main/resources/user.properties deleted file mode 100644 index dade9ad..0000000 --- a/src/main/resources/user.properties +++ /dev/null @@ -1 +0,0 @@ -capture_duration=5 \ No newline at end of file