Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changed send_json and its dependent functions to be non-blocking. #15

Merged
merged 17 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions wayfire/extra/ipc_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,6 @@ def center_cursor_on_view(self, view_id: int) -> None:
# Move the cursor to the calculated position
self._stipc.move_cursor(cursor_x, cursor_y)

def is_socket_active(self, socket):
"""
Check if the given socket is still active.

Args:
socket: The socket object to check.

Example:
socket = WayfireSocket()
is_socket_active(socket)

Returns:
bool: True if the socket is active, False otherwise.
"""
try:
socket.client.getpeername()
return True
except OSError:
return False

def move_view_to_empty_workspace(self, view_id: int) -> None:
"""
Moves a top-level view to an empty workspace.
Expand Down
95 changes: 65 additions & 30 deletions wayfire/ipc.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import socket
import json as js
import select
import time
import os
from typing import Any, List, Optional
from wayfire.core.template import get_msg_template, geometry_to_json


class WayfireSocket:
def __init__(self, socket_name: str | None=None, allow_manual_search=False):
if socket_name is None:
socket_name = os.getenv("WAYFIRE_SOCKET")

self.socket_name = None
self.pending_events = []
self.timeout = 3

if socket_name is None and allow_manual_search:
# the last item is the most recent socket file
Expand Down Expand Up @@ -42,24 +44,29 @@ def connect_client(self, socket_name):
self.client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.client.connect(socket_name)

def close(self):
self.client.close()
def is_connected(self):
if self.client is None:
return False

def read_exact(self, n: int):
response = bytes()
while n > 0:
read_this_time = self.client.recv(n)
if not read_this_time:
raise Exception("Failed to read anything from the socket!")
n -= len(read_this_time)
response += read_this_time
try:
if self.client.fileno() < 0:
return False
return True
except (socket.error, ValueError):
return False

return response
def close(self):
self.client.close()

def read_message(self):
rlen = int.from_bytes(self.read_exact(4), byteorder="little")
response_message = self.read_exact(rlen)
response = js.loads(response_message)
if not response_message:
raise Exception("Received empty response message")
try:
response = js.loads(response_message.decode("utf-8"))
except js.JSONDecodeError as e:
raise Exception(f"JSON decoding error: {e}")

if "error" in response and response["error"] == "No such method found!":
raise Exception(f"Method {response['method']} is not available. \
Expand All @@ -69,6 +76,51 @@ def read_message(self):
raise Exception(response["error"])
return response

def send_json(self, msg):
if 'method' not in msg:
raise Exception("Malformed JSON request: missing method!")

data = js.dumps(msg).encode("utf-8")
header = len(data).to_bytes(4, byteorder="little")

if self.is_connected():
self.client.send(header)
self.client.send(data)
else:
raise Exception("Unable to send data: The Wayfire socket instance is not connected.")

end_time = time.time() + self.timeout
while True:
remaining_time = end_time - time.time()
if remaining_time <= 0:
raise Exception("Response timeout")

readable, _, _ = select.select([self.client], [], [], remaining_time)
if readable:
try:
response = self.read_message()
except Exception as e:
raise Exception(f"Error reading message: {e}")

if 'event' in response:
self.pending_events.append(response)
continue

return response
else:
raise Exception("Response timeout")

def read_exact(self, n: int):
response = bytearray()
while n > 0:
read_this_time = self.client.recv(n)
if not read_this_time:
raise Exception("Failed to read anything from the socket!")
n -= len(read_this_time)
response += read_this_time

return bytes(response)

def read_next_event(self):
if self.pending_events:
return self.pending_events.pop(0)
Expand Down Expand Up @@ -277,23 +329,6 @@ def _wayfire_plugin_from_method(method: str) -> str:

return method.split("/")[0]

def send_json(self, msg):
if 'method' not in msg:
raise Exception("Malformed json request: missing method!")

data = js.dumps(msg).encode("utf8")
header = len(data).to_bytes(4, byteorder="little")
self.client.send(header)
self.client.send(data)

while True:
response = self.read_message()
if 'event' in response:
self.pending_events.append(response)
continue

return response

def get_output(self, output_id: int):
"""
Retrieves information about a specific output.
Expand Down