المرآة اللي محدش هيبنيها
مفيش أي تطبيق screen-mirroring في الدنيا بيعكس سطح مكتب حي أفقيًا، فبنيت برنامج بث كامل من Windows لـ Android عشان بس أشغّل تيليبرومتر واحد، وبعدين ماقدرتش أوقف.
أنا في مكالمة. حد بيعمل شير لسكرينه، حد تاني بيتكلم، وأنا بقرا سطر ملاحظات من شاشة تانية، متركبة ورا الكاميرا، وبتنعكس على قطعة زجاج. ده تيليبرومتر، نفس الخدعة اللي مذيعي الأخبار بيستخدموها من سبعين سنة، بس أنا مرتجلها بشاشة زيادة بدل جهاز مخصص. الفكرة بتشتغل، بس بشرط واحد: أي حاجة على الشاشة دي لازم تكون معكوسة. مقلوبة من الشمال لليمين. لأن قطعة الزجاج مش عارفة ولا مهتمة إنك قصدك تقرا النص عادي: هي بس بتعكس اللي موجود، بالمقلوب، ولو المصدر نفسه مش معكوس، يبقى كل حاجة بتشوفها من خلاله كمان مش معكوسة. الناس بتتحول لصور معكوسة من نفسها. تأشّر شمال، تلاقي نفسك بتأشّر يمين.
يبقى المطلوب: نعكس سطح المكتب، أفقيًا، ونبثه على شاشة تانية. ده كل اللي محتاجه. وافترضت إن في تطبيق remote-desktop، في مكان ما، عنده اختيار زي ده جاهز.
محدّش بيعكس سطح مكتب حي
محدش. ولا واحد.
راجعت القايمة البديهية الأول: UltraVNC، RealVNC، TeamViewer، AnyDesk. ولا واحد فيهم عنده خيار عكس، خالص. مش مخبّي في منيو، ولا وراء باقة مدفوعة. منتدى UltraVNC نفسه بيعتبرها حاجة الـ GPU driver المفروض يتكفل بيها، مش التطبيق، وده كلام صح، وفي نفس الوقت مالوش لازمة، لأن Windows نفسه مش عنده إعداد عكس أفقي. في طلب ميزة على PowerToys بيطلب بالظبط نفس الحاجة دي، متقدّم على أداة مايكروسوفت نفسها اللي هدفها سد الثغرات في Windows. اتقفل. “Windows مبيقدّمش خاصية عكس الشاشة، بس تدوير بس. من فضلك سجّل الطلب ده على Feedback Hub.” ده كلام مسؤولي مايكروسوفت نفسهم، على أداة مايكروسوفت نفسها لترقيع Windows، بيقولولك Windows مش قادر يعملها.
راجعت scrcpy كمان، لأنه الأداة اللي أغلب الناس بتستخدمها لعكس شاشة
Android على الكمبيوتر. أنا محتاج الاتجاه العكسي، بس لو حد كان بنى دعم
للعكس، كان هيكون موجود هناك. لقيت اتنين GitHub issues مفتوحين، ولا واحد
فيهم اتحل. واحد منهم
بيوصف بالظبط الموقف بتاعي، تابلت بيتعكس عن طريق scrcpy لتيليبرومتر، وماخدش
حل. scrcpy طلّع في الآخر خيار --orientation، بس ده تدوير: 0 أو 90 أو
180 أو 270 درجة. الصورة المعكوسة مش صورة مدوّرة. لو دوّرت وش معكوس 180
درجة، هيفضل وش معكوس، بس مقلوب كمان.
بعد كده راحت نظري ناحية الهاردوير، على أمل إن صناعة التيليبرومتر الاحترافية تكون حلّت الموضوع ده من زمان وأنا بس مش عارف الكلمات الصح للبحث. لحد حد ما. Prompter People بيبيعوا برنامج اسمه PowerFlip، بس هو بس بيعكس شرائح PowerPoint، مفيش حاجة تانية. الأجهزة الحقيقية من Ikan وDatavideo بتتجاوز البرنامج خالص: بتشحن شاشات بتعكس فيزيائيًا، متوصّلة مباشرة بلابتوب، والعكس بيحصل جوه إلكترونيات الشاشة نفسها قبل ما الصورة توصل للزجاج أصلًا. ده حل حقيقي وشغّال للابتوب متوصّل بشاشة عن طريق كابل، بس مش ليه علاقة باللي أنا محتاجه فعلاً: سطح مكتبي الحقيقي، معكوس، حي، بنفس الطريقة اللي شاشة تانية هتعرضه بيها، مش سكريبت متحمّل جوه نافذة تطبيق معين. أي حاجة فاتحة فعلاً. ملاحظات، سلايد متشيّر، مكالمة فيديو، اللي أنا باصص عليه بيتغيّر كل دقيقة، ومفيش حاجة من ده كله عايشة جوه تطبيق واحد مخصص.
ده الجزء اللي خدت أطول وقت عشان أسميه صح. الموضوع ماكانش أبدًا مشكلة “اقرا سكريبت”. تطبيقات التيليبرومتر المخصصة زي PromptSmart أو BIGVU بتحل المشكلة دي: بتكتب أو تلزّق السكريبت بتاعك قبليها، والتطبيق بيمرره سكرول. اللي أنا عايزه كان أبسط، وطلع أصعب بكتير: شاشة تانية. سطح مكتبي الحقيقي، أي حاجة فاتحة عليه، معكوس، حي، بنفس الطريقة اللي شاشة تانية موصّلة في الحيط هتعرضه بيها، بس من غير سلك ومعكوس. لو العكس مبيحصلش عند المصدر، قبل أي حاجة تانية، يبقى هي مش شاشة تانية حقيقية. دي مجرد تطبيق تاني بنافذته المحدودة، وعلى قد ما دوّرت، محدّش فعلاً بيبيع اللي أنا عايزه فعلاً.
كمان اتأكدت لو الهاردوير بتاع الـ video-switcher اللي أنا أصلاً عندي يقدر يعمل كده. ATEM Mini Extreme قاعد بين الكاميرا بتاعتي وكل حاجة تانية بابث بيها. منتدى Blackmagic نفسه بيأكد إن الـ Mini والـ Mini Extreme كمان معندهمش قدرة على عكس دخل HDMI. العكس لازم يحصل قبل ما الإشارة توصل لأي حاجة من الجهاز ده، أو محصلش خالص.
يبقى: مفيش برنامج، على أي بلاتفورم، من أي شركة، بيعمل كده. بطّلت أدوّر وبدأت أبني.
شكل الحكاية
الخطة كانت شبه محرجة في بساطتها على الورق: برنامج صغير على Windows بياخد جزء من سطح المكتب، يشفّره، ويبثه عبر الشبكة المحلية لتطبيق Android بيفك التشفير، يعكسه، ويعرضه full-screen على تابلت متركب ورا الكاميرا. مشروع عطلة أسبوع، على ما اتخيلت.
ماستناش عطلة أسبوع، لأن لحظة ما بقى عندي أنبوب فيديو شغّال بين الكمبيوتر والتابلت، ماقدرتش أسيبه لوحده. ما دام عندي فيديو باتنسيق منخفض ماشي في اتجاه واحد، ليه ما أضيفش لمس وكيبورد راجعين في الاتجاه التاني، وبكده التابلت بقى جهاز إدخال حقيقي تاني، مش مجرد مرآة. ما دام الإدخال راجع، ليه ما أضيفش الميكروفون بتاع التابلت نفسه، مبثوث للكمبيوتر وكأنه ميكروفون USB حقيقي. ما دام الميكروفون شغّال، ليه ما أمسكش زرار وأملي كلام مباشرة جوه أي نافذة فيها فوكس، وأنا قاعد على الكنبة. كل إجابة كانت “ليه لأ”، وفي خلال يومين تقريبًا كان عندي Windows host وAndroid client بيتكلموا عن طريق بروتوكول أنا صممته بنفسي: بايت واحد لنوع الرسالة، أربع بايتات لطولها، وبعدين أي محتوى (payload) يتناسب: JSON لما الرسالة نادرة ورخيصة التحليل، بايتات خام لما تكون حدث لمس أو جزء صوت وكل ميكروثانية من وقت التشفير مهمة. اتعمل versioning مرة واحدة، وبعدها إضافات بس، للأبد. مكنتش عايز أبدًا v2 مقدرش يتكلم مع عميل v1.
جانب الفيديو طلع الجزء السهل: virtual display driver، تشفير HEVC على هاردوير كارت الشاشة، وفك تشفير على جانب Android، مضبوط لحد ما الـ latency بقى واطي كفاية إن لمس التابلت حسّيته زي إني بلمس سطح المكتب الحقيقي فعلاً. الأخطاء المثيرة كانت في كل مكان تاني.
الميكروفون اللي بيعمل صوت طقطقة
خاصية “التابلت كميكروفون” اشتغلت، غالبًا، في خلال يوم. الصوت كان بيتبث من ميكروفون التابلت للكمبيوتر، عن طريق كابل صوت افتراضي، لأي مكالمة أو برنامج تسجيل شغّال ومستني. غالبًا. تحت كلمة “غالبًا” دي كان في خطأ من خمس طبقات مافهمتوش كويس غير بعد ما صلّحت الحاجة الغلط أربع مرات.
النسخة الأولى ماكانش فيها jitter buffer خالص. الصوت كان بيوصل من الشبكة على شكل دفعات، مش تدفق ثابت، وأي حاجة بتتوقع تدفق ثابت (يعني كل حاجة) كانت بتتقطّع. بنيت وسادة تكيّفية، نفس الفكرة اللي برامج الـ VoIP مستخدماها من عشرات السنين: احتفظ بجزء صغير من الصوت كاحتياطي، كبّره لما الشبكة تبقى وحشة، وصغّره تاني في الفترات الهادية. الحل ده صلّح التقطيع وجاب مشكلة جديدة، أهدى شوية: حلقة تغذية راجعة (feedback loop). منطق التوجيه بتاعي، الجزء اللي بيسرّع أو يبطّئ التشغيل شوية عشان يحافظ على حجم الـ buffer المستهدف، كان بيقيس الحاجة الغلط، واستقر على تذبذب كان بيصدر صوت طقطقة على فترة منتظمة بالظبط. مش ضوضاء عشوائية. متر إيقاع.
لحاق المشكلة دي وداني لجانب Android. بعد ما بنيت سلّم مصادر صوت متحقق
منه، وأكدت صفر underruns في لوجات الـ host نفسها، والتسجيل لسه فيه فجوات،
اكتشفت إن السكوت ماكانش مشكلة buffering أصلًا. مصدر الصوت
VOICE_COMMUNICATION بتاع سامسونج بيقفل الميكروفون لحظة ما يحس إنك في
مكالمة تليفون، وده اللي بث الصوت شكله بيحصل. تغيير المصدر صلّح الفجوات
وكشف الطبقة اللي بعدها: VOICE_RECOGNITION على الموبايل ده تحديدًا كان
بيمرر الإشارة عبر معالجة حوّلت الكلام لحاجة بين تشويش وصوت نفق رياح. الحل
كان سلّم بديل متحقق منه بيجرب مصدر، وينتقل للي بعده أول ما حالة التسجيل
مبتتأكدش، مع تعطيل كل تأثيرات الصوت صراحة، لأن واحدة من سلاسل DSP بتاعة
سامسونج نفسها طلعت هي اللي بتشوّه الإشارة من الأساس.
في المرحلة دي، التشغيل كان بيسأل عن صوت جديد كل مية ميلي ثانية تقريبًا، وهنا كانت الفجوة الحقيقية الأخيرة: نقص أقل من 20 ميلي ثانية، حلقة الـ polling ماكانتش عارفة تشوفه أصلًا لأسباب بنائية. أعدت كتابة مسار العرض عشان يبقى event-driven: Windows بيقول للـ sink بالظبط إمتى محتاج شريحة الصوت الجاية، وكل شريحة بتتملى فعليًا، صوت حقيقي لو موجود مستني، ولو مفيش، تلاشي راسم على شكل raised-cosine للسكوت بدل قطع مفاجئ:
constexpr int kFade = kRate / 100; // 10ms raised-cosine ramps
int gainPhase = 0; // 0 = silent .. kFade = unity
auto gain = [&](int p) { return 0.5 * (1.0 - cos(kPi * p / kFade)); };
القطع المفاجئ للسكوت هو دالة خطوة (step function)، ودالة الخطوة طاقتها بتنتشر على كل التردّدات، وده بالظبط اللي صوت الطقطقة هو أكوستيكيًا. الـ raised cosine بيحصر الانتقال تحت حدود اللي الأذن بتسمعه. أول ما كل فترة بقت مضمونة كاملة، سواء بصوت حقيقي أو بتلاشي صح، التسجيلات بقت نضيفة. تقريبًا.
فضلت شوية طقطقات، متباعدة بانتظام حسّيته ميكانيكي مش عشوائي، وأنا خلصت
نظريات. فوقفت عن التخمين وسجّلت المخرجات، ملف WAV عادي، ثلاثين ثانية،
جوه سكريبت Python. اكتشاف القيم الشاذة عن طريق المشتقة (derivative-outlier
detection) لقى ضجيج يعادل عشر أضعاف أرضية الضجيج المحلية عند كل طقطقة،
متباعدة بالتساوي بالظبط، وده استبعد أي حاجة ليها علاقة بالشبكة؛ الـ
jitter مبيجيش على جدول منتظم. الربط بين مغلّفات الإشارة (envelope
cross-correlation) صف الطقطقات مع ساعة العرض (render clock) مش ساعة
الشبكة. ده أشّر بالظبط على مكان واحد: الكابل الصوتي الافتراضي نفسه. جانب
العرض بتاعه كان شغال على 48kHz. جانب الالتقاط بتاعه كان شغال على
44.1kHz. مفيش حد بيعمل resampling بينهم، فالكابل كان بيرمي عينات بصمت
بمعدل ثابت عشان يلحق، بيلزّق الموجة عند فترات ثابتة، وده بالظبط نمط
الطقطقة اللي التحليل الجنائي كان لاقيه. عدّلت فورمات جهاز الالتقاط
مباشرة عن طريق IPolicyConfig::SetDeviceFormat، الكتابة الوحيدة اللي
فعلاً ثبتت (أي تعديل ألطف كان بيترجع لوحده صامت من بنّاء نقطة نهاية الصوت
بتاع Windows نفسه)، والطقطقات وقفت. كلها.
خمس أخطاء حقيقية، وواحد بس منهم كان مصدر الصوت الفعلي. الأربعة التانيين كانوا حقيقيين برضو. بس مكانوش هما ده.
الـ Shell اللي كان بيكراش
بعد كام أسبوع، Explorer بدأ يكراش. باستمرار، من غير نمط ثابت، أسوأ حاجة
لما بيحصل قرب حواف الشاشة أثناء السحب. لوج أحداث Windows نفسه كان بيوريني
نفس التوقيع في كل مرة: access violation جوه twinui.pcshell.dll، عند
نفس الـ offset بالظبط، تمن مرات على التوالي. ثبات زي ده عادة معناه سبب
واحد محدد وحتمي: مش هاردوير مش ثابت، مش race condition، حاجة الجهاز بيعملها
بنفس الطريقة كل مرة تطلبها منه.
نظريتي الأولى كانت اللمس نفسه: إيماءات محقونة (injected gestures) واقعة قرب حافة الشاشة، بتشغّل خاصية Windows للسحب من الحافة (edge-swipe recognizer). وسّعت المنطقة الميتة (dead zone)، وضفت حارس عشان أحداث العجلة ماتتبعتش أبدًا لنافذة يملكها الـ shell، وشحنت التحديث. Explorer فضل يكراش.
فبنيت الأداة اللي فعلاً تقدر تجاوب على السؤال بدل ما أخمّن تاني: لوج متجدد (rolling log) لكل إطار لمس الـ host حقنه، وخيط مراقبة (sentinel thread) بيراقب Process ID بتاع Explorer كل ثانية. لحظة ما Explorer يعيد تشغيل نفسه، الـ host كان بيسجّل آخر خمس ثواني بالظبط من اللي اتحقن قبل ما يموت. بعد كراشين، السجلين وريوا نفس الحاجة: مفيش حاجة غريبة في تدفق اللمس. ضغطة. إيماءة سريعة بإصبعين. مفيش حاجة المفروض تكرش شل.
بس السجلين كمان وريوا نفس سطر اللوج قبلها بحوالي ثلاث ثواني، في كل مرة:
“dismissed Windows touch keyboard.” ده كود أنا كتبته، خاصية بترفع
الكيبورد اللي على الشاشة بتاعي تلقائيًا عن طريق اكتشاف إن Windows بيحاول
يستدعي كيبورد اللمس بتاعه، وبتقفل البروسيس ده تمامًا عشان بتاعي ياخد
مكانه. طلع إن twinui.pcshell.dll هو بالظبط المكوّن اللي بيدير نافذة
الكيبورد ده من جوه Explorer. لما البروسيس اللي بيراقبه يتقفل من تحته،
ماكانش بيفشل بهدوء. كان بيكرش الـ shell كله، بعد كام ثانية، عند offset
ثابت، في كل مرة.
ماكانتش اللمس. ماكانتش حالة استثنائية. كانت خاصية أنا عملتها، بتقفل
بروسيس نظام التشغيل ماكانش متوقع إنه يموت. الحل كان شبه مهين في بساطته
أول ما عرفت أدوّر على إيه: اطلب من نافذة الكيبورد إنها تقفل بأدب،
WM_SYSCOMMAND مع SC_CLOSE، بدل ما تنهي البروسيس بتاعها. صفر كراشات
من ساعتها.
قضّيت أسبوع بصلّح حاجة غلط لأن الحاجة الغلط كانت قصة معقولة. الحاجة الصح كانت محتاجة دليل أنا لسه ماجمعتوش. دي المرة التانية كده، في المشروع ده. الخطأ كان في مكان أنا ماكنتش فكّرت أدوّر فيه، والحل كان إني أبني وسيلة أشوفه بيها فعلاً، بدل ما أفكر فيه أكتر.
بيعمل إيه دلوقتي
المرآة لسه شغّالة. ده كل سبب وجود المشروع ده أصلًا. اعكسها أفقيًا، ركّب التابلت ورا الكاميرا، وهتلاقيها تيليبرومتر لاسلكي حقيقي بزجاج beam-splitter قدامه، بيعرض سطح مكتبي الحقيقي بالظبط بنفس الطريقة اللي شاشة تانية هتعرضه بيها، بس معكوس: أي حاجة فاتحها، أي حاجة باقراها، وأي طريقة أنا فعلاً مستخدم بيها الجهاز في اللحظة دي.
كل حاجة تانية اتبنت فوق الخاصية دي الشغّالة. كيبورد مخصص عالشاشة بيرفع نفسه لحظة ما أدوس على حقل نص على السطح المبثوث، مفيش داعي أتنازع مع كيبورد اللمس بتاع Windows على المساحة. الضغط المستمر على زرار في الكيبورد ده بيبدأ تسجيل؛ سحب لفوق بيقفله ويثبته من غير إيد عشان مضطرش أفضل حاطط صباعي طول الجملة؛ ضغطة تانية بتنهي التسجيلة وتكتب النص المفرّغ مباشرة جوه أي نافذة فيها فوكس، عن طريق موديل Whisper محلي مش أي API سحابي. نقطة صغيرة على الواجهة بتنبض بالأحمر مع مستوى صوتي وأنا بتكلم لما الميكروفون شغّال، وبتبقى زرقاء وقت تسجيل مقطع إملاء. نظرة واحدة تكفي أعرف بيها لو الصوت فعلاً بيتدفق، وده كان لغز حقيقي وقت ملحمة التسجيل اللي فاتت. إصبعين بيعملوا سكرول لنافذة زي ما عجلة الماوس كانت هتعمل، لأن طلع إن أغلب محاكيات الـ terminal أصلًا مانفّذتش panning باللمس، وضغطتين متتاليتين مع سحب بيعملوا نفس الحيلة لتحديد النص، لأن الـ terminals كمان مانفّذتش تحديد باللمس.
مفيش حاجة من ده كله تخمين. تسعتاشر اختبار وحدة (unit tests) (هندسة الكيبورد، تأطير بروتوكول الاتصال، حالة إيماءة الإملاء) بتوقف أي نسخة جديدة قبل ما توصل لأي من موبايلاتي الاتنين. لو تعديل بكسر الحاجة اللي بتمنع رموز الكيبورد من إنها تفيض بره حدودها، النسخة ببساطة مبتحصلش.
كنت بثيت لايف قبل كده، بشكل أخشن، من غير أي حاجة من ده كله: تقدر تشوفه هنا. تابلت جديد جاي قريب، والخطة إني أجرب PromptCast لده صح: نفس المرآة، نفس الأنبوب منخفض الـ latency، بس موجّه لجمهور حقيقي مش مكالمة اجتماع.
اللي اتعلمته من بنائه
الخطأين الحقيقيين في القصة دي، الكابل اللي كان بيطقطق، والـ shell اللي كان بيكراش، كان تحتهم نفس الشكل تقريبًا. في الحالتين كان عندي نظرية معقولة، صلّحت اللي النظرية توقّعته، والعرض ماراحش، لأن النظرية كانت غلط ومفيش حاجة عملتها اختبرتها فعلاً ضد الدليل الحقيقي. الحل، في المرتين، كان نفسه: أوقف عن التفكير في النظام من بره، وابني حاجة تقدر تورّيني فعلاً اللي بيحصل جواه: موجة صوتية أحللها، ring buffer فيه بالظبط اللي اتحقن وإمتى. الخطأ عادة بيكون في مكان أنت لسه ماشُفتوش، وأسرع طريقة تلاقيه بيها نادرًا ما تكون إنك تفكر أكتر. هي إنك تبني الأداة اللي تقدر تثبته.
الدرس الأصغر عن نطاق المشروع. أنا ماقررتش من الأول إني أبني بروتوكول بث من Windows لـ Android، ولا كيبورد مخصص، ولا خط أنابيب “التابلت كميكروفون”، ولا نظام إملاء. أنا بس قررت إني أعكس شاشة. كل خاصية بعد كده كانت موجودة لأن أصلًا كان عندي قطعة البنية التحتية الوحيدة (أنبوب منخفض الـ latency بين جهازين) اللي خلّت تجربة الحاجة اللي بعدها رخيصة. ده الحجة الحقيقية لبناء أدواتك بنفسك بدل ما ترضى باللي موجود في السوق: مش إن السوق دايمًا غلط، بس إنك أول ما تمتلك الأنبوب كله، “ليه لأ” بتبطل سؤال محتاج تبرير.
إزاي اتبنى ده فعلاً
الدرس اللي فات، عن بناء الأداة بدل قبول نظرية معقولة، يستاهل إني أكون صريح فيه عن مصدره الحقيقي، لأني أنا مش اللي كتبت الكود اللي لقى أي واحد من السببين الجذريين.
Claude Fable 5 عمل تقريبًا كل التنفيذ في المشروع ده: البروتوكول، المشفّر وفاك التشفير، الكيبورد، خط أنابيب الصوت، وأداة التحليل الجنائي نفسها. دوري أنا كان التوجيه والاختبار، على هاردوير حقيقي، على مكالمات حقيقية، واتضح إن ده شغل حقيقي فعلاً.
كمان الموضوع ماخدش أسابيع. البروتوكول الأولي، أنبوب الفيديو، اللمس، خط أنابيب الميكروفون، والإملاء، كل ده اتظبط في يومين تقريبًا، سرعة مكنتش أقدر أوصلها لوحدي. ده مش جزء صغير من القصة دي. سبب إن “ليه لأ” فضلت الإجابة الصح، خاصية بعد خاصية، إن تكلفة تجربة الحاجة اللي بعدها كانت نزلت لتقريبًا صفر.
بشكل محدد: لما تصحيح الميكروفون الأولي راح يلف حوالين نظريات عن الموبايل الغلط، كان كفاية تصحيح واحد، “أنا فعلاً بختبر على الـ fold دلوقتي”، عشان يرجّع التحقيق كله للجهاز الصح. لما أربع حلول معقولة على التوالي مامسّوش الطقطقة الحقيقية، توجيهي ماكانش حل خامس أجربه. كان إني أفضل منضبط علميًا وأحلل موجة صوتية أسجّلها بنفسي. الجملة دي هي اللي فعلاً لقت اختلاف ساعة الكابل. لما Explorer فضل يكراش بعد أسبوع من ترقيعات معقولة لكن غلط، توجيهي تاني ماكانش حل. كان إننا محتاجين تسجيل حقيقي (logging) يوريني ليه ده بيحصل، مش تخمين تاني. ده اللي وصّل للـ ring buffer الجنائي اللي في الآخر مسك خاصيتي أنا بتقفل بروسيس الـ shell محتاجه يفضل حي.
الاختراقين في القصة دي جم من نفس المكان: مش نظرية أحسن، بس رفض قبول أي نظرية أصلًا لحد ما يبقى في دليل أتحقق بيه منها. أنا ماكتبتش أي حل من الاتنين. اللي أنا عملته، مرتين، في جزئين مش ليهم علاقة ببعض من النظام ده، إني أعرف بالظبط إمتى أوقف عن التخمين وأطلب دليل بدله. لو ده فعلاً المهارة الحقيقية وراء مشروع زي ده، أنا مبسوط إني أكون أنا اللي جبتها.