본문으로 건너뛰기
5분 AI.
GITHUB TRENDING KOTLIN WEEKLY #4 딥다이브 · 2026-10-23 분석

DroidDesk 딥다이브
— 손안의 리눅스 데스크톱을 연결하는 기술

모바일 앱

빠른 판단

무엇
Termux 사용자 공간·XFCE·X11을 Android 앱의 화면·입력과 연결하는 Linux 데스크탑 구성 도구
이럴 때
ARM64 Android 휴대폰에 Linux 개발·문서 작업 환경을 꾸리고 모바일 화면에서 데스크탑 앱을 사용할 때
Android 휴대폰에서 Linux 데스크톱을 쓰게 만드는 프로젝트입니다. 설치·관리 화면은 Flutter로, 운영체제와의 연결은 Kotlin으로, 경로 호환성은 C로 해결합니다. 설치 버튼 하나가 어떻게 패키지 설치·프로세스 실행·X11 화면 연결로 이어지는지 따라가 봅니다. 저장소: orailnoor/DroidDesk · 스타 4,383 · 포크 407 · GPL-3.0 · 2026-10-23 GitHub 기준. 코드 설명은 커밋 a410944 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

휴대폰에 새 커널을 설치하는 대신, 기존 Android 위에 작업 환경을 조립한다

DroidDesk는 Android의 Linux 커널을 이용해 데스크톱용 프로그램과 창 관리 환경을 실행하고, 그 화면과 입력을 휴대폰 앱에 연결하는 도구입니다. 기본 구성의 중심은 Termux 사용자 공간, XFCE 데스크톱, Termux:X11에서 가져온 화면·입력 구성요소입니다.

이 저장소에서 배울 핵심은 운영체제를 새로 만드는 법보다, 서로 다른 실행 환경의 경계를 연결하는 법입니다. 같은 설치 버튼에도 Dart와 Kotlin의 경계, 앱 권한의 경계, 프로세스의 경계, 파일 경로의 경계가 숨어 있습니다.

먼저 알아둘 용어
커널과 사용자 공간
커널은 CPU·메모리·파일·장치 사용을 관리하는 운영체제의 중심입니다. 사용자 공간은 그 위에서 실행되는 셸·에디터·브라우저 같은 프로그램들의 영역입니다. Android가 Linux 커널을 쓴다는 사실만으로 Ubuntu 프로그램이 모두 바로 실행되는 것은 아닙니다. CPU 구조뿐 아니라 라이브러리·파일 경로·권한·시스템 기능도 맞아야 합니다.
한 문장 비유

같은 건물의 다른 층을, 안내데스크와 통로로 연결하는 공사입니다

Android와 데스크톱 프로그램은 같은 건물의 전기·수도에 해당하는 커널을 이용합니다. 하지만 각 층의 방 번호와 출입 규칙은 다릅니다. DroidDesk는 프로그램이 찾는 주소를 고치고, 화면을 전달하는 통로를 열고, 사용자가 누른 키를 알맞은 곳에 전달합니다.

Flutter는 안내데스크, Kotlin은 시설 관리자, X11은 화면·입력 통로에 가깝습니다. 이 비유에서 중요한 것은 통로가 생겨도 건물의 출입 권한까지 무제한이 되지는 않는다는 점입니다.

1-1. 먼저 두 제품 경로를 구별하자

경로시작점과 실행 환경화면 연결
셸 설치판termux-linux-setup.sh. 별도 Termux에 패키지와 실행 스크립트를 설치하고 PRoot 배포판도 구성합니다.별도 Termux:X11 또는 선택형 VNC
독립형 앱 · 비루팅app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/LinuxRuntime.kt. 앱 내부에 Termux bootstrap을 풀고 기본 XFCE를 직접 실행합니다.앱에 내장된 X11 서버
독립형 앱 · 루팅app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/ChrootRuntime.kt. Ubuntu ARM64 rootfs를 준비하고 su를 거쳐 chroot로 실행합니다.같은 내장 X11 서버에 소켓을 연결

이 글은 독립형 앱의 실행 흐름을 중심으로 살펴봅니다. 루트의 셸 스크립트는 비교 대상으로 읽습니다. 한쪽의 설치 방법이나 지원 목록을 다른 쪽의 보장으로 옮기지 않는 것이 첫 번째 소스 읽기 연습입니다.

2왜 주목받는가

흥미로운 사용 장면과, 코드에서 드러나는 실제 차별점

2-1. 주머니 속 개발 환경이라는 명확한 사용 장면

2026-10-23 GitHub 기준 스타 4,383개, 포크 407개이며 GitHub Trending Kotlin Weekly #4로 선정됐습니다. 2026-04-20 생성 이후 약 6개월 만의 수치입니다. 다만 스타 수와 순위만으로 실제 사용자 수나 실행 안정성을 알 수는 없습니다.

README가 제시하는 장면은 이해하기 쉽습니다. 휴대폰에서 코드와 문서를 편집하고, 외부 화면에 연결해 데스크톱처럼 쓰다가 그대로 들고 나옵니다. 이 장면의 전달력, 여러 설치 단계를 묶는 자동화, 앱 안의 직접 X11 표시는 관심을 끌 만한 요소입니다. 이는 기능과 소스에서 읽어낸 해석이며, 트렌딩 유입 원인을 측정한 결과는 아닙니다.

2-2. 비슷한 도구와 비교하면 무엇이 보이나

선택지주요 접근DroidDesk에서 볼 차이
Termux + Termux:X11 수동 구성사용자가 패키지·환경 변수·화면 서버를 직접 조합합니다.앱이 bootstrap, XFCE 설치, 실행, 상태 표시를 연결합니다. 대신 자체 경로 호환 계층을 관리해야 합니다.
UserLAnd공식 소개상 루트 없이 Android 위에서 Linux 배포판이나 앱을 실행합니다.DroidDesk의 기본 비루팅 경로는 Termux 패키지를 직접 쓰고, 별도 프로세스의 X11 서버를 앱 화면에 연결합니다.
원격 데스크톱다른 컴퓨터의 프로그램 화면을 네트워크로 전달받습니다.DroidDesk는 휴대폰에서 프로그램을 실행하므로 별도 호스트가 필요 없는 대신 휴대폰 자원을 소비합니다.
DroidDesk 셸 설치판Termux에서 다중 데스크톱과 PRoot·선택형 VNC를 구성합니다.독립형 앱은 별도 Termux 앱 없이 시작하며, 비루팅 기본 데스크톱은 XFCE로 정규화됩니다.

비교 근거: Termux:X11 공식 저장소, UserLAnd 공식 소개, DroidDesk의 README.md와 LinuxRuntime.normalizedDesktop()입니다. 제품 간 속도 우열을 측정한 벤치마크는 이 비교에 포함되지 않습니다.

DroidDesk의 눈에 띄는 장점은 새로운 리눅스 기술보다, 설치부터 화면·입력·앱 실행까지 이어지는 통합 경험에 있습니다.

