diff --git a/gui/backend.go b/gui/backend.go index 9c65025a9bacb46a7d1735075ce796e59f7ebf4a..ab96edbeb7834aeafe7fe1d2e618d50ce36dcc9a 100644 --- a/gui/backend.go +++ b/gui/backend.go @@ -31,9 +31,14 @@ func Quit() { } -//export ToggleDonate -func ToggleDonate() { - backend.ToggleDonate() +//export DonateAccepted +func DonateAccepted() { + backend.DonateAccepted() +} + +//export DonateRejected +func DonateRejected() { + backend.DonateRejected() } //export SubscribeToEvent diff --git a/gui/handlers.cpp b/gui/handlers.cpp index e37de2d162ba44f380155fa6c8cd6f4b0f71dc70..39a57469cfb14b8b910bd64cfb0d22065d1d4f41 100644 --- a/gui/handlers.cpp +++ b/gui/handlers.cpp @@ -1,5 +1,7 @@ #include <QTimer> #include <QDebug> +#include <QDesktopServices> +#include <QUrl> #include "handlers.h" #include "lib/libgoshim.h" @@ -23,9 +25,19 @@ void Backend::unblock() Unblock(); } -void Backend::toggleDonate() +void Backend::donateAccepted() { - ToggleDonate(); + DonateAccepted(); +} + +void Backend::donateRejected() +{ + DonateRejected(); +} + +void Backend::openURL(QString link) +{ + QDesktopServices::openUrl(QUrl(link)); } void Backend::quit() diff --git a/gui/handlers.h b/gui/handlers.h index 3fe9eed5e18d32a172174fe94b59b40b12095132..e65fd5eb746452369075ed18f89bd25a489728dd 100644 --- a/gui/handlers.h +++ b/gui/handlers.h @@ -33,7 +33,9 @@ public slots: void switchOn(); void switchOff(); void unblock(); - void toggleDonate(); + void donateAccepted(); + void donateRejected(); + void openURL(QString link); void quit(); }; diff --git a/gui/qml/DonateDialog.qml b/gui/qml/DonateDialog.qml index b7431abe4284b43fdc34bf0c9470e739c8d34cf7..eb761a4acbe3b48c043864ac2887722f69bb5ffd 100644 --- a/gui/qml/DonateDialog.qml +++ b/gui/qml/DonateDialog.qml @@ -16,12 +16,13 @@ MessageDialog { onAccepted: { if (backend) { - backend.donateAccepted(true) + backend.openURL(ctx.donateURL) + backend.donateAccepted() } } onRejected: { if (backend) { - backend.donateAccepted(false) + backend.donateRejected() } } } diff --git a/gui/qml/main.qml b/gui/qml/main.qml index 98eac800a91d8d77cc62519caa376195a2a8ecd5..65c09cb96b0444db37e0046bf4d54e1546393e51 100644 --- a/gui/qml/main.qml +++ b/gui/qml/main.qml @@ -16,10 +16,9 @@ ApplicationWindow { target: jsonModel onDataChanged: { ctx = JSON.parse(jsonModel.getJson()); - if (ctx.donate == 'true') { + if (ctx.donateDialog == 'true') { console.debug(jsonModel.getJson()) donate.visible = true - backend.toggleDonate() } } } @@ -171,7 +170,9 @@ ApplicationWindow { MenuItem { text: qsTr("Donate...") visible: true - //onTriggered: donate.open() + onTriggered: { + donate.visible = true + } } MenuItem { diff --git a/pkg/backend/api.go b/pkg/backend/api.go index f924cbda122dac780530b4f047f3e02c9157a906..5cb0304d1d19ac59aef02ae503f87808cd663bc8 100644 --- a/pkg/backend/api.go +++ b/pkg/backend/api.go @@ -6,6 +6,7 @@ import ( "C" "fmt" "log" + "time" "unsafe" "0xacab.org/leap/bitmask-vpn/pkg/bitmask" @@ -23,18 +24,24 @@ func SwitchOff() { } func Unblock() { + //TODO fmt.Println("unblock... [not implemented]") } func Quit() { if ctx.Status != off { go setStatus(stopping) + ctx.cfg.SetUserStoppedVPN(true) stopVPN() } } -func ToggleDonate() { - toggleDonate() +func DonateAccepted() { + donateAccepted() +} + +func DonateRejected() { + donateRejected() } func SubscribeToEvent(event string, f unsafe.Pointer) { @@ -42,21 +49,23 @@ func SubscribeToEvent(event string, f unsafe.Pointer) { } func InitializeBitmaskContext() { - pi := bitmask.GetConfiguredProvider() + p := bitmask.GetConfiguredProvider() initOnce.Do(func() { - initializeContext(pi.Provider, pi.AppName) + initializeContext( + p.Provider, p.AppName) }) go ctx.updateStatus() - /* DEBUG - timer := time.NewTimer(time.Second * 3) go func() { - <-timer.C - fmt.Println("donate timer fired") - toggleDonate() + if needsDonationReminder() { + // wait a bit before launching reminder + timer := time.NewTimer(time.Minute * 5) + <-timer.C + showDonate() + } + }() - */ } func RefreshContext() *C.char { diff --git a/pkg/backend/bitmask.go b/pkg/backend/bitmask.go index 07d27ea6a2df61c0b4fcbfc4b16504aeb8300843..8fd2367ac9453fd1754a724ceffd24a20976d5a0 100644 --- a/pkg/backend/bitmask.go +++ b/pkg/backend/bitmask.go @@ -5,6 +5,7 @@ import ( "os" "0xacab.org/leap/bitmask-vpn/pkg/bitmask" + "0xacab.org/leap/bitmask-vpn/pkg/config" ) func initializeBitmask() { @@ -19,6 +20,7 @@ func initializeBitmask() { log.Println("error: cannot initialize bitmask") } ctx.bm = b + ctx.cfg = config.ParseConfig() } func startVPN() { @@ -36,16 +38,25 @@ func stopVPN() { } } +func wantDonations() bool { + if config.AskForDonations == "true" { + return true + } + return false +} + // initializeContext initializes an empty connStatus and assigns it to the // global ctx holder. This is expected to be called only once, so the public // api uses the sync.Once primitive to call this. func initializeContext(provider, appName string) { var st status = off ctx = &connectionCtx{ - AppName: appName, - Provider: provider, - Donate: false, - Status: st, + AppName: appName, + Provider: provider, + DonateURL: config.DonateURL, + AskForDonations: wantDonations(), + DonateDialog: false, + Status: st, } go trigger(OnStatusChanged) initializeBitmask() diff --git a/pkg/backend/donate.go b/pkg/backend/donate.go new file mode 100644 index 0000000000000000000000000000000000000000..d21668769c782a143d106e4701c755b6f151fe70 --- /dev/null +++ b/pkg/backend/donate.go @@ -0,0 +1,35 @@ +package backend + +import ( + "log" + "time" +) + +func needsDonationReminder() bool { + return ctx.cfg.NeedsDonationReminder() +} + +func donateAccepted() { + stmut.Lock() + defer stmut.Unlock() + ctx.DonateDialog = false + log.Println("marking as donated") + ctx.cfg.SetDonated() + go trigger(OnStatusChanged) +} + +func donateRejected() { + timer := time.NewTimer(time.Hour) + go func() { + <-timer.C + showDonate() + }() +} + +func showDonate() { + stmut.Lock() + defer stmut.Unlock() + ctx.DonateDialog = true + ctx.cfg.SetLastReminded() + go trigger(OnStatusChanged) +} diff --git a/pkg/backend/status.go b/pkg/backend/status.go index e2d31dbe1cbcadb36a3e80bc0ed8ba8fa6666a7f..7e9f211f3b3b7d3c5c3157343cb43d1389106d90 100644 --- a/pkg/backend/status.go +++ b/pkg/backend/status.go @@ -6,6 +6,7 @@ import ( "log" "0xacab.org/leap/bitmask-vpn/pkg/bitmask" + "0xacab.org/leap/bitmask-vpn/pkg/config" ) const ( @@ -20,60 +21,20 @@ const ( // if we ever switch again to a provider-agnostic app, we should keep a map here. var ctx *connectionCtx -// the status type reflects the current VPN status. Go code is responsible for updating -// it; the C gui just watches its changes and pulls its updates via the serialized -// context object. - -type status int - -const ( - off status = iota - starting - on - stopping - failed - unknown -) - -func (s status) String() string { - return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s] -} - -func (s status) MarshalJSON() ([]byte, error) { - b := bytes.NewBufferString(`"`) - b.WriteString(s.String()) - b.WriteString(`"`) - return b.Bytes(), nil -} - -func (s status) fromString(st string) status { - switch st { - case offStr: - return off - case startingStr: - return starting - case onStr: - return on - case stoppingStr: - return stopping - case failedStr: - return failed - default: - return unknown - } -} - // The connectionCtx keeps the global state that is passed around to C-land. It // also serves as the primary way of passing requests from the frontend to the // Go-core, by letting the UI write some of these variables and processing // them. type connectionCtx struct { - AppName string `json:"appName"` - Provider string `json:"provider"` - Donate bool `json:"donate"` - Status status `json:"status"` - bm bitmask.Bitmask + AppName string `json:"appName"` + Provider string `json:"provider"` + AskForDonations bool `json:"askForDonations"` + DonateDialog bool `json:"donateDialog"` + DonateURL string `json:"donateURL"` + Status status `json:"status"` + bm bitmask.Bitmask + cfg *config.Config } func (c connectionCtx) toJson() ([]byte, error) { @@ -110,11 +71,47 @@ func setStatus(st status) { go trigger(OnStatusChanged) } -func toggleDonate() { - stmut.Lock() - defer stmut.Unlock() - ctx.Donate = !ctx.Donate - go trigger(OnStatusChanged) +// the status type reflects the current VPN status. Go code is responsible for updating +// it; the C gui just watches its changes and pulls its updates via the serialized +// context object. + +type status int + +const ( + off status = iota + starting + on + stopping + failed + unknown +) + +func (s status) String() string { + return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s] +} + +func (s status) MarshalJSON() ([]byte, error) { + b := bytes.NewBufferString(`"`) + b.WriteString(s.String()) + b.WriteString(`"`) + return b.Bytes(), nil +} + +func (s status) fromString(st string) status { + switch st { + case offStr: + return off + case startingStr: + return starting + case onStr: + return on + case stoppingStr: + return stopping + case failedStr: + return failed + default: + return unknown + } } func setStatusFromStr(stStr string) { diff --git a/pkg/config/gui.go b/pkg/config/gui.go index ce3f14d61d7137165179a4f063f7fb003d35ea5e..5fa4886fcf3963d308cf105ec3c36e82ff17f8a8 100644 --- a/pkg/config/gui.go +++ b/pkg/config/gui.go @@ -37,7 +37,7 @@ var ( // Config holds the configuration of the systray type Config struct { file struct { - LastNotification time.Time + LastReminded time.Time Donated time.Time SelectGateway bool Obfs4 bool @@ -77,16 +77,16 @@ func (c *Config) SetUserStoppedVPN(vpnStopped bool) error { return c.save() } -func (c *Config) HasDonated() bool { - return c.file.Donated.Add(oneMonth).After(time.Now()) +func (c *Config) NeedsDonationReminder() bool { + return !c.hasDonated() && c.file.LastReminded.Add(oneDay).Before(time.Now()) } -func (c *Config) NeedsNotification() bool { - return !c.HasDonated() && c.file.LastNotification.Add(oneDay).Before(time.Now()) +func (c *Config) hasDonated() bool { + return c.file.Donated.Add(oneMonth).After(time.Now()) } -func (c *Config) SetNotification() error { - c.file.LastNotification = time.Now() +func (c *Config) SetLastReminded() error { + c.file.LastReminded = time.Now() return c.save() }