Session.kt 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. /*
  2. * Copyright (c) Milan Jurkulák 2026.
  3. * Contact:
  4. * e: mimoccc@gmail.com
  5. * e: mj@mjdev.org
  6. * w: https://mjdev.org
  7. */
  8. @file:OptIn(ExperimentalForeignApi::class)
  9. package eu.mjdev.compositor
  10. import kotlinx.cinterop.ExperimentalForeignApi
  11. import kotlinx.cinterop.toKString
  12. import mjdev.compositor.shim.mjc_socket_name
  13. import mjdev.compositor.shim.mjc_spawn
  14. import mjdev.compositor.shim.mjc_xwayland_display
  15. import platform.posix.setenv
  16. /**
  17. * Session bootstrap: exports environment to dbus, starts the
  18. * xdg desktop portals (full session only), the desktop shell and
  19. * optional extra startup commands. Ends the compositor when the
  20. * shell exits cleanly or keeps restarting it after crashes.
  21. */
  22. class Session(private val c: Compositor) {
  23. private var shellPid = -1
  24. private var crashRestarts = 0
  25. fun onReady() {
  26. val wayland = mjc_socket_name(c.server)?.toKString()
  27. val x11 = mjc_xwayland_display(c.server)?.toKString()
  28. println("mjdevc: WAYLAND_DISPLAY=$wayland DISPLAY=${x11 ?: "-"}")
  29. setenv("XDG_CURRENT_DESKTOP", "mjdev", 1)
  30. setenv("XDG_SESSION_TYPE", "wayland", 1)
  31. mjc_spawn(
  32. c.server,
  33. "dbus-update-activation-environment --systemd " +
  34. "WAYLAND_DISPLAY DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE " +
  35. "2>/dev/null || true"
  36. )
  37. // GTK apps inherit gsettings (not GTK_THEME env) so palette changes reload css live.
  38. mjc_spawn(
  39. c.server,
  40. "gsettings set org.gnome.desktop.interface gtk-theme Mjdev 2>/dev/null || true"
  41. )
  42. if (c.config.sessionMode) {
  43. // portals deliver live gtk-theme/color-scheme changes to gtk apps;
  44. // never started in nested mode where the host session owns them
  45. mjc_spawn(c.server, "/usr/libexec/xdg-desktop-portal-gtk")
  46. mjc_spawn(c.server, "sleep 1; exec /usr/libexec/xdg-desktop-portal")
  47. }
  48. c.config.startupCmds.forEach { mjc_spawn(c.server, it) }
  49. startShell()
  50. }
  51. private fun startShell() {
  52. val cmd = c.config.shellCmd ?: return
  53. shellPid = mjc_spawn(c.server, cmd)
  54. println("mjdevc: shell started pid=$shellPid: $cmd")
  55. }
  56. fun onChildExit(pid: Int, status: Int) {
  57. if (pid != shellPid || shellPid < 0) {
  58. return
  59. }
  60. val crashed = status != 0
  61. if (crashed && crashRestarts < MAX_SHELL_RESTARTS) {
  62. crashRestarts++
  63. println("mjdevc: shell crashed (status=$status), restart $crashRestarts")
  64. startShell()
  65. } else {
  66. println("mjdevc: shell exited (status=$status), terminating")
  67. c.terminate()
  68. }
  69. }
  70. companion object {
  71. private const val MAX_SHELL_RESTARTS = 3
  72. }
  73. }