2-3. 홍보 문장과 구현을 나란히 읽는 습관

문서의 표현커밋 a410944에서 확인한 구현
여러 데스크톱 지원셸 설치판은 XFCE/LXQt/MATE/KDE 분기가 있습니다. 앱의 비루팅 normalizedDesktop()은 인자를 무시하고 xfce4를 반환합니다.
비루팅 앱은 PRoot 미사용기본 데스크톱에는 맞습니다. 그러나 installOptionalApp("proot_debian")은 installMinimalDebian()으로 PRoot와 Debian을 설치하는 선택 기능입니다.
두 앱 모드의 GPU 가속비루팅 경로는 Adreno 장치와 Freedreno ICD 파일을 확인합니다. 루팅 ChrootRuntime의 기본 프로필은 LIBGL_ALWAYS_SOFTWARE=true입니다.
Ubuntu에서 되면 모두 가능README의 포괄적 주장입니다. Android 커널·권한·ARM64 패키지·그래픽 환경의 제약을 넘어서는 호환성 보장으로 읽을 수 없습니다.
소스 읽기 관점

기능 이름을 발견했다면 호출되는가 → 어떤 조건으로 분기하는가 → 성공을 무엇으로 판정하는가를 확인하세요. 클래스나 바이너리가 존재한다는 사실과 사용자가 그 기능을 실제로 쓰는지는 서로 다른 질문입니다.

3기술 스택 전체 지도

프론트엔드와 로컬 실행 계층, 빌드·배포 도구를 분리해서 보기

전통적인 웹 서비스처럼 프론트엔드와 원격 백엔드가 있는 구조는 아닙니다. 여기서 백엔드에 해당하는 역할은 기기 안의 Kotlin 실행 계층이 담당합니다. 패키지와 rootfs를 받는 네트워크 통신은 있지만, 핵심 데스크톱 실행에 별도 애플리케이션 서버를 두지 않습니다.

3-1. 프론트엔드: Flutter와 Dart

항목선언 / 잠금 버전역할과 출처
Dart SDK^3.10.3app/pubspec.yaml. lock에는 >=3.10.3 <4.0.0.
Flutter SDKlock 제약 >=3.38.4정확한 설치 버전 고정값은 아닙니다. app/pubspec.lock의 SDK 조건입니다.
provider^6.1.2 / 6.1.5+1ChangeNotifierProvider와 AppState로 상태 변경을 화면에 전달합니다.
shared_preferences^2.3.4 / 2.5.5테마·자동 시작·업데이트 확인 시각 같은 설정을 저장합니다.
google_fonts^6.2.1 / 6.3.3글꼴 구성. 화면 테마는 app/lib/theme/droid_theme.dart에 있습니다.
화면 보조 라이브러리percent_indicator ^4.2.3 / 4.2.5
flutter_animate ^4.5.2 / 4.5.2
진행 표시와 애니메이션을 위한 의존성입니다.
링크·벡터 표시url_launcher ^6.3.2 / 6.3.2
flutter_svg ^2.3.0 / 2.3.0
URL 열기와 SVG 표시를 위한 의존성입니다.
의존성 파일 읽기
버전 범위와 잠금 버전
app/pubspec.yaml은 허용하는 범위를, app/pubspec.lock은 의존성 해석 결과를 기록합니다. 예를 들어 ^6.1.2와 6.1.5+1은 모순이 아닙니다. SDK 최소 조건만 보고 정확한 Flutter 빌드 버전을 확정하면 안 됩니다.

3-2. 백엔드 역할: Kotlin·Java·C가 맡는 로컬 실행

계층주요 구성코드에서의 역할
KotlinLinuxRuntime, ChrootRuntime, MainActivity설치·프로세스 제어·환경 변수·Flutter 호출 처리
Android IPCBinder, AIDL, ParcelFileDescriptor앱 UI 프로세스와 X11 프로세스 사이의 연결 전달
Java / Termux:X11app/android/app/src/main/java/com/termux/x11/LorieView.java, 입력 처리 클래스화면 Surface 연결, 키보드·터치·마우스 이벤트 처리
C 호환 계층app/assets/socket_hook.clibc 경로 호출과 X11 소켓 연결 주소 재작성
데스크톱 구성XFCE, D-Bus, PulseAudio, Mesa, X11/TUR 저장소창·메뉴·앱 간 통신·소리·그래픽·패키지 공급
선택형 배포판Ubuntu 24.04.4 ARM64 / Debian루팅 chroot용 다운로드 URL / 비루팅 선택형 PRoot 설치 대상

XFCE·Mesa·일반 Linux 패키지는 설치 시 저장소에서 받습니다. 전체 버전을 고정하는 패키지 잠금 목록은 확인되지 않습니다. libXlorie.so 같은 동봉 바이너리도 정확한 상류 소스 리비전이 미기록이라고 COMPLIANCE.md에 적혀 있습니다.

3-3. 인프라·빌드·배포 설정

항목실제 설정값파일
앱 버전1.3.1+6: 표시 버전 1.3.1, 빌드 번호 6app/pubspec.yaml
Android Gradle Plugin / Kotlin8.11.1 / 2.2.20app/android/settings.gradle.kts
Gradle / Java 대상8.14 / Java·JVM 17app/android/gradle/wrapper/gradle-wrapper.properties, app/android/app/build.gradle.kts
Android·CPUminSdk=28, targetSdk=28, arm64-v8aapp/android/app/build.gradle.kts
네이티브 빌드CMake 3.22.1 지정. NDK·compileSdk는 Flutter 설정 참조app/android/app/build.gradle.kts, app/android/app/src/main/cpp/CMakeLists.txt
정적 검사·테스트 의존성flutter_lints ^6.0.0, flutter_testapp/pubspec.yaml. 의존성 선언이 테스트 커버리지를 뜻하지는 않습니다.
릴리스 경로Flutter APK 빌드 후 GitHub Releases에 첨부하는 수동 절차RELEASING.md. 추적 파일에 .github/workflows/는 없습니다.

빌드 도구 버전, 의존성 해석 결과, 실행 중 내려받는 패키지 버전은 각각 다른 수준의 재현성을 제공합니다. Gradle에 남은 wlroots 관련 주석만으로 Wayland가 실제로 동작한다고 판단하지 마세요. 현재 app/android/app/src/main/cpp/CMakeLists.txt는 JNI 파일 하나와 Android·log 라이브러리만 연결합니다.

4아키텍처 심화 분석

설치 제어 경로와 화면 전달 경로를 따로 추적한다

4-1. ASCII 시스템 구조도

