#!/usr/bin/env python3 """Native Linux AI Cockpit: JSON state renderer + event writer.""" from __future__ import annotations import sys from typing import Any import gi from state_store import StateStore gi.require_version("Adw", "1") gi.require_version("Gtk", "4.0") from gi.repository import Adw, Gio, Gtk, Pango # noqa: E402 APP_ID = "dev.sidecarcommando.AICockpit" SIDECAR_ICONS = { "focus": "system-run-symbolic", "scope": "media-playlist-shuffle-symbolic", "finish": "emblem-ok-symbolic", "reality": "edit-find-symbolic", "regulation": "preferences-system-symbolic", } CSS = """ window { background: #030b13; color: #f3f7ff; } .app-shell { background: radial-gradient(circle at 72% 0%, rgba(74, 42, 120, 0.16), transparent 34%), linear-gradient(135deg, #030912, #081522 46%, #06101a); } .sidebar { padding: 22px 14px 16px 14px; background: rgba(4, 13, 22, 0.94); border-right: 1px solid rgba(144, 166, 196, 0.18); } .main { padding: 18px 22px 18px 22px; } .brand-title { font-size: 23px; font-weight: 800; } .brand-subtitle, .muted { color: #a7b2c3; } .nav-item { padding: 12px 14px; border-radius: 8px; color: #aab6c8; } .nav-item-active { color: #bd8cff; background: rgba(108, 72, 170, 0.17); border-left: 2px solid #8e5aff; } .nav-icon, .work-icon { color: #9aa9bd; } .sidebar-focus { padding: 14px; border-radius: 8px; background: rgba(11, 24, 39, 0.82); border: 1px solid rgba(144, 166, 196, 0.16); } .sidebar-focus-dot { color: #59e17b; } .sidebar-focus-name { font-weight: 800; color: #f3f7ff; } .focus-panel, .lead-card, .sidecar-card, .next-step, .work-panel, .work-section, .code-panel { border: 1px solid rgba(144, 166, 196, 0.24); border-radius: 8px; background: rgba(13, 25, 40, 0.92); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.24); } .focus-panel { padding: 18px 20px; } .focus-target { border-left: 1px solid rgba(144, 166, 196, 0.18); padding-left: 22px; } .work-panel { padding: 14px; } .eyebrow { color: #9b6dff; font-weight: 800; font-size: 12px; letter-spacing: 0.06em; } .focus-subtitle { color: #d4dbea; font-size: 17px; } .focus-goal { color: #59e17b; font-size: 13px; font-weight: 800; } .focus-action-button, .owner-button { padding: 8px 12px; border-radius: 7px; background: rgba(36, 50, 68, 0.74); border: 1px solid rgba(144, 166, 196, 0.22); } .section-title { font-size: 14px; font-weight: 800; color: #9b6dff; } .field-label { color: #9aa9bd; font-size: 12px; font-weight: 700; } entry, textview { color: #edf4ff; background: #0b1725; border: 1px solid rgba(144, 166, 196, 0.22); border-radius: 8px; } .focus-entry { color: #f3f7ff; background: transparent; border: 0; box-shadow: none; padding: 0; font-size: 30px; font-weight: 800; } .code-panel { background: rgba(7, 17, 29, 0.96); } .code-header { padding: 10px 12px; border-bottom: 1px solid rgba(144, 166, 196, 0.18); } .code-text { font-family: monospace; color: #cdd8e7; padding: 12px; font-size: 14px; } .work-section { padding: 12px; } .todo-row { color: #d7e0ee; } .todo-done { color: #59e17b; } .action-grid { margin-top: 2px; } .work-footer { color: #8f9cad; font-size: 12px; } .lead-card { padding: 22px 20px; border-color: #9b6dff; background: linear-gradient(135deg, rgba(44, 22, 79, 0.84), rgba(11, 21, 35, 0.96)); } .lead-title { font-size: 26px; font-weight: 800; } .lead-copy { font-size: 16px; line-height: 1.55; } .sidecar-card { padding: 18px; min-height: 194px; } .sidecar-title { font-size: 19px; font-weight: 800; } .sidecar-copy { color: #d8e1ee; font-size: 14px; line-height: 1.45; } .badge { border-radius: 7px; padding: 5px 10px; font-size: 12px; font-weight: 800; } .badge-hard { color: #bd8cff; border: 1px solid #8e5aff; background: rgba(98, 57, 160, 0.18); } .badge-medium { color: #59e17b; border: 1px solid #2bb457; background: rgba(43, 180, 87, 0.14); } .badge-low { color: #4da9ff; border: 1px solid #2580d7; background: rgba(37, 128, 215, 0.13); } .sidecar-icon { min-width: 48px; min-height: 48px; border-radius: 999px; border: 1px solid currentColor; background: rgba(255, 255, 255, 0.04); } .lead-card .sidecar-icon { min-width: 58px; min-height: 58px; } .sidecar-focus { color: #bd8cff; border-color: #9b6dff; } .sidecar-scope { color: #59e17b; border-color: #2bb457; background: linear-gradient(135deg, rgba(20, 71, 50, 0.48), rgba(11, 24, 32, 0.95)); } .sidecar-finish { color: #f4d044; border-color: #d5b900; background: linear-gradient(135deg, rgba(82, 68, 13, 0.38), rgba(16, 25, 27, 0.95)); } .sidecar-reality { color: #4da9ff; border-color: #2580d7; background: linear-gradient(135deg, rgba(18, 57, 92, 0.42), rgba(10, 22, 34, 0.95)); } .sidecar-regulation { color: #ff8a25; border-color: #e56f00; background: linear-gradient(135deg, rgba(87, 42, 8, 0.36), rgba(19, 22, 24, 0.95)); } .next-step { padding: 16px 20px; border-color: #2387d6; background: rgba(9, 35, 58, 0.92); } .next-label { color: #4da9ff; font-size: 13px; font-weight: 800; } .next-copy { font-size: 18px; font-weight: 650; } .hint { color: #8793a4; font-size: 12px; } """ def text(value: Any, fallback: str = "") -> str: return value if isinstance(value, str) else fallback def set_text_view(view: Gtk.TextView, value: str) -> None: view.get_buffer().set_text(value) def focus_name(state: dict[str, Any]) -> str: return text(state.get("active_focus", {}).get("name"), "miniZentrale") class SidecarCard(Gtk.Box): def __init__(self, sidecar: dict[str, Any], lead: bool = False) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=12) self.sidecar = sidecar self.lead = lead sidecar_id = text(sidecar.get("id"), "unknown") tone = text(sidecar.get("tone"), "LOW") self.add_css_class("lead-card" if lead else "sidecar-card") self.add_css_class(f"sidecar-{sidecar_id}") if not lead: self.set_hexpand(True) self.set_vexpand(True) header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) header.set_valign(Gtk.Align.START) self.append(header) icon = Gtk.Box() icon.set_size_request(58 if lead else 48, 58 if lead else 48) icon.set_halign(Gtk.Align.START) icon.set_valign(Gtk.Align.START) icon.add_css_class("sidecar-icon") icon_image = Gtk.Image.new_from_icon_name(SIDECAR_ICONS.get(sidecar_id, "dialog-information-symbolic")) icon_image.set_pixel_size(30 if lead else 24) icon_image.set_halign(Gtk.Align.CENTER) icon_image.set_valign(Gtk.Align.CENTER) icon.append(icon_image) header.append(icon) title_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) title_box.set_hexpand(True) header.append(title_box) if lead: eyebrow = Gtk.Label(label="LEAD SIDECAR") eyebrow.set_xalign(0) eyebrow.add_css_class("eyebrow") title_box.append(eyebrow) title = Gtk.Label(label=text(sidecar.get("title"), "Sidecar")) title.set_xalign(0) title.set_wrap(True) title.add_css_class("lead-title" if lead else "sidecar-title") title_box.append(title) badge = Gtk.Label(label=tone) badge.add_css_class("badge") badge.add_css_class(f"badge-{tone.lower()}") header.append(badge) self.copy = Gtk.Label() self.copy.set_xalign(0) self.copy.set_valign(Gtk.Align.START) self.copy.set_wrap(True) self.copy.set_wrap_mode(Pango.WrapMode.WORD_CHAR) self.copy.add_css_class("lead-copy" if lead else "sidecar-copy") self.append(self.copy) def update(self, sentences: list[str]) -> None: self.copy.set_label("\n".join(sentences[:3])) class CockpitWindow(Adw.ApplicationWindow): def __init__(self, app: Adw.Application) -> None: super().__init__(application=app, title="AI Cockpit") self.set_default_size(1120, 760) self.store = StateStore() self.state = self.store.load_state() self.focus_entry = Gtk.Entry() self.focus_entry.set_has_frame(False) self.focus_entry.set_hexpand(True) self.focus_entry.set_editable(False) self.focus_entry.add_css_class("focus-entry") self.focus_subtitle = Gtk.Label() self.focus_goal_copy = Gtk.Label() self.sidebar_focus_name = Gtk.Label() self.active_time_label = Gtk.Label() self.file_label = Gtk.Label() self.work_status_label = Gtk.Label() self.code_label = Gtk.Label() self.notes_view = Gtk.TextView() self.threads_view = Gtk.TextView() self.todo_list = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=9) self.saved_label = Gtk.Label() self.language_label = Gtk.Label() self.lead_slot = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.quiet_grid = Gtk.Grid(column_spacing=14, row_spacing=14) self.next_copy = Gtk.Label() self._build() self.render_state() def _build(self) -> None: shell = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) shell.add_css_class("app-shell") self.set_content(shell) shell.append(self._build_sidebar()) scroller = Gtk.ScrolledWindow() scroller.set_hexpand(True) shell.append(scroller) root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16) root.add_css_class("main") scroller.set_child(root) root.append(self._build_focus_panel()) body = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=16) body.set_hexpand(True) body.set_vexpand(True) root.append(body) work_panel = self._build_work_panel() work_panel.set_size_request(430, -1) body.append(work_panel) output = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16) output.set_hexpand(True) output.set_vexpand(True) body.append(output) self.lead_slot.set_hexpand(True) output.append(self.lead_slot) self.quiet_grid.set_column_homogeneous(True) self.quiet_grid.set_row_homogeneous(True) self.quiet_grid.set_hexpand(True) self.quiet_grid.set_vexpand(True) output.append(self.quiet_grid) root.append(self._build_next_step()) hint = Gtk.Label(label="Hinweis: Nur ein Sidecar ist Lead. Die anderen sind leise Karten.") hint.set_halign(Gtk.Align.CENTER) hint.add_css_class("hint") root.append(hint) def _build_sidebar(self) -> Gtk.Box: sidebar = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=18) sidebar.add_css_class("sidebar") sidebar.set_size_request(214, -1) brand = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) sidebar.append(brand) brand_icon = Gtk.Image.new_from_icon_name("applications-science-symbolic") brand_icon.set_pixel_size(36) brand_icon.add_css_class("sidecar-focus") brand.append(brand_icon) brand_text = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) brand.append(brand_text) title = Gtk.Label(label="AI Cockpit") title.set_xalign(0) title.add_css_class("brand-title") brand_text.append(title) subtitle = Gtk.Label(label="Dein agentischer Leitstand") subtitle.set_xalign(0) subtitle.add_css_class("brand-subtitle") brand_text.append(subtitle) nav = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4) sidebar.append(nav) for index, (icon_name, label) in enumerate( ( ("view-dashboard-symbolic", "Cockpit"), ("find-location-symbolic", "Fokus"), ("package-x-generic-symbolic", "Komponenten"), ("folder-symbolic", "Parks"), ("text-x-generic-symbolic", "Beweise"), ("utilities-system-monitor-symbolic", "AI Log"), ("preferences-system-symbolic", "Einstellungen"), ) ): nav.append(self._nav_item(icon_name, label, active=index == 0)) spacer = Gtk.Box() spacer.set_vexpand(True) sidebar.append(spacer) focus_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=9) focus_box.add_css_class("sidebar-focus") sidebar.append(focus_box) focus_label = Gtk.Label(label="Aktiver Fokus") focus_label.set_xalign(0) focus_label.add_css_class("muted") focus_box.append(focus_label) focus_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=7) focus_box.append(focus_row) dot = Gtk.Label(label="●") dot.add_css_class("sidebar-focus-dot") focus_row.append(dot) self.sidebar_focus_name.set_xalign(0) self.sidebar_focus_name.add_css_class("sidebar-focus-name") focus_row.append(self.sidebar_focus_name) self.active_time_label.set_xalign(0) self.active_time_label.add_css_class("muted") focus_box.append(self.active_time_label) return sidebar def _nav_item(self, icon_name: str, label: str, active: bool = False) -> Gtk.Box: item = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) item.add_css_class("nav-item") if active: item.add_css_class("nav-item-active") icon = Gtk.Image.new_from_icon_name(icon_name) icon.set_pixel_size(24) icon.add_css_class("nav-icon") item.append(icon) item_label = Gtk.Label(label=label) item_label.set_xalign(0) item.append(item_label) return item def _build_focus_panel(self) -> Gtk.Box: focus_panel = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=22) focus_panel.add_css_class("focus-panel") focus_icon = Gtk.Box() focus_icon.set_size_request(70, 70) focus_icon.set_halign(Gtk.Align.START) focus_icon.set_valign(Gtk.Align.CENTER) focus_icon.add_css_class("sidecar-icon") focus_icon.add_css_class("sidecar-scope") focus_image = Gtk.Image.new_from_icon_name("find-location-symbolic") focus_image.set_pixel_size(38) focus_image.set_halign(Gtk.Align.CENTER) focus_image.set_valign(Gtk.Align.CENTER) focus_icon.append(focus_image) focus_panel.append(focus_icon) focus_text = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) focus_text.set_hexpand(True) focus_panel.append(focus_text) eyebrow = Gtk.Label(label="AKTIVER FOKUS") eyebrow.set_xalign(0) eyebrow.add_css_class("eyebrow") focus_text.append(eyebrow) focus_text.append(self.focus_entry) self.focus_subtitle.set_xalign(0) self.focus_subtitle.add_css_class("focus-subtitle") focus_text.append(self.focus_subtitle) target = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) target.set_size_request(360, -1) target.set_valign(Gtk.Align.CENTER) target.add_css_class("focus-target") focus_panel.append(target) target_label = Gtk.Label(label="FOKUS-ZIEL") target_label.set_xalign(0) target_label.add_css_class("focus-goal") target.append(target_label) self.focus_goal_copy.set_xalign(0) self.focus_goal_copy.set_wrap(True) target.append(self.focus_goal_copy) return focus_panel def _build_work_panel(self) -> Gtk.Box: panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) panel.add_css_class("work-panel") title = Gtk.Label(label="DEINE AKTUELLE ARBEIT") title.set_xalign(0) title.add_css_class("section-title") panel.append(title) panel.append(self._build_code_panel()) panel.append(self._text_field("NOTIZEN", self.notes_view, height=88)) panel.append(self._text_field("OFFENE FÄDEN", self.threads_view, height=70)) panel.append(self._build_todo_panel()) panel.append(self._build_action_bar()) footer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) footer.add_css_class("work-footer") panel.append(footer) self.saved_label.set_xalign(0) self.saved_label.set_hexpand(True) footer.append(self.saved_label) footer.append(self.language_label) return panel def _build_code_panel(self) -> Gtk.Box: panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) panel.add_css_class("code-panel") header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) header.add_css_class("code-header") panel.append(header) icon = Gtk.Image.new_from_icon_name("text-x-python-symbolic") icon.set_pixel_size(20) icon.add_css_class("work-icon") header.append(icon) self.file_label.set_xalign(0) self.file_label.set_hexpand(True) header.append(self.file_label) self.work_status_label.add_css_class("muted") header.append(self.work_status_label) self.code_label.set_xalign(0) self.code_label.set_wrap(False) self.code_label.add_css_class("code-text") panel.append(self.code_label) return panel def _build_todo_panel(self) -> Gtk.Box: panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=9) panel.add_css_class("work-section") title = Gtk.Label(label="TODO") title.set_xalign(0) title.add_css_class("field-label") panel.append(title) panel.append(self.todo_list) return panel def _build_action_bar(self) -> Gtk.Box: panel = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=9) panel.add_css_class("work-section") title = Gtk.Label(label="OWNER-AKTIONEN") title.set_xalign(0) title.add_css_class("field-label") panel.append(title) grid = Gtk.Grid(column_spacing=8, row_spacing=8) grid.add_css_class("action-grid") panel.append(grid) actions = ( ("Faden parken", "thread_parked"), ("Abschlussaktion setzen", "finish_action_set"), ("Todo erledigen", "todo_completed"), ("Beweis hinzufügen", "evidence_added"), ) for index, (label, event_type) in enumerate(actions): button = Gtk.Button(label=label) button.add_css_class("owner-button") button.connect("clicked", lambda _button, kind=event_type: self._handle_owner_action(kind)) grid.attach(button, index % 2, index // 2, 1, 1) return panel def _todo_row(self, item: dict[str, Any]) -> Gtk.Box: is_done = item.get("status") == "done" row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=9) row.add_css_class("todo-row") check = Gtk.CheckButton() check.set_active(is_done) check.set_sensitive(False) row.append(check) label = Gtk.Label(label=text(item.get("text"), "Todo")) label.set_xalign(0) label.set_hexpand(True) row.append(label) state_label = Gtk.Label(label="Erledigt" if is_done else "Offen") state_label.add_css_class("todo-done" if is_done else "muted") row.append(state_label) return row def _build_next_step(self) -> Gtk.Box: next_step = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) next_step.add_css_class("next-step") next_label = Gtk.Label(label="NÄCHSTER ERLAUBTER SCHRITT") next_label.set_xalign(0) next_label.add_css_class("next-label") next_step.append(next_label) self.next_copy.set_xalign(0) self.next_copy.set_wrap(True) self.next_copy.set_wrap_mode(Pango.WrapMode.WORD_CHAR) self.next_copy.add_css_class("next-copy") next_step.append(self.next_copy) return next_step def _field(self, label: str, widget: Gtk.Widget) -> Gtk.Box: box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) field_label = Gtk.Label(label=label) field_label.set_xalign(0) field_label.add_css_class("field-label") box.append(field_label) box.append(widget) return box def _text_field(self, label: str, view: Gtk.TextView, height: int) -> Gtk.Box: view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) view.set_monospace(False) view.set_editable(False) view.set_cursor_visible(False) frame = Gtk.ScrolledWindow() frame.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) frame.set_min_content_height(height) frame.set_child(view) return self._field(label, frame) def render_state(self) -> None: active_focus = self.state.get("active_focus", {}) current_work = self.state.get("current_work", {}) self.focus_entry.set_text(focus_name(self.state)) self.sidebar_focus_name.set_label(focus_name(self.state)) self.active_time_label.set_label(text(active_focus.get("active_for"))) self.focus_subtitle.set_label(text(active_focus.get("subtitle"))) self.focus_goal_copy.set_label(text(active_focus.get("goal"))) self.file_label.set_label(text(current_work.get("file_path"))) self.work_status_label.set_label(text(current_work.get("status"))) self.code_label.set_label("\n".join(str(line) for line in current_work.get("code_lines", []))) self.saved_label.set_label(text(current_work.get("saved"))) self.language_label.set_label(text(current_work.get("language"))) set_text_view(self.notes_view, "\n".join(str(note) for note in current_work.get("notes", []))) set_text_view(self.threads_view, self._threads_text()) self._render_todos() self._render_cards() self.next_copy.set_label(text(self.state.get("next_allowed_step"))) def _threads_text(self) -> str: rows = [] for thread in self.state.get("open_threads", []): rows.append( f"{text(thread.get('title'), 'Thread')} · " f"{text(thread.get('kind'), 'THREAD')} · " f"{text(thread.get('status'), 'open')}" ) return "\n".join(rows) def _render_todos(self) -> None: while child := self.todo_list.get_first_child(): self.todo_list.remove(child) for item in self.state.get("todos", []): self.todo_list.append(self._todo_row(item)) def _render_cards(self) -> None: while child := self.lead_slot.get_first_child(): self.lead_slot.remove(child) while child := self.quiet_grid.get_first_child(): self.quiet_grid.remove(child) sidecars = list(self.state.get("sidecars", [])) lead = next((sidecar for sidecar in sidecars if sidecar.get("is_lead") is True), sidecars[0]) lead_card = SidecarCard(lead, lead=True) lead_card.update([str(sentence) for sentence in lead.get("sentences", [])]) self.lead_slot.append(lead_card) quiet_sidecars = [sidecar for sidecar in sidecars if sidecar is not lead] for index, sidecar in enumerate(quiet_sidecars): card = SidecarCard(sidecar) card.update([str(sentence) for sentence in sidecar.get("sentences", [])]) self.quiet_grid.attach(card, index % 2, index // 2, 1, 1) def _handle_owner_action(self, event_type: str) -> None: payload: dict[str, Any] = {"active_focus": focus_name(self.state)} if event_type == "thread_parked": payload.update(self._park_thread()) elif event_type == "finish_action_set": payload.update(self._set_finish_action()) elif event_type == "todo_completed": payload.update(self._complete_todo()) elif event_type == "evidence_added": payload.update(self._add_evidence_note()) self.store.append_event({"type": event_type, "payload": payload}) self.store.save_state(self.state) self.render_state() def _park_thread(self) -> dict[str, Any]: for thread in self.state.get("open_threads", []): if thread.get("kind") != "ACTIVE" and thread.get("status") != "parked": thread["status"] = "parked" return {"thread_id": thread.get("id"), "result": "parked"} return {"result": "no_open_thread"} def _set_finish_action(self) -> dict[str, Any]: step = "Schließe die kleinste sichtbare Einheit ab: Unit-Test für evaluate_focus." self.state["next_allowed_step"] = step return {"next_allowed_step": step} def _complete_todo(self) -> dict[str, Any]: for todo in self.state.get("todos", []): if todo.get("status") != "done": todo["status"] = "done" return {"todo_id": todo.get("id"), "result": "done"} return {"result": "no_open_todo"} def _add_evidence_note(self) -> dict[str, Any]: note = "Beweis hinzugefügt: Owner-Aktion protokolliert." current_work = self.state.setdefault("current_work", {}) notes = current_work.setdefault("notes", []) if isinstance(notes, list): notes.append(note) return {"evidence": note} class CockpitApp(Adw.Application): def __init__(self) -> None: super().__init__(application_id=APP_ID, flags=Gio.ApplicationFlags.DEFAULT_FLAGS) self._css_provider = Gtk.CssProvider() self._css_provider.load_from_data(CSS.encode("utf-8")) self._css_installed = False def do_startup(self) -> None: Adw.Application.do_startup(self) Adw.StyleManager.get_default().set_color_scheme(Adw.ColorScheme.FORCE_DARK) def do_activate(self) -> None: window = self.props.active_window if window is None: window = CockpitWindow(self) if not self._css_installed: Gtk.StyleContext.add_provider_for_display( window.get_display(), self._css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, ) self._css_installed = True window.present() def main() -> int: app = CockpitApp() return app.run(sys.argv) if __name__ == "__main__": raise SystemExit(main())