config #2

Merged
zxq9 merged 8 commits from config into master 2025-05-27 23:15:20 +09:00
4 changed files with 323 additions and 429 deletions
Showing only changes of commit f901ba6c9d - Show all commits

View File

@ -30,8 +30,10 @@
exec_dir = platform_dir() :: file:filename(),
acc = none :: none | binary(),
keys = [] :: [],
mem_limit = 3550722201 :: pos_integer(),
prefs = #{} :: #{}}).
max_cores = 2 :: pos_integer(),
max_mem = 3550722201 :: pos_integer(),
prefs = #{} :: #{},
gmc_conf = none :: none | reference()}).
-type state() :: #s{}.
@ -51,19 +53,18 @@ gajudesk() ->
gen_server:cast(?MODULE, gajudesk).
-spec conf() -> {Account, Keys, Network}
when Account :: none | binary(), % <<"ak_...">>
Keys :: [binary()],
Network :: binary(). % <<"mainnet">> | <<"testnet">> | <<"devnet">>
-spec conf() -> ok.
conf() ->
gen_server:call(?MODULE, conf).
gen_server:cast(?MODULE, conf).
-spec conf({Account, Keys, Network}) -> ok
when Account :: none | binary(), % <<"ak_...">>
Keys :: [binary()],
Network :: binary(). % <<"mainnet">> | <<"testnet">> | <<"devnet">>
-spec conf({Account, Keys, Network, MaxCores, MaxMem}) -> ok
when Account :: none | binary(), % <<"ak_...">>
Keys :: [binary()], % [<<"ak_...">>]
Network :: binary(), % <<"mainnet">> | <<"testnet">> | <<"devnet">>
MaxCores :: none | integer(),
MaxMem :: none | integer().
conf(Info) ->
gen_server:cast(?MODULE, {conf, Info}).
@ -109,17 +110,18 @@ start_link() ->
init(none) ->
ok = log(info, "Starting"),
_ = process_flag(sensitive, true),
{Acc, Keys, Network, MemLimit} =
{Acc, Keys, Network, MaxCores, MaxMem} =
case read_conf() of
{ok, C} ->
{maps:get(account, C, none),
maps:get(keys, C, []),
maps:get(network, C, <<"mainnet">>),
maps:get(mem_limit, C, 3550722201)};
maps:get(max_cores, C, 2),
maps:get(max_mem, C, 3550722201)};
none ->
{none, [], <<"mainnet">>, 3550722201}
{none, [], <<"mainnet">>, 2, 3550722201}
end,
State = #s{network = Network, acc = Acc, keys = Keys, mem_limit = MemLimit},
State = #s{network = Network, acc = Acc, keys = Keys, max_cores = MaxCores, max_mem = MaxMem},
{ok, start_gui(State)}.
@ -154,8 +156,6 @@ start_gui(State) ->
handle_call(network, _, State = #s{network = Network}) ->
{reply, Network, State};
handle_call(conf, _, State = #s{acc = Acc, keys = Keys, network = Network}) ->
{reply, {Acc, Keys, Network}, State};
handle_call(bin_dir, _, State = #s{exec_dir = BinDir}) ->
{reply, BinDir, State};
handle_call(Unexpected, From, State) ->
@ -169,6 +169,12 @@ handle_cast(start_stop, State) ->
handle_cast(gajudesk, State) ->
ok = do_gajudesk(),
{noreply, State};
handle_cast(conf, State) ->
NewState = run_gmc_conf(State),
{noreply, NewState};
handle_cast({conf, Info}, State) ->
NewState = do_conf(Info, State),
{noreply, NewState};
handle_cast(stop, State) ->
ok = do_stop(),
ok = log(info, "Received a 'stop' message."),
@ -181,6 +187,10 @@ handle_cast(Unexpected, State) ->
handle_info({gproc_ps_event, Event, Data}, State) ->
ok = gmc_gui:message({Event, Data}),
{noreply, State};
handle_info({'DOWN', Mon, process, PID, Info}, State = #s{gmc_conf = Mon}) ->
ok = log(info, "gmc_conf ~p closed with ~p", [PID, Info]),
NewState = State#s{gmc_conf = none},
{noreply, NewState};
handle_info(Unexpected, State) ->
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
{noreply, State}.
@ -211,7 +221,7 @@ do_stop() ->
%%% Doers
do_start_stop(#s{acc = PubKey, network = Network, mem_limit = MemLimit}) ->
do_start_stop(#s{acc = PubKey, network = Network, max_mem = MaxMem}) ->
% smrt guy stuff happens here
{Bits, Eureka} =
case Network of
@ -238,8 +248,8 @@ do_start_stop(#s{acc = PubKey, network = Network, mem_limit = MemLimit}) ->
end
end,
Miner = unicode:characters_to_binary([Fatness, Bits, "-", Type]),
Count = optimize_count(MemLimit),
Instance = #{<<"executable">> => Miner, <<"executable_group">> => <<"gajumine">>},
Count = optimize_count(MaxMem),
Instance = #{<<"executable">> => Miner},
Workers = lists:duplicate(Count, Instance),
Profile =
[{pubkey, PubKey},
@ -260,24 +270,9 @@ do_start_stop(#s{acc = PubKey, network = Network, mem_limit = MemLimit}) ->
disconnected],
lists:foreach(fun gmhc_events:ensure_subscribed/1, Events).
optimize_count(MemLimit) ->
{Procs, Memory} =
case os:type() of
{unix, linux} ->
{processor, Cores} = hd(erlang:system_info(cpu_topology)),
P = length(Cores),
M = list_to_integer(string:trim(os:cmd("cat /proc/meminfo | grep MemTotal | awk '{print $2}'"))) * 1024,
{P, M};
{unix, darwin} ->
P = list_to_integer(string:trim(os:cmd("sysctl -n hw.physicalcpu"))),
M = list_to_integer(string:trim(os:cmd("sysctl -n hw.memsize"))),
{P, M};
{win32, nt} ->
P = list_to_integer(os:getenv("NUMBER_OF_PROCESSORS")),
M = list_to_integer(string:strip(lists:nth(2, string:split(os:cmd("wmic computersystem get TotalPhysicalMemory"), "\r\r\n", all)))),
{P, M}
end,
MeanMaps = min(MemLimit, Memory) div 3550722201,
optimize_count(MaxMem) ->
{Procs, Memory} = proc_mem(),
MeanMaps = min(MaxMem, Memory) div 3550722201,
Recommended = max(min(Procs, MeanMaps) - 1, 1),
Notice = fun(F, A) -> gmc_gui:message({notice, io_lib:format(F, A)}) end,
ok = Notice("Physical Processors: ~p", [Procs]),
@ -286,6 +281,23 @@ optimize_count(MemLimit) ->
ok = Notice("Workers: ~p", [Recommended]),
Recommended.
proc_mem() ->
case os:type() of
{unix, linux} ->
{processor, Cores} = hd(erlang:system_info(cpu_topology)),
P = length(Cores),
M = list_to_integer(string:trim(os:cmd("cat /proc/meminfo | grep MemTotal | awk '{print $2}'"))) * 1024,
{P, M};
{unix, darwin} ->
P = list_to_integer(string:trim(os:cmd("sysctl -n hw.physicalcpu"))),
M = list_to_integer(string:trim(os:cmd("sysctl -n hw.memsize"))),
{P, M};
{win32, nt} ->
P = list_to_integer(os:getenv("NUMBER_OF_PROCESSORS")),
M = list_to_integer(string:strip(lists:nth(2, string:split(os:cmd("wmic computersystem get TotalPhysicalMemory"), "\r\r\n", all)))),
{P, M}
end.
do_gajudesk() ->
ok = tell(info, "Running gajudesk"),
@ -307,13 +319,31 @@ run_gajudesk() ->
end.
run_gmc_conf(State = #s{gmc_conf = none, network = Net, acc = Acc, keys = Keys,
max_cores = MProcs, max_mem = MMem,
prefs = Prefs}) ->
{AProcs, AMem} = proc_mem(),
Win = gmc_conf:start_link({Prefs, {Net, Acc, Keys, {AProcs, AMem, MProcs, MMem}}}),
PID = wx_object:get_pid(Win),
Mon = monitor(process, PID),
ok = gmc_conf:to_front(),
State#s{gmc_conf = Mon};
run_gmc_conf(State) ->
ok = gmc_conf:to_front(),
State.
do_conf({Network, AccID, Keys, MaxCores, MaxMem}, State) ->
State#s{network = Network, acc = AccID, keys = Keys, max_cores = MaxCores, max_mem = MaxMem}.
%%% Utils
%% Paths and Names
persist(#s{network = Network, acc = Acc, keys = Keys, mem_limit = MemLimit, prefs = Prefs}) ->
Update = #{network => Network, account => Acc, keys => Keys, mem_limit => MemLimit},
persist(#s{network = Network, acc = Acc, keys = Keys, max_cores = MaxCores, max_mem = MaxMem, prefs = Prefs}) ->
Update = #{network => Network, account => Acc, keys => Keys, max_cores => MaxCores, max_mem => MaxMem},
Conf = maps:to_list(maps:merge(Prefs, Update)),
Path = gmc_prefs_path(),
ok = filelib:ensure_dir(Path),

249
src/gmc_conf.erl Normal file
View File

@ -0,0 +1,249 @@
%%% @doc
%%% GajuMine Configuration GUI
%%% @end
-module(gmc_conf).
-vsn("0.2.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("Craig Everett <craigeverett@qpq.swiss>").
-license("GPL-3.0-or-later").
-behavior(wx_object).
-include_lib("wx/include/wx.hrl").
-export([to_front/0, trouble/1]).
-export([start_link/1]).
-export([init/1, terminate/2, code_change/3,
handle_call/3, handle_cast/2, handle_info/2, handle_event/2]).
-include("$zx_include/zx_logger.hrl").
-record(s,
{wx = none :: none | wx:wx_object(),
frame = none :: none | wx:wx_object(),
lang = en :: en | jp,
j = none :: none | fun(),
net = none :: none | wx:wx_object(),
acc = none :: none | wx:wx_object(),
keys = none :: none | wx:wx_object(),
cores = none :: none | wx:wx_object(),
memory = none :: none | wx:wx_object()}).
%%% Interface functions
-spec to_front() -> ok.
to_front() ->
wx_object:cast(?MODULE, to_front).
-spec trouble(term()) -> ok.
trouble(Info) ->
wx_object:cast(?MODULE, {trouble, Info}).
%%% Startup Functions
start_link(Prefs) ->
wx_object:start_link({local, ?MODULE}, ?MODULE, Prefs, []).
init({Prefs, {Net, Acc, Keys, {AProcs, AMem, MProcs, MMem}}}) ->
ok = log(info, "GUI starting..."),
Lang = maps:get(lang, Prefs, en_US),
Trans = gd_jt:read_translations(?MODULE),
J = gd_jt:j(Lang, Trans),
Label = J("Configure GajuMine"),
WX = wx:new(),
Frame = wxFrame:new(WX, ?wxID_ANY, Label),
MainSz = wxBoxSizer:new(?wxVERTICAL),
AccSz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("GajuMining Account ID")}]),
AccTx = wxTextCtrl:new(Frame, ?wxID_ANY, [{value, acc(Acc)}]),
_ = wxStaticBoxSizer:add(AccSz, AccTx, wide(5)),
KeysSz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Additional Account IDs (optional)")}]),
KeysTx = wxTextCtrl:new(Frame, ?wxID_ANY, [{style, ?wxTE_MULTILINE}, {value, keys(Keys)}]),
_ = wxStaticBoxSizer:add(KeysSz, KeysTx, wide(5)),
StatSz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Max System Committment (optional)")}]),
AProcsS = integer_to_list(AProcs),
MProcsS = integer_to_list(MProcs),
AMemS = gigs(AMem),
MMemS = gigs(MMem),
Labels = [{J("HW Cores"), J("Max Cores"), AProcsS, MProcsS}, {J("Memory (GB)"), J("Max Memory"), AMemS, MMemS}],
{Grid, [CoresTx, MemoryTx]} = display_box(Frame, Labels),
Network = wxRadioBox:new(Frame, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, [J("Mainnet"), J("Testnet")]),
ok = wxRadioBox:setSelection(Network, net_num(Net)),
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
Affirm = wxButton:new(Frame, ?wxID_OK),
Cancel = wxButton:new(Frame, ?wxID_CANCEL),
_ = wxBoxSizer:add(StatSz, Grid, wide(5)),
_ = wxBoxSizer:add(ButtSz, Affirm, wide(5)),
_ = wxBoxSizer:add(ButtSz, Cancel, wide(5)),
_ = wxBoxSizer:add(MainSz, AccSz, base(5)),
_ = wxBoxSizer:add(MainSz, KeysSz, wide(5)),
_ = wxBoxSizer:add(MainSz, StatSz, base(5)),
_ = wxBoxSizer:add(MainSz, Network, base(5)),
_ = wxBoxSizer:add(MainSz, ButtSz, base(5)),
ok = wxFrame:setSizer(Frame, MainSz),
ok = wxBoxSizer:layout(MainSz),
ok = wxFrame:connect(Frame, command_button_clicked),
ok = wxFrame:connect(Frame, close_window),
ok = wxFrame:setSize(Frame, {500, 500}),
ok = wxFrame:center(Frame),
true = wxFrame:show(Frame),
State =
#s{wx = WX, frame = Frame,
lang = Lang, j = J,
net = Network, acc = AccTx, keys = KeysTx,
cores = CoresTx, memory = MemoryTx},
{Frame, State}.
base(Padding) -> [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALL}, {border, Padding}].
wide(Padding) -> [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, Padding}].
center() -> [{proportion, 0}, {flag, ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}].
gigs(Bytes) ->
float_to_list(Bytes / gig(), [{decimals, 2}, compact]).
gig() -> 1_073_741_824.
display_box(Parent, Labels) ->
Grid = wxFlexGridSizer:new(2, 4, 4),
ok = wxFlexGridSizer:setFlexibleDirection(Grid, ?wxHORIZONTAL),
ok = wxFlexGridSizer:addGrowableCol(Grid, 1),
Make =
fun({SO, SI, VS, VM}) ->
LO = [SO, ":"],
LI = [SI, ":"],
T_LO = wxStaticText:new(Parent, ?wxID_ANY, LO),
T_CO = wxStaticText:new(Parent, ?wxID_ANY, VS, [{style, ?wxALIGN_LEFT}]),
T_LI = wxStaticText:new(Parent, ?wxID_ANY, LI),
T_CI = wxTextCtrl:new(Parent, ?wxID_ANY, [{value, VM}]),
_ = wxFlexGridSizer:add(Grid, T_LO, center()),
_ = wxFlexGridSizer:add(Grid, T_CO, center()),
_ = wxFlexGridSizer:add(Grid, T_LI, center()),
_ = wxFlexGridSizer:add(Grid, T_CI, zxw:flags(wide)),
T_CI
end,
Controls = lists:map(Make, Labels),
{Grid, Controls}.
handle_call(Unexpected, From, State) ->
ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]),
{noreply, State}.
handle_cast(to_front, State = #s{frame = Frame}) ->
ok = wxFrame:raise(Frame),
{noreply, State};
handle_cast({trouble, Info}, State) ->
ok = handle_troubling(State, Info),
{noreply, State};
handle_cast(Unexpected, State) ->
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]),
{noreply, State}.
handle_info(Unexpected, State) ->
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
{noreply, State}.
handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, State) ->
ok =
case ID of
?wxID_OK -> done(State);
?wxID_CANCEL -> buh_bye(State)
end,
{noreply, State};
handle_event(#wx{event = #wxClose{}}, State) ->
ok = buh_bye(State),
{noreply, State};
handle_event(Event, State) ->
ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]),
{noreply, State}.
handle_troubling(#s{frame = Frame}, Info) ->
zxw:show_message(Frame, Info).
code_change(_, State, _) ->
{ok, State}.
terminate(Reason, State) ->
ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]),
wx:destroy().
buh_bye(#s{frame = Frame}) ->
wxWindow:destroy(Frame).
%%% Doers
done(State = #s{net = Network, acc = AccTx, keys = KeysTx, cores = CoresTx, memory = MemTx}) ->
Net =
case wxRadioBox:getSelection(Network) of
0 -> <<"mainnet">>;
1 -> <<"testnet">>;
_ -> <<"mainnet">>
end,
AccID = wxTextCtrl:getValue(AccTx),
MOAR_IDs = wxTextCtrl:getValue(KeysTx),
CoreS = wxTextCtrl:getValue(CoresTx),
GigsS = wxTextCtrl:getValue(MemTx),
ok = gmc_con:conf({Net, list_to_binary(AccID), binify_keys(MOAR_IDs), cores(CoreS), bytes(GigsS)}),
buh_bye(State).
cores("") ->
none;
cores(CoreS) ->
try
list_to_integer(CoreS)
catch
_:_ -> none
end.
bytes("") ->
none;
bytes(GigsS) ->
try
list_to_integer(GigsS) * gig()
catch
_:_ ->
try
trunc(list_to_float(GigsS) * gig())
catch
_:_ -> none
end
end.
% NOTE: 32 is space, 12288 is full-width space.
binify_keys(MOAR) ->
[list_to_binary(K) || K <- string:lexemes(MOAR, [$\r, $\n, 32, 12288, $\t, $,, $;])].
acc(none) -> "";
acc(AKID) -> AKID.
keys(List) ->
unicode:characters_to_list(lists:join("\n", List)).
net_num(<<"mainnet">>) -> 0;
net_num(<<"testnet">>) -> 1;
net_num(_) -> 0.

View File

@ -169,8 +169,8 @@ handle_call(Unexpected, From, State) ->
handle_cast(ask_conf, State) ->
ok = conf(State),
{noreply, State};
NewState = conf(State),
{noreply, NewState};
handle_cast({set_account, ID}, State) ->
ok = set_account(ID, State),
{noreply, State};
@ -379,68 +379,10 @@ explorer_url() ->
end.
conf(State = #s{frame = Frame, j = J}) ->
Outcome = conf(Frame, J),
ok = tell("Outcome: ~p", [Outcome]),
conf(State) ->
ok = gmc_con:conf(),
State.
conf(Frame, J) ->
{Acc, Keys, NetNum} = get_conf(),
Label = J("Configure GajuMine"),
Dialog = wxDialog:new(Frame, ?wxID_ANY, Label, [{size, {400, 400}}]),
Sizer = wxBoxSizer:new(?wxVERTICAL),
Network = wxRadioBox:new(Dialog, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, [J("Mainnet"), J("Testnet")]),
ok = wxRadioBox:setSelection(Network, NetNum),
GMAccSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("GajuMining Account ID")}]),
GMAccTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}, {value, Acc}]),
_ = wxStaticBoxSizer:add(GMAccSz, GMAccTx, zxw:flags(wide)),
AccsSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Additional Account IDs (optional)")}]),
AccsTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}, {value, Keys}]),
_ = wxStaticBoxSizer:add(AccsSz, AccsTx, zxw:flags(wide)),
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
Affirm = wxButton:new(Dialog, ?wxID_OK),
Cancel = wxButton:new(Dialog, ?wxID_CANCEL),
_ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)),
_ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)),
_ = wxBoxSizer:add(Sizer, Network, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, GMAccSz, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, AccsSz, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)),
ok = wxDialog:setSizer(Dialog, Sizer),
ok = wxBoxSizer:layout(Sizer),
ok = wxFrame:center(Dialog),
Outcome =
case wxDialog:showModal(Dialog) of
?wxID_OK ->
Net =
case wxRadioBox:getSelection(Network) of
0 -> <<"mainnet">>;
1 -> <<"testnet">>;
_ -> <<"mainnet">>
end,
AccID = wxTextCtrl:getValue(GMAccTx),
MOAR_IDs = wxTextCtrl:getValue(AccsTx),
{Net, AccID, MOAR_IDs};
?wxID_CANCEL ->
cancel
end,
ok = wxDialog:destroy(Dialog),
Outcome.
get_conf() ->
{ID, List, Network} = gmc_con:conf(),
{acc(ID), keys(List), net_num(Network)}.
acc(none) -> "";
acc(AKID) -> AKID.
keys(List) ->
unicode:characters_to_list(lists:join("\n", List)).
net_num(<<"mainnet">>) -> 0;
net_num(<<"testnet">>) -> 1;
net_num(_) -> 0.
open_browser(Frame, J, URL) ->
case wx_misc:launchDefaultBrowser(URL) of