[Android 앱 UI 프로세스] Flutter 화면 ── AppState(ChangeNotifier) │ │ └─ DroidDeskPlatform │ MethodChannel: com.droiddesk/core ▼ MainActivity.kt │ │ 비루팅 ▼ ▼ 루팅 LinuxRuntime ChrootRuntime ── RootShell ── su 앱 내부 Termux Ubuntu ARM64 rootfs + chroot │ │ └─────┬──────┘ ▼ XFCE / Linux 프로그램 (DISPLAY=:0) │ X11 Unix 소켓 ▼ [별도 :x11 프로세스] X11ServerService ── CmdEntryPoint / libXlorie.so │ │ Binder / AIDL / ParcelFileDescriptor ▼ [Android 앱 UI 프로세스] X11ServiceClient ── DesktopActivity ── LorieView ── Surface 화면 ▲ 터치·마우스·키보드 [DroidDeskService] 알림·WakeLock·중지 제어·AndroidAppBridge XFCE 메뉴 ── Python 런처 ── Unix 소켓 ── Android 앱 실행

위 그림의 두 통로를 혼동하지 마세요. Linux 프로그램은 X11 소켓으로 서버에 접속하고, Android 화면 쪽은 Binder로 받은 파일 디스크립터를 통해 네이티브 연결을 붙입니다. MethodChannel로 화면 픽셀을 계속 보내는 구조가 아닙니다.

설치 명령의 전달, 데스크톱 프로세스의 실행, 화면의 표시는 서로 다른 경로이므로 각각의 성공 조건을 확인해야 합니다.

4-2. 설계 패턴 ① 어댑터: Dart와 Android의 계약 만들기

app/lib/services/platform_bridge.dart의 DroidDeskPlatform은 Flutter 화면이 Android 세부 API를 직접 알지 않도록 중간 창구를 제공합니다. app/android/app/src/main/kotlin/com/orailnoor/droiddesk/MainActivity.kt의 configureFlutterEngine()이 같은 채널 이름으로 요청을 받습니다.

// platform_bridge.dart: 설치 요청 부분 발췌
static const _channel = MethodChannel('com.droiddesk/core');

static Future<bool> installDesktopNative({String de = 'xfce4'}) async {
  final result = await _channel.invokeMethod('installDesktopNative', {
    'de': de,
  });
  return result as bool? ?? false;
}

Kotlin은 이 요청을 installThread("install-desktop-native")에서 처리하고, LinuxRuntime.installDesktopEnvironmentNative()를 호출합니다. 진행 상황은 onInstallProgress라는 반대 방향 호출로 Dart에 돌아옵니다. 최종 bool 응답과 여러 번 발생하는 진행 알림의 역할이 다릅니다.

비동기 호출의 두 결과
완료 응답과 진행 이벤트
택배를 주문할 때 주문의 최종 결과와 배송 중 알림이 따로 오는 것과 같습니다. 이 앱은 같은 MethodChannel에 요청과 콜백 이름을 정의합니다. 구조 설명은 Flutter 플랫폼 채널 공식 문서와 함께 읽으면 좋습니다.

app/lib/state/app_state.dart의 initialize()는 콜백을 등록하고 notifyListeners()로 화면을 갱신합니다. onInstallProgress 처리에서 음수는 실패, 0 이상 1 미만은 진행 중, 1 이상은 완료로 해석합니다. 문자열 기반 메서드명과 동적 Map이 계약이므로 이름·키·자료형이 어긋나면 런타임 오류가 날 수 있습니다.

4-3. 설계 패턴 ② 프로세스 격리: X11의 수명을 화면과 분리하기

app/android/app/src/main/AndroidManifest.xml은 X11ServerService에 android:process=":x11", android:exported="false"를 선언합니다. X11 서버가 UI와 별도의 프로세스·파일 디스크립터 테이블을 갖게 하려는 구조입니다. 이는 Linux 배포판 전체를 보안 컨테이너로 격리한다는 뜻은 아닙니다.

// app/android/app/src/main/aidl/com/orailnoor/droiddesk/x11/IX11Service.aidl
interface IX11Service {
    boolean startServer();
    int getServerPid();
    ParcelFileDescriptor getXConnection();
    ParcelFileDescriptor getLogcatOutput();
}

app/android/app/src/main/kotlin/com/orailnoor/droiddesk/x11/X11ServerService.kt의 ensureServerStarted()는 HandlerThread에서 초기화하고 CountDownLatch로 중복 시작 요청을 합칩니다. CmdEntryPoint.start(arrayOf(":0", "-nolock"))가 실제 서버 시작 지점입니다.

app/android/app/src/main/kotlin/com/orailnoor/droiddesk/x11/X11ServiceClient.kt는 Binder 호출을 단일 작업 스레드에서 수행합니다. app/android/app/src/main/kotlin/com/orailnoor/droiddesk/view/DesktopActivity.kt의 connectToX11Service()는 받은 연결을 LorieView.connect(connectionFd.detachFd())로 넘깁니다. 같은 서버 PID에 이미 연결돼 있으면 새 FD를 닫고 연결을 재사용합니다.

비유

화면은 가게 진열창이고 X11 서버는 안쪽 작업장입니다. 진열창을 다시 열 때 작업장을 새로 짓지 않도록, 창의 수명과 작업장의 수명을 분리합니다. X11ServiceClient.connect()가 서비스를 시작한 뒤 바인딩하는 이유도 화면 이탈 때 서버가 바로 사라지지 않게 하기 위해서입니다.

반대로 서버를 완전히 중지할 때는 X11ServerService.onDestroy()가 자기 프로세스를 종료합니다. Java 서비스 객체를 없애도 네이티브 스레드가 자동으로 모두 끝나는 것은 아니라는 설명이 소스에 있습니다. 객체 해제와 프로세스 종료는 서로 다른 생명주기라는 점을 배울 수 있습니다.

4-4. 설계 패턴 ③ 호환 계층: 다른 앱의 절대 경로를 옮기기

Termux 패키지가 원래 기대하는 접두 경로는 /data/data/com.termux/files/usr입니다. DroidDesk는 자신의 context.filesDir/usr에 패키지를 둡니다. app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/LinuxRuntime.kt는 이 주소 차이를 세 가지 수준에서 보정합니다.

수준실제 함수해결하려는 문제
텍스트·스크립트patchShebangs(), wrapDpkgForPath()스크립트·설정 파일 본문의 옛 경로, 패키지 도구의 PATH·설정 경로
ELF 바이너리patchElfRunpaths(), patchElfFile()공유 라이브러리 검색 경로인 RPATH/RUNPATH에 박힌 옛 경로
실행 중 호출getTermuxEnv()와 C의 rewrite_path(), connect()libc 파일 접근과 X11 Unix 소켓 연결을 실제 앱 경로로 전환

patchElfFile()는 ELF64 프로그램 헤더의 PT_DYNAMIC에서 DT_STRTAB·DT_RPATH·DT_RUNPATH를 찾습니다. 주소를 파일 오프셋으로 바꾼 뒤 경로를 덮어쓰며, 공간이 좁으면 ${ORIGIN}이나 ${ORIGIN}/../lib를 사용합니다. 단순한 전체 문자열 치환과 다릅니다.

