@ -84,8 +84,8 @@ pub struct Composer {
embed_area : Area ,
embed : Option < EmbedStatus > ,
sign_mail : ToggleFlag ,
encrypt_mail: ToggleFlag ,
#[ cfg(feature = " gpgme " ) ]
gpg_state: gpg ::GpgComposeState ,
dirty : bool ,
has_changes : bool ,
initialized : bool ,
@ -107,8 +107,8 @@ impl Default for Composer {
form : FormWidget ::default ( ) ,
mode : ViewMode ::Edit ,
sign_mail : ToggleFlag ::Unset ,
encrypt_mail: ToggleFlag ::Unset ,
#[ cfg(feature = " gpgme " ) ]
gpg_state: gpg ::GpgComposeState ::new ( ) ,
dirty : true ,
has_changes : false ,
embed_area : ( ( 0 , 0 ) , ( 0 , 0 ) ) ,
@ -125,6 +125,8 @@ enum ViewMode {
Edit ,
Embed ,
SelectRecipients ( UIDialog < Address > ) ,
#[ cfg(feature = " gpgme " ) ]
SelectEncryptKey ( bool , gpg ::KeySelection ) ,
Send ( UIConfirmationDialog ) ,
WaitingForSendResult ( UIDialog < char > , JoinHandle < Result < ( ) > > ) ,
}
@ -430,14 +432,23 @@ impl Composer {
let attachments_no = self . draft . attachments ( ) . len ( ) ;
let theme_default = crate ::conf ::value ( context , "theme_default" ) ;
clear_area ( grid , area , theme_default ) ;
if self . sign_mail . is_true ( ) {
#[ cfg(feature = " gpgme " ) ]
if self . gpg_state . sign_mail . is_true ( ) {
let key_list = self
. gpg_state
. sign_keys
. iter ( )
. map ( | k | k . fingerprint ( ) )
. collect ::< Vec < _ > > ( )
. join ( ", " ) ;
write_string_to_grid (
& format! (
"โ sign with {}" ,
account_settings ! ( context [ self . account_hash ] . pgp . sign_key )
. as_ref ( )
. map ( | s | s . as_str ( ) )
. unwrap_or ( "default key" )
if self . gpg_state . sign_keys . is_empty ( ) {
"default key"
} else {
key_list . as_str ( )
}
) ,
grid ,
theme_default . fg ,
@ -465,14 +476,29 @@ impl Composer {
None ,
) ;
}
if self . encrypt_mail . is_true ( ) {
#[ cfg(feature = " gpgme " ) ]
if self . gpg_state . encrypt_mail . is_true ( ) {
let key_list = self
. gpg_state
. encrypt_keys
. iter ( )
. map ( | k | k . fingerprint ( ) )
. collect ::< Vec < _ > > ( )
. join ( ", " ) ;
write_string_to_grid (
& format! (
"โ encrypt with {}" ,
account_settings ! ( context [ self . account_hash ] . pgp . encrypt_key )
. as_ref ( )
. map ( | s | s . as_str ( ) )
. unwrap_or ( "default key" )
"{}{}" ,
if self . gpg_state . encrypt_keys . is_empty ( ) {
"โ no keys to encrypt with!"
} else {
"โ encrypt with "
} ,
if self . gpg_state . encrypt_keys . is_empty ( ) {
""
} else {
key_list . as_str ( )
}
) ,
grid ,
theme_default . fg ,
@ -575,8 +601,9 @@ impl Component for Composer {
let width = width ! ( area ) ;
if ! self . initialized {
if self . sign_mail . is_unset ( ) {
self . sign_mail = ToggleFlag ::InternalVal ( * account_settings ! (
#[ cfg(feature = " gpgme " ) ]
if self . gpg_state . sign_mail . is_unset ( ) {
self . gpg_state . sign_mail = ToggleFlag ::InternalVal ( * account_settings ! (
context [ self . account_hash ] . pgp . auto_sign
) ) ;
}
@ -752,6 +779,18 @@ impl Component for Composer {
ViewMode ::Send ( ref mut s ) = > {
s . draw ( grid , center_area ( area , s . content . size ( ) ) , context ) ;
}
#[ cfg(feature = " gpgme " ) ]
ViewMode ::SelectEncryptKey (
_ ,
gpg ::KeySelection ::Loaded {
ref mut widget ,
keys : _ ,
} ,
) = > {
widget . draw ( grid , center_area ( area , widget . content . size ( ) ) , context ) ;
}
#[ cfg(feature = " gpgme " ) ]
ViewMode ::SelectEncryptKey ( _ , _ ) = > { }
ViewMode ::SelectRecipients ( ref mut s ) = > {
s . draw ( grid , center_area ( area , s . content . size ( ) ) , context ) ;
}
@ -783,8 +822,8 @@ impl Component for Composer {
if let Some ( true ) = result . downcast_ref ::< bool > ( ) {
self . update_draft ( ) ;
match send_draft_async (
self . sign_mail ,
self . encrypt_mail ,
#[ cfg(feature = " gpgme " ) ]
self . gpg_state. clone ( ) ,
context ,
self . account_hash ,
self . draft . clone ( ) ,
@ -846,6 +885,14 @@ impl Component for Composer {
self . mode = ViewMode ::Edit ;
self . set_dirty ( true ) ;
}
#[ cfg(feature = " gpgme " ) ]
( ViewMode ::SelectEncryptKey ( _ , ref mut selector ) , UIEvent ::ComponentKill ( ref id ) )
if * id = = selector . id ( ) = >
{
self . mode = ViewMode ::Edit ;
self . set_dirty ( true ) ;
return true ;
}
( ViewMode ::Send ( ref mut selector ) , _ ) = > {
if selector . process_event ( event , context ) {
return true ;
@ -954,6 +1001,34 @@ impl Component for Composer {
return true ;
}
}
#[ cfg(feature = " gpgme " ) ]
(
ViewMode ::SelectEncryptKey ( is_encrypt , ref mut selector ) ,
UIEvent ::FinishedUIDialog ( id , result ) ,
) if * id = = selector . id ( ) = > {
debug ! ( & result ) ;
if let Some ( key ) = result . downcast_mut ::< Option < melib ::gpgme ::Key > > ( ) {
debug ! ( "got key {:?}" , key ) ;
if let Some ( key ) = key {
if * is_encrypt {
self . gpg_state . encrypt_keys . clear ( ) ;
self . gpg_state . encrypt_keys . push ( key . clone ( ) ) ;
} else {
self . gpg_state . sign_keys . clear ( ) ;
self . gpg_state . sign_keys . push ( key . clone ( ) ) ;
}
}
}
self . mode = ViewMode ::Edit ;
self . set_dirty ( true ) ;
return true ;
}
#[ cfg(feature = " gpgme " ) ]
( ViewMode ::SelectEncryptKey ( _ , ref mut selector ) , _ ) = > {
if selector . process_event ( event , context ) {
return true ;
}
}
_ = > { }
}
if self . cursor = = Cursor ::Headers
@ -1025,14 +1100,15 @@ impl Component for Composer {
if self . mode . is_edit ( )
& & ( self . cursor = = Cursor ::Sign | | self . cursor = = Cursor ::Encrypt ) = >
{
#[ cfg(feature = " gpgme " ) ]
match self . cursor {
Cursor ::Sign = > {
let is_true = self . sign_mail. is_true ( ) ;
self . sign_mail = ToggleFlag ::from ( ! is_true ) ;
let is_true = self . gpg_state. sign_mail. is_true ( ) ;
self . gpg_state. sign_mail = ToggleFlag ::from ( ! is_true ) ;
}
Cursor ::Encrypt = > {
let is_true = self . encrypt_mail. is_true ( ) ;
self . encrypt_mail = ToggleFlag ::from ( ! is_true ) ;
let is_true = self . gpg_state. encrypt_mail. is_true ( ) ;
self . gpg_state. encrypt_mail = ToggleFlag ::from ( ! is_true ) ;
}
_ = > { }
} ;
@ -1194,6 +1270,86 @@ impl Component for Composer {
self . set_dirty ( true ) ;
return true ;
}
UIEvent ::Input ( ref key )
if self . mode . is_edit ( )
& & self . cursor = = Cursor ::Sign
& & shortcut ! ( key = = shortcuts [ Self ::DESCRIPTION ] [ "edit_mail" ] ) = >
{
#[ cfg(feature = " gpgme " ) ]
match melib ::email ::parser ::address ::rfc2822address_list (
self . form . values ( ) [ "From" ] . as_str ( ) . as_bytes ( ) ,
)
. map_err ( | _err | -> MeliError { "No valid sender address in `From:`" . into ( ) } )
. and_then ( | ( _ , list ) | {
list . get ( 0 )
. cloned ( )
. ok_or_else ( | | "No valid sender address in `From:`" . into ( ) )
} )
. and_then ( | addr | {
gpg ::KeySelection ::new (
false ,
account_settings ! ( context [ self . account_hash ] . pgp . allow_remote_lookup )
. is_true ( ) ,
addr . get_email ( ) ,
* account_settings ! ( context [ self . account_hash ] . pgp . allow_remote_lookup ) ,
context ,
)
} ) {
Ok ( widget ) = > {
self . gpg_state . sign_mail = ToggleFlag ::from ( true ) ;
self . mode = ViewMode ::SelectEncryptKey ( false , widget ) ;
}
Err ( err ) = > {
context . replies . push_back ( UIEvent ::Notification (
Some ( "Could not list keys." . to_string ( ) ) ,
format! ( "libgpgme error: {}" , & err ) ,
Some ( NotificationType ::Error ( melib ::error ::ErrorKind ::External ) ) ,
) ) ;
}
}
self . set_dirty ( true ) ;
return true ;
}
UIEvent ::Input ( ref key )
if self . mode . is_edit ( )
& & self . cursor = = Cursor ::Encrypt
& & shortcut ! ( key = = shortcuts [ Self ::DESCRIPTION ] [ "edit_mail" ] ) = >
{
#[ cfg(feature = " gpgme " ) ]
match melib ::email ::parser ::address ::rfc2822address_list (
self . form . values ( ) [ "To" ] . as_str ( ) . as_bytes ( ) ,
)
. map_err ( | _err | -> MeliError { "No valid recipient addresses in `To:`" . into ( ) } )
. and_then ( | ( _ , list ) | {
list . get ( 0 )
. cloned ( )
. ok_or_else ( | | "No valid recipient addresses in `To:`" . into ( ) )
} )
. and_then ( | addr | {
gpg ::KeySelection ::new (
false ,
account_settings ! ( context [ self . account_hash ] . pgp . allow_remote_lookup )
. is_true ( ) ,
addr . get_email ( ) ,
* account_settings ! ( context [ self . account_hash ] . pgp . allow_remote_lookup ) ,
context ,
)
} ) {
Ok ( widget ) = > {
self . gpg_state . encrypt_mail = ToggleFlag ::from ( true ) ;
self . mode = ViewMode ::SelectEncryptKey ( true , widget ) ;
}
Err ( err ) = > {
context . replies . push_back ( UIEvent ::Notification (
Some ( "Could not list keys." . to_string ( ) ) ,
format! ( "libgpgme error: {}" , & err ) ,
Some ( NotificationType ::Error ( melib ::error ::ErrorKind ::External ) ) ,
) ) ;
}
}
self . set_dirty ( true ) ;
return true ;
}
UIEvent ::Input ( ref key )
if self . embed . is_some ( )
& & shortcut ! ( key = = shortcuts [ Self ::DESCRIPTION ] [ "edit_mail" ] ) = >
@ -1498,15 +1654,17 @@ impl Component for Composer {
) ;
return true ;
}
#[ cfg(feature = " gpgme " ) ]
Action ::Compose ( ComposeAction ::ToggleSign ) = > {
let is_true = self . sign_mail. is_true ( ) ;
self . sign_mail = ToggleFlag ::from ( ! is_true ) ;
let is_true = self . gpg_state. sign_mail. is_true ( ) ;
self . gpg_state. sign_mail = ToggleFlag ::from ( ! is_true ) ;
self . dirty = true ;
return true ;
}
#[ cfg(feature = " gpgme " ) ]
Action ::Compose ( ComposeAction ::ToggleEncrypt ) = > {
let is_true = self . encrypt_mail. is_true ( ) ;
self . encrypt_mail = ToggleFlag ::from ( ! is_true ) ;
let is_true = self . gpg_state. encrypt_mail. is_true ( ) ;
self . gpg_state. encrypt_mail = ToggleFlag ::from ( ! is_true ) ;
self . dirty = true ;
return true ;
}
@ -1527,6 +1685,10 @@ impl Component for Composer {
ViewMode ::SelectRecipients ( ref widget ) = > {
widget . is_dirty ( ) | | self . pager . is_dirty ( ) | | self . form . is_dirty ( )
}
#[ cfg(feature = " gpgme " ) ]
ViewMode ::SelectEncryptKey ( _ , ref widget ) = > {
widget . is_dirty ( ) | | self . pager . is_dirty ( ) | | self . form . is_dirty ( )
}
ViewMode ::Send ( ref widget ) = > {
widget . is_dirty ( ) | | self . pager . is_dirty ( ) | | self . form . is_dirty ( )
}
@ -1626,7 +1788,7 @@ impl Component for Composer {
}
pub fn send_draft (
sign_mail: ToggleFlag ,
_ sign_mail: ToggleFlag ,
context : & mut Context ,
account_hash : AccountHash ,
mut draft : Draft ,
@ -1635,7 +1797,7 @@ pub fn send_draft(
complete_in_background : bool ,
) -> Result < Option < JoinHandle < Result < ( ) > > > > {
let format_flowed = * account_settings ! ( context [ account_hash ] . composing . format_flowed ) ;
if sign_mail . is_true ( ) {
/* if sign_mail.is_true() {
let mut content_type = ContentType ::default ( ) ;
if format_flowed {
if let ContentType ::Text {
@ -1667,41 +1829,44 @@ pub fn send_draft(
)
. into ( ) ;
}
let output = crate ::components ::mail ::pgp ::sign (
body . into ( ) ,
account_settings ! ( context [ account_hash ] . pgp . gpg_binary )
. as_ref ( )
. map ( | s | s . as_ st r( ) ) ,
account_settings ! ( context [ account_hash ] . pgp . sign_key )
. as_ref ( )
. map ( | s | s . as_ st r( ) ) ,
) ;
match output {
Err ( err ) = > {
debug ! ( "{:?} could not sign draft msg" , err ) ;
log (
format! (
"Could not sign draft in account `{}`: {}." ,
context . accounts [ & account_hash ] . name ( ) ,
err . to_string ( )
) ,
ERROR ,
) ;
context . replies . push_back ( UIEvent ::Notification (
Some ( format! (
"Could not sign draft in account `{}`." ,
context . accounts [ & account_hash ] . name ( )
) ) ,
err . to_string ( ) ,
Some ( NotificationType ::Error ( err . kind ) ) ,
) ) ;
return Err ( err ) ;
}
Ok ( output ) = > {
draft . attachments . push ( output ) ;
}
let output = todo! ( ) ;
crate ::components ::mail ::pgp ::sign (
body . into ( ) ,
account_settings ! ( context [ account_hash ] . pgp . gpg_binary )
. as_ ref ( )
. map ( | s | s . as_str ( ) ) ,
account_settings ! ( context [ account_hash ] . pgp . sign_key )
. as_ ref ( )
. map ( | s | s . as_str ( ) ) ,
) ;
match output {
Err ( err ) = > {
debug ! ( "{:?} could not sign draft msg" , err ) ;
log (
format! (
"Could not sign draft in account `{}`: {}." ,
context . accounts [ & account_hash ] . name ( ) ,
err . to_string ( )
) ,
ERROR ,
) ;
context . replies . push_back ( UIEvent ::Notification (
Some ( format! (
"Could not sign draft in account `{}`." ,
context . accounts [ & account_hash ] . name ( )
) ) ,
err . to_string ( ) ,
Some ( NotificationType ::Error ( err . kind ) ) ,
) ) ;
return Err ( err ) ;
}
Ok ( output ) = > {
draft . attachments . push ( output ) ;
}
}
} else {
* /
{
let mut content_type = ContentType ::default ( ) ;
if format_flowed {
if let ContentType ::Text {
@ -1762,8 +1927,7 @@ pub fn save_draft(
}
pub fn send_draft_async (
sign_mail : ToggleFlag ,
encrypt_mail : ToggleFlag ,
#[ cfg(feature = " gpgme " ) ] gpg_state : gpg ::GpgComposeState ,
context : & mut Context ,
account_hash : AccountHash ,
mut draft : Draft ,
@ -1772,6 +1936,7 @@ pub fn send_draft_async(
) -> Result < Pin < Box < dyn Future < Output = Result < ( ) > > + Send > > > {
let format_flowed = * account_settings ! ( context [ account_hash ] . composing . format_flowed ) ;
let event_sender = context . sender . clone ( ) ;
#[ cfg(feature = " gpgme " ) ]
let mut filters_stack : Vec <
Box <
dyn FnOnce (
@ -1781,40 +1946,19 @@ pub fn send_draft_async(
+ Send ,
> ,
> = vec! [ ] ;
if sign_mail . is_true ( ) {
#[ cfg(feature = " gpgme " ) ]
if gpg_state . sign_mail . is_true ( ) & & ! gpg_state . encrypt_mail . is_true ( ) {
filters_stack . push ( Box ::new ( crate ::components ::mail ::pgp ::sign_filter (
account_settings ! ( context [ account_hash ] . pgp . gpg_binary )
. as_ref ( )
. map ( | s | s . to_string ( ) ) ,
account_settings ! ( context [ account_hash ] . pgp . sign_key )
. as_ref ( )
. map ( | s | s . to_string ( ) ) ,
gpg_state . sign_keys . clone ( ) ,
) ? ) ) ;
}
if encrypt_mail . is_true ( ) {
let mut recipients = vec! [ ] ;
if let Ok ( ( _ , v ) ) =
melib ::email ::parser ::address ::rfc2822address_list ( draft . headers ( ) [ "To" ] . as_bytes ( ) )
{
for addr in v {
recipients . push ( addr . get_email ( ) ) ;
}
}
if let Ok ( ( _ , v ) ) =
melib ::email ::parser ::address ::rfc2822address_list ( draft . headers ( ) [ "Cc" ] . as_bytes ( ) )
{
for addr in v {
recipients . push ( addr . get_email ( ) ) ;
}
}
} else if gpg_state . encrypt_mail . is_true ( ) {
filters_stack . push ( Box ::new ( crate ::components ::mail ::pgp ::encrypt_filter (
account_settings ! ( context [ account_hash ] . pgp . gpg_binary )
. as_ref ( )
. map ( | s | s . to_string ( ) ) ,
account_settings ! ( context [ account_hash ] . pgp . encrypt_key )
. as_ref ( )
. map ( | s | s . to_string ( ) ) ,
recipients ,
if gpg_state . sign_mail . is_true ( ) {
Some ( gpg_state . sign_keys . clone ( ) )
} else {
None
} ,
gpg_state . encrypt_keys . clone ( ) ,
) ? ) ) ;
}
let send_mail = account_settings ! ( context [ account_hash ] . composing . send_mail ) . clone ( ) ;
@ -1850,6 +1994,7 @@ pub fn send_draft_async(
. into ( ) ;
}
Ok ( Box ::pin ( async move {
#[ cfg(feature = " gpgme " ) ]
for f in filters_stack {
body = f ( body ) . await ? ;
}
@ -1882,3 +2027,277 @@ pub fn send_draft_async(
ret
} ) )
}
#[ cfg(feature = " gpgme " ) ]
mod gpg {
use super ::* ;
#[ derive(Debug) ]
pub enum KeySelection {
LoadingKeys {
handle : JoinHandle < Result < Vec < melib ::gpgme ::Key > > > ,
progress_spinner : ProgressSpinner ,
secret : bool ,
local : bool ,
pattern : String ,
allow_remote_lookup : ToggleFlag ,
} ,
Error {
id : ComponentId ,
err : MeliError ,
} ,
Loaded {
widget : UIDialog < melib ::gpgme ::Key > ,
keys : Vec < melib ::gpgme ::Key > ,
} ,
}
impl std ::fmt ::Display for KeySelection {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
write! ( f , "select pgp keys" )
}
}
impl KeySelection {
pub fn new (
secret : bool ,
local : bool ,
pattern : String ,
allow_remote_lookup : ToggleFlag ,
context : & mut Context ,
) -> Result < Self > {
use melib ::gpgme ::* ;
debug ! ( "KeySelection::new" ) ;
debug ! ( & secret ) ;
debug ! ( & local ) ;
debug ! ( & pattern ) ;
debug ! ( & allow_remote_lookup ) ;
let mut ctx = Context ::new ( ) ? ;
if local {
ctx . set_auto_key_locate ( LocateKey ::LOCAL ) ? ;
} else {
ctx . set_auto_key_locate ( LocateKey ::WKD | LocateKey ::LOCAL ) ? ;
}
let job = ctx . keylist ( secret , Some ( pattern . clone ( ) ) ) ? ;
let handle = context . job_executor . spawn_specialized ( job ) ;
let mut progress_spinner = ProgressSpinner ::new ( 8 ) ;
progress_spinner . start ( ) ;
Ok ( KeySelection ::LoadingKeys {
handle ,
secret ,
local ,
pattern ,
allow_remote_lookup ,
progress_spinner ,
} )
}
}
impl Component for KeySelection {
fn draw ( & mut self , grid : & mut CellBuffer , area : Area , context : & mut Context ) {
match self {
KeySelection ::LoadingKeys {
ref mut progress_spinner ,
..
} = > progress_spinner . draw ( grid , center_area ( area , ( 2 , 2 ) ) , context ) ,
KeySelection ::Error { ref err , .. } = > {
let theme_default = crate ::conf ::value ( context , "theme_default" ) ;
write_string_to_grid (
& err . to_string ( ) ,
grid ,
theme_default . fg ,
theme_default . bg ,
theme_default . attrs ,
center_area ( area , ( 15 , 2 ) ) ,
Some ( 0 ) ,
) ;
}
KeySelection ::Loaded { ref mut widget , .. } = > {
widget . draw ( grid , center_area ( area , widget . content . size ( ) ) , context )
}
}
}
fn process_event ( & mut self , event : & mut UIEvent , context : & mut Context ) -> bool {
debug ! ( & self ) ;
debug ! ( & event ) ;
match self {
KeySelection ::LoadingKeys {
ref mut progress_spinner ,
ref mut handle ,
secret ,
local ,
ref mut pattern ,
allow_remote_lookup ,
..
} = > match event {
UIEvent ::StatusEvent ( StatusEvent ::JobFinished ( ref id ) )
if * id = = handle . job_id = >
{
match handle . chan . try_recv ( ) . unwrap ( ) . unwrap ( ) {
Ok ( keys ) = > {
if keys . is_empty ( ) {
let id = progress_spinner . id ( ) ;
if allow_remote_lookup . is_true ( ) {
match Self ::new (
* secret ,
* local ,
std ::mem ::replace ( pattern , String ::new ( ) ) ,
* allow_remote_lookup ,
context ,
) {
Ok ( w ) = > {
* self = w ;
}
Err ( err ) = > * self = KeySelection ::Error { err , id } ,