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-03-27 10:59:59 +09:00
import java.awt.MouseInfo
import java.awt.Robot
import java.time.LocalTime
import java.util.concurrent.Executors
2026-02-04 14:52:09 +09:00
object SystemSleepPreventer {
private var process : Process ? = null
2026-03-27 10:59:59 +09:00
private val robot = Robot ( )
private val scheduler = Executors . newSingleThreadScheduledExecutor ( )
// 작동 시간 설정
private val startTime = LocalTime . of ( 8 , 30 )
private val endTime = LocalTime . of ( 16 , 0 )
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
!is Denied
} 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 ( !is Win ) {
checkAndRequestAccessibility ( )
}
2026-02-04 14:52:09 +09:00
2026-03-17 10:50:13 +09:00
if ( process ?. isAlive == true ) return
if ( !is Win ) {
2026-03-27 10:59:59 +09:00
// 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}")
// }
}
start2 ( )
}
fun start2 ( ) {
println ( " 🚀 화면 잠금 방지 프로그램이 시작되었습니다. (작동 시간: $startTime ~ $endTime ) " )
// 1분(60초)마다 체크
scheduler . scheduleAtFixedRate ( {
if ( isWorkingTime ( ) ) {
moveMouseSlightly ( )
} else {
println ( " 💤 현재는 휴식 시간입니다. ( ${LocalTime.now().withNano(0)} ) " )
}
} , 0 , 60 * 2 , TimeUnit . SECONDS )
}
private fun isWorkingTime ( ) : Boolean {
val now = LocalTime . now ( )
// 시작 시간 이후 AND 종료 시간 이전인지 확인
return now . isAfter ( startTime ) && now . isBefore ( endTime )
}
private fun moveMouseSlightly ( ) {
try {
val pointer = MouseInfo . getPointerInfo ( ) . location
val x = pointer . x
val y = pointer . y
// 현재 위치에서 1픽셀씩 이동했다가 복귀 (사용자 방해 최소화)
robot . mouseMove ( x + 1 , y + 1 )
robot . mouseMove ( x , y )
println ( " 🖱️ [ ${LocalTime.now().withNano(0)} ] 마우스 신호를 보냈습니다. " )
robot . keyPress ( java . awt . event . KeyEvent . VK _SHIFT )
robot . keyRelease ( java . awt . event . KeyEvent . VK _SHIFT )
println ( " 🖱️ [ ${LocalTime.now().withNano(0)} ] 키보드 신호를 보냈습니다. " )
} catch ( e : Exception ) {
println ( " ⚠️ 마우스 제어 실패: ${e.message} " )
}
}
private val osName = System . getProperty ( " os.name " ) . lowercase ( )
// 설정 시간
private val dimTime = LocalTime . of ( 16 , 0 ) // 오후 4시 이후 최저 밝기
fun start3 ( ) {
scheduler . scheduleAtFixedRate ( {
val now = LocalTime . now ( )
// 16:00 이후라면 밝기를 낮춤
if ( now . isAfter ( dimTime ) || now . isBefore ( LocalTime . of ( 8 , 30 ) ) ) {
setBrightness ( 0 )
} else {
setBrightness ( 80 ) // 업무 시간에는 다시 밝게 (80%)
}
} , 0 , 10 , TimeUnit . MINUTES ) // 10분마다 체크
}
private fun setBrightness ( level : Int ) {
try {
if ( osName . contains ( " mac " ) ) {
val keyCode = if ( level < 50 ) 145 else 144 // 145: 감소, 144: 증가
val action = if ( level < 50 ) " 어둡게 " else " 밝게 "
// 32번 연타하면 어떤 상태에서든 최소/최대에 도달합니다.
val script = """
tell application " System Events "
repeat 32 times
key code $ keyCode
end repeat
end tell
""" .trimIndent()
try {
val process = ProcessBuilder ( " osascript " , " -e " , script ) . start ( )
process . waitFor ( )
println ( " 🍏 Mac 화면을 $action 설정했습니다. ${keyCode} " )
} catch ( e : Exception ) {
println ( " ⚠️ AppleScript 실행 실패: ${e.message} " )
}
} else if ( osName . contains ( " win " ) ) {
// Windows: PowerShell 사용 (0 ~ 100 사이 값)
2026-03-30 15:11:35 +09:00
// val psCommand = "Get-CimInstance -Namespace root/WMI -ClassName WmiMonitorBrightnessMethods | ForEach-Object { \$.WmiSetBrightness(1, $level) }"
// ProcessBuilder("powershell.exe", "-Command", psCommand).start()
// 2: 모니터 끄기, -1: 모니터 켜기, 1: 저전력 모드
if ( level < 50 ) {
val psCommand =
" Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class Monitor { [DllImport( \" user32.dll \" )] public static extern int PostMessage(IntPtr hWnd, int hMsg, int wParam, int lParam); public static void Off() { PostMessage((IntPtr)0xffff, 0x0112, 0xF170, 2); } }'; [Monitor]::Off() "
ProcessBuilder ( " powershell.exe " , " -Command " , psCommand ) . start ( )
println ( " 🌙 윈도우 모니터를 절전 모드로 전환했습니다. " )
// // 0xF140은 화면 보호기를 실행하는 시스템 명령 상수입니다.
// val psCommand = "Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class Saver { [DllImport(\"user32.dll\")] public static extern int PostMessage(IntPtr hWnd, int hMsg, int wParam, int lParam); public static void Run() { PostMessage((IntPtr)0xffff, 0x0112, 0xF140, 0); } }'; [Saver]::Run()"
//
// ProcessBuilder("powershell.exe", "-Command", psCommand).start()
// println("🖥️ 화면 보호기를 실행했습니다.")
} else {
}
2026-03-17 10:50:13 +09:00
}
2026-03-27 10:59:59 +09:00
println ( " 🔆 밝기를 $level %로 설정했습니다. " )
} catch ( e : Exception ) {
println ( " ⚠️ 밝기 조절 실패: ${e.message} " )
2026-02-04 14:52:09 +09:00
}
}
2026-03-13 10:41:10 +09:00
/ * *
* 모니터를 즉시 잠자기 모드로 전환
* /
fun sleepDisplay ( ) {
try {
2026-03-27 10:59:59 +09:00
setBrightness ( 0 )
2026-03-13 10:41:10 +09:00
println ( " 🌙 [System] 오후 6시 30분: 모니터를 잠자기 모드로 전환합니다. " )
} catch ( e : Exception ) {
println ( " ⚠️ 모니터 잠자기 실패: ${e.message} " )
}
}
2026-03-27 10:59:59 +09:00
fun preventSleepWithRobot ( ) {
try {
val robot = Robot ( )
// 현재 마우스 위치 가져오기
val pointer = MouseInfo . getPointerInfo ( ) . location
val x = pointer . x
val y = pointer . y
// 마우스를 1픽셀 옆으로 움직였다가 다시 제자리로 (OS는 활동으로 간주함)
robot . mouseMove ( x + 1 , y + 1 )
robot . mouseMove ( x , y )
println ( " 🖱️ 마우스 이벤트를 시뮬레이션하여 화면 잠금을 방지했습니다. " )
} catch ( e : Exception ) {
println ( " ⚠️ 이벤트 생성 실패: ${e.message} " )
}
}
2026-03-13 10:41:10 +09:00
/ * *
* 마우스 움직임을 시뮬레이션하거나 디스플레이 깨우기 명령 실행
* /
fun wakeDisplay ( ) {
try {
2026-03-27 10:59:59 +09:00
setBrightness ( 100 )
2026-03-13 10:41:10 +09:00
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 종료됨: 시스템 절전 설정이 정상화됩니다. " )
}
}