المراية اللي كنت محتاجها
كنت عايز سطح مكتب Windows معكوس على تابلت Android. بناء الفكرة وصّلني لميكروفون بيطقطق، وExplorer بيكراش، ومراجعة لطريقتي في تشخيص الأخطاء مع AI.
أنا في مكالمة، وبقرا ملاحظات من شاشة نايمة قدام الكاميرا. قطعة زجاج مايلة بتعكس الشاشة ناحيتي، والكاميرا بتصوّر من خلالها. أقدر أقرا وأبص للعدسة في نفس الوقت، بشرط إن النص على الشاشة يكون معكوس أفقيًا. من غير العكس ده، انعكاس الكلام بيطلع مقلوب.

كنت عايز أبث سطح مكتب Windows مباشرة لشاشة Android وأعكسه عليها. افترضت إن تطبيق remote-desktop هيكون عنده اختيار لكده.
Claude Fable 5 عمل أغلب البحث وتقريبًا كل التنفيذ اللي جه بعد كده، بما فيه أدوات تشخيص الأخطاء. أنا وجّهت الشغل واختبرته على أجهزتي. النسخة الأولى اتجمعت في يومين تقريبًا. عشان تشتغل كويس في مكالمات حقيقية، احتجنا نراجع أكتر من إن الصورة ظهرت.
أدوّر على المراية المناسبة#
بصّينا على UltraVNC، وRealVNC، وTeamViewer، وAnyDesk. ما كنتش لقيت إعداد يحقق اللي محتاجه. طلب ميزة في PowerToys من 2021 وصف نفس الفرق بين تدوير الشاشة وعكسها عشان زجاج التيليبرومتر. الطلب اتقفل مع توجيه لـ Feedback Hub.
النسخة الأولى من المقالة بالغت في وصف البحث ده. قلت فيها إن scrcpy مش بيقدر يعكس الصورة وإن مفيش برنامج بيعمل اللي عايزه. التوثيق الحالي بيدعم العكس الأفقي بوضوح باستخدام --orientation=flip0. وطلب التيليبرومتر اللي كنت رابط له اتقفل كمان. Scrcpy بيعرض Android على الكمبيوتر؛ أنا كنت محتاج Windows على Android. اتجاه البث هو اللي كان فارق في الإعداد بتاعي.
كنت محتاج أي حاجة مفتوحة على سطح المكتب: سلايد متشارك في المكالمة، أو ملاحظاتي، أو نافذة أروح لها في النص. تطبيق بيمرّر سكريبت هيغطّي جزء من الاستخدام ده بس. بصّيت كمان على أجهزة تيليبرومتر مخصصة، وشفت لو ATEM Mini Extreme اللي عندي ممكن يساعد. وقتها كنت عايز أجرّب أبني حاجة للشاشة الموجودة معايا.
المطلوب كان شاشة Windows تانية على التابلت، والصورة النهائية تتعكس قبل ما توصل للزجاج. العكس ممكن يحصل في تطبيق Android. مش لازم يبقى إعداد في Windows.
اتصال واحد، وبعده خواص زيادة#
الخطة كانت برنامج على Windows يلتقط شاشة ويشفّر الفيديو ويبعتُه عبر الشبكة المحلية لتطبيق Android. التطبيق يفك التشفير ويعكس الفيديو ويعرضه بملء الشاشة تحت الزجاج.
أول ما الفيديو اشتغل، بقيت عايز اللمس والكيبورد يرجعوا للكمبيوتر. كده التابلت يبقى مفيد كجهاز إدخال كمان. بعدها حبيت الميكروفون بتاعه يبقى متاح لـ Windows، وبعده الإملاء جوه النافذة اللي فيها فوكس. كنت أقدر أجرّب كل خاصية باستخدام الاتصال اللي بنيناه، وفضلت أوافق على اللي بعدها.
الموجة الأولى، بالفيديو واللمس والميكروفون والإملاء، اتعملت في يومين تقريبًا. الـ host والعميل استخدموا بروتوكول صغير: بايت واحد لنوع الرسالة، وأربع بايتات لطول المحتوى، وبعدهم المحتوى نفسه. استخدمنا JSON لرسائل التحكم اللي مش بتتبعت كتير، وبايتات خام لرسائل اللمس والصوت المتكررة. كنت عايز التعديلات تبقى إضافات تحافظ على التوافق مع العملاء الأقدم.
الفيديو استخدم virtual display driver، وتشفير على كارت الشاشة بـ HEVC لما يكون مدعوم، وفك تشفير على Android. ظبّطناه لحد ما استجابة اللمس بقت مريحة ليا. ما عنديش مقارنة مقاسة للـ latency أحطّها جنب الانطباع ده. المشاكل الأصعب ظهرت لما بدأت أستخدم الخواص الزيادة.
الميكروفون اللي بيطقطق#
الميكروفون كان بيبعت الصوت من التابلت لكابل صوت افتراضي على الكمبيوتر، ومنه لبرنامج مكالمات أو تسجيل. اشتغل في حوالي يوم، بس التسجيلات كان فيها فجوات وطقطقة.
النسخة الأولى ما كانش فيها jitter buffer. الصوت كان بيوصل من الشبكة على دفعات، والتشغيل محتاج إمداد منتظم. ضفنا buffer بيتكبّر لما الاتصال يسوء، ويصغر لما يستقر. ده قلّل التقطيع، بس التحكم في سرعة التشغيل كان بيقيس كمية الصوت المحجوزة غلط وبدأ يتذبذب. صلّحنا ده كمان.
فضلت فيه فجوات في التسجيل، حتى والـ host بيسجّل صفر underruns. على جهاز Samsung اللي كنت بختبره، VOICE_COMMUNICATION كان بيقطع الميكروفون لفترات سكوت. التغيير لـ VOICE_RECOGNITION كشف مشكلة تانية: المعالجة على الجهاز ده كانت بتشوّه الإشارة.
ضفنا ترتيب بدائل ما بيقبلش مصدر صوت غير بعد ما يتهيّأ ويدخل فعلًا في حالة التسجيل. بيجرّب الصوت من غير معالجة لو مدعوم، وبعده مصدر الميكروفون، وبعدين الاختيارات الخاصة بالصوت البشري. ووقفنا إضافة تأثيرات صوت. التعديلات دي عالجت مشاكل الالتقاط اللي كنا شايفينها على الموبايل.
الـ host كان عنده مشكلة توقيت كمان. حلقة الـ polling كانت ممكن تفوّت نقص صوت قصير بين كل صحوة والتانية، بفاصل 20 ms. استبدلناها بتشغيل event-driven: Windows ينبهنا لما يحتاج فترة الصوت الجاية، والـ sink يملاها بالصوت المتاح أو يقلّل الصوت تدريجيًا لحد السكوت. التدرّج استخدم raised-cosine لمدة 10 ms:
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)); };
ده نعّم الانتقالات المفاجئة اللي ممكن تعمل طقطقة لما الصوت يتقطع. مسار التشغيل اتحسّن، بس الطقطقة فضلت موجودة.
بعد حوالي ست محاولات، طلبت من Fable يبطّل تخمين ويبني واجهة اختبار أقدر أسجّل بيها صوتي عبر اتصال PromptCast، من ميكروفون التابلت لـ Windows، عشان نفحص التسجيل بالتفصيل. استخدمتها وسجّلت ثلاثين ثانية من المخرجات في ملف WAV.
قلت إني سامع الطقطقات بفواصل متساوية. Fable فحص التسجيل واتأكد من تساوي الفواصل، واستبعد الاتصال كسبب: مشكلة الاتصال كانت هتعمل فجوات مش منتظمة، مش طقطقات بفواصل متساوية. التحليل لقى كمان تغيّرات حادة حوالي عشر أضعاف مستوى الضوضاء القريب من كل طقطقة. الفواصل المتساوية وجّهتنا لاحتمال مشكلة في الـ clock أو الـ sample rate، فقارنّا التسجيل بالصوت الواصل.
الطقطقات ما كانتش موجودة في الصوت الواصل. المقارنة وجّهتنا للمرحلة اللي بعده، الكابل الافتراضي. طرف التشغيل فيه كان مضبوط على 48 kHz، وطرف الالتقاط على 44.1 kHz. في الإعداد ده، فرق المعدّل بين الطرفين كان بيخلّي عينات تقع، وده عمل الانقطاعات المنتظمة اللي شفناها في الموجة.
ضبطنا صيغة الالتقاط عن طريق IPolicyConfig::SetDeviceFormat. تعديلات الـ registry السابقة ما كانتش بتثبت لما Windows يعيد تجهيز نقطة الصوت. التعديل ده ثبت، والطقطقة وقفت في تسجيلاتي. المثبّت بقى بيطبّق تصحيح الصيغة كمان.
صلّحنا مشاكل فعلية في الطريق. الطقطقة اللي فضلت كانت محتاجة مقارنة قبل الكابل وبعده، لأن عدادات الـ host ما بتورّيش اللي بيحصل بعد ما الصوت يخرج منه.
Explorer اللي كان بيكراش#
بعدها Windows Explorer بدأ يكراش، وده كان بيحصل كتير وأنا بسحب قرب حواف الشاشة. كنت بختبر الإيماءات أول ما التعديلات توصل. سجل أحداث Windows ورّانا access violation جوه twinui.pcshell.dll عند نفس الـ offset، تمن مرات على التوالي.
ده ادّانا توقيع ثابت للعطل، بس ما حدّدش اللي بيشغّله. تلات كراشات حصلوا خلال ست دقايق:
2026-08-31 07:05:30 Faulting application: Explorer.EXE
Faulting module: twinui.pcshell.dll, version 10.0.26100.9278
Exception code: 0xc0000005, fault offset: 0x00000000002bf184
2026-08-31 07:08:38 Faulting application: Explorer.EXE
Faulting module: twinui.pcshell.dll, version 10.0.26100.9278
Exception code: 0xc0000005, fault offset: 0x00000000002bf184
2026-08-31 07:10:55 Faulting application: Explorer.EXE
Faulting module: twinui.pcshell.dll, version 10.0.26100.9278
Exception code: 0xc0000005, fault offset: 0x00000000002bf184
النظرية الأولى كانت إن حقن اللمس قرب الحافة بيشغّل معالجة Windows للسحب من الحواف. وسّعنا المنطقة الميتة، ومنعنا أحداث عجلة الماوس من الوصول لنوافذ تابعة للـ shell. Explorer فضل يكراش.
طلبت سجل متجدد لكل إطار لمس بنحقنه، وthread تراجع معرّف عملية Explorer كل ثانية. لو Explorer أعاد التشغيل، الـ host يحفظ الخمس ثواني من الإدخال اللي سبقوا العطل.
السجلين اللي جم بعد كده ورّوا إيماءات عادية: ضغطة وحركة قصيرة بإصبعين. والاتنين كان فيهم نفس السطر قبل الكراش بحوالي ثلاث ثواني: “dismissed Windows touch keyboard.”
السطر ده كان من خاصية الكيبورد عندنا. لما Windows يحاول يظهر كيبورد اللمس، كنا بننهي العملية بتاعته عشان PromptCast يرفع كيبورده. السجلات ربطت إنهاء العملية بكراشات الـ shell اللي بتيجي بعدها. كنا بنبص على إحداثيات اللمس، وفيه كود عندنا بينهي عملية الـ shell بيستخدمها.
الحل كان نبعت لنافذة الكيبورد WM_SYSCOMMAND مع SC_CLOSE. ده بيخفي النافذة ويسيب العملية شغّالة. كراشات Explorer وقفت في اختباراتي.
السجل المفيد هنا كان تسلسل زمني: تطبيقنا عمل إيه قبل ما عملية تانية تقع. سجل الأحداث قال لنا Explorer وقع فين؛ واللوج الجديد ورّانا أنهي تصرّف نفحصه.
الشغل اتقسّم إزاي#
Fable نفّذ البروتوكول، وتشفير الفيديو وفكّه، والكيبورد، ومسار الصوت، والأدوات اللي استخدمناها لفحص العطلين. ده اللي خلّى السرعة دي ممكنة. دوري كان أقرر هنجرب إيه، وأختبر على الأجهزة وفي المكالمات، وأوصّل للـ Agent معلومات ما كانتش عنده.
تصحيح منهم كان ببساطة “أنا بختبر على الـ fold دلوقتي.” تشخيص الميكروفون في البداية كان راح ناحية الموبايل الغلط. تحديد الجهاز رجّع الشغل للسياق الصح.
لما الإصلاحات فضلت مش بتشيل الطقطقة، طلبت تحليل لتسجيل هعمله. ولما تعديلات اللمس ما وقفتش كراشات Explorer، طلبت logging حوالين العطل. Fable كان يقدر يبني الأدوات دي ويحلّل مخرجاتها؛ وأنا كنت محتاج ألاحظ إن الدليل الموجود مش بيجاوب على السؤال.
التحكم في الاتصال بين الجهازين خلّى كل خاصية زيادة قليلة التكلفة في التجربة. وكمان زوّد السلوك اللي محتاج أختبره وأصونه.
بستخدمه في إيه#
التابلت لسه تحت زجاج الـ beam-splitter، بيعرض شاشة Windows مع العكس الأفقي. أقدر أتنقل بين نوافذ المكالمة والملاحظات من غير ما أنقلهم لتطبيق تلقين مخصص.
الكيبورد المخصص بيظهر لما أدوس في حقل نص. الضغط المستمر على زرار الميكروفون بيبدأ الإملاء؛ والسحب لفوق بيثبّت التسجيل عشان أشيل صباعي. ضغطة تانية بتنهي التسجيل وتدخل النص في النافذة اللي فيها فوكس باستخدام نموذج Whisper محلي. مؤشر الميكروفون بيتفاعل مع مستوى الصوت بالأحمر، وبيبقى أزرق أثناء تسجيل الإملاء، فأعرف أنهي وضع صوت شغّال.
السكرول بإصبعين بيبعت أحداث عجلة ماوس، وضغطتين مع سحب بيحدّدوا النص. الإيماءات دي بتخلّيني أشتغل في الـ terminals اللي بستخدمها، حتى لما دعم اللمس فيها مش بيعمل اللي محتاجه.
بناء تطبيق Android بيشغّل تسعتاشر اختبار وحدة لهندسة الكيبورد، وتأطير البروتوكول، وإيماءات الإملاء، قبل تجهيز التطبيق. بتكشف أخطاء زي خروج رموز المفاتيح بره حدودها. تشخيص الميكروفون وExplorer احتاج كمان تسجيلات واختبار على الأجهزة الفعلية؛ اختبارات الوحدة دي ما كانتش تقدر تثبت إن النظام كله شغّال.

كنت بثيت لايف بإعداد أبسط قبل كده. لما كتبت المقالة أول مرة، كنت مستني تابلت جديد وعايز أجرّب PromptCast للبث لجمهور، جنب استخدامه في الاجتماعات.
بدأت برغبة إني أقرا الانعكاس صح في مكالمة. الموجة الصوتية وسجل الإدخال هما اللي سمحوا لي أفضل أستخدم الخواص اللي كبرت حوالين الفكرة دي.
اعرف أكتر عن طريقتي في الشغل. النشرة اللي تحت بتوصّل المقالات الجديدة لبريدك.
ملاحظة على التحديث: وضّحت طلبي لواجهة تسجيل وإنّي لاحظت بسمعي إن الطقطقات بفواصل متساوية قبل ما Fable يتأكد منها.