1
+ import logging
1
2
import argparse
2
3
import asyncio
3
4
4
5
from quamash import QEventLoop , QtWidgets , QtGui , QtCore
5
6
6
- from artiq .protocols .sync_struct import Subscriber
7
- from artiq .protocols .pc_rpc import Client
7
+ from artiq .protocols .sync_struct import Subscriber , process_mod
8
+ from artiq .protocols import pyon
9
+ from artiq .protocols .pipe_ipc import AsyncioChildComm
10
+
11
+
12
+ logger = logging .getLogger (__name__ )
13
+
14
+
15
+ class AppletIPCClient (AsyncioChildComm ):
16
+ def set_close_cb (self , close_cb ):
17
+ self .close_cb = close_cb
18
+
19
+ def write_pyon (self , obj ):
20
+ self .write (pyon .encode (obj ).encode () + b"\n " )
21
+
22
+ async def read_pyon (self ):
23
+ line = await self .readline ()
24
+ return pyon .decode (line .decode ())
25
+
26
+ async def embed (self , win_id ):
27
+ # This function is only called when not subscribed to anything,
28
+ # so the only normal replies are embed_done and terminate.
29
+ self .write_pyon ({"action" : "embed" ,
30
+ "win_id" : win_id })
31
+ reply = await self .read_pyon ()
32
+ if reply ["action" ] == "terminate" :
33
+ self .close_cb ()
34
+ elif reply ["action" ] != "embed_done" :
35
+ logger .error ("unexpected action reply to embed request: %s" ,
36
+ action )
37
+ self .close_cb ()
38
+
39
+ async def listen (self ):
40
+ data = None
41
+ while True :
42
+ obj = await self .read_pyon ()
43
+ try :
44
+ action = obj ["action" ]
45
+ if action == "terminate" :
46
+ self .close_cb ()
47
+ return
48
+ elif action == "mod" :
49
+ mod = obj ["mod" ]
50
+ if mod ["action" ] == "init" :
51
+ data = self .init_cb (mod ["struct" ])
52
+ else :
53
+ process_mod (data , mod )
54
+ self .mod_cb (mod )
55
+ else :
56
+ raise ValueError ("unknown action in parent message" )
57
+ except :
58
+ logger .error ("error processing parent message" ,
59
+ exc_info = True )
60
+ self .close_cb ()
61
+
62
+ def subscribe (self , datasets , init_cb , mod_cb ):
63
+ self .write_pyon ({"action" : "subscribe" ,
64
+ "datasets" : datasets })
65
+ self .init_cb = init_cb
66
+ self .mod_cb = mod_cb
67
+ asyncio .ensure_future (self .listen ())
8
68
9
69
10
70
class SimpleApplet :
@@ -13,27 +73,27 @@ def __init__(self, main_widget_class, cmd_description=None,
13
73
self .main_widget_class = main_widget_class
14
74
15
75
self .argparser = argparse .ArgumentParser (description = cmd_description )
76
+
16
77
self .argparser .add_argument ("--update-delay" , type = float ,
17
78
default = default_update_delay ,
18
79
help = "time to wait after a mod (buffering other mods) "
19
80
"before updating (default: %(default).2f)" )
20
- group = self .argparser .add_argument_group ("data server" )
21
- group .add_argument (
22
- "--server-notify" , default = "::1" ,
23
- help = "hostname or IP to connect to for dataset notifications" )
24
- group .add_argument (
25
- "--port-notify" , default = 3250 , type = int ,
26
- help = "TCP port to connect to for dataset notifications" )
27
- group = self .argparser .add_argument_group ("GUI server" )
81
+
82
+ group = self .argparser .add_argument_group ("standalone mode (default)" )
28
83
group .add_argument (
29
- "--server-gui" , default = "::1" ,
30
- help = "hostname or IP to connect to for GUI control" )
84
+ "--server" , default = "::1" ,
85
+ help = "hostname or IP of the master to connect to "
86
+ "for dataset notifications "
87
+ "(ignored in embedded mode)" )
31
88
group .add_argument (
32
- "--port-gui" , default = 6501 , type = int ,
33
- help = "TCP port to connect to for GUI control" )
34
- group .add_argument ("--embed" , default = None , type = int ,
35
- help = "embed main widget into existing window" )
89
+ "--port" , default = 3250 , type = int ,
90
+ help = "TCP port to connect to" )
91
+
92
+ self .argparser .add_argument ("--embed" , default = None ,
93
+ help = "embed into GUI" , metavar = "IPC_ADDRESS" )
94
+
36
95
self ._arggroup_datasets = self .argparser .add_argument_group ("datasets" )
96
+
37
97
self .dataset_args = set ()
38
98
39
99
def add_dataset (self , name , help = None , required = True ):
@@ -56,6 +116,15 @@ def quamash_init(self):
56
116
self .loop = QEventLoop (app )
57
117
asyncio .set_event_loop (self .loop )
58
118
119
+ def ipc_init (self ):
120
+ if self .args .embed is not None :
121
+ self .ipc = AppletIPCClient (self .args .embed )
122
+ self .loop .run_until_complete (self .ipc .connect ())
123
+
124
+ def ipc_close (self ):
125
+ if self .args .embed is not None :
126
+ self .ipc .close ()
127
+
59
128
def create_main_widget (self ):
60
129
self .main_widget = self .main_widget_class (self .args )
61
130
# Qt window embedding is ridiculously buggy, and empirical testing
@@ -65,22 +134,22 @@ def create_main_widget(self):
65
134
# 3. applet sends the ID to host, host embeds the widget
66
135
# 4. applet shows the widget
67
136
# Doing embedding the other way around (using QWindow.setParent in the
68
- # applet) breaks resizing; furthermore the host needs to know our
69
- # window ID to request graceful termination by closing the window.
137
+ # applet) breaks resizing.
70
138
if self .args .embed is not None :
139
+ self .ipc .set_close_cb (self .main_widget .close )
71
140
win_id = int (self .main_widget .winId ())
72
- remote = Client (self .args .server_gui , self .args .port_gui , "applets" )
73
- try :
74
- remote .embed (self .args .embed , win_id )
75
- finally :
76
- remote .close_rpc ()
141
+ self .loop .run_until_complete (self .ipc .embed (win_id ))
77
142
self .main_widget .show ()
78
143
79
144
def sub_init (self , data ):
80
145
self .data = data
81
146
return data
82
147
83
148
def filter_mod (self , mod ):
149
+ if self .args .embed is not None :
150
+ # the parent already filters for us
151
+ return True
152
+
84
153
if mod ["action" ] == "init" :
85
154
return True
86
155
if mod ["path" ]:
@@ -108,21 +177,32 @@ def sub_mod(self, mod):
108
177
else :
109
178
self .main_widget .data_changed (self .data , [mod ])
110
179
111
- def create_subscriber (self ):
112
- self .subscriber = Subscriber ("datasets" ,
113
- self .sub_init , self .sub_mod )
114
- self .loop .run_until_complete (self .subscriber .connect (
115
- self .args .server_notify , self .args .port_notify ))
180
+ def subscribe (self ):
181
+ if self .args .embed is None :
182
+ self .subscriber = Subscriber ("datasets" ,
183
+ self .sub_init , self .sub_mod )
184
+ self .loop .run_until_complete (self .subscriber .connect (
185
+ self .args .server , self .args .port ))
186
+ else :
187
+ self .ipc .subscribe (self .datasets , self .sub_init , self .sub_mod )
188
+
189
+ def unsubscribe (self ):
190
+ if self .args .embed is None :
191
+ self .loop .run_until_complete (self .subscriber .close ())
116
192
117
193
def run (self ):
118
194
self .args_init ()
119
195
self .quamash_init ()
120
196
try :
121
- self .create_main_widget ()
122
- self .create_subscriber ()
197
+ self .ipc_init ()
123
198
try :
124
- self .loop .run_forever ()
199
+ self .create_main_widget ()
200
+ self .subscribe ()
201
+ try :
202
+ self .loop .run_forever ()
203
+ finally :
204
+ self .unsubscribe ()
125
205
finally :
126
- self .loop . run_until_complete ( self . subscriber . close () )
206
+ self .ipc_close ( )
127
207
finally :
128
208
self .loop .close ()
0 commit comments