3 class MainWindow : Window
5 private const int WINDOW_WIDTH = 400;
6 private const int WINDOW_HEIGHT = 500;
8 private UIManager ui_manager = new UIManager();
9 private Entry search_entry;
10 private VBox vbox_right;
11 private CustomVBox custom_vbox;
12 private VBox services_internal_vbox;
14 private Entry username_entry;
15 private Entry password_entry;
17 private ListStore listmodel;
18 private TreeModelFilter filter;
20 public IdentityManagerModel identities_manager;
21 private SList<IdCard> candidates;
23 // private MoonshotServer ipc_server;
25 private IdCard default_id_card;
26 public Queue<IdentityRequest> request_queue;
28 private HashTable<Gtk.Button, string> service_button_map;
40 private const string layout =
41 "<menubar name='MenuBar'>" +
42 " <menu name='FileMenu' action='FileMenuAction'>" +
43 " <menuitem name='AddIdCard' action='AddIdCardAction' />" +
45 " <menuitem name='Quit' action='QuitAction' />" +
48 " <menu name='HelpMenu' action='HelpMenuAction'>" +
49 " <menuitem name='About' action='AboutAction' />" +
55 request_queue = new Queue<IdentityRequest>();
57 service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
59 this.title = "Moonshoot";
60 this.set_position (WindowPosition.CENTER);
61 set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
64 setup_identities_list();
70 public void add_candidate (IdCard idcard)
72 candidates.append (idcard);
75 private bool visible_func (TreeModel model, TreeIter iter)
80 Columns.IDCARD_COL, out id_card);
85 if (candidates != null)
87 bool is_candidate = false;
88 foreach (IdCard candidate in candidates)
90 if (candidate == id_card)
97 string entry_text = search_entry.get_text ();
98 if (entry_text == null || entry_text == "")
103 foreach (string search_text in entry_text.split(" "))
105 if (search_text == "")
109 string search_text_casefold = search_text.casefold ();
111 if (id_card.issuer != null)
113 string issuer_casefold = id_card.issuer;
115 if (issuer_casefold.contains (search_text_casefold))
119 if (id_card.display_name != null)
121 string display_name_casefold = id_card.display_name.casefold ();
123 if (display_name_casefold.contains (search_text_casefold))
127 if (id_card.services.length > 0)
129 foreach (string service in id_card.services)
131 string service_casefold = service.casefold ();
133 if (service_casefold.contains (search_text_casefold))
141 private void setup_identities_list ()
143 this.listmodel = new ListStore (Columns.N_COLUMNS, typeof (IdCard),
148 this.filter = new TreeModelFilter (listmodel, null);
150 filter.set_visible_func (visible_func);
153 private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
155 if (pos == EntryIconPosition.PRIMARY)
157 print ("Search entry icon pressed\n");
161 this.search_entry.set_text ("");
165 private void search_entry_text_changed_cb ()
167 this.filter.refilter ();
168 redraw_id_card_widgets ();
170 var has_text = this.search_entry.get_text_length () > 0;
171 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
172 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
174 this.vbox_right.set_visible (false);
177 private bool search_entry_key_press_event_cb (Gdk.EventKey e)
179 if(Gdk.keyval_name(e.keyval) == "Escape")
180 this.search_entry.set_text("");
182 // Continue processing this event, since the
183 // text entry functionality needs to see it too.
187 private void load_id_cards ()
189 // identities_manager = new IdentityManagerModel();
191 if (identities_manager.id_card_list == null)
194 foreach (IdCard id_card in identities_manager.id_card_list)
196 add_id_card_data (id_card);
197 add_id_card_widget (id_card);
200 this.default_id_card = identities_manager.id_card_list.data;
203 private void fill_details (IdCardWidget id_card_widget)
205 var id_card = id_card_widget.id_card;
206 this.username_entry.set_text (id_card.username);
207 this.password_entry.set_text (id_card.password ?? "");
209 var children = this.services_internal_vbox.get_children ();
210 foreach (var hbox in children)
212 fill_services_vbox (id_card_widget.id_card);
213 identities_manager.store_id_cards();
216 private void show_details (IdCard id_card)
218 this.vbox_right.set_visible (!vbox_right.get_visible ());
220 if (this.vbox_right.get_visible () == false)
222 this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
226 private void details_identity_cb (IdCardWidget id_card_widget)
228 fill_details (id_card_widget);
229 show_details (id_card_widget.id_card);
232 private IdCard get_id_card_data (AddIdentityDialog dialog)
234 var id_card = new IdCard ();
236 id_card.display_name = dialog.display_name;
237 id_card.issuer = dialog.issuer;
238 if (id_card.issuer == "")
239 id_card.issuer = "Issuer";
240 id_card.username = dialog.username;
241 id_card.password = dialog.password;
242 id_card.services = {};
243 id_card.set_data("pixbuf", find_icon ("avatar-default", 48));
248 private void add_id_card_data (IdCard id_card)
253 this.listmodel.append (out iter);
254 pixbuf = id_card.get_data("pixbuf");
256 Columns.IDCARD_COL, id_card,
257 Columns.LOGO_COL, pixbuf,
258 Columns.ISSUER_COL, id_card.issuer,
259 Columns.USERNAME_COL, id_card.username,
260 Columns.PASSWORD_COL, id_card.password);
263 private void remove_id_card_data (IdCard id_card)
268 if (listmodel.get_iter_first (out iter))
273 Columns.ISSUER_COL, out issuer);
275 if (id_card.issuer == issuer)
277 listmodel.remove (iter);
281 while (listmodel.iter_next (ref iter));
285 private void add_id_card_widget (IdCard id_card)
287 var id_card_widget = new IdCardWidget (id_card);
289 this.custom_vbox.add_id_card_widget (id_card_widget);
291 id_card_widget.details_id.connect (details_identity_cb);
292 id_card_widget.remove_id.connect (remove_identity_cb);
293 id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
294 id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
295 id_card_widget.expanded.connect (fill_details);
298 /* This method finds a valid display name */
299 public bool display_name_is_valid (string name,
300 out string? candidate)
302 foreach (IdCard id_card in identities_manager.id_card_list)
304 if (id_card.display_name == name)
306 if (&candidate != null)
308 for (int i=0; i<1000; i++)
310 string tmp = "%s %d".printf (name, i);
311 if (display_name_is_valid (tmp, null))
325 public void insert_id_card (IdCard id_card)
329 if (!display_name_is_valid (id_card.display_name, out candidate))
331 id_card.display_name = candidate;
334 this.identities_manager.id_card_list.prepend (id_card);
335 this.identities_manager.store_id_cards ();
337 add_id_card_data (id_card);
338 add_id_card_widget (id_card);
341 public bool add_identity (IdCard id_card)
343 /* TODO: Check if display name already exists */
345 var dialog = new Gtk.MessageDialog (this,
346 Gtk.DialogFlags.DESTROY_WITH_PARENT,
347 Gtk.MessageType.QUESTION,
348 Gtk.ButtonsType.YES_NO,
349 _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
350 id_card.display_name);
353 var ret = dialog.run ();
356 if (ret == Gtk.ResponseType.YES) {
357 id_card.set_data ("pixbuf", find_icon ("avatar-default", 48));
358 this.insert_id_card (id_card);
365 private void add_identity_manual_cb ()
367 var dialog = new AddIdentityDialog ();
368 var result = dialog.run ();
371 case ResponseType.OK:
372 insert_id_card (get_id_card_data (dialog));
380 private void remove_id_card_widget (IdCardWidget id_card_widget)
382 remove_id_card_data (id_card_widget.id_card);
384 this.custom_vbox.remove_id_card_widget (id_card_widget);
387 private void remove_identity (IdCardWidget id_card_widget)
389 var id_card = id_card_widget.id_card;
391 this.identities_manager.id_card_list.remove (id_card);
392 this.identities_manager.store_id_cards ();
394 remove_id_card_widget (id_card_widget);
397 private void redraw_id_card_widgets ()
402 var children = this.custom_vbox.get_children ();
403 foreach (var id_card_widget in children)
404 id_card_widget.destroy();
406 if (filter.get_iter_first (out iter))
411 Columns.IDCARD_COL, out id_card);
413 add_id_card_widget (id_card);
415 while (filter.iter_next (ref iter));
419 private void remove_identity_cb (IdCardWidget id_card_widget)
421 var id_card = id_card_widget.id_card;
423 var dialog = new MessageDialog (null,
424 DialogFlags.DESTROY_WITH_PARENT,
426 Gtk.ButtonsType.YES_NO,
427 _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
428 var result = dialog.run ();
430 case ResponseType.YES:
431 remove_identity (id_card_widget);
439 public void select_identity (IdentityRequest request)
441 IdCard identity = null;
443 this.request_queue.push_tail (request);
445 if (custom_vbox.current_idcard != null &&
446 custom_vbox.current_idcard.send_button != null)
447 custom_vbox.current_idcard.send_button.set_sensitive (true);
449 if (request.select_default)
451 identity = default_id_card;
454 if (identity == null)
456 bool has_nai = request.nai != null && request.nai != "";
457 bool has_srv = request.service != null && request.service != "";
458 bool confirm = false;
459 IdCard nai_provided = null;
461 foreach (IdCard id in identities_manager.id_card_list)
463 /* If NAI matches we add id card to the candidate list */
464 if (has_nai && request.nai == id.nai)
471 /* If any service matches we add id card to the candidate list */
474 foreach (string srv in id.services)
476 if (request.service == srv)
485 /* If more than one candidate we dissasociate service from all ids */
486 if (has_srv && candidates.length() > 1)
488 foreach (IdCard id in candidates)
491 SList<string> services_list = null;
492 bool has_service = false;
494 foreach (string srv in id.services)
496 if (srv == request.service)
501 services_list.append (srv);
507 if (services_list.length () == 0)
513 string[] services = new string[services_list.length ()];
514 foreach (string srv in services_list)
520 id.services = services;
524 identities_manager.store_id_cards ();
526 /* If there are no candidates we use the service matching rules */
527 if (candidates.length () == 0)
529 foreach (IdCard id in identities_manager.id_card_list)
531 foreach (Rule rule in id.rules)
533 if (!match_service_pattern (request.service, rule.pattern))
536 candidates.append (id);
538 if (rule.always_confirm == "true")
544 if (candidates.length () > 1)
546 if (has_nai && nai_provided != null)
548 identity = nai_provided;
555 identity = candidates.nth_data (0);
557 /* TODO: If candidate list empty return fail */
562 redraw_id_card_widgets ();
567 // Send back the identity (we can't directly run the
568 // callback because we may be being called from a 'yield')
569 Idle.add (() => { send_identity_cb (identity); return false; });
573 private bool match_service_pattern (string service, string pattern)
575 var pspec = new PatternSpec (pattern);
576 return pspec.match_string (service);
579 public void send_identity_cb (IdCard identity)
581 return_if_fail (request_queue.length > 0);
583 var request = this.request_queue.pop_head ();
584 bool reset_password = false;
586 if (request.service != null && request.service != "")
588 string[] services = new string[identity.services.length + 1];
590 for (int i = 0; i < identity.services.length; i++)
591 services[i] = identity.services[i];
593 services[identity.services.length] = request.service;
595 identity.services = services;
597 identities_manager.store_id_cards();
600 if (identity.password == null)
602 var dialog = new AddPasswordDialog ();
603 var result = dialog.run ();
606 case ResponseType.OK:
607 identity.password = dialog.password;
608 reset_password = ! dialog.remember;
618 if (this.request_queue.is_empty())
621 if (identity != null)
622 this.default_id_card = identity;
624 request.return_identity (identity);
627 identity.password = null;
632 private void label_make_bold (Label label)
634 var font_desc = new Pango.FontDescription ();
636 font_desc.set_weight (Pango.Weight.BOLD);
638 /* This will only affect the weight of the font, the rest is
639 * from the current state of the widget, which comes from the
640 * theme or user prefs, since the font desc only has the
641 * weight flag turned on.
643 label.modify_font (font_desc);
646 private void fill_services_vbox (IdCard id_card)
649 var n_columns = id_card.services.length;
651 var services_table = new Table (n_columns, 2, false);
652 services_table.set_col_spacings (10);
653 services_table.set_row_spacings (10);
654 this.services_internal_vbox.add (services_table);
656 service_button_map.remove_all ();
658 foreach (string service in id_card.services)
660 var label = new Label (service);
661 label.set_alignment (0, (float) 0.5);
663 var remove_button = new Button.from_stock (Stock.REMOVE);
665 var remove_button = new Button.from_stock (STOCK_REMOVE);
669 service_button_map.insert (remove_button, service);
671 remove_button.clicked.connect ((remove_button) =>
673 var dialog = new Gtk.MessageDialog (this,
674 Gtk.DialogFlags.DESTROY_WITH_PARENT,
675 Gtk.MessageType.QUESTION,
676 Gtk.ButtonsType.YES_NO,
677 _("Are you sure you want to stop '%s' ID Card to use %s?"),
678 custom_vbox.current_idcard.id_card.display_name);
679 var ret = dialog.run();
682 if (ret == Gtk.ResponseType.YES)
684 IdCard idcard = custom_vbox.current_idcard.id_card;
685 var candidate = service_button_map.lookup (remove_button);
687 SList<string> services = new SList<string>();
689 foreach (string srv in idcard.services)
691 if (srv == candidate)
693 services.append (srv);
696 idcard.services = new string[services.length()];
697 for (int j=0; j<idcard.services.length; j++)
699 idcard.services[j] = services.nth_data(j);
702 var children = services_internal_vbox.get_children ();
703 foreach (var hbox in children)
706 fill_services_vbox (idcard);
707 custom_vbox.current_idcard.update_id_card_label ();
711 services_table.attach_defaults (label, 0, 1, i, i+1);
712 services_table.attach_defaults (remove_button, 1, 2, i, i+1);
715 this.services_internal_vbox.show_all ();
718 private void on_about_action ()
721 "Javier Jardón <jjardon@codethink.co.uk>",
722 "Sam Thursfield <samthursfield@codethink.co.uk>",
723 "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
727 string copyright = "Copyright 2011 JANET";
731 Copyright (c) 2011, JANET(UK)
734 Redistribution and use in source and binary forms, with or without
735 modification, are permitted provided that the following conditions
738 1. Redistributions of source code must retain the above copyright
739 notice, this list of conditions and the following disclaimer.
741 2. Redistributions in binary form must reproduce the above copyright
742 notice, this list of conditions and the following disclaimer in the
743 documentation and/or other materials provided with the distribution.
745 3. Neither the name of JANET(UK) nor the names of its contributors
746 may be used to endorse or promote products derived from this software
747 without specific prior written permission.
749 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"
750 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
751 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
752 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
753 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
754 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
755 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
756 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
757 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
758 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
762 Gtk.show_about_dialog (this,
763 "comments", _("Moonshot project UI"),
764 "copyright", copyright,
765 "website", "http://www.project-moonshot.org/",
767 "website-label", _("Visit the Moonshot project web site"),
769 "translator-credits", _("translator-credits"),
774 private Gtk.ActionEntry[] create_actions() {
775 Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
777 Gtk.ActionEntry filemenu = { "FileMenuAction",
782 Gtk.ActionEntry add = { "AddIdCardAction",
790 N_("Add a new ID Card"),
791 add_identity_manual_cb };
793 Gtk.ActionEntry quit = { "QuitAction",
801 N_("Quit the application"),
805 Gtk.ActionEntry helpmenu = { "HelpMenuAction",
810 Gtk.ActionEntry about = { "AboutAction",
818 N_("About this application"),
826 private void create_ui_manager ()
828 Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
829 action_group.add_actions (create_actions (), this);
830 ui_manager.insert_action_group (action_group, 0);
833 ui_manager.add_ui_from_string (layout, -1);
837 stderr.printf ("%s\n", e.message);
839 ui_manager.ensure_update ();
842 private void build_ui()
844 create_ui_manager ();
846 this.search_entry = new Entry();
848 set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
849 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
850 find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
851 this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
852 _("Search identity or service"));
853 this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
855 this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
856 find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
857 this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
858 _("Clear the current search"));
859 this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
862 this.search_entry.icon_press.connect (search_entry_icon_press_cb);
863 this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
864 this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
866 this.custom_vbox = new CustomVBox (this, false, 6);
868 var viewport = new Viewport (null, null);
869 viewport.set_border_width (6);
870 viewport.set_shadow_type (ShadowType.NONE);
871 viewport.add (custom_vbox);
872 var scroll = new ScrolledWindow (null, null);
873 scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
874 scroll.set_shadow_type (ShadowType.IN);
875 scroll.add_with_viewport (viewport);
877 var vbox_left = new VBox (false, 0);
878 vbox_left.pack_start (search_entry, false, false, 6);
879 vbox_left.pack_start (scroll, true, true, 0);
880 vbox_left.set_size_request (WINDOW_WIDTH, 0);
882 var login_vbox_title = new Label (_("Login: "));
883 label_make_bold (login_vbox_title);
884 login_vbox_title.set_alignment (0, (float) 0.5);
885 var username_label = new Label (_("Username:"));
886 username_label.set_alignment (1, (float) 0.5);
887 this.username_entry = new Entry ();
888 var password_label = new Label (_("Password:"));
889 password_label.set_alignment (1, (float) 0.5);
890 this.password_entry = new Entry ();
891 password_entry.set_invisible_char ('*');
892 password_entry.set_visibility (false);
893 var remember_checkbutton = new CheckButton.with_label (_("Remember password"));
894 var login_table = new Table (3, 3, false);
895 login_table.set_col_spacings (10);
896 login_table.set_row_spacings (10);
897 login_table.attach_defaults (username_label, 0, 1, 0, 1);
898 login_table.attach_defaults (username_entry, 1, 2, 0, 1);
899 login_table.attach_defaults (password_label, 0, 1, 1, 2);
900 login_table.attach_defaults (password_entry, 1, 2, 1, 2);
901 login_table.attach_defaults (remember_checkbutton, 1, 2, 2, 3);
902 var login_vbox_alignment = new Alignment (0, 0, 0, 0);
903 login_vbox_alignment.set_padding (0, 0, 12, 0);
904 login_vbox_alignment.add (login_table);
905 var login_vbox = new VBox (false, 6);
906 login_vbox.pack_start (login_vbox_title, false, true, 0);
907 login_vbox.pack_start (login_vbox_alignment, false, true, 0);
909 var services_vbox_title = new Label (_("Services:"));
910 label_make_bold (services_vbox_title);
911 services_vbox_title.set_alignment (0, (float) 0.5);
912 var services_vbox_alignment = new Alignment (0, 0, 0, 0);
913 services_vbox_alignment.set_padding (0, 0, 12, 0);
914 this.services_internal_vbox = new VBox (true, 6);
915 services_vbox_alignment.add (services_internal_vbox);
916 var services_vbox = new VBox (false, 6);
917 services_vbox.pack_start (services_vbox_title, false, true, 0);
918 services_vbox.pack_start (services_vbox_alignment, false, true, 0);
920 this.vbox_right = new VBox (false, 18);
921 vbox_right.pack_start (login_vbox, false, true, 0);
922 vbox_right.pack_start (services_vbox, false, true, 0);
924 var hbox = new HBox (false, 12);
925 hbox.pack_start (vbox_left, true, true, 0);
926 hbox.pack_start (vbox_right, false, false, 0);
928 var main_vbox = new VBox (false, 0);
929 main_vbox.set_border_width (12);
930 var menubar = this.ui_manager.get_widget ("/MenuBar");
931 main_vbox.pack_start (menubar, false, false, 0);
932 main_vbox.pack_start (hbox, true, true, 0);
935 main_vbox.show_all();
936 this.vbox_right.hide ();
939 private void set_atk_name_description (Widget widget, string name, string description)
941 var atk_widget = widget.get_accessible ();
943 atk_widget.set_name (name);
944 atk_widget.set_description (description);
947 private void connect_signals()
949 this.destroy.connect (Gtk.main_quit);
953 private void init_ipc_server ()
955 // Errors will currently be sent via g_log - ie. to an
956 // obtrusive message box, on Windows
958 this.ipc_server = MoonshotServer.get_instance ();
959 MoonshotServer.start (this);
962 private void init_ipc_server ()
965 var conn = DBus.Bus.get (DBus.BusType.SESSION);
966 dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
967 "/org/freedesktop/DBus",
968 "org.freedesktop.DBus");
970 // try to register service in session bus
971 uint reply = bus.request_name ("org.janet.Moonshot", (uint) 0);
972 assert (reply == DBus.RequestNameReply.PRIMARY_OWNER);
974 this.ipc_server = new MoonshotServer (this);
975 conn.register_object ("/org/janet/moonshot", ipc_server);
979 stderr.printf ("%s\n", e.message);
983 private void bus_acquired_cb (DBusConnection conn)
986 conn.register_object ("/org/janet/moonshot", ipc_server);
990 stderr.printf ("%s\n", e.message);
994 private void init_ipc_server ()
996 this.ipc_server = new MoonshotServer (this);
997 GLib.Bus.own_name (GLib.BusType.SESSION,
998 "org.janet.Moonshot",
999 GLib.BusNameOwnerFlags.NONE,
1003 error ("Couldn't own name %s on DBus.", name);