atrade/src/main/kotlin/service/SystemSleepPreventer.kt

125 lines
4.8 KiB
Kotlin
Raw Normal View History

2026-02-04 14:52:09 +09:00
package service
import java.util.concurrent.TimeUnit
2026-02-05 14:26:02 +09:00
import org.slf4j.LoggerFactory
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
2026-02-04 14:52:09 +09:00
object SystemSleepPreventer {
private var process: Process? = null
2026-03-13 10:41:10 +09:00
fun checkAndRequestAccessibility() {
if (!hasAccessibilityPermission()) {
println("⚠️ [System] 접근성 권한이 없습니다. 설정창을 엽니다.")
openAccessibilitySettings()
} else {
println("✅ [System] 접근성 권한이 확인되었습니다.")
}
}
/**
* 실제로 가벼운 이벤트를 발생시켜 권한 유무를 확인하는 함수
*/
private fun hasAccessibilityPermission(): Boolean {
return try {
// System Events에 이름을 묻는 아주 가벼운 명령을 실행합니다.
val process = Runtime.getRuntime().exec(
arrayOf("osascript", "-e", "tell application \"System Events\" to get name")
)
// 권한이 없으면 팝업이 뜨며 대기할 수 있으므로, 아주 짧은 타임아웃을 줍니다.
val exited = process.waitFor(1500, java.util.concurrent.TimeUnit.MILLISECONDS)
if (!exited) {
// 타임아웃 발생 시, 시스템이 권한 승인 팝업을 띄우고 대기 중일 확률이 높습니다.
process.destroyForcibly()
return false
}
// 에러 스트림을 읽어 권한 거부 관련 메시지가 있는지 확인합니다.
val errorStream = process.errorStream.bufferedReader().readText()
val isDenied = errorStream.contains("not allowed") || process.exitValue() != 0
2026-02-04 14:52:09 +09:00
2026-03-13 10:41:10 +09:00
!isDenied
} catch (e: Exception) {
println("⚠️ [Permission] 체크 중 오류 발생: ${e.message}")
false
}
}
private fun openAccessibilitySettings() {
try {
Runtime.getRuntime().exec(arrayOf(
"open", "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
))
} catch (e: Exception) {
e.printStackTrace()
}
}
2026-02-04 14:52:09 +09:00
/**
* 맥의 절전 모드 디스플레이 취침을 방지하는 명령 실행
*/
fun start() {
2026-03-17 10:50:13 +09:00
val os = System.getProperty("os.name").lowercase()
val arch = System.getProperty("os.arch").lowercase()
val isWin = os.contains("win")
2026-02-05 14:26:02 +09:00
val root = LoggerFactory.getLogger("Exposed") as Logger
root.level = Level.ERROR
2026-03-17 10:50:13 +09:00
if (!isWin) {
checkAndRequestAccessibility()
}
2026-02-04 14:52:09 +09:00
2026-03-17 10:50:13 +09:00
if (process?.isAlive == true) return
if (!isWin) {
try {
// -i: 시스템 절전 방지, -d: 디스플레이 취침 방지, -m: 디스크 유휴 상태 방지
val command = listOf("caffeinate", "-i", "-d", "-m")
process = ProcessBuilder(command).start()
println("☕ [System] caffeinate 실행됨: 앱이 켜져 있는 동안 절전 모드가 방지됩니다.")
} catch (e: Exception) {
println("⚠️ [System] caffeinate 실행 실패: ${e.message}")
}
2026-02-04 14:52:09 +09:00
}
}
2026-03-13 10:41:10 +09:00
/**
* 모니터를 즉시 잠자기 모드로 전환
*/
fun sleepDisplay() {
try {
// pmset을 이용해 디스플레이를 즉시 끔
Runtime.getRuntime().exec("pmset displaysleepnow")
println("🌙 [System] 오후 6시 30분: 모니터를 잠자기 모드로 전환합니다.")
} catch (e: Exception) {
println("⚠️ 모니터 잠자기 실패: ${e.message}")
}
}
/**
* 마우스 움직임을 시뮬레이션하거나 디스플레이 깨우기 명령 실행
*/
fun wakeDisplay() {
try {
// caffeinate를 다시 실행하여 깨우거나,
// 쉘 명령어로 키 입력을 시뮬레이션하여 화면을 깨움
Runtime.getRuntime().exec(
arrayOf("caffeinate", "-u", "-t", "3600")
)
// Runtime.getRuntime().exec(arrayOf("osascript", "-e", "tell application \"System Events\" to key code 123"))
println("☀️ 오전 8시: 모니터를 깨웁니다.")
} catch (e: Exception) {
println("⚠️ 모니터 깨우기 실패: ${e.message}")
}
}
2026-02-04 14:52:09 +09:00
/**
* 종료 프로세스 함께 종료
*/
fun stop() {
process?.destroy()
// 프로세스가 강제 종료되지 않을 경우를 대비해 0.5초 대기 후 강제 종료
if (process?.waitFor(500, TimeUnit.MILLISECONDS) == false) {
process?.destroyForcibly()
}
println("🛑 [System] caffeinate 종료됨: 시스템 절전 설정이 정상화됩니다.")
}
}