לדלג לתוכן

קלט ושירותי מושב המשתמש

קלט גולמי וקלט של חלון

כאשר אנחנו מזיזים עכבר, התהליך שמצייר חלון בדרך כלל אינו קורא ישירות את קובץ ההתקן. הדרייבר מדווח לתת מערכת הקלט, צרכן מתאים במושב מקבל אירועים ומעבד אותם, ומנהל התצוגה מוסר לחלון אירועים לפי מיקוד ומדיניות. חלוקה זו מאפשרת להפריד בין בעלות על חומרה לבין זכות לקבל קלט של חלון מסוים.

ממשק evdev מציג אירועים גולמיים יחסית. ספריית libinput מוסיפה טיפול בהתנהגות התקנים כמו האצה, מחוות והבחנה בין סוגי קלט. פרוטוקול החלונות מוסיף תיאום עם המשטח והמושב. שלוש השכבות אינן חלופיות לחלוטין, ולכל אחת חוזה אחר.

אירוע בודד אינו תמיד דגימה מלאה

פרוטוקול הקלט כולל סוג, קוד וערך. קבוצת שינויים יכולה להסתיים באירוע סנכרון שמציין גבול דיווח. עבור תנועה יחסית נקבל הפרשים, ועבור ציר מוחלט נקבל מצב בתחום שההתקן מפרסם. פירוש כל ערך כמיקום מוחלט יפיק תנועה שגויה.

כאשר הצרכן איטי מדי ותור האירועים שלו גולש, הוא יכול לקבל הודעה שהמצב אינו מסונכרן. עליו להתעלם מן האירועים עד גבול הדיווח הבא ואז לשחזר מצב מתאים באמצעות ממשקי השאילתה. אי אפשר לשחזר תנועות יחסיות שכבר אבדו רק מקריאת מצב נוכחי של כפתורים.

קורא מעבדה שמכיר באובדן

נכתוב קורא לאירועים של עכבר מעבדה בלבד. הדוגמה מיועדת ליעד לינוקס בן שישים וארבע סיביות שבו מבנה הזמן תואם למבנה שבספריית libc. נוודא את גודל המבנה לפני הקריאה. אין להשתמש בפריסה זו כפורמט קובץ נייד בין ארכיטקטורות.

use std::fs::File;
use std::io::{self, Read};
use std::mem::{size_of, MaybeUninit};

#[repr(C)]
struct Event {
    time: libc::timeval,
    kind: u16,
    code: u16,
    value: i32,
}
fn read_event(file: &mut File) -> io::Result<Event> {
    let mut event = MaybeUninit::<Event>::zeroed();
    let bytes = unsafe {
        std::slice::from_raw_parts_mut(event.as_mut_ptr().cast::<u8>(),
                                      size_of::<Event>())
    };
    file.read_exact(bytes)?;
    Ok(unsafe { event.assume_init() })
}
fn main() -> io::Result<()> {
    assert_eq!(size_of::<Event>(), 24, "example requires the stated ABI");
    let path = std::env::args_os().nth(1).expect("provide a lab mouse event path");
    let mut file = File::open(path)?;
    let (mut x, mut y) = (0i64, 0i64);
    let mut lost = false;
    loop {
        let event = read_event(&mut file)?;
        if event.kind == 0 && event.code == 3 {
            lost = true;
            x = 0;
            y = 0;
            continue;
        }
        if lost {
            if event.kind == 0 && event.code == 0 {
                return Err(io::Error::other("state lost; resynchronization required"));
            }
            continue;
        }
        match (event.kind, event.code) {
            (2, 0) => x += i64::from(event.value),
            (2, 1) => y += i64::from(event.value),
            (0, 0) => {
                println!("relative packet dx={x} dy={y}");
                x = 0;
                y = 0;
            }
            _ => {}
        }
    }
}

כל שדות המבנה הם מספרים שלכל תבנית סיביות שלהם משמעות תקפה, והזיכרון אותחל לפני חשיפתו. הפעולה הבלתי בטוחה נועדה למסור לקריאה אזור כתיבה מיושר בגודל מדויק. אין בה היתר להניח שפריסה של מבנה אחר זהה. ביישום נייד נשתמש בכריכה שממפה את ממשק היעד ובשאילתות היכולות שלו.

הדוגמה מסיימת במפורש כאשר נדרש שחזור מצב. זו התנהגות מוגבלת אך עקבית. שיפור אמיתי יוסיף שאילתת כפתורים וצירים נתמכים ויתחיל דור דיווח חדש, תוך ציון שתנועות יחסיות שאבדו לא שוחזרו. המשך הדפסה כאילו לא אבד דבר היה מייצר תוצאה מטעה.

מושב אינו משתמש בלבד

אותו משתמש יכול להחזיק כמה מושבים, ורק חלקם פעילים על עמדת קלט ותצוגה מסוימת. שירות ניהול המושבים יכול להעניק לצרכן המורשה גישה להתקנים, להשהות אותה במעבר מושב ולהודיע על שינוי. בדיקת מספר המשתמש פעם אחת בתחילת התוכנית אינה מודל מלא של חיי הסמכות.

הפעלת ממשק גרפי כמנהל רק כדי לפתוח התקן מערבבת סמכויות. בדרך כלל נחלק בין לקוח גרפי במושב לבין שירות בעל סמכות מצומצמת. השירות יחשוף פעולות מוגדרות ויבדוק את זהות המבקש ואת מדיניות ההרשאה לכל פעולה רלוונטית. מחרוזת מזהה משתמש שהלקוח שלח אינה הוכחה לזהותו.

לצורך תקשורת נוכל להשתמש באפיק ההודעות של המערכת או באפיק המשתמש, בהתאם לחיי השירות ולגבול הסמכות. אפיק המשתמש אינו תמיד זהה לחיי חלון בודד או למושב מסוים. אם השירות שומר משאב עבור לקוח, עליו לטפל גם בהיעלמות שם החיבור ובסיום המושב המתאים.

פורטל הוא בקשה בעלת מחזור חיים

בפעולות כמו בחירת קובץ או שיתוף מסך, פורטל מאפשר ליישום לבקש יכולת דרך שירות שמתווך מדיניות ואינטראקציה עם המשתמש. בקשה אסינכרונית יכולה ליצור אובייקט בקשה ואחר כך לשלוח תשובה. חיבור לתשובה רק לאחר שליחת הבקשה עלול ליצור חלון מירוץ אם לא משתמשים במנגנון התיאום שהממשק מספק.

בשיתוף מסך, אישור הבקשה יכול להחזיר גישה לזרם מדיה מסוים ולא סמכות כללית לקרוא כל מסך. חיי הזרם קשורים למושב ולרשות שניתנה. אם המושב נסגר או הרשות נשללת, הלקוח חייב לסיים צריכה ולשחרר מתארים. בחירת מסך חדשה היא בקשה חדשה ולא הנחה שהאישור הישן מכסה הכול.

נבנה עבור כל בקשה מצבים של נוצרה, ממתינה, אושרה, נדחתה, בוטלה ונסגרה. נחליט מה קורה לתשובה שמגיעה אחרי ביטול מקומי. כמו בפעולת קלט ופלט, הפסקת ההמתנה אינה בהכרח ביטול הפעולה בצד השירות.

להרחבה ניעזר בתיעוד אירועי הקלט ובממשק פורטל שיתוף המסך.