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 ###
/src/main/resources/STT-model/
/src/main/resources/Semantic-model/
/logs/
/user.properties

View File

@ -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 {

View File

@ -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

View File

@ -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()
}

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
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)
}
}
}

View File

@ -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<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 {
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 {
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,
)
}
}

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 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<Int>) {
keyCodes.iterator().forEach { keyCode ->
keyCodes.forEach { keyCode ->
inputController.keyPress(keyCode)
}
}
fun sendKeyReleases(keyCodes: Array<Int>) {
keyCodes.iterator().forEach { keyCode ->
keyCodes.forEach { keyCode ->
inputController.keyRelease(keyCode)
}
}
fun sendKeyStrokes(keyCodes: Array<Int>, 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<Array<Int>>, 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<Int>) { //TODO: Create ButtonList Class
buttonCodes.iterator().forEach { buttonCode ->
buttonCodes.forEach { buttonCode ->
inputController.mousePress(buttonCode)
}
}
fun sendMouseReleases(buttonCodes: Array<Int>) {
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<Int>, 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<Int>, delay: Int?, location: Point) {
sendMouseLocation(location)
buttonCodes.iterator().forEach { buttonCode ->
buttonCodes.forEach { buttonCode ->
sendMouseStrokes(arrayOf(buttonCode), delay)
}
}
fun sendMouseStrokesAtLocations(commands: Array<Pair<Point,Array<Int>>>, 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)

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