실행 환경의 핵심 값 — getTermuxEnv()와 socket_hook.c를 요약
PREFIX       = 앱 내부 files/usr
TMPDIR       = 앱 내부 files/tmp
DISPLAY      = :0
LD_PRELOAD   = PREFIX/lib/libsocket_hook.so (파일이 있을 때)

프로그램이 찾는 /tmp/.X11-unix/X0
    → connect() 후크
    → TMPDIR/.X11-unix/X0

원래의 libc 함수 주소
    → dlsym(RTLD_NEXT, "connect")
    → 보정한 주소로 실제 연결

app/assets/socket_hook.c를 빌드한 공유 라이브러리는 동적 링커의 LD_PRELOAD로 먼저 로드됩니다. 후크가 모든 시스템 호출을 완벽히 가로채는 것은 아닙니다. 이 코드는 정의된 libc 함수와 경로 규칙을 보정하는 계층이며, 임의의 Ubuntu 바이너리를 보편적으로 변환하는 도구는 아닙니다.

4-5. 설계 패턴 ④ 상태 기반 복구: 설치됨·실행 중·준비됨을 구별하기

LinuxRuntime.isBootstrapped()는 .bootstrap_extracted 마커와 bin/bash를 확인합니다. getInstalledDE()는 마지막 설치 단계에서 기록하는 .de_installed를 읽습니다. 패키지 파일 몇 개만 생긴 부분 설치를 완료로 오인하지 않으려는 장치입니다.

installPackageGroup()은 설치 명령 뒤에 shebang을 다시 보정하고 dpkg --configure -a로 미완료 설정을 복구합니다. 최초 설치가 실패했어도 복구가 성공하고 dpkg-query -W로 요청 패키지가 조회되면 성공으로 판단합니다. 데이터베이스 트랜잭션처럼 모든 변경을 원상복구하는 방식은 아닙니다.

질문판정 지점판정의 한계
설치가 끝났나?완료 마커와 필요한 파일파일이 있다고 기능 전체가 정상이라는 보장은 없습니다.
세션이 살아 있나?sessionProcess?.isAlive프로세스 생존과 실제 창 표시 성공은 다릅니다.
데스크톱이 준비됐나?waitForDesktopReady()가 XFCE 프로세스 확인기본 45초 제한, 준비 확인 후 650ms 대기. 픽셀을 검사하는 방식은 아닙니다.

MainActivity.runPackageOperation()의 AtomicBoolean.compareAndSet()은 앱 스토어 패키지 작업이 겹치지 않게 합니다. 로그는 appendTerminalOutput()에서 약 150ms 단위로 모아 보냅니다. 패키지 관리자의 일관성과 UI 갱신 비용을 함께 생각한 부분입니다.

루팅 경로에서도 파일을 푸는 것만으로는 끝나지 않습니다. ChrootRuntime.ensureMounts()가 /dev·/proc·/sys 등을 연결하고, bindX11Socket()이 앱의 X11 소켓 디렉토리를 chroot 안에 bind mount합니다. 그 뒤 su -c를 거쳐 chroot 안의 셸과 데스크톱 세션을 시작합니다. 비루팅 경로의 경로 후크와 루팅 경로의 마운트는 같은 연결 문제를 다른 권한 수준에서 푸는 방법입니다.

분석에서 구현으로

새 기능을 설계한다면 재시도 조건, 중간 실패 후 상태, 중복 실행 방지, 완료 판정부터 써 보세요. 설치 명령 하나를 호출하는 것보다, 실패한 설치를 다시 이어갈 수 있게 만드는 일이 제품 코드에서 더 큰 비중을 차지합니다.

5디렉토리 구조 해부

화면부터 운영체제까지, 읽는 순서가 보이도록 묶기
DroidDesk/ ├── README.md / APP_TROUBLESHOOTING.md ├── termux-linux-setup.sh 별도 Termux 설치·런처 생성 ├── pi-launch_phone.sh Raspberry Pi의 VNC 뷰어 실행 ├── fetch_deps.sh wlroots 의존성 다운로드 도우미 ├── RELEASING.md APK 서명·공개 절차 ├── COMPLIANCE.md / THIRD_PARTY_NOTICES.md / LICENSES/ └── app/ ├── pubspec.yaml / pubspec.lock 의존성 선언·해석 결과 ├── assets/ │ ├── bootstrap-aarch64.zip 기본 Termux 사용자 공간 │ └── socket_hook.c 경로·소켓 호환 코드 ├── lib/ │ ├── main.dart 앱 시작·Provider·화면 선택 │ ├── state/app_state.dart 설치·실행·업데이트 상태 │ ├── services/ 플랫폼 채널·릴리스 확인 │ ├── screens/ 홈·설정·설치·패키지 화면 │ ├── widgets/ 공통 UI 부품 │ └── theme/ 테마 정의 ├── test_ffi.dart malloc/free 호출 실험 └── android/ ├── settings.gradle.kts / gradle/ / app/build.gradle.kts └── app/src/main/ ├── AndroidManifest.xml ├── kotlin/com/orailnoor/droiddesk/ │ ├── MainActivity.kt │ ├── runtime/ 실행·설치·연동·업데이트 │ ├── service/ 알림과 세션 유지 │ ├── x11/ X11 서비스·클라이언트·입력 │ ├── view/ 네이티브 데스크톱 화면 │ └── compositor/ Wayland용 미완성 연결 코드 ├── java/com/termux/x11/ 화면·입력 상류 코드 ├── aidl/ 프로세스 사이 인터페이스 ├── cpp/ JNI 파일·CMake 설정 ├── assets/droiddesk/ chroot 호환 C 코드 등 └── jniLibs/arm64-v8a/ 동봉 네이티브 바이너리

Kotlin의 실행·X11·화면 코드는 각각 app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/·app/android/app/src/main/kotlin/com/orailnoor/droiddesk/x11/·app/android/app/src/main/kotlin/com/orailnoor/droiddesk/view/에 모여 있습니다. 파일이 많아 보이지만 먼저 app/lib/main.dart → AppState → DroidDeskPlatform → MainActivity 순서로 읽으면 사용자 행동이 어느 실행 함수로 이어지는지 찾을 수 있습니다.

5-1. 코드 규모: 주 언어와 제품 전체는 다르다

분류파일 수물리적 줄 수읽을 때의 의미
Kotlin · .kt228,208Android와 Linux를 연결하는 중심 계층
Dart · .dart187,485사용자 화면과 상태·업데이트 서비스
Java · .java123,366Termux:X11 화면·입력 코드 포함
셸 · .sh31,199설치판과 보조 스크립트
C / C++2 / 1554 / 50경로 후크·chroot 호환 코드 / 미완성 JNI
Gradle Kotlin DSL · .kts3148빌드 설정. 위 .kt 수치와 별도 집계

