إدارة عدة شاشات باستخدام Window Management API

الحصول على معلومات حول الشاشات المتصلة وتحديد موضع النوافذ بالنسبة إلى تلك الشاشات

Window Management API

تتيح لك واجهة برمجة التطبيقات Window Management API إدراج شاشات العرض المتصلة بجهازك ووضع النوافذ على شاشات محدّدة.

حالات الاستخدام المقترَحة

في ما يلي أمثلة على المواقع الإلكترونية التي قد تستخدم واجهة برمجة التطبيقات هذه:

  • يمكن لمحرّرات الرسومات المتعددة النوافذ، مثل Gimp، وضع أدوات التعديل المختلفة في نوافذ موضوعة بدقة.
  • يمكن أن تعرض منصات التداول الافتراضية مؤشرات السوق في نوافذ متعددة، ويمكن عرض أي منها في وضع ملء الشاشة.
  • يمكن لتطبيقات العروض التقديمية عرض ملاحظات المحاضر على الشاشة الأساسية الداخلية والعرض التقديمي على جهاز عرض خارجي.

كيفية استخدام Window Management API

المشكلة

إنّ الطريقة المجربة والمختبرة للتحكّم في النوافذ، Window.open()، لا تتيح للأسف التعرّف على الشاشات الإضافية. على الرغم من أنّ بعض جوانب واجهة برمجة التطبيقات هذه تبدو قديمة بعض الشيء، مثل المَعلمة windowFeatures DOMString، إلا أنّها كانت مفيدة لنا على مر السنين. لتحديد موضع نافذة، يمكنك تمرير الإحداثيات كـ left وtop (أو screenX وscreenY على التوالي) وتمرير الحجم المطلوب كـ width وheight (أو innerWidth وinnerHeight على التوالي). على سبيل المثال، لفتح نافذة بحجم 400×300 بكسل على بُعد 50 بكسل من اليسار و50 بكسل من الأعلى، يمكنك استخدام الرمز التالي:

const popup = window.open(
  'https://example.com/',
  'My Popup',
  'left=50,top=50,width=400,height=300',
);

يمكنك الحصول على معلومات حول الشاشة الحالية من خلال الاطّلاع على السمة window.screen التي تعرض الكائن Screen. في ما يلي الناتج على جهاز MacBook Pro‏ 13 بوصة:

window.screen;
/* Output from my MacBook Pro 13″:
  availHeight: 969
  availLeft: 0
  availTop: 25
  availWidth: 1680
  colorDepth: 30
  height: 1050
  isExtended: true
  onchange: null
  orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
  pixelDepth: 30
  width: 1680
*/

مثل معظم الأشخاص العاملين في مجال التكنولوجيا، كان عليّ التكيّف مع واقع العمل الجديد وإنشاء مكتب منزلي شخصي. يبدو إعدادي كما هو موضح في الصورة أدناه (إذا كنت مهتمًا، يمكنك قراءة التفاصيل الكاملة حول إعدادي). أستخدم جهاز iPad بجانب جهاز MacBook، وأربطهما معًا من خلال ميزة Sidecar، ما يتيح لي تحويل جهاز iPad إلى شاشة ثانية بسرعة عند الحاجة.

مقعد مدرسي على كرسيين على مقعد المدرسة، توجد علب أحذية تحمل جهاز كمبيوتر محمول وجهازَي iPad محيطَين به.
إعداد شاشات متعددة:

إذا أردت الاستفادة من الشاشة الأكبر، يمكنني وضع النافذة المنبثقة من نموذج الرمز أعلاه على الشاشة الثانية. أقوم بذلك على النحو التالي:

popup.moveTo(2500, 50);

هذا تقدير تقريبي، إذ لا يمكن معرفة أبعاد الشاشة الثانية. لا تغطي المعلومات الواردة من window.screen سوى الشاشة المدمجة، وليس شاشة iPad. كانت قيمة width التي تم الإبلاغ عنها للشاشة المضمّنة هي 1680 بكسل، لذا قد ينجح الانتقال إلى 2500 بكسل في نقل النافذة إلى جهاز iPad، لأنّني أعلم أنّها تقع على يسار جهاز MacBook. كيف يمكنني إجراء ذلك في الحالة العامة؟ تبيّن أنّ هناك طريقة أفضل من التخمين. هذه الطريقة هي Window Management API.

رصد الميزات

للتحقّق مما إذا كانت واجهة Window Management API متوافقة، استخدِم ما يلي:

if ('getScreenDetails' in window) {
  // The Window Management API is supported.
}

إذن window-management

قبل أن أتمكّن من استخدام Window Management API، يجب أن أطلب من المستخدم الإذن بذلك. يمكن طلب الإذن window-management باستخدام Permissions API على النحو التالي:

