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

Initial RPC/hooks implementation #9

Merged
merged 20 commits into from
Dec 13, 2024
Merged

Initial RPC/hooks implementation #9

merged 20 commits into from
Dec 13, 2024

Conversation

lrustand
Copy link
Owner

@lrustand lrustand commented Dec 6, 2024

Work in progress! Current status is very experimental, expect breakage.

Current status:

  • Dual unix sockets for providing two bi-directional communication channels.
  • JSON based RPC communication
  • Run hooks in emacs when:
    • Entering mode
    • Leaving mode
    • New window
  • Evaluate python code in qutebrowser and get back the return value

@sarg
Copy link
Contributor

sarg commented Dec 7, 2024

I wonder why two sockets are necessary? One should be enough.
Here is a revised example:

emacs_ipc.py
from PyQt6.QtNetwork import QLocalSocket
from PyQt6.QtCore import QTextStream, QByteArray, pyqtSlot
from qutebrowser.api import message, cmdutils
from qutebrowser.keyinput import modeman
from qutebrowser.misc import objects
from qutebrowser.misc.ipc import IPCServer
from qutebrowser.utils import objreg

class EmacsIPCServer(IPCServer):
    def __init__(self, socketname):
        super().__init__(socketname)
        objreg.register(name = "emacs-ipc-server",
                        obj = self,
                        update = True)
        self.listen()

    @pyqtSlot()
    def on_timeout(self):
        # this needs to be adjusted
        print("Ignoring timeout")
        return

    def send_cmd(self, cmd):
        socket = self._get_socket()
        if socket and socket.isOpen():
            socket.write(QByteArray(cmd.encode("utf-8")))
            socket.flush()

def init():
    server = EmacsIPCServer("/tmp/emacs-ipc")

init()

@cmdutils.register()
def test(text: str) -> None:
    """Test launcher"""
    server = objreg.get("emacs-ipc-server")
    server.send_cmd(text)
Emacs part
(make-network-process
 :name "qutebrowser-ipc"
 :buffer (generate-new-buffer "qute-ipc")
 :family 'local
 :filter (lambda (proc string) (with-local-quit (eval (read string))))
 :service "/tmp/emacs-ipc"
 :nowait nil
 :sentinel (lambda (proc event)
             (when (string= event "connection broken by remote peer\n")
               (delete-process proc))))

Start qutebrowser, :confg-source emacs_ipc.py, eval the lisp code to connect, run :test (qutebrowser-launcher).
I've noticed that the ipc server doesn't work without explicit config-source, that seems to be because when qutebrowser starts it loads the config before enabling the QT event loop.

@sarg
Copy link
Contributor

sarg commented Dec 7, 2024

btw, it doesn't seem to fix the "Buffer is read-only issue" 😢

@lrustand
Copy link
Owner Author

lrustand commented Dec 7, 2024

btw, it doesn't seem to fix the "Buffer is read-only issue" 😢

Using your :test (qutebrowser-launcher) seems to have improved it somewhat for me. One scenario where the issue has now disappeared for me is when I open the launcher and close it again without selecting a candidate. Previously this triggered the same issue for me, but with your :test command qutebrowser is focused correctly after closing the launcher.

But this seems to indicate that there is something else causing this than the current theory. I'm leaning towards some weird interaction with the buffer focus when focusing and unfocusing the minibuffer. What it seems like to me is that the underlying emacs buffer of the EXWM window is given focus instead of the X11 window. But it is very weird that it doesn't happen for other minibuffer commands, and that it doesn't happen when manually opening the launcher through M-x.

I wonder why two sockets are necessary? One should be enough.
Here is a revised example:

Apparently that is indeed enough. I was thinking that there needed to be a server process in emacs for the communication inititated by qutebrowser, but seems like it doesn't matter which end is the server and which is the client.

@lrustand
Copy link
Owner Author

lrustand commented Dec 7, 2024

I tried various different commands to see what causes the issue and what doesn't.

Works:

  • M-x qutebrowser-launcher
  • :test '(qutebrowser-open-url "https://google.com")'
  • Aborting :test (qutebrowser-launcher) without selecting
  • Aborting :test (consult-buffer) without selecting

Fails:

  • :test (qutebrowser-launcher")
  • :test (qutebrowser-select-url)
  • :test '(consult--read (list "1" "2" "3"))'
  • :test '(completing-read "Select:" (list "1" "2" "3"))'
  • :test (consult-buffer) select a qutebrowser buffer

Seems the common denominator is accepting an entry through completing-read.

EDIT: Interestingly, read-string shows the opposite behaviour, i.e. accepting a string results in focus being sent to qutebrowser, but aborting read-string leads to unfocused qutebrowser.

@lrustand
Copy link
Owner Author

lrustand commented Dec 7, 2024

Reproducing now via alacritty as well! This seems to be a bug in EXWM, that happens whenever an X11 window has focus, and the minibuffer is focused without being called from an emacs keybinding.

Simply open an alacritty terminal (or I would assume any X11 terminal) and run emacsclient -e '(completing-read "Prompt: " (list "1" "2" "3"))

EDIT: Just verified it in xterm as well.

@lrustand
Copy link
Owner Author

Merging this as is, to enable working on other features depending on this. These changes should not affect any existing features.

@lrustand lrustand merged commit ed51eb1 into master Dec 13, 2024
@lrustand lrustand deleted the ipc branch December 13, 2024 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants