fix: IPC byte order, globales GTK-Theme, Session-Vorauswahl
- ipc.py: !I (Big-Endian) → =I (Native Byte Order) für greetd-Protokoll - Per-User GTK-Theme entfernt, stattdessen globales Theme aus moongreet.toml - Last-Session pro User in /var/cache/moongreet/last-session/ speichern/laden - PKGBUILD und install-Hook für last-session-Cache erweitert
This commit is contained in:
parent
ba4f30f254
commit
357d2459cf
@ -4,3 +4,5 @@
|
|||||||
[appearance]
|
[appearance]
|
||||||
# Absolute path to wallpaper image
|
# Absolute path to wallpaper image
|
||||||
background = "/usr/share/backgrounds/wallpaper.jpg"
|
background = "/usr/share/backgrounds/wallpaper.jpg"
|
||||||
|
# GTK theme for the greeter UI
|
||||||
|
gtk-theme = "Catppuccin-Mocha-Standard-Blue-Dark"
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
# Maintainer: Dominik Kressler
|
# Maintainer: Dominik Kressler
|
||||||
|
|
||||||
pkgname=moongreet-git
|
pkgname=moongreet-git
|
||||||
pkgver=0.1.0
|
pkgver=0.1.0.r6.gba4f30f
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="A greetd greeter for Wayland, built with Python + GTK4 + gtk4-layer-shell"
|
pkgdesc="A greetd greeter for Wayland, built with Python + GTK4 + gtk4-layer-shell"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
@ -46,6 +46,7 @@ package() {
|
|||||||
# Greeter config
|
# Greeter config
|
||||||
install -Dm644 config/moongreet.toml "$pkgdir/etc/moongreet/moongreet.toml"
|
install -Dm644 config/moongreet.toml "$pkgdir/etc/moongreet/moongreet.toml"
|
||||||
|
|
||||||
# Cache directory
|
# Cache directories
|
||||||
install -dm755 "$pkgdir/var/cache/moongreet"
|
install -dm755 "$pkgdir/var/cache/moongreet"
|
||||||
|
install -dm755 "$pkgdir/var/cache/moongreet/last-session"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
post_install() {
|
post_install() {
|
||||||
if getent passwd greeter > /dev/null 2>&1; then
|
if getent passwd greeter > /dev/null 2>&1; then
|
||||||
chown greeter:greeter /var/cache/moongreet
|
chown greeter:greeter /var/cache/moongreet
|
||||||
|
chown greeter:greeter /var/cache/moongreet/last-session
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> Moongreet installed."
|
echo "==> Moongreet installed."
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
# ABOUTME: Configuration loading from moongreet.toml.
|
# ABOUTME: Configuration loading from moongreet.toml.
|
||||||
# ABOUTME: Parses appearance and behavior settings with wallpaper path resolution.
|
# ABOUTME: Parses appearance and behavior settings with wallpaper path resolution.
|
||||||
|
|
||||||
|
import re
|
||||||
import tomllib
|
import tomllib
|
||||||
from contextlib import AbstractContextManager
|
from contextlib import AbstractContextManager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from importlib.resources import as_file, files
|
from importlib.resources import as_file, files
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
VALID_THEME_NAME = re.compile(r"^[A-Za-z0-9_-]+$")
|
||||||
|
|
||||||
DEFAULT_CONFIG_PATHS = [
|
DEFAULT_CONFIG_PATHS = [
|
||||||
Path("/etc/moongreet/moongreet.toml"),
|
Path("/etc/moongreet/moongreet.toml"),
|
||||||
]
|
]
|
||||||
@ -17,6 +20,7 @@ class Config:
|
|||||||
"""Greeter configuration loaded from moongreet.toml."""
|
"""Greeter configuration loaded from moongreet.toml."""
|
||||||
|
|
||||||
background: Path | None = None
|
background: Path | None = None
|
||||||
|
gtk_theme: str | None = None
|
||||||
|
|
||||||
|
|
||||||
def load_config(config_path: Path | None = None) -> Config:
|
def load_config(config_path: Path | None = None) -> Config:
|
||||||
@ -51,6 +55,10 @@ def load_config(config_path: Path | None = None) -> Config:
|
|||||||
bg_path = config_path.parent / bg_path
|
bg_path = config_path.parent / bg_path
|
||||||
config.background = bg_path
|
config.background = bg_path
|
||||||
|
|
||||||
|
gtk_theme = appearance.get("gtk-theme")
|
||||||
|
if gtk_theme and VALID_THEME_NAME.match(gtk_theme):
|
||||||
|
config.gtk_theme = gtk_theme
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -21,13 +21,14 @@ from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
|
|||||||
from moongreet.config import load_config, resolve_wallpaper_path
|
from moongreet.config import load_config, resolve_wallpaper_path
|
||||||
from moongreet.i18n import load_strings, Strings
|
from moongreet.i18n import load_strings, Strings
|
||||||
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
|
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
|
||||||
from moongreet.users import User, get_users, get_avatar_path, get_user_gtk_theme
|
from moongreet.users import User, get_users, get_avatar_path
|
||||||
from moongreet.sessions import Session, get_sessions
|
from moongreet.sessions import Session, get_sessions
|
||||||
from moongreet.power import reboot, shutdown
|
from moongreet.power import reboot, shutdown
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
LAST_USER_PATH = Path("/var/cache/moongreet/last-user")
|
LAST_USER_PATH = Path("/var/cache/moongreet/last-user")
|
||||||
|
LAST_SESSION_DIR = Path("/var/cache/moongreet/last-session")
|
||||||
FAILLOCK_MAX_ATTEMPTS = 3
|
FAILLOCK_MAX_ATTEMPTS = 3
|
||||||
VALID_USERNAME = re.compile(r"^[a-zA-Z0-9_.-]+$")
|
VALID_USERNAME = re.compile(r"^[a-zA-Z0-9_.-]+$")
|
||||||
MAX_USERNAME_LENGTH = 256
|
MAX_USERNAME_LENGTH = 256
|
||||||
@ -93,6 +94,7 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
self._failed_attempts: dict[str, int] = {}
|
self._failed_attempts: dict[str, int] = {}
|
||||||
self._bg_path = bg_path
|
self._bg_path = bg_path
|
||||||
|
|
||||||
|
self._apply_global_theme()
|
||||||
self._build_ui()
|
self._build_ui()
|
||||||
self._setup_keyboard_navigation()
|
self._setup_keyboard_navigation()
|
||||||
# Defer initial user selection until the window is realized,
|
# Defer initial user selection until the window is realized,
|
||||||
@ -286,26 +288,23 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
# which works in ZIP wheels too, no exists() check needed
|
# which works in ZIP wheels too, no exists() check needed
|
||||||
self._set_default_avatar()
|
self._set_default_avatar()
|
||||||
|
|
||||||
# Apply user's GTK theme if available
|
# Pre-select last used session for this user
|
||||||
self._apply_user_theme(user)
|
self._select_last_session(user)
|
||||||
|
|
||||||
# Focus password entry
|
# Focus password entry
|
||||||
self._password_entry.grab_focus()
|
self._password_entry.grab_focus()
|
||||||
|
|
||||||
def _apply_user_theme(self, user: User) -> None:
|
def _apply_global_theme(self) -> None:
|
||||||
"""Load the user's preferred GTK theme from their settings.ini."""
|
"""Apply the GTK theme from moongreet.toml configuration."""
|
||||||
gtk_config_dir = user.home / ".config" / "gtk-4.0"
|
theme_name = self._config.gtk_theme
|
||||||
theme_name = get_user_gtk_theme(config_dir=gtk_config_dir)
|
if not theme_name:
|
||||||
|
return
|
||||||
|
|
||||||
settings = Gtk.Settings.get_default()
|
settings = Gtk.Settings.get_default()
|
||||||
if settings is None:
|
if settings is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
current = settings.get_property("gtk-theme-name")
|
|
||||||
if theme_name and current != theme_name:
|
|
||||||
settings.set_property("gtk-theme-name", theme_name)
|
settings.set_property("gtk-theme-name", theme_name)
|
||||||
elif not theme_name and current:
|
|
||||||
settings.reset_property("gtk-theme-name")
|
|
||||||
|
|
||||||
def _get_foreground_color(self) -> str:
|
def _get_foreground_color(self) -> str:
|
||||||
"""Get the current GTK theme foreground color as a hex string."""
|
"""Get the current GTK theme foreground color as a hex string."""
|
||||||
@ -479,6 +478,7 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
|
|
||||||
if response.get("type") == "success":
|
if response.get("type") == "success":
|
||||||
self._save_last_user(user.username)
|
self._save_last_user(user.username)
|
||||||
|
self._save_last_session(user.username, session.name)
|
||||||
self._close_greetd_sock()
|
self._close_greetd_sock()
|
||||||
GLib.idle_add(self.get_application().quit)
|
GLib.idle_add(self.get_application().quit)
|
||||||
return
|
return
|
||||||
@ -533,6 +533,18 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
return self._sessions[idx]
|
return self._sessions[idx]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _select_last_session(self, user: User) -> None:
|
||||||
|
"""Pre-select the last used session for a user in the dropdown."""
|
||||||
|
if not self._sessions:
|
||||||
|
return
|
||||||
|
last_session_name = self._load_last_session(user.username)
|
||||||
|
if not last_session_name:
|
||||||
|
return
|
||||||
|
for i, session in enumerate(self._sessions):
|
||||||
|
if session.name == last_session_name:
|
||||||
|
self._session_dropdown.set_selected(i)
|
||||||
|
return
|
||||||
|
|
||||||
MAX_GREETD_ERROR_LENGTH = 200
|
MAX_GREETD_ERROR_LENGTH = 200
|
||||||
|
|
||||||
def _show_greetd_error(self, response: dict, fallback: str) -> None:
|
def _show_greetd_error(self, response: dict, fallback: str) -> None:
|
||||||
@ -585,3 +597,30 @@ class GreeterWindow(Gtk.ApplicationWindow):
|
|||||||
LAST_USER_PATH.write_text(username)
|
LAST_USER_PATH.write_text(username)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass # Non-critical — cache dir may not be writable
|
pass # Non-critical — cache dir may not be writable
|
||||||
|
|
||||||
|
MAX_SESSION_NAME_LENGTH = 256
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _save_last_session(username: str, session_name: str) -> None:
|
||||||
|
"""Save the last used session name for a user to cache."""
|
||||||
|
if not VALID_USERNAME.match(username) or len(username) > MAX_USERNAME_LENGTH:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
LAST_SESSION_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
(LAST_SESSION_DIR / username).write_text(session_name)
|
||||||
|
except OSError:
|
||||||
|
pass # Non-critical — cache dir may not be writable
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_last_session(username: str) -> str | None:
|
||||||
|
"""Load the last used session name for a user from cache."""
|
||||||
|
session_file = LAST_SESSION_DIR / username
|
||||||
|
if not session_file.exists():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
name = session_file.read_text().strip()
|
||||||
|
except OSError:
|
||||||
|
return None
|
||||||
|
if not name or len(name) > GreeterWindow.MAX_SESSION_NAME_LENGTH:
|
||||||
|
return None
|
||||||
|
return name
|
||||||
|
|||||||
@ -22,14 +22,14 @@ def _recvall(sock: Any, n: int) -> bytes:
|
|||||||
def send_message(sock: Any, msg: dict) -> None:
|
def send_message(sock: Any, msg: dict) -> None:
|
||||||
"""Send a length-prefixed JSON message to the greetd socket."""
|
"""Send a length-prefixed JSON message to the greetd socket."""
|
||||||
payload = json.dumps(msg).encode("utf-8")
|
payload = json.dumps(msg).encode("utf-8")
|
||||||
header = struct.pack("!I", len(payload))
|
header = struct.pack("=I", len(payload))
|
||||||
sock.sendall(header + payload)
|
sock.sendall(header + payload)
|
||||||
|
|
||||||
|
|
||||||
def recv_message(sock: Any) -> dict:
|
def recv_message(sock: Any) -> dict:
|
||||||
"""Receive a length-prefixed JSON message from the greetd socket."""
|
"""Receive a length-prefixed JSON message from the greetd socket."""
|
||||||
header = _recvall(sock, 4)
|
header = _recvall(sock, 4)
|
||||||
length = struct.unpack("!I", header)[0]
|
length = struct.unpack("=I", header)[0]
|
||||||
|
|
||||||
if length > MAX_PAYLOAD_SIZE:
|
if length > MAX_PAYLOAD_SIZE:
|
||||||
raise ConnectionError(f"Payload too large: {length} bytes (max {MAX_PAYLOAD_SIZE})")
|
raise ConnectionError(f"Payload too large: {length} bytes (max {MAX_PAYLOAD_SIZE})")
|
||||||
|
|||||||
@ -43,6 +43,36 @@ class TestLoadConfig:
|
|||||||
|
|
||||||
assert config.background is None
|
assert config.background is None
|
||||||
|
|
||||||
|
def test_loads_gtk_theme(self, tmp_path: Path) -> None:
|
||||||
|
toml_file = tmp_path / "moongreet.toml"
|
||||||
|
toml_file.write_text(
|
||||||
|
"[appearance]\n"
|
||||||
|
'gtk-theme = "Catppuccin-Mocha-Standard-Blue-Dark"\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
config = load_config(toml_file)
|
||||||
|
|
||||||
|
assert config.gtk_theme == "Catppuccin-Mocha-Standard-Blue-Dark"
|
||||||
|
|
||||||
|
def test_returns_none_gtk_theme_when_missing(self, tmp_path: Path) -> None:
|
||||||
|
toml_file = tmp_path / "moongreet.toml"
|
||||||
|
toml_file.write_text("[appearance]\n")
|
||||||
|
|
||||||
|
config = load_config(toml_file)
|
||||||
|
|
||||||
|
assert config.gtk_theme is None
|
||||||
|
|
||||||
|
def test_rejects_gtk_theme_with_path_traversal(self, tmp_path: Path) -> None:
|
||||||
|
toml_file = tmp_path / "moongreet.toml"
|
||||||
|
toml_file.write_text(
|
||||||
|
"[appearance]\n"
|
||||||
|
'gtk-theme = "../../etc/evil"\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
config = load_config(toml_file)
|
||||||
|
|
||||||
|
assert config.gtk_theme is None
|
||||||
|
|
||||||
def test_resolves_relative_path_against_config_dir(self, tmp_path: Path) -> None:
|
def test_resolves_relative_path_against_config_dir(self, tmp_path: Path) -> None:
|
||||||
toml_file = tmp_path / "moongreet.toml"
|
toml_file = tmp_path / "moongreet.toml"
|
||||||
toml_file.write_text(
|
toml_file.write_text(
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from moongreet.greeter import faillock_warning, FAILLOCK_MAX_ATTEMPTS
|
from moongreet.greeter import faillock_warning, FAILLOCK_MAX_ATTEMPTS, LAST_SESSION_DIR
|
||||||
from moongreet.i18n import load_strings
|
from moongreet.i18n import load_strings
|
||||||
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
|
from moongreet.ipc import create_session, post_auth_response, start_session, cancel_session
|
||||||
|
|
||||||
@ -59,14 +59,14 @@ class MockGreetd:
|
|||||||
header = self._recvall(conn, 4)
|
header = self._recvall(conn, 4)
|
||||||
if len(header) < 4:
|
if len(header) < 4:
|
||||||
break
|
break
|
||||||
length = struct.unpack("!I", header)[0]
|
length = struct.unpack("=I", header)[0]
|
||||||
payload = self._recvall(conn, length)
|
payload = self._recvall(conn, length)
|
||||||
msg = json.loads(payload.decode("utf-8"))
|
msg = json.loads(payload.decode("utf-8"))
|
||||||
self._received.append(msg)
|
self._received.append(msg)
|
||||||
|
|
||||||
# Send response
|
# Send response
|
||||||
resp_payload = json.dumps(response).encode("utf-8")
|
resp_payload = json.dumps(response).encode("utf-8")
|
||||||
conn.sendall(struct.pack("!I", len(resp_payload)) + resp_payload)
|
conn.sendall(struct.pack("=I", len(resp_payload)) + resp_payload)
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
@ -264,3 +264,45 @@ class TestLastUser:
|
|||||||
from moongreet.greeter import GreeterWindow
|
from moongreet.greeter import GreeterWindow
|
||||||
result = GreeterWindow._load_last_user()
|
result = GreeterWindow._load_last_user()
|
||||||
assert result is None
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestLastSession:
|
||||||
|
"""Tests for saving and loading the last session per user."""
|
||||||
|
|
||||||
|
def test_save_and_load_last_session(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setattr("moongreet.greeter.LAST_SESSION_DIR", tmp_path)
|
||||||
|
|
||||||
|
from moongreet.greeter import GreeterWindow
|
||||||
|
GreeterWindow._save_last_session("dominik", "Niri")
|
||||||
|
|
||||||
|
session_file = tmp_path / "dominik"
|
||||||
|
assert session_file.exists()
|
||||||
|
assert session_file.read_text() == "Niri"
|
||||||
|
|
||||||
|
result = GreeterWindow._load_last_session("dominik")
|
||||||
|
assert result == "Niri"
|
||||||
|
|
||||||
|
def test_load_last_session_missing_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setattr("moongreet.greeter.LAST_SESSION_DIR", tmp_path)
|
||||||
|
|
||||||
|
from moongreet.greeter import GreeterWindow
|
||||||
|
result = GreeterWindow._load_last_session("nobody")
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_load_last_session_rejects_oversized_name(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setattr("moongreet.greeter.LAST_SESSION_DIR", tmp_path)
|
||||||
|
(tmp_path / "dominik").write_text("A" * 300)
|
||||||
|
|
||||||
|
from moongreet.greeter import GreeterWindow
|
||||||
|
result = GreeterWindow._load_last_session("dominik")
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_save_last_session_validates_username(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""Usernames with path traversal should not create files outside the cache dir."""
|
||||||
|
monkeypatch.setattr("moongreet.greeter.LAST_SESSION_DIR", tmp_path)
|
||||||
|
|
||||||
|
from moongreet.greeter import GreeterWindow
|
||||||
|
GreeterWindow._save_last_session("../../etc/evil", "Niri")
|
||||||
|
|
||||||
|
# Should not have created any file
|
||||||
|
assert not (tmp_path / "../../etc/evil").exists()
|
||||||
|
|||||||
@ -38,7 +38,7 @@ class FakeSocket:
|
|||||||
def with_response(cls, response: dict) -> "FakeSocket":
|
def with_response(cls, response: dict) -> "FakeSocket":
|
||||||
"""Create a FakeSocket pre-loaded with a length-prefixed JSON response."""
|
"""Create a FakeSocket pre-loaded with a length-prefixed JSON response."""
|
||||||
payload = json.dumps(response).encode("utf-8")
|
payload = json.dumps(response).encode("utf-8")
|
||||||
data = struct.pack("!I", len(payload)) + payload
|
data = struct.pack("=I", len(payload)) + payload
|
||||||
return cls(recv_data=data)
|
return cls(recv_data=data)
|
||||||
|
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ class TestSendMessage:
|
|||||||
send_message(sock, msg)
|
send_message(sock, msg)
|
||||||
|
|
||||||
payload = json.dumps(msg).encode("utf-8")
|
payload = json.dumps(msg).encode("utf-8")
|
||||||
expected = struct.pack("!I", len(payload)) + payload
|
expected = struct.pack("=I", len(payload)) + payload
|
||||||
assert bytes(sock.sent) == expected
|
assert bytes(sock.sent) == expected
|
||||||
|
|
||||||
def test_sends_empty_dict(self) -> None:
|
def test_sends_empty_dict(self) -> None:
|
||||||
@ -82,7 +82,7 @@ class TestSendMessage:
|
|||||||
send_message(sock, {})
|
send_message(sock, {})
|
||||||
|
|
||||||
payload = json.dumps({}).encode("utf-8")
|
payload = json.dumps({}).encode("utf-8")
|
||||||
expected = struct.pack("!I", len(payload)) + payload
|
expected = struct.pack("=I", len(payload)) + payload
|
||||||
assert bytes(sock.sent) == expected
|
assert bytes(sock.sent) == expected
|
||||||
|
|
||||||
def test_sends_nested_message(self) -> None:
|
def test_sends_nested_message(self) -> None:
|
||||||
@ -93,7 +93,7 @@ class TestSendMessage:
|
|||||||
|
|
||||||
# Verify the payload is correctly length-prefixed
|
# Verify the payload is correctly length-prefixed
|
||||||
length_bytes = bytes(sock.sent[:4])
|
length_bytes = bytes(sock.sent[:4])
|
||||||
length = struct.unpack("!I", length_bytes)[0]
|
length = struct.unpack("=I", length_bytes)[0]
|
||||||
decoded = json.loads(sock.sent[4:])
|
decoded = json.loads(sock.sent[4:])
|
||||||
assert length == len(json.dumps(msg).encode("utf-8"))
|
assert length == len(json.dumps(msg).encode("utf-8"))
|
||||||
assert decoded == msg
|
assert decoded == msg
|
||||||
@ -132,7 +132,7 @@ class TestRecvMessage:
|
|||||||
"""recv() may return fewer bytes than requested — must loop."""
|
"""recv() may return fewer bytes than requested — must loop."""
|
||||||
response = {"type": "success"}
|
response = {"type": "success"}
|
||||||
payload = json.dumps(response).encode("utf-8")
|
payload = json.dumps(response).encode("utf-8")
|
||||||
data = struct.pack("!I", len(payload)) + payload
|
data = struct.pack("=I", len(payload)) + payload
|
||||||
sock = FragmentingSocket(data, chunk_size=3)
|
sock = FragmentingSocket(data, chunk_size=3)
|
||||||
|
|
||||||
result = recv_message(sock)
|
result = recv_message(sock)
|
||||||
@ -141,7 +141,7 @@ class TestRecvMessage:
|
|||||||
|
|
||||||
def test_rejects_oversized_payload(self) -> None:
|
def test_rejects_oversized_payload(self) -> None:
|
||||||
"""Payloads exceeding the size limit must be rejected."""
|
"""Payloads exceeding the size limit must be rejected."""
|
||||||
header = struct.pack("!I", 10_000_000)
|
header = struct.pack("=I", 10_000_000)
|
||||||
sock = FakeSocket(recv_data=header)
|
sock = FakeSocket(recv_data=header)
|
||||||
|
|
||||||
with pytest.raises(ConnectionError, match="too large"):
|
with pytest.raises(ConnectionError, match="too large"):
|
||||||
@ -162,7 +162,7 @@ class TestCreateSession:
|
|||||||
result = create_session(sock, "dominik")
|
result = create_session(sock, "dominik")
|
||||||
|
|
||||||
# Verify sent message
|
# Verify sent message
|
||||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||||
assert sent_msg == {"type": "create_session", "username": "dominik"}
|
assert sent_msg == {"type": "create_session", "username": "dominik"}
|
||||||
assert result == response
|
assert result == response
|
||||||
@ -177,7 +177,7 @@ class TestPostAuthResponse:
|
|||||||
|
|
||||||
result = post_auth_response(sock, "mypassword")
|
result = post_auth_response(sock, "mypassword")
|
||||||
|
|
||||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||||
assert sent_msg == {
|
assert sent_msg == {
|
||||||
"type": "post_auth_message_response",
|
"type": "post_auth_message_response",
|
||||||
@ -192,7 +192,7 @@ class TestPostAuthResponse:
|
|||||||
|
|
||||||
result = post_auth_response(sock, None)
|
result = post_auth_response(sock, None)
|
||||||
|
|
||||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||||
assert sent_msg == {
|
assert sent_msg == {
|
||||||
"type": "post_auth_message_response",
|
"type": "post_auth_message_response",
|
||||||
@ -209,7 +209,7 @@ class TestStartSession:
|
|||||||
|
|
||||||
result = start_session(sock, ["Hyprland"])
|
result = start_session(sock, ["Hyprland"])
|
||||||
|
|
||||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||||
assert sent_msg == {"type": "start_session", "cmd": ["Hyprland"]}
|
assert sent_msg == {"type": "start_session", "cmd": ["Hyprland"]}
|
||||||
assert result == response
|
assert result == response
|
||||||
@ -220,7 +220,7 @@ class TestStartSession:
|
|||||||
|
|
||||||
result = start_session(sock, ["sway", "--config", "/etc/sway/config"])
|
result = start_session(sock, ["sway", "--config", "/etc/sway/config"])
|
||||||
|
|
||||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||||
assert sent_msg == {
|
assert sent_msg == {
|
||||||
"type": "start_session",
|
"type": "start_session",
|
||||||
@ -237,7 +237,7 @@ class TestCancelSession:
|
|||||||
|
|
||||||
result = cancel_session(sock)
|
result = cancel_session(sock)
|
||||||
|
|
||||||
length = struct.unpack("!I", bytes(sock.sent[:4]))[0]
|
length = struct.unpack("=I", bytes(sock.sent[:4]))[0]
|
||||||
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
sent_msg = json.loads(sock.sent[4 : 4 + length])
|
||||||
assert sent_msg == {"type": "cancel_session"}
|
assert sent_msg == {"type": "cancel_session"}
|
||||||
assert result == response
|
assert result == response
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user