Compare commits
No commits in common. "53468659f40c42d00870d03b988f323ba8dbf994" and "b0148289ca2c644b94b1a58f50c7b19e207b1773" have entirely different histories.
53468659f4
...
b0148289ca
@ -3,6 +3,6 @@
|
|||||||
{registered,[]},
|
{registered,[]},
|
||||||
{included_applications,[]},
|
{included_applications,[]},
|
||||||
{applications,[stdlib,kernel]},
|
{applications,[stdlib,kernel]},
|
||||||
{vsn,"0.2.0"},
|
{vsn,"0.1.4"},
|
||||||
{modules,[gajumine,gmc_con,gmc_conf,gmc_gui,gmc_sup]},
|
{modules,[gajumine,gmc_con,gmc_gui,gmc_setup,gmc_sup]},
|
||||||
{mod,{gajumine,[]}}]}.
|
{mod,{gajumine,[]}}]}.
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(gajumine).
|
-module(gajumine).
|
||||||
-vsn("0.2.0").
|
-vsn("0.1.4").
|
||||||
-behavior(application).
|
-behavior(application).
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
-copyright("QPQ AG <craigeverett@qpq.swiss>").
|
-copyright("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
-license("GPL-3.0-or-later").
|
-license("GPL-3.0-or-later").
|
||||||
|
|
||||||
-export([start/2, stop/1, exec_bin_dir/0]).
|
-export([start/2, stop/1, exec_bin_dir/0]).
|
||||||
|
416
src/gmc_con.erl
416
src/gmc_con.erl
@ -3,14 +3,14 @@
|
|||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(gmc_con).
|
-module(gmc_con).
|
||||||
-vsn("0.2.0").
|
-vsn("0.1.4").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
-copyright("QPQ AG <craigeverett@qpq.swiss>").
|
-copyright("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
-license("GPL-3.0-or-later").
|
-license("GPL-3.0-or-later").
|
||||||
|
|
||||||
-behavior(gen_server).
|
-behavior(gen_server).
|
||||||
-export([start_stop/0, gajudesk/0]).
|
-export([start_stop/0, gajudesk/0]).
|
||||||
-export([conf/0, conf/1]).
|
-export([unlock/1, make_key/2, load_key/3, end_setup/0, bin_dir/0]).
|
||||||
-export([network/0]).
|
-export([network/0]).
|
||||||
-export([start_link/0, stop/0]).
|
-export([start_link/0, stop/0]).
|
||||||
-export([init/1, terminate/2, code_change/3,
|
-export([init/1, terminate/2, code_change/3,
|
||||||
@ -23,15 +23,12 @@
|
|||||||
|
|
||||||
|
|
||||||
-record(s,
|
-record(s,
|
||||||
{version = 1 :: integer(),
|
{version = 1 :: integer(),
|
||||||
window = none :: none | wx:wx_object(),
|
window = none :: none | wx:wx_object(),
|
||||||
network = <<"mainnet">> :: binary(), % <<"testnet">> | <<"mainnet">>
|
network = <<"testnet">> :: binary(), % <<"testnet">> | <<"mainnet">>
|
||||||
acc = none :: none | binary(),
|
exec_dir = platform_dir() :: file:filename(),
|
||||||
keys = [] :: [],
|
key = none :: none | {blob, binary()} | #key{},
|
||||||
max_cores = 2 :: pos_integer(),
|
pass = none :: none | binary()}).
|
||||||
max_mem = 3550722201 :: pos_integer(),
|
|
||||||
prefs = #{} :: #{},
|
|
||||||
gmc_conf = none :: none | reference()}).
|
|
||||||
|
|
||||||
-type state() :: #s{}.
|
-type state() :: #s{}.
|
||||||
|
|
||||||
@ -51,21 +48,40 @@ gajudesk() ->
|
|||||||
gen_server:cast(?MODULE, gajudesk).
|
gen_server:cast(?MODULE, gajudesk).
|
||||||
|
|
||||||
|
|
||||||
-spec conf() -> ok.
|
-spec unlock(Phrase) -> ok
|
||||||
|
when Phrase :: string().
|
||||||
|
|
||||||
conf() ->
|
unlock(Phrase) ->
|
||||||
gen_server:cast(?MODULE, conf).
|
gen_server:cast(?MODULE, {unlock, Phrase}).
|
||||||
|
|
||||||
|
|
||||||
-spec conf({Account, Keys, Network, MaxCores, MaxMem}) -> ok
|
-spec make_key(Phrase, Network) -> ok
|
||||||
when Account :: none | binary(), % <<"ak_...">>
|
when Phrase :: string(),
|
||||||
Keys :: [binary()], % [<<"ak_...">>]
|
Network :: mainnet | testnet.
|
||||||
Network :: binary(), % <<"mainnet">> | <<"testnet">> | <<"devnet">>
|
|
||||||
MaxCores :: none | integer(),
|
|
||||||
MaxMem :: none | integer().
|
|
||||||
|
|
||||||
conf(Info) ->
|
make_key(Phrase, Network) ->
|
||||||
gen_server:cast(?MODULE, {conf, Info}).
|
gen_server:cast(?MODULE, {make_key, Phrase, Network}).
|
||||||
|
|
||||||
|
|
||||||
|
-spec load_key(Phrase, Mnemonic, Network) -> ok
|
||||||
|
when Phrase :: string(),
|
||||||
|
Mnemonic :: string(),
|
||||||
|
Network :: mainnet | testnet.
|
||||||
|
|
||||||
|
load_key(Phrase, Mnemonic, Network) ->
|
||||||
|
gen_server:cast(?MODULE, {load_key, Phrase, Mnemonic, Network}).
|
||||||
|
|
||||||
|
|
||||||
|
-spec end_setup() -> ok.
|
||||||
|
|
||||||
|
end_setup() ->
|
||||||
|
gen_server:call(?MODULE, end_setup).
|
||||||
|
|
||||||
|
|
||||||
|
-spec bin_dir() -> file:filename().
|
||||||
|
|
||||||
|
bin_dir() ->
|
||||||
|
gen_server:call(?MODULE, bin_dir).
|
||||||
|
|
||||||
|
|
||||||
-spec network() -> mainnet | testnet | none.
|
-spec network() -> mainnet | testnet | none.
|
||||||
@ -101,54 +117,101 @@ start_link() ->
|
|||||||
|
|
||||||
init(none) ->
|
init(none) ->
|
||||||
ok = log(info, "Starting"),
|
ok = log(info, "Starting"),
|
||||||
{Acc, Keys, Network, MaxCores, MaxMem} =
|
_ = process_flag(sensitive, true),
|
||||||
case read_conf() of
|
case open_wallet() of
|
||||||
{ok, C} ->
|
{blob, Binary} ->
|
||||||
{maps:get(account, C, none),
|
NewState = start_gui(#s{key = {blob, Binary}}),
|
||||||
maps:get(keys, C, []),
|
ok = gmc_gui:ask_passphrase(),
|
||||||
maps:get(network, C, <<"mainnet">>),
|
{ok, NewState};
|
||||||
maps:get(max_cores, C, 2),
|
{ok, Wallet, NetworkID} ->
|
||||||
maps:get(max_mem, C, 3550722201)};
|
NewState = start_gui(#s{key = Wallet, network = NetworkID}),
|
||||||
none ->
|
{ok, NewState};
|
||||||
{none, [], <<"mainnet">>, 2, 3550722201}
|
error ->
|
||||||
|
Window = gmc_setup:start_link(#{}),
|
||||||
|
ok = log(info, "Window: ~p", [Window]),
|
||||||
|
State = #s{window = Window},
|
||||||
|
{ok, State}
|
||||||
|
end.
|
||||||
|
|
||||||
|
open_wallet() ->
|
||||||
|
GDPrefs = gd_read_prefs(),
|
||||||
|
Wallets = gd_get_prefs(wallets, GDPrefs, []),
|
||||||
|
case lists:keyfind(gm_name(), #wr.name, Wallets) of
|
||||||
|
#wr{path = Path, pass = true} ->
|
||||||
|
case file:read_file(Path) of
|
||||||
|
{ok, Binary} -> {blob, Binary};
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
|
#wr{path = Path, pass = false} ->
|
||||||
|
case file:read_file(Path) of
|
||||||
|
{ok, Binary} ->
|
||||||
|
% TODO: Comply with GD's wallet upgrade system
|
||||||
|
{ok, #wallet{keys = Keys, nets = [#net{id = NetID}]}} = zx_lib:b_to_t(Binary),
|
||||||
|
Key = lists:keyfind(gm_name(), #key.name, Keys),
|
||||||
|
{ok, Key, NetID};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
error
|
||||||
|
end.
|
||||||
|
|
||||||
|
platform_dir() ->
|
||||||
|
#{deps := Deps} = zx_daemon:meta(),
|
||||||
|
AppID = lists:keyfind("cuckoo_cpu", 2, Deps),
|
||||||
|
Priv = filename:join(zx_lib:ppath(lib, AppID), "priv"),
|
||||||
|
Dir =
|
||||||
|
case os:type() of
|
||||||
|
{unix, linux} ->
|
||||||
|
"linux_x86_64";
|
||||||
|
{unix, darwin} ->
|
||||||
|
% TODO: Check M2 vs x86
|
||||||
|
"mac_m2";
|
||||||
|
{win32, nt} ->
|
||||||
|
"win_x86_64"
|
||||||
end,
|
end,
|
||||||
State = #s{network = Network, acc = Acc, keys = Keys, max_cores = MaxCores, max_mem = MaxMem},
|
filename:join(Priv, Dir).
|
||||||
{ok, start_gui(State)}.
|
|
||||||
|
|
||||||
|
|
||||||
|
start_gui(State = #s{key = #key{id = ID}}) ->
|
||||||
start_gui(State = #s{acc = none}) ->
|
|
||||||
Window = gmc_gui:start_link(#{}),
|
Window = gmc_gui:start_link(#{}),
|
||||||
ok = gmc_gui:ask_conf(),
|
ok = gmc_gui:set_account(ID),
|
||||||
State#s{window = Window};
|
State#s{window = Window};
|
||||||
start_gui(State = #s{acc = AccID}) ->
|
start_gui(State) ->
|
||||||
Window = gmc_gui:start_link(#{}),
|
Window = gmc_gui:start_link(#{}),
|
||||||
ok = gmc_gui:set_account(AccID),
|
|
||||||
State#s{window = Window}.
|
State#s{window = Window}.
|
||||||
|
|
||||||
|
|
||||||
%%% gen_server Message Handling Callbacks
|
%%% gen_server Message Handling Callbacks
|
||||||
|
|
||||||
|
|
||||||
|
handle_call(end_setup, _, State) ->
|
||||||
|
NewState = end_setup(State),
|
||||||
|
{reply, ok, NewState};
|
||||||
handle_call(network, _, State = #s{network = Network}) ->
|
handle_call(network, _, State = #s{network = Network}) ->
|
||||||
{reply, Network, State};
|
{reply, Network, State};
|
||||||
|
handle_call(bin_dir, _, State = #s{exec_dir = BinDir}) ->
|
||||||
|
{reply, BinDir, State};
|
||||||
handle_call(Unexpected, From, State) ->
|
handle_call(Unexpected, From, State) ->
|
||||||
ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]),
|
ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
|
handle_cast({unlock, Phrase}, State) ->
|
||||||
|
NewState = unlock(Phrase, State),
|
||||||
|
{noreply, NewState};
|
||||||
|
handle_cast({make_key, Phrase, Network}, State) ->
|
||||||
|
NewState = make_key(Phrase, Network, State),
|
||||||
|
{noreply, NewState};
|
||||||
|
handle_cast({load_key, Phrase, Mnemonic, Network}, State) ->
|
||||||
|
NewState = load_key(Phrase, Mnemonic, Network, State),
|
||||||
|
{noreply, NewState};
|
||||||
handle_cast(start_stop, State) ->
|
handle_cast(start_stop, State) ->
|
||||||
ok = do_start_stop(State),
|
ok = do_start_stop(State),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_cast(gajudesk, State) ->
|
handle_cast(gajudesk, State) ->
|
||||||
ok = do_gajudesk(),
|
ok = do_gajudesk(),
|
||||||
{noreply, State};
|
{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) ->
|
handle_cast(stop, State) ->
|
||||||
ok = do_stop(),
|
ok = do_stop(),
|
||||||
ok = log(info, "Received a 'stop' message."),
|
ok = log(info, "Received a 'stop' message."),
|
||||||
@ -161,10 +224,6 @@ handle_cast(Unexpected, State) ->
|
|||||||
handle_info({gproc_ps_event, Event, Data}, State) ->
|
handle_info({gproc_ps_event, Event, Data}, State) ->
|
||||||
ok = gmc_gui:message({Event, Data}),
|
ok = gmc_gui:message({Event, Data}),
|
||||||
{noreply, State};
|
{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) ->
|
handle_info(Unexpected, State) ->
|
||||||
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
|
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
@ -174,9 +233,8 @@ code_change(_, State, _) ->
|
|||||||
{ok, State}.
|
{ok, State}.
|
||||||
|
|
||||||
|
|
||||||
terminate(Reason, State) ->
|
terminate(Reason, _) ->
|
||||||
ok = log(info, "Reason: ~p,", [Reason]),
|
ok = log(info, "Reason: ~p,", [Reason]),
|
||||||
ok = persist(State),
|
|
||||||
case whereis(gd_con) of
|
case whereis(gd_con) of
|
||||||
undefined ->
|
undefined ->
|
||||||
zx:stop();
|
zx:stop();
|
||||||
@ -195,7 +253,92 @@ do_stop() ->
|
|||||||
|
|
||||||
%%% Doers
|
%%% Doers
|
||||||
|
|
||||||
do_start_stop(#s{acc = PubKey, keys = Keys, network = Network, max_cores = MaxCores, max_mem = MaxMem}) ->
|
unlock(Phrase, State = #s{key = {blob, Cipher}}) ->
|
||||||
|
Pass = pass(unicode:characters_to_binary(Phrase)),
|
||||||
|
case decrypt(Pass, Cipher) of
|
||||||
|
{ok, Binary} ->
|
||||||
|
{ok, #wallet{keys = Keys}} = zx_lib:b_to_t(Binary),
|
||||||
|
Name = gm_name(),
|
||||||
|
Unlocked = #key{id = ID, pair = Pair} = lists:keyfind(Name, #key.name, Keys),
|
||||||
|
#{secret := Secret} = Pair,
|
||||||
|
Encrypted = Pair#{secret => encrypt(Pass, Secret)},
|
||||||
|
ok = gmc_gui:set_account(ID),
|
||||||
|
State#s{key = Unlocked#key{pair = Encrypted}, pass = Pass};
|
||||||
|
{error, bad_password} ->
|
||||||
|
ok = gmc_gui:ask_passphrase(),
|
||||||
|
State
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
make_key(Phrase, Network, State) ->
|
||||||
|
{ID, Pair = #{secret := <<K:32/binary, _/binary>>}} = hz_key_master:make_key(<<>>),
|
||||||
|
Mnemonic = hz_key_master:encode(K),
|
||||||
|
ok = gmc_setup:done(Mnemonic),
|
||||||
|
finalize_key(Phrase, ID, Pair, Network, State).
|
||||||
|
|
||||||
|
|
||||||
|
load_key(Phrase, Mnemonic, Network, State) ->
|
||||||
|
case hz_key_master:decode(Mnemonic) of
|
||||||
|
{ok, Secret} ->
|
||||||
|
{ID, Pair} = hz_key_master:make_key(Secret),
|
||||||
|
ok = gmc_setup:done(),
|
||||||
|
finalize_key(Phrase, ID, Pair, Network, State);
|
||||||
|
Error ->
|
||||||
|
ok = gmc_setup:trouble(Error),
|
||||||
|
State
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
finalize_key(Phrase, ID, Pair, Network, State) ->
|
||||||
|
NetBin = atom_to_binary(Network),
|
||||||
|
Pass =
|
||||||
|
case Phrase =:= "" of
|
||||||
|
false -> pass(unicode:characters_to_binary(Phrase));
|
||||||
|
true -> none
|
||||||
|
end,
|
||||||
|
Key = #key{id = ID} = add_wallet(Pass, ID, Pair, NetBin),
|
||||||
|
State#s{key = Key, pass = Pass, network = NetBin}.
|
||||||
|
|
||||||
|
|
||||||
|
add_wallet(Pass, ID, Pair = #{secret := Secret}, Network) ->
|
||||||
|
GDPrefsPath = gd_prefs_path(),
|
||||||
|
GDPrefs =
|
||||||
|
case file:consult(GDPrefsPath) of
|
||||||
|
{ok, Prefs} -> proplists:to_map(Prefs);
|
||||||
|
{error, enoent} -> #{}
|
||||||
|
end,
|
||||||
|
Wallets = gd_get_prefs(wallets, GDPrefs, []),
|
||||||
|
WalletPath = gajumining_wallet(),
|
||||||
|
Created = #key{name = gm_name(), id = ID, pair = Pair},
|
||||||
|
Net = #net{id = Network},
|
||||||
|
ChainID = <<"groot.", Network/binary>>,
|
||||||
|
Node = #node{ip = unicode:characters_to_list(lists:join($., ["groot", Network, "gajumaru", "io"]))},
|
||||||
|
New = #wallet{name = gm_name(),
|
||||||
|
poas = [#poa{name = gm_name(), id = ID}],
|
||||||
|
keys = [Created],
|
||||||
|
chain_id = ChainID,
|
||||||
|
endpoint = Node,
|
||||||
|
nets = [Net]},
|
||||||
|
{WalletBin, KeyPair, HasPass} =
|
||||||
|
case Pass =:= none of
|
||||||
|
false -> {encrypt(Pass, term_to_binary(New)), Pair#{secret => {cipher, encrypt(Pass, Secret)}}, true};
|
||||||
|
true -> {term_to_binary(New), Pair, false}
|
||||||
|
end,
|
||||||
|
Entry = #wr{name = gm_name(), path = WalletPath, pass = HasPass},
|
||||||
|
NewWallets = lists:keystore(gm_name(), #wr.name, Wallets, Entry),
|
||||||
|
NewGDPrefs = gd_put_prefs(wallets, NewWallets, GDPrefs),
|
||||||
|
ok = file:write_file(WalletPath, WalletBin),
|
||||||
|
ok = zx_lib:write_terms(GDPrefsPath, proplists:from_map(NewGDPrefs)),
|
||||||
|
Created#key{pair = KeyPair}.
|
||||||
|
|
||||||
|
|
||||||
|
end_setup(State = #s{window = Window}) ->
|
||||||
|
PID = wx_object:get_pid(Window),
|
||||||
|
true = unlink(PID),
|
||||||
|
start_gui(State#s{window = none}).
|
||||||
|
|
||||||
|
|
||||||
|
do_start_stop(#s{key = #key{id = PubKey}, network = Network}) ->
|
||||||
% smrt guy stuff happens here
|
% smrt guy stuff happens here
|
||||||
{Bits, Eureka} =
|
{Bits, Eureka} =
|
||||||
case Network of
|
case Network of
|
||||||
@ -221,15 +364,14 @@ do_start_stop(#s{acc = PubKey, keys = Keys, network = Network, max_cores = MaxCo
|
|||||||
<<"testnet">> -> {"mean", "generic.exe"}
|
<<"testnet">> -> {"mean", "generic.exe"}
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
Miner = filename:join(platform_dir(), unicode:characters_to_binary([Fatness, Bits, "-", Type])),
|
Miner = unicode:characters_to_binary([Fatness, Bits, "-", Type]),
|
||||||
Count = optimize_count(MaxCores, MaxMem),
|
Count = optimize_count(),
|
||||||
Instance = #{<<"executable">> => Miner},
|
Instance = #{<<"executable">> => Miner, <<"executable_group">> => <<"gajumine">>},
|
||||||
Workers = lists:duplicate(Count, Instance),
|
Workers = lists:duplicate(Count, Instance),
|
||||||
Profile =
|
Profile =
|
||||||
[{pubkey, PubKey},
|
[{pubkey, PubKey},
|
||||||
{workers, Workers},
|
{workers, Workers},
|
||||||
{pool_admin_url, Eureka},
|
{pool_admin_url, Eureka}],
|
||||||
{extra_pubkeys, Keys}],
|
|
||||||
ok = gmc_gui:message({notice, "Starting..."}),
|
ok = gmc_gui:message({notice, "Starting..."}),
|
||||||
ok = gmc_gui:message({notice, ["Miner: ", Miner]}),
|
ok = gmc_gui:message({notice, ["Miner: ", Miner]}),
|
||||||
{ok, Apps} = gmhc_app:start(Profile),
|
{ok, Apps} = gmhc_app:start(Profile),
|
||||||
@ -245,57 +387,33 @@ do_start_stop(#s{acc = PubKey, keys = Keys, network = Network, max_cores = MaxCo
|
|||||||
disconnected],
|
disconnected],
|
||||||
lists:foreach(fun gmhc_events:ensure_subscribed/1, Events).
|
lists:foreach(fun gmhc_events:ensure_subscribed/1, Events).
|
||||||
|
|
||||||
|
optimize_count() ->
|
||||||
optimize_count(MaxC, MaxM) ->
|
{Procs, Memory} =
|
||||||
MapSize = 3550722201,
|
case os:type() of
|
||||||
MaxCores = max(1, MaxC),
|
{unix, linux} ->
|
||||||
MaxMem = max(MapSize, MaxM),
|
{processor, Cores} = hd(erlang:system_info(cpu_topology)),
|
||||||
{Procs, Memory} = proc_mem(),
|
P = length(Cores),
|
||||||
MeanMaps = min(MaxMem, Memory) div MapSize,
|
M = list_to_integer(string:trim(os:cmd("cat /proc/meminfo | grep MemTotal | awk '{print $2}'"))) * 1024,
|
||||||
Recommended = min(MaxCores, max(min(Procs, MeanMaps) - 1, 1)),
|
{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 = Memory div 3550722201,
|
||||||
|
Recommended = max(min(Procs, MeanMaps) - 1, 1),
|
||||||
Notice = fun(F, A) -> gmc_gui:message({notice, io_lib:format(F, A)}) end,
|
Notice = fun(F, A) -> gmc_gui:message({notice, io_lib:format(F, A)}) end,
|
||||||
ok = Notice("Physical Processors: ~p", [Procs]),
|
ok = Notice("Physical Processors: ~p", [Procs]),
|
||||||
ok = Notice("Physical Memory: ~p", [Memory]),
|
ok = Notice("Physical Memory: ~p", [Memory]),
|
||||||
ok = Notice("Max Processor Commit: ~p", [MaxCores]),
|
|
||||||
ok = Notice("Max Memory Commit: ~p", [MaxMem]),
|
|
||||||
ok = Notice("29-bit Mean Map Space: ~p", [MeanMaps]),
|
ok = Notice("29-bit Mean Map Space: ~p", [MeanMaps]),
|
||||||
ok = Notice("Workers: ~p", [Recommended]),
|
ok = Notice("Workers: ~p", [Recommended]),
|
||||||
Recommended.
|
Recommended.
|
||||||
|
|
||||||
|
|
||||||
platform_dir() ->
|
|
||||||
Priv = cuckoo_cpu:priv(),
|
|
||||||
Dir =
|
|
||||||
case os:type() of
|
|
||||||
{unix, linux} ->
|
|
||||||
"linux_x86_64";
|
|
||||||
{unix, darwin} ->
|
|
||||||
% TODO: Check M2 vs x86
|
|
||||||
"mac_m2";
|
|
||||||
{win32, nt} ->
|
|
||||||
"win_x86_64"
|
|
||||||
end,
|
|
||||||
filename:join(Priv, Dir).
|
|
||||||
|
|
||||||
|
|
||||||
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() ->
|
do_gajudesk() ->
|
||||||
ok = tell(info, "Running gajudesk"),
|
ok = tell(info, "Running gajudesk"),
|
||||||
PID = spawn(fun run_gajudesk/0),
|
PID = spawn(fun run_gajudesk/0),
|
||||||
@ -316,56 +434,62 @@ run_gajudesk() ->
|
|||||||
end.
|
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) ->
|
|
||||||
ok = gmc_gui:set_account(AccID),
|
|
||||||
NewState =
|
|
||||||
State#s{network = Network,
|
|
||||||
acc = AccID,
|
|
||||||
keys = Keys,
|
|
||||||
max_cores = MaxCores,
|
|
||||||
max_mem = MaxMem},
|
|
||||||
ok = persist(NewState),
|
|
||||||
NewState.
|
|
||||||
|
|
||||||
|
|
||||||
%%% Utils
|
%%% Utils
|
||||||
|
|
||||||
|
%% Encryption stuff
|
||||||
|
% TODO: Expose these from GD itself
|
||||||
|
|
||||||
|
encrypt(Pass, Binary) ->
|
||||||
|
Flags = [{encrypt, true}, {padding, pkcs_padding}],
|
||||||
|
crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags).
|
||||||
|
|
||||||
|
|
||||||
|
decrypt(Pass, Binary) ->
|
||||||
|
Flags = [{encrypt, false}, {padding, pkcs_padding}],
|
||||||
|
try
|
||||||
|
{ok, crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags)}
|
||||||
|
catch
|
||||||
|
error:{error, L, "Can't finalize"} ->
|
||||||
|
ok = log(info, "Decrypt failed at ~p", [L]),
|
||||||
|
{error, bad_password};
|
||||||
|
E:R ->
|
||||||
|
{E, R}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
pass(Phrase) ->
|
||||||
|
crypto:hash(sha3_256, Phrase).
|
||||||
|
|
||||||
|
|
||||||
%% Paths and Names
|
%% Paths and Names
|
||||||
|
|
||||||
persist(#s{network = Network, acc = Acc, keys = Keys, max_cores = MaxCores, max_mem = MaxMem, prefs = Prefs}) ->
|
gm_name() ->
|
||||||
Update = #{network => Network, account => Acc, keys => Keys, max_cores => MaxCores, max_mem => MaxMem},
|
"GajuMine".
|
||||||
Conf = maps:to_list(maps:merge(Prefs, Update)),
|
|
||||||
Path = gmc_prefs_path(),
|
gajumining_wallet() ->
|
||||||
ok = filelib:ensure_dir(Path),
|
filename:join(zx_lib:path(var, "otpr", "gajudesk"), "gajumining.gaju").
|
||||||
zx_lib:write_terms(Path, Conf).
|
|
||||||
|
|
||||||
|
|
||||||
read_conf() ->
|
gd_prefs_path() ->
|
||||||
Path = gmc_prefs_path(),
|
filename:join(zx_lib:path(etc, "otpr", "gajudesk"), "prefs.eterms").
|
||||||
case file:consult(Path) of
|
|
||||||
{ok, Conf} ->
|
|
||||||
{ok, maps:from_list(Conf)};
|
%% GajuDesk Prefs
|
||||||
{error, enoent} ->
|
% TODO: Glob these into a gd_prefs module exposed from GD itself
|
||||||
none;
|
|
||||||
{error, Other} ->
|
gd_get_prefs(K, M, D) ->
|
||||||
ok = log(info, "Path ~p could not be read. Failed with ~p. Continuing.", [Path, Other]),
|
P = maps:get(gd_con, M, #{}),
|
||||||
none
|
maps:get(K, P, D).
|
||||||
|
|
||||||
|
|
||||||
|
gd_put_prefs(K, V, M) ->
|
||||||
|
P = maps:get(gd_con, M, #{}),
|
||||||
|
NewP = maps:put(K, V, P),
|
||||||
|
maps:put(gd_con, NewP, M).
|
||||||
|
|
||||||
|
|
||||||
|
gd_read_prefs() ->
|
||||||
|
case file:consult(gd_prefs_path()) of
|
||||||
|
{ok, Prefs} -> proplists:to_map(Prefs);
|
||||||
|
{error, enoent} -> #{}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
gmc_prefs_path() ->
|
|
||||||
filename:join(zx_lib:path(etc, "qpq", "gajumine"), "prefs.eterms").
|
|
||||||
|
249
src/gmc_conf.erl
249
src/gmc_conf.erl
@ -1,249 +0,0 @@
|
|||||||
%%% @doc
|
|
||||||
%%% GajuMine Configuration GUI
|
|
||||||
%%% @end
|
|
||||||
|
|
||||||
-module(gmc_conf).
|
|
||||||
-vsn("0.2.0").
|
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
|
||||||
-copyright("QPQ AG <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.
|
|
@ -3,14 +3,14 @@
|
|||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(gmc_gui).
|
-module(gmc_gui).
|
||||||
-vsn("0.2.0").
|
-vsn("0.1.4").
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
-copyright("QPQ AG <craigeverett@qpq.swiss>").
|
-copyright("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
-license("GPL-3.0-or-later").
|
-license("GPL-3.0-or-later").
|
||||||
|
|
||||||
-behavior(wx_object).
|
-behavior(wx_object).
|
||||||
-include_lib("wx/include/wx.hrl").
|
-include_lib("wx/include/wx.hrl").
|
||||||
-export([ask_conf/0, set_account/1, difficulty/1, speed/1, candidate/1, message/1]).
|
-export([ask_passphrase/0, set_account/1, difficulty/1, speed/1, candidate/1, message/1]).
|
||||||
-export([start_link/1]).
|
-export([start_link/1]).
|
||||||
-export([init/1, terminate/2, code_change/3,
|
-export([init/1, terminate/2, code_change/3,
|
||||||
handle_call/3, handle_cast/2, handle_info/2, handle_event/2]).
|
handle_call/3, handle_cast/2, handle_info/2, handle_event/2]).
|
||||||
@ -34,7 +34,7 @@
|
|||||||
candy = none :: none | wx:wx_object(),
|
candy = none :: none | wx:wx_object(),
|
||||||
height = none :: none | wx:wx_object(),
|
height = none :: none | wx:wx_object(),
|
||||||
block = none :: none | wx:wx_object(),
|
block = none :: none | wx:wx_object(),
|
||||||
% solved = 0 :: non_neg_integer(), % TODO: Add a widget to show this. Maybe
|
% solved = 0 :: non_neg_integer(), % Add a widget to show this. Maybe
|
||||||
mess = none :: none | wx:wx_object(),
|
mess = none :: none | wx:wx_object(),
|
||||||
buff = new_buff() :: buff(),
|
buff = new_buff() :: buff(),
|
||||||
buttons = [] :: [#w{}]}).
|
buttons = [] :: [#w{}]}).
|
||||||
@ -55,8 +55,8 @@ new_buff() ->
|
|||||||
|
|
||||||
%%% Interface functions
|
%%% Interface functions
|
||||||
|
|
||||||
ask_conf() ->
|
ask_passphrase() ->
|
||||||
wx_object:cast(?MODULE, ask_conf).
|
wx_object:cast(?MODULE, ask_passphrase).
|
||||||
|
|
||||||
|
|
||||||
set_account(ID) ->
|
set_account(ID) ->
|
||||||
@ -111,8 +111,7 @@ init(Prefs) ->
|
|||||||
[{start_stop, J("Start")},
|
[{start_stop, J("Start")},
|
||||||
{gajudesk, J("Open Wallet")},
|
{gajudesk, J("Open Wallet")},
|
||||||
{eureka, J("GajuMining")},
|
{eureka, J("GajuMining")},
|
||||||
{explorer, J("ChainExplorer")},
|
{explorer, J("ChainExplorer")}],
|
||||||
{conf, J("Configure")}],
|
|
||||||
|
|
||||||
MakeButton =
|
MakeButton =
|
||||||
fun({Name, Label}) ->
|
fun({Name, Label}) ->
|
||||||
@ -168,9 +167,9 @@ handle_call(Unexpected, From, State) ->
|
|||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
|
|
||||||
handle_cast(ask_conf, State) ->
|
handle_cast(ask_passphrase, State) ->
|
||||||
NewState = conf(State),
|
ok = ask_passphrase(State),
|
||||||
{noreply, NewState};
|
{noreply, State};
|
||||||
handle_cast({set_account, ID}, State) ->
|
handle_cast({set_account, ID}, State) ->
|
||||||
ok = set_account(ID, State),
|
ok = set_account(ID, State),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
@ -204,7 +203,6 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID},
|
|||||||
#w{name = gajudesk} -> gajudesk(State);
|
#w{name = gajudesk} -> gajudesk(State);
|
||||||
#w{name = eureka} -> eureka(State);
|
#w{name = eureka} -> eureka(State);
|
||||||
#w{name = explorer} -> explorer(State);
|
#w{name = explorer} -> explorer(State);
|
||||||
#w{name = conf} -> conf(State);
|
|
||||||
false -> State
|
false -> State
|
||||||
end,
|
end,
|
||||||
{noreply, NewState};
|
{noreply, NewState};
|
||||||
@ -225,6 +223,34 @@ terminate(Reason, State) ->
|
|||||||
wx:destroy().
|
wx:destroy().
|
||||||
|
|
||||||
|
|
||||||
|
ask_passphrase(#s{frame = Frame, j = J}) ->
|
||||||
|
Label = J("Unlock Account"),
|
||||||
|
Dialog = wxDialog:new(Frame, ?wxID_ANY, Label, [{size, {500, 115}}]),
|
||||||
|
Sizer = wxBoxSizer:new(?wxVERTICAL),
|
||||||
|
PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Password")}]),
|
||||||
|
PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]),
|
||||||
|
_ = wxStaticBoxSizer:add(PassSz, PassTx, 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, PassSz, zxw:flags(base)),
|
||||||
|
_ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)),
|
||||||
|
ok = wxDialog:setSizer(Dialog, Sizer),
|
||||||
|
ok = wxBoxSizer:layout(Sizer),
|
||||||
|
ok = wxFrame:center(Dialog),
|
||||||
|
ok =
|
||||||
|
case wxDialog:showModal(Dialog) of
|
||||||
|
?wxID_OK ->
|
||||||
|
Phrase = wxTextCtrl:getValue(PassTx),
|
||||||
|
gmc_con:unlock(Phrase);
|
||||||
|
?wxID_CANCEL ->
|
||||||
|
retire(Frame)
|
||||||
|
end,
|
||||||
|
wxDialog:destroy(Dialog).
|
||||||
|
|
||||||
|
|
||||||
set_account(ID, #s{id = Widget}) ->
|
set_account(ID, #s{id = Widget}) ->
|
||||||
wxStaticText:setLabel(Widget, ID).
|
wxStaticText:setLabel(Widget, ID).
|
||||||
|
|
||||||
@ -338,9 +364,7 @@ add_message2(Entry, {OMax, 0, IMax, 0, []}) ->
|
|||||||
{append, {OMax, 1, IMax, 1, [[Entry]]}}.
|
{append, {OMax, 1, IMax, 1, [[Entry]]}}.
|
||||||
|
|
||||||
|
|
||||||
start_stop(State = #s{buttons = Buttons}) ->
|
start_stop(State) ->
|
||||||
#w{wx = SSB} = lists:keyfind(start_stop, #w.name, Buttons),
|
|
||||||
_ = wxButton:disable(SSB),
|
|
||||||
ok = gmc_con:start_stop(),
|
ok = gmc_con:start_stop(),
|
||||||
State.
|
State.
|
||||||
|
|
||||||
@ -379,11 +403,6 @@ explorer_url() ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
conf(State) ->
|
|
||||||
ok = gmc_con:conf(),
|
|
||||||
State.
|
|
||||||
|
|
||||||
|
|
||||||
open_browser(Frame, J, URL) ->
|
open_browser(Frame, J, URL) ->
|
||||||
case wx_misc:launchDefaultBrowser(URL) of
|
case wx_misc:launchDefaultBrowser(URL) of
|
||||||
true ->
|
true ->
|
||||||
|
327
src/gmc_setup.erl
Normal file
327
src/gmc_setup.erl
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
%%% @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.1.4").
|
||||||
|
-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, 250}),
|
||||||
|
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.
|
||||||
|
|
@ -12,10 +12,10 @@
|
|||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(gmc_sup).
|
-module(gmc_sup).
|
||||||
-vsn("0.2.0").
|
-vsn("0.1.4").
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
-author("Craig Everett <craigeverett@qpq.swiss>").
|
-author("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
-copyright("QPQ AG <craigeverett@qpq.swiss>").
|
-copyright("Craig Everett <craigeverett@qpq.swiss>").
|
||||||
-license("GPL-3.0-or-later").
|
-license("GPL-3.0-or-later").
|
||||||
|
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
|
18
zomp.meta
18
zomp.meta
@ -1,18 +1,18 @@
|
|||||||
{name,"GajuMine"}.
|
{name,"GajuMine"}.
|
||||||
{type,gui}.
|
{type,gui}.
|
||||||
{modules,[]}.
|
{modules,[]}.
|
||||||
{prefix,"gmc"}.
|
|
||||||
{author,"Craig Everett"}.
|
{author,"Craig Everett"}.
|
||||||
|
{prefix,"gmc"}.
|
||||||
{desc,"Mining client for the Gajumaru Root"}.
|
{desc,"Mining client for the Gajumaru Root"}.
|
||||||
{package_id,{"qpq","gajumine",{0,2,0}}}.
|
{package_id,{"qpq","gajumine",{0,1,4}}}.
|
||||||
{deps,[{"qpq","cuckoo_cpu",{0,3,0}},
|
{deps,[{"uwiger","gmconfig",{0,1,2}},
|
||||||
{"uwiger","gmhive_client",{0,4,0}},
|
|
||||||
{"uwiger","gmcuckoo",{1,1,1}},
|
|
||||||
{"uwiger","gmhive_worker",{0,3,0}},
|
|
||||||
{"uwiger","gmconfig",{0,1,2}},
|
|
||||||
{"uwiger","gproc",{1,0,1}},
|
{"uwiger","gproc",{1,0,1}},
|
||||||
|
{"uwiger","gmhive_client",{0,2,1}},
|
||||||
|
{"qpq","cuckoo_cpu",{0,2,0}},
|
||||||
|
{"uwiger","gmhive_worker",{0,1,1}},
|
||||||
{"uwiger","gmhive_protocol",{0,1,1}},
|
{"uwiger","gmhive_protocol",{0,1,1}},
|
||||||
{"uwiger","enoise",{1,3,0}},
|
{"uwiger","enoise",{1,3,0}},
|
||||||
|
{"uwiger","gmcuckoo",{1,1,0}},
|
||||||
{"uwiger","setup",{2,2,4}},
|
{"uwiger","setup",{2,2,4}},
|
||||||
{"otpr","hakuzaru",{0,6,1}},
|
{"otpr","hakuzaru",{0,6,1}},
|
||||||
{"otpr","gajudesk",{0,5,3}},
|
{"otpr","gajudesk",{0,5,3}},
|
||||||
@ -28,9 +28,9 @@
|
|||||||
{key_name,none}.
|
{key_name,none}.
|
||||||
{a_email,"craigeverett@qpq.swiss"}.
|
{a_email,"craigeverett@qpq.swiss"}.
|
||||||
{c_email,"craigeverett@qpq.swiss"}.
|
{c_email,"craigeverett@qpq.swiss"}.
|
||||||
{copyright,"QPQ AG"}.
|
{copyright,"Craig Everett"}.
|
||||||
{file_exts,[]}.
|
{file_exts,[]}.
|
||||||
{license,"GPL-3.0-or-later"}.
|
{license,"GPL-3.0-or-later"}.
|
||||||
{repo_url,"https://git.qpq.swiss/zxq9/GajuMine"}.
|
{repo_url,"https://git.qpq.swiss/zxq9/GajuMine"}.
|
||||||
{tags,["qpq","gaju","gajumaru","hive","mining","crypto"]}.
|
{tags,[]}.
|
||||||
{ws_url,"https://gajumining.com"}.
|
{ws_url,"https://gajumining.com"}.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user