커밋 a410944의 Git 추적 파일 118개를 기준으로, 확장자별 텍스트의 실제 줄을 셌습니다. 마지막 개행이 없어도 한 줄로 세며 주석·빈 줄을 포함합니다. 압축 bootstrap 내부, 이미지, 글꼴, 동봉 .so, 라이선스·문서 등은 위 소스 합계에서 제외합니다. 위 표의 합계는 21,010줄이며 순수 실행문 수나 개발자가 직접 작성한 코드만의 분량은 아닙니다.

5-2. 파일 이름보다 실제 연결을 확인할 곳

app/android/app/src/main/cpp/compositor_jni.cpp에는 Wayland/wlroots 초기화·종료·입력 주입이 TODO로 남아 있습니다. JNI 함수 이름도 현재 Kotlin 패키지 경로와 다릅니다. 이 파일을 근거로 완성된 Wayland 렌더러가 있다고 설명하면 안 됩니다.

fetch_deps.sh는 wlroots 0.17.4-1, Wayland 1.22.0-1 등의 다운로드 목록과 작성자 컴퓨터의 절대 경로를 갖고 있습니다. 현재 앱의 실제 의존성 잠금 파일로 볼 수 없습니다. app/test_ffi.dart 역시 메모리 할당·해제 실험이며 자동화된 앱 테스트 모음이 아닙니다.

6학습 포인트 (기술별)

각 기술을 공부한 뒤, 어떤 질문에 답할 수 있어야 할까

한 기능을 화면에서 시스템 호출까지 따라가 보면, 여러 언어를 배우는 일이 하나의 문제를 푸는 과정으로 연결됩니다. 처음부터 모든 파일을 이해하려 하기보다 설치 진행률이나 클립보드처럼 작은 기능 하나를 선택하세요.

6-1. Dart·Flutter: 상태를 화면과 분리하기

app/lib/main.dart는 ChangeNotifierProvider로 AppState를 공급하고, context.watch<AppState>()로 상태를 구독합니다. isSetupComplete에 따라 시작 화면도 달라집니다. 위젯이 설치 프로세스를 직접 다루지 않고 상태를 받아 그린다는 점에 집중하세요.

실습 아이디어: 다운로드·추출·설치의 진행 이벤트를 표로 만든 뒤, 각 이벤트가 _isDownloading·_isExtracting·_isInstallingDE 중 무엇을 바꾸는지 표시하세요. 화면을 다시 그리는 것과 실제 설치가 진행되는 것을 분리해서 설명하는 연습입니다.

6-2. Kotlin·Android: 무거운 작업과 UI 스레드

MainActivity는 설치를 작업 스레드에 보내고 결과·콜백을 runOnUiThread로 돌려보냅니다. 하지만 코드 전체가 코루틴 기반으로 통일돼 있지는 않습니다. thread, Executor, Handler, CountDownLatch를 구분해서 읽어야 합니다.

동시성의 기본
스레드와 프로세스
스레드는 한 작업 공간 안에서 함께 일하는 여러 작업자이고, 프로세스는 메모리·자원 관리 단위가 나뉜 작업장입니다. HandlerThread를 만든다고 Android 프로세스가 하나 더 생기는 것은 아닙니다. 별도 프로세스는 Manifest의 :x11 선언에서 생깁니다.

실습 아이디어: X11ServiceClient.onServiceConnected()에서 Binder 요청과 화면 콜백이 각각 어느 스레드에서 실행되는지 화살표 두 색으로 표시해 보세요. 기다리는 호출이 UI에 놓이면 어떤 현상이 생길지도 함께 적습니다.

6-3. Linux: 프로그램 하나에도 실행 환경이 필요하다

LinuxRuntime.getTermuxEnv()에서 PATH·HOME·SHELL· DISPLAY·LD_LIBRARY_PATH·APT_CONFIG를 찾아보세요. 같은 바이너리도 어떤 환경을 물려받느냐에 따라 인터프리터·라이브러리·화면 서버를 찾지 못할 수 있습니다.

실습 아이디어: 각 변수를 빼면 실패할 것 같은 기능과 예상 오류를 한 줄씩 적으세요. 예측이라는 점을 표시하고, 코드에서 해당 값을 읽는 소비자를 찾으며 근거를 보강합니다. Android Termux의 Bionic 환경과 Ubuntu의 glibc 환경을 구별하는 것도 다음 학습 주제입니다.

6-4. C·ELF: 주소를 바꾸는 두 가지 시점

patchElfFile()는 파일 안의 동적 라이브러리 검색 정보를 고칩니다. 반면 app/assets/socket_hook.c는 프로그램 실행 중 libc 함수를 감싸 호출 인자를 고칩니다. 설치된 파일을 바꾸는 작업과 실행 중 호출을 중개하는 작업의 차이를 배울 수 있습니다.

실습 아이디어: rewrite_path()를 순수 문자열 함수로 옮겨 모의 입력을 넣으세요. 정확한 접두 경로, 접두 경로와 비슷하지만 다른 이름, 빈 값, 상대 경로를 구별해야 합니다. 실제 후크를 주입하지 않아도 경계 조건과 버퍼 크기 문제를 공부할 수 있습니다.

6-5. 그래픽: 화면 서버와 GPU 드라이버를 구별하기

X11은 프로그램이 창·입력과 통신하는 체계이고, GPU 드라이버는 그래픽 연산을 처리하는 경로입니다. LinuxRuntime.getTermuxEnv()는 Adreno와 ICD 파일이 있을 때 zink를 선택하고, 그렇지 않으면 swrast·llvmpipe로 전환합니다.

Mesa의 Zink 문서는 OpenGL 작업을 Vulkan API로 보내는 역할을 설명합니다. LLVMpipe 문서는 CPU 기반 소프트웨어 래스터화 경로를 설명합니다. 실습 아이디어: 장치 노드 존재 여부와 ICD 파일 존재 여부의 네 조합을 표로 만들어 선택되는 환경 변수를 써 보세요.

6-6. 배포: 다운로드 성공과 신뢰할 수 있는 업데이트

app/lib/services/update_service.dart의 UpdateService.check()는 자동 확인에 6시간 간격 제한과 건너뛴 태그를 적용하며, 수동 확인은 이 둘을 무시합니다. 초안·사전 릴리스는 제외하고 첫 APK 자산을 선택합니다. app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/AppUpdater.kt의 download()는 예상 크기가 0보다 클 때 파일 크기를 검사하고, verify()는 패키지명과 설치된 앱·내려받은 APK의 서명 인증서 지문을 확인합니다.

실습 아이디어: 새 버전 없음, APK 없음, 크기 불일치, 패키지명 불일치, 서명 불일치를 서로 다른 실패 사례로 나누세요. 버전 비교는 업데이트 후보를 고르고, 서명 확인은 설치할 파일의 신뢰 조건을 검사합니다. isNewer()는 앞의 숫자 세 부분을 비교하므로 모든 사전 릴리스 규칙을 구현한 완전한 SemVer 파서는 아닙니다.