let granted = false;
try {
  const { state } = await navigator.permissions.query({ name: 'window-management' });
  granted = state === 'granted';
} catch {
  // Nothing.
}

أثناء استخدام المتصفّحات التي تتضمّن اسم الإذن القديم والجديد، احرص على استخدام رمز برمجي دفاعي عند طلب الإذن، كما هو موضّح في المثال أدناه.

async function getWindowManagementPermissionState() {
  let state;
  // The new permission name.
  try {
    ({ state } = await navigator.permissions.query({
      name: "window-management",
    }));
  } catch (err) {
    return `${err.name}: ${err.message}`;
  }
  return state;
}

document.querySelector("button").addEventListener("click", async () => {
  const state = await getWindowManagementPermissionState();
  document.querySelector("pre").textContent = state;
});

يمكن للمتصفّح أن يختار عرض طلب الإذن بشكل ديناميكي عند محاولة استخدام أي من طرق واجهة برمجة التطبيقات الجديدة للمرة الأولى. يمكنك مواصلة القراءة لمعرفة المزيد.

السمة window.screen.isExtended

لمعرفة ما إذا كانت هناك أكثر من شاشة واحدة متصلة بجهازي، يمكنني الوصول إلى السمة window.screen.isExtended. تعرض هذه السمة true أو false. بالنسبة إلى الإعدادات الخاصة بي، تعرض هذه الدالة القيمة true.

window.screen.isExtended;
// Returns `true` or `false`.

طريقة getScreenDetails()

بعد أن عرفت أنّ الإعداد الحالي هو إعداد متعدد الشاشات، يمكنني الحصول على مزيد من المعلومات حول الشاشة الثانية باستخدام Window.getScreenDetails(). سيؤدي استدعاء هذه الدالة إلى عرض طلب إذن يسألني عمّا إذا كان الموقع الإلكتروني مسموحًا له بفتح النوافذ ووضعها على شاشتي. تعرض الدالة وعدًا يتم تنفيذه باستخدام العنصر ScreenDetailed. على جهاز MacBook Pro 13 مع جهاز iPad متصل، يتضمّن ذلك الحقل screens مع عنصرَين من النوع ScreenDetailed:

await window.getScreenDetails();
/* Output from my MacBook Pro 13″ with the iPad attached:
{
  currentScreen: ScreenDetailed {left: 0, top: 0, isPrimary: true, isInternal: true, devicePixelRatio: 2, …}
  oncurrentscreenchange: null
  onscreenschange: null
  screens: [{
    // The MacBook Pro
    availHeight: 969
    availLeft: 0
    availTop: 25
    availWidth: 1680
    colorDepth: 30
    devicePixelRatio: 2
    height: 1050
    isExtended: true
    isInternal: true
    isPrimary: true
    label: "Built-in Retina Display"
    left: 0
    onchange: null
    orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
    pixelDepth: 30
    top: 0
    width: 1680
  },
  {
    // The iPad
    availHeight: 999
    availLeft: 1680
    availTop: 25
    availWidth: 1366
    colorDepth: 24
    devicePixelRatio: 2
    height: 1024
    isExtended: true
    isInternal: false
    isPrimary: false
    label: "Sidecar Display (AirPlay)"
    left: 1680
    onchange: null
    orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
    pixelDepth: 24
    top: 0
    width: 1366
  }]
}
*/

تتوفّر معلومات عن الشاشات المرتبطة في مصفوفة screens. لاحظ كيف تبدأ قيمة left لجهاز iPad عند 1680، وهي width شاشة العرض المدمجة. يتيح لي ذلك تحديد طريقة ترتيب الشاشات منطقيًا (بجانب بعضها البعض، أو فوق بعضها البعض، وما إلى ذلك). تتوفّر الآن أيضًا بيانات لكل شاشة توضّح ما إذا كانت شاشة isInternal أو isPrimary. يُرجى العِلم أنّ الشاشة المضمّنة ليست بالضرورة الشاشة الأساسية.

الحقل currentScreen هو عنصر مباشر يتوافق مع window.screen الحالي. يتم تعديل العنصر عند تغيير الجهاز أو مواضع النوافذ على الشاشات المختلفة.

حدث screenschange

الشيء الوحيد الذي ينقصني الآن هو طريقة لرصد التغييرات في إعدادات الشاشة. يؤدي حدث جديد، وهو screenschange، هذا الإجراء بالضبط: يتم تنشيطه كلّما تم تعديل مجموعة الشاشات. (يُرجى العِلم أنّ كلمة "شاشات" هي صيغة الجمع في اسم الحدث). وهذا يعني أنّ الحدث يتم تنشيطه كلما تم توصيل شاشة جديدة أو شاشة حالية (سواء كانت موصولة فعليًا أو افتراضيًا في حالة Sidecar) أو فصلها.

