File: api.py

package info (click to toggle)
frescobaldi 3.3.0%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 24,212 kB
  • sloc: python: 39,014; javascript: 263; sh: 238; makefile: 80
file content (134 lines) | stat: -rw-r--r-- 4,489 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
#
# Copyright (c) 2012 - 2014 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
Inter-Process Communication with already running Frescobaldi instances.

This is done via a local (unix domain) socket, to which simple commands
are written. Every command is a line of ASCII characters, terminated by a
newline. Arguments are separated with spaces.
"""


from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import QLocalSocket

import app


_incoming_handlers = []


class Remote(object):
    """Speak to the remote Frescobaldi."""
    def __init__(self, socket):
        self.socket = socket

    def close(self):
        """Close and disconnect."""
        self.write(b'bye\n')
        self.socket.waitForBytesWritten()
        self.socket.disconnectFromServer()
        if self.socket.state() == QLocalSocket.ConnectedState:
            self.socket.waitForDisconnected(5000)

    def write(self, data):
        """Writes binary data."""
        while data:
            l = self.socket.write(data)
            data = data[l:]

    def command_line(self, args, urls):
        """Let remote Frescobaldi handle a command line."""
        if urls:
            if args.encoding:
                self.write('encoding {0}\n'.format(args.encoding).encode('utf-8'))
            for u in urls:
                self.write(b'open ' + u.toEncoded() + b'\n')
            self.write(b'set_current ' + u.toEncoded() + b'\n')
            if args.line is not None:
                self.write('set_cursor {0} {1}\n'.format(args.line, args.column).encode('utf-8'))
        self.write(b'activate_window\n')


class Incoming(object):
    """Handle an incoming connection."""
    def __init__(self, socket):
        """Start reading from the socket and perform the commands."""
        self.socket = socket
        self.data = bytearray()
        self.encoding = None
        _incoming_handlers.append(self)
        socket.readyRead.connect(self.read)

    def close(self):
        if self in _incoming_handlers:
            self.socket.deleteLater()
            _incoming_handlers.remove(self)

    def read(self):
        """Read from the socket and let command() handle the commands."""
        self.data.extend(self.socket.readAll())
        pos = self.data.find(b'\n')
        end = 0
        while pos != -1:
            self.command(self.data[end:pos])
            end = pos + 1
            pos = self.data.find(b'\n', end)
        del self.data[:end]
        if self.socket.state() == QLocalSocket.UnconnectedState:
            self.close()

    def command(self, command):
        """Perform one command."""
        command = command.split()
        cmd = command[0]
        args = command[1:]

        win = app.activeWindow()
        if not win:
            import mainwindow
            win = mainwindow.MainWindow()
            win.show()

        if cmd == b'open':
            url = QUrl.fromEncoded(args[0])
            win.openUrl(url, self.encoding)

        elif cmd == b'encoding':
            self.encoding = str(args[0])
        elif cmd == b'activate_window':
            win.activateWindow()
            win.raise_()
        elif cmd == b'set_current':
            url = QUrl.fromEncoded(args[0])
            try:
                win.setCurrentDocument(app.openUrl(url)) # already loaded
            except IOError:
                pass
        elif cmd == b'set_cursor':
            line, column = map(int, args)
            cursor = win.textCursor()
            pos = cursor.document().findBlockByNumber(line - 1).position() + column
            cursor.setPosition(pos)
            win.currentView().setTextCursor(cursor)
        elif cmd == b'bye':
            self.close()