1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2011-2021. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19-module(observer_wx).
20
21-behaviour(wx_object).
22
23-export([start/0, stop/0]).
24-export([create_menus/2, get_attrib/1, get_tracer/0, get_active_node/0, get_menubar/0,
25     get_scale/0, set_status/1, create_txt_dialog/4, try_rpc/4, return_to_localnode/2]).
26
27-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
28	 handle_call/3, handle_info/2, check_page_title/1]).
29
30%% Includes
31-include_lib("wx/include/wx.hrl").
32
33-include("observer_defs.hrl").
34
35%% Defines
36
37-define(ID_PING, 1).
38-define(ID_CONNECT, 2).
39-define(ID_NOTEBOOK, 3).
40-define(ID_CDV,      4).
41-define(ID_LOGVIEW, 5).
42
43-define(FIRST_NODES_MENU_ID, 1000).
44-define(LAST_NODES_MENU_ID,  2000).
45
46-define(TRACE_STR, "Trace Overview").
47-define(ALLOC_STR, "Memory Allocators").
48
49%% Records
50-record(state,
51	{frame,
52	 menubar,
53	 menus = [],
54	 status_bar,
55	 notebook,
56	 main_panel,
57         panels,
58	 active_tab,
59	 node,
60	 nodes,
61	 prev_node="",
62	 log = false,
63	 reply_to=false,
64         config
65	}).
66
67start() ->
68    case wx_object:start(?MODULE, [], []) of
69	Err = {error, _} -> Err;
70	_Obj -> ok
71    end.
72
73stop() ->
74    wx_object:call(observer, stop).
75
76create_menus(Object, Menus) when is_list(Menus) ->
77    wx_object:call(Object, {create_menus, Menus}).
78
79get_attrib(What) ->
80    wx_object:call(observer, {get_attrib, What}).
81
82set_status(What) ->
83    wx_object:cast(observer, {status_bar, What}).
84
85get_tracer() ->
86    wx_object:call(observer, get_tracer).
87
88get_active_node() ->
89    wx_object:call(observer, get_active_node).
90
91get_menubar() ->
92    wx_object:call(observer, get_menubar).
93
94get_scale() ->
95    ScaleStr = os:getenv("OBSERVER_SCALE", "1"),
96    try list_to_integer(ScaleStr) of
97        Scale when Scale < 1 -> 1;
98        Scale -> Scale
99    catch _:_ ->
100        1
101    end.
102
103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
104
105init(_Args) ->
106    %% put(debug, true),
107    register(observer, self()),
108    wx:new(),
109    catch wxSystemOptions:setOption("mac.listctrl.always_use_generic", 1),
110    Scale = get_scale(),
111    Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer",
112			[{size, {Scale * 850, Scale * 600}}, {style, ?wxDEFAULT_FRAME_STYLE}]),
113    IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
114    Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
115    wxFrame:setIcon(Frame, Icon),
116    wxIcon:destroy(Icon),
117
118    State = #state{frame = Frame},
119    UpdState = setup(State),
120    net_kernel:monitor_nodes(true),
121    process_flag(trap_exit, true),
122    {Frame, UpdState}.
123
124setup(#state{frame = Frame} = State) ->
125    %% Setup Menubar & Menus
126    Config = load_config(),
127    Cnf = fun(Who) ->
128                  proplists:get_value(Who, Config, #{})
129          end,
130    MenuBar = wxMenuBar:new(),
131
132    {Nodes, NodeMenus} = get_nodes(),
133    DefMenus = default_menus(NodeMenus),
134    observer_lib:create_menus(DefMenus, MenuBar, default),
135
136    wxFrame:setMenuBar(Frame, MenuBar),
137
138    %% Setup panels
139    Panel = wxPanel:new(Frame, []),
140    Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
141
142    %% System Panel
143    SysPanel = observer_sys_wx:start_link(Notebook, self(), Cnf(sys_panel)),
144    wxNotebook:addPage(Notebook, SysPanel, "System", []),
145
146    %% Setup sizer create early to get it when window shows
147    MainSizer = wxBoxSizer:new(?wxVERTICAL),
148
149    wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
150    wxPanel:setSizer(Panel, MainSizer),
151
152    StatusBar = wxStatusBar:new(Frame),
153    wxFrame:setStatusBar(Frame, StatusBar),
154    wxFrame:setTitle(Frame, atom_to_list(node())),
155    wxStatusBar:setStatusText(StatusBar, atom_to_list(node())),
156
157    wxNotebook:connect(Notebook, command_notebook_page_changed,
158                       [{skip, true}, {id, ?ID_NOTEBOOK}]),
159    wxFrame:connect(Frame, close_window, []),
160    wxMenu:connect(Frame, command_menu_selected),
161    wxFrame:show(Frame),
162
163    %% Freeze and thaw is buggy currently
164    DoFreeze = [?wxMAJOR_VERSION,?wxMINOR_VERSION] < [2,9]
165        orelse element(1, os:type()) =:= win32,
166    DoFreeze andalso wxWindow:freeze(Panel),
167    %% I postpone the creation of the other tabs so they can query/use
168    %% the window size
169
170    %% Perf Viewer Panel
171    PerfPanel = observer_perf_wx:start_link(Notebook, self(), Cnf(perf_panel)),
172    wxNotebook:addPage(Notebook, PerfPanel, "Load Charts", []),
173
174    %% Memory Allocator Viewer Panel
175    AllcPanel = observer_alloc_wx:start_link(Notebook, self(), Cnf(allc_panel)),
176    wxNotebook:addPage(Notebook, AllcPanel, ?ALLOC_STR, []),
177
178    %% App Viewer Panel
179    AppPanel = observer_app_wx:start_link(Notebook, self(), Cnf(app_panel)),
180    wxNotebook:addPage(Notebook, AppPanel, "Applications", []),
181
182    %% Process Panel
183    ProPanel = observer_pro_wx:start_link(Notebook, self(), Cnf(pro_panel)),
184    wxNotebook:addPage(Notebook, ProPanel, "Processes", []),
185
186    %% Port Panel
187    PortPanel = observer_port_wx:start_link(Notebook, self(), Cnf(port_panel)),
188    wxNotebook:addPage(Notebook, PortPanel, "Ports", []),
189
190    %% Socket Panel
191    SockPanel = observer_sock_wx:start_link(Notebook,
192					    self(),
193					    Cnf(sock_panel)),
194    wxNotebook:addPage(Notebook, SockPanel, "Sockets", []),
195
196    %% Table Viewer Panel
197    TVPanel = observer_tv_wx:start_link(Notebook, self(), Cnf(tv_panel)),
198    wxNotebook:addPage(Notebook, TVPanel, "Table Viewer", []),
199
200    %% Trace Viewer Panel
201    TracePanel = observer_trace_wx:start_link(Notebook, self(), Cnf(trace_panel)),
202    wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []),
203
204    %% Force redraw (windows needs it)
205    wxWindow:refresh(Panel),
206    DoFreeze andalso wxWindow:thaw(Panel),
207
208    wxFrame:raise(Frame),
209    wxFrame:setFocus(Frame),
210
211    SysPid = wx_object:get_pid(SysPanel),
212    SysPid ! {active, node()},
213    Panels =
214	[{sys_panel,   SysPanel,   "System"},   %% In order
215	 {perf_panel,  PerfPanel,  "Load Charts"},
216	 {allc_panel,  AllcPanel,  ?ALLOC_STR},
217	 {app_panel,   AppPanel,   "Applications"},
218	 {pro_panel,   ProPanel,   "Processes"},
219	 {port_panel,  PortPanel,  "Ports"},
220	%% if (SockPanel =:= undefined) -> [];
221	%%    true ->
222	%% 	[{sock_panel,  SockPanel,  "Sockets"}]
223	%% end ++
224	 {sock_panel,  SockPanel,  "Sockets"},
225	 {tv_panel,    TVPanel,    "Table Viewer"},
226	 {trace_panel, TracePanel, ?TRACE_STR}],
227
228    UpdState = State#state{main_panel = Panel,
229			   notebook   = Notebook,
230			   menubar    = MenuBar,
231			   status_bar = StatusBar,
232			   active_tab = SysPid,
233                           panels     = Panels,
234			   node       = node(),
235			   nodes      = Nodes
236			  },
237    %% Create resources which we don't want to duplicate
238    SysFont = wxSystemSettings:getFont(?wxSYS_OEM_FIXED_FONT),
239    %% OemFont = wxSystemSettings:getFont(?wxSYS_OEM_FIXED_FONT),
240    %% io:format("Sz sys ~p(~p) oem ~p(~p)~n",
241    %% 	      [wxFont:getPointSize(SysFont), wxFont:isFixedWidth(SysFont),
242    %% 	       wxFont:getPointSize(OemFont), wxFont:isFixedWidth(OemFont)]),
243    Fixed = case wxFont:isFixedWidth(SysFont) of
244		true -> SysFont;
245		false -> %% Sigh
246		    SysFontSize = wxFont:getPointSize(SysFont),
247		    wxFont:new(SysFontSize, ?wxFONTFAMILY_MODERN, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL)
248	    end,
249    put({font, fixed}, Fixed),
250    UpdState.
251
252
253%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
254
255%%Callbacks
256handle_event(#wx{event=#wxBookCtrl{type=command_notebook_page_changed, nSel=Next}},
257	     #state{active_tab=Previous, node=Node, panels=Panels, status_bar=SB} = State) ->
258    {_, Obj, _} = lists:nth(Next+1, Panels),
259    case wx_object:get_pid(Obj) of
260	Previous ->
261            {noreply, State};
262	Pid ->
263            wxStatusBar:setStatusText(SB, ""),
264	    Previous ! not_active,
265	    Pid ! {active, Node},
266	    {noreply, State#state{active_tab=Pid}}
267    end;
268
269handle_event(#wx{id = ?ID_CDV, event = #wxCommand{type = command_menu_selected}}, State) ->
270    spawn(crashdump_viewer, start, []),
271    {noreply, State};
272
273handle_event(#wx{event = #wxClose{}}, State) ->
274    stop_servers(State),
275    {noreply, State};
276
277handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) ->
278    stop_servers(State),
279    {noreply, State};
280
281handle_event(#wx{id = ?wxID_HELP, event = #wxCommand{type = command_menu_selected}}, State) ->
282    External = "http://www.erlang.org/doc/apps/observer/index.html",
283    Internal = filename:join([code:lib_dir(observer),"doc", "html", "index.html"]),
284    Help = case filelib:is_file(Internal) of
285	       true -> Internal;
286	       false -> External
287	   end,
288    wx_misc:launchDefaultBrowser(Help) orelse
289	create_txt_dialog(State#state.frame, "Could not launch browser: ~n " ++ Help,
290			  "Error", ?wxICON_ERROR),
291    {noreply, State};
292
293handle_event(#wx{id = ?wxID_ABOUT, event = #wxCommand{type = command_menu_selected}},
294	     State = #state{frame=Frame}) ->
295    AboutString = "Observe an erlang system\n"
296	"Authors: Olle Mattson & Magnus Eriksson & Dan Gudmundsson",
297    Style = [{style, ?wxOK bor ?wxSTAY_ON_TOP},
298	     {caption, "About"}],
299    wxMessageDialog:showModal(wxMessageDialog:new(Frame, AboutString, Style)),
300    {noreply, State};
301
302
303handle_event(#wx{id = ?ID_CONNECT, event = #wxCommand{type = command_menu_selected}},
304	     #state{frame = Frame} = State) ->
305    UpdState = case create_connect_dialog(connect, State) of
306		   cancel ->
307		       State;
308		   {value, [], _, _} ->
309		       create_txt_dialog(Frame, "Node must have a name",
310					 "Error", ?wxICON_ERROR),
311		       State;
312		   {value, NodeName, LongOrShort, Cookie} -> %Shortname,
313		       try
314			   case connect(list_to_atom(NodeName), LongOrShort, list_to_atom(Cookie)) of
315			       {ok, set_cookie} ->
316				   change_node_view(node(), State);
317			       {error, set_cookie} ->
318				   create_txt_dialog(Frame, "Could not set cookie",
319						     "Error", ?wxICON_ERROR),
320				   State;
321			       {error, net_kernel, _Reason} ->
322				   create_txt_dialog(Frame, "Could not enable node",
323						     "Error", ?wxICON_ERROR),
324				   State
325			   end
326		       catch _:_ ->
327			       create_txt_dialog(Frame, "Could not enable node",
328						 "Error", ?wxICON_ERROR),
329			       State
330		       end
331	       end,
332    {noreply, UpdState};
333
334handle_event(#wx{id = ?ID_PING, event = #wxCommand{type = command_menu_selected}},
335	     #state{frame = Frame} = State) ->
336    UpdState = case create_connect_dialog(ping, State) of
337		   cancel -> State;
338		   {value, Value} when is_list(Value) ->
339		       try
340			   Node = list_to_atom(Value),
341			   case net_adm:ping(Node) of
342			       pang ->
343				   create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION),
344				   State#state{prev_node=Value};
345			       pong ->
346				   State1 = change_node_view(Node, State),
347				   State1#state{prev_node=Value}
348			   end
349		       catch _:_ ->
350			       create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION),
351			       State#state{prev_node=Value}
352		       end
353	       end,
354    {noreply, UpdState};
355
356handle_event(#wx{id = ?ID_LOGVIEW, event = #wxCommand{type = command_menu_selected}},
357	     #state{frame = Frame, log = PrevLog, node = Node} = State) ->
358    try
359	ok = ensure_sasl_started(Node),
360	ok = ensure_mf_h_handler_used(Node),
361	ok = ensure_rb_mode(Node, PrevLog),
362	case PrevLog of
363	    false ->
364		rpc:block_call(Node, rb, start, []),
365		set_status("Observer - " ++ atom_to_list(Node) ++ " (rb_server started)"),
366		{noreply, State#state{log=true}};
367	    true ->
368		rpc:block_call(Node, rb, stop, []),
369		set_status("Observer - " ++ atom_to_list(Node) ++ " (rb_server stopped)"),
370		{noreply, State#state{log=false}}
371	end
372    catch
373	throw:Reason ->
374	    create_txt_dialog(Frame, Reason, "Log view status", ?wxICON_ERROR),
375	    {noreply, State}
376    end;
377
378handle_event(#wx{id = Id, event = #wxCommand{type = command_menu_selected}},
379	     #state{nodes= Ns , node = PrevNode, log = PrevLog} = State)
380  when Id > ?FIRST_NODES_MENU_ID, Id < ?LAST_NODES_MENU_ID ->
381    Node = lists:nth(Id - ?FIRST_NODES_MENU_ID, Ns),
382    %% Close rb_server only if another node than current one selected
383    LState = case PrevLog of
384		 true  -> case Node == PrevNode of
385			      false -> rpc:block_call(PrevNode, rb, stop, []),
386				       State#state{log=false} ;
387			      true  -> State
388			  end;
389		 false -> State
390             end,
391    {noreply, change_node_view(Node, LState)};
392
393handle_event(Event, #state{active_tab=Pid} = State) ->
394    Pid ! Event,
395    {noreply, State}.
396
397handle_cast({status_bar, Msg}, State=#state{status_bar=SB}) ->
398    wxStatusBar:setStatusText(SB, Msg),
399    {noreply, State};
400
401handle_cast(_Cast, State) ->
402    {noreply, State}.
403
404handle_call({create_menus, TabMenus}, _From,
405	    State = #state{menubar=MenuBar, menus=PrevTabMenus}) ->
406    if TabMenus == PrevTabMenus -> ignore;
407       true ->
408	    wx:batch(fun() ->
409			     clean_menus(PrevTabMenus, MenuBar),
410			     observer_lib:create_menus(TabMenus, MenuBar, plugin)
411		     end)
412    end,
413    {reply, ok, State#state{menus=TabMenus}};
414
415handle_call({get_attrib, Attrib}, _From, State) ->
416    {reply, get(Attrib), State};
417
418handle_call(get_tracer, _From, State=#state{panels=Panels}) ->
419    {_, TraceP, _} = lists:keyfind(trace_panel, 1, Panels),
420    {reply, TraceP, State};
421
422handle_call(get_active_node, _From, State=#state{node=Node}) ->
423    {reply, Node, State};
424
425handle_call(get_menubar, _From, State=#state{menubar=MenuBar}) ->
426    {reply, MenuBar, State};
427
428handle_call(stop, From, State) ->
429    stop_servers(State),
430    {noreply, State#state{reply_to=From}};
431
432handle_call(log_status, _From, State) ->
433    {reply, State#state.log, State};
434
435handle_call(_Msg, _From, State) ->
436    {reply, ok, State}.
437
438handle_info({nodeup, _Node}, State) ->
439    State2 = update_node_list(State),
440    {noreply, State2};
441
442handle_info({nodedown, Node},
443	    #state{frame = Frame} = State) ->
444    State2 = case Node =:= State#state.node of
445		 true ->
446		     change_node_view(node(), State);
447		 false ->
448		     State
449	     end,
450    State3 = update_node_list(State2),
451    Msg = ["Node down: " | atom_to_list(Node)],
452    create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION),
453    {noreply, State3};
454
455handle_info({open_link, Id0}, State = #state{panels=Panels,frame=Frame}) ->
456    Id = case Id0 of
457	      [_|_] -> try list_to_pid(Id0) catch _:_ -> Id0 end;
458	      _ -> Id0
459	  end,
460    %% Forward to process tab
461    case Id of
462	Pid when is_pid(Pid) ->
463            {pro_panel, ProcViewer, _} = lists:keyfind(pro_panel, 1, Panels),
464	    wx_object:get_pid(ProcViewer) ! {procinfo_open, Pid};
465	"#Port" ++ _ = Port ->
466            {port_panel, PortViewer, _} = lists:keyfind(port_panel, 1, Panels),
467	    wx_object:get_pid(PortViewer) ! {portinfo_open, Port};
468	_ ->
469	    Msg = io_lib:format("Information about ~p is not available or implemented",[Id]),
470	    Info = wxMessageDialog:new(Frame, Msg),
471	    wxMessageDialog:showModal(Info),
472	    wxMessageDialog:destroy(Info)
473    end,
474    {noreply, State};
475
476handle_info({get_debug_info, From}, State = #state{notebook=Notebook, active_tab=Pid}) ->
477    From ! {observer_debug, wx:get_env(), Notebook, Pid},
478    {noreply, State};
479
480handle_info({'EXIT', Pid, Reason}, State) ->
481    case Reason of
482	normal ->
483	    {noreply, State};
484	_ ->
485	    io:format("Observer: Child (~s) crashed exiting:  ~p ~tp~n",
486		      [pid2panel(Pid, State), Pid, Reason]),
487	    {stop, normal, State}
488    end;
489
490handle_info({stop, Me}, State) when Me =:= self() ->
491    {stop, normal, State};
492
493handle_info(_Info, State) ->
494    {noreply, State}.
495
496stop_servers(#state{node=Node, log=LogOn, panels=Panels} = _State) ->
497    LogOn andalso rpc:block_call(Node, rb, stop, []),
498    Me = self(),
499    save_config(Panels),
500    Stop = fun() ->
501		   try
502		       _ = [wx_object:stop(Panel) || {_, Panel, _} <- Panels],
503		       ok
504		   catch _:_ -> ok
505		   end,
506		   Me ! {stop, Me}
507	   end,
508    spawn(Stop).
509
510terminate(_Reason, #state{frame = Frame, reply_to=From}) ->
511    wxFrame:destroy(Frame),
512    wx:destroy(),
513    case From of
514	false -> ignore;
515	_ -> gen_server:reply(From, ok)
516    end,
517    ok.
518
519load_config() ->
520    case file:consult(config_file()) of
521        {ok, Config} -> Config;
522        _ -> []
523    end.
524
525save_config(Panels) ->
526    Configs = [{Name, wx_object:call(Panel, get_config)} || {Name, Panel, _} <- Panels],
527    File = config_file(),
528    case filelib:ensure_dir(File) of
529        ok ->
530            Format = [io_lib:format("~tp.~n",[Conf]) || Conf <- Configs],
531            _ = file:write_file(File, Format);
532        _ ->
533            ignore
534    end.
535
536config_file() ->
537    Dir = filename:basedir(user_config, "erl_observer"),
538    filename:join(Dir, "config.txt").
539
540code_change(_, _, State) ->
541    {ok, State}.
542
543%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
544
545try_rpc(Node, Mod, Func, Args) ->
546    case
547	rpc:call(Node, Mod, Func, Args) of
548	{badrpc, Reason} ->
549	    error_logger:error_report([{node, Node},
550				       {call, {Mod, Func, Args}},
551				       {reason, {badrpc, Reason}}]),
552	    observer ! {nodedown, Node},
553	    error({badrpc, Reason});
554	Res ->
555	    Res
556    end.
557
558return_to_localnode(Frame, Node) ->
559    case node() =/= Node of
560	true ->
561	    create_txt_dialog(Frame, "Error occured on remote node",
562			      "Error", ?wxICON_ERROR),
563	    disconnect_node(Node);
564	false ->
565	    ok
566    end.
567
568create_txt_dialog(Frame, Msg, Title, Style) ->
569    MD = wxMessageDialog:new(Frame, Msg, [{style, Style}, {caption,Title}]),
570    wxDialog:showModal(MD),
571    wxDialog:destroy(MD).
572
573connect(NodeName, 0, Cookie) ->
574    connect2(NodeName, shortnames, Cookie);
575connect(NodeName, 1, Cookie) ->
576    connect2(NodeName, longnames, Cookie).
577
578connect2(NodeName, Opts, Cookie) ->
579    case net_adm:names() of
580	{ok, _} -> %% Epmd is running
581	    ok;
582	{error, address} ->
583	    Epmd = os:find_executable("epmd"),
584	    os:cmd(Epmd)
585    end,
586    case net_kernel:start([NodeName, Opts]) of
587	{ok, _} ->
588	    case is_alive() of
589		true ->
590		    erlang:set_cookie(node(), Cookie),
591		    {ok, set_cookie};
592		false ->
593		    {error, set_cookie}
594	    end;
595	{error, Reason} ->
596	    {error, net_kernel, Reason}
597    end.
598
599change_node_view(Node, #state{active_tab=Tab} = State) ->
600    Tab ! not_active,
601    Tab ! {active, Node},
602    StatusText = ["Observer - " | atom_to_list(Node)],
603    wxFrame:setTitle(State#state.frame, StatusText),
604    wxStatusBar:setStatusText(State#state.status_bar, StatusText),
605    State#state{node = Node}.
606
607check_page_title(Notebook) ->
608    Selection = wxNotebook:getSelection(Notebook),
609    wxNotebook:getPageText(Notebook, Selection).
610
611pid2panel(Pid, #state{panels=Panels}) ->
612    PanelPids = [{Name, wx_object:get_pid(Obj)} || {Name, Obj, _} <- Panels],
613    case lists:keyfind(Pid, 2, PanelPids) of
614        false -> "unknown";
615        {Name,_} -> Name
616    end.
617
618create_connect_dialog(ping, #state{frame = Frame, prev_node=Prev}) ->
619    Dialog = wxTextEntryDialog:new(Frame, "Connect to node", [{value, Prev}]),
620    case wxDialog:showModal(Dialog) of
621	?wxID_OK ->
622	    Value = wxTextEntryDialog:getValue(Dialog),
623	    wxDialog:destroy(Dialog),
624	    {value, Value};
625	?wxID_CANCEL ->
626	    wxDialog:destroy(Dialog),
627	    cancel
628    end;
629create_connect_dialog(connect, #state{frame = Frame}) ->
630    Dialog = wxDialog:new(Frame, ?wxID_ANY, "Distribute node",
631			  [{style, ?wxDEFAULT_FRAME_STYLE bor ?wxRESIZE_BORDER}]),
632
633    VSizer = wxBoxSizer:new(?wxVERTICAL),
634
635    Choices = ["Short name", "Long name"],
636    RadioBox = wxRadioBox:new(Dialog, 1, "", ?wxDefaultPosition, ?wxDefaultSize,
637			      Choices, [{majorDim, 2}, {style, ?wxHORIZONTAL}]),
638
639    NameText = wxStaticText:new(Dialog, ?wxID_ANY, "Node name: "),
640    NameCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {300,-1}}]),
641    wxTextCtrl:setValue(NameCtrl, "observer"),
642    CookieText = wxStaticText:new(Dialog, ?wxID_ANY, "Secret cookie: "),
643    CookieCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY,[{style, ?wxTE_PASSWORD}]),
644
645    BtnSizer = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
646    Dir = ?wxLEFT bor ?wxRIGHT bor ?wxDOWN,
647    Flags = [{flag, ?wxEXPAND bor Dir bor ?wxALIGN_CENTER_VERTICAL}, {border, 5}],
648    wxSizer:add(VSizer, RadioBox, Flags),
649    wxSizer:addSpacer(VSizer, 10),
650    wxSizer:add(VSizer, NameText, [{flag, ?wxLEFT}, {border, 5}]),
651    wxSizer:add(VSizer, NameCtrl, Flags),
652    wxSizer:addSpacer(VSizer, 10),
653    wxSizer:add(VSizer, CookieText, [{flag, ?wxLEFT}, {border, 5}]),
654    wxSizer:add(VSizer, CookieCtrl, Flags),
655    wxSizer:addSpacer(VSizer, 10),
656    wxSizer:add(VSizer, BtnSizer, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL},{border, 5}]),
657
658    wxWindow:setSizerAndFit(Dialog, VSizer),
659    wxSizer:setSizeHints(VSizer, Dialog),
660    {ok,[[HomeDir]]} = init:get_argument(home),
661    CookiePath = filename:join(HomeDir, ".erlang.cookie"),
662    DefaultCookie = case filelib:is_file(CookiePath) of
663			true ->
664			    {ok, Bin} = file:read_file(CookiePath),
665			    binary_to_list(Bin);
666			false ->
667			    ""
668		    end,
669    wxTextCtrl:setValue(CookieCtrl, DefaultCookie),
670    case wxDialog:showModal(Dialog) of
671	?wxID_OK ->
672	    NameValue = wxTextCtrl:getValue(NameCtrl),
673	    NameLngthValue = wxRadioBox:getSelection(RadioBox),
674	    CookieValue = wxTextCtrl:getValue(CookieCtrl),
675	    wxDialog:destroy(Dialog),
676	    {value, NameValue, NameLngthValue, CookieValue};
677	?wxID_CANCEL ->
678	    wxDialog:destroy(Dialog),
679	    cancel
680    end.
681
682default_menus(NodesMenuItems) ->
683    CDV   = #create_menu{id = ?ID_CDV, text = "Examine Crashdump"},
684    Quit  = #create_menu{id = ?wxID_EXIT, text = "Quit"},
685    About = #create_menu{id = ?wxID_ABOUT, text = "About"},
686    Help  = #create_menu{id = ?wxID_HELP},
687    FileMenu = {"File", [CDV, Quit]},
688    NodeMenu = case erlang:is_alive() of
689		   true ->  {"Nodes", NodesMenuItems ++
690				 [#create_menu{id = ?ID_PING, text = "Connect Node"}]};
691		   false -> {"Nodes", NodesMenuItems ++
692				 [#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}]}
693	       end,
694    LogMenu =  {"Log", [#create_menu{id = ?ID_LOGVIEW, text = "Toggle log view"}]},
695    case os:type() =:= {unix, darwin} of
696	false ->
697	    FileMenu = {"File", [CDV, Quit]},
698	    HelpMenu = {"Help", [About,Help]},
699	    [FileMenu, NodeMenu, LogMenu, HelpMenu];
700	true ->
701	    %% On Mac quit and about will be moved to the "default' place
702	    %% automagicly, so just add them to a menu that always exist.
703	    %% But not to the help menu for some reason
704
705	    {Tag, Menus} = FileMenu,
706	    [{Tag, Menus ++ [Quit,About]}, NodeMenu, LogMenu, {"&Help", [Help]}]
707    end.
708
709clean_menus(Menus, MenuBar) ->
710    remove_menu_items(Menus, MenuBar).
711
712remove_menu_items([{MenuStr = "File", Menus}|Rest], MenuBar) ->
713    case wxMenuBar:findMenu(MenuBar, MenuStr) of
714	?wxNOT_FOUND ->
715	    remove_menu_items(Rest, MenuBar);
716	MenuId ->
717	    Menu = wxMenuBar:getMenu(MenuBar, MenuId),
718	    Items = [wxMenu:findItem(Menu, Tag) || #create_menu{text=Tag} <- Menus],
719	    [wxMenu:delete(Menu, MItem) || MItem <- Items],
720	    remove_menu_items(Rest, MenuBar)
721    end;
722remove_menu_items([{"Nodes", _}|_], _MB) ->
723    ok;
724remove_menu_items([{Tag, _Menus}|Rest], MenuBar) ->
725    case wxMenuBar:findMenu(MenuBar, Tag) of
726	?wxNOT_FOUND ->
727	    remove_menu_items(Rest, MenuBar);
728	MenuId ->
729	    Menu = wxMenuBar:getMenu(MenuBar, MenuId),
730	    wxMenuBar:remove(MenuBar, MenuId),
731	    Items = wxMenu:getMenuItems(Menu),
732	    [wxMenu:'Destroy'(Menu, Item) || Item <- Items],
733	    wxMenu:destroy(Menu),
734	    remove_menu_items(Rest, MenuBar)
735    end;
736remove_menu_items([], _MB) ->
737    ok.
738
739get_nodes() ->
740    Nodes0 = case erlang:is_alive() of
741		false -> [];
742		true  ->
743		    case net_adm:names() of
744			{error, _} -> nodes();
745			{ok, Names} ->
746			    epmd_nodes(Names) ++ nodes()
747		    end
748	     end,
749    Nodes = lists:usort(Nodes0),
750    {_, Menues} =
751	lists:foldl(fun(Node, {Id, Acc}) when Id < ?LAST_NODES_MENU_ID ->
752			    {Id + 1, [#create_menu{id=Id + ?FIRST_NODES_MENU_ID,
753						   text=atom_to_list(Node)} | Acc]}
754		    end, {1, []}, Nodes),
755    {Nodes, lists:reverse(Menues)}.
756
757epmd_nodes(Names) ->
758    [_, Host] = string:lexemes(atom_to_list(node()),"@"),
759    [list_to_atom(Name ++ [$@|Host]) || {Name, _} <- Names].
760
761update_node_list(State = #state{menubar=MenuBar}) ->
762    {Nodes, NodesMenuItems} = get_nodes(),
763    NodeMenu = case wxMenuBar:findMenu(MenuBar, "Nodes") of
764		   ?wxNOT_FOUND ->
765		       Menu = wxMenu:new(),
766		       wxMenuBar:append(MenuBar, Menu, "Nodes"),
767		       Menu;
768		   NodeMenuId ->
769		       Menu = wxMenuBar:getMenu(MenuBar, NodeMenuId),
770		       wx:foreach(fun(Item) -> wxMenu:'Destroy'(Menu, Item) end,
771				  wxMenu:getMenuItems(Menu)),
772		       Menu
773	       end,
774
775    Index = wx:foldl(fun(Record, Index) ->
776			     observer_lib:create_menu_item(Record, NodeMenu, Index)
777		     end, 0, NodesMenuItems),
778
779    Dist = case erlang:is_alive() of
780	       true  -> #create_menu{id = ?ID_PING, text = "Connect node"};
781	       false -> #create_menu{id = ?ID_CONNECT, text = "Enable distribution"}
782	   end,
783    observer_lib:create_menu_item(Dist, NodeMenu, Index),
784    State#state{nodes = Nodes}.
785
786ensure_sasl_started(Node) ->
787   %% is sasl started ?
788   Apps = rpc:block_call(Node, application, which_applications, []),
789   case lists:keyfind(sasl, 1, Apps) of
790       false        ->  throw("Error: sasl application not started."),
791                        error;
792       {sasl, _, _} ->  ok
793   end.
794
795ensure_mf_h_handler_used(Node) ->
796   %% is log_mf_h used ?
797   Handlers =
798        case rpc:block_call(Node, gen_event, which_handlers, [error_logger]) of
799            {badrpc,{'EXIT',noproc}} -> []; % OTP-21+ and no event handler exists
800            Hs -> Hs
801        end,
802   case lists:any(fun(L)-> L == log_mf_h end, Handlers) of
803       false -> throw("Error: log_mf_h handler not used in sasl."),
804                error;
805       true  -> ok
806   end.
807
808ensure_rb_mode(Node, PrevLog) ->
809    ok = ensure_rb_module_loaded(Node),
810    ok = is_rb_compatible(Node),
811    ok = is_rb_server_running(Node, PrevLog),
812    ok.
813
814
815ensure_rb_module_loaded(Node) ->
816   %% Need to ensure that module is loaded in order to detect exported
817   %% functions on interactive nodes
818   case rpc:block_call(Node, code, ensure_loaded, [rb]) of
819       {badrpc, Reason} ->
820	   throw("Error: badrpc - " ++ io_lib:format("~tp",[Reason]));
821       {error, Reason} ->
822	   throw("Error: rb module load error - " ++ io_lib:format("~tp",[Reason]));
823       {module,rb} ->
824	   ok
825   end.
826
827is_rb_compatible(Node) ->
828   %% Simply test that rb:log_list/0 is exported
829   case rpc:block_call(Node, erlang, function_exported, [rb, log_list, 0]) of
830       false -> throw("Error: Node's Erlang release must be at least R16B02.");
831       true  -> ok
832   end.
833
834is_rb_server_running(Node, LogState) ->
835   %% If already started, somebody else may use it.
836   %% We cannot use it too, as far log file would be overriden. Not fair.
837   case rpc:block_call(Node, erlang, whereis, [rb_server]) of
838       Pid when is_pid(Pid), (LogState == false) ->
839	   throw("Error: rb_server is already started and maybe used by someone.");
840       Pid when is_pid(Pid) ->
841	   ok;
842       undefined ->
843	   ok
844   end.
845
846
847%% d(F) ->
848%%     d(F, []).
849
850%% d(F, A) ->
851%%     d(get(debug), F, A).
852
853%% d(true, F, A) ->
854%%     io:format("[owx] " ++ F ++ "~n", A);
855%% d(_, _, _) ->
856%%     ok.
857
858
859