View File

@ -1,327 +0,0 @@
%%% @doc
%%% GajuMine Setup GUI
%%%
%%% In the common case, when a user first runs the program they will not have any
%%% accounrts, keys, or mining system setup. The purpose of this program is to provide
%%% a first-run guide for them to get set up for mining and explain to the user what is
%%% going on.
%%%
%%% There are two cases to cover:
%%% 1. A new miner who doesn't know how anything works and needs to get set up from scratch.
%%% 2. A miner who does know a bit about how things work and wants to import a key.
%%% @end
-module(gmc_setup).
-vsn("0.2.0").
-author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("Craig Everett <craigeverett@qpq.swiss>").
-license("GPL-3.0-or-later").
-behavior(wx_object).
-include_lib("wx/include/wx.hrl").
-export([trouble/1, done/0, done/1]).
-export([start_link/1]).
-export([init/1, terminate/2, code_change/3,
handle_call/3, handle_cast/2, handle_info/2, handle_event/2]).
-include("$zx_include/zx_logger.hrl").
-record(w,
{name = none :: atom(),
id = 0 :: integer(),
wx = none :: none | wx:wx_object()}).
-record(s,
{wx = none :: none | wx:wx_object(),
frame = none :: none | wx:wx_object(),
lang = en :: en | jp,
j = none :: none | fun(),
buttons = [] :: [#w{}]}).
%%% Interface functions
trouble(Info) ->
wx_object:cast(?MODULE, {trouble, Info}).
done() ->
wx_object:cast(?MODULE, done).
done(Mnemonic) ->
wx_object:cast(?MODULE, {done, Mnemonic}).
%%% Startup Functions
start_link(Prefs) ->
wx_object:start_link({local, ?MODULE}, ?MODULE, Prefs, []).
init(Prefs) ->
ok = log(info, "GUI starting..."),
Lang = maps:get(lang, Prefs, en_US),
Trans = gd_jt:read_translations(?MODULE),
J = gd_jt:j(Lang, Trans),
WX = wx:new(),
Frame = wxFrame:new(WX, ?wxID_ANY, J("GajuMine Setup")),
MainSz = wxBoxSizer:new(?wxVERTICAL),
ButtonTemplates =
[{new_key, J("Create a New Account")},
{recover, J("Recover Existing Account")}],
MakeButton =
fun({Name, Label}) ->
B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]),
#w{name = Name, id = wxButton:getId(B), wx = B}
end,
Buttons = lists:map(MakeButton, ButtonTemplates),
Add = fun(#w{wx = Button}) -> wxBoxSizer:add(MainSz, Button, zxw:flags(wide)) end,
ok = lists:foreach(Add, Buttons),
ok = wxFrame:setSizer(Frame, MainSz),
ok = wxSizer:layout(MainSz),
ok = wxFrame:connect(Frame, command_button_clicked),
ok = wxFrame:connect(Frame, close_window),
ok = wxFrame:setSize(Frame, {300, 300}),
ok = wxFrame:center(Frame),
true = wxFrame:show(Frame),
State =
#s{wx = WX, frame = Frame,
lang = Lang, j = J,
buttons = Buttons},
{Frame, State}.
handle_call(Unexpected, From, State) ->
ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]),
{noreply, State}.
handle_cast({trouble, Info}, State) ->
ok = handle_troubling(State, Info),
{noreply, State};
handle_cast(done, State = #s{frame = Frame}) ->
ok = gmc_con:end_setup(),
ok = wxWindow:destroy(Frame),
{noreply, State};
handle_cast({done, Mnemonic}, State) ->
ok = done(Mnemonic, State),
{noreply, State};
handle_cast(Unexpected, State) ->
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]),
{noreply, State}.
handle_info(Unexpected, State) ->
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
{noreply, State}.
handle_event(#wx{event = #wxCommand{type = command_button_clicked},
id = ID},
State = #s{buttons = Buttons}) ->
ok =
case lists:keyfind(ID, #w.id, Buttons) of
#w{name = new_key} -> new_key(State);
#w{name = recover} -> recover(State)
end,
{noreply, State};
handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame}) ->
ok = gmc_con:stop(),
ok = wxWindow:destroy(Frame),
{noreply, State};
handle_event(Event, State) ->
ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]),
{noreply, State}.
handle_troubling(#s{frame = Frame}, Info) ->
zxw:show_message(Frame, Info).
code_change(_, State, _) ->
{ok, State}.
terminate(Reason, State) ->
ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]),
wx:destroy().
new_key(State = #s{frame = Frame, j = J}) ->
case new_key(Frame, J) of
{ok, Phrase, Network} ->
gmc_con:make_key(Phrase, Network);
mismatch ->
ok = zxw:show_message(Frame, J("Password entered incorrectly. Try again.")),
new_key(State);
cancel ->
ok
end.
new_key(Frame, J) ->
Label = J("Generate New Account Key"),
Dialog = wxDialog:new(Frame, ?wxID_ANY, Label, [{size, {500, 180}}]),
Sizer = wxBoxSizer:new(?wxVERTICAL),
Network = wxRadioBox:new(Dialog, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, [J("Mainnet"), J("Testnet")]),
PassSz1 = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Create Password")}]),
PassTx1 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]),
_ = wxStaticBoxSizer:add(PassSz1, PassTx1, zxw:flags(wide)),
PassSz2 = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Confirm Password")}]),
PassTx2 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]),
_ = wxStaticBoxSizer:add(PassSz2, PassTx2, zxw:flags(wide)),
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
Affirm = wxButton:new(Dialog, ?wxID_OK),
Cancel = wxButton:new(Dialog, ?wxID_CANCEL),
_ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)),
_ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)),
_ = wxBoxSizer:add(Sizer, Network, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, PassSz1, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, PassSz2, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)),
ok = wxDialog:setSizer(Dialog, Sizer),
ok = wxDialog:setSize(Dialog, {400, 300}),
ok = wxBoxSizer:layout(Sizer),
ok = wxFrame:center(Dialog),
Outcome =
case wxDialog:showModal(Dialog) of
?wxID_OK ->
Net =
case wxRadioBox:getSelection(Network) of
0 -> mainnet;
1 -> testnet;
_ -> mainnet
end,
Pass1 = wxTextCtrl:getValue(PassTx1),
Pass2 = wxTextCtrl:getValue(PassTx2),
case Pass1 =:= Pass2 of
true -> {ok, Pass1, Net};
false -> mismatch
end;
?wxID_CANCEL ->
cancel
end,
ok = wxDialog:destroy(Dialog),
Outcome.
recover(State = #s{frame = Frame, j = J}) ->
case recover(Frame, J) of
{ok, Phrase, Mnemonic, Network} ->
gmc_con:load_key(Phrase, Mnemonic, Network);
mismatch ->
ok = zxw:show_message(Frame, J("Password entered incorrectly. Try again.")),
recover(State);
cancel ->
ok
end.
recover(Frame, J) ->
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Import Account Key")),
Sizer = wxBoxSizer:new(?wxVERTICAL),
Network = wxRadioBox:new(Dialog, ?wxID_ANY, J("Network"), {0, 0}, {50, 50}, [J("Mainnet"), J("Testnet")]),
PassSz1 = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Create Password")}]),
PassTx1 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]),
_ = wxStaticBoxSizer:add(PassSz1, PassTx1, zxw:flags(wide)),
PassSz2 = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Confirm Password")}]),
PassTx2 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]),
_ = wxStaticBoxSizer:add(PassSz2, PassTx2, zxw:flags(wide)),
MnemSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Recovery Phrase")}]),
MnemTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}]),
_ = wxStaticBoxSizer:add(MnemSz, MnemTx, zxw:flags(wide)),
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
Affirm = wxButton:new(Dialog, ?wxID_OK),
Cancel = wxButton:new(Dialog, ?wxID_CANCEL),
_ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)),
_ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)),
_ = wxBoxSizer:add(Sizer, Network, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, PassSz1, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, PassSz2, zxw:flags(base)),
_ = wxBoxSizer:add(Sizer, MnemSz, zxw:flags(wide)),
_ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)),
ok = wxDialog:setSizer(Dialog, Sizer),
ok = wxDialog:setSize(Dialog, {400, 400}),
ok = wxBoxSizer:layout(Sizer),
ok = wxFrame:center(Dialog),
Outcome =
case wxDialog:showModal(Dialog) of
?wxID_OK ->
Pass1 = wxTextCtrl:getValue(PassTx1),
Pass2 = wxTextCtrl:getValue(PassTx2),
case Pass1 =:= Pass2 of
true ->
Net =
case wxRadioBox:getSelection(Network) of
0 -> mainnet;
1 -> testnet;
_ -> mainnet
end,
Mnemonic = wxTextCtrl:getValue(MnemTx),
{ok, Pass1, Mnemonic, Net};
false ->
mismatch
end;
?wxID_CANCEL ->
cancel
end,
ok = wxDialog:destroy(Dialog),
Outcome.
done(Mnemonic, #s{frame = Frame, j = J}) ->
Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Mnemonic")),
Sizer = wxBoxSizer:new(?wxVERTICAL),
MnemSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Recovery Phrase")}]),
Options = [{value, Mnemonic}, {style, ?wxTE_MULTILINE bor ?wxTE_READONLY}],
MnemTx = wxTextCtrl:new(Dialog, ?wxID_ANY, Options),
_ = wxStaticBoxSizer:add(MnemSz, MnemTx, zxw:flags(wide)),
ButtSz = wxBoxSizer:new(?wxHORIZONTAL),
CloseB = wxButton:new(Dialog, ?wxID_CANCEL, [{label, J("Close")}]),
CopyB = wxButton:new(Dialog, ?wxID_OK, [{label, J("Copy to Clipboard")}]),
_ = wxBoxSizer:add(ButtSz, CloseB, zxw:flags(wide)),
_ = wxBoxSizer:add(ButtSz, CopyB, zxw:flags(wide)),
_ = wxBoxSizer:add(Sizer, MnemSz, zxw:flags(wide)),
_ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)),
ok = wxDialog:setSizer(Dialog, Sizer),
ok = wxBoxSizer:layout(Sizer),
ok = wxFrame:center(Dialog),
ok = wxStyledTextCtrl:setFocus(MnemTx),
ok =
case wxDialog:showModal(Dialog) of
?wxID_CANCEL -> ok;
?wxID_OK -> copy_to_clipboard(Mnemonic)
end,
ok = wxDialog:destroy(Dialog),
ok = gmc_con:end_setup(),
ok = wxWindow:destroy(Frame),
ok.
copy_to_clipboard(String) ->
CB = wxClipboard:get(),
case wxClipboard:open(CB) of
true ->
Text = wxTextDataObject:new([{text, String}]),
case wxClipboard:setData(CB, Text) of
true ->
R = wxClipboard:flush(CB),
log(info, "String copied to system clipboard. Flushed: ~p", [R]);
false ->
log(info, "Failed to copy to clipboard")
end,
ok = wxClipboard:close(CB);
false ->
log(info, "Failed to acquire the clipboard.")
end.