52 Commits

Author SHA1 Message Date
zxq9 cee09ebf58 Merge pull request 'Deps update' (#19) from deps-0.4.2 into master
Reviewed-on: #19
2025-12-03 09:34:31 +09:00
zxq9 9495b606b4 Deps update 2025-12-03 09:35:03 +09:00
zxq9 3fb82b59a8 Merge branch 'master' into uw-is_key-type-error 2025-11-21 11:57:57 +09:00
zxq9 eb6a94a169 Merge pull request 'Fix key check in config' (#18) from fixcheck into master
Reviewed-on: #18
2025-11-21 11:50:32 +09:00
zxq9 54802cf607 Fix key check in config 2025-11-21 11:50:41 +09:00
Ulf Wiger b1e649961c Correct type error in gmc_conf:is_key/1 2025-11-20 20:35:32 +01:00
zxq9 68cc86723d Merge pull request 'iface' (#16) from iface into master
Reviewed-on: #16
2025-11-20 16:02:06 +09:00
zxq9 30dda02cc4 MacOS works now 2025-11-20 10:39:26 +09:00
zxq9 8ee712e81a Windows works! 2025-11-20 10:04:19 +09:00
zxq9 4b6a4372d9 Fix wallet launch 2025-11-20 09:10:52 +09:00
zxq9 5b95daa27e Interface update. v0.4.0 2025-11-20 00:52:19 +09:00
zxq9 54a70d4b0b WIP 2025-11-19 21:17:57 +09:00
zxq9 6d0d371d5d Update deps 2025-11-07 19:56:00 +09:00
zxq9 a47a259135 Fixing interface issues and GajuDesk startup call 2025-11-07 19:53:16 +09:00
zxq9 c6ab147d97 Move to gmhive_client-0.9.3 2025-10-25 17:28:45 +09:00
zxq9 c421ee1785 Update gmhive_client 2025-10-25 09:31:01 +09:00
zxq9 a5dea54919 usort additional keys list 2025-10-15 21:01:25 +09:00
zxq9 159d522522 Deps update + patch verup 2025-10-15 20:56:35 +09:00
zxq9 97c546610d Verup deps, and start listening for gproc messages at startup 2025-10-15 15:52:46 +09:00
zxq9 cb1d879b99 Handle whitespace around account ID 2025-10-14 21:55:04 +09:00
zxq9 66a06480d5 Merge pull request 'Match on more messages, verup dependencies/' (#11) from deps-0.3.1 into master
Reviewed-on: #11
Reviewed-by: Jarvis Carroll <jarviscarrol@qpq.swiss>
2025-09-24 15:46:18 +09:00
zxq9 33d6edaa42 Updating gmhive_client one more time... 2025-09-24 15:45:41 +09:00
zxq9 86ac9be07d Match on more messages, verup dependencies/ 2025-09-23 19:24:57 +09:00
zxq9 9b88ac7a90 Add autostart argument 2025-08-14 11:11:00 +09:00
zxq9 ff538018c5 verup 2025-08-14 10:47:51 +09:00
zxq9 56be96c4f3 Update system arch discovery method 2025-08-14 10:41:52 +09:00
zxq9 5e3e8c9eae Make version update stick 2025-08-12 09:54:47 +09:00
zxq9 ecf597eed2 Update cuckoo_cpu dep 2025-08-12 09:48:31 +09:00
zxq9 0ec8109684 Merge pull request 'Fix mac path' (#9) from mac-path into master
Reviewed-on: #9
2025-08-12 08:22:18 +09:00
zxq9 ca0d48b498 Fix mac path 2025-08-12 08:17:08 +09:00
zxq9 7eaacaddce Patch fix to make Windows use generic until we fix AVX2 2025-08-11 17:43:08 +09:00
zxq9 be63fe141b Merge pull request 'naive-arch2' (#8) from naive-arch2 into master
Reviewed-on: #8
Reviewed-by: Jarvis Carroll <jarviscarrol@qpq.swiss>
2025-08-11 15:18:03 +09:00
zxq9 06907a6c8b Update deps 2025-08-11 14:44:10 +09:00
zxq9 7feea1421b make init more robust 2025-08-11 14:34:51 +09:00
zxq9 5d4fd6a2ab Merge pull request 'Add Mac arch detection (relatively naive)' (#7) from naive-arch2 into master
Reviewed-on: #7
Reviewed-by: Jarvis Carroll <jarviscarrol@qpq.swiss>
2025-08-11 12:19:21 +09:00
zxq9 35842b8ac7 Add Mac arch detection (relatively naive) 2025-08-08 13:00:00 +09:00
zxq9 6795c187c3 Drop test export 2025-06-18 23:04:38 +09:00
zxq9 09ec2a8e0d Oops 2025-06-18 23:02:31 +09:00
zxq9 a9581291aa Make Windows memory inspection more robust 2025-06-18 22:41:11 +09:00
zxq9 2938686b1c Update deps to use new gmhive_client 2025-06-12 21:33:11 +09:00
zxq9 bd1c779d09 Change how we discover memory on Windows 2025-06-12 20:31:52 +09:00
zxq9 002196059e Fix configuration crashes 2025-06-12 18:04:36 +09:00
zxq9 e86a130180 Add version to title, update deps to memory fix 2025-05-28 21:22:03 +09:00
zxq9 53468659f4 Merge pull request 'config' (#2) from config into master
Reviewed-on: #2
2025-05-27 23:15:19 +09:00
zxq9 104f303a8a Add tags, fix default notices 2025-05-27 22:48:40 +09:00
zxq9 558c2d6920 Ensure the madness of Windows console noises do not plague the ears of the innocent 2025-05-27 22:27:04 +09:00
zxq9 7f2b3c6a45 Fix config key 2025-05-27 22:08:12 +09:00
zxq9 f0326f4b99 Fix cuckoo_cpu priv dir lookup 2025-05-27 21:55:47 +09:00
zxq9 74c7f2bb9c Update deps 2025-05-27 21:03:50 +09:00
zxq9 562f86e078 Test candidate 1 2025-05-27 20:56:00 +09:00
zxq9 f901ba6c9d WIP: Adding config interface 2025-05-27 20:12:23 +09:00
zxq9 bccabcb1c0 WIP: Configuration interface 2025-05-26 16:34:21 +09:00
8 changed files with 674 additions and 718 deletions
+2 -2
View File
@@ -3,6 +3,6 @@
{registered,[]}, {registered,[]},
{included_applications,[]}, {included_applications,[]},
{applications,[stdlib,kernel]}, {applications,[stdlib,kernel]},
{vsn,"0.1.4"}, {vsn,"0.4.2"},
{modules,[gajumine,gmc_con,gmc_gui,gmc_setup,gmc_sup]}, {modules,[gajumine,gmc_con,gmc_conf,gmc_gui,gmc_sup]},
{mod,{gajumine,[]}}]}. {mod,{gajumine,[]}}]}.
+3 -2
View File
@@ -3,10 +3,10 @@
%%% @end %%% @end
-module(gajumine). -module(gajumine).
-vsn("0.1.4"). -vsn("0.4.2").
-behavior(application). -behavior(application).
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("Craig Everett <craigeverett@qpq.swiss>"). -copyright("QPQ AG <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]).
@@ -29,6 +29,7 @@ start(normal, _Args) ->
{ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(ssl),
ok = application:ensure_started(hakuzaru), ok = application:ensure_started(hakuzaru),
ok = application:ensure_started(zxwidgets), ok = application:ensure_started(zxwidgets),
ok = application:ensure_started(gproc),
gmc_sup:start_link(). gmc_sup:start_link().
+256 -300
View File
@@ -3,14 +3,14 @@
%%% @end %%% @end
-module(gmc_con). -module(gmc_con).
-vsn("0.1.4"). -vsn("0.4.2").
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("Craig Everett <craigeverett@qpq.swiss>"). -copyright("QPQ AG <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([unlock/1, make_key/2, load_key/3, end_setup/0, bin_dir/0]). -export([conf/0, conf/1]).
-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,12 +23,16 @@
-record(s, -record(s,
{version = 1 :: integer(), {version = 1 :: integer(),
window = none :: none | wx:wx_object(), window = none :: none | wx:wx_object(),
network = <<"testnet">> :: binary(), % <<"testnet">> | <<"mainnet">> network = <<"mainnet">> :: binary(), % <<"testnet">> | <<"mainnet">>
exec_dir = platform_dir() :: file:filename(), acc = none :: none | binary(),
key = none :: none | {blob, binary()} | #key{}, keys = [] :: [],
pass = none :: none | binary()}). max_cores = 2 :: pos_integer(),
max_mem = 3550722201 :: pos_integer(),
prefs = #{} :: #{},
gmc_conf = none :: none | reference(),
toggle = {stop, 0} :: {start | stop, integer()}}).
-type state() :: #s{}. -type state() :: #s{}.
@@ -48,40 +52,21 @@ gajudesk() ->
gen_server:cast(?MODULE, gajudesk). gen_server:cast(?MODULE, gajudesk).
-spec unlock(Phrase) -> ok -spec conf() -> ok.
when Phrase :: string().
unlock(Phrase) -> conf() ->
gen_server:cast(?MODULE, {unlock, Phrase}). gen_server:cast(?MODULE, conf).
-spec make_key(Phrase, Network) -> ok -spec conf({Account, Keys, Network, MaxCores, MaxMem}) -> ok
when Phrase :: string(), when Account :: none | binary(), % <<"ak_...">>
Network :: mainnet | testnet. Keys :: [binary()], % [<<"ak_...">>]
Network :: binary(), % <<"mainnet">> | <<"testnet">> | <<"devnet">>
MaxCores :: none | integer(),
MaxMem :: none | integer().
make_key(Phrase, Network) -> conf(Info) ->
gen_server:cast(?MODULE, {make_key, Phrase, Network}). gen_server:cast(?MODULE, {conf, Info}).
-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.
@@ -117,101 +102,79 @@ start_link() ->
init(none) -> init(none) ->
ok = log(info, "Starting"), ok = log(info, "Starting"),
_ = process_flag(sensitive, true), {AProcs, AMem} = proc_mem(),
case open_wallet() of TwoGraphs = default_spec(mem) * 2,
{blob, Binary} -> RProcs =
NewState = start_gui(#s{key = {blob, Binary}}), case AProcs >= 2 of
ok = gmc_gui:ask_passphrase(), true -> 2;
{ok, NewState}; false -> 1
{ok, Wallet, NetworkID} ->
NewState = start_gui(#s{key = Wallet, network = NetworkID}),
{ok, NewState};
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,
filename:join(Priv, Dir). RMem =
case AMem >= TwoGraphs of
true -> TwoGraphs;
false -> default_spec(mem)
end,
{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(max_cores, C, RProcs),
maps:get(max_mem, C, RMem)};
none ->
{none, [], <<"mainnet">>, RProcs, RMem}
end,
State = #s{network = Network, acc = Acc, keys = Keys, max_cores = MaxCores, max_mem = MaxMem},
Events =
[pool_notification,
connected,
puzzle,
result,
error,
disconnected],
ok = lists:foreach(fun gmhc_events:ensure_subscribed/1, Events),
{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:set_account(ID), ok = gmc_gui:ask_conf(),
State#s{window = Window}; State#s{window = Window};
start_gui(State) -> start_gui(State = #s{acc = AccID}) ->
Window = gmc_gui:start_link(#{}), Window = gmc_gui:start_link(#{}),
ok = gmc_gui:set_account(AccID),
ok =
case lists:member("autostart", zx_daemon:argv()) of
false -> ok;
true -> gmc_gui:start_stop()
end,
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), NewState = do_start_stop(State),
{noreply, State}; {noreply, NewState};
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."),
@@ -222,19 +185,32 @@ 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 = gproc_ps_event({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}.
gproc_ps_event(Info = {disconnected, #{info := #{reconnecting := false, error := #{code := Code, message := Message}}}}) ->
ok = log(error, "Error terminal event received. Code: ~p. Message: ~p", [Code, Message]),
ok = gmc_gui:message(Info),
gmc_gui:start_stop();
gproc_ps_event(Info) ->
gmc_gui:message(Info).
code_change(_, State, _) -> code_change(_, State, _) ->
{ok, State}. {ok, State}.
terminate(Reason, _) -> terminate(Reason, State) ->
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();
@@ -245,100 +221,25 @@ terminate(Reason, _) ->
do_stop() -> do_stop() ->
case is_pid(whereis(gd_con)) of zx:stop().
false -> zx:stop();
true -> application:stop(gajumine)
end.
%%% Doers %%% Doers
unlock(Phrase, State = #s{key = {blob, Cipher}}) -> do_start_stop(State = #s{toggle = {Last, TS}}) ->
Pass = pass(unicode:characters_to_binary(Phrase)), Now = erlang:system_time(second),
case decrypt(Pass, Cipher) of Clicklimit = Now - 1,
{ok, Binary} -> case TS < Clicklimit of
{ok, #wallet{keys = Keys}} = zx_lib:b_to_t(Binary), true ->
Name = gm_name(), case Last of
Unlocked = #key{id = ID, pair = Pair} = lists:keyfind(Name, #key.name, Keys), stop -> do_start(State#s{toggle = {start, Now}});
#{secret := Secret} = Pair, start -> do_stop(State#s{toggle = {stop, Now}})
Encrypted = Pair#{secret => encrypt(Pass, Secret)}, end;
ok = gmc_gui:set_account(ID), false -> State
State#s{key = Unlocked#key{pair = Encrypted}, pass = Pass};
{error, bad_password} ->
ok = gmc_gui:ask_passphrase(),
State
end. end.
make_key(Phrase, Network, State) -> do_start(State = #s{acc = PubKey, keys = Keys, network = Network, max_cores = MaxCores, max_mem = MaxMem}) ->
{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
@@ -360,136 +261,191 @@ do_start_stop(#s{key = #key{id = PubKey}, network = Network}) ->
% Check avx2. % Check avx2.
% Both should be provided by the F# start program % Both should be provided by the F# start program
case Network of case Network of
<<"mainnet">> -> {"mean", "avx2.exe"}; % <<"mainnet">> -> {"mean", "avx2.exe"};
<<"mainnet">> -> {"mean", "generic.exe"};
<<"testnet">> -> {"mean", "generic.exe"} <<"testnet">> -> {"mean", "generic.exe"}
end end
end, end,
Miner = unicode:characters_to_binary([Fatness, Bits, "-", Type]), Miner = filename:join(platform_dir(), unicode:characters_to_binary([Fatness, Bits, "-", Type])),
Count = optimize_count(), Count = optimize_count(MaxCores, MaxMem),
Instance = #{<<"executable">> => Miner, <<"executable_group">> => <<"gajumine">>}, Instance = #{<<"executable">> => Miner},
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),
Started = io_lib:format("Apps started: ~p", [Apps]), Started = io_lib:format("Apps started:~n ~p", [Apps]),
ok = log(info, Started), ok = log(info, Started),
ok = gmc_gui:message({notice, Started}), ok = gmc_gui:message({notice, Started}),
Events = State.
[pool_notification,
connected,
puzzle,
result,
error,
disconnected],
lists:foreach(fun gmhc_events:ensure_subscribed/1, Events).
optimize_count() ->
{Procs, Memory} = do_stop(State) ->
case os:type() of ok = gmc_gui:message({notice, "Stopping."}),
{unix, linux} -> ok =
{processor, Cores} = hd(erlang:system_info(cpu_topology)), case application:stop(gmhive_client) of
P = length(Cores), ok -> ok;
M = list_to_integer(string:trim(os:cmd("cat /proc/meminfo | grep MemTotal | awk '{print $2}'"))) * 1024, Error -> log(warning, "application:stop(gmhive_client) returned: ~p", [Error])
{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, end,
MeanMaps = Memory div 3550722201, State.
Recommended = max(min(Procs, MeanMaps) - 1, 1),
optimize_count(MaxC, MaxM) ->
GraphSize = 3550722201,
MaxCores = max(1, MaxC),
MaxMem = max(GraphSize, MaxM),
{Procs, Memory} = proc_mem(),
MeanGraphs = min(MaxMem, Memory) div GraphSize,
Recommended = min(MaxCores, min(Procs, MeanGraphs)),
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("29-bit Mean Map Space: ~p", [MeanMaps]), ok = Notice("Max Processor Commit: ~p", [MaxCores]),
ok = Notice("Max Memory Commit: ~p", [MaxMem]),
ok = Notice("29-bit Mean Graph Space: ~p", [MeanGraphs]),
ok = Notice("Workers: ~p", [Recommended]), ok = Notice("Workers: ~p", [Recommended]),
Recommended. Recommended.
do_gajudesk() -> platform_dir() ->
ok = tell(info, "Running gajudesk"), Priv = cuckoo_cpu:priv(),
PID = spawn(fun run_gajudesk/0), Dir =
tell(info, "GajuDesk launched at PID: ~p", [PID]). case os:type() of
{unix, linux} ->
"linux_x86_64";
{unix, darwin} ->
case string:trim(os:cmd("uname -m")) of
"arm64" ->
"mac_arm64";
"x86_64" ->
"mac_x86_64";
Other ->
ok = log(info, "uname -m: ~p", [Other]),
"mac_x86_64"
end;
{win32, nt} ->
"win_x86_64"
end,
filename:join(Priv, Dir).
run_gajudesk() ->
R = "otpr", proc_mem() ->
N = "gajudesk", case os:type() of
{ok, V} = zx:latest({R, N}), {unix, linux} ->
{ok, PackageString} = zx_lib:package_string({R, N, V}), {processor, Cores} = hd(erlang:system_info(cpu_topology)),
try P = length(Cores),
case zx:run(PackageString, []) of M = s_to_i(mem, string:trim(os:cmd("cat /proc/meminfo | grep MemTotal | awk '{print $2}'"))) * 1024,
ok -> ok; {P, M};
Error -> tell(error, "gajudesk died with: ~p", [Error]) {unix, darwin} ->
end P = s_to_i(cpu, string:trim(os:cmd("sysctl -n hw.physicalcpu"))),
catch M = s_to_i(mem, string:trim(os:cmd("sysctl -n hw.memsize"))),
E:R -> tell(error, "gajudesk died with: ~p", [{E, R}]) {P, M};
{win32, nt} ->
P = s_to_i(cpu, os:getenv("NUMBER_OF_PROCESSORS")),
M = win_mem(),
{P, M}
end. end.
s_to_i(Type, "") ->
default_spec(Type);
s_to_i(Type, String) ->
case is_0_9(String) of
true -> list_to_integer(String);
false -> default_spec(Type)
end.
is_0_9("") ->
true;
is_0_9([H | T]) when $0 =< H, H =< $9 ->
is_0_9(T);
is_0_9(_) ->
false.
default_spec(cpu) -> 1;
default_spec(mem) -> 3550722201.
win_mem() ->
Out = os:cmd("powershell -Command \"(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory\""),
s_to_i(mem, lists:last(string:split(string:trim(Out), "\r\n", all))).
do_gajudesk() ->
ok = log(info, "Running gajudesk"),
GajuDesk = "zx run gajudesk",
Command =
case os:type() of
{unix, darwin} -> detach_darwin(GajuDesk);
{unix, _} -> detach_unix(GajuDesk);
{win32, nt} -> detach_windows(GajuDesk)
end,
Out = os:cmd(Command),
log(info, "os:cmd(~s) -> ~s", [Command, Out]).
detach_darwin(Command) ->
"nohup " ++ Command ++ " >/dev/null 2>&1 & disown".
detach_unix(Command) ->
"setsid sh -c 'exec nohup " ++ Command ++ " >/dev/null 2>&1' &".
detach_windows(Command) ->
PSCmd = "Start-Process -WindowStyle Hidden -FilePath cmd.exe -ArgumentList '/c " ++ Command ++ "'",
"powershell -NoProfile -Command \"" ++ PSCmd ++ "\"".
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
gm_name() -> persist(#s{network = Network, acc = Acc, keys = Keys, max_cores = MaxCores, max_mem = MaxMem, prefs = Prefs}) ->
"GajuMine". Update = #{network => Network, account => Acc, keys => Keys, max_cores => MaxCores, max_mem => MaxMem},
Conf = maps:to_list(maps:merge(Prefs, Update)),
gajumining_wallet() -> Path = gmc_prefs_path(),
filename:join(zx_lib:path(var, "otpr", "gajudesk"), "gajumining.gaju"). ok = filelib:ensure_dir(Path),
zx_lib:write_terms(Path, Conf).
gd_prefs_path() -> read_conf() ->
filename:join(zx_lib:path(etc, "otpr", "gajudesk"), "prefs.eterms"). Path = gmc_prefs_path(),
case file:consult(Path) of
{ok, Conf} ->
%% GajuDesk Prefs {ok, maps:from_list(Conf)};
% TODO: Glob these into a gd_prefs module exposed from GD itself {error, enoent} ->
none;
gd_get_prefs(K, M, D) -> {error, Other} ->
P = maps:get(gd_con, M, #{}), ok = log(info, "Path ~p could not be read. Failed with ~p. Continuing.", [Path, Other]),
maps:get(K, P, D). none
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").
+280
View File
@@ -0,0 +1,280 @@
%%% @doc
%%% GajuMine Configuration GUI
%%% @end
-module(gmc_conf).
-vsn("0.4.2").
-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 = 2 :: pos_integer(),
memory = 3550722201 :: pos_integer()}).
%%% 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),
Panel = wxWindow:new(Frame, ?wxID_ANY),
TopSz = wxBoxSizer:new(?wxVERTICAL),
_ = wxBoxSizer:add(TopSz, Panel, zxw:flags(wide)),
MainSz = wxBoxSizer:new(?wxVERTICAL),
AccSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("GajuMining Account ID")}]),
AccTx = wxTextCtrl:new(Panel, ?wxID_ANY, [{value, acc(Acc)}]),
_ = wxStaticBoxSizer:add(AccSz, AccTx, wide(5)),
KeysSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("Additional Account IDs (optional)")}]),
KeysTx = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, ?wxTE_MULTILINE}, {value, keys(Keys)}]),
_ = wxStaticBoxSizer:add(KeysSz, KeysTx, wide(5)),
StatSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("Max System Committment (optional)")}]),
AProcsS = i_to_l(AProcs),
MProcsS = i_to_l(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(Panel, Labels),
Network = wxRadioBox:new(Panel, ?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(Panel, ?wxID_OK),
Cancel = wxButton:new(Panel, ?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 = wxWindow:setSizer(Panel, MainSz),
ok = wxFrame:setSizer(Frame, TopSz),
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}.
i_to_l(none) -> "0";
i_to_l(N) when is_integer(N) -> integer_to_list(N).
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(none) ->
"0";
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 = string:trim(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(AccID, MOAR_IDs), cores(CoreS), bytes(GigsS)}),
buh_bye(State).
% NOTE: 32 is space, 12288 is full-width space.
binify_keys(AccID, MOAR) ->
Unwashed = lists:usort([K || K <- string:lexemes(MOAR, [$\r, $\n, 32, 12288, $\t, $,, $;])]),
Scrubbed = lists:delete(AccID, Unwashed),
Keys =
case lists:partition(fun is_key/1, Scrubbed) of
{Cleaned, []} ->
Cleaned;
{Cleaned, Trash} ->
Message = io_lib:format("The following keys are invalid:~n~p", [Trash]),
ok = gmc_gui:message({notice, Message}),
Cleaned
end,
lists:map(fun list_to_binary/1, Keys).
is_key(Mystery) ->
try
MysteryBin = list_to_binary(Mystery),
{account_pubkey, _} = gmser_api_encoder:decode(MysteryBin),
true
catch
_:_ -> false
end.
cores("") ->
2;
cores(CoreS) ->
try
list_to_integer(CoreS)
catch
_:_ -> 2
end.
bytes("") ->
3550722201;
bytes(GigsS) ->
try
list_to_integer(GigsS) * gig()
catch
_:_ ->
try
trunc(list_to_float(GigsS) * gig())
catch
_:_ -> 3550722201
end
end.
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.
+117 -71
View File
@@ -3,14 +3,14 @@
%%% @end %%% @end
-module(gmc_gui). -module(gmc_gui).
-vsn("0.1.4"). -vsn("0.4.2").
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("Craig Everett <craigeverett@qpq.swiss>"). -copyright("QPQ AG <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_passphrase/0, set_account/1, difficulty/1, speed/1, candidate/1, message/1]). -export([ask_conf/0, set_account/1, difficulty/1, speed/1, candidate/1, message/1, start_stop/0]).
-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]).
@@ -23,21 +23,23 @@
wx = none :: none | wx:wx_object()}). wx = none :: none | wx:wx_object()}).
-record(s, -record(s,
{wx = none :: none | wx:wx_object(), {wx = none :: none | wx:wx_object(),
frame = none :: none | wx:wx_object(), frame = none :: none | wx:wx_object(),
lang = en :: en | jp, lang = en :: en | jp,
j = none :: none | fun(), j = none :: none | fun(),
id = none :: none | wx:wx_object(), id = none :: none | wx:wx_object(),
diff = none :: none | wx:wx_object(), diff = none :: none | wx:wx_object(),
perf = none :: none | wx:wx_object(), perf = none :: none | wx:wx_object(),
hist = {ts(), 0} :: {LastTS :: integer(), Average :: integer()}, hist = {ts(), 0} :: {LastTS :: integer(), Average :: integer()},
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(), % Add a widget to show this. Maybe % solved = 0 :: non_neg_integer(), % TODO: 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{}],
toggle = start :: stop | start,
toggle_t = none :: none | reference()}).
ts() -> ts() ->
erlang:system_time(microsecond). erlang:system_time(microsecond).
@@ -55,8 +57,8 @@ new_buff() ->
%%% Interface functions %%% Interface functions
ask_passphrase() -> ask_conf() ->
wx_object:cast(?MODULE, ask_passphrase). wx_object:cast(?MODULE, ask_conf).
set_account(ID) -> set_account(ID) ->
@@ -79,6 +81,10 @@ message(Terms) ->
wx_object:cast(?MODULE, {message, Terms}). wx_object:cast(?MODULE, {message, Terms}).
start_stop() ->
wx_object:cast(?MODULE, start_stop).
%%% Startup Functions %%% Startup Functions
@@ -87,54 +93,60 @@ start_link(Title) ->
init(Prefs) -> init(Prefs) ->
ok = log(info, "GUI starting..."),
Lang = maps:get(lang, Prefs, en_US), Lang = maps:get(lang, Prefs, en_US),
Trans = gd_jt:read_translations(?MODULE), Trans = gd_jt:read_translations(?MODULE),
J = gd_jt:j(Lang, Trans), J = gd_jt:j(Lang, Trans),
AppName = J("GajuMine"),
VSN = proplists:get_value(vsn, ?MODULE:module_info(attributes)),
WX = wx:new(), WX = wx:new(),
Frame = wxFrame:new(WX, ?wxID_ANY, J("GajuMine")), Frame = wxFrame:new(WX, ?wxID_ANY, AppName ++ " v" ++ VSN),
Panel = wxWindow:new(Frame, ?wxID_ANY),
TopSz = wxBoxSizer:new(?wxVERTICAL),
_ = wxBoxSizer:add(TopSz, Panel, zxw:flags(wide)),
MainSz = wxBoxSizer:new(?wxVERTICAL), MainSz = wxBoxSizer:new(?wxVERTICAL),
LeftRight = wxBoxSizer:new(?wxHORIZONTAL), LeftRight = wxBoxSizer:new(?wxHORIZONTAL),
Left = wxBoxSizer:new(?wxVERTICAL), Left = wxBoxSizer:new(?wxVERTICAL),
Right = wxBoxSizer:new(?wxVERTICAL), Right = wxBoxSizer:new(?wxVERTICAL),
Labels = [J("ID"), J("Target"), J("Maps/s"), J("Candidate"), J("Height"), J("BlockHash")], Labels = [J("ID"), J("Target"), J("Graphs/s"), J("Candidate"), J("Height"), J("BlockHash")],
{Grid, [ID_C, DiffC, PerfC, CandyC, HeightC, BlockC]} = display_box(Frame, Labels), {Grid, [ID_C, DiffC, PerfC, CandyC, HeightC, BlockC]} = display_box(Panel, Labels),
Style = ?wxTE_MULTILINE bor ?wxTE_READONLY, Style = ?wxTE_MULTILINE bor ?wxTE_READONLY,
MessSz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Messages")}]), MessSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, J("Messages")}]),
MessC = wxTextCtrl:new(Frame, ?wxID_ANY, [{style, Style}]), MessC = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, Style}]),
_ = wxStaticBoxSizer:add(MessSz, MessC, zxw:flags(wide)), _ = wxStaticBoxSizer:add(MessSz, MessC, zxw:flags({wide, 5})),
ButtonTemplates = ButtonTemplates =
[{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}) ->
B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), B = wxButton:new(Panel, ?wxID_ANY, [{label, Label}]),
#w{name = Name, id = wxButton:getId(B), wx = B} #w{name = Name, id = wxButton:getId(B), wx = B}
end, end,
Buttons = lists:map(MakeButton, ButtonTemplates), Buttons = lists:map(MakeButton, ButtonTemplates),
_ = wxBoxSizer:add(Left, Grid, zxw:flags(base)), _ = wxBoxSizer:add(Left, Grid, zxw:flags({base, 5})),
_ = wxBoxSizer:add(Left, MessSz, zxw:flags(wide)), _ = wxBoxSizer:add(Left, MessSz, zxw:flags({wide, 5})),
Add = fun(#w{wx = Button}) -> wxBoxSizer:add(Right, Button, zxw:flags(wide)) end, Add = fun(#w{wx = Button}) -> wxBoxSizer:add(Right, Button, zxw:flags(wide)) end,
ok = lists:foreach(Add, Buttons), ok = lists:foreach(Add, Buttons),
_ = wxBoxSizer:add(LeftRight, Left, zxw:flags(wide)), _ = wxBoxSizer:add(LeftRight, Left, zxw:flags(wide)),
_ = wxBoxSizer:add(LeftRight, Right, zxw:flags(base)), _ = wxBoxSizer:add(LeftRight, Right, zxw:flags(base)),
_ = wxBoxSizer:add(MainSz, LeftRight, zxw:flags(wide)), _ = wxBoxSizer:add(MainSz, LeftRight, zxw:flags({wide, 5})),
ok = wxFrame:setSizer(Frame, MainSz), ok = wxWindow:setSizer(Panel, MainSz),
ok = wxSizer:layout(MainSz), ok = wxFrame:setSizer(Frame, TopSz),
ok = wxBoxSizer:layout(MainSz),
ok = wxFrame:connect(Frame, command_button_clicked), ok = wxFrame:connect(Frame, command_button_clicked),
ok = wxFrame:connect(Frame, close_window), ok = wxFrame:connect(Frame, close_window),
ok = wxFrame:setSize(Frame, {650, 300}), ok = wxFrame:setSize(Frame, {780, 350}),
ok = wxFrame:center(Frame), ok = wxFrame:center(Frame),
true = wxFrame:show(Frame), true = wxFrame:show(Frame),
State = State =
@@ -167,9 +179,9 @@ handle_call(Unexpected, From, State) ->
{noreply, State}. {noreply, State}.
handle_cast(ask_passphrase, State) -> handle_cast(ask_conf, State) ->
ok = ask_passphrase(State), NewState = conf(State),
{noreply, State}; {noreply, NewState};
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};
@@ -185,11 +197,17 @@ handle_cast({candidate, Block}, State) ->
handle_cast({message, Terms}, State) -> handle_cast({message, Terms}, State) ->
NewState = do_message(Terms, State), NewState = do_message(Terms, State),
{noreply, NewState}; {noreply, NewState};
handle_cast(start_stop, State) ->
NewState = start_stop(State),
{noreply, NewState};
handle_cast(Unexpected, State) -> handle_cast(Unexpected, State) ->
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]),
{noreply, State}. {noreply, State}.
handle_info(toggle, State) ->
NewState = toggle(State),
{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}.
@@ -203,6 +221,7 @@ 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};
@@ -223,34 +242,6 @@ 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).
@@ -302,17 +293,43 @@ do_message({pool_notification, #{info := #{msg := MSG}}}, State = #s{height = He
ok = wxStaticText:setLabel(CandT, Candidate), ok = wxStaticText:setLabel(CandT, Candidate),
State; State;
#{solution_accepted := #{seq := Seq}} -> #{solution_accepted := #{seq := Seq}} ->
Entry = io_lib:format("~nSolution Accepted! You solved one! Sequence: ~w", [Seq]), Entry = io_lib:format("~nThe hive has produced a solution! Sequence: ~w", [Seq]),
do_message2(Entry, State); do_message2(Entry, State);
Other -> Other ->
Entry = io_lib:format("~nUnexpected 'pool_notification': ~tp", [Other]), Entry = io_lib:format("~nUnexpected 'pool_notification': ~tp", [Other]),
do_message2(Entry, State) do_message2(Entry, State)
end; end;
do_message({connected, _}, State) -> do_message({connected, _}, State) ->
Entry = "\nConnected!", Entry = "\nConnected",
do_message2(Entry, State);
do_message({disconnected, #{info := #{error := #{code := Code, message := Message}}}}, State = #s{}) ->
Format = "~nAn issue has been reported by the Hive Leader.~nError code: ~p '~s'",
Entry = io_lib:format(Format, [Code, Message]),
do_message2(Entry, State); do_message2(Entry, State);
do_message({disconnected, _}, State) -> do_message({disconnected, _}, State) ->
Entry = "\nDisconnected!", Entry = "\nDisconnected",
do_message2(Entry, State);
do_message({error, #{info := #{info := #{error := get_failed, data := {error, #{code := 404}}, url := URL}}}}, State) ->
ID =
case uri_string:parse(URL) of
#{path := Path} ->
lists:last(string:split(Path, "/", trailing));
{error, Reason, Info} ->
ok = log(warning, "uri_string:parse/1 failed with: ~p: ~p", [Reason, Info]),
"UNKNOWN"
end,
Format =
"\nLookup for mining ID ~s failed."
"\nCheck that you have joined the hive and have your account ID configured correctly.",
Entry = io_lib:format(Format, [ID]),
do_message2(Entry, State);
do_message({error, #{info := #{info := #{error := get_failed, data := {error, #{code := 443}}}}}}, State) ->
Entry =
"\nThis system is not providing the needed TLS CA Certificate."
"\nlease contact the support community and let them know you have a 443 error with GajuMine.",
do_message2(Entry, State);
do_message({error, #{info := #{info := #{error := connect_failure, data := {error, failed}}, module := gmhc_eureka}}}, State) ->
Entry = "\nConnection terminated. Retrying.",
do_message2(Entry, State); do_message2(Entry, State);
do_message(Terms, State) -> do_message(Terms, State) ->
tell(info, "~p", [Terms]), tell(info, "~p", [Terms]),
@@ -364,9 +381,33 @@ add_message2(Entry, {OMax, 0, IMax, 0, []}) ->
{append, {OMax, 1, IMax, 1, [[Entry]]}}. {append, {OMax, 1, IMax, 1, [[Entry]]}}.
start_stop(State) -> start_stop(State = #s{buttons = Buttons, toggle = Last, toggle_t = T}) ->
ok = cancel_timer(T),
#w{wx = SSB} = lists:keyfind(start_stop, #w.name, Buttons),
_ = wxButton:disable(SSB),
ok = gmc_con:start_stop(), ok = gmc_con:start_stop(),
State. Timer = erlang:send_after(2000, self(), toggle),
Next =
case Last of
stop -> start;
start -> stop
end,
State#s{toggle = Next, toggle_t = Timer}.
cancel_timer(none) -> ok;
cancel_timer(Time) -> erlang:cancel_timer(Time).
toggle(State = #s{buttons = Buttons, toggle = Next, j = J}) ->
#w{wx = SSB} = lists:keyfind(start_stop, #w.name, Buttons),
Label =
case Next of
start -> J("Start");
stop -> J("Stop")
end,
ok = wxButton:setLabel(SSB, Label),
_ = wxButton:enable(SSB),
State#s{toggle_t = none}.
gajudesk(State) -> gajudesk(State) ->
@@ -403,6 +444,11 @@ 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
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.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.
+2 -2
View File
@@ -12,10 +12,10 @@
%%% @end %%% @end
-module(gmc_sup). -module(gmc_sup).
-vsn("0.1.4"). -vsn("0.4.2").
-behaviour(supervisor). -behaviour(supervisor).
-author("Craig Everett <craigeverett@qpq.swiss>"). -author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("Craig Everett <craigeverett@qpq.swiss>"). -copyright("QPQ AG <craigeverett@qpq.swiss>").
-license("GPL-3.0-or-later"). -license("GPL-3.0-or-later").
-export([start_link/0]). -export([start_link/0]).
+13 -13
View File
@@ -1,22 +1,22 @@
{name,"GajuMine"}. {name,"GajuMine"}.
{type,gui}. {type,gui}.
{modules,[]}. {modules,[]}.
{author,"Craig Everett"}.
{prefix,"gmc"}. {prefix,"gmc"}.
{author,"Craig Everett"}.
{desc,"Mining client for the Gajumaru Root"}. {desc,"Mining client for the Gajumaru Root"}.
{package_id,{"qpq","gajumine",{0,1,4}}}. {package_id,{"qpq","gajumine",{0,4,2}}}.
{deps,[{"uwiger","gmconfig",{0,1,2}}, {deps,[{"uwiger","gmhive_protocol",{0,3,1}},
{"uwiger","setup",{3,0,0}},
{"uwiger","gmhive_client",{0,10,1}},
{"otpr","zxwidgets",{1,1,0}},
{"otpr","hakuzaru",{0,7,0}},
{"uwiger","gmcuckoo",{1,2,4}},
{"uwiger","gmhive_worker",{0,5,1}},
{"qpq","cuckoo_cpu",{0,3,2}},
{"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","enoise",{1,3,0}}, {"uwiger","enoise",{1,3,0}},
{"uwiger","gmcuckoo",{1,1,0}},
{"uwiger","setup",{2,2,4}},
{"otpr","hakuzaru",{0,6,1}},
{"otpr","gajudesk",{0,5,3}}, {"otpr","gajudesk",{0,5,3}},
{"otpr","zxwidgets",{1,0,1}},
{"otpr","ec_utils",{1,0,0}}, {"otpr","ec_utils",{1,0,0}},
{"otpr","eblake2",{1,0,1}}, {"otpr","eblake2",{1,0,1}},
{"otpr","base58",{0,1,1}}, {"otpr","base58",{0,1,1}},
@@ -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,"Craig Everett"}. {copyright,"QPQ AG"}.
{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,[]}. {tags,["qpq","gaju","gajumaru","hive","mining","crypto"]}.
{ws_url,"https://gajumining.com"}. {ws_url,"https://gajumining.com"}.