Merge 9947f70ba8 into 7e3df43463
This commit is contained in:
commit
3dd350a289
|
|
@ -15,8 +15,9 @@ from typing import Optional
|
|||
from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt
|
||||
from qutebrowser.qt.network import QLocalSocket, QLocalServer, QAbstractSocket
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.utils import log, usertypes, error, standarddir, utils, debug, qtutils
|
||||
from qutebrowser.utils import log, usertypes, standarddir, utils, debug, qtutils
|
||||
from qutebrowser.misc.ipcclient import (Error, SocketError,
|
||||
send_to_running_instance, display_error)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
|
|
@ -69,38 +70,6 @@ def _get_socketname(basedir):
|
|||
return os.path.join(standarddir.runtime(), filename)
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
"""Base class for IPC exceptions."""
|
||||
|
||||
|
||||
class SocketError(Error):
|
||||
|
||||
"""Exception raised when there was an error with a QLocalSocket.
|
||||
|
||||
Args:
|
||||
code: The error code.
|
||||
message: The error message.
|
||||
action: The action which was taken when the error happened.
|
||||
"""
|
||||
|
||||
def __init__(self, action, socket):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
action: The action which was taken when the error happened.
|
||||
socket: The QLocalSocket which has the error set.
|
||||
"""
|
||||
super().__init__()
|
||||
self.action = action
|
||||
self.code: QLocalSocket.LocalSocketError = socket.error()
|
||||
self.message: str = socket.errorString()
|
||||
|
||||
def __str__(self):
|
||||
return "Error while {}: {} ({})".format(
|
||||
self.action, self.message, debug.qenum_key(QLocalSocket, self.code))
|
||||
|
||||
|
||||
class ListenError(Error):
|
||||
|
||||
"""Exception raised when there was a problem with listening to IPC.
|
||||
|
|
@ -462,65 +431,6 @@ class IPCServer(QObject):
|
|||
self._server = None
|
||||
|
||||
|
||||
def send_to_running_instance(socketname, command, target_arg, *, socket=None):
|
||||
"""Try to send a commandline to a running instance.
|
||||
|
||||
Blocks for CONNECT_TIMEOUT ms.
|
||||
|
||||
Args:
|
||||
socketname: The name which should be used for the socket.
|
||||
command: The command to send to the running instance.
|
||||
target_arg: --target command line argument
|
||||
socket: The socket to read data from, or None.
|
||||
|
||||
Return:
|
||||
True if connecting was successful, False if no connection was made.
|
||||
"""
|
||||
if socket is None:
|
||||
socket = QLocalSocket()
|
||||
|
||||
log.ipc.debug("Connecting to {}".format(socketname))
|
||||
socket.connectToServer(socketname)
|
||||
|
||||
connected = socket.waitForConnected(CONNECT_TIMEOUT)
|
||||
if connected:
|
||||
log.ipc.info("Opening in existing instance")
|
||||
json_data = {'args': command, 'target_arg': target_arg,
|
||||
'version': qutebrowser.__version__,
|
||||
'protocol_version': PROTOCOL_VERSION}
|
||||
try:
|
||||
cwd = os.getcwd()
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
json_data['cwd'] = cwd
|
||||
line = json.dumps(json_data) + '\n'
|
||||
data = line.encode('utf-8')
|
||||
log.ipc.debug("Writing: {!r}".format(data))
|
||||
socket.writeData(data)
|
||||
socket.waitForBytesWritten(WRITE_TIMEOUT)
|
||||
if socket.error() != QLocalSocket.LocalSocketError.UnknownSocketError:
|
||||
raise SocketError("writing to running instance", socket)
|
||||
socket.disconnectFromServer()
|
||||
if socket.state() != QLocalSocket.LocalSocketState.UnconnectedState:
|
||||
socket.waitForDisconnected(CONNECT_TIMEOUT)
|
||||
return True
|
||||
else:
|
||||
if socket.error() not in [QLocalSocket.LocalSocketError.ConnectionRefusedError,
|
||||
QLocalSocket.LocalSocketError.ServerNotFoundError]:
|
||||
raise SocketError("connecting to running instance", socket)
|
||||
log.ipc.debug("No existing instance present ({})".format(
|
||||
debug.qenum_key(QLocalSocket, socket.error())))
|
||||
return False
|
||||
|
||||
|
||||
def display_error(exc, args):
|
||||
"""Display a message box with an IPC error."""
|
||||
error.handle_fatal_exc(
|
||||
exc, "Error while connecting to running instance!",
|
||||
no_err_windows=args.no_err_windows)
|
||||
|
||||
|
||||
def send_or_listen(args):
|
||||
"""Send the args to a running instance or start a new IPCServer.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""Utilities for IPCClient with existing instances."""
|
||||
|
||||
import os
|
||||
import json
|
||||
import getpass
|
||||
import hashlib
|
||||
|
||||
from qutebrowser.qt.network import QLocalSocket
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.utils import log, error, standarddir, utils, debug
|
||||
|
||||
|
||||
CONNECT_TIMEOUT = 100 # timeout for connecting/disconnecting
|
||||
WRITE_TIMEOUT = 1000
|
||||
READ_TIMEOUT = 5000
|
||||
ATIME_INTERVAL = 5000 * 60 # 5 minutes
|
||||
PROTOCOL_VERSION = 1
|
||||
|
||||
|
||||
def _get_socketname_windows(basedir):
|
||||
"""Get a socketname to use for Windows."""
|
||||
try:
|
||||
username = getpass.getuser()
|
||||
except ImportError:
|
||||
# getpass.getuser() first tries a couple of environment variables. If
|
||||
# none of those are set (i.e., USERNAME is missing), it tries to import
|
||||
# the "pwd" module which is unavailable on Windows.
|
||||
raise Error("Could not find username. This should only happen if "
|
||||
"there is a bug in the application launching qutebrowser, "
|
||||
"preventing the USERNAME environment variable from being "
|
||||
"passed. If you know more about when this happens, please "
|
||||
"report this to mail@qutebrowser.org.")
|
||||
|
||||
parts = ['qutebrowser', username]
|
||||
if basedir is not None:
|
||||
md5 = hashlib.md5(basedir.encode('utf-8')).hexdigest()
|
||||
parts.append(md5)
|
||||
return '-'.join(parts)
|
||||
|
||||
|
||||
def _get_socketname(basedir):
|
||||
"""Get a socketname to use."""
|
||||
if utils.is_windows: # pragma: no cover
|
||||
return _get_socketname_windows(basedir)
|
||||
|
||||
parts_to_hash = [getpass.getuser()]
|
||||
if basedir is not None:
|
||||
parts_to_hash.append(basedir)
|
||||
|
||||
data_to_hash = '-'.join(parts_to_hash).encode('utf-8')
|
||||
md5 = hashlib.md5(data_to_hash).hexdigest()
|
||||
|
||||
prefix = 'i-' if utils.is_mac else 'ipc-'
|
||||
filename = '{}{}'.format(prefix, md5)
|
||||
return os.path.join(standarddir.runtime(), filename)
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
"""Base class for IPC exceptions."""
|
||||
|
||||
|
||||
class SocketError(Error):
|
||||
|
||||
"""Exception raised when there was an error with a QLocalSocket.
|
||||
|
||||
Args:
|
||||
code: The error code.
|
||||
message: The error message.
|
||||
action: The action which was taken when the error happened.
|
||||
"""
|
||||
|
||||
def __init__(self, action, socket):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
action: The action which was taken when the error happened.
|
||||
socket: The QLocalSocket which has the error set.
|
||||
"""
|
||||
super().__init__()
|
||||
self.action = action
|
||||
self.code: QLocalSocket.LocalSocketError = socket.error()
|
||||
self.message: str = socket.errorString()
|
||||
|
||||
def __str__(self):
|
||||
return "Error while {}: {} ({})".format(
|
||||
self.action, self.message, debug.qenum_key(QLocalSocket, self.code))
|
||||
|
||||
|
||||
def send_to_running_instance(socketname, command, target_arg, *, socket=None):
|
||||
"""Try to send a commandline to a running instance.
|
||||
|
||||
Blocks for CONNECT_TIMEOUT ms.
|
||||
|
||||
Args:
|
||||
socketname: The name which should be used for the socket.
|
||||
command: The command to send to the running instance.
|
||||
target_arg: --target command line argument
|
||||
socket: The socket to read data from, or None.
|
||||
|
||||
Return:
|
||||
True if connecting was successful, False if no connection was made.
|
||||
"""
|
||||
if socket is None:
|
||||
socket = QLocalSocket()
|
||||
|
||||
log.ipc.debug("Connecting to {}".format(socketname))
|
||||
socket.connectToServer(socketname)
|
||||
|
||||
connected = socket.waitForConnected(CONNECT_TIMEOUT)
|
||||
if connected:
|
||||
log.ipc.info("Opening in existing instance")
|
||||
json_data = {'args': command, 'target_arg': target_arg,
|
||||
'version': qutebrowser.__version__,
|
||||
'protocol_version': PROTOCOL_VERSION}
|
||||
try:
|
||||
cwd = os.getcwd()
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
json_data['cwd'] = cwd
|
||||
line = json.dumps(json_data) + '\n'
|
||||
data = line.encode('utf-8')
|
||||
log.ipc.debug("Writing: {!r}".format(data))
|
||||
socket.writeData(data)
|
||||
socket.waitForBytesWritten(WRITE_TIMEOUT)
|
||||
if socket.error() != QLocalSocket.LocalSocketError.UnknownSocketError:
|
||||
raise SocketError("writing to running instance", socket)
|
||||
socket.disconnectFromServer()
|
||||
if socket.state() != QLocalSocket.LocalSocketState.UnconnectedState:
|
||||
socket.waitForDisconnected(CONNECT_TIMEOUT)
|
||||
return True
|
||||
else:
|
||||
if socket.error() not in [QLocalSocket.LocalSocketError.ConnectionRefusedError,
|
||||
QLocalSocket.LocalSocketError.ServerNotFoundError]:
|
||||
raise SocketError("connecting to running instance", socket)
|
||||
log.ipc.debug("No existing instance present ({})".format(
|
||||
debug.qenum_key(QLocalSocket, socket.error())))
|
||||
return False
|
||||
|
||||
|
||||
def display_error(exc, args):
|
||||
"""Display a message box with an IPC error."""
|
||||
error.handle_fatal_exc(
|
||||
exc, "Error while connecting to running instance!",
|
||||
no_err_windows=args.no_err_windows)
|
||||
|
||||
|
||||
def send(args):
|
||||
"""send a message to IPC Server."""
|
||||
socketname = _get_socketname(args.basedir)
|
||||
try:
|
||||
sent = send_to_running_instance(socketname, args.command,
|
||||
args.target)
|
||||
if sent:
|
||||
return True
|
||||
return False
|
||||
|
||||
except Error as e:
|
||||
display_error(e, args)
|
||||
raise e
|
||||
|
|
@ -220,6 +220,20 @@ def _validate_untrusted_args(argv):
|
|||
sys.exit("Found {} after --untrusted-args, aborting.".format(arg))
|
||||
|
||||
|
||||
def start_new_instance(args):
|
||||
"""start a new instance."""
|
||||
import tempfile
|
||||
from qutebrowser.utils import standarddir
|
||||
from qutebrowser.misc import ipcclient
|
||||
|
||||
if args.temp_basedir:
|
||||
args.basedir = tempfile.mkdtemp(prefix='qutebrowser-basedir-')
|
||||
|
||||
# In order to get socket path for starting a new instance
|
||||
standarddir.init(args)
|
||||
return ipcclient.send(args)
|
||||
|
||||
|
||||
def main():
|
||||
_validate_untrusted_args(sys.argv)
|
||||
parser = get_argparser()
|
||||
|
|
@ -228,6 +242,8 @@ def main():
|
|||
if args.json_args is not None:
|
||||
args = _unpack_json_args(args)
|
||||
earlyinit.early_init(args)
|
||||
if start_new_instance(args):
|
||||
sys.exit()
|
||||
# We do this imports late as earlyinit needs to be run first (because of
|
||||
# version checking and other early initialization)
|
||||
from qutebrowser import app
|
||||
|
|
|
|||
|
|
@ -552,7 +552,7 @@ class TestSendToRunningInstance:
|
|||
timeout=5000) as raw_blocker:
|
||||
with testutils.change_cwd(tmp_path):
|
||||
if not has_cwd:
|
||||
m = mocker.patch('qutebrowser.misc.ipc.os')
|
||||
m = mocker.patch('qutebrowser.misc.ipcclient.os')
|
||||
m.getcwd.side_effect = OSError
|
||||
sent = ipc.send_to_running_instance(
|
||||
'qute-test', ['foo'], None)
|
||||
|
|
@ -649,7 +649,7 @@ class TestSendOrListen:
|
|||
|
||||
@pytest.fixture
|
||||
def qlocalsocket_mock(self, mocker):
|
||||
m = mocker.patch('qutebrowser.misc.ipc.QLocalSocket', autospec=True)
|
||||
m = mocker.patch('qutebrowser.misc.ipcclient.QLocalSocket', autospec=True)
|
||||
m().errorString.return_value = "Error string"
|
||||
m.LocalSocketError = QLocalSocket.LocalSocketError
|
||||
m.LocalSocketState = QLocalSocket.LocalSocketState
|
||||
|
|
@ -739,9 +739,10 @@ class TestSendOrListen:
|
|||
with pytest.raises(ipc.Error):
|
||||
ipc.send_or_listen(args)
|
||||
|
||||
module_name = "ipcclient" if has_error else "ipc"
|
||||
error_msgs = [
|
||||
'Handling fatal misc.ipc.{} with --no-err-windows!'.format(
|
||||
exc_name),
|
||||
'Handling fatal misc.{}.{} with --no-err-windows!'.format(
|
||||
module_name, exc_name),
|
||||
'',
|
||||
'title: Error while connecting to running instance!',
|
||||
'pre_text: ',
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class Error(Exception):
|
|||
(ValueError('exception'), 'ValueError', 'exception'),
|
||||
(ValueError, 'ValueError', 'none'),
|
||||
# "qutebrowser." stripped
|
||||
(ipc.Error, 'misc.ipc.Error', 'none'),
|
||||
(ipc.Error, 'misc.ipcclient.Error', 'none'),
|
||||
(Error, 'test_error.Error', 'none'),
|
||||
])
|
||||
def test_no_err_windows(caplog, exc, name, exc_text):
|
||||
|
|
|
|||
Loading…
Reference in New Issue