يُرجى العِلم أنّه عليك البحث عن تفاصيل الشاشة الجديدة بشكل غير متزامن، لأنّ الحدث screenschange نفسه لا يوفّر هذه البيانات. للبحث عن تفاصيل الشاشة، استخدِم العنصر المباشر من واجهة Screensمخزّنة مؤقتًا.

const screenDetails = await window.getScreenDetails();
let cachedScreensLength = screenDetails.screens.length;
screenDetails.addEventListener('screenschange', (event) => {
  if (screenDetails.screens.length !== cachedScreensLength) {
    console.log(
      `The screen count changed from ${cachedScreensLength} to ${screenDetails.screens.length}`,
    );
    cachedScreensLength = screenDetails.screens.length;
  }
});

حدث currentscreenchange

إذا كنت مهتمًا فقط بالتغييرات التي تطرأ على الشاشة الحالية (أي قيمة الكائن المباشر currentScreen)، يمكنني الاستماع إلى حدث currentscreenchange.

const screenDetails = await window.getScreenDetails();
screenDetails.addEventListener('currentscreenchange', async (event) => {
  const details = screenDetails.currentScreen;
  console.log('The current screen has changed.', event, details);
});

حدث change

أخيرًا، إذا كنت مهتمًا فقط بالتغييرات التي تطرأ على شاشة معيّنة، يمكنني الاستماع إلى حدث change الخاص بهذه الشاشة.

const firstScreen = (await window.getScreenDetails())[0];
firstScreen.addEventListener('change', async (event) => {
  console.log('The first screen has changed.', event, firstScreen);
});

خيارات جديدة لملء الشاشة

حتى الآن، كان بإمكانك طلب عرض العناصر في وضع ملء الشاشة باستخدام الطريقة requestFullScreen() التي تحمل الاسم المناسب. تتلقّى الطريقة المَعلمة options التي يمكنك تمرير FullscreenOptions إليها. حتى الآن، كانت السمة الوحيدة التي تتضمّنها هي navigationUI. تضيف Window Management API السمة screen الجديدة التي تتيح لك تحديد الشاشة التي سيتم بدء عرض ملء الشاشة عليها. على سبيل المثال، إذا كنت تريد عرض الشاشة الأساسية بملء الشاشة، اتّبِع الخطوات التالية:

try {
  const primaryScreen = (await getScreenDetails()).screens.filter((screen) => screen.isPrimary)[0];
  await document.body.requestFullscreen({ screen: primaryScreen });
} catch (err) {
  console.error(err.name, err.message);
}

Polyfill

لا يمكن تنفيذ Window Management API باستخدام polyfill، ولكن يمكنك محاكاة شكله حتى تتمكّن من كتابة الرمز البرمجي حصريًا باستخدام واجهة برمجة التطبيقات الجديدة:

if (!('getScreenDetails' in window)) {
  // Returning a one-element array with the current screen,
  // noting that there might be more.
  window.getScreenDetails = async () => [window.screen];
  // Set to `false`, noting that this might be a lie.
  window.screen.isExtended = false;
}

أما الجوانب الأخرى من واجهة برمجة التطبيقات، أي أحداث تغيير الشاشة المختلفة والسمة screen الخاصة بالعنصر FullscreenOptions، فلن يتم تشغيلها أو سيتم تجاهلها بدون تنبيه من قِبل المتصفحات غير المتوافقة.

عرض توضيحي

إذا كنت مثلي، فستراقب عن كثب تطوّر العملات المشفرة المختلفة. (في الواقع، أنا لا أوافق على ذلك لأنني أحب هذا الكوكب، ولكن لأغراض هذه المقالة، افترض أنّني أوافق على ذلك). لتتبُّع العملات المشفّرة التي أملكها، طوّرتُ تطبيق ويب يتيح لي متابعة الأسواق في جميع مواقف الحياة، مثلاً وأنا مستلقٍ على سريري حيث أستخدم شاشة واحدة.

شاشة تلفزيون كبيرة في نهاية سرير تظهر ساقا المؤلف جزئيًا على الشاشة، يظهر مكتب مزيّف لتداول العملات المشفّرة.
الاسترخاء ومتابعة الأسواق:

بما أنّ هذا المحتوى يتناول العملات المشفّرة، يمكن أن تصبح الأسواق محمومة في أي وقت. في حال حدوث ذلك، يمكنني الانتقال بسرعة إلى مكتبي حيث أستخدم شاشات متعددة. يمكنني النقر على نافذة أي عملة والاطّلاع بسرعة على التفاصيل الكاملة في عرض ملء الشاشة على الشاشة المقابلة. في ما يلي صورة حديثة لي تم التقاطها خلال مذبحة YCY الأخيرة. لقد فاجأني تمامًا، ولم أتمالك نفسي فوضعت يدي على وجهي.

