scrcpy'ye Sürükle-Bırak XAPK Kurucu Ekledim
scrcpy'nin sürükle-bırak APK kurma özelliğini XAPK/APKS/APKM paketlerine genişlettim. İnce bir C yaması, işi yapan bir Python yardımcısı ve yol boyunca takıldığım SDL2, argv ve güvenlik detayları.
Telefonuma bir XAPK kurmak her seferinde beni deli ediyordu. Dosyayı telefona at, reklamla dolu bir XAPK kurucu uygulaması aç, reklamı izle, klasörlerde dosyayı bul, kur, bir reklam daha. Basit bir oyunu kurmak bazen dakikalarımı alıyordu. Oysa scrcpy ile telefonumu bilgisayardan zaten yönetiyorum ve düz bir .apk dosyasını pencereye sürükleyip bıraktığımda kendiliğinden kuruluyor. Kafama takılan soru basitti: neden aynısı XAPK için de olmasın?
İşe girişmeden önce XAPK'nın ne olduğunu netleştirmem gerekti. Meğer içinde bir sihir yokmuş: bir XAPK aslında sadece bir ZIP arşivi. İçinde bir base APK, birkaç split APK (dil, ekran yoğunluğu, mimari parçaları) ve varsa OBB veri dosyaları duruyor. Yani "XAPK kur" demek pratikte "tüm split'leri tek oturumda adb install-multiple ile kur, OBB varsa /sdcard/Android/obb/ altına koy" demekmiş. Bunu anlayınca iş birden yapılabilir göründü.
- scrcpy v3.3.3yamalanan taban (SDL2 tabanlı son sürüm)
- adbinstall-multiple ve OBB push
- Python 3 (stdlib)yardımcı; pip bağımlılığı yok
- notify-sendmasaüstü bildirimi (opsiyonel)
- meson / ninjascrcpy'yi kaynaktan derlemek için
İki parça: ince bir C yaması, işi yapan bir Python yardımcısı
En baştan bir tasarım kararı verdim: scrcpy'nin C tarafına mümkün olduğunca az dokunmak, asıl mantığı ayrı bir Python yardımcısına koymak. Üç sebebi vardı. Birincisi, scrcpy hızlı güncelleniyor; C yaması ne kadar ince olursa yeni sürüme rebase etmek o kadar kolay oluyor (fark topu topu ~40 satır kaldı). İkincisi, ZIP açma, manifest okuma, adb komutu kurma gibi asıl iş Python'da olunca bunu bir cihaz bağlamadan test edebiliyorum. Üçüncüsü, yardımcı tek başına da çalışıyor; scrcpy olmadan terminalden de XAPK kurabiliyorum.
scrcpy'nin neresine bağlandım
scrcpy'de pencereye bir dosya bıraktığında olan şey şu: input_manager.c bırakılan dosyayı file_pusher.c'ye iletiyor, orada dosya .apk ise adb install çalışıyor, değilse dosya olduğu gibi /sdcard/Download'a push ediliyor. Yani XAPK'lar da bugüne kadar sessizce Download klasörüne kopyalanıyordu. Yapmam gereken, uzantıyı tanıyan bir kontrol ve yeni bir aksiyon türü (INSTALL_XAPK) eklemekti.
static bool
is_xapk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && (ext_iequals(ext, ".xapk") || ext_iequals(ext, ".apks")
|| ext_iequals(ext, ".apkm"));
}
// sürüklenen dosya .apk ise adb install, XAPK ailesindense yardımcıyı çağır,
// gerisini eskisi gibi /sdcard/Download'a push et
if (is_xapk(file)) {
action = SC_FILE_PUSHER_ACTION_INSTALL_XAPK;
} else if (is_apk(file)) {
action = SC_FILE_PUSHER_ACTION_INSTALL_APK;
} else {
action = SC_FILE_PUSHER_ACTION_PUSH_FILE;
}İlk çalıştırmada yardımcı hiç düzgün tetiklenmiyordu. Sorun aptalca ama öğreticiydi: yardımcıyı çağırırken argv dizisini kurarken serial'i koşulsuz ekliyordum. Ama scrcpy cihaz serial'ini bazen NULL veriyor. NULL'u argv'nin ortasına koyunca dizi orada bitmiş sayılıyor ve asıl dosya argümanı komple düşüyordu. Yani yardımcı dosyasız çağrılıyor, hiçbir şey kurulmuyordu.
// serial NULL olabilir; NULL ise --serial'i tamamen atlamalıyım, yoksa argv
// dizisi ortadan bir NULL ile erken biter ve dosya argümanı düşerdi
const char *argv[6];
size_t n = 0;
argv[n++] = helper; // "xapk-install"
if (serial) {
argv[n++] = "--serial";
argv[n++] = serial;
}
argv[n++] = file;
argv[n] = NULL;Aynı yamada küçük bir yan detay daha çıktı: scrcpy'nin log satırı "install mı push mu" ayrımını ikili bir koşulla yapıyordu. Üçüncü bir aksiyon (INSTALL_XAPK) ekleyince "INSTALL_APK değilse push" varsayımı yanlış hale geldi, o satırı da düzeltmem gerekti. İki durumluk bir mantığa üçüncüyü eklerken böyle sessiz varsayımlar insanı yakalıyor.
İki format, bir de biraz güvenlik paranoyası
Bütün XAPK'lar aynı değil. APKPure/APKMirror paketlerinde bir manifest.json var; oradan paket adını, split listesini ve OBB'leri okuyorum. Ama SAI gibi araçların ürettiği .apks dosyalarında manifest yok. O durumda arşivdeki her .apk'yı split kabul ediyorum, .obb dosyalarını topluyorum ve paket adını OBB dosya adının standart biçiminden türetiyorum. main/patch ile başlayıp ortadaki paket adını taşıyan o klasik isim kuralı işime yaradı.
def _package_from_obb_name(name):
"""`main.123.com.example.app.obb` -> `com.example.app` (yoksa None)."""
base = posixpath.basename(name)
if not base.endswith(".obb"):
return None
tokens = base[:-4].split(".")
if len(tokens) < 3 or tokens[0] not in ("main", "patch"):
return None
return ".".join(tokens[2:])Asıl zorlandığım yer: SDL2 mı, SDL3 mü
İşin en sinir bozucu kısmı XAPK mantığı değil, scrcpy'yi derleyebilmekti. scrcpy v4.0 ve sonrası SDL3 istiyor (libsdl3-dev), ama SDL3 bazı dağıtımların (mesela Ubuntu 24.04) repolarında henüz yok. Bu yüzden SDL2 tabanlı son sürüm olan v3.3.3'ü hedefledim; SDL2 her yerde repoda var, kutudan çıktığı gibi derleniyor.
İkinci tuzak daha sinsiydi. Bende sistemde elle kurulmuş, repo dışı ve daha yeni bir libsdl2 runtime vardı. Bu yüzden apt ile stok libsdl2-dev kurmaya çalışınca sürüm çakışması veriyordu. Sisteme dokunup runtime'ımı bozmak istemedim. Çözüm olarak install.sh, stok SDL2 geliştirme başlıklarını yerel bir klasöre indirip açıyor, symlink'i mevcut runtime'a yöneltiyor ve scrcpy'yi PKG_CONFIG_PATH ile ona bağlıyor. 2.30 başlıkları ile 2.31 runtime ABI-uyumlu olduğu için sorunsuz derleniyor ve benim özel SDL2'm aynen kalıyor. Kimseye tavsiye edilecek bir durum değil ama gerçek makinelerin hâli bu.
Cihazsız test ettim, sırada upstream
Asıl mantığı Python'a taşımanın karşılığını testte aldım. Runtime'da küçük sahte .xapk ve .apks ZIP'leri üretip (içinde boş placeholder .apk/.obb girişleri ve bir manifest.json ile) subprocess'i mock'ladım. Böylece dosya sınıflandırma, manifest ayrıştırma ve üretilen adb komut dizisi doğru mu, hepsini telefon bağlamadan doğrulayabildim. --dry-run bayrağı da hiçbir şey kurmadan çalıştıracağı komutları basıyor, bu hem test hem de güven için çok işe yaradı.
# scrcpy olmadan, düz terminalden de çalışır
xapk-install oyun.xapk # varsayılan cihaza kur
xapk-install -s SERIAL oyun.xapk # belirli cihaza kur
xapk-install --dry-run oyun.xapk # kurma, sadece çalıştıracağı adb komutlarını yazdırŞimdi kafamdaki soru bunu scrcpy'ye upstream olarak önermek. Genymobile/scrcpy'de bu isteğin açık bir issue'su var (#5378). Native C'de yapmanın iki açık noktası var: client'ta şu an bir ZIP okuyucu yok (miniz gibi tek başlıklı bir kütüphane şart olur) ve manifest için JSON bağımlılığı gerekir. İkincisini tamamen atlamak mümkün: formattan bağımsız gidip arşivdeki her .apk'yı install-multiple ile kurmak ve OBB paketini yine dosya adından türetmek. Bakalım bakımcılar sıcak bakacak mı.
Bu projede en çok vakit alan kısım, tahmin ettiğim gibi XAPK formatı değil, çevresindeki ekosistemdi: SDL sürümleri, argv'nin köşe durumları, ZIP'e güvenmemek. İşi Python'a taşıyıp C'yi olabildiğince ince tutma kararı hem testi hem de gelecekteki rebase'i kurtardı. Küçük bir özellik gibi başladı, bana asıl scrcpy'nin ve Android paketleme dünyasının nasıl çalıştığını öğretti.