6-7. 작은 연동 기능: 앱 실행과 클립보드

app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/AndroidAppBridge.kt의 syncLaunchers()는 Android 앱 목록을 Linux의 .desktop 파일로 바꾸고 Python 런처를 만듭니다. 런처는 Unix 소켓으로 패키지명을 보내고, serve()와 launchPackage()가 Android의 정상 실행 Intent로 연결합니다. Android 앱이 Linux 프로그램으로 바뀌는 것은 아닙니다.

루팅 DesktopActivity.startDesktopSessionIfRequested()에는 AndroidAppSync.syncApps()와 FIFO 기반 android_intent_pipe 경로도 남아 있습니다. 유사한 기능의 구현이 둘이면 현재 호출 경로를 함께 확인해야 합니다. 클래스 이름 하나만 보고 연동이 모두 한 방식으로 통일됐다고 판단하지 마세요.

app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/ClipboardSync.kt는 800ms 간격으로 평문을 동기화하고 busy.compareAndSet()으로 작업 중복을 막습니다. 실습 아이디어: 양쪽 텍스트가 동시에 바뀌었을 때 Android 변경을 먼저 처리하는 분기를 찾고, 자신이 원하는 충돌 해결 규칙과 비교해 보세요. 이 기능의 시작·중지는 데스크톱 창의 포커스와도 연결됩니다.

7하드웨어 / 시스템 요구사항

코드의 필수 조건, 문서의 안내, 학습용 권장치를 구별한다

7-1. 실행 대상은 ARM64 Android

항목확인한 조건해석
CPUabiFilters = arm64-v8a, ARM64 bootstrap·동봉 .so이 구성은 32비트 ARM·x86 Android를 지원 대상으로 빌드하지 않습니다.
AndroidminSdk=28, targetSdk=28최소 API 28은 Android 9입니다. 최신 OS에서 모든 기기가 검증됐다는 뜻은 아닙니다.
루트 권한기본 앱 경로는 불필요. chroot 경로는 su 필요PRoot 안의 가상 root 사용자와 Android의 실제 루트 권한은 다릅니다.
GPU비루팅: Adreno 장치 노드 + Freedreno ICD 존재 시 Turnip/Zink기타 조건은 llvmpipe. 루팅 Ubuntu 기본 프로필도 소프트웨어 렌더링입니다.
네트워크최초 데스크톱 패키지 설치, rootfs·선택 앱 다운로드에 필요이미 설치한 로컬 앱의 실행과 온라인 설치·업데이트를 구분해야 합니다.
화면·입력휴대폰 화면만으로 시작 가능코딩·문서 작업에는 키보드와 마우스가 편하지만 앱 코드의 필수 조건은 아닙니다.

ARM64와 최소 Android 버전을 만족하는 것만으로 GPU 가속, 외부 화면 출력, 모든 Ubuntu 앱의 호환성까지 보장되지는 않습니다. 낮게 설정된 targetSdk는 앱 내부 실행 파일의 호환성을 위한 선택이라고 Gradle 주석에 설명돼 있습니다. Android 정책 전체를 해제하거나 모든 제조사 제약을 없애는 설정으로 해석해서는 안 됩니다.

7-2. 메모리·저장 공간은 어느 정도인가

이 커밋에는 기기 RAM의 공식 최소·권장 사양표가 없습니다. app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/RootfsManager.kt 주석의 압축 300~500MB·해제 후 2~4GB도 일반적인 예상치이며, 고정된 Ubuntu Base 파일의 실측 크기나 XFCE까지 설치한 최종 사용량은 아닙니다.

구분학습용 준비 기준근거 수준
소스 읽기·모의 실습일반 개발용 컴퓨터와 텍스트 편집기Android 기기나 루팅 없이 이 글의 과제를 진행할 수 있습니다.
가벼운 기기 체험RAM 4GB 이상, 여유 저장 공간 8GB 이상부터 검토작성자의 준비용 권장치입니다. 검증된 최소 사양이 아닙니다.
브라우저·에디터 병행RAM 6~8GB 이상과 추가 저장 공간을 고려작업량에 따른 여유 제안입니다. Blender·LLM 성능 기준으로 사용할 수 없습니다.

실제 공간에는 다운로드 캐시, 압축 해제본, 패키지, 사용자 파일이 함께 들어갑니다. LinuxRuntime.getStorageInfo()와 cleanPackageCache()가 별도 기능인 이유입니다. CPU 렌더링은 앱 실행 가능 여부와 별개로 응답성·발열·전력 소비에 영향을 줍니다.

7-3. 모니터 연결과 세션 유지의 조건

USB-C 단자가 있다고 영상 출력이 되는 것은 아닙니다. README의 직접 HDMI 연결 경로는 휴대폰이 USB-C 영상 출력을 지원할 때의 안내입니다. Raspberry Pi 경로는 셸 설치판에서 VNC를 켜고, Pi가 USB 테더링 네트워크를 통해 화면을 받는 방식입니다.

pi-launch_phone.sh는 기본 경로의 게이트웨이를 휴대폰 IP로 간주해 vncviewer를 5901 포트로 연결합니다. USB 인터페이스 자체를 판별하지 않으므로 다른 기본 경로가 잡혀 있으면 잘못된 주소를 선택할 수 있습니다.

통신 범위를 확인할 지점

셸 설치판의 선택형 VNC 실행 명령은 -localhost no를 사용해 외부 연결을 허용합니다. USB 테더링에서만 연결된다고 코드가 강제하지는 않습니다. 이 설정은 앱 내부 X11 경로와 구별해서 이해해야 하며, 접속 가능한 네트워크와 인증 설정을 함께 검토해야 합니다.

app/android/app/src/main/kotlin/com/orailnoor/droiddesk/service/DroidDeskService.kt는 포그라운드 알림과 PARTIAL_WAKE_LOCK을 사용합니다. 이는 CPU 수면과 세션 유지에 도움을 주는 수단이지 Android의 모든 백그라운드 종료를 막는 보장은 아닙니다. 실제 기기별 동작은 제조사 정책과 배터리 설정의 영향을 받습니다.

8직접 해볼 수 있는 실습 과제

실제 패키지 설치 없이도 기능 경계를 이해하고 검증할 수 있는 다섯 과제

아래 소스 탐색 명령은 저장소 루트를 기준으로 적었습니다. 코드를 고치는 과제는 원본과 분리한 연습용 코드에서 진행하세요. 실습의 완료 기준을 실행 화면 하나로 끝내지 말고, 정상·실패·중복 요청을 설명할 수 있는 결과물로 정하세요.

과제 1 · 입문

버전 두 장부 만들기

할 일: app/pubspec.yaml과 app/pubspec.lock에서 직접 의존성 일곱 개의 이름·허용 범위·해석 버전을 표로 만드세요. SDK 조건과 패키지 버전도 분리합니다.

