5 gpgApplet - GNOME applet for OpenGPG text encryption
9 gpgApplet allows encrypting and decrypting the content of the
10 clipboard with a symmetric cipher using a passphrase. This is
11 a graphical frontend on top of GnuPG.
13 Asymmetric decryption and clipboard verification are also supported.
17 gpgApplet does not handle passphrase input. Since it also does not
18 offer terminal interaction unless explicitly run from there, it relies
19 in practice on some kind of GnuPG agent such as pinentry, Seahorse 2.x
20 or GNOME keyring 3.x to manage passphrase input.
25 use warnings FATAL
=> 'all';
30 use Glib
qw{TRUE FALSE
};
32 use Gtk2
::Gdk
::Keysyms
;
35 use Encode
qw{decode encode find_encoding
};
37 use gpgApplet
::GnuPG
::Interface
;
39 use I18N
::Langinfo
qw{langinfo CODESET
};
40 use List
::MoreUtils
qw{none
};
46 setlocale
(LC_MESSAGES
, "");
54 use constant C_SELECT
=> 0;
55 use constant C_NAME
=> 1;
56 use constant C_KEYID
=> 2;
57 use constant C_STATUS
=> 3;
58 use constant C_FINGERPRINT
=> 4;
59 use constant C_USERIDS
=> 5;
60 use constant C_TRUSTED
=> 6;
61 use constant VISIBLE_COLS
=> (C_NAME
, C_KEYID
, C_STATUS
);
62 use constant HIDDEN_COLS
=> (C_FINGERPRINT
, C_USERIDS
, C_TRUSTED
);
64 use constant COMBO_NAME
=> 0;
65 use constant COMBO_KEYID
=> 1;
66 use constant COMBO_FINGERPRINT
=> 2;
67 use constant COMBO_ROLE
=> 3;
69 my $gnupg = gpgApplet
::GnuPG
::Interface
->new();
70 my $codeset = langinfo
(CODESET
());
71 my $encoding = find_encoding
($codeset);
72 my $main_window = Gtk2
::Window
->new();
73 my $icon_factory = Gtk2
::IconFactory
->new();
74 # Set always_trust since GnuPG otherwise will fail if the key's
75 # trust hasn't been set.
76 my %gnupg_options = (armor
=> 1, always_trust
=> 0, meta_interactive
=> 0);
78 my $pgp_encrypted_msg = {
80 header
=> '-----BEGIN PGP MESSAGE-----',
81 footer
=> '-----END PGP MESSAGE-----'
83 my $pgp_signed_msg = {
85 header
=> '-----BEGIN PGP SIGNED MESSAGE-----',
86 middle
=> '-----BEGIN PGP SIGNATURE-----',
87 footer
=> '-----END PGP SIGNATURE-----'
89 my @pgp_headers = ($pgp_encrypted_msg, $pgp_signed_msg);
95 my $statusicon = build_statusicon
();
96 $statusicon->set_visible(TRUE
);
97 init_freshest_clipboard
();
98 init_icons_stock
($icon_factory);
99 detect_received
(freshest_clipboard
());
109 Gtk2
::Clipboard
->get($_)
111 Gtk2
::Gdk
->SELECTION_CLIPBOARD,
112 Gtk2
::Gdk
->SELECTION_PRIMARY
117 my $freshest_clipboard;
119 sub init_freshest_clipboard
{
120 $freshest_clipboard = Gtk2
::Clipboard
->get(Gtk2
::Gdk
->SELECTION_CLIPBOARD);
123 sub freshest_clipboard
{
124 return $freshest_clipboard;
127 sub set_freshest_clipboard
{
128 $freshest_clipboard = shift;
132 sub build_statusicon
{
133 my $icon = Gtk2
::StatusIcon
->new;
134 $icon->set_visible(FALSE
);
135 $icon->set_from_icon_name('seahorse');
136 $icon->set_tooltip($encoding->decode(gettext
('OpenPGP encryption applet')));
138 my $menu = Gtk2
::Menu
->new;
139 my $mexit = Gtk2
::MenuItem
->new($encoding->decode(gettext
('Exit')));
140 $mexit->signal_connect('activate' => sub { Gtk2
->main_quit; });
141 my $mabout = Gtk2
::MenuItem
->new($encoding->decode(gettext
('About')));
142 $mabout->signal_connect('activate' => sub { Gtk2
->show_about_dialog(
144 'program-name' => 'gpgApplet',
145 'license' => q{This program is free software; you can redistribute it and/or modify it under the terms of either:
147 a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or
149 b) the "Artistic License" which comes with Perl.
152 'website' => 'https://tails.boum.org/',
154 $menu->append($mexit);
155 $menu->append(Gtk2
::SeparatorMenuItem
->new);
156 $menu->append($mabout);
158 $icon->signal_connect('popup-menu', sub {
163 $menu->popup(undef, undef, undef, undef, $event, $time);
166 $icon->signal_connect('button-press-event' => sub {
169 return unless $event->button == 1;
170 my $action_menu = build_action_menu
();
171 $action_menu->show_all;
172 $action_menu->popup(undef, undef, undef, undef, $event->button, $event->time);
175 foreach (all_clipboards
()) {
176 $_->signal_connect("owner-change" => sub {
177 my $clipboard = shift;
179 handle_clipboard_owner_change
($clipboard);
186 sub build_action_menu
{
187 my $action_menu = Gtk2
::Menu
->new;
189 my $text_type = detect_text_type
(get_validated_clipboard_text
());
191 if ($text_type eq 'text' or $text_type eq 'none') {
192 my $msymencrypt = Gtk2
::MenuItem
->new_with_mnemonic($encoding->decode(gettext
('Encrypt Clipboard with _Passphrase')));
193 $msymencrypt->signal_connect('activate' => sub { operate_on_clipboard
(\
&symmetric_encrypt
, ['text']); });
194 $action_menu->append($msymencrypt);
195 my $msignencrypt = Gtk2
::MenuItem
->new_with_mnemonic($encoding->decode(gettext
('Sign/Encrypt Clipboard with Public _Keys')));
196 $msignencrypt->signal_connect('activate' => sub { operate_on_clipboard
(\
&public_crypto
, ['text']); });
197 $action_menu->append($msignencrypt);
199 if ($text_type eq 'message' or $text_type eq 'signed') {
200 my $mdecryptver = Gtk2
::MenuItem
->new_with_mnemonic($encoding->decode(gettext
('_Decrypt/Verify Clipboard')));
201 $mdecryptver->signal_connect('activate' => sub { operate_on_clipboard
(\
&decrypt_verify
, ['message', 'signed']); });
202 $action_menu->append($mdecryptver);
204 my $mmanage = Gtk2
::MenuItem
->new_with_mnemonic($encoding->decode(gettext
('_Manage Keys')));
205 $mmanage->signal_connect('activate' => sub { manage_keys
(); });
206 $action_menu->append($mmanage);
212 system("seahorse &");
216 map { $_->{type
} } @pgp_headers;
219 sub detect_text_type
{
222 unless (defined $text && length($text)) {
226 foreach (@pgp_headers) {
227 my $header = $_->{header
};
228 my $footer = $_->{footer
};
229 return $_->{type
} if $text =~ m{$header.*$footer}ms;
235 sub text_is_of_type
{
237 my @valid_types = @_;
239 my $text_type = detect_text_type
($text);
240 if (none
{ $_ eq $text_type } @valid_types) {
244 gettext
("The clipboard does not contain valid input data."))
250 sub get_validated_clipboard_text
{
254 if (exists $args->{valid_types
} && defined $args->{valid_types
}) {
255 @valid_types = @
{ $args->{valid_types
} };
258 @valid_types = all_text_types
();
261 my $clipboard = freshest_clipboard
();
262 # Note: according to the GTK documentation, the wait_for_text method
263 # is supposed to always returns UTF-8. But it seems like the Perl
264 # bindings decode it and we get a string of chars instead of bytes.
265 my $content = $clipboard->wait_for_text;
266 my ($is_valid, $reason) = text_is_of_type
($content, @valid_types);
267 return ($content) if $is_valid;
271 sub set_clipboards_text
{
273 my $encoded_text = $encoding->encode($text);
275 # Note: according to the GTK documentation, the set_text method
276 # is supposed to need input encoded in UTF-8. But it seems like the Perl
277 # bindings encode it, and we need to pass a string of chars instead of bytes.
278 foreach (all_clipboards
()) {
281 # GTK fails setting the primary selection above, so let's use xclip :/
282 # xclip needs encoded text.
283 open(my $xclip, '| xclip') or die "Error opening pipe to xclip";
284 print $xclip $encoded_text or die "Error copying data to X clipboard";
285 close $xclip or die "Error closing pipe to xclip";
292 # Below taken from doc/DETAILS in GnuPG's sources
294 case
"o" { $trusted = FALSE
;
295 $status = $encoding->decode(gettext
("Unknown Trust")); }
296 case
"-" { $trusted = FALSE
;
297 $status = $encoding->decode(gettext
("Unknown Trust")); }
298 case
"q" { $trusted = FALSE
;
299 $status = $encoding->decode(gettext
("Unknown Trust")); }
300 case
"m" { $trusted = FALSE
;
301 $status = $encoding->decode(gettext
("Marginal Trust")); }
302 case
"f" { $trusted = TRUE
;
303 $status = $encoding->decode(gettext
("Full Trust")); }
304 case
"u" { $trusted = TRUE
;
305 $status = $encoding->decode(gettext
("Ultimate Trust")); }
308 return ($status, $trusted);
311 sub get_private_key_status
{
314 my $fingerprint = $encoding->decode($key->fingerprint->as_hex_string());
315 my $pubkey = ($gnupg->get_public_keys_light($fingerprint))[0]; # ignore collisions
317 # a valid key may lack signing capabilities
318 return unless $pubkey->usage_flags =~ m/S/;
320 my $validity = $pubkey->user_ids->[0]->validity;
321 return get_status
($validity);
324 sub get_public_key_status
{
327 # a valid key may lack encryption capabilities
328 return unless $key->usage_flags =~ m/E/;
330 my $validity = $key->user_ids->[0]->validity;
331 return get_status
($validity);
337 my ($status, $trusted) = (ref($key) eq "GnuPG::SecretKey") ?
338 get_private_key_status
($key)
339 : get_public_key_status
($key);
340 # no status implies expired, revoked, etc. keys, which we don't want to list
341 return if !defined $status;
343 my $name = $encoding->decode($key->user_ids->[0]->as_string);
344 my $userids = join("\n", map { my $a = $_->as_string; my $b = $encoding->decode("$a"); my $c ="x" . "$b" }
346 my $keyid = $encoding->decode($key->short_hex_id);
348 my $fingerprint = $encoding->decode($key->fingerprint->as_hex_string());
349 # Gtk2::SimpleList encodes these strings itself.
350 return [FALSE
, $name, $keyid, $status, $fingerprint, $userids, $trusted];
353 sub make_pub_key_list
{
354 my $pub_keys_ref = shift;
356 my $list = Gtk2
::SimpleList
->new (
357 "" => 'bool', # C_SELECT
358 $encoding->decode(gettext
("Name")) => 'text', # C_NAME
359 $encoding->decode(gettext
("Key ID")) => 'text', # C_KEYID
360 $encoding->decode(gettext
("Status")) => 'text', # C_STATUS
361 "" => 'text', # C_FINGERPRINT
362 "" => 'text', # C_USERIDS
363 "" => 'bool' # C_TRUSTED
365 foreach my $i (VISIBLE_COLS
) {
366 my $col = $list->get_column($i);
367 $col->set_max_width(400);
368 $col->set_resizable(TRUE
);
369 $col->set_sort_column_id($i);
371 foreach my $i (HIDDEN_COLS
) {
372 $list->get_column($i)->set_visible(FALSE
);
374 $list->set_search_column(C_NAME
);
375 $list->get_selection->set_mode('single');
376 $list->get_selection->unselect_all;
377 # Initially sort by name (couldn't find a cleaner way)
378 $list->get_column(C_NAME
)->signal_emit('clicked');
380 # If we used Gtk2::TreeView instead of Gtk2::SimpleList we could
381 # show all user ids directly in the list, but we make it simple
382 # for us and instead show them in the tooltip.
383 $list->set_has_tooltip(TRUE
);
384 $list->signal_connect('query-tooltip' => sub {
385 my ($wx, $wy, $tooltip) = ($_[1], $_[2], $_[4]);
386 my ($x, $y) = $list->convert_widget_to_bin_window_coords($wx, $wy);
387 my $path = $list->get_path_at_pos($x, $y);
388 return FALSE
unless defined $path;
389 my $row = ($path->get_indices)[0];
391 join(" ", (${$list->{data
}}[$row][C_FINGERPRINT
] =~ m/..../g));
392 my $fingerprint_label = $encoding->decode(gettext
("Fingerprint:"));
393 my $uids = "${$list->{data}}[$row][C_USERIDS]";
394 my $uids_label = $encoding->decode(
395 ngettext
("User ID:", "User IDs:", ($uids =~ tr/\n//) + 1));
396 my $text = sprintf("%s\n%s\n%s\n%s", $uids_label, $uids,
397 $fingerprint_label, $fingerprint);
398 $tooltip->set_text("$text");
402 $list->signal_connect('row-activated' => sub {
403 # Since we use 'single' selection mode, there can only be one
404 my $index = ($list->get_selected_indices)[0];
405 my $old_val = $list->{data
}->[$index]->[C_SELECT
];
406 $list->{data
}->[$index]->[C_SELECT
] = !$old_val;
409 push @
{$list->{data
}},
410 grep { $_ } map { create_key_row
($_) } @
{$pub_keys_ref};
417 sub make_priv_key_combo
{
418 my $priv_keys_ref = shift;
420 my $list_store = Gtk2
::ListStore
->new(
421 qw
/Glib::String Glib::String Glib::String Glib::String/);
422 my $iter = $list_store->append;
423 $list_store->set ($iter,
424 COMBO_NAME
, $encoding->decode(gettext
(
425 "None (Don't sign)")),
427 COMBO_FINGERPRINT
, "",
429 $iter = $list_store->append;
430 $list_store->set ($iter,
433 COMBO_FINGERPRINT
, "",
434 COMBO_ROLE
, "separator");
435 foreach my $key (@
{$priv_keys_ref}) {
436 my $row = create_key_row
($key);
437 next unless $row; # skip keys without signing capability
438 $iter = $list_store->append;
439 $list_store->set ($iter,
440 COMBO_NAME
, "$row->[C_NAME]",
441 COMBO_KEYID
, "($row->[C_KEYID])",
442 COMBO_FINGERPRINT
, "$row->[C_FINGERPRINT]",
446 my $sorted_list = Gtk2
::TreeModelSort
->new_with_model($list_store);
447 $sorted_list->set_default_sort_func(sub {
448 my ($model, $iter1, $iter2, $data) = @_;
449 my $name1 = $model->get($iter1, COMBO_NAME
);
450 my $name2 = $model->get($iter2, COMBO_NAME
);
451 my $role1 = $model->get($iter1, COMBO_ROLE
);
452 my $role2 = $model->get($iter2, COMBO_ROLE
);
454 if ($role1 eq "none") {
456 } elsif ($role2 eq "none") {
458 } elsif ($role1 eq "separator") {
460 } elsif ($role2 eq "separator") {
463 return (lc $name1 cmp lc $name2);
467 my $combo = Gtk2
::ComboBox
->new_with_model($sorted_list);
468 my $renderer = Gtk2
::CellRendererText
->new();
469 $combo->pack_start($renderer, FALSE
);
470 $combo->add_attribute($renderer, 'text', COMBO_NAME
);
471 $renderer = Gtk2
::CellRendererText
->new();
472 $combo->pack_start($renderer, FALSE
);
473 $combo->add_attribute($renderer, 'text', COMBO_KEYID
);
474 $combo->set_row_separator_func( sub {
475 my ($model, $iter, $data) = @_;
476 return TRUE
if ($model->get($iter, COMBO_ROLE
) eq "separator");
478 $combo->set_active(0);
484 my $priv_keys_ref = shift;
485 my $pub_keys_ref = shift;
487 my $pub_key_label = Gtk2
::Label
->new(
488 $encoding->decode(gettext
("Select recipients:")));
490 my $pub_key_list = make_pub_key_list
($pub_keys_ref);
491 my $pub_key_list_scroll = Gtk2
::ScrolledWindow
->new;
492 $pub_key_list_scroll->set_policy('automatic', 'always');
493 $pub_key_list_scroll->add($pub_key_list);
495 my $hide_recipients_checkbox = Gtk2
::CheckButton
->new(
496 $encoding->decode(gettext
("Hide recipients")));
497 $hide_recipients_checkbox->set_has_tooltip(TRUE
);
498 $hide_recipients_checkbox->set_tooltip_text(
499 $encoding->decode(gettext
("Hide the user IDs of all recipients of " .
500 "an encrypted message. Otherwise anyone " .
501 "that sees the encrypted message can see " .
502 "who the recipients are.")));
504 my $priv_key_label = Gtk2
::Label
->new(
505 $encoding->decode(gettext
("Sign message as:")));
507 my $priv_key_combo = make_priv_key_combo
($priv_keys_ref);
509 my $dialog = Gtk2
::Dialog
->new($encoding->decode(gettext
("Choose keys")),
510 $main_window, 'destroy-with-parent',
511 'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok' );
512 $dialog->set_default_size(650,500);
513 $dialog->set_default_response('ok');
514 my $vbox = $dialog->get_content_area;
515 $vbox->pack_start($pub_key_label, FALSE
, FALSE
, 5);
516 $vbox->pack_start($pub_key_list_scroll, TRUE
, TRUE
, 0);
517 my $hbox = Gtk2
::HBox
->new;
518 $hbox->pack_start($priv_key_label, FALSE
, FALSE
, 0);
519 $hbox->pack_start($priv_key_combo, TRUE
, TRUE
, 0);
520 $vbox->pack_start($hbox, FALSE
, FALSE
, 5);
521 $vbox->pack_start($hide_recipients_checkbox, FALSE
, FALSE
, 0);
523 $pub_key_list->grab_focus;
526 $dialog->signal_connect('key-press-event' => sub {
528 return unless $event->keyval == $Gtk2::Gdk
::Keysyms
{Return
};
529 $dialog->response('ok');
533 while ($dialog->run eq 'ok') {
536 my $always_trust = 0;
538 # Get signing key, if any
539 my $priv_key_combo_model = $priv_key_combo->get_model;
540 my $priv_key_iter = $priv_key_combo->get_active_iter;
541 $signer = $priv_key_combo_model->get($priv_key_iter, COMBO_FINGERPRINT
);
543 # Get public keys, if any
544 my @list_selection = grep { $_->[C_SELECT
] } @
{$pub_key_list->{data
}};
545 if (@list_selection) {
546 my @unauth = grep { ! $_->[C_TRUSTED
] } @list_selection;
548 my $title = $encoding->decode(gettext
(
549 "Do you trust these keys?"
551 my $warning = $encoding->decode(ngettext
(
552 "The following selected key is not fully trusted:",
553 "The following selected keys are not fully trusted:",
556 my $msg = sprintf("%s\n", $warning);
557 foreach my $key (@unauth) {
558 # Each key will be listed RTL *or* LTR depending on the
559 # direction of the first character of the name. This
560 # unfortunately causes mixing of LTR and RTL in the
561 # same list. A potential FIXME would be to display this
562 # with a custom windows using SimpleList for the keys.
563 # Also note that everything in $key (which originates
564 # from $pub_key_list) already has been decoded, so we
565 # don't have to do it again here.
566 my $key_name = "$key->[C_NAME] ($key->[C_KEYID])";
567 $msg = sprintf("%s%s\n", $msg, $key_name);
569 my $question = $encoding->decode(ngettext
(
570 "Do you trust this key enough to use it anyway?",
571 "Do you trust these keys enough to use them anyway?",
574 $msg = sprintf("%s%s", $msg, $question);
575 next unless display_question
($dialog, $title, $msg);
578 @recipients = map { $_->[C_FINGERPRINT
] } @list_selection;
581 if (!@recipients && !$signer) {
582 display_error
($dialog,
583 $encoding->decode(gettext
("No keys selected")),
584 $encoding->decode(gettext
(
585 "You must select a private key to sign the " .
586 "message, or some public keys to encrypt the " .
594 always_trust
=> $always_trust,
595 hide_recipients
=> $hide_recipients_checkbox->get_active,
597 recipients
=> \
@recipients,
606 my $handles = $args->{handles
};
608 my @priv_keys = $gnupg->get_secret_keys_light();
609 my @pub_keys = $gnupg->get_public_keys_light();
611 if (@priv_keys == 0 && @pub_keys == 0) {
612 display_error
($main_window,
613 $encoding->decode(gettext
("No keys available")),
614 $encoding->decode(gettext
(
615 "You need a private key to sign messages or a " .
616 "public key to encrypt messages."
621 my $chosen = choose_keys
(\
@priv_keys, \
@pub_keys);
622 my $signer = $chosen->{signer
};
623 my $recipients_ref = $chosen->{recipients
};
624 my @recipients = @
{$recipients_ref} if defined $recipients_ref;
625 my $always_trust = $chosen->{always_trust
};
626 my $hide_recipients = $chosen->{hide_recipients
};
628 $gnupg->options->always_trust($always_trust);
629 $gnupg->options->clear_extra_args;
630 $gnupg->options->clear_meta_signing_key_id;
631 $gnupg->options->clear_recipients();
634 $gnupg->options->meta_signing_key_id($signer);
638 if ($hide_recipients) {
639 # Since gpg's --no-throw-keyids seems to be broken (it doesn't
640 # work via the CLI either) we can't just push it to extra_args :/.
641 foreach my $recipient (@recipients) {
642 $gnupg->options->push_extra_args('--hidden-recipient',
646 $gnupg->options->push_recipients(@recipients);
652 if ($signer && !@recipients) {
653 $result = $gnupg->clearsign(handles
=> $handles);
654 } elsif (@recipients && !$signer) {
655 $result = $gnupg->encrypt(handles
=> $handles);
656 } elsif ($signer && @recipients) {
657 $result = $gnupg->sign_and_encrypt(handles
=> $handles);
660 $gnupg->options->always_trust(0);
661 $gnupg->options->clear_extra_args;
662 $gnupg->options->clear_meta_signing_key_id;
663 $gnupg->options->clear_recipients();
668 sub symmetric_encrypt
{
670 my $handles = $args->{handles
};
672 return $gnupg->encrypt_symmetrically(handles
=> $handles);
677 my $handles = $args->{handles
};
678 my $input = $args->{input
};
680 my $text_type = detect_text_type
($input);
682 $text_type eq 'message'
683 ?
$gnupg->decrypt(handles
=> $handles)
684 : $gnupg->verify(handles
=> $handles);
687 sub gpg_operate_on_text
{
688 my $operation = shift;
691 $gnupg->options->hash_init(%gnupg_options);
692 my $in_h = IO
::Handle
->new();
693 my $err_h = IO
::Handle
->new();
694 my $out_h = IO
::Handle
->new();
695 my $handles = GnuPG
::Handles
->new(
709 my $pid = $operation->($args) or return;
711 # We assume the sender/recipient uses the same charset as us :/
712 # PGP/MIME was invented for a reason.
713 print $in_h $encoding->encode($text);
716 my ($err, $out) = read_err_out
($err_h, $out_h);
717 my @raw_stderr = @
{$err};
718 my @raw_stdout = @
{$out};
720 waitpid $pid, 0; # Clean up the finished GnuPG process.
722 my $std_err = $encoding->decode(join('', @raw_stderr));
723 my $std_out = $encoding->decode(join('', @raw_stdout));
725 if ($CHILD_ERROR == 0) {
726 if ($operation eq \
&decrypt_verify
) {
728 if ($text =~ m/$pgp_signed_msg->{header}/) {
730 $msg =~ s/^.*$pgp_signed_msg->{header}\nHash: [^\n]*\n\n//m;
731 $msg =~ s/^$pgp_signed_msg->{middle}.*//ms;
735 display_output
($msg, $std_err);
737 set_clipboards_text
($std_out);
743 $encoding->decode(gettext
("GnuPG error")),
744 $std_out . "\n\n" . $std_err
752 sub operate_on_clipboard
{
753 my $operation = shift;
754 my $valid_types = shift;
756 my ($text, $clip_error) = get_validated_clipboard_text
(
757 { valid_types
=> $valid_types }
760 if (defined $clip_error) {
763 $clip_error, # already translated and decoded
764 $encoding->decode(gettext
("Therefore the operation cannot be " .
770 gpg_operate_on_text
($operation, $text);
778 my $dialog = Gtk2
::MessageDialog
->new(
779 $parent, 'destroy-with-parent', 'error', 'ok',
782 $dialog->format_secondary_text($msg);
783 $dialog->signal_connect(
784 response
=> sub { my $self = shift; $self->destroy; }
786 $dialog->set_position('center');
793 sub display_question
{
798 my $dialog = Gtk2
::MessageDialog
->new(
799 $parent, 'destroy-with-parent', 'question', 'yes-no', $title);
800 $dialog->format_secondary_text($msg);
801 $dialog->set_position('center');
802 my $answer = $dialog->run;
804 return $answer eq 'yes' ? TRUE
: FALSE
;
807 # FIXME: let window grow depending on output text size
812 my $dialog = Gtk2
::MessageDialog
->new(
813 $main_window, 'destroy-with-parent', 'info', 'ok',
814 $encoding->decode(gettext
("GnuPG results"))
816 my $my_width_request = 800;
817 my $my_height_request = 600;
818 # TRANSLATORS: GnuPG stdout (encrypted or decrypted message)
819 $dialog->format_secondary_text(sprintf($encoding->decode(gettext
(
823 my $msg_area = $dialog->get_content_area;
825 my $outbuf = Gtk2
::TextBuffer
->new();
826 $outbuf->set_text($std_out);
827 my $text_desc = Pango
::FontDescription
->new;
828 $text_desc->set_family('Monospace');
829 my $textview_out = Gtk2
::TextView
->new_with_buffer($outbuf);
830 $textview_out->set_editable(FALSE
);
831 $textview_out->set_cursor_visible(FALSE
);
832 $textview_out->set_left_margin(10);
833 $textview_out->set_right_margin(10);
834 $textview_out->set_wrap_mode('word');
835 $textview_out->modify_font($text_desc);
836 my $scrolled_win_out = Gtk2
::ScrolledWindow
->new;
837 $scrolled_win_out->set_policy('automatic', 'automatic');
838 $scrolled_win_out->add($textview_out);
839 $msg_area->pack_start($scrolled_win_out, TRUE
, TRUE
, 0);
841 if (defined $std_err && length($std_err)) {
842 my $std_err_title = Gtk2
::Label
->new(
843 # TRANSLATORS: GnuPG stderr (other informational messages)
844 $encoding->decode(gettext
(
845 "Other messages provided by GnuPG:"
847 $std_err_title->set_alignment(0, 0);
848 $std_err_title->set_padding(10, 0);
849 $msg_area->pack_start($std_err_title, FALSE
, FALSE
, 0);
850 my $std_err_buf = Gtk2
::TextBuffer
->new();
851 $std_err_buf->set_text($std_err);
852 my $textview_err = Gtk2
::TextView
->new_with_buffer($std_err_buf);
853 $textview_err->set_editable(FALSE
);
854 $textview_err->set_cursor_visible(FALSE
);
855 $textview_err->set_left_margin(10);
856 $textview_err->set_right_margin(10);
857 $textview_err->set_wrap_mode('word');
858 $textview_err->modify_font($text_desc);
859 my $scrolled_win_err = Gtk2
::ScrolledWindow
->new;
860 $scrolled_win_err->set_policy('automatic', 'automatic');
861 $scrolled_win_err->add($textview_err);
862 $scrolled_win_err->set_size_request(-1, $my_height_request/5);
863 $msg_area->pack_start($scrolled_win_err, FALSE
, FALSE
, 0);
866 $dialog->signal_connect(
867 response
=> sub { my $self = shift; $self->destroy; }
869 my $screen_width = $dialog->get_screen()->get_width();
870 my $screen_height = $dialog->get_screen()->get_height();
871 if ($screen_width > $my_width_request || $screen_height > $my_height_request) {
872 $dialog->set_size_request($my_width_request, $my_height_request);
876 $dialog->set_resizable(TRUE
);
877 $dialog->set_position('center');
883 # Read stdout and stderr at the same time, one line at a time, to
884 # avoid dead-locking due to one of the buffers being full.
893 my $err_l = <$err_h>;
894 my $out_l = <$out_h>;
895 push @
{$err}, $err_l if defined $err_l;
896 push @
{$out}, $out_l if defined $out_l;
897 last unless ($err_l || $out_l);
906 my $text_type = shift;
908 $statusicon->set_from_stock("gpgApplet-${text_type}");
911 sub detect_received
{
912 my $clipboard = shift;
914 update_icon
(detect_text_type
(get_validated_clipboard_text
()));
917 sub handle_clipboard_owner_change
{
918 my $clipboard = shift;
920 # Each time the applet is used, we receive an owner-change signal for an
921 # empty PRIMARY clipboard. We don't want to swich from a valid clipboard to
923 my $content = $clipboard->wait_for_text;
924 if (defined $content && length $content) {
925 set_freshest_clipboard
($clipboard);
927 detect_received
($clipboard);
930 sub make_icon_source
{
936 my $filename = "/usr/share/pixmaps/gpgApplet/$base/$icon.$ext";
937 my $source = Gtk2
::IconSource
->new();
938 $source->set_filename($filename);
939 $source->set_direction_wildcarded(1);
940 $source->set_state_wildcarded(1);
942 $source->set_size_wildcarded(0);
943 $source->set_size($size);
945 $source->set_size_wildcarded(1);
951 sub init_icons_stock
{
954 $factory->add_default;
955 my @stock_ids = map { "gpgApplet-$_" } qw{ message none signed text
};
957 foreach my $stock_id (@stock_ids) {
958 my $iconset = Gtk2
::IconSet
->new();
959 $iconset->add_source(make_icon_source
($stock_id, "22x22", "png", 'button'));
960 $iconset->add_source(make_icon_source
($stock_id, "22x22", "png", 'menu'));
961 $iconset->add_source(make_icon_source
($stock_id, "22x22", "png", 'large-toolbar'));
962 $iconset->add_source(make_icon_source
($stock_id, "22x22", "png", 'small-toolbar'));
963 $iconset->add_source(make_icon_source
($stock_id, "48x48", "png", 'dialog'));
964 $iconset->add_source(make_icon_source
($stock_id, "scalable", "svg"));
965 $factory->add($stock_id, $iconset);