Devlog #4: TrackMyLayout Utility
@CompTalks (Denys Skobalo)Можна сказати закінчив розробку ядра утиліти. Наступним кроком, буде реалізувати UI, і це буде Swift. В мене досвід з Swift не велекий, але я мав досівд на курсі в університеті. Також, вибір впав на SwiftUI, так як захотів спробувати щось нове тому, що в університеті вивчали Swift+Storyboard, а мене не сильно прикалує робити через UI, надаю перевагу коду.
Перед початком роботи над інтерфейсом, змінив архітектуру проєкта, та провів невеликий рефакторинг. А саме розбив головну функцію агента слідкуваня за активним вікном на дві функції, agetn_active_app_start та agent_active_app_stop , з архітектурних та життєвих причин, а не з естетики. Головна причина це керування життєвим циклом (lifecycle management). Раніше функція startActiveApplication реалізовувала одночасне ініціалізування агента, запускала CFRunLoop, і фактично блокувала потік назавжди. Це нормально для CLI утиліти, але погано для SwiftUI, де:
-
RunLoopуже існує (керується SwiftUI), - компоненти мають запускатись і зупинятись реактивно.
Що дали оновлені функції:
agent_active_app_start()
- створює
AXObserver/NSWorkspace observer - реєструє callback, та не блокує потік
active_active_app_stop()
- знімає observer
- відписується від notifications
- звільняє ресурси (
AXObserver, CFRunLoopSource, memory)
Це критично важливо, бо у SwfitUI, View може з’являтися і зникати (onAppear / onDisappear), App може переходити у background, та агент не повинен жити вічно без контролю. І також це чіткий контракт API, start починає слухати, stop припиняє слухати. Без сюрпризів, без implict run loop.
Після рефакторингу, відкрив XCode, створив проєкт. Перше що я хотів, це протестувати агента який відслідковує додатки, тобто, щоб було вікно на якому є кнопка Start Agent і в терміналі виводилось активний додаток. Написав базовий код для вікна та використання агента в додатку.
Запустив, отримав warning'и та помилки, все як завжди. Почав розбиратись, створив TrackMyLayout-Bridging-Header.h як міст між Core та UI , щоб використовувати функції з Objective-C/C в Swfit коді, зайшов у Project Settings, підключив міст, переніс ядро в файлову архітектуру додатка. Спроба номер два, очищення кешу білда, заново білд, успішно, запуск, і все готово. Додаток стартує, має кнопку, при натисканні запускається агент, в терміналі аутпут логів. Отже підключення агента до Swift'а пройшло успішно.
Так, як я переніс файли ядра до файлової архітектури додатка, прийшлось трохи переписати main.c , а точніш додати CFRunLoop для того, щоб агент міг реагувати на події в системі без SwiftUI, тобто без GUI, яке саме підтримує RunLoop. CLI не мала б власного циклу обробки подій, і тоді агент просто виконав би функції і завершився, не відстежуючи зміни активного додатку. Додавши CFRunLoop, створюється власний event loop, який слухає події системи, таймери та observer-и, завдяки чому агент працює у real-time та реагує на зміни активного додатку так само, як це відбувається у графічному додатку.
Наступний крок, це реалізувати GUI для утиліти, також розібратись як зробити, щоб утиліти працювало в Menu-bar тобто у фоновому режимі, на кшталт утиліт Rectangle, RunCat тощо. Це поки що всі ідеї, що прийшли мені у голову.