완료 기준: provider가 ^6.1.2인데 lock에는 6.1.5+1인 이유, Flutter >=3.38.4가 정확한 설치 버전이 아닌 이유를 각각 설명합니다.

# 읽기 전용 탐색: 의존성 선언과 잠금 결과
rg -n 'provider|shared_preferences|google_fonts|sdk:' app/pubspec.yaml
rg -n -A8 '^  provider:|^sdks:' app/pubspec.lock
과제 2 · 입문

설치 버튼부터 진행 알림까지 연결선 그리기

할 일: Dart의 installDesktopNative에서 Kotlin 런타임까지 호출을 찾고, onInstallProgress가 돌아오는 방향을 그립니다. 호출명·인자·반환값을 화살표에 써 넣으세요.

완료 기준: de 인자는 문자열, 최종 응답은 bool, 진행 이벤트는 progress와 status라는 사실을 소스 위치와 함께 설명합니다. 여기에 normalizedDesktop()를 붙여 다른 DE를 요청했을 때의 결과도 예측합니다.

# 읽기 전용 탐색: 요청과 콜백이 만나는 네 위치
rg -n 'installDesktopNative|onInstallProgress' \
  app/lib/services/platform_bridge.dart app/lib/state/app_state.dart \
  app/android/app/src/main/kotlin/com/orailnoor/droiddesk/MainActivity.kt
rg -n 'normalizedDesktop|installDesktopEnvironmentNative' \
  app/android/app/src/main/kotlin/com/orailnoor/droiddesk/runtime/LinuxRuntime.kt
과제 3 · 중급

진행률을 해석하는 순수 함수 만들기

할 일: 연습용 코드에 진행률을 받아 대기/진행/성공/실패 중 하나를 반환하는 함수를 만듭니다. AppState.initialize()의 규칙을 먼저 옮기고, NaN과 범위 밖 값은 별도로 처리하도록 설계합니다.

완료 기준: -1, 0, 0.5, 1, 1.2, NaN 입력의 기대 결과를 표로 적고, 실패 뒤 재시도와 완료 뒤 늦게 도착한 진행 이벤트를 어떻게 다룰지도 정합니다.

연습용 의사 코드 — 실제 AppState 구현의 직접 인용은 아닙니다.
분류(progress):
    숫자가 유효하지 않으면 → 실패
    progress < 0 이면     → 실패
    progress < 1 이면     → 진행
    그 밖에는             → 성공

추가 상태: 작업 ID, 마지막 상태, 취소 여부
질문: 이전 작업의 완료 이벤트가 새 작업에 도착하면?
과제 4 · 중급

모의 릴리스로 업데이트 조건 검토하기

할 일: UpdateService.check()의 판단을 연습용 순수 함수로 분리하고, 버전·draft·prerelease·assets만 가진 모의 JSON을 넣습니다. 실제 GitHub 조회나 APK 설치는 필요 없습니다.

완료 기준: 같은 버전, 낮은 버전, 높은 버전이지만 APK 없음, 사전 릴리스, 건너뛴 태그의 결과를 설명합니다. 자동 확인과 수동 확인의 차이도 적습니다.

확장: AppUpdater.download(), AppUpdater.verify()를 읽고 크기·패키지명·서명 지문 불일치를 서로 다른 오류로 표현하세요. SHA-256이 APK 전체의 공개 체크섬인지 인증서 지문인지 구별해야 합니다.

과제 5 · 고급

X11 시작 대기에 시간 제한 설계하기

할 일: X11ServerService.ensureServerStarted()의 제한 없는 latch.await()를 읽고, 연습용 모델에 시간 제한과 취소를 추가하는 설계안을 만듭니다.

완료 기준: 동시 시작 요청 두 개, 서버 초기화 실패, 대기 중 화면 종료, 제한 시간 뒤 뒤늦은 성공의 네 경우를 시퀀스 도식으로 설명합니다. 실패를 UI에 보낸 뒤 FD가 나중에 도착하면 누가 닫는지도 명시합니다.

확장: 연결 해제 콜백과 active 플래그, DesktopActivity.connectToX11Service()의 FD 재사용·해제 규칙을 설계 근거로 연결하세요.

9관련 기술 심화 학습 로드맵

6주 동안 한 기능씩 경계를 넓혀 가는 순서

매주 소스에서 질문 하나를 고르고 설명 자료 하나를 남기는 방식입니다. 처음에는 프로그램을 실행하지 않고도 따라갈 수 있습니다. 학습 진도는 읽은 파일 수보다, 입력에서 결과까지의 흐름을 스스로 설명할 수 있는지로 확인하세요.

주차학습 주제와 읽을 코드직접 남길 결과물
1주차Dart 기본 문법, Future, Flutter 위젯. app/lib/main.dart·AppState설정 전/설정 후 화면이 선택되는 조건표와 의존성 버전표
2주차Provider와 플랫폼 채널. app/lib/services/platform_bridge.dart·MainActivity.configureFlutterEngine()요청명·인자·응답·콜백을 연결한 인터페이스 명세 한 장
3주차프로세스, 환경 변수, 패키지 설치 상태. LinuxRuntime부분 설치 → 복구 → 완료 마커의 상태 전이도
4주차Binder·AIDL·FD 소유권. X11ServerService·X11ServiceClient화면 이탈·재진입·서버 종료 시퀀스와 FD 해제 책임표
5주차C 함수 포인터, 동적 링커, ELF64. app/assets/socket_hook.c·patchElfFile()경로 치환 모의 테스트와 RPATH/RUNPATH 수정 단계 설명
6주차업데이트·서명·재현 가능한 빌드. UpdateService·AppUpdater·COMPLIANCE.md모의 릴리스 사례표와 바이너리-소스 대응 목록 초안

9-1. 관심사별로 갈라지는 다음 단계

학습 순서의 비유

건물 전체 배선을 외우려 하지 말고 조명 스위치 하나에서 전등까지 따라가세요. 설치 버튼 하나를 끝까지 설명할 수 있으면, 앱 스토어·업데이트·클립보드에도 같은 추적 방법을 적용할 수 있습니다.

10핵심 키워드 사전