المؤلف يضع يديه على وجهه المذعور وهو يحدّق في مكتب تداول العملات المشفّرة الوهمي.
أشعر بالذعر، وأشاهد مذبحة YCY.

يمكنك تجربة العرض التوضيحي المضمّن أدناه، أو الاطّلاع على الرمز المصدر على GitHub.

الأمان والأذونات

صمَّم فريق Chrome واجهة Window Management API ونفَّذها باستخدام المبادئ الأساسية المحدّدة في التحكّم في الوصول إلى الميزات الفعّالة لمنصة الويب، بما في ذلك تحكّم المستخدم والشفافية وبيئة العمل المريحة. تعرض واجهة برمجة التطبيقات Window Management API معلومات جديدة حول الشاشات المتصلة بجهاز، ما يزيد من مساحة تحديد بصمة المستخدمين، خاصةً أولئك الذين لديهم شاشات متعددة متصلة بأجهزتهم بشكل دائم. وللحدّ من هذه المخاوف بشأن الخصوصية، تقتصر خصائص الشاشة المعروضة على الحد الأدنى المطلوب لحالات الاستخدام الشائعة في مواضع الإعلانات. يجب الحصول على إذن المستخدم لكي تتمكّن المواقع الإلكترونية من الحصول على معلومات حول الشاشات المتعدّدة وتحديد مواضع النوافذ على الشاشات الأخرى. في حين يعرض Chromium تصنيفات مفصّلة للشاشة، يمكن للمتصفّحات عرض تصنيفات أقل وصفًا (أو حتى تصنيفات فارغة).

تحكّم المستخدم

يتحكّم المستخدم بشكل كامل في مستوى ظهور إعداداته. ويمكنهم قبول طلب الإذن أو رفضه، وإبطال إذن تم منحه سابقًا من خلال ميزة معلومات الموقع الإلكتروني في المتصفّح.

التحكّم في المؤسسة

يمكن لمستخدمي Chrome Enterprise التحكّم في العديد من جوانب Window Management API كما هو موضّح في القسم ذي الصلة من إعدادات مجموعات السياسات الذرية.

الشفافية

يتم عرض ما إذا تم منح الإذن باستخدام Window Management API في معلومات الموقع الإلكتروني للمتصفّح، ويمكن أيضًا الاستعلام عنه من خلال Permissions API.

استمرار الإذن

يحتفظ المتصفّح بأذونات الوصول. يمكن إلغاء الإذن من خلال معلومات الموقع الإلكتروني في المتصفّح.

الملاحظات

يريد فريق Chrome معرفة رأيك في تجربة استخدام Window Management API.

أخبِرنا عن تصميم واجهة برمجة التطبيقات

هل هناك أي شيء في واجهة برمجة التطبيقات لا يعمل على النحو المتوقّع؟ أو هل هناك طرق أو سمات مفقودة تحتاج إليها لتنفيذ فكرتك؟ هل لديك سؤال أو تعليق بشأن نموذج الأمان؟

  • يمكنك الإبلاغ عن مشكلة في المواصفات في مستودع GitHub ذي الصلة، أو إضافة أفكارك إلى مشكلة حالية.

الإبلاغ عن مشكلة في عملية التنفيذ

هل عثرت على خطأ في تنفيذ Chrome؟ أو هل يختلف التنفيذ عن المواصفات؟

  • يمكنك الإبلاغ عن خطأ على new.crbug.com. احرص على تضمين أكبر قدر ممكن من التفاصيل، وتعليمات بسيطة لإعادة إنتاج الخطأ، وأدخِل Blink>Screen>MultiScreen في المربّع المكوّنات.

إظهار الدعم لواجهة برمجة التطبيقات

هل تخطّط لاستخدام Window Management API؟ يساعد دعمك العلني فريق Chrome في تحديد أولويات الميزات، ويوضّح لمورّدي المتصفّحات الآخرين مدى أهمية توفيرها.

  • شارِك كيف تخطّط لاستخدامه في سلسلة محادثات WICG Discourse.
  • يمكنك إرسال تغريدة إلى ‎@ChromiumDev باستخدام الهاشتاغ #WindowManagement وإخبارنا بمكان استخدامك لهذه الميزة وكيفية استخدامها.
  • اطلب من مورّدي المتصفحات الآخرين تنفيذ واجهة برمجة التطبيقات.

روابط مفيدة

الإقرارات

تم تعديل مواصفات Window Management API بواسطة فيكتور كوستان و جوشوا بيل و مايك واسرمان. تم تنفيذ واجهة برمجة التطبيقات من قِبل مايك واسرمان و أدريان ووكر. راجع هذه المقالة جو ميدلي وفرانسوا بوفورت وكايسي باسكس. نشكر "لورا تورنت بويغ" على الصور.