용어를 외우기보다, 이 저장소에서 어디에 쓰이는지 연결하기
용어쉬운 설명
ARM64 / aarch64이 앱이 대상으로 삼는 64비트 ARM CPU 구조입니다. Android 빌드의 ABI 이름은 arm64-v8a입니다.
ABI바이너리가 서로 호출되고 로드될 때 지켜야 하는 규약입니다. CPU 구조와 함수 호출 방식 등이 맞아야 합니다.
TermuxAndroid에서 셸과 Linux 도구를 쓸 수 있게 하는 환경입니다. DroidDesk 앱은 그 사용자 공간을 자신의 저장 영역에 구성합니다.
bootstrap패키지 관리와 셸 실행을 시작할 수 있는 최소 기본 파일 묶음입니다. 여기서는 ARM64 ZIP이 앱 자산으로 들어갑니다.
prefix프로그램·라이브러리·설정 파일이 모이는 공통 경로입니다. DroidDesk의 경로 재배치가 필요한 출발점입니다.
TURTermux User Repository입니다. 추가 앱을 공급하는 패키지 저장소이며 DroidDesk와 같은 프로젝트는 아닙니다.
XFCE패널·창 관리자·파일 관리자 등을 묶은 데스크톱 환경입니다. 비루팅 앱의 기본 지원 데스크톱입니다.
X11그래픽 프로그램과 화면·입력을 담당하는 서버가 통신하는 체계입니다. Linux 프로그램이 클라이언트입니다.
DISPLAY=:0프로그램이 사용할 X 서버의 디스플레이 번호입니다. 화면 해상도나 Android 모니터 번호 자체를 뜻하지 않습니다.
VNC화면을 원격으로 전달하고 입력을 돌려보내는 방식입니다. 이 저장소에서는 주로 셸 설치판의 선택형 외부 접속 경로입니다.
PRoot시스템 호출을 추적해 파일 경로 등을 바꾸며 root 없이 chroot와 비슷한 환경을 만드는 도구입니다. 실제 root 권한을 주지는 않습니다.
chroot / rootfschroot는 프로세스가 보는 루트 경로를 바꾸는 기능이고, rootfs는 그 안의 기본 파일 트리입니다. chroot만으로 완전한 보안 격리가 되지는 않습니다.
MethodChannelFlutter와 플랫폼 코드가 메서드명·인자·응답을 주고받는 통로입니다. 이 앱의 채널 이름은 com.droiddesk/core입니다.
Binder / AIDLAndroid 프로세스 사이 호출 장치와 그 인터페이스 정의 언어입니다. UI와 X11 서비스의 계약에 쓰입니다.
FD열린 파일·소켓 같은 자원을 가리키는 프로세스 내부 번호입니다. 번호만 복사하는 것과 IPC로 실제 자원 핸들을 전달하는 것은 다릅니다.
JNI / .soJNI는 Java·Kotlin 쪽과 C/C++를 연결하는 규약입니다. .so는 실행 중 불러오는 공유 라이브러리 파일입니다.
LD_PRELOAD동적 링커가 지정한 라이브러리를 먼저 로드하도록 하는 설정입니다. 여기서는 libc 함수의 경로 인자를 보정하는 데 사용합니다.
shebang셸 스크립트 첫 줄의 #!와 실행기 경로입니다. 잘못된 절대 경로가 남으면 파일이 있어도 실행에 실패할 수 있습니다.
ELF / RPATH / RUNPATHLinux 바이너리 형식과 공유 라이브러리를 찾는 검색 경로 정보입니다. Kotlin 코드가 해당 바이너리 메타데이터를 직접 읽고 고칩니다.
D-Bus / PulseAudio각각 데스크톱 구성요소 사이의 메시지 통신과 소리 처리를 담당합니다. X11 화면 연결과는 별도 역할입니다.
Turnip / ZinkTurnip은 Adreno용 Mesa Vulkan 드라이버이고, Zink는 OpenGL 작업을 Vulkan 경로로 전달하는 Mesa 드라이버입니다.
llvmpipeCPU로 그래픽을 계산하는 Mesa 소프트웨어 렌더러입니다. GPU 경로가 준비되지 않았을 때의 대안입니다.
WakeLock필요한 동안 CPU 수면 등을 제어하는 Android 장치입니다. 앱 강제 종료를 모두 막는 보호막은 아닙니다.
서명 인증서 지문앱 서명 인증서를 해시로 식별한 값입니다. 이 앱의 업데이트 코드는 설치된 앱과 다운로드 APK의 지문 교집합을 확인합니다.

PRoot의 시스템 호출 추적과 실제 루트 권한의 차이는 PRoot-Distro 공식 문서에서도 설명합니다. 이 문서의 최신 명령 체계와 DroidDesk가 설치 시 받는 패키지 버전은 별개이므로 개념 설명과 버전 정보를 구별해 읽으세요.

11참고 링크

구현 근거와 공식 문서, 그리고 적용 전에 확인할 범위

11-1. 공식 저장소와 주요 소스

11-2. 관련 기술의 공식 문서

현재 상태 (2026-10-23 확인)

항목확인 내용
선정GitHub Trending Kotlin Weekly #4 · 2026-10-23 분석
스타 / 포크4,383 / 407 · 2026-10-23 GitHub 기준
생성일 / 주 언어2026-04-20 / Kotlin · 2026-10-23 GitHub 기준
최신 릴리스v1.3.1 · 2026-10-14 · 2026-10-23 GitHub 기준. 앱 선언 버전은 1.3.1+6입니다.
최근 push2026-10-14 · 2026-10-23 GitHub 기준
분석 기준 커밋a410944 · 2026-10-14 · 커밋 메시지 Update README.md
코드 규모Git 추적 118파일. Kotlin 8,208줄, Dart 7,485줄, Java 3,366줄, 셸 1,199줄, C 554줄, C++ 50줄, Gradle KTS 148줄
대상 플랫폼Android ARM64 · 앱 minSdk 28 · 비루팅 Termux 기본 경로 / 루팅 Ubuntu chroot
테스트 / CIflutter_test 의존성, FFI 실험 파일은 있으나 일반적인 app/test/ 테스트 모음과 GitHub Actions 워크플로는 추적 파일에서 확인되지 않습니다.
프로젝트 라이선스GPL-3.0-only. 상류 파일의 고지는 유지되며 동봉 구성요소는 각자의 라이선스·출처 기록을 확인해야 합니다.
쓰기 전에 알아둘 한계
코드의 실행 흐름이 모든 기기에서의 동작을 보장하지는 않습니다

커밋 a410944의 실행 흐름만으로 기기별 설치 성공, 발열·배터리, GPU 성능, 외부 모니터, 앱별 동작을 보장할 수는 없습니다. README의 Blender 실행·로컬 AI 초당 5토큰 이상 등의 주장은 기기·모델·측정 조건이 충분히 제시된 공통 벤치마크가 아닙니다.

기본 비루팅 데스크톱, 선택형 PRoot Debian, 루팅 chroot, 셸 설치판은 제약이 다릅니다. 실제 루트 권한이 없는 환경에서 커널 권한이 필요한 네트워크 기능까지 자동으로 가능해지는 것은 아닙니다. APP_TROUBLESHOOTING.md의 PRoot 중심 설명도 현재 기본 앱 경로 전체에 그대로 적용할 수 없습니다.

COMPLIANCE.md와 THIRD_PARTY_NOTICES.md에는 네이티브 바이너리의 정확한 소스 리비전·재현 빌드, custom-prefix bootstrap, 일부 동봉물의 출처·고지 등 미완료 항목이 남아 있습니다. 프로젝트가 GPL 소스라는 사실만으로 배포 APK의 모든 출처·재현성 문제가 해결됐다고 볼 수 없습니다.